CommitFest 7161 Review: Apply Extended Statistics to Join Clause During Parameterized Path Costing

code-review · generated from canonical Markdown

Type
code-review
Source
postgresql-master
Slug
commitfest-7161-review
Tags
code-reviewcommitfestcoveragepatch-reviewpg-review-harness
Updated
2026-10-08T09:26:29.961921+00:00
Verdict / Summaryready-for-committer (test follow-up required)
Front matter
schema: review-report-v2
target: "CommitFest 7161: Apply extended statistics to join clause during parameterized path costing"
source: postgresql-master
slug: commitfest-7161-review
head: 061065e28f086e3e2d7191a7785b9f218b7976d8
base: 061065e28f086e3e2d7191a7785b9f218b7976d8
verdict: "ready-for-committer (test follow-up required)"
blocker_counts:
  verified: 0
  unverified: 0
coverage_requirement: required
coverage_status: measured

CommitFest 7161 Review: Apply Extended Statistics to Join Clause During Parameterized Path Costing

Author: Ilia Evdokimov (Tantor Labs) CommitFest: PG20-3 (2026-11-01 – 2026-11-30), patch 7161 Patch version: v1, +42 −3, 2 files Review date: 2026-10-08 Model: glm-5.2:cloud

Verdict

ready-for-committer (test follow-up required)

No BLOCKER findings. No correctness/security MAJOR findings. One test-only MAJOR (F-1: no regression tests for the new behavior). The patch is functionally correct and the author's repro confirms the intended behavior. The test gap should be addressed before commit.

Blocker split: verified=0, unverified=0

Patch Summary

The patch makes functional-dependency extended statistics applicable when costing parameterized base-relation paths. Previously, clauselist_selectivity_ext only applied extended stats when all clauses referenced a single relation (find_single_rel_for_clauses returned non-NULL). For a parameterized scan, clauses include join quals like t1.a = t2.b, so find_single_rel_for_clauses returns NULL and no extended stats were used — even though varRelid identifies the scanned relation.

Two coordinated changes:

  1. src/backend/optimizer/path/clausesel.c:158-166 — when

use_extended_stats && rel == NULL && varRelid != 0, look up paramrel = find_base_rel(root, varRelid) and, if it is an RTE_RELATION with stats, call dependencies_clauselist_selectivity on it, multiplying the result into s1.

  1. src/backend/statistics/dependencies.c:743-788 —

dependency_is_compatible_clause now allows up to two varnos (was BMS_SINGLETON), and in the OpExpr branch, when neither arg is pseudoconstant, accepts Var(relid) = Var(other-single-rel) and treats the other relation's Var as a pseudoconstant.

Runtime Verification

Built PostgreSQL with meson+ninja in a disposable scratch build with coverage instrumentation (b_coverage=true). Ran the author's repro and additional scenarios against a scratch PostgreSQL instance:

No crash observed in any tested scenario (inner join, LEFT JOIN, RIGHT JOIN, FULL JOIN, 3-way joins with outer joins, OR clauses).

Changed-Code Coverage

Counter epoch: fresh disposable gcov epoch at HEAD Execution lane: host-disposable (passed)

Coverage Totals

Metricclausesel.cdependencies.cTotal
Changed executable lines41620
Covered changed lines4913
Compiler-uncovered lines077
Structural uncovered lines000
Changed branches102030
Covered changed branches7613

Uncovered Lines (7)

FileLinesFunctionCoverabilityReason
dependencies.c745dependency_is_compatible_clausecoverable-now0-varno clause edge case (bms_num_members == 0)
dependencies.c781-785dependency_is_compatible_clauseinfeasible-with-reasonRight-arg-is-relid path: unreachable through normal SQL because EC clause generation always places inner rel Var on left
dependencies.c787dependency_is_compatible_clauseinfeasible-with-reasonNeither-arg-matches-relid: unreachable in parameterized path context due to EC Var ordering

Uncovered Branches (17)

