supabase-postgres.git / summary / log / commit / refs

commit    ad9cfaa31c64bbae7f752180efc92ff54ff27b95
Author:   Márton Boros <martonboros@gmail.com>
Commit:   GitHub <noreply@github.com>
Date:     Fri Sep 04 15:59:37 2026 +0000

    feat(nix): expose per-version extension packages, catalog, and update tools (#2317)
    
    * feat(nix): expose per-version extension packages, catalog, and instance tools
    
    Adds passthru.perVersion (version -> single-version derivation) to each
    multi-version extension (18 files, one line each) -- the attrset already
    existed inline as the buildEnv input, this surfaces it instead of
    discarding it. No change to any buildEnv output or extension identity.
    
    New flake-parts module nix/packages/extension-catalog.nix, exposing
    (packages + legacyPackages):
    
    - site-extensions-catalog-<major> (15/17/orioledb-17): pg-extensions-
      catalog.json mapping { ext -> { extversion -> store path } }, plus
      bin/{resolve,update} preset with PG_EXTENSIONS_CATALOG. Assembled at
      build time from the wrappers' control files, so keys are real
      extversions (pgsql-http nix "1.5.0" -> "1.5") and include
      sub-extensions (postgis_topology, plcoffee, ...). unsafeDiscardReferences
      keeps the closure to just the JSON (6.8 KiB, not 2.4 GiB).
    - site-extensions-versions-<major> (legacyPackages): the generic
      single-version packages the catalog points at (lean, no multi-version
      buildEnv), so the CI matrix builds and caches them.
    - Generic tools, one version each (not per-major): site-extensions-resolve
      (manifest json -> store paths), download-nix-store-paths (retry/timeout
      substitution, not extension-specific), site-extensions-update (chains
      both + nix-env --install --remove-all onto PROFILE, atomic).
    
    Part of PSQL-1530.
    
    * docs(nix): clarify env vs profile terminology in comment
    
    * bashism
    
    Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com>
    
    * bashism
    
    Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com>
    
    * bashism
    
    Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com>
    
    * bashism
    
    Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com>
    
    * bashism
    
    Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com>
    
    * bashism
    
    Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com>
    
    * site-extensions-resolve: fail on empty extension name instead of skipping
    
    * download-nix-store-paths: let nix parallelize+retry instead of serial timeout loop
    
    * inline download-nix-store-paths into site-extensions-update
    
    * inline single-use script helper into site-extensions-resolve
    
    * site-extensions-update: require manifest arg, no silent default
    
    * site-extensions-update: rename PROFILE override to NIX_PROFILE
    
    ---------
    
    Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com>


nix/ext/hypopg.nix | 1 + nix/ext/index_advisor.nix | 1 + nix/ext/pg-safeupdate.nix | 1 + nix/ext/pg_cron/default.nix | 1 + nix/ext/pg_net.nix | 1 + nix/ext/pg_partman.nix | 1 + nix/ext/pg_repack.nix | 1 + nix/ext/pg_tle.nix | 1 + nix/ext/pgaudit.nix | 1 + nix/ext/pgmq/default.nix | 1 + nix/ext/pgroonga/default.nix | 1 + nix/ext/pgrouting/default.nix | 1 + nix/ext/pgsodium.nix | 1 + nix/ext/pgsql-http.nix | 1 + nix/ext/pgvector.nix | 1 + nix/ext/plv8/default.nix | 1 + nix/ext/postgis.nix | 1 + nix/ext/vault.nix | 1 + nix/packages/default.nix | 2 +- nix/packages/extension-catalog.nix | 165 +++++++++++++++++++++++++++++++++++++ 20 files changed, 184 insertions(+), 1 deletion(-) diff --git a/nix/ext/hypopg.nix b/nix/ext/hypopg.nix index d95a1c7f..9bd49272 100644 --- a/nix/ext/hypopg.nix +++ b/nix/ext/hypopg.nix @@ -98,6 +98,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/index_advisor.nix b/nix/ext/index_advisor.nix index 3130d4bc..58921271 100644 --- a/nix/ext/index_advisor.nix +++ b/nix/ext/index_advisor.nix @@ -83,6 +83,7 @@ pkgs.buildEnv { ]; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg-safeupdate.nix b/nix/ext/pg-safeupdate.nix index 168772ce..452e9c2c 100644 --- a/nix/ext/pg-safeupdate.nix +++ b/nix/ext/pg-safeupdate.nix @@ -83,6 +83,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_cron/default.nix b/nix/ext/pg_cron/default.nix index 1fd3b403..197420c9 100644 --- a/nix/ext/pg_cron/default.nix +++ b/nix/ext/pg_cron/default.nix @@ -139,6 +139,7 @@ buildEnv { }; passthru = { + perVersion = lib.mapAttrs (name: value: build name value) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version latestOnly; diff --git a/nix/ext/pg_net.nix b/nix/ext/pg_net.nix index 87b994a5..f769bad3 100644 --- a/nix/ext/pg_net.nix +++ b/nix/ext/pg_net.nix @@ -145,6 +145,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_partman.nix b/nix/ext/pg_partman.nix index 4c36e765..0c8a4eee 100644 --- a/nix/ext/pg_partman.nix +++ b/nix/ext/pg_partman.nix @@ -100,6 +100,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version libName latestOnly; diff --git a/nix/ext/pg_repack.nix b/nix/ext/pg_repack.nix index 1a7500ea..78d97640 100644 --- a/nix/ext/pg_repack.nix +++ b/nix/ext/pg_repack.nix @@ -140,6 +140,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_tle.nix b/nix/ext/pg_tle.nix index 45ba6703..9c7ef867 100644 --- a/nix/ext/pg_tle.nix +++ b/nix/ext/pg_tle.nix @@ -111,6 +111,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgaudit.nix b/nix/ext/pgaudit.nix index 74429fa4..8fb727c6 100644 --- a/nix/ext/pgaudit.nix +++ b/nix/ext/pgaudit.nix @@ -240,6 +240,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgmq/default.nix b/nix/ext/pgmq/default.nix index c12531f2..7e2076a2 100644 --- a/nix/ext/pgmq/default.nix +++ b/nix/ext/pgmq/default.nix @@ -103,6 +103,7 @@ buildEnv { pathsToLink = [ "/share/postgresql/extension" ]; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgroonga/default.nix b/nix/ext/pgroonga/default.nix index 42345bd4..b9c3829a 100644 --- a/nix/ext/pgroonga/default.nix +++ b/nix/ext/pgroonga/default.nix @@ -181,6 +181,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgrouting/default.nix b/nix/ext/pgrouting/default.nix index 87d190cd..cc00281e 100644 --- a/nix/ext/pgrouting/default.nix +++ b/nix/ext/pgrouting/default.nix @@ -153,6 +153,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgsodium.nix b/nix/ext/pgsodium.nix index 5a615a87..b5a2dcb7 100644 --- a/nix/ext/pgsodium.nix +++ b/nix/ext/pgsodium.nix @@ -112,6 +112,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgsql-http.nix b/nix/ext/pgsql-http.nix index 5815f916..885fcc47 100644 --- a/nix/ext/pgsql-http.nix +++ b/nix/ext/pgsql-http.nix @@ -113,6 +113,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgvector.nix b/nix/ext/pgvector.nix index 5f613b3d..2db9d0c1 100644 --- a/nix/ext/pgvector.nix +++ b/nix/ext/pgvector.nix @@ -96,6 +96,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/plv8/default.nix b/nix/ext/plv8/default.nix index 93a17434..73199193 100644 --- a/nix/ext/plv8/default.nix +++ b/nix/ext/plv8/default.nix @@ -238,6 +238,7 @@ buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/postgis.nix b/nix/ext/postgis.nix index 99afea6f..3f722d9a 100644 --- a/nix/ext/postgis.nix +++ b/nix/ext/postgis.nix @@ -256,6 +256,7 @@ in ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/vault.nix b/nix/ext/vault.nix index 18ffe0aa..9ab5391b 100644 --- a/nix/ext/vault.nix +++ b/nix/ext/vault.nix @@ -99,6 +99,7 @@ pkgs.buildEnv { ''; passthru = { + perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/packages/default.nix b/nix/packages/default.nix index c6f1a293..b7ad2b20 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -4,8 +4,8 @@ ./postgres.nix ./postgres-env.nix ./site-env.nix + ./extension-catalog.nix ]; - perSystem = { inputs', diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix new file mode 100644 index 00000000..d81dcba3 --- /dev/null +++ b/nix/packages/extension-catalog.nix @@ -0,0 +1,165 @@ +# Multi version extension catalog and update tools. +{ + perSystem = + { + pkgs, + lib, + self', + ... + }: + let + # Mirror a per-version package, adding unversioned control/library names + # only where missing — packages that already ship generic names (e.g. + # postgis and its `-3` convention) pass through untouched. + mkGenericVersion = + leanPkg: + pkgs.runCommand "${leanPkg.pname}-${leanPkg.version}-generic" { } '' + set -euo pipefail + shopt -s nullglob + mkdir -p "$out/lib" "$out/share/postgresql/extension" + ext="$out/share/postgresql/extension" + + for f in ${leanPkg}/lib/*; do ln -s "$f" "$out/lib/$(basename "$f")"; done + for f in ${leanPkg}/share/postgresql/extension/*; do ln -s "$f" "$ext/$(basename "$f")"; done + + # <name>.control with default_version, from <name>--<version>.control + for cf in "$ext"/*--*.control; do + b=$(basename "$cf") + n=''${b%%--*} + v=''${b#*--}; v=''${v%.control} + [[ -e "$ext/$n.control" ]] && continue + { echo "default_version = '$v'"; cat "$cf"; } > "$ext/$n.control" + done + + # <base>.so -> <base>-<version>.so + for f in "$out"/lib/*-[0-9]*; do + b=$(basename "$f") + suffix=.''${b##*.} + core=''${b%.*} + base=''${core%-[0-9]*} + [[ "$base" != "$core" ]] || continue + gen=$base$suffix + [[ -e "$out/lib/$gen" ]] || ln -sfn "$b" "$out/lib/$gen" + done + + # Sanity check. No generic control is fine only for preload-only + # modules (e.g. pg-safeupdate), which still ship a library. + generic=0 + for c in "$ext"/*.control; do + case "$(basename "$c")" in + *--*) ;; + *) generic=$((generic + 1)) ;; + esac + done + libs=$(ls -A "$out/lib" 2>/dev/null | wc -l) + if ((generic == 0)) && ((libs == 0)); then + echo "extension-catalog: empty wrapper for ${leanPkg.pname} (no control, no lib)" >&2 + exit 1 + fi + ''; + + wrappersFor = + extsSet: + lib.mapAttrs (_: drv: lib.mapAttrs (_: mkGenericVersion) drv.perVersion) ( + lib.filterAttrs (_: drv: lib.isDerivation drv && drv ? perVersion) extsSet + ); + + # Make catalog json from a set of wrappers: { <ext>: { <version>: <path> } } + # Keyed on each wrapper's actual default_version, not the nix attr name, + # which can differ (pgsql-http "1.5.0" builds extversion "1.5"). + mkCatalogFile = + wrappers: + pkgs.runCommand "pg-extensions-catalog" + { + paths = map toString (lib.concatMap lib.attrValues (lib.attrValues wrappers)); + nativeBuildInputs = [ pkgs.jq ]; + # Without this the literal wrapper paths in the JSON would pull + # every wrapper's closure into the catalog's runtime closure. + __structuredAttrs = true; + unsafeDiscardReferences.out = true; + } + '' + set -euo pipefail + obj='{}' + for path in "''${paths[@]}"; do + for ctrl in "$path"/share/postgresql/extension/*.control; do + base=$(basename "$ctrl") + case "$base" in *--*) continue ;; esac + name=''${base%.control} + ver=$(sed -n "s/^default_version = '\(.*\)'.*/\1/p" "$ctrl" | head -1) + [[ -n "$ver" ]] || { echo "no default_version in $ctrl" >&2; exit 1; } + obj=$(printf '%s' "$obj" | jq --arg e "$name" --arg v "$ver" --arg p "$path" '.[$e][$v] = $p') + done + done + mkdir -p "$out/share" + printf '%s\n' "$obj" | jq -S . > "$out/share/pg-extensions-catalog.json" + ''; + + # Multi version extensions as attrsets: { <major> = { <ext> = { <version> = drv; }; }; } + perMajor = lib.genAttrs [ "15" "17" "orioledb-17" ] ( + major: wrappersFor self'.legacyPackages."psql_${major}".exts + ); + + # Catalog json plus bin/site-extensions-{resolve,update} that use it by default. + catalogs = lib.mapAttrs' ( + major: wrappers: + lib.nameValuePair "site-extensions-catalog-${major}" ( + pkgs.runCommand "site-extensions-catalog" { nativeBuildInputs = [ pkgs.makeWrapper ]; } '' + mkdir -p "$out/share" "$out/bin" + ln -s ${mkCatalogFile wrappers}/share/pg-extensions-catalog.json "$out/share/" + makeWrapper ${self'.packages.site-extensions-resolve}/bin/site-extensions-resolve \ + "$out/bin/site-extensions-resolve" \ + --set PG_EXTENSIONS_CATALOG "$out/share/pg-extensions-catalog.json" + makeWrapper ${self'.packages.site-extensions-update}/bin/site-extensions-update \ + "$out/bin/site-extensions-update" \ + --set PG_EXTENSIONS_CATALOG "$out/share/pg-extensions-catalog.json" + '' + ) + ) perMajor; + + versions = lib.mapAttrs' ( + major: wrappers: + lib.nameValuePair "site-extensions-versions-${major}" ( + lib.recurseIntoAttrs (lib.mapAttrs (_: lib.recurseIntoAttrs) wrappers) + ) + ) perMajor; + in + { + packages = catalogs // { + # Takes manifest json as argument. Format: {<ext>: <version>}. + # Prints nix-store paths of the resolved extensions, one per line. + # Does not download or install. + site-extensions-resolve = pkgs.writeShellApplication { + name = "site-extensions-resolve"; + runtimeInputs = [ + pkgs.coreutils + pkgs.jq + ]; + text = '' + : "''${PG_EXTENSIONS_CATALOG:?PG_EXTENSIONS_CATALOG must point at a pg-extensions-catalog.json}" + manifest="''${1:?Usage: $0 path-to/pg-extensions.json}" + jq -r 'to_entries[] | "\(.key)=\(.value)"' "$manifest" | while IFS='=' read -r name version; do + [ -n "$name" ] || { echo "ERROR: empty extension name in $manifest" >&2; exit 1; } + jq -er --arg n "$name" --arg v "$version" \ + '.[$n][$v] // error("\($n)=\($v) not in catalog")' "$PG_EXTENSIONS_CATALOG" + done | sort -u + ''; + }; + + # Takes manifest json as argument. + # Downloads paths and installs them as an env into the profile, replacing all existing ones. + site-extensions-update = pkgs.writeShellApplication { + name = "site-extensions-update"; + runtimeInputs = [ self'.packages.site-extensions-resolve ]; + text = '' + manifest="''${1:?Usage: $0 path-to/pg-extensions.json}" + profile="''${NIX_PROFILE:-/nix/var/nix/profiles/site-extensions}" + readarray -t paths < <(site-extensions-resolve "$manifest") + nix-store -r --option stalled-download-timeout 120 "''${paths[@]}" >/dev/null + nix-env --profile "$profile" --install "''${paths[@]}" --remove-all + ''; + }; + }; + legacyPackages = catalogs // versions; + }; +}