Deep dive

The graph database you don’t have to add yet

PostgreSQL 19 can query existing tables as a property graph. The useful boundary is knowing when graph syntax is enough and when another database earns its place.

Share

One ugly join is a cheap reason to add a database. It is also a bad one.

A second store forces decisions about replication, freshness, backfills, permissions, restores, and which copy wins when records disagree. Those decisions remain after everyone has forgotten the query that started them.

PostgreSQL 19 changes that trade. It can declare a property graph over ordinary tables and query those rows with graph pattern syntax. The records do not move. There is no new graph storage engine hiding under the command. GRAPH_TABLE is rewritten into a relational query and handed to the PostgreSQL machinery that was already there.1

The interesting part of this feature is not that PostgreSQL can now draw edges between circles. It is that graph-shaped questions no longer force an immediate storage decision. A team can improve the language of a query while keeping one transaction boundary, one backup chain, and one version of the truth.

The graph database still has a case. It just has to make a better one.

This was anticipated

Property graphs did not arrive because somebody saw a product comparison and bolted Cypher onto the parser. SQL/PGQ is part 16 of the SQL:2023 standard. Peter Eisentraut posted the first PostgreSQL patch series to pgsql-hackers in February 2024. The feature that landed for PostgreSQL 19 followed that standard and added the graph DDL, GRAPH_TABLE, catalog entries, and dump support.2

Anticipated, then, is literal. The standard existed, and the patch had been public for more than two years.

The more useful question is what PostgreSQL anticipated.

It did not anticipate that every relational database would become a native graph database. It anticipated that relational data would sometimes need a graph vocabulary.

That distinction appeared in the first review thread. Andres Freund questioned whether rewriting a graph query so early would discard information the planner could use. Tomas Vondra raised the same concern around future indexes and executor nodes. Eisentraut's answer was blunt: SQL/PGQ was designed to expand like a view into joins and unions. Native graph storage and specialized execution were different approaches. This patch was not trying to be those things.3

The argument was not about whether relationships exist in relational data. Of course they do. It was about how far a new query language should penetrate the storage and execution engine.

PostgreSQL 19 chose the conservative answer first.

PostgreSQL did not add graph storage

CREATE PROPERTY GRAPH records how tables participate in a graph. Vertex tables provide the things. Edge tables provide the relationships. Labels and properties decide which relational names the graph query can see.

The PostgreSQL documentation compares the result to a view because the graph is not physically materialized.4 That view-like definition is the feature.

Suppose an application already has people and follows:

