diff --git a/nix/ext/plpgsql-check.nix b/nix/ext/plpgsql-check.nix index 41312b1c7..dc4cd946e 100644 --- a/nix/ext/plpgsql-check.nix +++ b/nix/ext/plpgsql-check.nix @@ -4,49 +4,142 @@ fetchFromGitHub, postgresql, postgresqlTestHook, + buildEnv, + makeWrapper, + switch-ext-version, }: +let + pname = "plpgsql_check"; -stdenv.mkDerivation rec { - pname = "plpgsql-check"; - version = "2.7.11"; + # Load version configuration from external file + allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname}; - src = fetchFromGitHub { - owner = "okbob"; - repo = "plpgsql_check"; - rev = "v${version}"; - hash = "sha256-vR3MvfmUP2QEAtXFpq0NCCKck3wZPD+H3QleHtyVQJs="; - }; + # Filter versions compatible with current PostgreSQL version + supportedVersions = lib.filterAttrs ( + _: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql + ) allVersions; - buildInputs = [ postgresql ]; + # Derived version information + versions = lib.naturalSort (lib.attrNames supportedVersions); + latestVersion = lib.last versions; + numberOfVersions = builtins.length versions; + packages = builtins.attrValues ( + lib.mapAttrs (name: value: build name value.hash value.revision) supportedVersions + ); - installPhase = '' - install -D -t $out/lib *${postgresql.dlSuffix} - install -D -t $out/share/postgresql/extension *.sql - install -D -t $out/share/postgresql/extension *.control - ''; + # Build function for individual versions + build = + version: hash: revision: + stdenv.mkDerivation rec { + inherit pname version; - passthru.tests.extension = stdenv.mkDerivation { - name = "plpgsql-check-test"; - dontUnpack = true; - doCheck = true; - buildInputs = [ postgresqlTestHook ]; - nativeCheckInputs = [ (postgresql.withPackages (ps: [ ps.plpgsql_check ])) ]; - postgresqlTestUserOptions = "LOGIN SUPERUSER"; - failureHook = "postgresqlStop"; - checkPhase = '' - runHook preCheck - psql -a -v ON_ERROR_STOP=1 -c "CREATE EXTENSION plpgsql_check;" - runHook postCheck - ''; - installPhase = "touch $out"; - }; + src = fetchFromGitHub { + owner = "okbob"; + repo = "plpgsql_check"; + rev = "v${revision}"; + inherit hash; + }; + + buildInputs = [ postgresql ]; + + installPhase = '' + mkdir -p $out/{lib,share/postgresql/extension} + + # Install shared library with version suffix + mv ${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix} + + # Create version-specific control file + sed -e "/^default_version =/d" \ + -e "s|^module_pathname = .*|module_pathname = '\$libdir/${pname}-${version}'|" \ + ${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control + + # For the latest version, create default control file and symlink and copy SQL upgrade scripts + if [[ "${version}" == "${latestVersion}" ]]; then + cp *.sql $out/share/postgresql/extension + else + mv ./${pname}--${version}.sql $out/share/postgresql/extension/${pname}--${version}.sql + fi + ''; + + passthru.tests.extension = stdenv.mkDerivation { + name = "plpgsql-check-test"; + dontUnpack = true; + doCheck = true; + buildInputs = [ postgresqlTestHook ]; + nativeCheckInputs = [ (postgresql.withPackages (ps: [ ps.plpgsql_check ])) ]; + postgresqlTestUserOptions = "LOGIN SUPERUSER"; + failureHook = "postgresqlStop"; + checkPhase = '' + runHook preCheck + psql -a -v ON_ERROR_STOP=1 -c "CREATE EXTENSION plpgsql_check;" + runHook postCheck + ''; + installPhase = "touch $out"; + }; + + meta = with lib; { + description = "Linter tool for language PL/pgSQL"; + homepage = "https://github.com/okbob/plpgsql_check"; + changelog = "https://github.com/okbob/plpgsql_check/releases/tag/v${version}"; + license = licenses.mit; + maintainers = [ maintainers.marsam ]; + inherit (postgresql.meta) platforms; + }; + }; +in +buildEnv { + name = pname; + paths = packages; + nativeBuildInputs = [ makeWrapper ]; + + pathsToLink = [ + "/lib" + "/share/postgresql/extension" + ]; + + postBuild = '' + { + echo "default_version = '${latestVersion}'" + cat $out/share/postgresql/extension/${pname}--${latestVersion}.control + } > $out/share/postgresql/extension/${pname}.control + ln -sfn ${pname}-${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix} + + # Verify all expected library files are present + expectedFiles=${toString (numberOfVersions + 1)} + actualFiles=$(ls -l $out/lib/${pname}*${postgresql.dlSuffix} | wc -l) + + if [[ "$actualFiles" != "$expectedFiles" ]]; then + echo "Error: Expected $expectedFiles library files, found $actualFiles" + echo "Files found:" + ls -la $out/lib/*${postgresql.dlSuffix} || true + exit 1 + fi + + # Create empty upgrade files between consecutive versions + # plpgsql_check ships without upgrade scripts - extensions are backward-compatible + previous_version="" + for ver in ${lib.concatStringsSep " " versions}; do + if [[ -n "$previous_version" ]]; then + touch $out/share/postgresql/extension/${pname}--''${previous_version}--''${ver}.sql + fi + previous_version=$ver + done + + makeWrapper ${lib.getExe switch-ext-version} $out/bin/switch_plpgsql_check_version \ + --prefix EXT_WRAPPER : "$out" --prefix EXT_NAME : "${pname}" + ''; - meta = with lib; { - description = "Linter tool for language PL/pgSQL"; - homepage = "https://github.com/okbob/plpgsql_check"; - changelog = "https://github.com/okbob/plpgsql_check/releases/tag/v${version}"; - platforms = postgresql.meta.platforms; - license = licenses.mit; - maintainers = [ maintainers.marsam ]; + passthru = { + inherit versions numberOfVersions switch-ext-version; + pname = "${pname}-all"; + hasBackgroundWorker = true; + defaultSettings = { + shared_preload_libraries = [ + "plpgsql" + "plpgsql_check" + ]; + }; + version = + "multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions); }; } diff --git a/nix/ext/tests/plpgsql_check.nix b/nix/ext/tests/plpgsql_check.nix new file mode 100644 index 000000000..5ab950df7 --- /dev/null +++ b/nix/ext/tests/plpgsql_check.nix @@ -0,0 +1,175 @@ +{ self, pkgs }: +let + pname = "plpgsql_check"; + inherit (pkgs) lib; + installedExtension = + postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all"; + 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; + lib = pkg; + withPackages = _: 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; + psql_15 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15; + psql_17 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17; +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 = psql_15; + enableTCPIP = true; + initialScript = pkgs.writeText "init-postgres-with-password" '' + CREATE USER test WITH PASSWORD 'secret'; + ''; + authentication = '' + host test postgres samenet scram-sha-256 + ''; + settings = (installedExtension "15").defaultSettings or { }; + }; + + networking.firewall.allowedTCPPorts = [ config.services.postgresql.settings.port ]; + + specialisation.postgresql17.configuration = { + services.postgresql = { + package = lib.mkForce psql_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 = psql_15; + newPostgresql = psql_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 = True + pg17_configuration = "${pg17-configuration}" + ext_has_background_worker = ${ + if (installedExtension "15") ? hasBackgroundWorker then "True" else "False" + } + sql_test_directory = Path("${../../tests}") + pg_regress_test_name = "${(installedExtension "15").pgRegressTestName or pname}" + + ${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) + + if ext_has_background_worker: + with subtest("Test switch_${pname}_version"): + test.check_switch_extension_with_background_worker(Path("${psql_15}/lib/${pname}.so"), "15") + + with subtest("Check pg_regress with postgresql 15 after installing the last version"): + test.check_pg_regress(Path("${psql_15}/lib/pgxs/src/test/regress/pg_regress"), "15", pg_regress_test_name) + + with subtest("switch to postgresql 17"): + server.succeed( + f"{pg17_configuration}/bin/switch-to-configuration test >&2" + ) + + if ext_has_background_worker: + with subtest("Test switch_${pname}_version"): + test.check_switch_extension_with_background_worker(Path("${psql_17}/lib/${pname}.so"), "17") + + with subtest("Check upgrade path with postgresql 17"): + test.check_upgrade_path("17") + + last_version = versions["17"][-1] + with subtest("Check last version of the extension after postgresql upgrade"): + test.assert_version_matches(last_version) + + with subtest("Check pg_regress with postgresql 17 after extension upgrade"): + test.check_pg_regress(Path("${psql_17}/lib/pgxs/src/test/regress/pg_regress"), "17", pg_regress_test_name) + + with subtest("Check the install of the last version of the extension"): + test.check_install_last_version("17") + + with subtest("Check pg_regress with postgresql 17 after installing the last version"): + test.check_pg_regress(Path("${psql_17}/lib/pgxs/src/test/regress/pg_regress"), "17", pg_regress_test_name) + ''; +} diff --git a/nix/ext/versions.json b/nix/ext/versions.json index b7d88bbd9..701acb206 100644 --- a/nix/ext/versions.json +++ b/nix/ext/versions.json @@ -382,6 +382,23 @@ "hash": "sha256-j5F1PPdwfQRbV8XJ8Mloi8FvZF0MTl4eyIJcBYQy1E4=" } }, + "plpgsql_check": { + "2.2": { + "postgresql": [ + "15" + ], + "hash": "sha256-8HFyIzJ1iF3K2vTlibFallvkMKjFTJ2DO64fORToD8E=", + "revision": "2.2.6" + }, + "2.7": { + "postgresql": [ + "15", + "17" + ], + "hash": "sha256-vR3MvfmUP2QEAtXFpq0NCCKck3wZPD+H3QleHtyVQJs=", + "revision": "2.7.11" + } + }, "postgis": { "3.3.2": { "postgresql": [