FileLineBranchCoverabilityReason
clausesel.c1581, 3coverable-nowFalse-branch paths of if condition — exercised by non-parameterized queries
clausesel.c1621coverable-nowFalse-branch: paramrel has no stats — exercised before CREATE STATISTICS
dependencies.c7432, 3coverable-nowFalse-branch of > 2 check — exercised by 1-2 varno clauses
dependencies.c7762coverable-nowFalse-branch: largrelids not singleton
dependencies.c7772, 4coverable-nowFalse-branch: largrelids singleton != relid
dependencies.c7792coverable-nowFalse-branch: rargrelids == relid (same-relation clause)
dependencies.c781-784allinfeasible-with-reasonRight-arg-is-relid else-if: unreachable due to EC Var ordering normalization

Excluded Changed Coordinates

None — all changed lines have gcov coverage coordinates.

Non-Instrumented Changes

None — all changed files are C source files with coverage instrumentation.

Findings

F-1

— MAJOR (tests): No test coverage for the new behavior

Location: src/test/regress/sql/stats_ext.sql (absence) Class: tests Verification: verified (code inspection + runtime testing)

The patch changes planner estimates for parameterized paths but adds no regression tests. Existing stats_ext.sql covers functional dependencies only for single-relation WHERE restrictions. Without tests, a future refactor could silently revert the behavior.

Repair: Append a test section to stats_ext.sql that creates correlated columns with functional dependency statistics, forces a parameterized nested loop join, and verifies the per-probe row estimate changes. Test patch validated by applying to scratch source, rebuilding, and confirming rows=1 without stats → rows=10 with stats on the inner Index Scan.

Test example (regress):

sql
CREATE TABLE dep_param_t1 (a int, b int, c int) WITH (autovacuum_enabled = off);
CREATE TABLE dep_param_t2 (a int, b int) WITH (autovacuum_enabled = off);
INSERT INTO dep_param_t1 SELECT i%100, i%100, i%100 FROM generate_series(1,1000) i;
INSERT INTO dep_param_t2 SELECT i, i FROM generate_series(0,99) i;
CREATE INDEX dep_param_t1_a_b_idx ON dep_param_t1 (a, b);
ANALYZE dep_param_t1, dep_param_t2;
SET enable_hashjoin = off; SET enable_mergejoin = off; SET enable_bitmapscan = off;
EXPLAIN (COSTS ON)
SELECT * FROM dep_param_t2, dep_param_t1
WHERE dep_param_t1.a = dep_param_t2.a AND dep_param_t1.b = dep_param_t2.b;
CREATE STATISTICS dep_param_st (dependencies) ON a, b FROM dep_param_t1;
ANALYZE dep_param_t1;
EXPLAIN (COSTS ON)
SELECT * FROM dep_param_t2, dep_param_t1
WHERE dep_param_t1.a = dep_param_t2.a AND dep_param_t1.b = dep_param_t2.b;

Oracle: rows=1 without stats → rows=10 with stats on inner Index Scan.

F-2

— MINOR (correctness): find_base_rel can ERROR on unexpected varRelid

Location: src/backend/optimizer/path/clausesel.c:160 Class: correctness Verification: verified (code inspection + runtime testing)

find_base_rel(root, varRelid) calls elog(ERROR) if the slot is NULL or out of range. All current callers pass valid base-rel varRelids, so no crash is reachable today. Using find_base_rel_noerr + NULL check is a low-cost defensive improvement.

Repair:

c
RelOptInfo *paramrel = find_base_rel_noerr(root, varRelid);
if (paramrel && paramrel->rtekind == RTE_RELATION && paramrel->statlist != NIL)

Validated: applied to scratch source, rebuilt, ran join tests — no regression.

F-3

— MINOR (maintainability): Bitmapset leak — largrelids/rargrelids not freed

Location: src/backend/statistics/dependencies.c:773-774 Class: maintainability Verification: verified (code inspection)

The new pull_varnos results are never bms_free'd. Planner context resets per-query, but this is inconsistent with PostgreSQL's bitmapset hygiene.

Repair: Add bms_free(largrelids); bms_free(rargrelids); before all exit paths. Validated: applied, rebuilt, no regression.

F-4

— MINOR (correctness): Outer-join/nullingrels scope limitation

Location: src/backend/statistics/dependencies.c:743, 773-784 Class: correctness Verification: cannot-verify (scope limitation, not a code defect)

pull_varnos includes varnullingrels, which could cause BMS_SINGLETON tests to fail for outer-join-nullled Vars. However, runtime testing showed the patch works for LEFT JOIN parameterized scans because EC-generated clauses use plain Vars without nulling relids. This is a scope limitation, not a bug. No repair needed for current scope; should be addressed if the caller surface expands to pass nullled Var clauses.

