Review — CommitFest 61, Patch 7034: Rewrite undirected edge patterns in GRAPH_TABLE using UNION ALL

report · generated from canonical Markdown

Type
report
Source
postgresql-master
Slug
commitfest-7034-review
Tags
commitfest-7034graph-tablepatch-reviewpg-review-harnesspostgresqlreview-report-v2
Updated
2026-08-04T09:41:42.527954+00:00
Verdict / Summaryneeds-review
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

FieldValue
Kindworking-tree diff (uncommitted)
HEAD7cb9060dcde08ca63df1f0d701b0674e0e8ae811
BaseHEAD
Diff SHA-256c461ea8269cd39c9618c36bbf516a5c3dc03639f321cc27a2fadbae2cf406d39
Filesrc/backend/rewrite/rewriteGraphTable.c (+310 / -26)
Changed functionsgenerate_query_for_graph_path (modified), generate_undirected_edge_union_query (added), build_edge_self_loop_filter (added)
Subsystemrewrite (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:

One cannot-verify MAJOR is a live, unresolved design objection for the committer:

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

SeatHandoff report slugmarkdown_sha256
pg-patch-reviewercommitfest-7034-review-handoff-patch-reviewer9f6848fd992d0c8bfc15b1f7c0652afc63ce46c2b917b57180f1681e151848d0
pg-security-reviewercommitfest-7034-review-handoff-security-reviewer49d8328963b0aee82c748722f19d1af6f08fba5e4497d9bf44db82154b0c5be4
pg-finding-verifiercommitfest-7034-review-handoff-verifierfb12d10eb89e5e9ed10a7118b179529ea07b618d00629cf6d715e4b046577193
pg-test-authorcommitfest-7034-review-handoff-test-authordb2b3528a40d8e0315ab9fe5229d8e26f56859135bbac0d8e108e5cf05a5f80a
pg-change-coverage-reviewercommitfest-7034-20260804T090229Z-review-handoff-coveragee78cf57894112ea641f7b7fb0ad25b89866667485b8469e0ae50e775d7d9bb2f
thread ledgercommitfest-7034-review-thread-ledgerdc84f205125513a157779e61fb90d6a7cac2d7ecc33cab735846a35e14c0e17

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

F1

[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 when use_union_all = true (distinct path factors, EDGE_PATTERN_ANY, same vertex table). Existing tests hit use_union_all on g4/ptne but contain no self-loop edge (src = dest); the only self-loop edge in the suite (e3_3 at 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 shows Filter: (src_id <> dest_id) in the backward branch; self-loop v33|e331|v33 appears 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 sha256 a1f1474e311e22396e3accffbb0803055e2fafdd5a81ecd042ae0855e384f53b, bound to HEAD 7cb9060dcde. Validation: passed (apply-check + compile + targeted-test + two-run).
F2

[MAJOR, correctness] — Hand-built Query/SetOperationStmt bypasses transformSetOperationStmt

  • Location: src/backend/rewrite/rewriteGraphTable.c:630-797
  • Mechanism: architecture-risk
  • Description: generate_undirected_edge_union_query constructs the UNION ALL Query by hand rather than via transformSetOperationStmt. The resulting structure must satisfy every invariant is_simple_union_all/pull_up_simple_union_all expect. 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_3 appears 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).
F3

[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.
F4

[MINOR, robustness] — Assert vs elog for array-size checks

  • Location: src/backend/rewrite/rewriteGraphTable.c:671,677
  • Mechanism: error-handling
  • Description: Assert(n1 == n3) and Assert(n1 == n2) instead of elog(ERROR, ...) as in sibling build_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 uses elog(ERROR).
  • Repair: Replace Asserts with if (n1 != n3) elog(ERROR, ...) / if (n1 != n2) elog(ERROR, ...). Patch sha256 68817457e0ff7a92af488edeeb20481390e961c6775d4c20e6587310cec54824. Validation: passed (compile + regression identical).
F5

[MINOR, robustness] — Missing operator-validity Assert

  • Location: src/backend/rewrite/rewriteGraphTable.c:826-848
  • Mechanism: robustness
  • Description: build_edge_self_loop_filter does not assert the operator is a binary boolean-returning equality operator, unlike build_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 patch 68817457e0ff7a92af488edeeb20481390e961c6775d4c20e6587310cec54824. Validation: passed.
F6

[MINOR, correctness] — Composite-key swap last-match-wins ambiguity

  • Location: src/backend/rewrite/rewriteGraphTable.c:756-763
  • Mechanism: edge-case
  • Description: The bwd_attnum swap loop does not guard against a column appearing in both src_key_attnums and dest_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.
F7

[MINOR, style] — Redundant perminfoindex=0 for RTE_SUBQUERY

  • Location: src/backend/rewrite/rewriteGraphTable.c:539
  • Mechanism: style
  • Description: pni->p_rte->perminfoindex = 0 is a no-op; addRangeTableEntryForSubquery already 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

FindingSeverityStatusMethod
F1MAJORverifiedtwo-run (regression SQL with oracle)
F2MAJORverifiedtargeted-test (EXPLAIN plan shape)
F3MAJORcannot-verifyinspection (thread ledger)
F4MINORverifiedstatic-check (inspection)
F5MINORverifiedstatic-check (inspection)
F6MINORverifiedstatic-check (inspection)
F7MINORverifiedstatic-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

FindingPatch sha256ValidationMethods
F1a1f1474e...4f53bpassedapply-check, compile, targeted-test, two-run
F2a1f1474e...4f53bpassedapply-check, compile, targeted-test, two-run
F3waivedwaiveddesign-choice
F468817457e0...4824passedapply-check, compile, targeted-test
F568817457e0...4824passedapply-check, compile, targeted-test
F668817457e0...4824passedapply-check, compile, targeted-test
F7waivedpassedstatic-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)

  1. 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.
  2. 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

gcov epoch (fresh, integrity-checked)

Raw metrics

MetricCount
Changed lines total310
Changed non-executable (structural)161
Changed executable lines149
Covered changed lines (graph_table + TG4)142
Covered changed lines (graph_table only)141
Uncovered changed lines7
Integrity errors0

Uncovered changed lines (all infeasible-with-reason)

LinesFunctionMechanismClassificationReason
658generate_undirected_edge_union_queryerror-path (cache lookup)infeasible-with-reasonRequires corrupt/missing pg_propgraph_element row; DDL writes atomically at CREATE PROPERTY GRAPH
732,734,735,737,738generate_undirected_edge_union_querydropped-column branchinfeasible-with-reasonRequires edge table with DROP COLUMN'd column in UNION ALL undirected pattern; no existing fixture; low-value defensive code (v2 motivation)
828build_edge_self_loop_filtererror-path (cache lookup)infeasible-with-reasonRequires 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

FunctionKindCoverage note
generate_undirected_edge_union_queryaddedentry count=3; uncovered lines 658, 732-738 (all infeasible)
build_edge_self_loop_filteraddedentry count=3; uncovered line 828 (infeasible); line 852 covered by TG4
generate_query_for_graph_pathmodifiedall changed executable lines covered

Test examples

Residual limitations

pgsql-hackers thread ledger

Per-finding thread dispositions

FindingDispositionThread reference
F1already-raisedAshutosh (2026-07-16) requested multi-edge undirected tests
F2author-deferredAuthor showed EXPLAIN pull-up in v2 post; no regression test added
F3unreconciledLive objection from Ashutosh (2026-07-30); no agreement
F4-F7newNot discussed on thread

Development-branch catversion exception: working-tree diff on master; no catalog/storage format change; no catversion bump required.

Aggregated Test Plan

TestVehicleFilesStatus
TG1: self-loop dedup filterregressgraph_table.sql, graph_table.outpassed (two-run)
TG3: EXPLAIN pull-up confirmationregressgraph_table.sqlpassed
TG4: composite-key undirectedregressgraph_table.sqlpassed (proposed)
F4/F5/F6: robustness fixescompile + regressrewriteGraphTable.cpassed

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

Residual risk

Submission review

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.