| From: | ayoub(dot)kazar(at)data-bene(dot)io |
|---|---|
| To: | assam258(at)gmail(dot)com |
| Cc: | pgsql-hackers <pgsql-hackers(at)postgresql(dot)org> |
| Subject: | Re: SQL/PGQ: Support multi-pattern path matching in GRAPH_TABLE |
| Date: | 2026-07-29 12:17:44 |
| Message-ID: | dabbcd65b77bc1031cff6131f6d35a90@data-bene.io |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hello Henson,
On 2026-04-24 06:44, Henson Choi wrote:
> Hi hackers,
>
> Now that the SQL/PGQ core has been committed, I'd like to propose
> extending GRAPH_TABLE to accept multiple path patterns in the MATCH
> clause, i.e. the comma-separated form:
>
> SELECT ... FROM GRAPH_TABLE (g
> MATCH <path_pattern_1>, <path_pattern_2>, ...
> COLUMNS (...)
> );
>
> This shape is not supported today — the parser rejects it with
>
> multiple path patterns in one GRAPH_TABLE clause not supported
>
> and the rewriter asserts that the path_pattern_list has exactly one
> entry. Among the features that are not yet covered, I think this one
> has the highest practical need: many realistic graph queries express
> joins and star-shaped traversals most naturally as multiple
> comma-separated patterns, and without it those queries have to be
> rewritten into more awkward forms. The attached patch lifts the
> restriction and wires the existing path-rewriting pipeline through
> the list of path patterns.
>
> What the patch does
> -------------------
>
> Parser side: the error that rejected multi-pattern MATCH is
> removed, so such queries now reach the rewriter.
>
> Rewriter side: each path pattern is processed as its own chain,
> so adjacency linking never crosses a path boundary. Element
> variables that share a name across paths are still merged into
> the same element — shared variables produce joins, disconnected
> paths produce cross products.
>
> An earlier version flattened all element patterns into a single
> list, but that treated elements from adjacent paths as adjacent
> within one path and broke on vertex-vertex boundaries. The
> per-path approach is the minimal fix; a more principled cross-path
> join construction is left for this thread to settle.
>
> Examples
> --------
>
> Shared variable (join):
>
> MATCH (a IS vl1)-[e1 IS el1]->(b IS vl2),
> (b)-[e2 IS el2]->(c IS vl3)
>
> -- b is shared -> the two patterns are joined on b.
>
> Star/hub:
>
> MATCH (a IS vl1)-[]->(b IS vl2),
> (a)-[]->(c IS vl3),
> (d IS vl2)-[]->(a)
>
> -- three patterns meeting at a.
>
> Disconnected patterns (cross product):
>
> MATCH (a IS vl1), (b IS vl3)
>
> Partial connection mixed with a disconnected piece:
>
> MATCH (a)-[]->(b), (b)-[]->(c), (d IS vl1)
I didn't find any issues in the implementation, just some remarks on
tests and comments.
+-- Disconnected patterns (no shared variables) - should produce cross
product
+-- (a)->(b) and (c)->(d) are independent, result is Cartesian product
+-- vl1->vl2 has 3 paths, vl1->vl3 has 3 paths, so cross product = 3 x 3
= 9 rows
+SELECT a_name, b_name, c_name, d_name FROM GRAPH_TABLE (g1
+ MATCH (a IS vl1)-[]->(b IS vl2),
+ (c IS vl1)-[]->(d IS vl3)
+ COLUMNS (a.vname AS a_name, b.vname AS b_name,
+ c.vname AS c_name, d.vname AS d_name)
+) ORDER BY 1, 2, 3, 4;
+ a_name | b_name | c_name | d_name
+--------+--------+--------+--------
+ v11 | v22 | v11 | v22
+ v11 | v22 | v11 | v31
+ v11 | v22 | v11 | v33
+ v11 | v22 | v12 | v21
+ v11 | v22 | v13 | v23
+ v12 | v21 | v11 | v22
+ v12 | v21 | v11 | v31
+ v12 | v21 | v11 | v33
+ v12 | v21 | v12 | v21
+ v12 | v21 | v13 | v23
+ v13 | v23 | v11 | v22
+ v13 | v23 | v11 | v31
+ v13 | v23 | v11 | v33
+ v13 | v23 | v12 | v21
+ v13 | v23 | v13 | v23
+(15 rows)
Here it should say:
+-- vl1->vl2 gives 3 paths; vl1->vl3 gives 5 paths (v2 also carries
vl3); cross product = 3 x 5 = 15 rows
instead of 9 rows
Also,
+-- Disconnected patterns: EXPLAIN should show cross join (Nested Loop
without join condition)
+EXPLAIN (COSTS OFF) SELECT a_name, b_name, c_name, d_name FROM
GRAPH_TABLE (g1
+ MATCH (a IS vl1)-[]->(b IS vl2),
+ (c IS vl1)-[]->(d IS vl3)
+ COLUMNS (a.vname AS a_name, b.vname AS b_name,
+ c.vname AS c_name, d.vname AS d_name)
+);
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------
+ Append
+ -> Merge Join
+ Merge Cond: (v1.id = e1_2.id_1)
+ -> Nested Loop
+ -> Index Scan using v1_pkey on v1
+ -> Materialize
+ -> Nested Loop
+ -> Merge Join
+ Merge Cond: ((e1_2_1.id_2_1 =
v2_1.id1) AND (e1_2_1.id_2_2 = v2_1.id2))
+ -> Sort
+ Sort Key: e1_2_1.id_2_1,
e1_2_1.id_2_2
+ -> Seq Scan on e1_2 e1_2_1
+ -> Sort
+ Sort Key: v2_1.id1, v2_1.id2
+ -> Seq Scan on v2 v2_1
+ -> Index Scan using v1_pkey on v1 v1_1
+ Index Cond: (id = e1_2_1.id_1)
+ -> Sort
+ Sort Key: e1_2.id_1
+ -> Merge Join
+ Merge Cond: ((e1_2.id_2_1 = v2.id1) AND
(e1_2.id_2_2 = v2.id2))
+ -> Sort
+ Sort Key: e1_2.id_2_1, e1_2.id_2_2
+ -> Seq Scan on e1_2
+ -> Sort
Isn't this test fragile ? What if EXPLAIN changes, what if the plan
changes (like what if the cost model changes ?) ...
If this is true maybe a count(*) test suffices for the cross product
(not entirely).
Another one,
+-- Multi-pattern with three patterns sharing one variable
+SELECT a_name, b2_name, b3_name FROM GRAPH_TABLE (g1
+ MATCH (a IS vl1)-[]->(b2 IS vl2),
+ (a)-[]->(b3 IS vl3)
+ COLUMNS (a.vname AS a_name, b2.vname AS b2_name, b3.vname AS
b3_name)
+) ORDER BY a_name, b2_name, b3_name;
This is two patterns, not three.
Finally, updated some part of comment above
generate_queries_for_path_pattern on new path_pattern_list arg.
Attached is the patch that is rebased in addition to the above comments.
Regards,
Ayoub
| Attachment | Content-Type | Size |
|---|---|---|
| v2-0001-SQL-PGQ-Support-multi-pattern-path-matching-in-GRAPH_TABLE.patch | text/x-diff | 31.5 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Amit Kapila | 2026-07-29 12:18:46 | Re: Race between pg_dump and ALTER SEQUENCE can cause read failure |
| Previous Message | r314tive | 2026-07-29 12:00:42 | Re: datachecksums: handle invalid and dropped databases during enable |