How the Cypher Query Engine Executes Read-Only OpenCypher Queries Against the Codebase Graph
The Cypher query engine translates read-only OpenCypher queries into SQLite SQL statements, executing them through a six-stage pipeline that guarantees graph immutability by rejecting write operations during parsing.
The codebase-memory-mcp repository implements a specialized Cypher query engine that enables read-only graph traversal over codebases stored in SQLite. This engine allows developers to query function call graphs and code dependencies using standard OpenCypher syntax while ensuring the underlying graph remains immutable.
Execution Pipeline Overview
The Cypher query engine processes queries through a strict pipeline defined in src/cypher/cypher.c. Each query string undergoes lexical analysis, parsing, planning, SQL generation, execution, and formatting before returning results. The engine explicitly blocks all mutating operations—CREATE, DELETE, SET, MERGE, and similar clauses trigger immediate parser errors (lines 816–838), ensuring only read-only operations reach the SQLite backend.
Stage-by-Stage Execution Flow
1. Lexing and Tokenization
The pipeline begins with cbm_lex, a lexer that tokenizes the raw Cypher query string. This stage identifies keywords, identifiers, literals, and structural symbols while validating basic syntax correctness.
cbm_lex *lex = NULL;
char *error = NULL;
/* Initialize lexer with query string */
if (cbm_lex_new(&lex, "MATCH (n:Function) RETURN n LIMIT 10", &error) != 0) {
fprintf(stderr, "Lex error: %s\n", error);
return;
}
The lexer handles the initial validation before passing tokens to the recursive-descent parser.
2. Parsing and AST Construction
A recursive-descent parser consumes the token stream to build an abstract syntax tree (AST). This stage enforces the read-only grammar subset by explicitly checking for prohibited clauses. Any attempt to use write operations triggers an error message such as "unsupported Cypher feature: CREATE clause (write operations not supported)" before execution begins.
3. Logical Planning
The planner analyzes the AST to construct a query graph describing node and edge traversal patterns. It identifies MATCH patterns, optional paths, label filters (WHERE n:Label), and relationship directions. This logical plan abstracts the Cypher semantics into a traversal structure that the SQL generator can translate.
4. SQL Translation
The logical plan converts to SQLite-compatible SQL statements. The translator maps Cypher constructs to relational operations:
MATCH … WHERE …becomesSELECT … FROM … WHERE …- Label tests convert to joins against the
labelstable LIMIT,ORDER BY, andSKIPclauses map directly to SQL equivalents
This translation occurs in src/cypher/cypher.c (lines 2953–3240), producing optimized queries against the internal schema.
5. SQLite Execution
The generated SQL executes through the store's SQLite connection using sqlite3_prepare_v2 and related APIs. Because the parser already filtered write operations, the executor never issues INSERT, UPDATE, or DELETE statements. Results stream back as rows matching the requested projection.
cbm_result *res = NULL;
cbm_store *store = cbm_store_open("path/to/store.db");
/* Execute the parsed query */
if (cbm_cypher_execute(store, lex, &res, &error) != 0) {
fprintf(stderr, "Execution error: %s\n", error);
return;
}
6. Result Serialization
Raw SQLite rows marshal into JSON format for consumption by the CLI (query_graph command) or C API callers. The src/mcp/mcp.c file (lines 415–437) handles CLI output formatting, returning structured JSON arrays containing matched nodes and edges.
Read-Only Safety Guarantees
The engine implements defense-in-depth to prevent graph mutation. The parser explicitly rejects these Cypher features with dedicated error paths:
- Data modification:
CREATE,DELETE,DETACH DELETE,SET,REMOVE,MERGE - Schema operations:
DROP,CONSTRAINTdefinitions - Procedural extensions:
CALL,YIELD,FOREACH
Even malformed attempts that might otherwise translate to SQL (such as ATTACH) fail during parsing, ensuring the SQLite authorizer never receives mutation commands.
Practical Usage Examples
CLI Query Execution
Query the call graph for functions named "main":
query_graph "MATCH (f:Function)-[e:CALLS]->(g) WHERE f.name = 'main' RETURN f,g"
The CLI routes the string through the six-stage pipeline and prints a JSON array of matching node pairs.
C Library Integration
Embed the engine directly in C applications:
#include "codebase_memory_mcp/cypher.h"
cbm_store *store = cbm_store_open("codebase.db");
cbm_lex *lex = NULL;
char *error = NULL;
if (cbm_lex_new(&lex, "MATCH (n) RETURN n LIMIT 10", &error) == 0) {
cbm_result *res = NULL;
if (cbm_cypher_execute(store, lex, &res, &error) == 0) {
for (size_t i = 0; i < res->row_count; ++i) {
printf("%s\n", res->rows[i].json);
}
}
}
Write Operation Rejection
Attempting mutations returns immediate errors:
char *err = NULL;
cbm_lex_new(&lex, "CREATE (n:Node {name: 'test'})", &err);
/* err contains: "unsupported Cypher feature: CREATE clause (write operations not supported)" */
Key Source Files and Architecture
| File | Role |
|---|---|
src/cypher/cypher.h |
Public API declarations for cbm_lex, cbm_cypher_execute, and result structures |
src/cypher/cypher.c |
Core implementation including lexer (lines 0–30), parser error handling (lines 816–838), query-graph construction (lines 1148–2211), SQL generation (lines 2953–3240), and execution loop (lines 322–350) |
src/mcp/mcp.c |
CLI frontend implementing query_graph command and JSON output formatting (lines 415–437) |
src/store/store.c |
SQLite store wrapper managing database connections and query execution |
tests/test_cypher.c |
Test suite validating read-only query execution and write-clause rejection |
Summary
- The Cypher query engine translates OpenCypher to SQLite SQL through a six-stage pipeline (lex, parse, plan, translate, execute, format).
- Read-only guarantees are enforced at the parser level (lines 816–838 in
cypher.c), blockingCREATE,DELETE,SET,MERGE, and other mutating clauses. - The engine stores codebase graphs in SQLite, converting
MATCHpatterns toSELECTstatements with joins against thelabelstable. - Results return as JSON through the CLI (
query_graph) or C API (cbm_cypher_execute). - Direct SQLite execution uses
sqlite3_prepare_v2, ensuring prepared statement safety and performance.
Frequently Asked Questions
What Cypher clauses are blocked by the read-only engine?
The engine rejects CREATE, DELETE, DETACH DELETE, SET, REMOVE, MERGE, CALL, YIELD, FOREACH, DROP, and CONSTRAINT operations. Each blocked clause triggers a specific error message during parsing, preventing any write operations from reaching the SQLite backend.
How does the engine convert Cypher MATCH statements to SQL?
The planner analyzes MATCH patterns to build a query graph, then the SQL generator (lines 2953–3240 in cypher.c) translates these into SELECT statements. Label filters become joins against the labels table, WHERE clauses map to SQL WHERE, and pagination controls (LIMIT, SKIP) translate directly to SQL equivalents.
Can I use the Cypher engine as a standalone library?
Yes. Include src/cypher/cypher.h and link against the library. Initialize lexers with cbm_lex_new(), open stores with cbm_store_open(), and execute queries via cbm_cypher_execute(). The API returns structured results containing JSON-encoded rows suitable for integration into existing tools.
What database does the Cypher engine query against?
The engine queries an internal SQLite store defined in src/store/store.c. The codebase graph persists as relational tables (nodes, edges, labels), and the Cypher engine dynamically generates SQL to traverse these tables without requiring a separate graph database server.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →