Skip to content

Commit 140c610

Browse files
committed
feat: support multiple versions of the pg_stat_monitor extension
Build multiple versions of the pg_stat_monitor extension on different PostgreSQL versions. Add test for the extensions and their upgrade on PostgreSQL 15 and 17.
1 parent c57842f commit 140c610

File tree

4 files changed

+274
-38
lines changed

4 files changed

+274
-38
lines changed

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,8 @@
13841384
devShell = devShells.default;
13851385
} // pkgs.lib.optionalAttrs (system == "aarch64-linux") {
13861386
inherit (basePackages) postgresql_15_debug postgresql_15_src postgresql_orioledb-17_debug postgresql_orioledb-17_src postgresql_17_debug postgresql_17_src;
1387+
} // pkgs.lib.optionalAttrs (system == "x86_64-linux") {
1388+
pg_stat_monitor = import ./nix/ext/tests/pg_stat_monitor.nix { inherit self; inherit pkgs; };
13871389
};
13881390

13891391
# Apps is a list of names of things that can be executed with 'nix run';

nix/ext/pg_stat_monitor.nix

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,105 @@
1-
{ lib, stdenv, fetchFromGitHub, postgresql }:
2-
1+
{
2+
lib,
3+
stdenv,
4+
fetchFromGitHub,
5+
postgresql,
6+
buildEnv,
7+
}:
38
let
4-
# NOTE (aseipp): the 1.x series of pg_stat_monitor has some non-standard and
5-
# weird build logic (Percona projects in general seem to have their own
6-
# strange build harness) where it will try to pick the right .sql file to
7-
# install into the extension dir based on the postgresql major version. for
8-
# our purposes, we only need to support v13 and v14+, so just replicate this
9-
# logic from the makefile and pick the right file here.
10-
#
11-
# this seems to all be cleaned up in version 2.0 of the extension, so ideally
12-
# we could upgrade to it later on and nuke this.
13-
# DEPRECATED sqlFilename = if lib.versionOlder postgresql.version "14"
14-
# then "pg_stat_monitor--1.0.13.sql.in"
15-
# else "pg_stat_monitor--1.0.14.sql.in";
16-
17-
in
18-
stdenv.mkDerivation rec {
199
pname = "pg_stat_monitor";
20-
version = "2.1.0";
2110

22-
buildInputs = [ postgresql ];
11+
# Load version configuration from external file
12+
allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname};
2313

