diff --git a/flake.nix b/flake.nix index 335ccdf42..c829a2349 100644 --- a/flake.nix +++ b/flake.nix @@ -1382,6 +1382,8 @@ inherit (basePackages) wal-g-2 wal-g-3 dbmate-tool pg_regress; } // pkgs.lib.optionalAttrs (system == "aarch64-linux") { inherit (basePackages) postgresql_15_debug postgresql_15_src postgresql_orioledb-17_debug postgresql_orioledb-17_src postgresql_17_debug postgresql_17_src; + } // pkgs.lib.optionalAttrs (system == "x86_64-linux") { + pg_graphql = import ./nix/ext/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..d6d199407 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" @@ -88,7 +94,13 @@ let export PGRX_HOME=$(mktemp -d) export PGDATA="$PGRX_HOME/data-${pgrxPostgresMajor}/" cargo-pgrx pgrx init "--pg${pgrxPostgresMajor}" ${lib.getDev postgresql}/bin/pg_config - echo "unix_socket_directories = '$(mktemp -d)'" > "$PGDATA/postgresql.conf" + + # unix sockets work in sandbox, too. + export PGHOST="$(mktemp -d)" + cat > "$PGDATA/postgresql.conf" < $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_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 = '' + create_sql_files() { + PREVIOUS_VERSION="" + while IFS= read -r i; do + FILENAME=$(basename "$i") + DIRNAME=$(dirname "$i") + VERSION="$(grep -oE '[0-9]+\.[0-9]+\.[0-9]+' <<< $FILENAME)" + if [[ "$PREVIOUS_VERSION" != "" ]]; then + echo "Processing $i" + MIGRATION_FILENAME="$DIRNAME/''${FILENAME/$VERSION/$PREVIOUS_VERSION--$VERSION}" + cp "$i" "$MIGRATION_FILENAME" + fi + PREVIOUS_VERSION="$VERSION" + done < <(find $out -name '*.sql' | sort -V) + } - doCheck = false; + create_sql_files - meta = with lib; { - description = "GraphQL support for PostreSQL"; - homepage = "https://github.com/supabase/${pname}"; - platforms = postgresql.meta.platforms; - license = licenses.postgresql; + # 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/tests/pg_graphql.nix b/nix/ext/tests/pg_graphql.nix new file mode 100644 index 000000000..d5910b117 --- /dev/null +++ b/nix/ext/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") + ''; +} diff --git a/nix/ext/versions.json b/nix/ext/versions.json new file mode 100644 index 000000000..763d73147 --- /dev/null +++ b/nix/ext/versions.json @@ -0,0 +1,110 @@ +{ + "pg_graphql": { + "1.2.2": { + "postgresql": [ + "15" + ], + "hash": "sha256-SKbUDasdhz/L5UDyMH4gXmFfHHhGx81H90gfIclGwjU=", + "pgrx": "0.9.5", + "rust": "1.70.0" + }, + "1.2.3": { + "postgresql": [ + "15" + ], + "hash": "sha256-876bRLAUstBcCnhDvO+MllAC1VM//LLW1W6h028zr/8=", + "pgrx": "0.9.7", + "rust": "1.70.0" + }, + "1.4.1": { + "postgresql": [ + "15" + ], + "hash": "sha256-onyVIM5/l/cpYeAa7ya6h7bmRfqG1dPjc67oGGkgsOs=", + "pgrx": "0.10.2", + "rust": "1.70.0" + }, + "1.4.2": { + "postgresql": [ + "15" + ], + "hash": "sha256-/JweVmfcWqDtFeP3tBl/g6hlqAqbwPHpcHdX9HeqZuU=", + "pgrx": "0.10.2", + "rust": "1.70.0" + }, + "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" + } + } +}