Skip to content

Commit f2816c5

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

File tree

4 files changed

+313
-47
lines changed

4 files changed

+313
-47
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+
pgrouting = import ./nix/ext/tests/pgrouting.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/pgrouting.nix

Lines changed: 134 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,146 @@
1-
{ lib, stdenv, fetchFromGitHub, postgresql, perl, cmake, boost }:
2-
3-
stdenv.mkDerivation rec {
1+
{
2+
lib,
3+
stdenv,
4+
fetchFromGitHub,
5+
postgresql,
6+
perl,
7+
cmake,
8+
boost,
9+
buildEnv,
10+
}:
11+
let
412
pname = "pgrouting";
5-
version = "3.4.1";
613

7-
nativeBuildInputs = [ cmake perl ];
8-
buildInputs = [ postgresql boost ];
14+
# Load version configuration from external file
15+
allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname};
916

10-
src = fetchFromGitHub {
11-
owner = "pgRouting";
12-
repo = pname;
13-
rev = "v${version}";
14-
hash = "sha256-QC77AnPGpPQGEWi6JtJdiNsB2su5+aV2pKg5ImR2B0k=";
15-
};
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;
1621

17-
#disable compile time warnings for incompatible pointer types only on macos and pg16
18-
NIX_CFLAGS_COMPILE = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16")
19-
"-Wno-error=int-conversion -Wno-error=incompatible-pointer-types";
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) supportedVersions
28+
);
2029

