Skip to content

Commit 9bb2878

Browse files
jfrochesamrose
andauthored
feat: support multiple versions of the plpgsql_check extension (#1684)
* feat: support multiple versions of the plpgsql_check extension Build multiple versions of the plpgsql_check extension on different PostgreSQL versions. Add test for the extensions and their upgrade on PostgreSQL 15 and 17. * tests: refactor to use the default nixos tests * fix(plpgsql-check): add missing upgrade path files between versions Generate empty SQL files for version-to-version upgrade paths to satisfy PostgreSQL extension upgrade requirements. * feat: requires switch version script due to shared_preload * fix: test with plpgsql_check extension --------- Co-authored-by: Sam Rose <[email protected]>
1 parent 388c2da commit 9bb2878

File tree

3 files changed

+322
-37
lines changed

3 files changed

+322
-37
lines changed

nix/ext/plpgsql-check.nix

Lines changed: 130 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,142 @@
44
fetchFromGitHub,
55
postgresql,
66
postgresqlTestHook,
7+
buildEnv,
8+
makeWrapper,
9+
switch-ext-version,
710
}:
11+
let
12+
pname = "plpgsql_check";
813

9-
stdenv.mkDerivation rec {
10-
pname = "plpgsql-check";
11-
version = "2.7.11";
14+
# Load version configuration from external file
15+
allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname};
1216

13-
src = fetchFromGitHub {
14-
owner = "okbob";
15-
repo = "plpgsql_check";
16-
rev = "v${version}";
17-
hash = "sha256-vR3MvfmUP2QEAtXFpq0NCCKck3wZPD+H3QleHtyVQJs=";
18-
};
17+
# Filter versions compatible with current PostgreSQL version
18+
supportedVersions = lib.filterAttrs (
19+
_: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql
20+
) allVersions;
1921

20-
buildInputs = [ postgresql ];
22+
# Derived version information
23+
versions = lib.naturalSort (lib.attrNames supportedVersions);
24+
latestVersion = lib.last versions;
25+
numberOfVersions = builtins.length versions;
26+
packages = builtins.attrValues (
27+
lib.mapAttrs (name: value: build name value.hash value.revision) supportedVersions
28+
);
2129

22-
installPhase = ''
23-
install -D -t $out/lib *${postgresql.dlSuffix}
24-
install -D -t $out/share/postgresql/extension *.sql
25-
install -D -t $out/share/postgresql/extension *.control
26-
'';
30+
# Build function for individual versions
31+
build =
32+
version: hash: revision:
33+
stdenv.mkDerivation rec {
34+
inherit pname version;
2735

28-
passthru.tests.extension = stdenv.mkDerivation {
29-
name = "plpgsql-check-test";
30-
dontUnpack = true;
31-
doCheck = true;
32-
buildInputs = [ postgresqlTestHook ];
33-
nativeCheckInputs = [ (postgresql.withPackages (ps: [ ps.plpgsql_check ])) ];
34-
postgresqlTestUserOptions = "LOGIN SUPERUSER";
35-
failureHook = "postgresqlStop";
36-
checkPhase = ''
37-
runHook preCheck
38-
psql -a -v ON_ERROR_STOP=1 -c "CREATE EXTENSION plpgsql_check;"
39-
runHook postCheck
40-
'';
41-
installPhase = "touch $out";
42-
};
36+
src = fetchFromGitHub {
37+
owner = "okbob";
38+
repo = "plpgsql_check";
39+
rev = "v${revision}";
40+
inherit hash;
41+
};
42+
43+
buildInputs = [ postgresql ];
44+
45+
installPhase = ''
46+
mkdir -p $out/{lib,share/postgresql/extension}
47+
48+
# Install shared library with version suffix
49+
mv ${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix}
50+
51+
# Create version-specific control file
52+
sed -e "/^default_version =/d" \
53+
-e "s|^module_pathname = .*|module_pathname = '\$libdir/${pname}-${version}'|" \
54+
${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control
55+
56+
# For the latest version, create default control file and symlink and copy SQL upgrade scripts
57+
if [[ "${version}" == "${latestVersion}" ]]; then
58+
cp *.sql $out/share/postgresql/extension
59+
else
60+
mv ./${pname}--${version}.sql $out/share/postgresql/extension/${pname}--${version}.sql
61+
fi
62+
'';
63+
64+
passthru.tests.extension = stdenv.mkDerivation {
65+
name = "plpgsql-check-test";
66+
dontUnpack = true;
67+
doCheck = true;
68+
buildInputs = [ postgresqlTestHook ];
69+
nativeCheckInputs = [ (postgresql.withPackages (ps: [ ps.plpgsql_check ])) ];
70+
postgresqlTestUserOptions = "LOGIN SUPERUSER";
71+
failureHook = "postgresqlStop";
72+
checkPhase = ''
73+
runHook preCheck
74+
psql -a -v ON_ERROR_STOP=1 -c "CREATE EXTENSION plpgsql_check;"
75+
runHook postCheck
76+
'';
77+
installPhase = "touch $out";
78+
};
79+
80+
meta = with lib; {
81+
description = "Linter tool for language PL/pgSQL";
82+
homepage = "https://github.com/okbob/plpgsql_check";
83+
changelog = "https://github.com/okbob/plpgsql_check/releases/tag/v${version}";
84+
license = licenses.mit;
85+
maintainers = [ maintainers.marsam ];
86+
inherit (postgresql.meta) platforms;
87+
};
88+
};
89+
in
90+
buildEnv {
91+
name = pname;
92+
paths = packages;
93+
nativeBuildInputs = [ makeWrapper ];
94+
95+
pathsToLink = [
96+
"/lib"
97+
"/share/postgresql/extension"
98+
];
99+
100+
postBuild = ''
101+
{
102+
echo "default_version = '${latestVersion}'"
103+
cat $out/share/postgresql/extension/${pname}--${latestVersion}.control
104+
} > $out/share/postgresql/extension/${pname}.control
105+
ln -sfn ${pname}-${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix}
106+
107+
# Verify all expected library files are present
108+
expectedFiles=${toString (numberOfVersions + 1)}
109+
actualFiles=$(ls -l $out/lib/${pname}*${postgresql.dlSuffix} | wc -l)
110+
111+
if [[ "$actualFiles" != "$expectedFiles" ]]; then
112+
echo "Error: Expected $expectedFiles library files, found $actualFiles"
113+
echo "Files found:"
114+
ls -la $out/lib/*${postgresql.dlSuffix} || true
115+
exit 1
116+
fi
117+
118+
# Create empty upgrade files between consecutive versions
119+
# plpgsql_check ships without upgrade scripts - extensions are backward-compatible
120+
previous_version=""
121+
for ver in ${lib.concatStringsSep " " versions}; do
122+
if [[ -n "$previous_version" ]]; then
123+
touch $out/share/postgresql/extension/${pname}--''${previous_version}--''${ver}.sql
124+
fi
125+
previous_version=$ver
126+
done
127+
128+
makeWrapper ${lib.getExe switch-ext-version} $out/bin/switch_plpgsql_check_version \
129+
--prefix EXT_WRAPPER : "$out" --prefix EXT_NAME : "${pname}"
130+
'';
43131

44-
meta = with lib; {
45-
description = "Linter tool for language PL/pgSQL";
46-
homepage = "https://github.com/okbob/plpgsql_check";
47-
changelog = "https://github.com/okbob/plpgsql_check/releases/tag/v${version}";
48-
platforms = postgresql.meta.platforms;
49-
license = licenses.mit;
50-
maintainers = [ maintainers.marsam ];
132+
passthru = {
133+
inherit versions numberOfVersions switch-ext-version;
134+
pname = "${pname}-all";
135+
hasBackgroundWorker = true;
136+
defaultSettings = {
137+
shared_preload_libraries = [
138+
"plpgsql"
139+
"plpgsql_check"
140+
];
141+
};
142+
version =
143+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
51144
};
52145
}

nix/ext/tests/plpgsql_check.nix

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
{ self, pkgs }:
2+
let
3+
pname = "plpgsql_check";
4+
inherit (pkgs) lib;
5+
installedExtension =
6+
postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all";
7+
versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions;
8+
postgresqlWithExtension =
9+
postgresql:
10+
let
11+
majorVersion = lib.versions.major postgresql.version;
12+
pkg = pkgs.buildEnv {
13+
name = "postgresql-${majorVersion}-${pname}";
14+
paths = [
15+
postgresql
16+
postgresql.lib
17+
(installedExtension majorVersion)
18+
];
19+
passthru = {
20+
inherit (postgresql) version psqlSchema;
21+
lib = pkg;
22+
withPackages = _: pkg;
23+
};
24+
nativeBuildInputs = [ pkgs.makeWrapper ];
25+
pathsToLink = [
26+
"/"
27+
"/bin"
28+
"/lib"
29+
];
30+
postBuild = ''
31+
wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib
32+
wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib
33+
wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib
34+
'';
35+
};
36+
in
37+
pkg;
38+
psql_15 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
39+
psql_17 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17;
40+
in
41+
self.inputs.nixpkgs.lib.nixos.runTest {
42+
name = pname;
43+
hostPkgs = pkgs;
44+
nodes.server =
45+
{ config, ... }:
46+
{
47+
virtualisation = {
48+
forwardPorts = [
49+
{
50+
from = "host";
51+
host.port = 13022;
52+
guest.port = 22;
53+
}
54+
];
55+
};
56+
services.openssh = {
57+
enable = true;
58+
};
59+
60+
services.postgresql = {
61+
enable = true;
62+
package = psql_15;
63+
enableTCPIP = true;
64+
initialScript = pkgs.writeText "init-postgres-with-password" ''
65+
CREATE USER test WITH PASSWORD 'secret';
66+
'';
67+
authentication = ''
68+
host test postgres samenet scram-sha-256
69+
'';
70+
settings = (installedExtension "15").defaultSettings or { };
71+
};
72+
73+
networking.firewall.allowedTCPPorts = [ config.services.postgresql.settings.port ];
74+
75+
specialisation.postgresql17.configuration = {
76+
services.postgresql = {
77+
package = lib.mkForce psql_17;
78+
};
79+
80+
systemd.services.postgresql-migrate = {
81+
serviceConfig = {
82+
Type = "oneshot";
83+
RemainAfterExit = true;
84+
User = "postgres";
85+
Group = "postgres";
86+
StateDirectory = "postgresql";
87+
WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}";
88+
};
89+
script =
90+
let
91+
oldPostgresql = psql_15;
92+
newPostgresql = psql_17;
93+
oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}";
94+
newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}";
95+
in
96+
''
97+
if [[ ! -d ${newDataDir} ]]; then
98+
install -d -m 0700 -o postgres -g postgres "${newDataDir}"
99+
${newPostgresql}/bin/initdb -D "${newDataDir}"
100+
${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \
101+
--old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin"
102+
else
103+
echo "${newDataDir} already exists"
104+
fi
105+
'';
106+
};
107+
108+
systemd.services.postgresql = {
109+
after = [ "postgresql-migrate.service" ];
110+
requires = [ "postgresql-migrate.service" ];
111+
};
112+
};
113+
};
114+
testScript =
115+
{ nodes, ... }:
116+
let
117+
pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17";
118+
in
119+
''
120+
from pathlib import Path
121+
versions = {
122+
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
123+
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
124+
}
125+
extension_name = "${pname}"
126+
support_upgrade = True
127+
pg17_configuration = "${pg17-configuration}"
128+
ext_has_background_worker = ${
129+
if (installedExtension "15") ? hasBackgroundWorker then "True" else "False"
130+
}
131+
sql_test_directory = Path("${../../tests}")
132+
pg_regress_test_name = "${(installedExtension "15").pgRegressTestName or pname}"
133+
134+
${builtins.readFile ./lib.py}
135+
136+
start_all()
137+
138+
server.wait_for_unit("multi-user.target")
139+
server.wait_for_unit("postgresql.service")
140+
141+
test = PostgresExtensionTest(server, extension_name, versions, sql_test_directory, support_upgrade)
142+
143+
if ext_has_background_worker:
144+
with subtest("Test switch_${pname}_version"):
145+
test.check_switch_extension_with_background_worker(Path("${psql_15}/lib/${pname}.so"), "15")
146+
147+
with subtest("Check pg_regress with postgresql 15 after installing the last version"):
148+
test.check_pg_regress(Path("${psql_15}/lib/pgxs/src/test/regress/pg_regress"), "15", pg_regress_test_name)
149+
150+
with subtest("switch to postgresql 17"):
151+
server.succeed(
152+
f"{pg17_configuration}/bin/switch-to-configuration test >&2"
153+
)
154+
155+
if ext_has_background_worker:
156+
with subtest("Test switch_${pname}_version"):
157+
test.check_switch_extension_with_background_worker(Path("${psql_17}/lib/${pname}.so"), "17")
158+
159+
with subtest("Check upgrade path with postgresql 17"):
160+
test.check_upgrade_path("17")
161+
162+
last_version = versions["17"][-1]
163+
with subtest("Check last version of the extension after postgresql upgrade"):
164+
test.assert_version_matches(last_version)
165+
166+
with subtest("Check pg_regress with postgresql 17 after extension upgrade"):
167+
test.check_pg_regress(Path("${psql_17}/lib/pgxs/src/test/regress/pg_regress"), "17", pg_regress_test_name)
168+
169+
with subtest("Check the install of the last version of the extension"):
170+
test.check_install_last_version("17")
171+
172+
with subtest("Check pg_regress with postgresql 17 after installing the last version"):
173+
test.check_pg_regress(Path("${psql_17}/lib/pgxs/src/test/regress/pg_regress"), "17", pg_regress_test_name)
174+
'';
175+
}

nix/ext/versions.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,23 @@
382382
"hash": "sha256-j5F1PPdwfQRbV8XJ8Mloi8FvZF0MTl4eyIJcBYQy1E4="
383383
}
384384
},
385+
"plpgsql_check": {
386+
"2.2": {
387+
"postgresql": [
388+
"15"
389+
],
390+
"hash": "sha256-8HFyIzJ1iF3K2vTlibFallvkMKjFTJ2DO64fORToD8E=",
391+
"revision": "2.2.6"
392+
},
393+
"2.7": {
394+
"postgresql": [
395+
"15",
396+
"17"
397+
],
398+
"hash": "sha256-vR3MvfmUP2QEAtXFpq0NCCKck3wZPD+H3QleHtyVQJs=",
399+
"revision": "2.7.11"
400+
}
401+
},
385402
"postgis": {
386403
"3.3.2": {
387404
"postgresql": [

0 commit comments

Comments
 (0)