24-
src = fetchFromGitHub {
25-
owner = "percona";
26-
repo = pname;
27-
rev = "refs/tags/${version}";
28-
hash = "sha256-STJVvvrLVLe1JevNu6u6EftzAWv+X+J8lu66su7Or2s=";
29-
};
14+
# Filter versions compatible with current PostgreSQL version
15+
supportedVersions = lib.filterAttrs (
16+
_: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql
17+
) allVersions;
18+
19+
# Derived version information
20+
versions = lib.naturalSort (lib.attrNames supportedVersions);
21+
latestVersion = lib.last versions;
22+
numberOfVersions = builtins.length versions;
23+
packages = builtins.attrValues (
24+
lib.mapAttrs (name: value: build name value.hash value.revision) supportedVersions
25+
);
26+
27+
# Build function for individual versions
28+
build =
29+
version: hash: revision:
30+
stdenv.mkDerivation rec {
31+
inherit pname version;
32+
33+
buildInputs = [ postgresql ];
34+
35+
src = fetchFromGitHub {
36+
owner = "percona";
37+
repo = pname;
38+
rev = "refs/tags/${revision}";
39+
inherit hash;
40+
};
41+
42+
makeFlags = [ "USE_PGXS=1" ];
43+
44+
installPhase = ''
45+
mkdir -p $out/{lib,share/postgresql/extension}
46+
47+
# Install shared library with version suffix
48+
mv ${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix}
49+
50+
# Create version-specific control file
51+
sed -e "/^default_version =/d" \
52+
-e "s|^module_pathname = .*|module_pathname = '\$libdir/${pname}-${version}'|" \
53+
${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control
54+
55+
# For the latest version, create default control file and symlink and copy SQL upgrade scripts
56+
if [[ "${version}" == "${latestVersion}" ]]; then
57+
{
58+
echo "default_version = '${version}'"
59+
cat $out/share/postgresql/extension/${pname}--${version}.control
60+
} > $out/share/postgresql/extension/${pname}.control
61+
ln -sfn ${pname}-${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix}
62+
cp *.sql $out/share/postgresql/extension
63+
else
64+
mv ./pg_stat_monitor--${version}.sql.in $out/share/postgresql/extension/pg_stat_monitor--${version}.sql
65+
fi
66+
'';
67+
68+
meta = with lib; {
69+
description = "Query Performance Monitoring Tool for PostgreSQL";
70+
homepage = "https://github.com/percona/${pname}";
71+
license = licenses.postgresql;
72+
broken = lib.versionOlder postgresql.version "15";
73+
inherit (postgresql.meta) platforms;
74+
};
75+
};
76+
in
77+
buildEnv {
78+
name = pname;
79+
paths = packages;
80+
81+
pathsToLink = [
82+
"/lib"
83+
"/share/postgresql/extension"
84+
];
3085

31-
makeFlags = [ "USE_PGXS=1" ];
86+
postBuild = ''
87+
# Verify all expected library files are present
88+
expectedFiles=${toString (numberOfVersions + 1)}
89+
actualFiles=$(ls -l $out/lib/${pname}*${postgresql.dlSuffix} | wc -l)
3290
33-
installPhase = ''
34-
mkdir -p $out/{lib,share/postgresql/extension}
35-
36-
cp *${postgresql.dlSuffix} $out/lib
37-
cp *.sql $out/share/postgresql/extension
38-
cp *.control $out/share/postgresql/extension
91+
if [[ "$actualFiles" != "$expectedFiles" ]]; then
92+
echo "Error: Expected $expectedFiles library files, found $actualFiles"
93+
echo "Files found:"
94+
ls -la $out/lib/*${postgresql.dlSuffix} || true
95+
exit 1
96+
fi
3997
'';
4098

41-
meta = with lib; {
42-
description = "Query Performance Monitoring Tool for PostgreSQL";
43-
homepage = "https://github.com/percona/${pname}";
44-
platforms = postgresql.meta.platforms;
45-
license = licenses.postgresql;
46-
broken = lib.versionOlder postgresql.version "15";
99+
passthru = {
100+
inherit versions numberOfVersions;
101+
pname = "${pname}-all";
102+
version =
103+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
47104
};
48105
}

nix/ext/tests/pg_stat_monitor.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 = "pg_stat_monitor";
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+
};
106+
testScript =
107+
{ nodes, ... }:
108+
let
109+
pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17";
110+
in
111+
''
112+
versions = {
113+
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
114+
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
115+
}
116+
117+
def run_sql(query):
118+
return server.succeed(f"""sudo -u postgres psql -t -A -F\",\" -c \"{query}\" """).strip()
119+
120+
def check_upgrade_path(pg_version):
121+
with subtest("Check ${pname} upgrade path"):
122+
firstVersion = versions[pg_version][0]
123+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION IF EXISTS ${pname};'")
124+
run_sql(f"""CREATE EXTENSION ${pname} WITH VERSION '{firstVersion}' CASCADE;""")
125+
installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""")
126+
assert installed_version == firstVersion, f"Expected ${pname} version {firstVersion}, but found {installed_version}"
127+
for version in versions[pg_version][1:]:
128+
run_sql(f"""ALTER EXTENSION ${pname} UPDATE TO '{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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"pg_stat_monitor": {
3+
"1.0": {
4+
"postgresql": [
5+
"15"
6+
],
7+
"revision": "1.0.1",
8+
"hash": "sha256-sQEpIknAFOmvNTX2G23X4BvMdy3Ms7sXx7hLZt8jyUk="
9+
},
10+
"2.1": {
11+
"postgresql": [
12+
"15",
13+
"17"
14+
],
15+
"revision": "2.1.0",
16+
"hash": "sha256-STJVvvrLVLe1JevNu6u6EftzAWv+X+J8lu66su7Or2s="
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)