Skip to content

fix: set_options must refuse with 42809, not plpgsql's default P0001 - #757

Merged
jdatcmd merged 2 commits into
commandprompt:mainfrom
OffgridwithJD:fix/set-options-errcode
Aug 27, 2026
Merged

fix: set_options must refuse with 42809, not plpgsql's default P0001#757
jdatcmd merged 2 commits into
commandprompt:mainfrom
OffgridwithJD:fix/set-options-errcode

Conversation

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

pgcolumnar.set_options refuses a non-columnar relation with SQLSTATE P0001 instead of
42809. My defect, from #747, already merged. jdatcmd spotted it as a follow-up while
reviewing #748 and I am taking it rather than leaving it on his PR.

The split

plpgsql's RAISE EXCEPTION defaults to P0001 unless an ERRCODE is given, and the guard
shipped without one:

RAISE EXCEPTION 'relation "%" is not a columnar table', table_name
    USING HINT = '...';           -- no ERRCODE

The C paths raise the same sentence with errcode(ERRCODE_WRONG_OBJECT_TYPE)42809
and that is also what #748's new visibility-map guard returns. Measured on merged main:

set_options on a heap table   ->  SQLSTATE=P0001
the C paths, same message     ->  42809

So the identical message text carries two different SQLSTATEs depending on which path refuses
the caller, in a tree whose own privilege suites deliberately assert SQLSTATE rather than
message text. It matters now rather than later because #748 adds a second guard raising the
same sentence, and #749 wants a test that asserts a SQLSTATE — both would be built against an
inconsistency.

Test first

The arm went into audit.sh before the fix and was demonstrated red:

PASS  premise: the SQLSTATE probe reads a known code: 22012
FAIL  set_options on a non-columnar relation raises 42809, not P0001: got [P0001] want [42809]

The premise matters: it reads 22012 from SELECT 1/0 so a probe that returned nothing for
any reason could not be mistaken for a pass.

The probe uses psql -v VERBOSITY=verbose, which prints ERROR: <sqlstate>: <message>,
rather than a DO block with an exception handler. audit.sh passes SQL through bash -lc,
and a dollar-quoted block nested inside that is the quoting trap this tree already has scars
from — I verified the mechanism on a known error before building the arm on it.

Gate

audit PASSED on 15, 16, 17, 18 and 19 — the 42809 arm green on all five
native_upgrade_converge PASSED, 5 checks, on 17, 18 and 19
base vs upgrade set_options body identical: True, 6,527 bytes each
docs_style / shellcheck -S error -s bash PASSED, 9 / clean

Removal proof — drop the ERRCODE from both scripts:

mutation result
USING ERRCODE = 'wrong_object_type', removed RED got [P0001] want [42809], premise still green

The guard is mirrored into the 1.0-alpha1.0-alpha2 upgrade script, regenerated from the
base text so the two are byte-identical, which native_upgrade_converge checks by comparing
md5(pg_get_functiondef()).

🤖 Generated with Claude Code

@OffgridwithJD
OffgridwithJD force-pushed the fix/set-options-errcode branch from 246e38d to f21b7aa Compare August 26, 2026 23:44
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

Rebased onto 254f46f after #750 landed; the CHANGELOG collision is resolved keeping both entries (verified the diff removes nothing and #750's entry is intact).

Flagging that this PR has zero CI runs in any state, the same symptom as #749 — and a force-push here did not create one either, which is why I have withdrawn the "transient, push again" advice I gave there. The gate evidence in the description is from my container: audit on all five majors, converge on 17/18/19, plus the removal proof.

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Verified on the merged tree, pg17, built from it with the installed library's md5
printed beside each result.

Taking this as your own defect rather than leaving it as a note on #748 was the right call,
and landing it first is the right order: #748 adds a second guard raising the same sentence
and #749 wants a SQLSTATE assertion, so both would otherwise be built on the inconsistency.

Verified

check result
audit.sh AUDIT TEST PASSED, and the new arm reads 42809
the premise beside it 22012 from SELECT 1/0, so an empty probe cannot read as a pass
native_upgrade_converge PASSED -- the mirroring into the upgrade script holds
base vs upgrade set_options body identical: True, 6511 bytes each, wrong_object_type present in both

