jrgemignani commented on PR #2360:
URL: https://github.com/apache/age/pull/2360#issuecomment-4442461982

   @gregfelice 
   
   From Opus 4.7 -
   
   # Deep review: PR #2360 — Pattern expressions as boolean expressions
   
   I read the PR description and the actual diff (`cypher_gram.y`, Makefile, 
regression test). Status: 3 commits, force-pushed onto current master 
(`84e29542`), Copilot's 3+2+0 rounds resolved, `jrgemignani` approved. Here's 
my analysis.
   
   ## What it does (correctly)
   
   The change is fundamentally sound:
   
   1. **Switches `cypher_gram.y` to `%glr-parser`.** Right call — the `(a)` 
ambiguity (parenthesized expr vs. node pattern) genuinely is unresolvable at 
one-token lookahead. LALR(1) can't see far enough to know whether `(` 
introduces `(1+2)` or `(a)-[:R]->(b)`. GLR is the textbook tool.
   2. **Adds `anonymous_path %dprec 1` to `expr_atom`**, alongside the existing 
`'(' expr ')' %dprec 2`. Bare `(a)` tied to both productions — `%dprec 2 > 
%dprec 1` correctly disambiguates to expression-variable.
   3. **Refactors EXISTS-wrapping into `make_exists_pattern_sublink()`** — and 
re-uses it for both the existing `EXISTS(pattern)` production and the new 
bare-pattern production. The refactor is byte-identical to what the inline code 
did (same `cypher_sub_pattern`, `CSP_EXISTS`, `EXISTS_SUBLINK`, 
`node_to_agtype(..., "boolean", ...)`). Zero semantic change for the existing 
EXISTS path. ✓
   4. **`YYRHSLOC(rhs, 1)` macro fix** — silently correct and necessary. GLR 
has a different rhs stack layout than LALR(1); `(rhs)[1]` indexes the wrong 
slot under GLR. This isn't mentioned in the description but it's a real 
correctness fix the PR depends on.
   5. **Tests cover meaningful surface area** — WHERE, NOT, AND/OR, RETURN 
(alias + positional), mixed projections, CASE WHEN, SET, WITH pipeline, 
backward-compat with `EXISTS(...)`. Good coverage of the new contexts.
   
   ## Concerns worth raising
   
   ### 1. Removing `-Werror` is heavier than needed
   
   The Makefile change is:
   
   ```diff
   - src/backend/parser/cypher_gram.c: BISONFLAGS += --defines=... -Werror
   + src/backend/parser/cypher_gram.c: BISONFLAGS += --defines=... 
-Wno-conflicts-sr -Wno-conflicts-rr
   ```
   
   This drops `-Werror` entirely, not just the two conflict categories. Any 
*other* Bison warning (deprecated `%name-prefix=`, undeclared types, unused 
rules, etc.) will silently be ignored going forward.
   
   **Suggested:** keep `-Werror`, add only the two no-error opts:
   
   ```
   BISONFLAGS += --defines=... -Werror -Wno-error=conflicts-sr 
-Wno-error=conflicts-rr
   ```
   
   Bison 3.4+ supports `-Wno-error=<category>`. Keeps the safety net on 
everything else.
   
   ### 2. No `%expect` budget at all is fragile
   
   The description's argument — "exact counts can drift across Bison versions" 
— is correct. But going to "any number of conflicts is fine" loses the alarm 
bell that catches *new, unintentional* conflicts a future grammar change might 
introduce. A new rule that adds 12 more conflicts would silently slip in.
   
   **Suggested:** `%expect 7 / %expect-rr 3` with a code comment "expected to 
drift on Bison upgrades; update as needed." This is the standard PostgreSQL 
approach in `gram.y` (which has `%expect 0` and updates when needed). The point 
of `%expect` is not to pin a version, it's to fail loudly when *unexpected* 
conflicts appear. If the worry is distro drift, the right answer is a wider 
`%expect` value (e.g., `%expect 12 %expect-rr 5`) — generous enough to absorb 
Bison-version churn, tight enough to flag a new grammar bug.
   
   ### 3. GLR runtime cost on the parse hot path
   
   GLR forks parse stacks at every conflict point. On ambiguous prefixes it 
does several full reductions before discarding losers. AGE parses Cypher on 
every single `cypher()` call, often inside tight per-row contexts (UDF 
arguments, prepared statements re-planned, etc.). I don't see any parse-time 
benchmark in the PR.
   
   Concretely: a `MATCH (a)` query enters the `(` ambiguity at character 7, and 
GLR will explore both `'(' expr ')'` and `anonymous_path` until it sees the 
closing `)` and what follows. For short read-only queries, parser time is 
non-trivial relative to plan+execute.
   
   **Recommended diligence before merge:**
   
   - Microbench: `pgbench -f script.sql -T 30 -c 4` running 1000× of typical 
Cypher query (`MATCH (a:Person {id: $1}) RETURN a`) on LALR build vs. GLR 
build. Look for backend CPU% in parse.
   - Spot-check `EXPLAIN (ANALYZE, BUFFERS) SELECT cypher(...)` planning time, 
before/after.
   
   If parse time regresses meaningfully (say >5%) on simple queries, this is 
worth knowing and documenting, even if it's accepted as the cost of the 
feature. If it's noise (<1%), it's a non-issue.
   
   ### 4. Pattern expression semantics — coverage gaps
   
   The grammar change makes `anonymous_path` legal anywhere an `expr_atom` is. 
