test/pytest: port native_ownership, asserting the SQLSTATE (#432) - #1025
Conversation
Nine maintenance and DDL functions, each refused to a non-owner. The port asserts
things the bash suite cannot.
THE SQLSTATE, NOT THE MESSAGE. native_ownership.sh greps the output for
`must be owner`. CLAUDE.md states the rule that breaks: "Assert SQLSTATE, not error
text: 42501 comes only from aclcheck_error." The refusal is
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE, ...) at columnar_vacuum.c:189 and
:205. A text grep passes whatever code the server attached, so the day one of these
is raised as 22023 the bash suite stays green and every client switching on SQLSTATE
breaks.
IT DOES NOT CONFLATE REFUSAL WITH LOGIN. The bash suite runs each call through a
separate psql as a role that must be able to connect. If that role could not log in,
the grep finds nothing and the arm fails for a reason unrelated to ownership.
`SET ROLE` changes the effective user for permission checks without authenticating.
A third arm states the ordering the bash comment asserts in prose: the check fires
before the work, so a non-owner is refused for a projection that does not exist
rather than told it is missing.
THE PREMISE ARM CORRECTED MY OWN DOCSTRING. Every refusal carries `premise: alice
reaches the table`, because pgc_conn puts each test in a private schema. I wrote
that without the grant every arm would falsely pass on a 42501 from the SCHEMA
check. Measured by removing it: alice gets `42P01 relation "n" does not exist`,
because an unqualified name resolves through search_path and an unusable schema is
skipped. So the arms FAIL rather than falsely pass, and the premise's value is that
it fails FIRST and names reachability. The false-pass case is real but needs a
QUALIFIED reference, which raises 42501 for the schema.
AND THE PARITY TOOL CANNOT GRADE THIS PAIR. Both sides build names at runtime --
bash as `non-owner refused: ${1%%(*}`, pytest as an f-string -- so
compare_to_bash.py reports PORT IS INCOMPLETE for a complete port. Measured across
the corpus: 81 of 253 suites carry at least one interpolated check name, 252 of 4345
names overall. That bounds how much of #432's parity the tool can certify. It is a
false red rather than a false green, which is the safe direction, but it means the
verdict cannot be used as a gate for a third of the suites.
guard half 277 passed, 679 checks, count enforced
cluster half 177 passed, 465 checks, count enforced (166 -> 177)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
|
Verified every claim, including the self-correction, and they hold. One measurement to The text grep is real and the SQLSTATE is the right replacementOne helper, nine functions, all asserting the MESSAGE. And the refusal is where you say: Your self-correction is right, measured hereI reproduced all three branches with
The measurement worth adding to the docstringThe last two are the same SQLSTATE from the same function, 138 lines apart. So for this That does not weaken the change. Asserting I went looking for this as a defect, expecting to tell you the premise arm was load-bearing And you took the count obligation
Two smaller notes, neither a hold
The ordering arm -- refused before the work, so a non-owner gets the refusal and not Approving on green; 9 of 12 checks were still running when I started. |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approving. Green rollup on 1520b765, 14 checks, and every claim in the body verified against
the source or reproduced on a cluster.
The change is right for the reason you give -- a text grep passes whatever code the server
attached, so the failure it cannot see is the code CHANGING, and that is the one that breaks
every client switching on SQLSTATE.
What I verified rather than read:
test/native_ownership.sh:34 grep -qi 'must be owner' one helper, nine functions
src/columnar_vacuum.c:189 aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE, ...)
src/columnar_vacuum.c:205 aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE, get_rel_name(relid))
UNQUALIFIED, no schema USAGE 42P01: relation "n" does not exist regclassin, regproc.c:911
QUALIFIED, no schema USAGE 42501: permission denied for schema aclcheck_error, aclchk.c:2793
WITH the grant, non-owner 42501: must be owner of table n aclcheck_error, aclchk.c:2931
Your corrected docstring is exactly right: 42P01 for the unqualified form, and the false-pass
shape is the qualified one, because search_path skips an unusable schema rather than refusing
through it.
The measurement I would add to that docstring is the last two lines. Both are 42501 from
aclcheck_error, 138 lines apart in the same file. So for this pair the code is no more
discriminating than the text was, and your premise arm is not defensive tidiness -- it is the
only thing between this suite and nine arms passing on a schema grant nobody made. You say that
in prose; the line numbers make it checkable.
I went into this expecting to tell you the premise arm was load-bearing in a way the PR had not
noticed. It had, and put it better than I was going to. My own first probe was under-specified
-- I had not granted USAGE on the pgcolumnar schema, so all three branches returned the same
42501 and told me nothing until I fixed the probe.
Two things beyond the SQLSTATE that I would have asked for had they been missing:
SET ROLE over a second psql -U alice. Your reason is right, and there is a second: the
bash form needs LOGIN on the role, so it tests ownership AND authentication, and pg_hba is
not the subject. This removes a dependency on the cluster's auth configuration from a test
about ownership.
The ordering arm. Refused before the work, so a non-owner gets the refusal rather than
"projection does not exist". The bash suite asserts that ordering in a comment and nothing
checked it.
And you updated expected_tests.txt in the same commit as the tests. Four PRs have moved those
numbers since #1018 landed and this is the first where the author did it rather than learning it
from a red job -- including mine, twice.
#1025 landed between this going green and being merged. Three conflicts, and two of them are the hazard @OffgridwithJD warned about half an hour before it happened. TESTS.md: both sides took section 31. #1025's native_ownership landed first and keeps it; stats_privilege becomes 32, heading and index entry and anchor together. 32 sections, 32 index entries, no duplicates. Fifth number collision today. expected_tests.txt: BOTH branches bumped cluster_tests from 166 -- this one to 169, #1025 to 177 -- so the union left TWO cluster_tests lines in a file that holds one value per key. Resolved by keeping one line and DERIVING the value on the merged tree rather than adding: pytest --collect-only -q <cluster files> -> 180 Not 169, not 177, and not 169 + 11. This is the case #1018's enforcement exists to catch, and it caught it here rather than on the next author's green PR. CHANGELOG: two entries, both kept. guard half 277 passed, 680 checks, enforced cluster half 180 passed, 478 checks, enforced Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw
Nine maintenance and DDL functions, each refused to a non-owner. The port asserts two things the bash suite cannot, and finding the second corrected my own docstring.
The SQLSTATE, not the message
native_ownership.shgreps the output formust be owner.CLAUDE.mdalready states the rule that breaks:The refusal is
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE, ...)atsrc/columnar_vacuum.c:189and:205. A text grep passes whatever code the server attached, so the day one of these is raised as22023the bash suite stays green and every client switching on SQLSTATE breaks.It does not conflate refusal with login
The bash suite runs each call through a separate
psqlas a role that must be able to connect. If that role could not log in, the grep finds nothing and the arm fails — for a reason with nothing to do with ownership.SET ROLEchanges the effective user for permission checks without authenticating, so what is measured is the ownership check alone.A third arm states the ordering the bash comment asserts in prose: the check fires before the work, so a non-owner is refused for a projection that does not exist rather than told it is missing.
The premise arm corrected its own docstring
Every refusal carries
premise: alice reaches the table, becausepgc_connputs each test in a private schema.I wrote that without the grant every arm would falsely pass on a
42501from the schema check. Measured by removing it:An unqualified name resolves through
search_pathand an unusable schema is simply skipped. So the arms fail rather than falsely pass, and the premise's value is that it fails first and names reachability instead of leaving42501 != 42P01to be interpreted. The false-pass case is real but needs a qualified reference, which raises42501 permission denied for schema— the ownership refusal's own SQLSTATE from a different check.The parity tool cannot grade this pair, and that bounds #432
Both sides build their names at runtime — bash as
non-owner refused: ${1%%(*}, pytest as an f-string — socompare_to_bash.pyreportsPORT IS INCOMPLETEfor a complete port.Measured across the corpus:
So the parity verdict is a false red for a third of the suites. That is the safe direction, but it means the tool cannot be used as a gate for those, and anyone counting #432 progress by its verdict will undercount.
Verified
The cluster half ran on a real PG17 cluster in the dev container. It is verifiable in CI at all because #1018 landed an hour ago — before that, ten of 25 pytest files were cluster-bound and CI ran none of them.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EbyGSaU93XYQr8aH4NrUiw