Skip to content
Draft
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
27 changes: 26 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,38 @@ jobs:
- name: Test on PostgreSQL ${{ matrix.pg }}
run: pg-build-test

# Real update-path leg: 0.1.1 (extension_drop's last REAL published PGXN
# release, 2017, recovered from PGXN's dist archive -- see
# sql/extension_drop--0.1.1.sql, RELEASE.md, HISTORY.asc) -> stable
# (current source). This is a SQL-level, PostgreSQL-version-agnostic
# concern -- extension_drop's install/update scripts have nothing
# catalog-version-sensitive in them (unlike e.g. pg_upgrade compatibility,
# which does need a version matrix) -- so this runs as a single job on the
# newest supported major rather than repeating the (a) job's whole PG
# matrix a second time.
extension-update-test:
name: 🔄 extension update test (0.1.1 → stable)
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
steps:
- name: Start PostgreSQL
run: pg-start 17
- name: Check out the repo
uses: actions/checkout@v5
- name: Build and install extension_drop (and its cat_tools dependency)
run: make install
- name: "Prove the update path: install 0.1.1, plant guard, ALTER EXTENSION UPDATE, re-prove guard, assert version"
run: bin/test_update_path.sh
- name: Run the pgTAP suite in update mode (0.1.1 -> stable) against the result
run: make test-update

# A single stable check name for use as a required status check in branch
# protection. Matrix jobs produce names like "🐘 PostgreSQL 14" that change
# with the matrix; this aggregates them into one. It passes if every needed
# job succeeded or was skipped (e.g. a docs-only push with paths-ignore) and
# fails if any failed or were cancelled.
all-checks-passed:
needs: [test]
needs: [test, extension-update-test]
if: always()
runs-on: ubuntu-latest
steps:
Expand Down
8 changes: 8 additions & 0 deletions HISTORY.asc
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
STABLE
------
== Add a real update path from 0.1.1
Recovered the actual 0.1.1 install script from PGXN's dist archive (it was
never committed to this repo -- only ever generated as a build artifact and
uploaded directly) as `sql/extension_drop--0.1.1.sql`, and added
`sql/extension_drop--0.1.1--stable.sql` to bring an existing 0.1.1 install
up to date. Previously there was no update path at all from the only version
ever actually published to PGXN.

== Remove redundant client_min_messages handling from install script
`CREATE EXTENSION` already raises `client_min_messages` to `WARNING` for
the install script (only-raising, so a stricter caller is respected) and
Expand Down
75 changes: 75 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,80 @@
# Run test/install/load.sql (extension install) COMMITTED, once, before the
# main pgTAP suite, via pgxntool's test/install feature. Set explicitly
# (rather than left to auto-detect) so an accidentally emptied test/install/
# is a hard build error instead of silently falling back to "disabled".
# Must be set before `include pgxntool/base.mk` below -- base.mk reads it
# while parsing.
PGXNTOOL_ENABLE_TEST_INSTALL = yes

# TEST_LOAD_SOURCE selects how test/install/load.sql installs extension_drop:
# - fresh (default): CREATE EXTENSION extension_drop (current version).
# - update: CREATE EXTENSION at TEST_UPDATE_FROM, then ALTER EXTENSION
# UPDATE -- to TEST_UPDATE_TO if set, otherwise to the current version.
# Running the SAME suite/expected output against the result asserts
# update behaves identically to a fresh install. TEST_UPDATE_FROM
# defaults to 0.1.1, extension_drop's last REAL published PGXN release
# (2017) -- its install script was recovered from PGXN's dist archive
# and committed as sql/extension_drop--0.1.1.sql (it was never in this
# repo's git history; see RELEASE.md and HISTORY.asc), with a matching
# update-diff script at sql/extension_drop--0.1.1--stable.sql. Empty
# TEST_UPDATE_TO (the default) means "update to the current
# default_version", which is now the `stable` pseudo-version.
# - existing: the extension is ALREADY installed (a real pg_upgrade, or an
# ALTER EXTENSION UPDATE done outside the suite). load.sql does not
# touch it; it only asserts presence + current version. Pair with
# CONTRIB_TESTDB=<db> and EXTRA_REGRESS_OPTS=--use-existing to point
# pg_regress at that database instead of a throwaway one.
#
# Propagated to load.sql as a GUC: pg_regress doesn't forward make variables,
# but the psql processes it spawns inherit the environment, so PGOPTIONS
# reaches load.sql. Exported UNCONDITIONALLY so load.sql can read it without
# missing_ok and fail loudly if it didn't propagate, rather than silently
# defaulting to the wrong mode. The mode is also validated here at
# make-parse-time, so a typo like `TEST_LOAD_SOURCE=fresh ` or
# `TEST_LOAD_SOURCE=typo` fails immediately instead of quietly running the
# default.
TEST_LOAD_SOURCE ?= fresh
ifeq ($(filter $(TEST_LOAD_SOURCE),fresh update existing),)
$(error TEST_LOAD_SOURCE must be 'fresh', 'update' or 'existing', got '$(TEST_LOAD_SOURCE)')
endif