F-5

— MINOR (docs): No documentation update

Location: doc/src/sgml/planstats.sgml:458 Class: docs Verification: cannot-verify (documentation change)

The Functional Dependencies docs don't mention the new parameterized-path application. A short paragraph should be added noting that dependency statistics are also applied when costing parameterized scan paths.

F-6

— NIT (maintainability): s1 *= vs s1 = stylistic inconsistency

Location: src/backend/optimizer/path/clausesel.c:163 Class: maintainability Verification: cannot-verify (stylistic observation)

The *= form is correct and arguably preferable. No repair needed.

F-7

— MINOR (maintainability): pull_varnos(NULL,...) PHV handling — REFUTED

Location: src/backend/statistics/dependencies.c:773 Class: maintainability Verification: refuted

pull_varnos(NULL, ...) is intentional and correct: PHVs are not relevant for functional dependency compatibility. If a PHV appeared, the clause would fail the IsA(clause_expr, Var) check at line 902 and be rejected.

Verification Summary

FindingSeverityClassVerificationRepairValidation
F-1MAJORtestsverifiedtest patchpassed
F-2MINORcorrectnessverifiedfind_base_rel_noerrpassed
F-3MINORmaintainabilityverifiedbms_free addedpassed
F-4MINORcorrectnesscannot-verifywaived (scope limitation)waived
F-5MINORdocscannot-verifywaived (doc change)waived
F-6NITmaintainabilitycannot-verifywaived (stylistic)waived
F-7MINORmaintainabilityrefutedN/AN/A

Repair Validation Summary

FindingPatch SHA-256MethodsStatus
F-17a3e49085402da1974d30cd4a30b9ecdbcd14642e580e14392dc7be7d27aedf6apply-check, diff-check, compile, targeted-testpassed
F-2562a165131fd2ee41163f0a75bdd1657c2d6a510e93fcba8df5c24517b31e420apply-check, diff-check, compile, targeted-testpassed
F-3c9f10d48bfae3350caff3a2634d56076a5f85212e6624f8800675f8cf6dc52a9apply-check, diff-check, compile, targeted-testpassed
F-4waivedN/Awaived
F-5waivedN/Awaived
F-6waivedN/Awaived
F-7N/A (refuted)N/AN/A

pgsql-hackers Thread Ledger

Thread: 7b1bd383-fa74-4c0e-a75a-52ff9c753750@tantorlabs.com Status: reconciled (3 messages)

MessageDateSenderKindSummary
7b1bd383...@tantorlabs.com2026-08-17Ilia EvdokimovpatchOriginal submission with repro showing rows=2 → rows=200
e651725f...@vondra.me2026-08-21Tomas VondrareviewAsked about MCV/ndistinct; noted scan-vs-join estimate inconsistency
24bf4ee2...@tantorlabs.com2026-09-15Ilia Evdokimovauthor-replyExplained MCV/ndistinct are separate works; chose single-rel side first

Finding links:

Aggregated Test Plan

  1. F-1 test (regress): src/test/regress/sql/stats_ext.sql — parameterized

join with functional dependencies, assert rows=1 → rows=10 transition. Covers clausesel.c:158-165 and dependencies.c:743-780.

  1. F-2/F-3 repairs validated with the same test plus inner/LEFT/RIGHT join

scenarios — no regression.

Provenance / Model Routes

SeatModelTask
pg-patch-reviewerglm-5.2:cloudPatch review (six-heading)
pg-security-reviewerglm-5.2:cloudSecurity + C memory safety
pg-finding-verifierglm-5.2:cloudFinding verification + repair validation
pg-change-coverage-reviewerglm-5.2:cloudChanged-code coverage analysis
Coordinatorglm-5.2:cloudOrchestration, verdict, report

Residual Risk

its value distribution. This is a sound first-order approximation but may be less accurate for highly skewed join keys. The author acknowledges this and plans MCV/ndistinct as follow-up work.

not resolved by this patch and remains an open item for join-level estimation follow-up.

due to EC clause Var ordering normalization. This is defensive code that is correct but untestable through regression tests.

Maintainer CC

Tomas Vondra (reviewer of extended statistics code), as the thread reviewer.