Skip to content

GFQL Cypher: WITH rebind onto a live alias is unguarded; edge-ident column collision (round-008) #1911

Description

@lmeyerov

Round-008 scoping probe: ~175 query-cases × both engines over 6 fixtures, oracles pre-registered in writing before any code ran, every finding re-verified in a fresh process with different label/type names. Scripts in session scratchpad probe-r8-scope/.

Scoping is strong where it counts — scope narrowing (17 cases) errors clearly on every dropped node/rel/path alias with zero silent re-binding; OPTIONAL cross-clause visibility (11), parameter/alias collision (8), degenerate scopes (9/9), alias-kind confusion (11, declines only), and an internal-column leak audit were all QUIET. Four silent-wrongs survived.

1. SILENT-WRONG (pandas, incl. default engine): WITH X AS Y where Y is already bound returns all-NULL properties.

MATCH (a:P)-[:K]->(b:P) WITH a AS b RETURN b.name

oracle Sa, Sb · pandas None, None · polars correct. The renamed alias takes its ROWS from the source alias but resolves .property against the SHADOWED alias's node table — on disjoint node sets that is 100% data loss. Symmetric in both directions, both dtypes, 3 fixtures, survives ORDER BY. Discriminators: whole-entity RETURN b is correct on both engines (only property access corrupts), and WITH a AS fresh (non-colliding) is a clean typed decline — so the guard exists and is bypassed exactly when the target name is another live alias.

2. SILENT-WRONG (BOTH engines): simultaneous rebind ignored. WITH a AS b, b AS a returns the unswapped values, byte-identical to the no-WITH control (openCypher WITH projections are simultaneous; oracle is the swap). Same with item order reversed. Also WITH a, b WITH a AS b RETURN b.name returns the shadowed original. Note test_ir_query_graph.py:345 shows the IR already models WITH a AS b correctly — the defect is downstream in execution. Cross-kind rebind IS guarded (#1357); same-kind onto a live alias is not.

3. SILENT-WRONG (pandas) + HARD CRASH (polars): user edge column named __gfql_edge_ident__. Introduced by this campaign's trail work. gfql_unified.py:671 _with_edge_identity tests the hardcoded literal instead of using generate_safe_column_name, contrary to the convention lazy/engine/polars/reserved_columns.py mandates:

  • pandas: RETURN r.__gfql_edge_ident__ → nulls (oracle E1, E2); whole-entity RETURN r drops the column entirely.
  • polars: every relationship query on such a graph dies, including ones never mentioning the column — duplicate column name __gfql_edge_ident__ from row_pipeline.py:1775's unconditional with_row_index.
    Node-side __gfql_edge_ident__ and both-side __cypher_group__ are handled correctly; this is edge-ident-specific. Two-line fix with the helper the repo already mandates.

4. Alias named identically to the property it projects returns TrueMATCH (name:P) RETURN name.nameTrue, True. Root cause compute/ast.py:273: assign(**{self._name: True}) overwrites the user column with the alias marker. The node-side case is already pinned by a test asserting cross-engine parity — but that asserted parity is broken in three uncovered variants: rel-alias-equals-edge-column (pandas True / polars typed error), MATCH (id:P) RETURN id.id (pandas raw untyped ValueError / polars True), and a WHERE variant (pandas schema error / polars correct []). Corruption is contained (doesn't spread to other aliases; WHERE runs before tagging).

Lower severity: WITH $x AS x ... WHERE a.name = x leaks the substituted value as a token in the error; a misattributed "Unresolved identifier 'a'" where the fresh alias is actually q; path aliases are never bound at all (MATCH p = ... RETURN p.name → "Unknown Cypher alias 'p'" with a misleading suggestion); cosmetic null/dtype divergences (nan vs None, 5.0 vs 5).

Priority: 1+2 share one root (unguarded same-kind rebind onto a live alias) — either implement true simultaneous rebind or extend the existing guard to reject it, mirroring #1357. 3 is a two-line fix and is a regression this campaign introduced.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions