Skip to content

VLP both-endpoint WHERE filter applied only in base case → cyclic self-paths leak at *1..N (N≥3) #1103

Description

@genezhang

Summary

A WHERE predicate that references both VLP endpoints (e.g. WHERE friend <> root, or its property form WHERE friend.id <> root.id) is placed in the recursive CTE's base case only, never on the recursive arm. On a cyclic graph this lets paths that return to the start node survive past the first hop.

Repro (LDBC SF1, standard schema)

-- Rows where the endpoint IS the root, despite the filter excluding them:
MATCH (root:Person {id: 14})-[:KNOWS*1..3]-(friend:Person)
WHERE friend <> root AND friend.id = 14
RETURN count(*) AS self_leak
  • *1..2self_leak = 0 ✓ (a 2-hop KNOWS path cannot return to root without reusing an edge, which edge-uniqueness already blocks)
  • *1..3self_leak = 52 ✗ (3-hop paths root → a → b → root survive)

The property form WHERE friend.id <> root.id produces the identical SQL and the identical 52-row leak, confirming this is independent of the #1100 bare-node-identity normalization (that fix reaches exact parity with this pre-existing path).

Root cause

In categorize_filters (src/render_plan/filter_pipeline.rs:258-262), a predicate referencing both start and end aliases is routed to start_filters, which the VLP CTE generator applies only in the base case (src/render_plan/cte_manager/mod.rs:1298-1302). The recursive arm re-derives end_node each hop but never re-applies the both-endpoint predicate, so a later hop can land back on the start node.

A both-endpoint identity filter (end != start) is hop-invariant and must be applied on every arm (base + recursive), like the per-hop end filters in #1003/#607.

Impact

  • Only manifests at max_hops ≥ 3 on graphs with cycles (LDBC KNOWS is symmetric → cyclic).
  • LDBC IC9/IC5 use KNOWS*1..2 and are unaffected (verified self_leak = 0).

Acceptance

  • friend <> root on *1..N excludes root at every hop, on a cyclic fixture.
  • Both bare-node and .id property forms fixed together (same seam).
  • Add a cyclic-fixture regression test (an acyclic graph cannot expose this — see the "cyclic oracle for edge-uniqueness" lesson).

Surfaced by the #1100 bare-node-identity fix (#1102) making the bare-alias form executable; the underlying leak predates it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    design-cycleSystemic root cause; needs a design-cycle fix, not a per-shape patch. Excluded from bug count.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions