Skip to content
Open
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 nix/ext/pg_cron/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ let
supportedVersions;
versionsBuilt = if latestOnly then [ latestVersion ] else versions;
numberOfVersionsBuilt = builtins.length versionsBuilt;
# Legacy bare extversion strings mapped to their schema-identical canonical
# version. Shared via passthru so the test can't drift from this table.
legacyVersions = {
"1.0" = "1.0.0";
"1.1" = "1.1.0";
"1.2" = "1.2.0";
"1.3" = "1.3.1";
"1.4" = "1.4.0";
"1.4-1" = "1.4.2";
"1.5" = "1.5.2";
"1.6" = "1.6.4";
};
legacyAlignmentMigrations = lib.concatStrings (
lib.mapAttrsToList (legacy: canonical: ''
cat > $out/share/postgresql/extension/${pname}--${legacy}--${canonical}.sql << 'EOF'
-- Alignment migration: ${legacy} and ${canonical} are schema-identical.
EOF
'') legacyVersions
);
build =
version: versionData:
stdenv.mkDerivation rec {
Expand Down Expand Up @@ -57,12 +76,17 @@ let
mv $out/share/postgresql/extension/pg_cron--1.0--1.1.sql $out/share/postgresql/extension/pg_cron--1.0.0--1.1.0.sql
mv $out/share/postgresql/extension/pg_cron--1.1--1.2.sql $out/share/postgresql/extension/pg_cron--1.1.0--1.2.0.sql
mv $out/share/postgresql/extension/pg_cron--1.2--1.3.sql $out/share/postgresql/extension/pg_cron--1.2.0--1.3.1.sql
mv $out/share/postgresql/extension/pg_cron--1.3--1.4.sql $out/share/postgresql/extension/pg_cron--1.3.1--1.4.2.sql
mv $out/share/postgresql/extension/pg_cron--1.3--1.4.sql $out/share/postgresql/extension/pg_cron--1.3.1--1.4.0.sql
mv $out/share/postgresql/extension/pg_cron--1.4--1.4-1.sql $out/share/postgresql/extension/pg_cron--1.4.0--1.4.1.sql
cat > $out/share/postgresql/extension/pg_cron--1.4.1--1.4.2.sql << 'EOF'
-- Alignment migration: 1.4.1 and 1.4.2 are schema-identical.
EOF
mv $out/share/postgresql/extension/pg_cron--1.4-1--1.5.sql $out/share/postgresql/extension/pg_cron--1.4.2--1.5.2.sql
mv $out/share/postgresql/extension/pg_cron--1.5--1.6.sql $out/share/postgresql/extension/pg_cron--1.5.2--1.6.4.sql
${legacyAlignmentMigrations}
fi


# Create versioned control file with modified module path
sed -e "/^default_version =/d" \
-e "/^schema =/d" \
Expand Down Expand Up @@ -120,6 +144,7 @@ buildEnv {
numberOfVersions = numberOfVersionsBuilt;
inherit switch-ext-version latestOnly;
hasBackgroundWorker = true;
inherit legacyVersions;
defaultSettings = {
shared_preload_libraries = [ "pg_cron" ];
"cron.database_name" = "postgres";
Expand Down
14 changes: 14 additions & 0 deletions nix/ext/tests/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ let
in
''
from pathlib import Path
import json
versions = {
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
Expand All @@ -230,6 +231,7 @@ let
pg_regress_test_name = "${(installedExtension "15").pgRegressTestName or pname}"
ext_schema = "${(installedExtension "15").defaultSchema or "public"}"
lib_name = "${(installedExtension "15").libName or pname}"
legacy_versions = json.loads('${builtins.toJSON ((installedExtension "15").legacyVersions or { })}')
print(f"Running tests for extension: {lib_name}")

${builtins.readFile ./lib.py}
Expand All @@ -247,6 +249,10 @@ let
''
with subtest("Check upgrade path with postgresql 15"):
test.check_upgrade_path("15")

if legacy_versions:
with subtest("Check legacy extversion strings upgrade to latest with postgresql 15"):
test.check_legacy_versions_upgrade_to_latest("15", legacy_versions)
''
else
""
Expand Down Expand Up @@ -306,6 +312,10 @@ let
''
with subtest("Check upgrade path with postgresql 17"):
test.check_upgrade_path("17")

if legacy_versions:
with subtest("Check legacy extversion strings upgrade to latest with postgresql 17"):
test.check_legacy_versions_upgrade_to_latest("17", legacy_versions)
''
else
""
Expand Down Expand Up @@ -334,6 +344,10 @@ let
with subtest("Check upgrade path with orioledb 17"):
test.check_upgrade_path("orioledb-17")

if legacy_versions:
with subtest("Check legacy extversion strings upgrade to latest with orioledb 17"):
test.check_legacy_versions_upgrade_to_latest("orioledb-17", legacy_versions)

with subtest("Check pg_regress with orioledb 17 after installing the last version"):
test.check_pg_regress(Path("${orioledb_17}/lib/pgxs/src/test/regress/pg_regress"), "orioledb-17", pg_regress_test_name)
''
Expand Down
53 changes: 53 additions & 0 deletions nix/ext/tests/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,59 @@ def check_upgrade_path(self, pg_version: str):
self.drop_extension()
self.install_extension(version)

def check_legacy_versions_upgrade_to_latest(
self, pg_version: str, legacy_versions: Mapping[str, str]
):
"""Prove every legacy extversion string upgrades cleanly to latest.

legacy_versions maps a legacy/bare extversion string (e.g. pg_cron's
"1.6") to the schema-identical canonical version it has an alignment
migration to (e.g. "1.6.4"). Postgres can't install a legacy string
directly -- there's no base script for it, only an alignment edge off
of it -- so for each entry this installs the canonical version fresh,
then patches pg_extension.extversion directly to the legacy string.
This reproduces the actual field state (and is the same manual fix
used to recover a stuck project) rather than a state Postgres would
ever arrive at on its own.

From there, a bare ALTER EXTENSION UPDATE (no explicit target version)
is run -- matching how the real upgrade-completion path invokes it --
and the result must land on the latest canonical version. If any
legacy string has no path to latest, this fails loudly, which is
exactly the stranded-project bug this test exists to catch.

The extension is restored to the latest canonical version on exit so
the subtests that follow see the expected state.

Args:
pg_version: PostgreSQL version under test (e.g. "15")
legacy_versions: Mapping of legacy extversion string to the
canonical version it is schema-identical to
"""
if not legacy_versions:
return

available = self.versions.get(pg_version, [])
if not available:
raise ValueError(
f"No versions available for PostgreSQL version {pg_version}"
)
latest = available[-1]

for legacy_version, canonical_version in legacy_versions.items():
self.drop_extension()
self.install_extension(canonical_version)
self.run_sql(
f"UPDATE pg_extension SET extversion = '{legacy_version}' "
f"WHERE extname = '{self.extension_name}';"
)
self.run_sql(f"ALTER EXTENSION {self.extension_name} UPDATE;")
self.assert_version_matches(latest)

# Restore canonical latest so following subtests have expected state
self.drop_extension()
self.install_extension(latest)

def check_install_last_version(self, pg_version: str) -> str:
"""Test if the install of the last version of the extension works for a given PostgreSQL version.

Expand Down
Loading