Skip to content

query_graph: a variable the WITH clause dropped is still accepted, and its column comes back empty with no error #1919

Description

@CaptainMittens

Version

codebase-memory-mcp dev — built from source at main 997d087b.

Platform

macOS (Apple Silicon)

Install channel

Built from source

Binary variant

standard

What happened, and what did you expect?

A RETURN may name a variable that the WITH before it dropped. The query
runs, exits clean, prints a column for that variable, and fills the column with
empty strings. Nothing reports an error.

Real Cypher refuses this query and names the offending variable. Here the
caller cannot tell a wrong query from a graph that holds no such data.

The empty column is the harmful part. A reader — a person or an agent — sees a
column of nothing and reads it as "the graph has no data here". The true answer
is "your query named something that is out of scope".

Reproduction

The repository's own cypher test fixture reproduces it, so no outside code is
needed. setup_cypher_store() in tests/test_cypher.c builds a store with
three CALLS edges.

  1. Code being indexed: the fixture built by setup_cypher_store() in
    tests/test_cypher.c.

  2. The query:

    MATCH (f:Function)-[:CALLS]->(g) WITH f.name AS caller RETURN caller, g.name
    

    g does not survive the WITH. A plain MATCH does this as readily as an
    OPTIONAL MATCH, so the optional half is not the cause.

  3. Result: three rows. Column g.name is present and every value is "".
    Return code 0. No error text anywhere.

    Expected: a parse or execution error naming g as out of scope.

To see it without writing a test, run the same query through query_graph
against any indexed project, using a variable the WITH did not carry.

Logs

PROBE rc=0 col_count=2 row_count=3
PROBE column[0]="caller"
PROBE column[1]="g.name"
PROBE row[0] = ["HandleOrder"] [""]
PROBE row[1] = ["HandleOrder"] [""]
PROBE row[2] = ["ValidateOrder"] [""]

Captured by a throwaway test against the fixture above, on main 997d087b
plus the one commit in #1918. That commit only touches RETURN *, so it does
not affect this path.

Where it comes from

Nothing checks a projected name against the live scope. An unbound variable
resolves to NULL and renders as "", and that is deliberate:

  • src/cypher/cypher.c:2568return NULL; /* unbound variable */
  • src/cypher/cypher.c:3345 and :3350 — an unmatched OPTIONAL MATCH target
    stays unbound on purpose, and the projection renders it "".

That convention is right for the OPTIONAL MATCH case. Its cost is that a name
which was never carried through looks exactly like an optional row that did not
match, so no error can be raised at projection time.

A search for not in scope, unknown variable, undefined variable and
scope check across src/cypher/cypher.c at 997d087b returns nothing.

Suggested fix

Add one check after parsing, before execution: every variable a RETURN or
WITH item names must be either a pattern variable still in scope, or an alias
the previous WITH created. Report the name that fails.

The check belongs at parse time, which keeps the "" convention for an
unmatched OPTIONAL MATCH target exactly as it is. The check is about names
that were never carried through, not about rows that did not match.

Related

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code — this repository's own test fixture.

Metadata

Metadata

Assignees

No one assigned

    Labels

    cypherCypher query language parser/executor bugsparsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions