CommitFest 7094 Review: coerce_type string->unknown passthrough

report · generated from canonical Markdown

Type
report
Source
postgresql-master
Slug
commitfest-7094-review
Tags
commitfest-7094coveragepg-review-harnesspostgresqlreview-report-v2waiting-on-author
Updated
2026-09-03T11:48:00.466261+00:00
Verdict / Summarywaiting-on-author
Front matter
schema: review-report-v2
slug: commitfest-7094-review
source: postgresql-master
target: CommitFest 7094 — coerce_type string-category->unknown passthrough
base: 534db08f972852b77ea5e96e77d5fe45f3d8df93
head: 534db08f972852b77ea5e96e77d5fe45f3d8df93
verdict: waiting-on-author
blocker_counts:
  verified: 1
  unverified: 0
model: glm-5.2:cloud
diff_sha: 2e02c9be6b35f40bee3d29e96c71e0132d44f9fc9289976d3e1568bd75d36d72

CommitFest 7094 Review — coerce_type string→unknown passthrough

Summary

The staged patch (10 lines, src/backend/parser/parse_coerce.c:415-422) adds a special case in coerce_type: when the target type is UNKNOWNOID and the input type's base category is TYPCATEGORY_STRING, return the node unchanged. This is the V1 approach proposed by Jian He on pgsql-hackers thread 635318 (2026-01-29), which Tom Lane explicitly objected to ("I certainly don't like the cast pathway you suggest adding to make it not be one — that seems likely to cause lots of not-very-desirable behaviors"). The author later moved to a V2 (2026-04-22) that disallows unknown targets in find_coercion_pathway instead. The staged patch is stale relative to thread consensus.

Verdict: waiting-on-author — 1 verified BLOCKER (F1: stale vs thread / live objection), 5 MAJOR findings, 1 coverage MAJOR (COV-1), 4 MINOR findings. The V2 repair (un-stage V1; add if (targetTypeId == UNKNOWNOID) return COERCION_PATH_NONE; in find_coercion_pathway) resolves all findings simultaneously and was validated by compile + full make installcheck (243/243 pass) + behavioral probes.

Patch under review

diff
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d3240f4b265..f75f808da3b 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -411,6 +411,16 @@ coerce_type(ParseState *pstate, Node *node,
 		}
 		return result;
 	}
+
+	if (targetTypeId == UNKNOWNOID)
+	{
+		Oid			inputBaseTypeId = getBaseType(inputTypeId);
+		TYPCATEGORY s_typcategory = TypeCategory(inputBaseTypeId);
+
+		if (s_typcategory == TYPCATEGORY_STRING)
+			return node;
+	}
+
 	pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext,
 									 &funcId);

Findings

F1

— BLOCKER — correctness/design — src/backend/parser/parse_coerce.c:415-422

Stale vs thread consensus; Tom Lane live objection. The staged patch implements the V1 "string-category → unknown passthrough" that Tom Lane objected to on thread 635318 (reply 1127584.1769649859@sss.pgh.pa.us). The author later moved to V2 (CACJufxF7iSeRF2+bprERSwBe5zShVch1EJK-KcxQCcLT-UHBbg@mail.gmail.com, 2026-04-22) that disallows unknown targets in find_coercion_pathway.

  • Status: verified (inspection + reproduction)
  • Repair: Adopt V2 — un-stage V1; add if (targetTypeId == UNKNOWNOID) return COERCION_PATH_NONE; in find_coercion_pathway after the internal-type rejection. Patch sha256 18b93f26a2257cd7f2c95b85c2ef12f800b5757c35d939c2f9acf00ee910397c, applies to HEAD 534db08f972852b77ea5e96e77d5fe45f3d8df93.
  • Validation: passed (apply-check, diff-check, compile, targeted-test, repro; installcheck 243/243).
F2

— MAJOR — correctness — src/backend/parser/parse_coerce.c:415-422

Predicate/executor asymmetry. can_coerce_type has no matching special case for targetTypeId==UNKNOWNOID + string-category input. coerce_to_target_type gates coerce_type with can_coerce_type (line 88), returning NULL before coerce_type is called. The new branch is dead code via the primary path — confirmed by coverage (line 415 branch taken 0% in 24208 evaluations).

  • Status: verified (inspection + reproduction + verified-by-coverage)
F3

