What’s New in PostgreSQL 18? UUID v7, Virtual Columns, and More
PostgreSQL 18, released on September 25 2025, introduces developer‑focused features such as native UUID v7 generation, virtual generated columns, enhanced RETURNING clauses, automatic BUFFERS in EXPLAIN ANALYZE, and a unified pg_get_acl() permission function, all aimed at improving everyday database development.
PostgreSQL 18 was officially released on September 25, 2025, bringing several developer‑focused enhancements.
Native UUID v7 support
The new uuidv7() function generates UUID version 7 values, offering the global uniqueness of UUIDs with the performance benefits of sequential keys, effectively ending the long‑standing debate between SERIAL/IDENTITY and UUID primary keys.
-- Generate a UUIDv7
SELECT uuidv7();
-- Create a table using UUIDv7 as primary key
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuidv7(),
customer_id INT,
total DECIMAL(10,2),
created_at TIMESTAMP DEFAULT NOW()
);Virtual generated columns
PostgreSQL 12 introduced generated columns with the STORED option. PostgreSQL 18 adds the VIRTUAL option as the default, computing column values on read rather than on write, aligning PostgreSQL with other mainstream databases.
-- Create table with virtual generated column
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
salary DECIMAL(10,2),
annual_salary DECIMAL(12,2) GENERATED ALWAYS AS (salary * 12) VIRTUAL
);Space saving : No physical storage for derived data.
Dynamic calculation : Values are computed at query time.
Virtual columns cannot be indexed, whereas STORED columns can, which remains useful for heavy calculations that change rarely.
RETURNING clause enhancements
The RETURNING clause now allows access to both old and new row values in DML statements, simplifying audit‑log implementations.
-- Update prices and see both old and new values
UPDATE products
SET price = price * 1.10
WHERE price <= 99.99
RETURNING name,
old.price AS old_price,
new.price AS new_price,
new.price - old.price AS price_change;EXPLAIN ANALYZE defaults to BUFFERS
EXPLAIN ANALYZE now automatically includes the BUFFERS option, providing detailed I/O statistics without extra flags.
postgres=# EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;
-- PostgreSQL 18: BUFFERS included automatically
Seq Scan on users (cost=0.00..18.75 rows=125 width=64) (actual time=0.029..0.087 rows=178 loops=1)
Filter: (age > 25)
Buffers: shared hit=12
Planning: Buffers: shared hit=156 read=3
I/O Timings: shared read=0.024
Planning Time: 0.512 ms
Execution Time: 0.734 mspg_get_acl() for unified permission checks
The new pg_get_acl() function returns the access control list of any database object, replacing the need to query different system catalogs ( pg_class, pg_proc, etc.) for permissions.
SELECT (pg_identify_object(s.classid,s.objid,s.objsubid)).*,
pg_catalog.pg_get_acl(s.classid,s.objid,s.objsubid) AS acl
FROM pg_catalog.pg_shdepend AS s
JOIN pg_catalog.pg_database AS d
ON d.datname = current_database() AND d.oid = s.dbid
JOIN pg_catalog.pg_authid AS a
ON a.oid = s.refobjid AND s.refclassid = 'pg_authid'::regclass
WHERE s.deptype = 'a';These quality‑of‑life improvements, while less flashy than the new asynchronous I/O subsystem, make everyday development with PostgreSQL 18 smoother and more efficient, benefiting both human developers and AI agents.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
