Review — CommitFest 61, Patch 7034: Rewrite undirected edge patterns in GRAPH_TABLE using UNION ALL
report · generated from canonical Markdown
report
postgresql-master
commitfest-7034-review2026-08-04T09:41:42.527954+00:00
Front matter
schema: review-report-v2 commitfest: 61 patch: 7034 title: Rewrite undirected edge patterns in GRAPH_TABLE using UNION ALL author: Ayoub Kazar canonical_message_id: c172f9dd5704b5d32c1f573f6ac33566@data-bene.io head: 7cb9060dcde08ca63df1f0d701b0674e0e8ae811 diff_sha256: c461ea8269cd39c9618c36bbf516a5c3dc03639f321cc27a2fadbae2cf406d39 scope_identity: working-tree-diff@7cb9060dcde08ca63df1f0d701b0674e0e8ae811/sha256:c461ea8269cd39c9618c36bbf516a5c3dc03639f321cc27a2fadbae2cf406d39 verdict: needs-review blocker_split: verified: 0 unverified: 0
Review — CommitFest 61, Patch 7034
Rewrite undirected edge patterns in GRAPH_TABLE using UNION ALL Author: Ayoub Kazar · pgsql-hackers thread 651853
Target
| Field | Value |
|---|---|
| Kind | working-tree diff (uncommitted) |
| HEAD | 7cb9060dcde08ca63df1f0d701b0674e0e8ae811 |
| Base | HEAD |
| Diff SHA-256 | c461ea8269cd39c9618c36bbf516a5c3dc03639f321cc27a2fadbae2cf406d39 |
| File | src/backend/rewrite/rewriteGraphTable.c (+310 / -26) |
| Changed functions | generate_query_for_graph_path (modified), generate_undirected_edge_union_query (added), build_edge_self_loop_filter (added) |
| Subsystem | rewrite (GRAPH_TABLE / SQL-PGQ) |
Deterministic verdict
PG-state verdict: needs-review Generic verdict: changes-requested Blocker split: verified 0 / unverified 0 (no BLOCKER findings)
Rationale
Two verified MAJOR findings require author attention before commit:
- F1 — no regression test exercises the
NOT(srckey = destkey)self-loop dedup filter via the UNION ALL path. The proposed TG1 test fills this gap and passed a two-run gate. - F2 —
generate_undirected_edge_union_queryhand-builds aQuery/SetOperationStmttree bypassingtransformSetOperationStmt, relying on untestedpull_up_simple_union_allinvariants. EXPLAIN confirms pull-up fires currently; the proposed TG3 EXPLAIN test guards the plan shape.
One cannot-verify MAJOR is a live, unresolved design objection for the committer:
- F3 — reviewer Ashutosh Bapat (2026-07-30, thread 651853) argues OR-expansion belongs in the planner, not an unconditional rewriter rewrite ("we should not always rewrite the undirected edge as UNION as the patch does"). The author has not obtained agreement. This is an architectural decision; a committer must weigh it.
Four verified MINOR robustness/style fixes (F4 Assert→elog, F5 operator-validity Assert, F6 composite-key swap Assert, F7 redundant perminfoindex=0) are quick consistency improvements.
Specialist seats and model provenance
| Seat | Handoff report slug | markdown_sha256 |
|---|---|---|
| pg-patch-reviewer | commitfest-7034-review-handoff-patch-reviewer | 9f6848fd992d0c8bfc15b1f7c0652afc63ce46c2b917b57180f1681e151848d0 |
| pg-security-reviewer | commitfest-7034-review-handoff-security-reviewer | 49d8328963b0aee82c748722f19d1af6f08fba5e4497d9bf44db82154b0c5be4 |
| pg-finding-verifier | commitfest-7034-review-handoff-verifier | fb12d10eb89e5e9ed10a7118b179529ea07b618d00629cf6d715e4b046577193 |
| pg-test-author | commitfest-7034-review-handoff-test-author | db2b3528a40d8e0315ab9fe5229d8e26f56859135bbac0d8e108e5cf05a5f80a |
| pg-change-coverage-reviewer | commitfest-7034-20260804T090229Z-review-handoff-coverage | e78cf57894112ea641f7b7fb0ad25b89866667485b8469e0ae50e775d7d9bb2f |
| thread ledger | commitfest-7034-review-thread-ledger | dc84f205125513a157779e61fb90d6a7cac2d7ecc33cab735846a35e14c0e17 |
Model: glm-5.2:cloud (user-fixed for this run). Router policy v1, hash 997c035512fa1b35aa17149ea386a861af531ec05839ce06810f38268a3caebe. Recommended tier for finding-verifier and coverage-reviewer: sonnet (claude-sonnet-5); overridden by user to glm-5.2:cloud.
Findings
[MAJOR, tests] — No test exercises the self-loop dedup filter via the UNION ALL path
- Location:
src/test/regress/sql/graph_table.sql:536-537 - Mechanism: test-coverage-gap
- Description: The new
build_edge_self_loop_filter()(NOT(srckey = destkey)on the backward branch) is only invoked whenuse_union_all = true(distinct path factors,EDGE_PATTERN_ANY, same vertex table). Existing tests hituse_union_allong4/ptnebut contain no self-loop edge (src = dest); the only self-loop edge in the suite (e3_3at line 399) is exercised via the same-path-factor case which skips UNION ALL. A regression that doubled or dropped self-loops would not be caught. - Verification:
verified(two-run gate). Patched EXPLAIN showsFilter: (src_id <> dest_id)in the backward branch; self-loopv33|e331|v33appears once; 77 rows match baseline. - Repair: Add TG1 test (INSERT non-self-loop edge, run undirected
(a)-[c]-(b)query, EXPLAIN confirming pull-up). Patch sha256a1f1474e311e22396e3accffbb0803055e2fafdd5a81ecd042ae0855e384f53b, bound to HEAD7cb9060dcde. Validation: passed (apply-check + compile + targeted-test + two-run).
[MAJOR, correctness] — Hand-built Query/SetOperationStmt bypasses transformSetOperationStmt
- Location:
src/backend/rewrite/rewriteGraphTable.c:630-797 - Mechanism: architecture-risk
- Description:
generate_undirected_edge_union_queryconstructs the UNION ALLQueryby hand rather than viatransformSetOperationStmt. The resulting structure must satisfy every invariantis_simple_union_all/pull_up_simple_union_allexpect. A silent pull-up failure would regress performance with no warning. The author's v2 thread post shows pull-up succeeding, but no regression test asserts the plan shape. - Verification:
verified(EXPLAIN plan shape). Pull-up fires:e3_3appears as Seq Scan + Subquery Scan with Filter (not an opaque SubqueryScan). The code-quality concern (fragile invariants) remains; TG3 EXPLAIN test guards it. - Repair: Add TG3 EXPLAIN test (included in the F1 patch). Patch sha256
a1f1474e311e22396e3accffbb0803055e2fafdd5a81ecd042ae0855e384f53b. Validation: passed (apply-check + compile + targeted-test).
[MAJOR, design] — Unconditional rewrite vs. cost-based OR expansion (live objection)
- Location:
src/backend/rewrite/rewriteGraphTable.c:479-486 - Mechanism: design-objection
- Description: The patch unconditionally emits UNION ALL. On-list, Ashutosh Bapat (2026-07-30): "we should not always rewrite the undirected edge as UNION as the patch does" — suggesting cost-based OR-expansion in the planner. The author argues the graph case is special; no agreement reached.
- Verification:
cannot-verify(design decision for committer). Thread ledger confirms the objection is live and unresolved. - Repair: waived (design-choice). A code patch cannot resolve a design disagreement. Evidence needed: committer judgment on whether the unconditional rewrite is acceptable or whether cost-based OR-expansion in the planner is required.
[MINOR, robustness] — Assert vs elog for array-size checks
- Location:
src/backend/rewrite/rewriteGraphTable.c:671,677 - Mechanism: error-handling
- Description:
Assert(n1 == n3)andAssert(n1 == n2)instead ofelog(ERROR, ...)as in siblingbuild_edge_vertex_link_quals:1478-1481. Assert is compiled out in non-cassert builds; catalog corruption would cause silent buffer overrun. - Verification:
verified(inspection). Sibling function useselog(ERROR). - Repair: Replace Asserts with
if (n1 != n3) elog(ERROR, ...)/if (n1 != n2) elog(ERROR, ...). Patch sha25668817457e0ff7a92af488edeeb20481390e961c6775d4c20e6587310cec54824. Validation: passed (compile + regression identical).
[MINOR, robustness] — Missing operator-validity Assert
- Location:
src/backend/rewrite/rewriteGraphTable.c:826-848 - Mechanism: robustness
- Description:
build_edge_self_loop_filterdoes not assert the operator is a binary boolean-returning equality operator, unlikebuild_edge_vertex_link_quals:1510-1513. - Verification:
verified(inspection). - Repair: Add
Assert(opform->oprkind == 'b' && RegProcedureIsValid(opform->oprcode) && opform->oprresult == BOOLOID && !get_func_retset(opform->oprcode)). Included in F4/F5/F6 combined patch68817457e0ff7a92af488edeeb20481390e961c6775d4c20e6587310cec54824. Validation: passed.
[MINOR, correctness] — Composite-key swap last-match-wins ambiguity
- Location:
src/backend/rewrite/rewriteGraphTable.c:756-763 - Mechanism: edge-case
- Description: The
bwd_attnumswap loop does not guard against a column appearing in bothsrc_key_attnumsanddest_key_attnums(last match wins). For valid property graph definitions this never happens, but there is no Assert. - Verification:
verified(inspection). - Repair: Add
Assert(src_key_attnums[k] != dest_key_attnums[k])at the top of the swap loop. Included in F4/F5/F6 combined patch. Validation: passed.
[MINOR, style] — Redundant perminfoindex=0 for RTE_SUBQUERY
- Location:
src/backend/rewrite/rewriteGraphTable.c:539 - Mechanism: style
- Description:
pni->p_rte->perminfoindex = 0is a no-op;addRangeTableEntryForSubqueryalready leaves it at the makeNode default of 0. - Verification:
verified(inspection). - Repair: Optional cleanup — remove the line. waived (design-choice, cosmetic). Validation: passed (static-check).
Verification Summary
| Finding | Severity | Status | Method |
|---|---|---|---|
| F1 | MAJOR | verified | two-run (regression SQL with oracle) |
| F2 | MAJOR | verified | targeted-test (EXPLAIN plan shape) |
| F3 | MAJOR | cannot-verify | inspection (thread ledger) |
| F4 | MINOR | verified | static-check (inspection) |
| F5 | MINOR | verified | static-check (inspection) |
| F6 | MINOR | verified | static-check (inspection) |
| F7 | MINOR | verified | static-check (inspection) |
Counts: verified = 6, cannot-verify = 1, refuted = 0. Verified MAJOR = 2 (F1, F2), cannot-verify MAJOR = 1 (F3), verified MINOR = 4 (F4-F7).
Repair Validation Summary
| Finding | Patch sha256 | Validation | Methods |
|---|---|---|---|
| F1 | a1f1474e...4f53b | passed | apply-check, compile, targeted-test, two-run |
| F2 | a1f1474e...4f53b | passed | apply-check, compile, targeted-test, two-run |
| F3 | waived | waived | design-choice |
| F4 | 68817457e0...4824 | passed | apply-check, compile, targeted-test |
| F5 | 68817457e0...4824 | passed | apply-check, compile, targeted-test |
| F6 | 68817457e0...4824 | passed | apply-check, compile, targeted-test |
| F7 | waived | passed | static-check |
A build lane was available: a configured source tree (HEAD 7cb9060dcde, --enable-cassert --enable-debug) was built and a disposable cluster initialized. pg_regress create_property_graph graph_table passed against both patched and baseline trees. Candidate edits stayed in disposable scratch; no modifications to the review worktree.
Changed-Code Coverage
Coverage requirement: required (changed instrumentable path: src/backend/rewrite/rewriteGraphTable.c)
Tests executed (qualify the coverage)
pg_regress create_property_graph graph_table— EXIT 0. TAP:ok 1 - create_property_graph (563 ms),ok 2 - graph_table (795 ms). All 2 tests passed.- TG4 composite-key undirected query (gt7034_cg, 2-column keys, self-loop ceSelf) — proposed test, confirms multi-key AND_EXPR branch (line 852) and composite-key swap loop.
Build manifest
- Build type: VPATH autoconf, disposable build directory (not the review worktree)
- Configure:
--enable-cassert --enable-debug, CFLAGS-g -O0 --coverage -fprofile-arcs -ftest-coverage - Compiler: gcc 15.2.0; gcov 15.2.0
- Cluster: disposable
initdb --no-locale -E UTF8, disposable port, stopped before gcov - Execution lane: host-disposable (passed)
gcov epoch (fresh, integrity-checked)
- Runs: 47. No stamp mismatch, merge mismatch, or profiling errors.
integrity_errors: [] - Whole-file lines: 557. Lines executed: 96.95% (graph_table only) / 97.13% (graph_table + TG4)
Raw metrics
| Metric | Count |
|---|---|
| Changed lines total | 310 |
| Changed non-executable (structural) | 161 |
| Changed executable lines | 149 |
| Covered changed lines (graph_table + TG4) | 142 |
| Covered changed lines (graph_table only) | 141 |
| Uncovered changed lines | 7 |
| Integrity errors | 0 |
Uncovered changed lines (all infeasible-with-reason)
| Lines | Function | Mechanism | Classification | Reason |
|---|---|---|---|---|
| 658 | generate_undirected_edge_union_query | error-path (cache lookup) | infeasible-with-reason | Requires corrupt/missing pg_propgraph_element row; DDL writes atomically at CREATE PROPERTY GRAPH |
| 732,734,735,737,738 | generate_undirected_edge_union_query | dropped-column branch | infeasible-with-reason | Requires edge table with DROP COLUMN'd column in UNION ALL undirected pattern; no existing fixture; low-value defensive code (v2 motivation) |
| 828 | build_edge_self_loop_filter | error-path (cache lookup) | infeasible-with-reason | Requires invalid operator OID in pgesrceqop; DDL validates at CREATE PROPERTY GRAPH |
Line 852 (multi-key AND_EXPR branch): covered by proposed TG4 (count = 1). Coverable-now under the current-tree-only (graph_table regression without TG4) assumption.
Coverability disposition
Terminal status: complete-with-explicit-infeasible-waivers. No coverable-now uncovered region remains under the "graph_table + TG4" test set. No COV-* finding emitted.
Touched functions
| Function | Kind | Coverage note |
|---|---|---|
| generate_undirected_edge_union_query | added | entry count=3; uncovered lines 658, 732-738 (all infeasible) |
| build_edge_self_loop_filter | added | entry count=3; uncovered line 828 (infeasible); line 852 covered by TG4 |
| generate_query_for_graph_path | modified | all changed executable lines covered |
Test examples
- TG4 (covers line 852): composite-key undirected self-loop test with 2-column keys. Exercises the multi-key
AND_EXPRbranch and composite-key swap loop. See pg-test-author handoff for full SQL.
Residual limitations
- Branch coverage not separately measured via
gcovr --decisions; line-level gcov gate is authoritative. - TG4 is a proposed test not yet in the tree; the 142-covered figure includes TG4, the 141-covered figure is current-tree-only.
- Dropped-column branch (732-738) waived as infeasible-with-reason; a concrete test skeleton is available if promoted.
pgsql-hackers thread ledger
- Schema: pg-review-thread-ledger/v1
- Status: reconciled
- Thread: 651853 (6 messages, 2 participants: Ayoub Kazar author, Ashutosh Bapat reviewer)
- Ledger report:
commitfest-7034-review-thread-ledger - Canonical Message-ID:
c172f9dd5704b5d32c1f573f6ac33566@data-bene.io
Per-finding thread dispositions
| Finding | Disposition | Thread reference |
|---|---|---|
| F1 | already-raised | Ashutosh (2026-07-16) requested multi-edge undirected tests |
| F2 | author-deferred | Author showed EXPLAIN pull-up in v2 post; no regression test added |
| F3 | unreconciled | Live objection from Ashutosh (2026-07-30); no agreement |
| F4-F7 | new | Not discussed on thread |
Development-branch catversion exception: working-tree diff on master; no catalog/storage format change; no catversion bump required.
Aggregated Test Plan
| Test | Vehicle | Files | Status |
|---|---|---|---|
| TG1: self-loop dedup filter | regress | graph_table.sql, graph_table.out | passed (two-run) |
| TG3: EXPLAIN pull-up confirmation | regress | graph_table.sql | passed |
| TG4: composite-key undirected | regress | graph_table.sql | passed (proposed) |
| F4/F5/F6: robustness fixes | compile + regress | rewriteGraphTable.c | passed |
Security review
No exploitable security defects found. The patch preserves the current-user SELECT-privilege model (security_invoker-by-default) for property graph edge relations, including through the new UNION ALL subquery: inner edge-relation RTEs carry RTEPermissionInfo with selectedCols marked; the outer RTE_SUBQUERY correctly skips redundant perminfo. No SQL injection, no privilege escalation, no search_path issue, no TOCTOU, no superuser-guard gap. The one MINOR (Assert-only array checks, F4) is a discipline issue with no SQL-reachable exploit path.
Verified clean / non-findings
- Memory context: allocations via
palloc/makeVar/makeNodein the per-query context; no wrong-context growth. table_open/table_closebalance:table_openat 654,table_close(rel, NoLock)at 781; balanced;NoLockcorrect.- SysCache balance:
SearchSysCache1(PROPGRAPHELOID)at 656 released at 686;SearchSysCache1(OPEROID)at 826 released at 847; balanced. - Permission handling: outer
path_queryvar-pull loop correctly skipsRTE_SUBQUERYvars (606-607); inner perminfos handle SELECT privilege. - No catalog/storage format change; no OID, catversion, WAL, or pg_dump impact. No docs change required (performance optimization, user-visible semantics unchanged).
Residual risk
- F3 (design objection) is unresolved on-list; a committer must decide whether the unconditional rewrite is acceptable or whether cost-based OR-expansion in the planner is preferred.
- F2's hand-built Query relies on
pull_up_simple_union_allinvariants that are not documented as a contract; a future planner change could silently break the rewrite. The TG3 EXPLAIN test guards against silent regression. - Small-scale / cold-cache regression: the unconditional rewrite (F3) could regress cases where the OR/BitmapOr plan is cheaper. The author should present a no-regression benchmark at small scale.
Submission review
- Patch format: working-tree diff on
7cb9060dcde; posted v2 (2026-07-27). No catversion bump needed. No docs change needed. - Applies: single-file change; no merge conflicts anticipated. pgindent advisable before commit.
- Release-note relevance: performance improvement for GRAPH_TABLE undirected edge patterns (LSQB SF1 Q6: 2400s timeout → 41s; Q9: timeout → 98s).
Provenance
Coordinator: pg-review-harness (glm-5.2:cloud, user-fixed). Specialist seats via durable Amauta handoff reports (see table above). All handoff reports verified by the coordinator via get_report (sha256 + metadata + frozen scope identity match). Coverage lane executed by the coordinator in a disposable build under the run scratch directory. No nested-task transcript was used as evidence; all specialist artifacts were consumed from persisted Amauta reports.