Repository navigation
perf(parser): elide redundant static edge type filters - #2559
linhongyu510 wants to merge 5 commits into
Conversation
Add an EXPLAIN regression that expects a statically typed relationship predicate to produce the same filter-free plan as the equivalent MATCH without the redundant type check.\n\nRefs apache#2481
Preserve non-redundant predicates and avoid mutating the original OPTIONAL MATCH WHERE tree. Refs apache#2481
MuhammadTahaNaveed
left a comment
There was a problem hiding this comment.
Reviewed-by: GPT-6-Astra xhigh
43 regression tests pass on PostgreSQL 18.3, but the default-label case below returns incorrect results.
| strcmp(rel->name, variable_name) != 0) | ||
| continue; | ||
|
|
||
| if (rel->varlen != NULL || rel->parsed_label == NULL) |
There was a problem hiding this comment.
Please exclude IS_DEFAULT_LABEL_EDGE(rel->parsed_label) here. _ag_label_edge scans child labels, so this predicate isn't redundant. This returns "P" on the PR and no rows on master:
CREATE EXTENSION IF NOT EXISTS age;
LOAD 'age';
SET search_path = ag_catalog, public;
SELECT create_graph('type_filter_test');
SELECT * FROM cypher('type_filter_test', $$
CREATE ()-[:P]->()
$$) AS (v agtype);
SELECT * FROM cypher('type_filter_test', $$
MATCH ()-[r:_ag_label_edge]->()
WHERE type(r) = '_ag_label_edge'
RETURN type(r)
$$) AS (edge_type agtype);Please cover this case in regress/sql/expr.sql.
| { | ||
| Node *arg = lfirst(cell); | ||
|
|
||
| if (!is_redundant_edge_type_qual(pattern, arg)) |
There was a problem hiding this comment.
Non-blocking: could this simplify nested AND expressions too? WHERE r.v > 0 AND (type(r) = 'P' AND r.v < 4) keeps the type filter, while the equivalent flat expression removes it. Reusing this helper recursively would make parentheses behave consistently.
The default label edge (_ag_label_edge) scans child labels, so a `type(r) = '_ag_label_edge'` predicate is not redundant with the pattern edge. Exclude it from elision so the _ag_label_edge regression case returns rows consistently. Address review from MuhammadTahaNaveed on apache#2559.
The default edge label (_ag_label_edge) scans every child edge label, so a `type(r) = '_ag_label_edge'` predicate is not redundant with the pattern edge. No edge is ever stored with this synthetic label, so the filtered result must stay empty. This captures the case requested in review on apache#2559. Before the parser fix this returned the child edge label ("P"); after it, the result is empty (0 rows).
|
Thanks for the careful review — and for the exact reproduction. I verified the default-label case against a local build. Root causeIn Fix
Reproduction (your exact SQL)CREATE EXTENSION IF NOT EXISTS age;
LOAD 'age';
SET search_path = ag_catalog, public;
SELECT create_graph('type_filter_test');
SELECT * FROM cypher('type_filter_test', $$ CREATE ()-[:P]->() $$) AS (v agtype);
SELECT * FROM cypher('type_filter_test', $$
MATCH ()-[r:_ag_label_edge]->()
WHERE type(r) = '_ag_label_edge'
RETURN type(r)
$$) AS (edge_type agtype);Built AGE against local PostgreSQL 18.6 (
Regression coverageAdded the default-label case to
Happy to adjust the test placement or wording. Could you re-review at head Note: this diagnosis, the local reproduction/build, and the regression-test addition were performed with AI assistance; all results above are from measured local |
|
Verified the default-label edge fix end-to-end in a clean Docker environment (real Environment
Baseline (pre-fix (1 of 42 tests failed: Head Note on a separate flaky test |
|
@linhongyu510 what do you think of this #2559 (comment)? |
|
Re: the non-blocking question on discussion_r4251546518 — Good catch; I agree the asymmetry is real. Today the helper only inspects the top-level AND conjunction, so a My view: making it recursion-aware is the right fix. A This is a separate enhancement from the default-label-edge bug this PR fixes ( 🤖 AI-assisted (Claude Code); assessment only, no code pushed for this suggestion. |
@linhongyu510 Better to add it as part of this PR |
A predicate is redundant with the edge's
static label whenever it sits in an AND-conjunct already implied by the
pattern edge, regardless of parenthesization. Previously only the
top-level AND args were inspected, so the semantically flat
r.v > 0 AND type(r) = 'P' AND r.v < 4
elided the predicate while its parenthesized twin
r.v > 0 AND (type(r) = 'P' AND r.v < 4)
kept it -- a parentheses-dependent asymmetry.
simplify_redundant_edge_type_qual now recurses into nested AND_EXPR
groups, while deliberately stopping at OR_EXPR/NOT_EXPR boundaries
(crossing an OR branch would be over-elision). A flag tracks
elision that happens inside a nested group so the outer conjunction is
rebuilt even when its own arg count is unchanged.
Regress cases added to expr.sql: nested-AND count, nested-AND EXPLAIN
(shows type() elided, siblings preserved), and an OR-group guard.
|
Follow-up to the review comment on the nested-AND asymmetry — the recursion-aware elision is now pushed as a subsequent commit on top of the original blocking-label fix (new head The original bug-fix diff is unchanged and still minimal.
Verification:
Could you take another look? Happy to adjust the recursion boundary or split commits if preferred. 针对上一轮 review 提到的嵌套 AND 不对称问题,递归版本的冗余 edge type() 消除已作为后续提交推到原 blocking-label 修复之上(新 head Implementation and verification were AI-assisted; I reviewed the AST walk, the diff, and the regression output before pushing.(实现与验证有 AI 辅助,我复核后再推送。) |
Summary
type(r) = "<label>"when the same fixed-length relationship variable is already constrained byMATCH ... [r:<label>]OPTIONAL MATCHWHERE treeCorrectness boundary
The rewrite is intentionally conservative. It only handles a direct equality whose left side is
type(<single relationship variable>)and whose right side is the exact static relationship label. It does not rewrite OR expressions, unlabeled relationships, variable-length relationships, mismatched labels, or ambiguous repeated bindings.Verification
master: the EXPLAIN regression failed because thePrelationship scan still contained_label_name(...) = "P"expr,cypher_match, andcypher_vlepassedBenchmark
20,000
(:X)-[:P]->(:Y)relationships, 12 alternating query pairs in an isolated PostgreSQL 18.6 container:type(r)medianmasterAll 24 measured queries returned
20000. This demonstrates removal of the redundant runtime filter in this environment; it does not claim universal absolute latency.Closes #2481
Implementation and verification were AI-assisted. I reviewed the AST constraints, final diff, regression output, and benchmark results before submission.