21-
cmakeFlags = [
22-
"-DPOSTGRESQL_VERSION=${postgresql.version}"
23-
] ++ lib.optionals (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") [
24-
"-DCMAKE_MACOSX_RPATH=ON"
25-
"-DCMAKE_SHARED_MODULE_SUFFIX=.dylib"
26-
"-DCMAKE_SHARED_LIBRARY_SUFFIX=.dylib"
27-
];
30+
# Build function for individual versions
31+
build =
32+
version: hash:
33+
stdenv.mkDerivation rec {
34+
inherit pname version;
2835

29-
preConfigure = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
30-
export DLSUFFIX=.dylib
31-
export CMAKE_SHARED_LIBRARY_SUFFIX=.dylib
32-
export CMAKE_SHARED_MODULE_SUFFIX=.dylib
33-
export MACOSX_RPATH=ON
34-
'';
36+
nativeBuildInputs = [
37+
cmake
38+
perl
39+
];
40+
buildInputs = [
41+
postgresql
42+
boost
43+
];
3544

36-
postBuild = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
37-
shopt -s nullglob
38-
for file in lib/libpgrouting-*.so; do
39-
if [ -f "$file" ]; then
40-
mv "$file" "''${file%.so}.dylib"
41-
fi
42-
done
43-
shopt -u nullglob
44-
'';
45+
src = fetchFromGitHub {
46+
owner = "pgRouting";
47+
repo = pname;
48+
rev = "v${version}";
49+
inherit hash;
50+
};
51+
52+
#disable compile time warnings for incompatible pointer types only on macos and pg16
53+
NIX_CFLAGS_COMPILE = lib.optionalString (
54+
stdenv.isDarwin && lib.versionAtLeast postgresql.version "16"
55+
) "-Wno-error=int-conversion -Wno-error=incompatible-pointer-types";
56+
57+
cmakeFlags =
58+
[
59+
"-DPOSTGRESQL_VERSION=${postgresql.version}"
60+
]
61+
++ lib.optionals (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") [
62+
"-DCMAKE_MACOSX_RPATH=ON"
63+
"-DCMAKE_SHARED_MODULE_SUFFIX=.dylib"
64+
"-DCMAKE_SHARED_LIBRARY_SUFFIX=.dylib"
65+
];
66+
67+
preConfigure = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
68+
export DLSUFFIX=.dylib
69+
export CMAKE_SHARED_LIBRARY_SUFFIX=.dylib
70+
export CMAKE_SHARED_MODULE_SUFFIX=.dylib
71+
export MACOSX_RPATH=ON
72+
'';
73+
74+
postBuild = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
75+
shopt -s nullglob
76+
for file in lib/libpgrouting-*.so; do
77+
if [ -f "$file" ]; then
78+
mv "$file" "''${file%.so}.dylib"
79+
fi
80+
done
81+
shopt -u nullglob
82+
'';
83+
84+
installPhase = ''
85+
MAJ_MIN_VERSION=${lib.concatStringsSep "." (lib.take 2 (builtins.splitVersion version))}
86+
87+
mkdir -p $out/{lib,share/postgresql/extension}
88+
89+
# Install shared library with version suffix
90+
install -D lib/libpgrouting-$MAJ_MIN_VERSION${postgresql.dlSuffix} -t $out/lib
91+
92+
# Create version-specific control file
93+
sed -e "/^default_version =/d" \
94+
-e "s|^module_pathname = .*|module_pathname = '\$libdir/lib${pname}-$MAJ_MIN_VERSION'|" \
95+
sql/common/${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control
96+
97+
# Copy SQL upgrade scripts
98+
cp sql/${pname}--*.sql $out/share/postgresql/extension
99+
100+
if [[ "${version}" == "${latestVersion}" ]]; then
101+
{
102+
echo "default_version = '${version}'"
103+
cat $out/share/postgresql/extension/${pname}--${version}.control
104+
} > $out/share/postgresql/extension/${pname}.control
105+
ln -sfn $out/lib/lib${pname}-$MAJ_MIN_VERSION${postgresql.dlSuffix} $out/lib/lib${pname}${postgresql.dlSuffix}
106+
fi
107+
'';
108+
109+
meta = with lib; {
110+
description = "A PostgreSQL/PostGIS extension that provides geospatial routing functionality";
111+
homepage = "https://pgrouting.org/";
112+
changelog = "https://github.com/pgRouting/pgrouting/releases/tag/v${version}";
113+
license = licenses.gpl2Plus;
114+
inherit (postgresql.meta) platforms;
115+
};
116+
};
117+
in
118+
buildEnv {
119+
name = pname;
120+
paths = packages;
121+
122+
pathsToLink = [
123+
"/lib"
124+
"/share/postgresql/extension"
125+
];
126+
127+
postBuild = ''
128+
#Verify all expected library files are present
129+
expectedFiles=${toString (numberOfVersions + 1)}
130+
actualFiles=$(ls -l $out/lib/lib${pname}*${postgresql.dlSuffix} | wc -l)
45131
46-
installPhase = ''
47-
install -D lib/*${postgresql.dlSuffix} -t $out/lib
48-
install -D sql/pgrouting--*.sql -t $out/share/postgresql/extension
49-
install -D sql/common/pgrouting.control -t $out/share/postgresql/extension
132+
if [[ "$actualFiles" != "$expectedFiles" ]]; then
133+
echo "Error: Expected $expectedFiles library files, found $actualFiles"
134+
echo "Files found:"
135+
ls -la $out/lib/*${postgresql.dlSuffix} || true
136+
exit 1
137+
fi
50138
'';
51139

52-
meta = with lib; {
53-
description = "A PostgreSQL/PostGIS extension that provides geospatial routing functionality";
54-
homepage = "https://pgrouting.org/";
55-
changelog = "https://github.com/pgRouting/pgrouting/releases/tag/v${version}";
56-
platforms = postgresql.meta.platforms;
57-
license = licenses.gpl2Plus;
140+
passthru = {
141+
inherit versions numberOfVersions;
142+
pname = "${pname}-all";
143+
version =
144+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
58145
};
59146
}

nix/ext/tests/pgrouting.nix

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

nix/ext/versions.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"pgrouting": {
3+
"3.4.1": {
4+
"postgresql": [
5+
"15",
6+
"17"
7+
],
8+
"hash": "sha256-QC77AnPGpPQGEWi6JtJdiNsB2su5+aV2pKg5ImR2B0k="
9+
},
10+
"3.8.0": {
11+
"postgresql": [
12+
"15",
13+
"17"
14+
],
15+
"hash": "sha256-Lvf7TQ3GywbzZmcd9wi3s8I5sCXIQAPeXNTRk/J46to="
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)