Skip to content

Commit a3789f0

Browse files
jfrochesamrose
authored andcommitted
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.
1 parent 388c2da commit a3789f0

File tree

3 files changed

+281
-37
lines changed

3 files changed

+281
-37
lines changed

nix/ext/plpgsql-check.nix

Lines changed: 106 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,118 @@
44
fetchFromGitHub,
55
postgresql,
66
postgresqlTestHook,
7+
buildEnv,
78
}:
9+
let
10+
pname = "plpgsql_check";
811

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

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

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

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-
'';
28+
# Build function for individual versions
29+
build =
30+
version: hash: revision:
31+
stdenv.mkDerivation rec {
32+
inherit pname version;
2733

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

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 ];
115+
passthru = {
116+
inherit versions numberOfVersions;
117+
pname = "${pname}-all";
118+
version =
119+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
51120
};
52121
}

nix/ext/tests/plpgsql_check.nix

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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+
in
39+
self.inputs.nixpkgs.lib.nixos.runTest {
40+
name = pname;
41+
hostPkgs = pkgs;
42+
nodes.server =
43+
{ config, ... }:
44+
{
45+
virtualisation = {
46+
forwardPorts = [
47+
{
48+
from = "host";
49+
host.port = 13022;
50+
guest.port = 22;
51+
}
52+
];
53+
};
54+
services.openssh = {
55+
enable = true;
56+
};
57+
users.users.root.openssh.authorizedKeys.keys = [
58+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIo+ulCUfJjnCVgfM4946Ih5Nm8DeZZiayYeABHGPEl7 jfroche"
59+
];
60+
61+
services.postgresql = {
62+
enable = true;
63+
package = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
64+
};
65+
66+
specialisation.postgresql17.configuration = {
67+
services.postgresql = {
68+
package = lib.mkForce (postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17);
69+
};
70+
71+
systemd.services.postgresql-migrate = {
72+
serviceConfig = {
73+
Type = "oneshot";
74+
RemainAfterExit = true;
75+
User = "postgres";
76+
Group = "postgres";
77+
StateDirectory = "postgresql";
78+
WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}";
79+
};
80+
script =
81+
let
82+
oldPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
83+
newPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17;
84+
oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}";
85+
newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}";
86+
in
87+
''
88+
if [[ ! -d ${newDataDir} ]]; then
89+
install -d -m 0700 -o postgres -g postgres "${newDataDir}"
90+
${newPostgresql}/bin/initdb -D "${newDataDir}"
91+
${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \
92+
--old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin"
93+
else
94+
echo "${newDataDir} already exists"
95+
fi
96+
'';
97+
};
98+
99+
systemd.services.postgresql = {
100+
after = [ "postgresql-migrate.service" ];
101+
requires = [ "postgresql-migrate.service" ];
102+
};
103+
};
104+
};
105+
testScript =
106+
{ nodes, ... }:
107+
let
108+
pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17";
109+
in
110+
''
111+
versions = {
112+
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
113+
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
114+
}
115+
116+
def run_sql(query):
117+
return server.succeed(f"""sudo -u postgres psql -t -A -F\",\" -c \"{query}\" """).strip()
118+
119+
def check_upgrade_path(pg_version):
120+
with subtest("Check ${pname} upgrade path"):
121+
firstVersion = versions[pg_version][0]
122+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION IF EXISTS ${pname};'")
123+
run_sql(f"""CREATE EXTENSION ${pname} WITH VERSION '{firstVersion}' CASCADE;""")
124+
installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""")
125+
assert installed_version == firstVersion, f"Expected ${pname} version {firstVersion}, but found {installed_version}"
126+
for version in versions[pg_version][1:]:
127+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION IF EXISTS ${pname};'")
128+
run_sql(f"""CREATE EXTENSION ${pname} WITH VERSION '{version}';""")
129+
installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""")
130+
assert installed_version == version, f"Expected ${pname} version {version}, but found {installed_version}"
131+
132+
start_all()
133+
134+
server.wait_for_unit("multi-user.target")
135+
server.wait_for_unit("postgresql.service")
136+
137+
check_upgrade_path("15")
138+
139+
with subtest("Check ${pname} latest extension version"):
140+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION ${pname};'")
141+
server.succeed("sudo -u postgres psql -c 'CREATE EXTENSION ${pname} CASCADE;'")
142+
installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""")
143+
latestVersion = versions["15"][-1]
144+
assert f"${pname},{latestVersion}" in installed_extensions
145+
146+
with subtest("switch to postgresql 17"):
147+
server.succeed(
148+
"${pg17-configuration}/bin/switch-to-configuration test >&2"
149+
)
150+
151+
with subtest("Check ${pname} latest extension version after upgrade"):
152+
installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""")
153+
latestVersion = versions["17"][-1]
154+
assert f"${pname},{latestVersion}" in installed_extensions
155+
156+
check_upgrade_path("17")
157+
'';
158+
}

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)