— MAJOR — correctness — src/backend/parser/parse_coerce.c:420-421

No RelabelType. The branch returns node with no RelabelType, so exprType(result) is the input type (e.g. text), not UNKNOWNOID. Contrast the documented no-RelabelType case at lines 172-191 (ANY* with explicit NB comment).

  • Status: verified (reproduction)
F4

— MAJOR — correctness/design — src/backend/parser/parse_coerce.c:418

Asymmetric category filter. Only TYPCATEGORY_STRING inputs pass through; all other categories still error at line 554. Arbitrary asymmetry with no rationale.

  • Status: verified (inspection + reproduction)
F5

— MAJOR — tests — src/backend/parser/parse_coerce.c:415-422

No tests added. The branch is neither proven reachable nor behavior-preserving.

  • Status: verified (inspection)
  • Repair test: src/test/regress/sql/cast_unknown.sql (new) — covers unknown::unknown, text::unknown (error), int4::unknown (error), domain-over-string ::unknown (error), unknown::text (pass).
F6

— MAJOR — docs — whole patch

No documentation. No doc/src/sgml/ change documenting the new coercion rule (or, under V2, the new error).

  • Status: cannot-verify (doc gap confirmed; exact SGML placement uncertain)
  • Repair: typed design-choice waiver; V2 introduces no new coercion rule, so a one-line clarification in the unknown type section is proportional.
F7

— MINOR — correctness — src/backend/parser/parse_coerce.c:415-422

No ccontext gating. The branch fires regardless of COERCION_IMPLICIT/ASSIGNMENT/EXPLICIT/PLPGSQL context. Existing I/O-cast fallbacks are gated (find_coercion_pathway lines 3293-3301).

  • Status: verified (inspection)
F8

— MINOR — correctness — src/backend/parser/parse_coerce.c:417

Domain identity silently stripped. getBaseType(inputTypeId) on a domain over a string type strips domain identity (no coerce_to_domain, no constraint re-validation).

  • Status: verified (reproduction)
F9

— MINOR — maintainability — src/backend/parser/parse_coerce.c:415-422

Missing comment. The new branch has no comment; every neighboring special case (178-190, 200-214, 460-467) carries an explanatory comment.

  • Status: verified (inspection)
F10

— MINOR — maintainability — src/backend/parser/parse_coerce.c:417

Non-idiomatic naming. inputBaseTypeId / s_typcategory depart from conventions (baseTypeId at line 217; typcategory elsewhere; s_ prefix unused).

  • Status: verified (inspection)
F11

— MINOR — performance — src/backend/parser/parse_coerce.c:417-418

Redundant syscache lookup. getBaseType + TypeCategory run before find_coercion_pathway which already does getBaseType.

  • Status: verified (inspection)
COV-1

— MAJOR — tests — src/backend/parser/parse_coerce.c:415-421

Dead code confirmed by coverage. The V1 branch is unreachable: targetTypeId == UNKNOWNOID condition was never true in 24208 evaluations across the full 243-test regression suite. gcov: 0% line coverage (5/5 uncovered), 0% branch coverage (2/2 uncovered).

  • Status: verified (verified-by-coverage)
  • coverage_reference: line src/backend/parser/parse_coerce.c:415

Verification Summary

FindingSeverityClassStatusMethod
F1BLOCKERcorrectnessverifiedinspection + reproduction
F2MAJORcorrectnessverifiedinspection + reproduction + verified-by-coverage
F3MAJORcorrectnessverifiedreproduction
F4MAJORcorrectnessverifiedinspection + reproduction
F5MAJORtestsverifiedinspection
F6MAJORdocscannot-verifyinspection
F7MINORcorrectnessverifiedinspection
F8MINORcorrectnessverifiedreproduction
F9MINORmaintainabilityverifiedinspection
F10MINORmaintainabilityverifiedinspection
F11MINORperformanceverifiedinspection
COV-1MAJORtestsverifiedverified-by-coverage

Counts: verified 11, cannot-verify 1, refuted 0. Verified BLOCKER split: 1/1.

Repair Validation Summary

All verified findings share a single substantive repair: the V2 find_coercion_pathway guard (un-stage V1 coerce_type block; add if (targetTypeId == UNKNOWNOID) return COERCION_PATH_NONE; in find_coercion_pathway).

Changed-Code Coverage

Coverage requirement: required (1 changed instrumentable path: src/backend/parser/parse_coerce.c).

