# How the Cypher Query Engine Executes Read-Only OpenCypher Queries Against the Codebase Graph

> Learn how the Cypher query engine executes read-only OpenCypher queries against your codebase graph using a six-stage pipeline. Discover how it ensures graph immutability.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: internals
- Published: 2026-07-22

---

**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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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.

```c
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 …` becomes `SELECT … FROM … WHERE …`
- Label tests convert to joins against the `labels` table
- `LIMIT`, `ORDER BY`, and `SKIP` clauses map directly to SQL equivalents

This translation occurs in [`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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.

```c
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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`, `CONSTRAINT` definitions
- **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":

```bash
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:

```c
#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:

```c
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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/cypher/cypher.h) | Public API declarations for `cbm_lex`, `cbm_cypher_execute`, and result structures |
| [`src/cypher/cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/mcp/mcp.c) | CLI frontend implementing `query_graph` command and JSON output formatting (lines 415–437) |
| [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) | SQLite store wrapper managing database connections and query execution |
| [`tests/test_cypher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/cypher.c)), blocking `CREATE`, `DELETE`, `SET`, `MERGE`, and other mutating clauses.
- The engine stores codebase graphs in SQLite, converting `MATCH` patterns to `SELECT` statements with joins against the `labels` table.
- 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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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`](https://github.com/DeusData/codebase-memory-mcp/blob/main/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.