That opens the door wider than the tests probe:
   
   - **Single-node labeled patterns in expression position**: `RETURN 
(a:Person)` is, in openCypher, the pattern "does `a` have label `Person`?" — a 
boolean. Under this PR, `(a:Person)` would be parsed by GLR — does it pick the 
`(expr)` branch (which would fail because `a:Person` isn't an expression) or 
the `anonymous_path` branch (which would succeed)? Per `%dprec`, both fork; the 
failing fork is dropped; we get the pattern interpretation. The test suite 
doesn't include this case. Worth adding.
   - **In list/map literals**: `RETURN [(a)-[:R]->(b), (a)-[:S]->(c)]` — list 
of booleans. `RETURN {flag: (a)-[:R]->(b)}` — map containing a boolean. The 
grammar now allows these. Untested.
   - **In function arguments**: `RETURN count((a)-[:R]->(b))`. Should work 
(boolean is countable). Untested.
   - **In OPTIONAL MATCH WHERE**: `OPTIONAL MATCH (a) WHERE (a)-[:R]->(:L)`. 
Untested.
   - **Nested EXISTS**: `WHERE EXISTS((a)-[:R]->(b)) AND (a)-[:S]->(c)`. The 
PR's "mixed" test doesn't combine the two syntaxes in one predicate. Untested.
   
   None of these is a blocker — they're follow-up coverage. But the PR 
description claims "matching what openCypher defines and what Neo4j users 
expect," and these are exactly the cases Neo4j users will try.
   
   ### 5. Performance of the resulting plan
   
   Bare pattern → `EXISTS` sublink → planner builds a correlated subquery. In a 
`RETURN a.name, (a)-[:KNOWS]->(:Person) AS knows` over a million rows, the 
planner has to either:
   
   - (a) Evaluate the EXISTS sublink per row (O(N) sublink evals), or
   - (b) Convert it to a semi-join (PG's `pull_up_sublinks`).
   
   For Cypher, the pattern is a SRF (cypher's own scan), not a SQL subquery the 
planner can semi-join in the usual way. So path (a) is what will happen, and on 
large data it will be slow.
   
   That's the *correct* semantics. But it's worth documenting in the PR or a 
follow-up that pattern-expressions in projection contexts are O(rows × subquery 
cost), distinct from pattern-expressions in `WHERE` (where the planner *might* 
pull it up). Tests use 4 rows — invisible. Real workloads would notice.
   
   ### 6. The `expr_func_subexpr: EXISTS '(' anonymous_path ')'` production 
stays
   
   It's now redundant with `anonymous_path %dprec 1` in `expr_atom`. Both 
produce the same `make_exists_pattern_sublink(...)` output. The grammar will 
accept both spellings. Keeping the explicit form is fine for backward compat 
(users may have written `WHERE EXISTS((...))` for years), but it does add to 
the conflict count. Could be noted in the description as "intentionally 
redundant for backward compatibility."
   
   ### 7. `var_name_opt` `%dprec 1` on both alternatives
   
   ```
   var_name_opt:
       /* empty */ %dprec 1
           { $$ = NULL; }
       | var_name %dprec 1
       ;
   ```
   
   Equal priorities on both alternatives is a no-op for `%dprec`. Bison will 
treat this as "no preference" and discard the failing fork. That's fine, but 
the annotation is misleading — the priorities don't disambiguate, they're 
inert. Either remove them or set unequal values. Minor.
   
   ## Verdict
   
   **Direction: correct.** GLR is the right tool, the refactor preserves 
semantics exactly, the test coverage spans the new contexts, and Copilot's 
review rounds caught the small stuff.
   
   **Items I'd want addressed before merge:**
   
   1. Keep `-Werror`, scope down to `-Wno-error=conflicts-{sr,rr}` rather than 
nuking `-Werror`.
   2. Re-introduce some `%expect` budget (generous, e.g. `%expect 10 %expect-rr 
5`) so future grammar changes that add unintended conflicts get flagged.
   3. Quick parse-time microbench (LALR vs. GLR) on a couple of representative 
read-only Cypher queries. Even a one-line "no measurable regression on `MATCH 
(a:L {p: $1}) RETURN a`" in the PR description would satisfy.
   
   **Items I'd address as a follow-up (not blocking):**
   
   4. Test coverage gaps: `RETURN (a:Label)`, pattern in list/map literals, 
pattern in function args, OPTIONAL MATCH WHERE.
   5. Document the EXISTS-sublink-per-row cost in projection contexts so users 
aren't surprised on large data.
   6. Note the `YYRHSLOC` fix in the PR body — it's not just a stylistic 
change, it's required for GLR correctness.
   
   **Items that are fine as-is:**
   
   7. The `make_exists_pattern_sublink` refactor (semantics preserved 
byte-for-byte).
   8. The conflict explanation block comment (genuinely helpful for future 
grammar maintainers).
   9. The decision to broaden scope from "WHERE only" to "all expression 
contexts" — Copilot was right, the grammar change inherently does this and a 
WHERE-only restriction would have required a parallel grammar branch.
   
   Overall, this is a high-quality feature PR that solves a real 
openCypher-compat gap, and the maintainer approval is well-earned. The Makefile 
`-Werror` removal and absent `%expect` budget are the two items I'd push back 
on; everything else is polish.


-- 
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]

Reply via email to