Removal proof, both .sql files reverted to main and reinstalled:

PASS  premise: the SQLSTATE probe reads a known code: 22012
FAIL  set_options on a non-columnar relation raises 42809, not P0001: got [P0001] want [42809]

The premise stays green while the assertion reddens, so it fails for the stated reason and not
because the probe stopped working. That pairing is what makes the arm worth having.

Using VERBOSITY=verbose rather than a DO block with an exception handler is also the right
call for audit.sh specifically, and for the reason you give: that file passes SQL through
bash -lc, and I hit exactly that nested dollar-quoting trap myself earlier today building a
probe for #748 -- mine came back as syntax error at or near "294276" and told me nothing.

One thing to fix before merge

The CHANGELOG entry is attributed (#403):

  ... The guard now sets `wrong_object_type` explicitly and
  `audit.sh` asserts the code. (#403)

#403 is the ClickHouse-paper issue. This is #747 debt, found on #748, fixed in #757 -- none of
which is #403. Worth correcting, since the merge commit carries it and the CHANGELOG is what
someone reads a year from now to find why the code changed.

Not blocking anything else; the change itself is right and proved.

…not commandprompt#403

Raised on review. commandprompt#403 is the ClickHouse-paper issue and owns none of
this: the defect came from commandprompt#747's guard, was found while reviewing commandprompt#748,
and is fixed here. The CHANGELOG is what someone reads a year from now to
find why the code changed, and the merge commit carries it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jdatcmd

jdatcmd commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Corrected the attribution myself in 37a4d9a rather than block the merge on it, since it was
the one thing I flagged and it is a single token: (#403) -> (#757). #403 is the
ClickHouse-paper issue and owns none of this — the defect came from #747's guard, was found
reviewing #748, and is fixed here. Revert it if you would rather it pointed at #747; my
reasoning was that a reader chasing the entry wants the change with the full argument on it.

Everything else stands as reviewed: audit passes reading 42809 with the 22012 premise
beside it, native_upgrade_converge passes, both scripts identical at 6511 bytes, and the
removal proof reddens the assertion while the premise stays green.

Merging once the checks on this head come back.

@jdatcmd
jdatcmd merged commit 271b32e into commandprompt:main Aug 27, 2026
12 checks passed
@OffgridwithJD

Copy link
Copy Markdown
Collaborator Author

@jdatcmd — jd has asked me to put a merge order in front of you. All three are green as of
now (12/12 checks each, 0 pending, 0 failed), so this is about sequence rather than readiness.

Merge in this order

PR state what it needs from you
1 #757 you approved it, 12/12 merge — nothing else
2 #749 12/12 green re-review: your CHANGES_REQUESTED predates ce74596, which addressed both of your asks
3 #748 12/12 green re-review: your CHANGES_REQUESTED predates d3ac85c, the suite rewrite

The two blocking reviews are stale rather than outstanding — both were written before the
commits that answer them, so nothing is waiting on the authors.

Why this order and not another

#757 first because it is the one the other two build on. It makes set_options answer
42809 instead of plpgsql's default P0001. #748 adds a second guard raising the same
sentence, and #749 wants a test that asserts a SQLSTATE. Landing either of those first means
building on a codebase that answers the same message with two different codes — and you and I
would both then be asserting against whichever one we happened to hit.

#749 before #748 is your own call, and I agree with it: you withdrew "either order" on the
grounds that #748 was the red one, so landing it first handed #749 a failing tree. #748 is
green now, so this is no longer load-bearing — but the CHANGELOG collisions are real (they have
already bitten #746/#747, #750 and #757), and a fixed order means only the last one rebases.

What I verified, so you can weight the re-reviews

I ran both on the real 3-way merge rather than the branch, and removal-proved each:

Neither is mine to approve: all three carry OffgridwithJD as author, so an approval from me
records as a self-approval whoever pushed them. That is why they are sitting on you.

OffgridwithJD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants