Getting Ready for PostgreSQL 19
Short summary
A practical guide to what's actually changing in PostgreSQL 19, covering six compatibility breaks you need to know before upgrading and four SQL features worth using once you do. The author tested everything against Beta 3 and explains what each change means for your workload.
- Six breaking changes ship in PG 19: JIT disabled by default, standard_conforming_strings forced on, RADIUS auth removed, MD5 warnings enabled, inet/cidr GiST indexes rebuilt, and max_locks_per_transaction doubled—most are silent behavior shifts that break things only if you don't know to look.
- FOR PORTION OF lets you split and update a time-bounded row in one statement instead of hand-rolling UPDATE then INSERT, useful for temporal data like contract period changes.
- INSERT ... ON CONFLICT DO SELECT returns the conflicting row directly without a separate query, solving the common pattern of "upsert and fetch back the row" in registration flows and dedup-on-write pipelines.
- Window functions now support IGNORE NULLS to skip over NULL values and find the nearest actual value, replacing workarounds like window frame tricks or separate counters.
Full summary
PostgreSQL 19 is shipping soon with more breaking changes than usual, and you need to know about them before upgrading. JIT compilation is now disabled by default because the optimizer's cost model was unreliable—if your analytical queries depended on it kicking in automatically, you'll need to enable it manually. The `standard_conforming_strings` setting is locked on permanently, which means old database dumps taken with it disabled won't load into PG 19 without being re-dumped first. RADIUS authentication is gone entirely due to security concerns with UDP-only support. MD5 password authentication now warns on every login and is on its way out; you should switch to scram-sha-256. The `inet`/`cidr` GiST index default is changing because the old btree_gist opclasses were broken and could exclude rows they shouldn't have—pg_upgrade will refuse to touch clusters using them until you reindex. A few more: CR/LF characters are banned from database names for security, `max_locks_per_transaction` doubles from 64 to 128 (if you tuned this before, double your value), and `default_toast_compression` switches from pglz to lz4, which is faster but changes storage and CPU behavior silently.
The release adds three genuinely useful SQL features. `FOR PORTION OF` lets you update or delete just a slice of a date range in one statement instead of manually splitting rows—update a driver's team name for only part of their contract period, and PostgreSQL automatically creates the necessary rows. `INSERT ... ON CONFLICT DO SELECT ... RETURNING` fills a gap that's existed since PG 9.5: you can now get back the conflicting row directly when an insert fails, optionally locking it and filtering it with a WHERE clause. This makes idempotent upsert patterns work cleanly without a separate SELECT round trip. Window functions like `lag()`, `lead()`, `first_value()`, and `last_value()` now accept `IGNORE NULLS` to skip over NULL values and find the nearest real one—useful when you have sparse data like a race result with a DNF leaving a gap.
These aren't exotic changes, but they're easy to miss in release notes if you only skim the highlights. The author tested everything against a real PG 19 Beta 3 instance, so the syntax works today. The breaking changes will silently corrupt behavior or fail upgrades if you don't catch them beforehand, while the new features solve real problems that previously required workarounds or extra queries.