/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
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.mjsOutput
[{"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
| Area | Change in 18 |
|---|---|
| UUIDs | uuidv7() generates time-ordered UUIDs; uuidv4() is an alias of gen_random_uuid() |
| Generated columns | virtual by default; STORED still available |
| RETURNING | OLD and NEW in INSERT, UPDATE, DELETE and MERGE |
| Constraints | temporal PRIMARY KEY / UNIQUE with WITHOUT OVERLAPS, and PERIOD foreign keys |
| I/O | asynchronous I/O subsystem (io_method) for sequential scans, bitmap heap scans and vacuum |
| Indexes | skip scan for multicolumn B-tree indexes when the leading column is not filtered |
| Authentication | OAuth 2.0 authentication method; MD5 passwords deprecated |
| Upgrades | pg_upgrade keeps planner statistics, so the new cluster does not start with empty statistics |
Upgrade checklist
- Find MD5 roles:
SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%';and reset their passwords withpassword_encryption = 'scram-sha-256'. - Add
STOREDto new generated columns if you index them or need them materialised. - Check that the data checksum setting of the old cluster matches the new one before
pg_upgrade. - Test extensions: each must have a build for PostgreSQL 18.
Sources
Related pages
commands · postgresqlPostgreSQL upsert: INSERT … ON CONFLICT DO UPDATE and MERGEInsert a row or update it if the key exists with INSERT ... ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col, skip duplicates with DO NOTHING, or use MERGE.codes · postgresqlPostgreSQL SQLSTATE error codes: 23505, 23503, 42P01, 40001 and moreThe PostgreSQL SQLSTATE codes apps handle most: 23505 unique violation, 23503 foreign key, 23502 not null, 42P01 missing table, 40001 serialization failure.cheat sheets · postgresqlpsql cheat sheet: connect, list, describe and run SQL filespsql cheat sheet: connection strings, \l \dt \d+ and \du, switching database, expanded output, timing, running files with -f, \copy to CSV, and catalog queries.