The Register ran a piece on Friday about PostgreSQL 19 and standardised graph queries, and within about a day the aggregators had boiled it down to the usual thing: Postgres is a graph database now, go and delete Neo4j.
Well. Sort of. The feature is real and I like it more than I expected to. But its shape is close to the opposite of what that headline implies, and the gap matters, because it changes what you'd actually use the thing for.
Status check first: 19 isn't out. Beta 3 landed on 13 August, there's no release candidate yet, and the project's usual September/October cadence puts GA a few weeks away. Feature freeze was a while back, though, so what's in there is what's shipping.
What actually landed
SQL/PGQ — Property Graph Queries — is part of SQL:2023, ISO/IEC 9075-16 if you want to sound severe in a meeting. Peter Eisentraut committed it on 16 March with Ashutosh Bapat, capping a patch series that had been grinding through commitfests for years.
You declare a graph over tables you already have:
CREATE PROPERTY GRAPH social
VERTEX TABLES (people, posts)
EDGE TABLES (
follows SOURCE KEY (follower_id) REFERENCES people (id)
DESTINATION KEY (followee_id) REFERENCES people (id)
);
And then you query it with the ASCII art:
SELECT * FROM GRAPH_TABLE (social
MATCH (a IS people) -[f IS follows]-> (b IS people)
WHERE a.name = 'ada'
COLUMNS (b.name AS followed)
);
The -[f]-> is a directed edge. The labels after IS refer to graph labels, not table names, which is nicer than it looks — the graph can call it person while the table underneath is still tbl_people_v2_final.
Here's the part every summary skips. A property graph is a new relkind, RELKIND_PROPGRAPH, and it behaves like a view. It is metadata. There's no graph storage, no adjacency structure, nothing gets copied, nothing gets migrated. The pattern is rewritten into ordinary joins in the rewriter, before the planner has any idea a graph was involved. EXPLAIN shows you hash joins, same as it always did.
So "Postgres is a graph database now" is wrong in the one specific way that matters: nothing about how your data sits on disk changed. What you got is syntax.
Being only syntax is the good news
I'm not being sniffy about that. Syntax is a real deliverable.
The query above, hand-written, is a two-table join and it's fine. Now do four hops. You end up with the same table aliased five times, a column of ON clauses that all look identical, and one spot where you typed follower_id where you meant followee_id — and the query doesn't fail, it quietly returns the wrong people. Everyone has shipped that bug. I've shipped it against a table with nine rows in it and still needed twenty minutes to find it.
Pattern syntax makes direction visual. -[f]-> points somewhere. You can see when the arrow is backwards. That is not a small thing when the alternative is parsing column names inside a WHERE clause at six in the evening.
And there's a quieter benefit. CREATE PROPERTY GRAPH refuses to run if your vertex tables have no primary keys, and the default edge inference wants foreign keys. So before it saves you a single line of SQL, the first thing this feature does is decline to work on a schema where nobody bothered declaring the constraints. Which I find genuinely funny. It's a graph feature that opens by auditing your relational hygiene.
Now the part the headline gets wrong
Every pattern in PostgreSQL 19 is fixed depth.
You cannot write a quantifier. No *, no +, no {1,5} hanging off an edge. Every hop is spelled out by hand, in the pattern. Variable-length paths are "a future release", which in Postgres years means do not put it on a roadmap.
Sit with what that removes. No transitive closure. No shortest path. No is there any route at all between these two accounts. No everything downstream of this node where the depth isn't known in advance — which is to say, no trees. Want any of it? You're back in WITH RECURSIVE, exactly where you were in 18, exactly where you were in 12.
That's not a rough edge you file down. That's the actual reason people go and get a graph database. Nobody in the history of computing has migrated to Neo4j because two-table joins were hard. They migrate because at hop seven the recursive CTE starts taking eleven seconds, or because they got tired of writing traversal by hand and want the engine to own it.
So PostgreSQL 19 ships the ergonomics of graph queries without the algorithms. It's good notation for the easy case. The easy case was already easy.
Still worth having. I just think the honest expectation is "nicer joins", not "we can retire the graph service". If a post tells you otherwise, check whether it contains one example with more than three hops in it. Mostly they don't. depesz's write-up is the one that doesn't oversell it.
Where I'd actually reach for it
Anywhere you've got a relationship you keep having to re-explain to people.
The blog you're reading pairs its Spanish and English posts through a shared key, so the hreflang tags come out right. That's a graph. Two nodes and an edge. It's modelled as a text column and it works fine — but "these two rows are the same article in different languages" is a sentence I have now written in three separate code comments, because the column name doesn't say it and the schema has no opinion about it. A property graph is somewhere to put that sentence where the database can read it too.
That's the use case. Not scale. Documentation with teeth.
The rest of 19, which is the better release
Graph queries got the headlines and are the least consequential thing in this release.
REPACK is in core, with a CONCURRENTLY variant that keeps the table readable and writable while it builds the new heap. That's pg_repack finally coming in from the cold after a decade as the extension everyone installs and nobody writes into the runbook.
Autovacuum goes parallel — autovacuum_max_parallel_workers, per-table overrides, and a scoring system deciding which tables get seen to first. The defaults are conservative, so it does nothing until you tell it to, which is the right call and also means you will forget.
pg_plan_advice lets you pin planner decisions. Every DBA who ever wanted query hints in Postgres is going to have complicated feelings about them arriving as a core extension, from a project that spent twenty years patiently explaining why hints are bad.
And JIT is off by default now. The stated reason is that cost-based activation turned out to be unreliable in general use. I've been switching it off in production since 12; most people I know have been switching it off in production since 12. It's genuinely good to see a project write down the heuristic was wrong instead of leaving it on and letting everyone find out through one mysteriously slow analytics query a quarter.
One gotcha worth carrying in your head: default_toast_compression now defaults to lz4, but nothing recompresses what's already there. Old pglz chunks stay pglz until those rows get updated, and neither flavour of REPACK rewrites them. So the benchmark you run the week after upgrading is measuring a mixture, and when it comes back underwhelming, that's why.
So
Postgres 19 got a nice piece of standard notation for something you were already doing with joins, and did not get the thing that makes graph databases a separate product. Both halves are true, and the internet has decided to carry only the first one.
Upgrade? Yes — for REPACK and parallel autovacuum. Then some evening, when you're staring at the fifth self-join in a query about who follows whom, go and write a CREATE PROPERTY GRAPH over it and find out whether the arrow was pointing the way you thought it was.
Just don't cancel the Neo4j contract on the strength of a headline. Count the hops first.
Comments