Skip to content

/changelogs/postgresql/postgresql-17-to-18

PostgreSQL 17 to 18: uuidv7, virtual generated columns, OLD and NEW in RETURNING

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated 2026-10-20
tool
PostgreSQL
versions
17 → 18

PostgreSQL 18 was released in September 2025. A dump-and-restore or pg_upgrade is needed to move from 17, as for every major version. The changes most applications notice are new SQL features; a few defaults changed too.

New SQL, run on PostgreSQL 18

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund @electric-sql/pglite@0.5.10 >/dev/null 2>&1
cat > q.mjs <<'EOF'
import { PGlite } from "@electric-sql/pglite";
const db = new PGlite();
const show = async (sql) => console.log(JSON.stringify((await db.query(sql)).rows));
await show(`select current_setting('server_version') as version`);
// uuidv7(): time-ordered UUIDs, good for primary keys
await show(`select uuidv7() < uuidv7() as time_ordered, length(uuidv7()::text) as len`);
// generated columns are virtual unless you write STORED
await db.query(`create table items (id int primary key, price numeric, qty int,
                total numeric generated always as (price * qty))`);
await db.query(`insert into items values (1, 2.5, 4)`);
await show(`select attgenerated from pg_attribute where attrelid = 'items'::regclass and attname = 'total'`);
// RETURNING can read the old and the new row
await show(`update items set qty = 10 where id = 1 returning old.qty as old_qty, new.qty as new_qty, new.total`);
EOF
node q.mjs

Output

[{"version":"18.6"}]
[{"time_ordered":true,"len":36}]
[{"attgenerated":"v"}]
[{"old_qty":4,"new_qty":10,"total":"25.0"}]

attgenerated = 'v' means virtual; a STORED column shows 's'.

Highlights

AreaChange in 18
UUIDsuuidv7() generates time-ordered UUIDs; uuidv4() is an alias of gen_random_uuid()
Generated columnsvirtual by default; STORED still available
RETURNINGOLD and NEW in INSERT, UPDATE, DELETE and MERGE
Constraintstemporal PRIMARY KEY / UNIQUE with WITHOUT OVERLAPS, and PERIOD foreign keys
I/Oasynchronous I/O subsystem (io_method) for sequential scans, bitmap heap scans and vacuum
Indexesskip scan for multicolumn B-tree indexes when the leading column is not filtered
AuthenticationOAuth 2.0 authentication method; MD5 passwords deprecated
Upgradespg_upgrade keeps planner statistics, so the new cluster does not start with empty statistics

Upgrade checklist

  1. Find MD5 roles: SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%'; and reset their passwords with password_encryption = 'scram-sha-256'.
  2. Add STORED to new generated columns if you index them or need them materialised.
  3. Check that the data checksum setting of the old cluster matches the new one before pg_upgrade.
  4. Test extensions: each must have a build for PostgreSQL 18.

Sources