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
- Lexer: Converts the query string into a stream of tokens (e.g.,
MATCH, LPAREN, IDENT("p")). - Parser: Consumes tokens to build an Abstract Syntax Tree (AST) representing the query structure.
- 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. - Physical Planner: Translates the logical plan into specific execution strategies, such as
IndexScan $\rightarrow$ FilterIterator $\rightarrow$ ProjectIterator. - Executor: Executes the iterators, applying filters and projecting properties to produce the final result set.
- 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()