crprashant opened a new pull request, #2419: URL: https://github.com/apache/age/pull/2419
## Summary Fixes [#2382](https://github.com/apache/age/issues/2382): variable-length relationship patterns with a zero lower bound (`[*0..N]`) failed to produce the zero-hop self-binding when the edge label did not exist in the graph. Per Neo4j/openCypher semantics, a pattern like `(p)-[:FRIEND*0..1]-(f)` must always include the zero-length match where `f` binds to the same node as `p`, regardless of whether any `FRIEND` edge exists. Apache AGE was treating any missing edge label as fatal for the entire MATCH. ### Repro (before this PR) ```sql SELECT * FROM cypher('fuzz_graph', $$ CREATE (:Person {name:'Alice'}) $$) AS (v agtype); SELECT * FROM cypher('fuzz_graph', $$ MATCH (p:Person {name: 'Alice'}) OPTIONAL MATCH (p)-[:FRIEND*0..1]-(f:Person) RETURN p.name AS person, f.name AS friend $$) AS (person agtype, friend agtype); -- Before: Alice | NULL -- After: Alice | Alice (matches Neo4j) ``` ### Root cause Two separate label-validity gates short-circuited the MATCH when an edge label was not in the cache: - `match_check_valid_label()` injected a `WHERE false` (or volatile equivalent) at the MATCH level. - `path_check_valid_label()` made the planner emit `NULL::agtype` constants in place of vertex bindings even when the vertex labels themselves were valid. Neither check distinguished fixed-length patterns (which legitimately require an edge of the label) from VLE patterns with a lower bound of 0 (which don't). There was also a latent runtime bug in `is_an_edge_match()`: when the user requested a label that did not exist, the InvalidOid check fell through the existing "no constraints → match all" fast-path. Once the parser-side gate was relaxed, `[:NOEXIST*0..N]` would have begun traversing arbitrary other-label edges. This PR fixes that path too so the zero-hop case is the only thing that matches. ### Fix 1. **`src/backend/parser/cypher_clause.c`** — new helper `is_zero_lower_bound_vle()` that defensively inspects the `vle()` FuncCall built by `build_VLE_relation()` (in `cypher_gram.y`). Both `match_check_valid_label()` and `path_check_valid_label()` use it to skip the false-where short-circuit only for relationships that are zero-bound VLEs. Mixed patterns where another segment is genuinely unsatisfiable still short-circuit correctly. 2. **`src/backend/utils/adt/age_vle.c`** — `is_an_edge_match()` returns false early when the requested label does not exist. The zero-hop self-binding is generated by `build_VLE_zero_container()` and never calls `is_an_edge_match()`, so this fix does not affect the zero-hop semantics. 3. **`regress/sql/cypher_vle.sql`** — seven regression cases: - plain MATCH `[:NOEXIST*0..1]` → zero-hop only (no leakage onto other-label edges in the graph) - OPTIONAL MATCH form (issue's exact repro shape) - `[*0..0]` → zero-hop only - fixed-length `[:NOEXIST*1..1]` → 0 rows (preserved) - OPTIONAL fixed-length missing → outer row preserved with NULL - mixed pattern `(a)-[:NOEXIST*0..1]-(b)-[:STILL_MISSING]-(c)` → 0 rows - sanity: zero-bound on an existing label (unchanged behaviour) ### Validation - `make installcheck EXTRA_TESTS="pgvector fuzzystrmatch pg_trgm"`: 36/37 pass on PG18. The single failure (`age_upgrade`) is pre-existing on `master` at `54e19fac` and is unrelated to this change. - All 7 new regression cases pass. - `cypher_vle` (which is the suite the new tests live in) passes cleanly. - Manual rebuild + `EXPLAIN (VERBOSE, ANALYZE)` confirms the planner no longer constant-folds `f.name` to `NULL::agtype` and the query returns exactly the expected zero-hop row. ### Notes for reviewers - The helper validates that `rel->varlen` is a `FuncCall` named `"vle"` with at least 5 args before reading `args[3]`; any deviation falls back to the existing short-circuit, so future parser refactors will be safe. - `A_Const` integer inspection follows the same direct field access (`val.ival.type` / `val.ival.ival`) used elsewhere in `cypher_gram.y` (e.g. line 696). - No bison or grammar changes; no new shift/reduce conflicts. Closes #2382. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