CREATE TABLE person (
    id bigint PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE follows (
    follower bigint REFERENCES person (id),
    followee bigint REFERENCES person (id),
    since date NOT NULL,
    PRIMARY KEY (follower, followee)
);

PostgreSQL 19 can expose those same rows as a graph:

CREATE PROPERTY GRAPH social
    VERTEX TABLES (
        person KEY (id)
            LABEL person PROPERTIES (id, name)
    )
    EDGE TABLES (
        follows KEY (follower, followee)
            SOURCE KEY (follower) REFERENCES person (id)
            DESTINATION KEY (followee) REFERENCES person (id)
            LABEL follows PROPERTIES (since)
    );

social does not contain another copy of Ada. It contains the declaration that a row in person may be treated as a vertex and a row in follows may be treated as a directed edge.

The normal relational model remains available. The graph view is another way to ask it a question.

The same facts can have two useful shapes

To find the people Ada follows, the graph query reads close to the relationship:

SELECT *
FROM GRAPH_TABLE (
    social
    MATCH (a IS person WHERE a.name = 'Ada')
          -[f IS follows]->(b IS person)
    COLUMNS (
        b.name AS follows,
        f.since AS since
    )
);

The result of GRAPH_TABLE is still tabular. It can be filtered, ordered, joined to other tables, or placed inside a larger SQL query.5 Graph syntax does not exile the rest of SQL.

The same question can be written with joins:

SELECT b.name, f.since
FROM person AS a
JOIN follows AS f ON f.follower = a.id
JOIN person AS b ON b.id = f.followee
WHERE a.name = 'Ada';

Neither form is morally superior. The graph form starts pulling ahead when the thing a reader cares about is the path rather than the intermediate aliases.

Two fixed hops make that clearer:

SELECT *
FROM GRAPH_TABLE (
    social
    MATCH (a IS person WHERE a.name = 'Ada')
          -[IS follows]->()
          -[IS follows]->(c IS person)
    COLUMNS (c.name AS two_hops_away)
);

The relational version is possible. It needs two references to follows, three to person, and enough alias discipline to keep the path readable. That is not a database failure. It is a language mismatch.

Pull back GRAPH_TABLE.

The two-hop pattern rewrites to ordinary relations and joins.

An awkward query does not prove the storage engine is wrong. It may only mean the query needs a better vocabulary.

One difficult query is not an architecture decision

A dedicated graph database can be the correct system. The mistake is treating its query syntax as if it arrived alone.

If PostgreSQL remains authoritative, the graph store needs a copy of the people and relationships. Something must publish changes. Something must replay them after an outage. The team needs a definition of acceptable lag and a plan for a broken change stream. Schema changes now cross a boundary. A restore has to account for two systems that may have recovered to different points in time.

If the graph store becomes authoritative instead, the bill changes but does not disappear. Now relational consumers need their own projection, and transactions that used to update a person and their relationships together may span systems or accept weaker guarantees.

The point is to price the operating model. A database introduced as a nicer query becomes a replication and recovery system the moment it holds a copy.

I use a simple threshold:

Do not add a graph database for one graph-shaped read. Add it when traversal has become a workload.

A workload shows up in more than syntax. The depth varies at runtime. Paths are ranked, pruned, or searched repeatedly. Latency depends on traversing a large and changing neighborhood. The data layout, indexes, planner, and execution model all start mattering in specifically graph-shaped ways. At that point, a specialized system has something substantial to benchmark.

Before that point, moving the data can be a very expensive way to remove aliases from a query.

Where PostgreSQL 19 stops

The first PostgreSQL 19 implementation handles fixed patterns. One hop works. Two hops work when both are written into the pattern. The beta 3 implementation tested by VictoriaMetrics rejects quantified edges such as -[IS follows]->{1,3}.6

That gap is not a missing convenience around the edges. Variable-length traversal changes the execution problem.

With two fixed hops, the rewriter knows how many relations are involved. With “one to any number of hops,” it does not. The engine needs runtime iteration, rules for revisiting vertices or edges, a way to carry path state, and a plan for pruning before the number of possible paths gets silly.

The PostgreSQL discussion shows this is understood. A 2026 design note considered two broad approaches for variable-length edges: rewrite into a recursive CTE, or add a custom executor node that can perform depth-first traversal and stream results.7 The first stays close to existing relational machinery. The second preserves more graph meaning deeper into planning and execution. Neither is a tiny parser patch.

This is also why the old review concern still matters. Rewriting a graph pattern into standard relational form buys immediate integration with the existing planner, permissions, statistics, and execution stack. It can also discard information that a future graph-aware planner might want.

That is the real boundary in PostgreSQL 19. It has graph syntax. It does not yet have a graph-native executor.

“Postgres has it all” is the wrong victory lap

PostgreSQL has earned its reputation for taking on new data shapes. That reputation can turn into a strange form of database patriotism where every new feature is proof that no other system should exist.

Property graphs support a better reading.

PostgreSQL is good at letting one durable set of records participate in more than one model. Tables can remain tables while a query sees vertices and edges. A graph result can return to ordinary SQL. The base table permissions still matter. The source rows stay in the existing backup chain, and pg_dump carries the graph definition.24

There is already an extension story around PostgreSQL and graphs. Apache AGE adds graph capabilities and Cypher-oriented work on top of PostgreSQL.8 The SQL/PGQ mailing-list discussion has even considered a layered future where extension compatibility sits over the core standard feature.9 That boundary can move as the core implementation grows without forcing every graph feature into core.

A general-purpose database should not win a specialized benchmark by slogan. If a native graph engine traverses the production graph faster, at lower cost, with operational behavior the team can live with, that result matters. If it provides algorithms or path semantics the application actually needs, that matters too.

The useful effect of PostgreSQL 19 is that the comparison can begin later and with better evidence. A team can model relationships, ship fixed graph queries, observe the workload, and learn where the pain really is before introducing another source of truth.

The admission test for a second store

I would ask four questions before moving the data.

Is the limit syntax or execution? If the query is readable in GRAPH_TABLE and the relational plan behaves well, a second engine is solving a problem that has not appeared.

Does path depth come from the question or from the code? Fixed, known paths fit the PostgreSQL 19 design. Runtime traversal is where the first release runs out of road.

Which graph behavior changes the outcome? “Graph queries are nicer” is too soft. Shortest paths, dense neighborhood expansion, path ranking, and traversal-specific latency targets can be measured.

Who will own the copy? Name the change feed, lag objective, backfill procedure, permission model, restore order, and pager before approving the database. If those nouns have no owners, the architecture is not ready.

Either answer can be right. The graph store wins when traversal justifies its operating cost. PostgreSQL wins when the team gets a clearer query without a new failure domain.

PostgreSQL 19 does not end the relational-versus-graph argument. It gives a team more time to find out which problem it actually has.

The graph database you do not have to add yet may still be the database you need later. By then, it will have a production workload to argue from.

Footnotes

  1. The PostgreSQL 19 property graph documentation explains that SQL/PGQ graphs remain backed by tables and share PostgreSQL's planning and execution infrastructure. PostgreSQL 19 is still a development version as of publication.

  2. PostgreSQL's SQL/PGQ commit names ISO/IEC 9075-16:2023 and records the main objects added by the patch. 2

  3. The February 2024 pgsql-hackers review thread contains the early debate about view-style rewriting, planner information, indexes, and specialized execution.

  4. CREATE PROPERTY GRAPH does not materialize a graph. The same page documents how access to the base relations is checked. 2

  5. The PostgreSQL 19 graph query documentation describes GRAPH_TABLE as a table-like result that can join back into ordinary SQL.

  6. VictoriaMetrics ran its PostgreSQL 19 tour against beta 3 and shows the current quantified-edge error. This may change in a later release.

  7. The same pgsql-hackers thread includes the January 2026 variable-length-edge design note and discussion.

  8. Apache AGE is an Apache project that adds graph functionality to PostgreSQL.

  9. The PostgreSQL SQL/PGQ thread also discusses an AGE-like compatibility layer over core SQL/PGQ functionality. It is design discussion, not a committed PostgreSQL roadmap.

Share

Comments