Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 0 additions & 50 deletions test/fixtures/pgcolumnar--1.0-alpha2.sql
Original file line number Diff line number Diff line change
Expand Up @@ -374,56 +374,6 @@ CREATE FUNCTION pgcolumnar.set_options(
DECLARE
col name;
BEGIN
/*
* The options are per-relation and are read by the columnar writer, so a row
* recorded for a relation that is not columnar can never be used. Storing one
* is not merely useless: the drop hook that clears pgcolumnar.options fires
* only for columnar relations, so the row outlives the table and is left
* keyed to a dangling oid that a later relation reusing that oid inherits.
* Measured before this guard, on the same cluster: set_options on a heap
* table stored a row, DROP TABLE left it behind, and regclass then rendered
* as the bare oid; the identical sequence on a columnar table cleaned up.
*
* Rejecting is safe for the one workflow that could want the other order:
* ALTER TABLE ... SET ACCESS METHOD pgcolumnar keeps the relation's oid
* (measured), so options set after the conversion apply to the same relation
* a caller would have been trying to name before it.
*
* The ERRCODE is explicit. plpgsql's RAISE EXCEPTION defaults to P0001, and
* the C paths raise this same sentence with ERRCODE_WRONG_OBJECT_TYPE
* (42809). Without it the identical message carried two different SQLSTATEs
* depending on which path refused the caller, in a tree whose own privilege
* suites deliberately assert SQLSTATE rather than message text.
*
* relkind is part of the test, and it is what makes the guard match the
* cleanup rather than merely look strict. The drop hook returns before it
* examines the access method for anything that is not an ordinary table
* (columnar_tableam.c: `if (get_rel_relkind(objectId) != RELKIND_RELATION)
* return;`), so 'r' is exactly the set of relations whose options row can
* ever be cleaned up. From PG17 a PARTITIONED table may carry an access
* method, so `relam = pgcolumnar` alone admits a parent that has no storage,
* that the writer never writes, and whose row the hook will never clear.
* Measured on 17.6 with the amname-only test: accepted, one row recorded,
* and the row still there after DROP TABLE keyed to the dropped oid, while
* an ordinary columnar table in the same run cleaned up. PG16 and earlier
* cannot reach it -- they refuse `PARTITION BY ... USING pgcolumnar`
* outright, checked on 16.14 -- so this is PG17, 18 and 19.
*/
IF NOT EXISTS (SELECT 1 FROM pg_class c
JOIN pg_am a ON a.oid = c.relam
WHERE c.oid = table_name
AND a.amname = 'pgcolumnar'
AND c.relkind = 'r') THEN
RAISE EXCEPTION 'relation "%" is not a columnar table', table_name
USING ERRCODE = 'wrong_object_type',
HINT = 'Per-table options are read by the columnar writer and '
'apply only to an ordinary table using the pgcolumnar access '
'method. A partitioned table has no storage of its own: set the '
'options on each partition. Otherwise convert the table first '
'with ALTER TABLE ... SET ACCESS METHOD pgcolumnar, then set '
'the options.';
END IF;

IF encode_effort IS NOT NULL AND
encode_effort NOT IN ('full', 'fast') THEN
RAISE EXCEPTION 'unknown columnar encode_effort "%"', encode_effort
Expand Down
84 changes: 84 additions & 0 deletions test/native_upgrade_converge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,90 @@ done
cleanup() { for f in "${STAGED[@]:-}"; do [ -n "$f" ] && rm -f "$f"; done; }
trap cleanup EXIT

# ---- each fixture must be what its tag actually shipped (#901) --------------
#
# THE FIXTURES ARE THE PREMISE OF EVERY ARM BELOW. This suite claims that each
# released starting point upgrades to the current catalog, and it can only claim
# that if the fixture IS the released starting point.
#
# They were made by renaming the root base script when the next cycle opened. A
# rename at cycle-open equals the release only if nothing touched the file between
# the tag and the rename, and for `1.0-alpha2` something did: two post-release
# `set_options` fixes. The fixture was the released script plus those, so this
# suite spent two cycles upgrading from a state no user ever had.
#
# Comparing BLOB IDS rather than diffing, because a blob id is a lookup. No
# similarity heuristic and no pathspec can distort it, and a rename-detection
# argument once turned a content-preserving move into a fabricated `R098` here.
#
# `1.0-alpha` IS DELIBERATELY ABSENT FROM THIS LIST, and that is the finding this
# check exists to stop being invisible. `v1.0-alpha` shipped
# `pgcolumnar--1.0-dev.sql` with `default_version = 1.0-dev`; there is no
# `pgcolumnar--1.0-alpha.sql` at that tag or any other. Its fixture is constructed
# rather than released, and it cannot be replaced by the real `1.0-dev` script:
# that script names the pre-rename C symbol and the current library does not
# export it.
#
# ERROR: could not find function "columnar_handler" in file "pgcolumnar.so"
#
# So no single-library test can start from a genuine `1.0-dev` or `1.0-alpha`
# install. The arm is kept because it is the only cover for the
# `1.0-dev--1.0-alpha` and `1.0-alpha--1.0-alpha2` upgrade scripts, which ship.
# What it tests is catalog shape, not a released artifact, and it is named that
# way rather than counted with the others.
# DERIVED FROM THE FIXTURES ON DISK rather than listed, so a fixture added at the
# next cycle-open is checked without anyone remembering to add it here. A list
# that has to be edited alongside the thing it describes goes stale, and this
# suite already carries three copies of its version list for exactly that reason.
# A hardcoded list here could not see its own incompleteness: the one case it
# must catch is a NEW fixture, and a new fixture is precisely what it would omit.
#
# THE LOCAL TAG IS TRUSTED, AND THAT IS A REAL LIMIT. `git fetch` never moves an
# existing local tag, so a stale one compares the fixture against the wrong blob
# and this arm reports a drift that is not there, or misses one that is. A false
# release-integrity issue has already been filed off a stale local ref in this
# repository. If this arm fails and the fixture looks right, check the tag against
# the server before believing it:
#
# git ls-remote origin refs/tags/v<version>
_FX_TAGGED=""
for _fx_f in "$HERE"/fixtures/pgcolumnar--*.sql; do
[ -f "$_fx_f" ] || continue
_fx_v="${_fx_f##*/pgcolumnar--}"; _fx_v="${_fx_v%.sql}"
# 1.0-alpha is synthetic and has no tag artifact: see above.
[ "$_fx_v" = "1.0-alpha" ] && continue
_FX_TAGGED="$_FX_TAGGED $_fx_v"
done
_FX_TAGGED="${_FX_TAGGED# }"

# The sweep is a claim too. An empty one would make every arm below vanish and the
# suite would report clean having compared nothing.
check "premise: the fixture sweep found fixtures to compare against their tags" \
"$([ -n "$_FX_TAGGED" ] && echo yes || echo no)" "yes"

_fx_git() { git -C "$HERE/.." "$@" 2>/dev/null; }
if ! _fx_git rev-parse --git-dir >/dev/null; then
_fx_why="no git repository in the tree under test"
elif [ -z "$(_fx_git tag -l 'v1.0-alpha*')" ]; then
# CI checks out at depth 1 with no tags, so this cannot run there. It runs
# locally and in the five-major release gate, which is where a fixture is
# captured and therefore where it can be captured wrongly.
_fx_why="no release tags in this checkout"
else
_fx_why=""
fi

for v in $_FX_TAGGED; do
_fx_name="the $v fixture is byte-identical to what v$v shipped"
if [ -n "$_fx_why" ]; then
check_skip "$_fx_name" "SKIP $_fx_name ($_fx_why)" "$_fx_why"
continue
fi
_fx_tag="$(_fx_git rev-parse "v$v:pgcolumnar--$v.sql" || echo "no such path at v$v")"
_fx_fix="$(_fx_git hash-object "$HERE/fixtures/pgcolumnar--$v.sql" || echo "fixture missing")"
check "$_fx_name" "$_fx_fix" "$_fx_tag"
done

P() { env PATH="$PGC_BINDIR:$PATH" psql -h 127.0.0.1 -p "$PGC_PORT" -U postgres -tAq "$@"; }

# Comprehensive catalog snapshot of the pgcolumnar schema, one line per object.
Expand Down
Loading