# update-mode version range (load.sql only reads these in update mode).
# Empty TEST_UPDATE_TO means "update to the current default_version" (now
# `stable`). TEST_UPDATE_FROM defaults to 0.1.1, the actual recovered compat
# floor -- still overridable (e.g. once a second real release ships) but no
# longer required on every invocation. The guard below just protects against
# someone explicitly blanking it out (TEST_UPDATE_FROM= on the command line).
TEST_UPDATE_FROM ?= 0.1.1
TEST_UPDATE_TO ?=
ifeq ($(TEST_LOAD_SOURCE),update)
ifeq ($(strip $(TEST_UPDATE_FROM)),)
$(error TEST_UPDATE_FROM must not be blank when TEST_LOAD_SOURCE=update)
endif
endif

export PGOPTIONS := $(PGOPTIONS) -c extension_drop.test_load_mode=$(TEST_LOAD_SOURCE) -c extension_drop.test_update_from=$(TEST_UPDATE_FROM) -c extension_drop.test_update_to=$(TEST_UPDATE_TO)

# make test-update == make test TEST_LOAD_SOURCE=update. Must recurse (a
# fresh $(MAKE)) rather than depend on `test`, so the parse-time
# TEST_LOAD_SOURCE conditional above re-evaluates with update set.
.PHONY: test-update
test-update:
$(MAKE) test TEST_LOAD_SOURCE=update

include pgxntool/base.mk

# The recovered real 0.1.1 install script (see sql/extension_drop--0.1.1.sql
# and RELEASE.md) is a single-version file for a version that ISN'T the
# current default_version ('stable'), so base.mk's DATA wildcard -- which
# only picks up the CURRENT version file plus two-dash update-diff scripts,
# not other historical single-version install files -- won't ship it on its
# own. Without this, `pgxn install extension_drop` (or any local `make
# install`) would silently stop being able to `CREATE EXTENSION extension_drop
# VERSION '0.1.1'` at all, even though the update-diff script depends on that
# exact file being installed. Same gap already filed as
# Postgres-Extensions/pgxntool#48.
DATA += sql/extension_drop--0.1.1.sql

