diff --git a/flake.nix b/flake.nix index f22415789..d3608fe1f 100644 --- a/flake.nix +++ b/flake.nix @@ -1382,6 +1382,7 @@ psql_15 = makeCheckHarness basePackages.psql_15.bin; psql_17 = makeCheckHarness basePackages.psql_17.bin; psql_orioledb-17 = makeCheckHarness basePackages.psql_orioledb-17.bin; + pg_graphql = import ./nix/tests/pg_graphql.nix { inherit self; inherit pkgs; }; }; # Apps is a list of names of things that can be executed with 'nix run'; diff --git a/nix/cargo-pgrx/buildPgrxExtension.nix b/nix/cargo-pgrx/buildPgrxExtension.nix index 89293ab62..4e65c769b 100644 --- a/nix/cargo-pgrx/buildPgrxExtension.nix +++ b/nix/cargo-pgrx/buildPgrxExtension.nix @@ -27,13 +27,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -{ lib -, cargo-pgrx -, pkg-config -, rustPlatform -, stdenv -, Security -, writeShellScriptBin +{ + lib, + cargo-pgrx, + pkg-config, + rustPlatform, + stdenv, + darwin, + writeShellScriptBin, }: # The idea behind: Use it mostly like rustPlatform.buildRustPackage and so @@ -47,26 +48,31 @@ # unnecessary and heavy dependency. If you set this to true, you also # have to add `rustfmt` to `nativeBuildInputs`. -{ buildAndTestSubdir ? null -, buildType ? "release" -, buildFeatures ? [ ] -, cargoBuildFlags ? [ ] -, postgresql -# cargo-pgrx calls rustfmt on generated bindings, this is not strictly necessary, so we avoid the -# dependency here. Set to false and provide rustfmt in nativeBuildInputs, if you need it, e.g. -# if you include the generated code in the output via postInstall. -, useFakeRustfmt ? true -, usePgTestCheckFeature ? true -, ... -} @ args: +{ + buildAndTestSubdir ? null, + buildType ? "release", + buildFeatures ? [ ], + cargoBuildFlags ? [ ], + postgresql, + # cargo-pgrx calls rustfmt on generated bindings, this is not strictly necessary, so we avoid the + # dependency here. Set to false and provide rustfmt in nativeBuildInputs, if you need it, e.g. + # if you include the generated code in the output via postInstall. + useFakeRustfmt ? true, + usePgTestCheckFeature ? true, + ... +}@args: let - rustfmtInNativeBuildInputs = lib.lists.any (dep: lib.getName dep == "rustfmt") (args.nativeBuildInputs or []); + rustfmtInNativeBuildInputs = lib.lists.any (dep: lib.getName dep == "rustfmt") ( + args.nativeBuildInputs or [ ] + ); in -assert lib.asserts.assertMsg ((args.installPhase or "") == "") - "buildPgrxExtensions overwrites the installPhase, so providing one does nothing"; -assert lib.asserts.assertMsg ((args.buildPhase or "") == "") - "buildPgrxExtensions overwrites the buildPhase, so providing one does nothing"; +assert lib.asserts.assertMsg ( + (args.installPhase or "") == "" +) "buildPgrxExtensions overwrites the installPhase, so providing one does nothing"; +assert lib.asserts.assertMsg ( + (args.buildPhase or "") == "" +) "buildPgrxExtensions overwrites the buildPhase, so providing one does nothing"; assert lib.asserts.assertMsg (useFakeRustfmt -> !rustfmtInNativeBuildInputs) "The parameter useFakeRustfmt is set to true, but rustfmt is included in nativeBuildInputs. Either set useFakeRustfmt to false or remove rustfmt from nativeBuildInputs."; assert lib.asserts.assertMsg (!useFakeRustfmt -> rustfmtInNativeBuildInputs) @@ -75,7 +81,7 @@ assert lib.asserts.assertMsg (!useFakeRustfmt -> rustfmtInNativeBuildInputs) let fakeRustfmt = writeShellScriptBin "rustfmt" '' exit 0 - ''; + ''; maybeDebugFlag = lib.optionalString (buildType != "release") "--debug"; maybeEnterBuildAndTestSubdir = lib.optionalString (buildAndTestSubdir != null) '' export CARGO_TARGET_DIR="$(pwd)/target" @@ -97,19 +103,28 @@ let pg_ctl stop ''; - argsForBuildRustPackage = builtins.removeAttrs args [ "postgresql" "useFakeRustfmt" "usePgTestCheckFeature" ]; + argsForBuildRustPackage = builtins.removeAttrs args [ + "postgresql" + "useFakeRustfmt" + "usePgTestCheckFeature" + ]; # so we don't accidentally `(rustPlatform.buildRustPackage argsForBuildRustPackage) // { ... }` because # we forgot parentheses finalArgs = argsForBuildRustPackage // { - buildInputs = (args.buildInputs or [ ]) ++ lib.optionals stdenv.hostPlatform.isDarwin [ Security ]; - - nativeBuildInputs = (args.nativeBuildInputs or [ ]) ++ [ - cargo-pgrx - postgresql - pkg-config - rustPlatform.bindgenHook - ] ++ lib.optionals useFakeRustfmt [ fakeRustfmt ]; + buildInputs = + (args.buildInputs or [ ]) + ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.apple_sdk.frameworks.Security ]; + + nativeBuildInputs = + (args.nativeBuildInputs or [ ]) + ++ [ + cargo-pgrx + postgresql + pkg-config + rustPlatform.bindgenHook + ] + ++ lib.optionals useFakeRustfmt [ fakeRustfmt ]; buildPhase = '' runHook preBuild @@ -143,6 +158,7 @@ let cargo-pgrx pgrx stop all mv $out/${postgresql}/* $out + mv $out/${postgresql.lib}/* $out rm -rf $out/nix ${maybeLeaveBuildAndTestSubdir} @@ -155,7 +171,10 @@ let RUST_BACKTRACE = "full"; checkNoDefaultFeatures = true; - checkFeatures = (args.checkFeatures or [ ]) ++ (lib.optionals usePgTestCheckFeature [ "pg_test" ]) ++ [ "pg${pgrxPostgresMajor}" ]; + checkFeatures = + (args.checkFeatures or [ ]) + ++ (lib.optionals usePgTestCheckFeature [ "pg_test" ]) + ++ [ "pg${pgrxPostgresMajor}" ]; }; in rustPlatform.buildRustPackage finalArgs diff --git a/nix/cargo-pgrx/default.nix b/nix/cargo-pgrx/default.nix index c1e1a4dee..f0030722e 100644 --- a/nix/cargo-pgrx/default.nix +++ b/nix/cargo-pgrx/default.nix @@ -1,22 +1,24 @@ -{ lib -, darwin -, fetchCrate -, openssl -, pkg-config -, makeRustPlatform -, stdenv -, rust-bin +{ + lib, + darwin, + fetchCrate, + openssl, + pkg-config, + makeRustPlatform, + stdenv, + rust-bin, + rustVersion ? "1.85.1", }: let - rustVersion = "1.85.1"; rustPlatform = makeRustPlatform { cargo = rust-bin.stable.${rustVersion}.default; rustc = rust-bin.stable.${rustVersion}.default; }; - generic = - { version - , hash - , cargoHash + mkCargoPgrx = + { + version, + hash, + cargoHash, }: rustPlatform.buildRustPackage rec { # rust-overlay uses 'cargo-auditable' wrapper for 'cargo' command, but it @@ -33,12 +35,14 @@ let nativeBuildInputs = lib.optionals stdenv.hostPlatform.isLinux [ pkg-config ]; - buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ - openssl - ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ - darwin.apple_sdk.frameworks.Security - ]; - + buildInputs = + lib.optionals stdenv.hostPlatform.isLinux [ + openssl + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ + darwin.apple_sdk.frameworks.Security + ]; + OPENSSL_DIR = "${openssl.dev}"; OPENSSL_INCLUDE_DIR = "${openssl.dev}/include"; OPENSSL_LIB_DIR = "${openssl.out}/lib"; @@ -61,25 +65,25 @@ let }; in { - cargo-pgrx_0_11_3 = generic { + cargo-pgrx_0_11_3 = mkCargoPgrx { version = "0.11.3"; hash = "sha256-UHIfwOdXoJvR4Svha6ud0FxahP1wPwUtviUwUnTmLXU="; cargoHash = "sha256-j4HnD8Zt9uhlV5N7ldIy9564o9qFEqs5KfXHmnQ1WEw="; }; - cargo-pgrx_0_12_6 = generic { + cargo-pgrx_0_12_6 = mkCargoPgrx { version = "0.12.6"; hash = "sha256-7aQkrApALZe6EoQGVShGBj0UIATnfOy2DytFj9IWdEA="; cargoHash = "sha256-Di4UldQwAt3xVyvgQT1gUhdvYUVp7n/a72pnX45kP0w="; }; - cargo-pgrx_0_12_9 = generic { + cargo-pgrx_0_12_9 = mkCargoPgrx { version = "0.12.9"; hash = "sha256-aR3DZAjeEEAjLQfZ0ZxkjLqTVMIEbU0UiZ62T4BkQq8="; cargoHash = "sha256-KTKcol9qSNLQZGW32e6fBb6cPkUGItknyVpLdBYqrBY="; }; - cargo-pgrx_0_14_3 = generic { + cargo-pgrx_0_14_3 = mkCargoPgrx { version = "0.14.3"; hash = "sha256-3TsNpEqNm3Uol5XPW1i0XEbP2fF2+RKB2d7lO6BDnvQ="; cargoHash = "sha256-Ny7j56pwB+2eEK62X0nWfFKQy5fBz+Q1oyvecivxLkk="; }; - inherit rustPlatform; + inherit mkCargoPgrx; } diff --git a/nix/cargo-pgrx/mkPgrxExtension.nix b/nix/cargo-pgrx/mkPgrxExtension.nix new file mode 100644 index 000000000..c83cbc83e --- /dev/null +++ b/nix/cargo-pgrx/mkPgrxExtension.nix @@ -0,0 +1,45 @@ +{ + callPackage, + rustVersion, + pgrxVersion, + makeRustPlatform, + rust-bin, +}: +let + inherit + ( + (callPackage ./default.nix { + inherit rustVersion; + }) + ) + mkCargoPgrx + ; + + rustPlatform = makeRustPlatform { + cargo = rust-bin.stable.${rustVersion}.default; + rustc = rust-bin.stable.${rustVersion}.default; + }; + + versions = builtins.fromJSON (builtins.readFile ./versions.json); + + cargo-pgrx = + let + pgrx = + versions.${pgrxVersion} + or (throw "Unsupported pgrx version ${pgrxVersion}. Available versions: ${builtins.attrNames versions}. Change 'nix/cargo-pgrx/versions.json' to add support for new versions."); + mapping = { + inherit (pgrx) hash; + cargoHash = + pgrx.rust."${rustVersion}".cargoHash + or (throw "Unsupported rust version ${rustVersion} for pgrx version ${pgrxVersion}. Available Rust versions: ${builtins.attrNames pgrx.rust}. Change 'nix/cargo-pgrx/versions.json' to add support for new versions."); + }; + in + mkCargoPgrx { + inherit (mapping) hash cargoHash; + version = pgrxVersion; + }; +in +callPackage ./buildPgrxExtension.nix { + inherit rustPlatform; + inherit cargo-pgrx; +} diff --git a/nix/cargo-pgrx/versions.json b/nix/cargo-pgrx/versions.json new file mode 100644 index 000000000..ca562fb4c --- /dev/null +++ b/nix/cargo-pgrx/versions.json @@ -0,0 +1,37 @@ +{ + "0.11.2": { + "hash": "sha256-8NlpMDFaltTIA8G4JioYm8LaPJ2RGKH5o6sd6lBHmmM=", + "rust": { + "1.70.0": { + "cargoHash": "sha256-qTb3JV3u42EilaK2jP9oa5D09mkuHyRbGGRs9Rg4TzI=" + }, + "1.85.1": { + "cargoHash": "sha256-CbU5B0pvB9ApTZOdYP/ZwuIG8bqGzk/ING2PCM0q2bQ=" + } + } + }, + "0.11.3": { + "hash": "sha256-UHIfwOdXoJvR4Svha6ud0FxahP1wPwUtviUwUnTmLXU=", + "rust": { + "1.85.1": { + "cargoHash": "sha256-KBlT3FARjGcbtHIGDoC6ir3aNXXfDRmIoy990SOqoFg=" + } + } + }, + "0.12.6": { + "hash": "sha256-7aQkrApALZe6EoQGVShGBj0UIATnfOy2DytFj9IWdEA=", + "rust": { + "1.81.0": { + "cargoHash": "sha256-Di4UldQwAt3xVyvgQT1gUhdvYUVp7n/a72pnX45kP0w=" + } + } + }, + "0.12.9": { + "hash": "sha256-aR3DZAjeEEAjLQfZ0ZxkjLqTVMIEbU0UiZ62T4BkQq8=", + "rust": { + "1.81.0": { + "cargoHash": "sha256-53HKhvsKLTa2JCByLEcK3UzWXoM+LTatd98zvS1C9no=" + } + } + } +} diff --git a/nix/ext/pg_graphql.nix b/nix/ext/pg_graphql.nix index ef406f8d4..331d331b0 100644 --- a/nix/ext/pg_graphql.nix +++ b/nix/ext/pg_graphql.nix @@ -1,48 +1,157 @@ -{ lib, stdenv, fetchFromGitHub, postgresql, buildPgrxExtension_0_12_9, cargo, rust-bin }: +{ + callPackages, + lib, + stdenv, + buildEnv, + fetchFromGitHub, + postgresql, + rust-bin, + rsync, +}: let - rustVersion = "1.81.0"; - cargo = rust-bin.stable.${rustVersion}.default; -in -buildPgrxExtension_0_12_9 rec { pname = "pg_graphql"; - version = "1.5.11"; - inherit postgresql; - - src = fetchFromGitHub { - owner = "supabase"; - repo = pname; - rev = "v${version}"; - hash = "sha256-BMZc9ui+2J3U24HzZZVCU5+KWhz+5qeUsRGeptiqbek="; - }; + build = + version: hash: rustVersion: pgrxVersion: + let + cargo = rust-bin.stable.${rustVersion}.default; + previousVersions = lib.filter (v: v != version) versions; # FIXME + mkPgrxExtension = callPackages ../cargo-pgrx/mkPgrxExtension.nix { + inherit rustVersion pgrxVersion; + }; - nativeBuildInputs = [ cargo ]; - buildInputs = [ postgresql ]; - - CARGO = "${cargo}/bin/cargo"; - - cargoLock = { - lockFile = "${src}/Cargo.lock"; - }; - # Setting RUSTFLAGS in env to ensure it's available for all phases - env = lib.optionalAttrs stdenv.isDarwin { - POSTGRES_LIB = "${postgresql}/lib"; - PGPORT = toString (5430 + - (if builtins.match ".*_.*" postgresql.version != null then 1 else 0) + # +1 for OrioleDB - ((builtins.fromJSON (builtins.substring 0 2 postgresql.version)) - 15) * 2); # +2 for each major version - RUSTFLAGS = "-C link-arg=-undefined -C link-arg=dynamic_lookup"; - NIX_BUILD_CORES = "4"; # Limit parallel jobs - CARGO_BUILD_JOBS = "4"; # Limit cargo parallelism - }; - CARGO_PROFILE_RELEASE_BUILD_OVERRIDE_DEBUG = true; + in + mkPgrxExtension rec { + inherit pname version postgresql; + + src = fetchFromGitHub { + owner = "supabase"; + repo = pname; + rev = "v${version}"; + inherit hash; + }; + + nativeBuildInputs = [ cargo ]; + buildInputs = [ postgresql ]; + + CARGO = "${cargo}/bin/cargo"; + + cargoLock = { + lockFile = "${src}/Cargo.lock"; + }; + # Setting RUSTFLAGS in env to ensure it's available for all phases + env = lib.optionalAttrs stdenv.isDarwin { + POSTGRES_LIB = "${postgresql}/lib"; + PGPORT = toString ( + 5430 + + (if builtins.match ".*_.*" postgresql.version != null then 1 else 0) + # +1 for OrioleDB + + ((builtins.fromJSON (builtins.substring 0 2 postgresql.version)) - 15) * 2 + ); # +2 for each major version + RUSTFLAGS = "-C link-arg=-undefined -C link-arg=dynamic_lookup"; + NIX_BUILD_CORES = "4"; # Limit parallel jobs + CARGO_BUILD_JOBS = "4"; # Limit cargo parallelism + }; + CARGO_PROFILE_RELEASE_BUILD_OVERRIDE_DEBUG = true; + + preBuild = '' + echo "Processing git tags..." + echo '${builtins.concatStringsSep "," previousVersions}' | sed 's/,/\n/g' > git_tags.txt + ''; + postInstall = '' + mv $out/lib/${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix} - doCheck = false; + create_sql_files() { + echo "Creating SQL files for previous versions..." + current_version="${version}" + sql_file="$out/share/postgresql/extension/${pname}--$current_version.sql" - meta = with lib; { - description = "GraphQL support for PostreSQL"; - homepage = "https://github.com/supabase/${pname}"; - platforms = postgresql.meta.platforms; - license = licenses.postgresql; + if [ -f "$sql_file" ]; then + while read -r previous_version; do + if [ "$(printf '%s\n' "$previous_version" "$current_version" | sort -V | head -n1)" = "$previous_version" ] && [ "$previous_version" != "$current_version" ]; then + new_file="$out/share/postgresql/extension/${pname}--$previous_version--$current_version.sql" + sed -i 's/create\s\+function/CREATE OR REPLACE FUNCTION/Ig' "$sql_file" + echo "Creating $new_file" + { + echo "DROP EVENT TRIGGER IF EXISTS graphql_watch_ddl;" + echo "DROP EVENT TRIGGER IF EXISTS graphql_watch_drop;" + cat $sql_file + } > "$new_file" + fi + done < git_tags.txt + else + echo "Warning: $sql_file not found" + fi + rm git_tags.txt + } + + create_control_files() { + sed -e "/^default_version =/d" \ + -e "s|^module_pathname = .*|module_pathname = '\$libdir/${pname}'|" \ + ${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control + rm $out/share/postgresql/extension/${pname}.control + + if [[ "${version}" == "${latestVersion}" ]]; then + { + 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} + fi + } + + create_sql_files + create_control_files + ''; + + preCheck = '' + export PGRX_HOME=$(mktemp -d) + export NIX_PGLIBDIR=$PGRX_HOME/${lib.versions.major postgresql.version}/lib + ${lib.getExe rsync} --chmod=ugo+w -a ${postgresql}/ ${postgresql.lib}/ $PGRX_HOME/${lib.versions.major postgresql.version}/ + cargo pgrx init --pg${lib.versions.major postgresql.version} $PGRX_HOME/${lib.versions.major postgresql.version}/bin/pg_config + ''; + + doCheck = false; + + meta = with lib; { + description = "GraphQL support for PostreSQL"; + homepage = "https://github.com/supabase/${pname}"; + license = licenses.postgresql; + inherit (postgresql.meta) platforms; + }; + }; + allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).pg_graphql; + supportedVersions = lib.filterAttrs ( + _: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql + ) allVersions; + 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.rust value.pgrx) supportedVersions + ); + +in +buildEnv { + name = pname; + paths = packages; + pathsToLink = [ + "/lib" + "/share/postgresql/extension" + ]; + postBuild = '' + # checks + (set -x + test "$(ls -A $out/lib/${pname}*${postgresql.dlSuffix} | wc -l)" = "${ + toString (numberOfVersions + 1) + }" + ) + ''; + passthru = { + inherit versions numberOfVersions; + pname = "${pname}-all"; + version = + "multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions); }; } diff --git a/nix/ext/versions.json b/nix/ext/versions.json new file mode 100644 index 000000000..cf09616ae --- /dev/null +++ b/nix/ext/versions.json @@ -0,0 +1,78 @@ +{ + "pg_graphql": { + "1.4.4": { + "postgresql": [ + "15" + ], + "hash": "sha256-Kxo4o8+hfSTOjvhYyGF2BpksWfW/AMCCH4qom4AGw18=", + "pgrx": "0.11.2", + "rust": "1.70.0" + }, + "1.5.0": { + "postgresql": [ + "15" + ], + "hash": "sha256-28ANRZyF22qF2YAxNAAkPfGOM3+xiO6IHdXsTp0CTQE=", + "pgrx": "0.11.2", + "rust": "1.85.1" + }, + "1.5.1": { + "postgresql": [ + "15" + ], + "hash": "sha256-cAiD2iSFmZwC+Zy0x+MABseWCxXRtRY74Dj0oBKet+o=", + "pgrx": "0.11.2", + "rust": "1.85.1" + }, + "1.5.1-mergeless": { + "postgresql": [ + "15" + ], + "hash": "sha256-X4YR2ishxWCQDMwxHKuGGjlpbpRrUBoHeeLfM/UIHWc=", + "pgrx": "0.11.2", + "rust": "1.85.1" + }, + "1.5.4": { + "postgresql": [ + "15" + ], + "hash": "sha256-419RVol44akUFZ/0B97VjAXCUrWcKFDAFuVjvJnbkP4=", + "pgrx": "0.11.3", + "rust": "1.85.1" + }, + "1.5.6": { + "postgresql": [ + "15" + ], + "hash": "sha256-v/40TR/1bplbQuD3Hv3gE7oh6cfn9fA6U5s+FTAwxtA=", + "pgrx": "0.11.3", + "rust": "1.85.1" + }, + "1.5.7": { + "postgresql": [ + "15" + ], + "hash": "sha256-Q6XfcTKVOjo5pGy8QACc4QCHolKxEGU8e0TTC6Zg8go=", + "pgrx": "0.11.3", + "rust": "1.85.1" + }, + "1.5.9": { + "postgresql": [ + "15", + "17" + ], + "hash": "sha256-YpLN43FtLhp2cb7cyM+4gEx8GTwsRiKTfxaMq0b8hk0=", + "pgrx": "0.12.6", + "rust": "1.81.0" + }, + "1.5.11": { + "postgresql": [ + "15", + "17" + ], + "hash": "sha256-BMZc9ui+2J3U24HzZZVCU5+KWhz+5qeUsRGeptiqbek=", + "pgrx": "0.12.9", + "rust": "1.81.0" + } + } +} diff --git a/nix/tests/pg_graphql.nix b/nix/tests/pg_graphql.nix new file mode 100644 index 000000000..d5910b117 --- /dev/null +++ b/nix/tests/pg_graphql.nix @@ -0,0 +1,158 @@ +{ self, pkgs }: +let + pname = "pg_graphql"; + 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; +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; + }; + users.users.root.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIo+ulCUfJjnCVgfM4946Ih5Nm8DeZZiayYeABHGPEl7 jfroche" + ]; + + services.postgresql = { + enable = true; + package = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15; + }; + + specialisation.postgresql17.configuration = { + services.postgresql = { + package = lib.mkForce (postgresqlWithExtension self.packages.${pkgs.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.system}.postgresql_15; + newPostgresql = postgresqlWithExtension self.packages.${pkgs.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}';""") + 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};'") + installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""") + latestVersion = versions["15"][-1] + assert f"${pname},{latestVersion}" in 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"): + installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""") + latestVersion = versions["17"][-1] + assert f"${pname},{latestVersion}" in installed_extensions + + check_upgrade_path("17") + ''; +}