Execution lane: host-disposable (passed). Fresh coverage-instrumented build (CFLAGS=-O0 -g -fprofile-arcs -ftest-coverage) of V1-patched source in disposable scratch; initdb cluster on disposable port 55809; full pg_regress installcheck 243/243 tests passed; gcov -b -f parse_coerce.c collected with 813 runs.

Metrics

MetricValue
changed_executable_lines5
covered_changed_lines0
compiler_uncovered_changed_lines5
uncovered_changed_lines5
structural_uncovered_changed_lines0
changed_lines_without_coverage_coordinates0
changed_branches2
covered_changed_branches0
integrity_errors[]

Identity check: covered (0) + compiler-uncovered (5) = changed executable (5) ✓. Uncovered (5) + structural (0) = compiler-uncovered (5) ✓.

Uncovered regions

coerce_type lines 415-421 — coverability: requires-special-harness-or-injection-point

LineSourcegcov
415if (targetTypeId == UNKNOWNOID)24208 executions, branch 0 (fallthrough) taken 0%
417Oid inputBaseTypeId = getBaseType(inputTypeId);##### never executed
418TYPCATEGORY s_typcategory = TypeCategory(inputBaseTypeId);##### never executed
420if (s_typcategory == TYPCATEGORY_STRING)##### never executed
421return node;##### never executed

Reason: The V1 branch is unreachable via the primary coerce_to_target_type path because can_coerce_type (line 88 gate) returns false for target=UNKNOWNOID before coerce_type is called. Reaching the branch requires a direct coerce_type caller that bypasses the can_coerce_type gate. This confirms finding F2: the branch is dead code.

Uncovered branches

LineBranchCountCoverability
4150 (target==UNKNOWNOID true)0requires-special-harness-or-injection-point
4200 (category==STRING true)0requires-special-harness-or-injection-point

Touched functions

FunctionPathSpanEnteredChanged lines (covered/total)Changed branches (covered/total)
coerce_typesrc/backend/parser/parse_coerce.c158-555true0/50/2

Test examples

src/test/regress/sql/cast_unknown.sql (new) — exercises unknown::unknown, text::unknown (error), int4::unknown (error), domain-over-string ::unknown (error), unknown::text (pass). Fails on V1 (text::unknown returns text), passes on V2 (text::unknown errors). The V2 repair removes the dead branch entirely.

pgsql-hackers Thread Ledger

Status: reconciled. Ledger report: commitfest-7094-review-thread-ledger.

Thread "ERROR: failed to find conversion function from unknown to text" (Thread-ID 635318, pgsql-hackers):

#Message-IDDateSenderKindSummary
1CACJufxHu0sXO8791FDcNXp2bFnE89jyuGkJbLCQkhgWq6XuNLg@mail.gmail.com2026-01-29Jian HepatchProposes V1 (exact staged code)
21127584.1769649859@sss.pgh.pa.us2026-01-29Tom LanereviewObjects to V1 — "not-very-desirable behaviors"
3CACJufxGEpQiMUOFcw5G67sNqgdyXxB7h-mpA_6neqNRf4qov=g@mail.gmail.com2026-01-29Jian Heauthor-replyAgrees to disallow instead
4CACJufxF7iSeRF2+bprERSwBe5zShVch1EJK-KcxQCcLT-UHBbg@mail.gmail.com2026-04-22Jian HepatchV2 — disallow unknown targets in find_coercion_pathway

Finding links: F1 → already-raised (Tom Lane objection); F2-F11, COV-1 → new (harness findings not previously raised).

Catversion exception: Development branch; no catversion bump required (no pg_cast/pg_type/pg_proc row changes).

Aggregated Test Plan

  1. src/test/regress/sql/cast_unknown.sql (new) — content provided in F5/COV-1 repair test_example. Register serially after create_cast.
  2. src/test/regress/expected/cast_unknown.out (new) — generate from a V2 make installcheck run per the expected-out approval rule.
  3. Oracle: fail-on-V1 / pass-on-V2 for text::unknown; pass-on-baseline-and-V2 for unknown::unknown and unknown::text.

Verified-clean / non-findings

Provenance / Model Routes

Residual Risk

Maintainer CC

Jian He (author, jian.universality@gmail.com); Tom Lane (tgl@sss.pgh.pa.us, objector).