sqlite-graph

repository·main·Indexed 42 days ago

https://github.com/agentflare-ai/sqlite-graph

A SQLite extension that adds graph database capabilities, including Cypher query support and graph algorithms, directly into the SQLite engine. It provides SQL functions for node and edge manipulation, pathfinding, and structural analysis. Includes a plugin for sqlite-utils (version 0.1.0a0) to execute Cypher queries and manage graph statistics via the CLI.

Tokens
37.4K
Snippets
119
Records
150
Agent score
53%

What's inside sqlite-graph

  1. Version history and release roadmap

    main

    The project is currently in the Alpha v0.1.0 phase, focusing on testing and feedback.

    Current Version: v0.1.0-alpha.0

    • Core graph operations functional
    • Basic algorithms working
    • Security hardening complete

    Planned Roadmap

    • v0.1.1: Bug fixes and minor improvements
    • v0.2.0: Full Cypher query support
    • v0.3.0: Advanced algorithms
    • v0.4.0: Performance optimization
    • v0.5.0: Advanced features
    • v1.0.0: Production ready
  2. How to create and use a graph in sqlite-graph

    main

    To initialize the graph engine, create a virtual table using the graph() module. This automatically creates backing tables for nodes and edges.

    Initialization:

    CREATE VIRTUAL TABLE graph USING graph()

    Backing Tables: Once created, you can query these tables directly using standard SQL:

    • graph_nodes(id INTEGER, properties TEXT, labels TEXT)
    • graph_edges(id INTEGER, source INTEGER, target INTEGER, edge_type TEXT, properties TEXT)
    CREATE VIRTUAL TABLE graph USING graph();
  3. Core concepts of SQLite Graph

    main

    The extension uses a graph model built on top of SQLite. The primary abstractions are:

    • Nodes: Entities in your graph (e.g., people, places, things).
    • Edges: Relationships between nodes.
    • Labels: Categories assigned to nodes (e.g., Person, Company).
    • Properties: Key-value data stored on both nodes and edges (typically as JSON).
    • Cypher: A graph query language used to perform pattern matching and graph operations.
  4. Use Cypher Query Language for Graph Queries

    main

    The extension supports a subset of the Cypher query language for pattern matching and graph manipulation.

    Common Operations:

    • MATCH: Find patterns (nodes, relationships, or both).
    • CREATE: Create nodes and relationships.
    • MERGE: Ensure a pattern exists (create if missing).
    • SET: Update properties or labels.
    • DETACH DELETE: Delete a node and all its connected relationships.
    • WHERE: Filter results based on properties or pattern existence.
    -- Pattern matching
    MATCH (a:Person)-[:KNOWS]->(b:Person) 
    RETURN a.name, b.name;
    
    -- Create nodes and relationships
    CREATE (a:Person {name: 'Alice'}),
           (b:Person {name: 'Bob'}),
           (a)-[:KNOWS {since: 2020}]->(b);
    
    -- Merge with lifecycle hooks
    MERGE (n:Person {name: 'David'})
    ON CREATE SET n.created = timestamp()
    ON MATCH SET n.updated = timestamp();
    
    -- Delete a node and its relationships
    MATCH (n:Person {name: 'Old User'})
    DETACH DELETE n;
  5. Understand the Create Release workflow behavior

    main

    The "Create Release" GitHub Action workflow performs the following sequence of operations:

    1. Code Checkout: Checks out the repository code.
    2. Version Determination: Resolves the version from the user input or the VERSION file.
    3. Format Validation: Ensures the version follows the X.Y.Z or X.Y.Z-suffix format.
    4. Tag Collision Check: Verifies that the tag does not already exist (the workflow fails if it does).
    5. Branch Validation: Ensures the workflow is running on the main branch (unless skip_branch_validation is enabled).
    6. Tag Creation: Creates an annotated git tag.
    7. Tag Push: Pushes the tag to GitHub.
    8. Trigger Release: Once the tag is pushed, it automatically triggers the separate "Release" workflow, which builds binaries for Ubuntu 22.04 and 24.04 and publishes the GitHub Release.
  6. How memory is managed in sqlite-graph

    main

    The extension integrates deeply with SQLite's memory management to ensure stability and compatibility:

    • Allocation: All memory allocations use SQLite's internal allocators (sqlite3_malloc, sqlite3_free). This allows the extension to respect SQLite's memory accounting, support custom allocators, and adhere to memory limits.
    • Lifecycle Management: Nodes and edges use a reference counting mechanism (graphNodeRetain and graphNodeRelease) to manage their lifecycle.
    • Performance: Memory pools are used for frequently allocated small objects like iterators and execution contexts to reduce fragmentation.
  7. How the SQLite Graph extension architecture works

    main

    The SQLite Graph Database Extension is a loadable extension that integrates graph capabilities into SQLite via the Virtual Table interface. It allows users to execute openCypher queries alongside standard SQL while maintaining SQLite's ACID properties.

    Core Architecture Layers

    1. SQLite Core: The host environment providing the Virtual Table interface.
    2. Graph Extension Entry Point: Handles initialization (sqlite3_graph_init), registration of virtual tables, and SQL functions.
    3. Virtual Table Manager: Bridges SQLite and the graph engine by implementing methods like graphCreate(), graphBestIndex(), graphOpen(), and graphFilter().
    4. Cypher Query Engine: A multi-stage pipeline that processes openCypher queries:
      • Lexer/Parser: Tokenizes strings and builds an Abstract Syntax Tree (AST).
      • Logical Planner: Optimizes the query (e.g., predicate pushdown) and creates a logical plan.
      • Physical Planner: Selects execution strategies (e.g., index scans) and generates iterators.
      • Executor: Runs the physical plan and materializes results.
    5. Graph Storage Layer: Manages in-memory data structures including hash tables for lookups, B-trees for property indexes, and adjacency lists for traversals.
    6. Graph Algorithms: Provides built-in implementations for shortest path (Dijkstra, BFS), centrality (PageRank), and traversals (DFS, BFS).
  8. How data is stored and indexed in sqlite-graph

    main

    The extension uses a hybrid storage model combining hash tables, adjacency lists, and B-trees to manage graph data:

    Node and Edge Storage

    • Nodes: Stored in a hash table for $O(1)$ lookup by ID, with a linked list to allow for iteration.
    • Edges: Stored in hash tables with adjacency lists to facilitate graph traversal. The system maintains both node_outgoing (edges leaving a node) and node_incoming (edges entering a node) lists.

    Indexing

    • Label Index: A B-tree mapping labels to lists of node IDs, optimized for MATCH (n:Label) queries.
    • Property Index: A B-tree mapping (property_key, property_value) pairs to lists of node/edge IDs. These are created automatically for frequently accessed properties.

    Property Storage

    Properties are stored as JSON strings using SQLite's native JSON functions. This allows for:

    • Flexible schemas.
    • Complex nested data structures.
    • Efficient serialization/deserialization.
  9. Handle property projection limitations in RETURN

    main

    While the parser accepts property projection (e.g., RETURN n.property), the current implementation returns the entire node instead of the specific property value.

    Example of current behavior: MATCH (p:Person) WHERE p.age > 25 RETURN p.name returns Node(1) instead of the string value "Alice".

    Workaround: Perform property extraction in your application layer after retrieving the nodes.

    // This currently returns the whole node, not just the name string
    MATCH (p:Person) WHERE p.age > 25 RETURN p.name
  10. How Cypher queries are processed

    main

    When a Cypher query is executed, it passes through a structured pipeline to transform high-level syntax into low-level data retrieval:

    Example Pipeline for: MATCH (p:Person {age: 30}) RETURN p.name

    1. Lexer: Converts the query string into a stream of tokens (e.g., MATCH, LPAREN, IDENT("p")).
    2. Parser: Consumes tokens to build an Abstract Syntax Tree (AST) representing the query structure.
    3. Logical Planner: Analyzes the AST to create an optimized logical plan. For the example above, it might decide to use a label index for Person and push down the age=30 filter.
    4. Physical Planner: Translates the logical plan into specific execution strategies, such as IndexScan $\rightarrow$ FilterIterator $\rightarrow$ ProjectIterator.
    5. Executor: Executes the iterators, applying filters and projecting properties to produce the final result set.
    6. Virtual Table Interface: The results are handed back to SQLite via standard virtual table methods like graphOpen(), graphFilter(), and graphNext().
    1. Lexer
       Input: "MATCH (p:Person {age: 30}) RETURN p.name"
       Output: [MATCH, LPAREN, IDENT("p"), COLON, IDENT("Person"), ...]
    
    2. Parser
       Input: Token stream
       Output: AST with MATCH clause, pattern, RETURN clause
    
    3. Logical Planner
       - Identify pattern: node with label "Person" and property filter
       - Optimization: use label index for "Person"
       - Optimization: push down property filter
       - Generate logical plan: LabelScan(Person) -> Filter(age=30) -> Return(name)
    
    4. Physical Planner
       - Choose index scan strategy
       - Generate iterators: IndexScan -> FilterIterator -> ProjectIterator
    
    5. Executor
       - Open index scan on "Person" label
       - For each matching node:
         - Apply filter: age = 30
         - Project property: name
       - Add to result set
       - Return results to SQLite
    
    6. Virtual Table Interface
       - SQLite calls graphOpen() to create cursor
       - SQLite calls graphFilter() to start iteration
       - SQLite calls graphNext() for each row
       - Results returned via graphColumn()
  11. How to add new graph algorithms to sqlite-graph

    main

    To extend the extension with new graph algorithms, follow these steps:

    1. Implement the algorithm logic in src/graph-algo.c.
    2. Register the algorithm as a SQL function in src/graph.c using sqlite3_create_function.
    3. (Optional) Add Cypher procedure call syntax to support the algorithm via the Cypher language.

    Example of registering a shortest_path function:

    void graphRegisterAlgorithms(sqlite3 *db) {
      sqlite3_create_function(db, "shortest_path", 2,
        SQLITE_UTF8 | SQLITE_DETERMINISTIC,
        NULL, shortestPathFunc, NULL, NULL);
    }
  12. Build sqlite-graph from source

    main

    If you need to build the extension manually, ensure you have the necessary build tools installed and then use make.

    # Install build dependencies
    sudo apt-get update
    sudo apt-get install build-essential sqlite3 libsqlite3-dev
    
    # Clone and build
    git clone https://github.com/agentflare-ai/sqlite-graph.git
    cd sqlite-graph
    make