Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ This is the same PostgreSQL build that powers [Supabase](https://supabase.io), b


## Primary Features
- ✅ Postgres [postgresql-15.19](https://www.postgresql.org/docs/15/index.html)
- ✅ Postgres [postgresql-17.11](https://www.postgresql.org/docs/17/index.html)
- ✅ Postgres [orioledb-postgresql-17_22](https://github.com/orioledb/orioledb)
- ✅ Postgres [postgresql-15.20](https://www.postgresql.org/docs/15/index.html)
- ✅ Postgres [postgresql-17.12](https://www.postgresql.org/docs/17/index.html)
- ✅ Postgres [orioledb-postgresql-17_23](https://github.com/orioledb/orioledb)
- ✅ Ubuntu 24.04 (Noble Numbat).
- ✅ [wal_level](https://www.postgresql.org/docs/current/runtime-config-wal.html) = logical and [max_replication_slots](https://www.postgresql.org/docs/current/runtime-config-replication.html) = 5. Ready for replication.
- ✅ [Large Systems Extensions](https://github.com/aws/aws-graviton-getting-started#building-for-graviton-and-graviton2). Enabled for ARM images.
Expand Down
4 changes: 2 additions & 2 deletions ansible/postgres_release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
postgres_release:
postgresorioledb-17: "17.11.0.019-orioledb"
postgres17: "17.11.0.019"
postgres15: "15.19.0.019"
postgres17: "17.12.0.000"
postgres15: "15.20.0.000"
4 changes: 2 additions & 2 deletions migrations/schema-15.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

\restrict SupabaseTestDumpKey123

-- Dumped from database version 15.19
-- Dumped by pg_dump version 15.19
-- Dumped from database version 15.20
-- Dumped by pg_dump version 15.20

SET statement_timeout = 0;
SET lock_timeout = 0;
Expand Down
4 changes: 2 additions & 2 deletions migrations/schema-17.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

\restrict SupabaseTestDumpKey123

-- Dumped from database version 17.11
-- Dumped by pg_dump version 17.11
-- Dumped from database version 17.12
-- Dumped by pg_dump version 17.12

SET statement_timeout = 0;
SET lock_timeout = 0;
Expand Down
16 changes: 15 additions & 1 deletion nix/checks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,22 @@
};
};

# Tests to skip for OrioleDB (not compatible with OrioleDB storage)
# Tests to skip for OrioleDB.
orioleDBSkipTests = [
"index_advisor" # index_advisor doesn't support OrioleDB tables

# OrioleDB still uses PostgreSQL 17.11.
# drop these skips when it includes the 17.12 fixes.
"alter_column_type_rowtype_deps"
"bpchar_btree_dedup"
"cast_function_execute_privilege"
"drop_owned_rls_policy"
"function_def_target_alias"
"jsonpath_gin_lookup"
"partition_prune_range"
"to_char_v_overflow"
"upsert_rls_conflict_where"
"upsert_view_visibility"
];

# Helper function to filter SQL files based on version
Expand Down Expand Up @@ -187,6 +200,7 @@
"output_plugin_libraries" # needs wal_level=logical + logical-decoding infra, not exercised in the CLI variant
"btree_gist_nan" # needs btree_gist, not in the CLI prime file
"hstore_copy_binary" # needs hstore
"bpchar_btree_dedup" # needs pageinspect, not in the CLI prime file
"pgrst_watch_triggers" # needs dblink, not in the CLI prime file
# Version-specific extension tests
"z_17_ext_interface"
Expand Down
8 changes: 4 additions & 4 deletions nix/config.nix
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ in
supportedPostgresVersions = {
postgres = {
"15" = {
version = "15.19";
hash = "sha256-4aZKh6RrgluIwILkUYFhpHqrU8RWlJZPi6HfKPeFn4k=";
version = "15.20";
hash = "sha256-MOZcu7dxo5tVPtLffmViLmVkeu1V28Gb0P6FP/CghEI=";
};
"17" = {
version = "17.11";
hash = "sha256-3Sfys8Wec+0UqjMkkBJCv2mgMqY0eAXydOYmAyLUKXk=";
version = "17.12";
hash = "sha256-nPE2pwPY7i+DRlxDvk6ejnkwyCsL+6E75+JTe+dprFU=";
};
};
orioledb = {
Expand Down
7 changes: 3 additions & 4 deletions nix/ext/tests/lib.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ let
inherit (pkgs) lib;
system = pkgs.pkgsLinux.stdenv.hostPlatform.system;

expectedVersions = {
"15" = "15.19";
"17" = "17.11";
};
expectedVersions = lib.mapAttrs (
_: config: config.version
) self.supabase.supportedPostgresVersions.postgres;

defaultPort = 5432;

Expand Down
20 changes: 14 additions & 6 deletions nix/packages/docker-image-test.nix
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ writeShellApplication {
# Tests to skip for OrioleDB
ORIOLEDB_SKIP_TESTS=(
"index_advisor"

# OrioleDB still uses PostgreSQL 17.11.
# drop these skips when it includes the 17.12 fixes.
"alter_column_type_rowtype_deps"
"bpchar_btree_dedup"
"cast_function_execute_privilege"
"drop_owned_rls_policy"
"function_def_target_alias"
"jsonpath_gin_lookup"
"partition_prune_range"
"to_char_v_overflow"
"upsert_rls_conflict_where"
"upsert_view_visibility"
)

# Tests to skip for multigres images (pgsodium not installed; vault has z_multigres-17_ variant)
Expand All @@ -114,12 +127,7 @@ writeShellApplication {
"vault"
)

# Tests to skip for multigres-orioledb-17 (superset of multigres skips)
MULTIGRES_ORIOLEDB_SKIP_TESTS=(
"pgsodium"
"vault"
"index_advisor"
)
MULTIGRES_ORIOLEDB_SKIP_TESTS=("''${ORIOLEDB_SKIP_TESTS[@]}" "''${MULTIGRES_SKIP_TESTS[@]}")

get_test_list() {
local version="$1"
Expand Down
33 changes: 33 additions & 0 deletions nix/tests/expected/alter_column_type_rowtype_deps.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- Fixes CVE-2026-6460.
--
-- What changed: after a column type change, stored expressions could still read the table's row type in its old layout.
-- That could crash the backend or expose memory.
-- Postgres now refuses ALTER COLUMN TYPE when certain stored code uses the row type.
-- It also adds the composite_type_mismatch setting.
--
-- Who notices: migrations such as `ALTER TABLE profiles ALTER COLUMN x TYPE ...`.
-- They now fail with "cannot alter table ... because function ... uses its row type" if the row type appears in any of these:
-- - a BEGIN ATOMIC function body
-- - a default for a function argument or a column
-- - an index expression on another table
-- To fix: drop the dependent object, run the ALTER, then recreate the object.
-- Functions that only take or return the row type are fine, for example `RETURNS SETOF profiles` for a PostgREST RPC.
--
-- Upstream commits: 22762f03a and others (PG 15.20), bfcb6625e and others (PG 17.12).
BEGIN;
-- The new GUC exists.
SELECT count(*) AS has_guc FROM pg_settings WHERE name = 'composite_type_mismatch';
has_guc
---------
1
(1 row)

CREATE TABLE act_tbl (a bytea);
CREATE FUNCTION act_func() RETURNS act_tbl
IMMUTABLE LANGUAGE sql BEGIN ATOMIC SELECT '(\\x4649584d45)'::act_tbl; END;
-- Must now be rejected.
SAVEPOINT s;
ALTER TABLE act_tbl ALTER COLUMN a TYPE text;
ERROR: cannot alter table "act_tbl" because function act_func() uses its row type
ROLLBACK TO SAVEPOINT s;
ROLLBACK;
36 changes: 36 additions & 0 deletions nix/tests/expected/bpchar_btree_dedup.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- What changed: btree deduplication isn't safe for bpchar, because bpchar equality ignores trailing spaces.
-- In rare cases index-only scans returned wrong results.
-- Postgres no longer deduplicates bpchar_ops indexes.
-- New indexes report allequalimage = false, which this test checks.
--
-- Who notices: anyone with a btree index on a `bpchar` or `char(n)` column, including primary keys and unique constraints.
-- Upstream recommends a REINDEX after the upgrade.
-- Until then, amcheck's bt_index_check() reports these indexes as corrupt ("metapage incorrectly indicates that deduplication is safe").
-- Expect support tickets about it.
-- This query lists the indexes to rebuild:
--
-- SELECT DISTINCT i.indexrelid::regclass FROM pg_index i
-- JOIN pg_class c ON c.oid = i.indexrelid
-- JOIN pg_am am ON am.oid = c.relam AND am.amname = 'btree'
-- JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)
-- WHERE a.atttypid = 'bpchar'::regtype;
--
-- Upstream commits: de0d4f0b1 (PG 15.20), 74f26a193 (PG 17.12).
BEGIN;
CREATE TABLE bpchar_dedup (b bpchar, c char(10), t text) USING heap;
CREATE INDEX bpchar_dedup_b ON bpchar_dedup (b);
CREATE INDEX bpchar_dedup_c ON bpchar_dedup (c);
CREATE INDEX bpchar_dedup_t ON bpchar_dedup (t);
-- bpchar and char(n) report false after the fix.
-- text still reports true.
SELECT 'b' AS col, allequalimage FROM bt_metap('bpchar_dedup_b')
UNION ALL SELECT 'c', allequalimage FROM bt_metap('bpchar_dedup_c')
UNION ALL SELECT 't', allequalimage FROM bt_metap('bpchar_dedup_t');
col | allequalimage
-----+---------------
b | f
c | f
t | t
(3 rows)

ROLLBACK;
29 changes: 29 additions & 0 deletions nix/tests/expected/cast_function_execute_privilege.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- Fixes CVE-2026-116303.
--
-- What changed: to_json() and to_jsonb() called a user-defined cast function without checking EXECUTE on it.
-- They now check, like normal cast evaluation.
--
-- Who notices: projects that meet both conditions:
-- - they defined a custom cast to json or jsonb
-- - they revoked EXECUTE on its function from PUBLIC, for example with a blanket revoke from anon and authenticated
-- Those roles now get "permission denied for function ..." when they convert the type to JSON.
-- That includes PostgREST responses.
-- To fix: grant EXECUTE on the cast function.
--
-- Upstream commits: 879c760d8 (PG 15.20), 9520dbea7 (PG 17.12).
BEGIN;
-- Use a separate role because Supabase grants postgres EXECUTE by default.
CREATE ROLE regress_cast_priv_user NOLOGIN;
CREATE TYPE cast_priv_enum AS ENUM ('a');
CREATE FUNCTION cast_priv_enum_to_json(cast_priv_enum) RETURNS json
LANGUAGE sql IMMUTABLE AS $$ SELECT to_json($1::text) $$;
CREATE CAST (cast_priv_enum AS json) WITH FUNCTION cast_priv_enum_to_json(cast_priv_enum);
REVOKE ALL ON FUNCTION cast_priv_enum_to_json(cast_priv_enum) FROM PUBLIC;
SET ROLE regress_cast_priv_user;
-- Must be denied.
SAVEPOINT s;
SELECT to_json('a'::cast_priv_enum);
ERROR: permission denied for function cast_priv_enum_to_json
ROLLBACK TO SAVEPOINT s;
RESET ROLE;
ROLLBACK;
28 changes: 28 additions & 0 deletions nix/tests/expected/drop_owned_rls_policy.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- Fixes CVE-2026-116311.
--
-- What changed: `DROP OWNED BY some_role` could remove the role from RLS policies on tables the caller doesn't own.
-- A role could use this to escape a restrictive policy.
-- Postgres now leaves those policies alone and issues a WARNING.
--
-- Who notices: clean-up scripts that run `DROP OWNED BY some_role` as a role that doesn't own every affected table.
-- The policies stay, so a later `DROP ROLE some_role` can fail on the remaining dependency.
-- To fix: run the clean-up as the table owner (on Supabase, usually `postgres`), or remove the role from those policies first.
--
-- Upstream commits: 1f2dd21c1 (PG 15.20), 21c732aad (PG 17.12).
BEGIN;
CREATE ROLE regress_dob_role NOLOGIN;
CREATE TABLE dob_tbl (c int);
ALTER TABLE dob_tbl ENABLE ROW LEVEL SECURITY;
CREATE POLICY dob_restrict ON dob_tbl AS RESTRICTIVE TO regress_dob_role USING (c > 0);
SET ROLE regress_dob_role;
DROP OWNED BY regress_dob_role; -- should warn
WARNING: role "regress_dob_role" could not be removed from policy "dob_restrict" for table "dob_tbl"
RESET ROLE;
-- Policy must still apply to regress_dob_role.
SELECT polname, polroles::regrole[] FROM pg_policy WHERE polrelid = 'dob_tbl'::regclass;
polname | polroles
--------------+--------------------
dob_restrict | {regress_dob_role}
(1 row)

ROLLBACK;
32 changes: 32 additions & 0 deletions nix/tests/expected/function_def_target_alias.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- What changed: pg_get_functiondef(), pg_get_ruledef() and \d+ now always print AS before a target-table alias in UPDATE and DELETE.
-- So `UPDATE t x` becomes `UPDATE t AS x`.
-- Without AS, an alias named `set` produced a definition that failed to restore.
--
-- Who notices: queries run the same.
-- Schema-diff tools compare these printed definitions, though.
-- So `supabase db diff`, `supabase db pull` and migra may report changes to unchanged BEGIN ATOMIC functions and rules right after the upgrade.
--
-- Upstream commits: 697e1d3d5 (PG 15.20), 91246c726 (PG 17.12).
BEGIN;
CREATE TABLE fdta_tbl (id int, val int);
CREATE FUNCTION fdta_func() RETURNS void LANGUAGE sql
BEGIN ATOMIC
UPDATE fdta_tbl t SET val = t.val + 1 WHERE t.id = 1;
DELETE FROM fdta_tbl d WHERE d.id = 2;
END;
SELECT pg_get_functiondef('fdta_func'::regproc);
pg_get_functiondef
-----------------------------------------------
CREATE OR REPLACE FUNCTION public.fdta_func()+
RETURNS void +
LANGUAGE sql +
BEGIN ATOMIC +
UPDATE fdta_tbl AS t SET val = (t.val + 1) +
WHERE (t.id = 1); +
DELETE FROM fdta_tbl AS d +
WHERE (d.id = 2); +
END +

(1 row)

ROLLBACK;
64 changes: 64 additions & 0 deletions nix/tests/expected/jsonpath_gin_lookup.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- What changed: jsonpath searches with `@@` and `@?` on a jsonb_ops GIN index missed matching rows in these cases:
-- - a top-level scalar, for example `strict $ == "a scalar"`
-- - a method on a literal, for example `"25".double()`
-- - an AND inside a NOT
-- The test checks that index scans find the same rows as a sequential scan.
--
-- Who notices: these queries may return more rows after the upgrade (the correct ones).
-- Before, the result depended on whether the planner chose the index.
-- Existing indexes don't need a REINDEX.
--
-- Upstream commits: 3b2a60481 + 04a8915fa (PG 15.20), 19656b77d + 65f1b71c3 (PG 17.12).
BEGIN;
CREATE TABLE jgin (j jsonb) USING heap;
INSERT INTO jgin VALUES
('"a scalar"'),
('{"age": 25}'),
('{"age": 25.0}'),
('{"bad": false, "coauthors": 98}'),
('{"bad": true, "coauthors": 98}'),
('{"bad": false, "coauthors": 1}'),
('{}');
CREATE INDEX jgin_idx ON jgin USING gin (j jsonb_ops);
-- Ground truth (seqscan).
SET LOCAL enable_bitmapscan = off;
SELECT count(*) FROM jgin WHERE j @@ 'strict $ == "a scalar"'::jsonpath;
count
-------
1
(1 row)

SELECT count(*) FROM jgin WHERE j @@ '$.age == "25".double()'::jsonpath;
count
-------
2
(1 row)

SELECT count(*) FROM jgin WHERE j @@ '!(!($.bad == false) && $.coauthors == 98)'::jsonpath;
count
-------
6
(1 row)

-- Through the GIN index: must match.
SET LOCAL enable_bitmapscan = on;
SET LOCAL enable_seqscan = off;
SELECT count(*) FROM jgin WHERE j @@ 'strict $ == "a scalar"'::jsonpath;
count
-------
1
(1 row)

SELECT count(*) FROM jgin WHERE j @@ '$.age == "25".double()'::jsonpath;
count
-------
2
(1 row)

SELECT count(*) FROM jgin WHERE j @@ '!(!($.bad == false) && $.coauthors == 98)'::jsonpath;
count
-------
6
(1 row)

ROLLBACK;
Loading
Loading