Skip to content

Commit 91a8313

Browse files
jfrochesamrose
authored andcommitted
feat: support multiple versions of the http extension
Build multiple versions of the http extension on different PostgreSQL versions. Add test for the extensions and their upgrade on PostgreSQL 15 and 17.
1 parent dd54146 commit 91a8313

File tree

3 files changed

+269
-29
lines changed

3 files changed

+269
-29
lines changed

nix/ext/pgsql-http.nix

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,109 @@
11
{
2+
pkgs,
23
lib,
34
stdenv,
45
fetchFromGitHub,
5-
curl,
66
postgresql,
7+
curl,
78
}:
9+
let
10+
pname = "http";
811

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

13-
buildInputs = [
14-
curl
15-
postgresql
16-
];
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;
1719

18-
src = fetchFromGitHub {
19-
owner = "pramsey";
20-
repo = pname;
21-
rev = "refs/tags/v${version}";
22-
hash = "sha256-C8eqi0q1dnshUAZjIsZFwa5FTYc7vmATF3vv2CReWPM=";
23-
};
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) supportedVersions
26+
);
27+
28+
# Build function for individual versions
29+
build =
30+
version: hash:
31+
stdenv.mkDerivation rec {
32+
inherit pname version;
33+
34+
buildInputs = [
35+
curl
36+
postgresql
37+
];
38+
39+
src = fetchFromGitHub {
40+
owner = "pramsey";
41+
repo = "pgsql-http";
42+
rev = "refs/tags/v${version}";
43+
inherit hash;
44+
};
45+
46+
installPhase = ''
47+
runHook preInstall
2448
25-
installPhase = ''
26-
mkdir -p $out/{lib,share/postgresql/extension}
49+
mkdir -p $out/{lib,share/postgresql/extension}
50+
51+
# Install versioned library
52+
install -Dm755 ${pname}${postgresql.dlSuffix} $out/lib/${pname}--${version}${postgresql.dlSuffix}
53+
54+
cp ${pname}--${version}.sql $out/share/postgresql/extension/${pname}--${version}.sql
55+
56+
# Create versioned control file with modified module path
57+
sed -e "/^default_version =/d" \
58+
-e "s|^module_pathname = .*|module_pathname = '\$libdir/${pname}'|" \
59+
${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control
60+
61+
# For the latest version, create default control file and symlink and copy SQL upgrade scripts
62+
if [[ "${version}" == "${latestVersion}" ]]; then
63+
{
64+
echo "default_version = '${version}'"
65+
cat $out/share/postgresql/extension/${pname}--${version}.control
66+
} > $out/share/postgresql/extension/${pname}.control
67+
ln -sfn ${pname}--${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix}
68+
cp *.sql $out/share/postgresql/extension
69+
fi
70+
71+
runHook postInstall
72+
'';
73+
74+
meta = with lib; {
75+
description = "HTTP client for Postgres";
76+
homepage = "https://github.com/pramsey/${pname}";
77+
inherit (postgresql.meta) platforms;
78+
license = licenses.postgresql;
79+
};
80+
};
81+
in
82+
pkgs.buildEnv {
83+
name = pname;
84+
paths = packages;
85+
86+
pathsToLink = [
87+
"/lib"
88+
"/share/postgresql/extension"
89+
];
90+
postBuild = ''
91+
# Verify all expected library files are present
92+
expectedFiles=${toString (numberOfVersions + 1)}
93+
actualFiles=$(ls -A $out/lib/${pname}*${postgresql.dlSuffix} | wc -l)
2794
28-
cp *${postgresql.dlSuffix} $out/lib
29-
cp *.sql $out/share/postgresql/extension
30-
cp *.control $out/share/postgresql/extension
95+
if [[ "$actualFiles" != "$expectedFiles" ]]; then
96+
echo "Error: Expected $expectedFiles library files, found $actualFiles"
97+
echo "Files found:"
98+
ls -la $out/lib/${pname}*${postgresql.dlSuffix} || true
99+
exit 1
100+
fi
31101
'';
32102

33-
meta = with lib; {
34-
description = "HTTP client for Postgres";
35-
homepage = "https://github.com/pramsey/${pname}";
36-
platforms = postgresql.meta.platforms;
37-
license = licenses.postgresql;
103+
passthru = {
104+
inherit versions numberOfVersions;
105+
pname = "${pname}-all";
106+
version =
107+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
38108
};
39109
}

nix/ext/tests/http.nix

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

nix/ext/versions.json

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
{
2-
"index_advisor": {
3-
"0.2.0": {
2+
"http": {
3+
"1.5": {
4+
"postgresql": [
5+
"15"
6+
],
7+
"hash": "sha256-+N/CXm4arRgvhglanfvO0FNOBUWV5RL8mn/9FpNvcjY="
8+
},
9+
"1.6": {
410
"postgresql": [
511
"15",
6-
"17",
7-
"orioledb-17"
12+
"17"
813
],
9-
"hash": "sha256-G0eQk2bY5CNPMeokN/nb05g03CuiplRf902YXFVQFbs="
14+
"hash": "sha256-C8eqi0q1dnshUAZjIsZFwa5FTYc7vmATF3vv2CReWPM="
1015
}
1116
},
1217
"hypopg": {
@@ -24,6 +29,16 @@
2429
"hash": "sha256-88uKPSnITRZ2VkelI56jZ9GWazG/Rn39QlyHKJKSKMM="
2530
}
2631
},
32+
"index_advisor": {
33+
"0.2.0": {
34+
"postgresql": [
35+
"15",
36+
"17",
37+
"orioledb-17"
38+
],
39+
"hash": "sha256-G0eQk2bY5CNPMeokN/nb05g03CuiplRf902YXFVQFbs="
40+
}
41+
},
2742
"pg_cron": {
2843
"1.3.1": {
2944
"postgresql": [

0 commit comments

Comments
 (0)