From 9edddb59c97335d2a7ed9205c8e0ae9071439ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Thu, 6 Nov 2025 17:01:50 +0100 Subject: [PATCH 1/4] fix(exts): handle pg_upgrade generated update_extensions.sql script in the extension tests pg_upgrade may generate an update_extensions.sql script to update extensions after a major version upgrade. This commit modifies the extension tests to check for the presence of this script after upgrading PostgreSQL to version 17. If the script exists, it is executed to ensure that the extensions are updated correctly. --- nix/ext/tests/default.nix | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/nix/ext/tests/default.nix b/nix/ext/tests/default.nix index 8777a72587..fc69ed02b6 100644 --- a/nix/ext/tests/default.nix +++ b/nix/ext/tests/default.nix @@ -215,13 +215,25 @@ let "" } + has_update_script = False with subtest("switch to postgresql 17"): server.succeed( f"{pg17_configuration}/bin/switch-to-configuration test >&2" ) + has_update_script = server.succeed( + "test -f /var/lib/postgresql/update_extensions.sql && echo 'yes' || echo 'no'" + ).strip() == "yes" + if has_update_script: + # Run the extension update script generated during the upgrade + test.run_sql_file("/var/lib/postgresql/update_extensions.sql") with subtest("Check last version of the extension after postgresql upgrade"): - test.assert_version_matches(last_version) + if has_update_script: + # If there was an update script, the last version should be installed + test.assert_version_matches(versions["17"][-1]) + else: + # Otherwise, the version should match the last version from postgresql 15 + test.assert_version_matches(last_version) ${ if support_upgrade then From b0efb7128f91c9bf678dfd3639ed3763a0ccb806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Thu, 6 Nov 2025 17:01:50 +0100 Subject: [PATCH 2/4] feat(ext): use the generic postgres extension test for postgis Thanks to the improvement related to the generated pg_upgrade script, we can now reuse the generic postgres extension test for postgis. --- nix/ext/tests/default.nix | 3 +- nix/ext/tests/postgis.nix | 168 -------------------------------------- 2 files changed, 2 insertions(+), 169 deletions(-) delete mode 100644 nix/ext/tests/postgis.nix diff --git a/nix/ext/tests/default.nix b/nix/ext/tests/default.nix index fc69ed02b6..4083e093cb 100644 --- a/nix/ext/tests/default.nix +++ b/nix/ext/tests/default.nix @@ -283,11 +283,12 @@ builtins.listToAttrs ( "pg_hashids" "pg_jsonschema" "pg_net" + "pg_partman" "pg_repack" "pg_stat_monitor" "pg_tle" "pgaudit" - "pg_partman" + "postgis" "vector" "wal2json" "wrappers" diff --git a/nix/ext/tests/postgis.nix b/nix/ext/tests/postgis.nix deleted file mode 100644 index 7599ab7c99..0000000000 --- a/nix/ext/tests/postgis.nix +++ /dev/null @@ -1,168 +0,0 @@ -{ self, pkgs }: -let - pname = "postgis"; - inherit (pkgs) lib; - installedExtension = - postgresMajorVersion: - self.legacyPackages.${pkgs.stdenv.hostPlatform.system}."psql_${postgresMajorVersion}".exts."${ - pname - }"; - versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions; - postgresqlWithExtension = - postgresql: - let - majorVersion = lib.versions.major postgresql.version; - pkg = pkgs.buildEnv { - name = "postgresql-${majorVersion}-${pname}"; - paths = [ - postgresql - postgresql.lib - (installedExtension majorVersion) - ]; - passthru = { - inherit (postgresql) version psqlSchema; - installedExtensions = [ (installedExtension majorVersion) ]; - lib = pkg; - withPackages = _: pkg; - withJIT = pkg; - withoutJIT = pkg; - }; - nativeBuildInputs = [ pkgs.makeWrapper ]; - pathsToLink = [ - "/" - "/bin" - "/lib" - ]; - postBuild = '' - wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib - wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib - wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib - ''; - }; - in - pkg; -in -self.inputs.nixpkgs.lib.nixos.runTest { - name = pname; - hostPkgs = pkgs; - nodes.server = - { config, ... }: - { - virtualisation = { - forwardPorts = [ - { - from = "host"; - host.port = 13022; - guest.port = 22; - } - ]; - }; - services.openssh = { - enable = true; - }; - - services.postgresql = { - enable = true; - package = postgresqlWithExtension self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_15; - }; - - specialisation.postgresql17.configuration = { - services.postgresql = { - package = lib.mkForce ( - postgresqlWithExtension self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_17 - ); - }; - - systemd.services.postgresql-migrate = { - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = "postgres"; - Group = "postgres"; - StateDirectory = "postgresql"; - WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}"; - }; - script = - let - oldPostgresql = - postgresqlWithExtension - self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_15; - newPostgresql = - postgresqlWithExtension - self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_17; - oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}"; - newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}"; - in - '' - if [[ ! -d ${newDataDir} ]]; then - install -d -m 0700 -o postgres -g postgres "${newDataDir}" - ${newPostgresql}/bin/initdb -D "${newDataDir}" - ${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \ - --old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin" - else - echo "${newDataDir} already exists" - fi - ''; - }; - - systemd.services.postgresql = { - after = [ "postgresql-migrate.service" ]; - requires = [ "postgresql-migrate.service" ]; - }; - }; - }; - testScript = - { nodes, ... }: - let - pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17"; - in - '' - versions = { - "15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}], - "17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}], - } - - def run_sql(query): - return server.succeed(f"""sudo -u postgres psql -t -A -F\",\" -c \"{query}\" """).strip() - - def check_upgrade_path(pg_version): - with subtest("Check ${pname} upgrade path"): - firstVersion = versions[pg_version][0] - server.succeed("sudo -u postgres psql -c 'DROP EXTENSION IF EXISTS ${pname};'") - run_sql(f"""CREATE EXTENSION ${pname} WITH VERSION '{firstVersion}' CASCADE;""") - installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""") - assert installed_version == firstVersion, f"Expected ${pname} version {firstVersion}, but found {installed_version}" - for version in versions[pg_version][1:]: - run_sql(f"""ALTER EXTENSION ${pname} UPDATE TO '{version}';""") - installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""") - assert installed_version == version, f"Expected ${pname} version {version}, but found {installed_version}" - - start_all() - - server.wait_for_unit("multi-user.target") - server.wait_for_unit("postgresql.service") - - check_upgrade_path("15") - - with subtest("Check ${pname} latest extension version"): - server.succeed("sudo -u postgres psql -c 'DROP EXTENSION ${pname};'") - server.succeed("sudo -u postgres psql -c 'CREATE EXTENSION ${pname} CASCADE;'") - installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension where extname = '${pname}';""") - latestVersion = versions["15"][-1] - majMinVersion = ".".join(latestVersion.split('.')[:1]) - assert f"${pname},{majMinVersion}" in installed_extensions, f"Expected ${pname} version {latestVersion}, but found {installed_extensions}" - - with subtest("switch to postgresql 17"): - server.succeed( - "${pg17-configuration}/bin/switch-to-configuration test >&2" - ) - - with subtest("Check ${pname} latest extension version after upgrade"): - installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""") - latestVersion = versions["17"][-1] - majMinVersion = ".".join(latestVersion.split('.')[:1]) - assert f"${pname},{majMinVersion}" in installed_extensions - - check_upgrade_path("17") - ''; -} From d01605b0c634d1576afefc19b593b0636f7252e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Thu, 6 Nov 2025 17:25:45 +0100 Subject: [PATCH 3/4] chore(ext): document exceptions to generic extension tests This helps us identify why certain extensions do not use the generic tests. --- nix/ext/tests/http.nix | 3 + nix/ext/tests/pg_repack.nix | 177 ++++++++++++++++++++++++++++++++++++ nix/ext/tests/pgrouting.nix | 1 + 3 files changed, 181 insertions(+) create mode 100644 nix/ext/tests/pg_repack.nix diff --git a/nix/ext/tests/http.nix b/nix/ext/tests/http.nix index fab6a0d204..22bd45c473 100644 --- a/nix/ext/tests/http.nix +++ b/nix/ext/tests/http.nix @@ -166,3 +166,6 @@ self.inputs.nixpkgs.lib.nixos.runTest { check_upgrade_path("17") ''; } +# We don't use the generic test for this extension because: +# http is not using semver versioning scheme, so we need to adapt the version checks +# otherwise the test fails with ERROR: extension "http" has no installation script nor update path for version "1.5.0" diff --git a/nix/ext/tests/pg_repack.nix b/nix/ext/tests/pg_repack.nix new file mode 100644 index 0000000000..c81e29b2d1 --- /dev/null +++ b/nix/ext/tests/pg_repack.nix @@ -0,0 +1,177 @@ +{ self, pkgs }: +let + pname = "pg_repack"; + inherit (pkgs) lib; + installedExtension = + postgresMajorVersion: + self.legacyPackages.${pkgs.stdenv.hostPlatform.system}."psql_${postgresMajorVersion}".exts."${ + pname + }"; + versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions; + postgresqlWithExtension = + postgresql: + let + majorVersion = lib.versions.major postgresql.version; + pkg = pkgs.buildEnv { + name = "postgresql-${majorVersion}-${pname}"; + paths = [ + postgresql + postgresql.lib + (installedExtension majorVersion) + ]; + passthru = { + inherit (postgresql) version psqlSchema; + installedExtensions = [ (installedExtension majorVersion) ]; + lib = pkg; + withPackages = _: pkg; + withJIT = pkg; + withoutJIT = pkg; + }; + nativeBuildInputs = [ pkgs.makeWrapper ]; + pathsToLink = [ + "/" + "/bin" + "/lib" + ]; + postBuild = '' + wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib + wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib + wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib + ''; + }; + in + pkg; +in +self.inputs.nixpkgs.lib.nixos.runTest { + name = pname; + hostPkgs = pkgs; + nodes.server = + { config, ... }: + { + virtualisation = { + forwardPorts = [ + { + from = "host"; + host.port = 13022; + guest.port = 22; + } + ]; + }; + services.openssh = { + enable = true; + }; + + services.postgresql = { + enable = true; + package = postgresqlWithExtension self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_15; + enableTCPIP = true; + authentication = '' + local all postgres peer map=postgres + local all all peer map=root + ''; + identMap = '' + root root supabase_admin + postgres postgres postgres + ''; + ensureUsers = [ + { + name = "supabase_admin"; + ensureClauses.superuser = true; + } + ]; + }; + + networking.firewall.allowedTCPPorts = [ config.services.postgresql.settings.port ]; + + specialisation.postgresql17.configuration = { + services.postgresql = { + package = lib.mkForce ( + postgresqlWithExtension self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_17 + ); + }; + + systemd.services.postgresql-migrate = { + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "postgres"; + Group = "postgres"; + StateDirectory = "postgresql"; + WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}"; + }; + script = + let + oldPostgresql = + postgresqlWithExtension + self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_15; + newPostgresql = + postgresqlWithExtension + self.packages.${pkgs.stdenv.hostPlatform.system}.postgresql_17; + oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}"; + newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}"; + in + '' + if [[ ! -d ${newDataDir} ]]; then + install -d -m 0700 -o postgres -g postgres "${newDataDir}" + ${newPostgresql}/bin/initdb -D "${newDataDir}" + ${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \ + --old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin" + else + echo "${newDataDir} already exists" + fi + ''; + }; + + systemd.services.postgresql = { + after = [ "postgresql-migrate.service" ]; + requires = [ "postgresql-migrate.service" ]; + }; + }; + }; + testScript = + { nodes, ... }: + let + pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17"; + in + '' + from pathlib import Path + versions = { + "15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}], + "17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}], + } + extension_name = "${pname}" + support_upgrade = False + pg17_configuration = "${pg17-configuration}" + sql_test_directory = Path("${../../tests}") + + ${builtins.readFile ./lib.py} + + start_all() + + server.wait_for_unit("multi-user.target") + server.wait_for_unit("postgresql.service") + + test = PostgresExtensionTest(server, extension_name, versions, sql_test_directory, support_upgrade) + + with subtest("Check upgrade path with postgresql 15"): + test.check_upgrade_path("15") + + last_version = None + with subtest("Check the install of the last version of the extension"): + last_version = test.check_install_last_version("15") + + with subtest("switch to postgresql 17"): + server.succeed( + f"{pg17_configuration}/bin/switch-to-configuration test >&2" + ) + + with subtest("Check last version of the extension after upgrade"): + test.assert_version_matches(last_version) + + with subtest("Check upgrade path with postgresql 17"): + test.check_upgrade_path("17") + ''; +} +# We don't use the generic test for this extension because: +# pg_repack does not support upgrade as the extension doesn't provide the upgrade SQL scripts +# and fails with ERROR: extension "pg_repack" has no update path from version "1.4.8" to version "1.5.0" diff --git a/nix/ext/tests/pgrouting.nix b/nix/ext/tests/pgrouting.nix index 15fac76c5d..5a023d5caa 100644 --- a/nix/ext/tests/pgrouting.nix +++ b/nix/ext/tests/pgrouting.nix @@ -242,3 +242,4 @@ self.inputs.nixpkgs.lib.nixos.runTest { check_upgrade_path("orioledb-17") ''; } +# We don't use the generic test for this extension because: it requires postgis to be installed as well. From 55113c3599f066d6abc2fecf743cd82feea8a7e1 Mon Sep 17 00:00:00 2001 From: Yvan Sraka Date: Mon, 19 Jan 2026 12:45:28 +0100 Subject: [PATCH 4/4] fix(exts): wait for postgresql.service after switch-to-configuration Address CodeRabbit feedback to prevent race conditions when running update scripts by ensuring PostgreSQL is fully ready before proceeding. --- nix/ext/tests/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/ext/tests/default.nix b/nix/ext/tests/default.nix index 4083e093cb..ce2b21a74f 100644 --- a/nix/ext/tests/default.nix +++ b/nix/ext/tests/default.nix @@ -220,6 +220,7 @@ let server.succeed( f"{pg17_configuration}/bin/switch-to-configuration test >&2" ) + server.wait_for_unit("postgresql.service") has_update_script = server.succeed( "test -f /var/lib/postgresql/update_extensions.sql && echo 'yes' || echo 'no'" ).strip() == "yes"