testdeps: test_extension
test_extension: $(DESTDIR)$datadir)/extension/extension_drop_test.control $(wildcard $(TESTDIR)/*)
$(DESTDIR)$datadir)/extension/extension_drop_test.control:
Expand Down
76 changes: 76 additions & 0 deletions bin/test_update_path.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/sh
# Exercises the real extension_drop update path: 0.1.1 (the last REAL PGXN
# release, recovered from PGXN's dist archive -- see sql/extension_drop--0.1.1.sql
# and RELEASE.md/HISTORY.asc) -> stable (this repo's current source).
#
# Assumes extension_drop and cat_tools are already built and installed into
# the active PostgreSQL cluster (`make install`, which pulls in the cat_tools
# deps target first) and that psql's ambient connection defaults reach it.
#
# Each step is PROVEN, not assumed -- see advanced-extension-testing.md
# section 4 (the dependency-guard technique) and section 6(d) (dynamic
# version assertions, never hardcoded):
# 1. CREATE EXTENSION extension_drop VERSION '0.1.1' CASCADE -- installs
# the recovered real historical release (CASCADE also pulls in
# cat_tools if not already present, matching test/install/load.sql).
# 2. Plant a dependency-guard view and prove a non-CASCADE DROP EXTENSION
# is blocked -- BEFORE the update, proving the guard actually attaches
# to the 0.1.1-era extension_drop__commands table.
# 3. ALTER EXTENSION extension_drop UPDATE -- runs
# sql/extension_drop--0.1.1--stable.sql.
# 4. Re-prove the SAME guard still blocks a non-CASCADE drop -- proves the
# update script didn't touch extension_drop__commands's identity.
# 5. Assert the installed version now matches extension_drop.control's
# default_version, read dynamically from the control file rather than
# hardcoded, with empty-value guards.
set -eu

cd "$(dirname "$0")/.."

DB=${1:-extension_drop_update_test}

dropdb --if-exists "$DB"
createdb "$DB"

psql -v ON_ERROR_STOP=1 -d "$DB" -c "
CREATE EXTENSION extension_drop VERSION '0.1.1' CASCADE;

CREATE SCHEMA extension_drop_drop_guard;
CREATE VIEW extension_drop_drop_guard.guard AS
SELECT NULL::extension_drop__commands AS guarded_member;
"

assert_guard_blocks_drop() {
label=$1
if psql -v ON_ERROR_STOP=1 -d "$DB" -c 'DROP EXTENSION extension_drop' >/tmp/guard_drop.out 2>/tmp/guard_drop.err; then
echo "FAIL ($label): DROP EXTENSION extension_drop succeeded -- the dependency guard did not block it" >&2
exit 1
fi
if ! grep -q 'cannot drop extension extension_drop because other objects depend on it' /tmp/guard_drop.err; then
echo "FAIL ($label): DROP EXTENSION failed, but not with the expected dependency-guard error:" >&2
cat /tmp/guard_drop.err >&2
exit 1
fi
echo "OK ($label): non-CASCADE DROP EXTENSION extension_drop is blocked by the dependency guard"
}

assert_guard_blocks_drop "pre-update, at 0.1.1"

psql -v ON_ERROR_STOP=1 -d "$DB" -c "SET client_min_messages = ERROR; ALTER EXTENSION extension_drop UPDATE"

assert_guard_blocks_drop "post-update"

INSTALLED=$(psql -tAc "SELECT extversion FROM pg_extension WHERE extname = 'extension_drop'" -d "$DB" | tr -d '[:space:]')
EXPECTED=$(sed -n "s/^default_version[[:space:]]*=[[:space:]]*'\([^']*\)'.*/\1/p" extension_drop.control | tr -d '[:space:]')

if [ -z "$INSTALLED" ] || [ -z "$EXPECTED" ] || [ "$INSTALLED" != "$EXPECTED" ]; then
echo "FAIL: installed='$INSTALLED' expected='$EXPECTED' (derived from extension_drop.control)" >&2
exit 1
fi
echo "OK: extension_drop landed at '$INSTALLED' after update, matching extension_drop.control's default_version"

dropdb "$DB"

echo "PASS: 0.1.1 -> $INSTALLED update path verified (install, guard survival, version assertion)."

# vi: expandtab ts=2 sw=2
60 changes: 60 additions & 0 deletions sql/extension_drop--0.1.1--stable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Update path from 0.1.1 (extension_drop's last REAL published PGXN release,
* 2017 -- recovered from PGXN's dist archive as sql/extension_drop--0.1.1.sql
* since it was never committed to this repo's git history; see RELEASE.md
* and HISTORY.asc) to `stable` (this repo's current in-development source).
*
* The only actual behavioral delta between the two, found by diffing the
* recovered 0.1.1 script against current sql/extension_drop.sql, is the
* extension_drop__event_trigger() function body gaining one entry-point
* RAISE DEBUG line (added in the same commit that also fixed a cat_tools
* function rename -- see git history of sql/extension_drop.sql). That's the
* one change here.
*
* Two other differences the diff turned up are deliberately NOT replayed
* here, because neither one changes anything about the objects this
* extension leaves behind after install completes:
* - The client_min_messages save/restore HISTORY.asc's `stable` section
* documents removing: that code only ever ran inside the install
* script's own session, saving/restoring a GUC and dropping its own
* temp table before the script finished -- nothing it did was ever part
* of the extension's persisted state, so an already-installed 0.1.1 has
* nothing left to clean up.
* - cat_tools.function__arg_types_text() being renamed to
* cat_tools.routine__parse_arg_types_text(): that call only happens
* inside the CREATE EXTENSION script's internal __extension_drop.create_function()
* builder, transiently, to compute the argument list text for the
* REVOKE/GRANT statements it executes immediately -- it's never stored
* in any persisted function body. (cat_tools 0.3.0 also keeps the old
* name as a deprecated wrapper, so a fresh `CREATE EXTENSION
* extension_drop VERSION '0.1.1'` still works today for testing this
* very update path.)
*/
CREATE OR REPLACE FUNCTION extension_drop__event_trigger(
) RETURNS event_trigger LANGUAGE plpgsql SET search_path FROM CURRENT AS
$body$
DECLARE
r extension_drop__commands;
BEGIN
RAISE DEBUG 'extension_drop event trigger entry: tg_event %, tg_tag %', tg_event, tg_tag;
FOR r IN
SELECT c.*
FROM extension_drop__commands c
JOIN pg_event_trigger_dropped_objects() d
ON c.extension_name = d.object_name
AND d.object_type = 'extension'
LOOP
RAISE DEBUG E'extension "%" is being dropped; executing SQL:\n%', r.extension_name, r.sql;
EXECUTE r.sql;
DELETE FROM extension_drop__commands WHERE extension_name = r.extension_name;
END LOOP;

/*
* Need to do this after the fact since the extensions being dropped have
* already been removed from the catalog by the time this function is called.
*/
PERFORM extension_drop__sanity_assert();
END
$body$;

-- vim: sw=2 ts=2 expandtab
Loading