Hi Ashutosh,

On Thu, May 14, 2026 at 2:26 PM Ashutosh Bapat
<[email protected]> wrote:
>
> Hi Junwang,
> Thanks for working on this. The performance improvement is impressive.
> I haven't verified it myself though at this time.
>
> Henson said it right. The first version separately enumeration and
> conversion clearly to keep things simple. Things like variable length
> element patterns, embedded path patterns can change the way we
> enumerate the paths. Whatever the enumeration method is failing early
> is best strategy. However, the question is whether your implementation
> of "failing early" is going to make it difficult implement the above
> mentioned advanced features or simplify it or not affect those at all.
> We need to think and discuss a bit.

Yeah, it makes sense to me.

>
> Delaying "failing early" implementation till we implement those
> features is safest strategy. If the lack of it makes the feature
> prohibitively useless, we will need to implement it early and deal
> with the difficulties it brings. But the examples so far are mostly
> artificial, not practical. That doesn't make me feel like it's worth
> taking the risk. There are many other bugs that need to be fixed.
>
> I am very glad that this patch demonstrates that "failing early"
> improves things significantly. So we will incorporate this strategy
> sooner or later.

Sure, let's find out what's the best way.

>
> On Thu, May 14, 2026 at 6:08 AM Junwang Zhao <[email protected]> wrote:
> >
>
> Some cosmetic comments on v2
>
> + if (list_length(graph_path) == 1)
> + return true;
>
> I would move this earlier in the function, to make it more readable.

Done in attached v3.

>
> rev_feasible is clever, but may need more comments.

Added.

>
> How do we make sure that the edge direction checks in
> generate_query_for_graph_path and graph_path_edge_is_feasible remain
> consistent? What I had in mind instead was to start constructing Join
> tree while we are creating paths so that edge direction feasibility
> checks also create the edge connection quals avoiding the duplicate
> logic. However, we will need to make sure that any unusable objects we
> create during that process are discarded in time.
>
> It will be better to place CHECK_FOR_INTERRUPTS alongside stack depth
> check. But for it to be added there, we need an evidence that the
> function is really consuming a lot of time. Your earlier measurements
> hint towards that, but they are overall query times. Can you please
> share actual time spent in this function out of the total execution
> time?

I added temporary time logs around the generate_queries_for_path_pattern_recurse
in generate_queries_for_path_pattern, and got the path expansion
time consuming.

query time:

[local] zjw@postgres:5432-97559=# SELECT count(*) FROM GRAPH_TABLE (g5
MATCH (a IS
  vl2)-[e1]->(b)-[e2]->(c)-[e3]->(d)-[e4]->(e) COLUMNS (a.id AS aid));
 count
-------
     0
(1 row)

Time: 5577.000 ms (00:05.577)

path expansion time:

2026-05-14 23:22:41.513 CST [97559] LOG:  GRAPH_TABLE path expansion
took 4648.482 ms and generated 1 path queries

>
> If you separate CHECK_FOR_INTERRUPTS change as a separate patch, it
> can be committed independent of the optimization.

That's v3-0001 now.


Summary:

v3-0001: adds CHECK_FOR_INTERRUPTS() in recursive graph path query
generation to keep query cancellation responsive on complex patterns.
v3-0002: adds temporary timing logs to measure performance during
GRAPH_TABLE path expansion, not to be committed.
v3-0003: the failing early(or early pruning) logic with comments resolved.
v3-0004: adds a test which enumerates the full N^K combinations before
the early pruning logic, with Henson's last comment resolved.

>
> --
> Best Wishes,
> Ashutosh Bapat



--
Regards

Junwang Zhao
From fadb73ded263a7b116fec4b9a603d6610443af34 Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email protected]>
Date: Wed, 13 May 2026 09:13:34 +0800
Subject: [PATCH v3 4/4] add a test of long chain pattern that survives pruning
 used to enumerate the full N^K product

---
 src/test/regress/expected/graph_table.out | 87 +++++++++++++++++++++++
 src/test/regress/sql/graph_table.sql      | 83 +++++++++++++++++++++
 2 files changed, 170 insertions(+)

diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index cc6d80afd82..768ada77841 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -920,6 +920,93 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e
   30 | 300 |  10
 (2 rows)
 
+-- long chain MATCH with branching between layers (stress for graph-table prefix pruning
+-- vs enumerating full cross products along the trail)
+CREATE TABLE gv5_v1 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v2 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v3 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v4 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v5 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v6 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v7 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v8 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_e1 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e2 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e3 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e4 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e5 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e6 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e7 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e8 (id int PRIMARY KEY, src int, dest int);
+INSERT INTO gv5_v1 VALUES (1, 100);
+INSERT INTO gv5_v2 VALUES (1, 201), (2, 202);
+INSERT INTO gv5_v3 VALUES (1, 301), (2, 302);
+INSERT INTO gv5_v4 VALUES (1, 401), (2, 402);
+INSERT INTO gv5_v5 VALUES (1, 501), (2, 502);
+INSERT INTO gv5_v6 VALUES (6, 603);
+INSERT INTO gv5_v7 VALUES (7, 704);
+INSERT INTO gv5_v8 VALUES (8, 805);
+INSERT INTO gv5_e1 VALUES (101, 1, 1), (102, 1, 2);
+INSERT INTO gv5_e2 VALUES (201, 1, 1), (202, 1, 2), (203, 2, 1), (204, 2, 2);
+INSERT INTO gv5_e3 VALUES (301, 1, 1), (302, 1, 2), (303, 2, 1), (304, 2, 2);
+INSERT INTO gv5_e4 VALUES (401, 1, 1), (402, 1, 2), (403, 2, 1), (404, 2, 2);
+INSERT INTO gv5_e5 VALUES (501, 1, 6);
+INSERT INTO gv5_e6 VALUES (601, 6, 7);
+INSERT INTO gv5_e7 VALUES (701, 7, 8);
+INSERT INTO gv5_e8 VALUES (801, 8, 1);
+CREATE PROPERTY GRAPH g5
+    VERTEX TABLES (
+        gv5_v1 LABEL vl PROPERTIES (id, val) LABEL vl2 PROPERTIES (id, val),
+        gv5_v2 LABEL vl PROPERTIES (id, val),
+        gv5_v3 LABEL vl PROPERTIES (id, val),
+        gv5_v4 LABEL vl PROPERTIES (id, val),
+        gv5_v5 LABEL vl PROPERTIES (id, val),
+        gv5_v6 LABEL vl PROPERTIES (id, val),
+        gv5_v7 LABEL vl PROPERTIES (id, val),
+        gv5_v8 LABEL vl PROPERTIES (id, val)
+    )
+    EDGE TABLES (
+        gv5_e1 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v1 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v2 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e2 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v2 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v3 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e3 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v3 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v4 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e4 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v4 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v5 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e5 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v5 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v6 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e6 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v6 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v7 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e7 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v7 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v8 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e8 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v8 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v1 (id)
+            LABEL el PROPERTIES (id)
+    );
+-- 16 paths along v1->v5 (2^4) with gv5_v1.id = 1
+SELECT count(*) FROM GRAPH_TABLE (g5 MATCH (a IS vl2)-[e1]->(b)-[e2]->(c)-[e3]->(d)-[e4]->(e)
+    COLUMNS (a.id AS aid));
+ count 
+-------
+    16
+(1 row)
+
 -- ruleutils reverse parsing
 -- The query in the view definition is intentionally complex to test one view with many
 -- features like label disjunction, lateral references, WHERE clauses in graph
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 0e381ec72bc..18b7f74bc1c 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -523,6 +523,89 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s IS ptnv)-[e IS ptne]->(d IS ptnv) COLUMNS
 SELECT * FROM GRAPH_TABLE (g4 MATCH (s)-[e]-(d) WHERE s.id = 3 COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
 SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
 
+-- long chain MATCH with branching between layers (stress for graph-table prefix pruning
+-- vs enumerating full cross products along the trail)
+CREATE TABLE gv5_v1 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v2 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v3 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v4 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v5 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v6 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v7 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_v8 (id int PRIMARY KEY, val int);
+CREATE TABLE gv5_e1 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e2 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e3 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e4 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e5 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e6 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e7 (id int PRIMARY KEY, src int, dest int);
+CREATE TABLE gv5_e8 (id int PRIMARY KEY, src int, dest int);
+INSERT INTO gv5_v1 VALUES (1, 100);
+INSERT INTO gv5_v2 VALUES (1, 201), (2, 202);
+INSERT INTO gv5_v3 VALUES (1, 301), (2, 302);
+INSERT INTO gv5_v4 VALUES (1, 401), (2, 402);
+INSERT INTO gv5_v5 VALUES (1, 501), (2, 502);
+INSERT INTO gv5_v6 VALUES (6, 603);
+INSERT INTO gv5_v7 VALUES (7, 704);
+INSERT INTO gv5_v8 VALUES (8, 805);
+INSERT INTO gv5_e1 VALUES (101, 1, 1), (102, 1, 2);
+INSERT INTO gv5_e2 VALUES (201, 1, 1), (202, 1, 2), (203, 2, 1), (204, 2, 2);
+INSERT INTO gv5_e3 VALUES (301, 1, 1), (302, 1, 2), (303, 2, 1), (304, 2, 2);
+INSERT INTO gv5_e4 VALUES (401, 1, 1), (402, 1, 2), (403, 2, 1), (404, 2, 2);
+INSERT INTO gv5_e5 VALUES (501, 1, 6);
+INSERT INTO gv5_e6 VALUES (601, 6, 7);
+INSERT INTO gv5_e7 VALUES (701, 7, 8);
+INSERT INTO gv5_e8 VALUES (801, 8, 1);
+CREATE PROPERTY GRAPH g5
+    VERTEX TABLES (
+        gv5_v1 LABEL vl PROPERTIES (id, val) LABEL vl2 PROPERTIES (id, val),
+        gv5_v2 LABEL vl PROPERTIES (id, val),
+        gv5_v3 LABEL vl PROPERTIES (id, val),
+        gv5_v4 LABEL vl PROPERTIES (id, val),
+        gv5_v5 LABEL vl PROPERTIES (id, val),
+        gv5_v6 LABEL vl PROPERTIES (id, val),
+        gv5_v7 LABEL vl PROPERTIES (id, val),
+        gv5_v8 LABEL vl PROPERTIES (id, val)
+    )
+    EDGE TABLES (
+        gv5_e1 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v1 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v2 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e2 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v2 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v3 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e3 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v3 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v4 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e4 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v4 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v5 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e5 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v5 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v6 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e6 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v6 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v7 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e7 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v7 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v8 (id)
+            LABEL el PROPERTIES (id),
+        gv5_e8 KEY (id)
+            SOURCE KEY (src) REFERENCES gv5_v8 (id)
+            DESTINATION KEY (dest) REFERENCES gv5_v1 (id)
+            LABEL el PROPERTIES (id)
+    );
+-- 16 paths along v1->v5 (2^4) with gv5_v1.id = 1
+SELECT count(*) FROM GRAPH_TABLE (g5 MATCH (a IS vl2)-[e1]->(b)-[e2]->(c)-[e3]->(d)-[e4]->(e)
+    COLUMNS (a.id AS aid));
+
 -- ruleutils reverse parsing
 -- The query in the view definition is intentionally complex to test one view with many
 -- features like label disjunction, lateral references, WHERE clauses in graph
-- 
2.41.0

From 39cd8c1859b1b73fff5d86faf0835b3cf2e90a40 Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email protected]>
Date: Sat, 9 May 2026 10:41:40 +0800
Subject: [PATCH v3 3/4] Prune non-matching graph path prefixes during DFS

Add an early feasibility check in generate_queries_for_path_pattern_recurse()
so DFS stops exploring a path prefix as soon as the newly appended
element can no longer satisfy edge-vertex adjacency.

When the new element is an edge, validate it against any already-
selected elements in the current prefix. When the new element is a
vertex, validate only the immediately preceding edge. That is
sufficient here because repeated vertex variables are merged into
a single path factor before DFS begins.

This keeps the existing query generation semantics unchanged while
avoiding the work of enumerating many full-length paths that would
later be rejected by generate_query_for_graph_path().

The cyclic case where a closing edge has both endpoints already
in the prefix is already exercised by the existing same-variable
loop patterns in graph_table.sql (e.g. (a)-[b]->(a)-[b]->(a)),
because repeated vertex names are merged into a single path factor
before DFS. Likewise, the "all edge candidates pruned" path into
generate_query_for_empty_path_pattern() is already hit by the
MATCH (o IS orders)-[IS customer_orders]->(c IS customers) case,
where no catalog edge matches the declared direction; pruning just
makes those branches easier to reach. Neither case needs a
dedicated new test beyond what is already there.
---
 src/backend/rewrite/rewriteGraphTable.c | 109 +++++++++++++++++++++++-
 1 file changed, 108 insertions(+), 1 deletion(-)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 36bed558587..c2125dfbd44 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -102,6 +102,8 @@ static Query *generate_union_from_pathqueries(List **pathqueries);
 static List *get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf);
 static bool is_property_associated_with_label(Oid labeloid, Oid propoid);
 static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex);
+static bool graph_path_prefix_is_feasible_with_new_element(List *graph_path, struct path_element *new_pe);
+static bool graph_path_edge_is_feasible(List *graph_path, struct path_element *edge_pe);
 
 /*
  * Convert GRAPH_TABLE clause into a subquery using relational
@@ -378,9 +380,27 @@ generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries,
 
 	foreach_ptr(struct path_element, pe, path_elems)
 	{
-		/* Update current path being built with current element. */
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Add the next selected element to the current path before checking
+		 * feasibility, since the pruning logic inspects the resulting prefix
+		 * using path-factor positions inside graph_path.
+		 */
 		cur_path = lappend(cur_path, pe);
 
+		/*
+		 * If the currently selected prefix already makes any edge unable to
+		 * connect the adjacent selected vertices, abandon it right away. If
+		 * every candidate eventually prunes, DFS returns NIL pathqueries and
+		 * caller routes to generate_query_for_empty_path_pattern().
+		 */
+		if (!graph_path_prefix_is_feasible_with_new_element(cur_path, pe))
+		{
+			cur_path = list_delete_last(cur_path);
+			continue;
+		}
+
 		/*
 		 * If this is the last element in the path, generate query for the
 		 * completed path. Else recurse processing the next element.
@@ -405,6 +425,88 @@ generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries,
 	return pathqueries;
 }
 
+/*
+ * Check whether appending the newest selected element can still lead to a
+ * valid graph path.
+ *
+ * Since the older prefix was already known to be feasible, the newly appended
+ * element can invalidate only the edge constraints it participates in.
+ */
+static bool
+graph_path_prefix_is_feasible_with_new_element(List *graph_path, struct path_element *new_pe)
+{
+	struct path_factor *pf = new_pe->path_factor;
+	struct path_element *prev_pe;
+
+	if (list_length(graph_path) == 1)
+		return true;
+
+	if (IS_EDGE_PATTERN(pf->kind))
+		return graph_path_edge_is_feasible(graph_path, new_pe);
+
+	Assert(pf->kind == VERTEX_PATTERN);
+	Assert(list_length(graph_path) > 0);
+
+	/*
+	 * Repeated vertex variables are merged into one path factor before the
+	 * DFS begins, so appending a vertex extends only the immediately
+	 * preceding edge in the prefix. Any later edge referencing the same
+	 * factor will be checked when that edge itself is appended.
+	 */
+	prev_pe = list_nth(graph_path, list_length(graph_path) - 2);
+
+	/*
+	 * Merged duplicate vertices only drop redundant factors from
+	 * path_factors, not from the DFS path; preceding slot is always an edge
+	 * for a vertex.
+	 */
+	Assert(IS_EDGE_PATTERN(prev_pe->path_factor->kind));
+
+	return graph_path_edge_is_feasible(graph_path, prev_pe);
+}
+
+/*
+ * Check whether the selected endpoints of an edge in the current path prefix
+ * still allow at least one valid direction for that edge.
+ */
+static bool
+graph_path_edge_is_feasible(List *graph_path, struct path_element *edge_pe)
+{
+	struct path_factor *pf = edge_pe->path_factor;
+	int			prefix_len = list_length(graph_path);
+
+	/*
+	 * Track feasibility for the edge's declared direction and, for ANY edges,
+	 * its reverse. As endpoints are resolved, both candidates are refined;
+	 * the prefix remains feasible if at least one remains valid.
+	 */
+	bool		feasible = true;
+	bool		rev_feasible = (pf->kind == EDGE_PATTERN_ANY);
+
+	Assert(IS_EDGE_PATTERN(pf->kind));
+
+	if (pf->src_pf->factorpos < prefix_len)
+	{
+		struct path_element *src_pe;
+
+		src_pe = list_nth(graph_path, pf->src_pf->factorpos);
+		feasible = feasible && src_pe->elemoid == edge_pe->srcvertexid;
+		rev_feasible = rev_feasible && src_pe->elemoid == edge_pe->destvertexid;
+	}
+
+	if (pf->dest_pf->factorpos < prefix_len)
+	{
+		struct path_element *dest_pe;
+
+		dest_pe = list_nth(graph_path, pf->dest_pf->factorpos);
+		feasible = feasible && dest_pe->elemoid == edge_pe->destvertexid;
+		rev_feasible = rev_feasible && dest_pe->elemoid == edge_pe->srcvertexid;
+	}
+
+	/* Keep this prefix only if at least one direction still works. */
+	return feasible || rev_feasible;
+}
+
 /*
  * Construct a query representing given graph path.
  *
@@ -499,6 +601,11 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 			 * If the given edge element does not connect the adjacent vertex
 			 * elements in this path, the path is broken. Abandon this path as
 			 * it won't return any rows.
+			 *
+			 * Prefix pruning rejects such adjacency before we arrive at query
+			 * construction, so this guard is ordinarily unreachable; keep it
+			 * as a defensive counterpart to graph_path_edge_is_feasible()
+			 * rather than relying on tighter coupling alone.
 			 */
 			if (edge_qual == NULL)
 				return NULL;
-- 
2.41.0

From 57fd68342ed5f0e846249424352df2777ab7278b Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email protected]>
Date: Thu, 14 May 2026 22:53:14 +0800
Subject: [PATCH v3 2/4] Add temporary timing logs for GRAPH_TABLE path
 expansion.

---
 src/backend/rewrite/rewriteGraphTable.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 2a87eeb6d79..36bed558587 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -23,6 +23,7 @@
 #include "catalog/pg_propgraph_label.h"
 #include "catalog/pg_propgraph_label_property.h"
 #include "catalog/pg_propgraph_property.h"
+#include "executor/instrument.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -179,6 +180,8 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 	int			factorpos = 0;
 	List	   *path_factors = NIL;
 	struct path_factor *prev_pf = NULL;
+	instr_time	expansion_start;
+	instr_time	expansion_elapsed;
 
 	Assert(list_length(path_pattern) > 0);
 
@@ -343,8 +346,15 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 		path_elem_lists = lappend(path_elem_lists,
 								  get_path_elements_for_path_factor(rte->relid, pf));
 
+	INSTR_TIME_SET_CURRENT(expansion_start);
 	pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
 															NIL, path_elem_lists, 0);
+	INSTR_TIME_SET_CURRENT(expansion_elapsed);
+	INSTR_TIME_SUBTRACT(expansion_elapsed, expansion_start);
+	elog(LOG,
+		 "GRAPH_TABLE path expansion took %.3f ms and generated %d path queries",
+		 INSTR_TIME_GET_MILLISEC(expansion_elapsed),
+		 list_length(pathqueries));
 	if (!pathqueries)
 		pathqueries = list_make1(generate_query_for_empty_path_pattern(rte));
 
-- 
2.41.0

From fd77b3559fb912d79e671c91cd71153d808ff0ef Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email protected]>
Date: Thu, 14 May 2026 22:18:29 +0800
Subject: [PATCH v3 1/4] Add interrupt checks during graph path query
 generation.

Put a CHECK_FOR_INTERRUPTS() alongside stack depth checks in
recursive path expansion so query cancellation remains responsive
for complex GRAPH_TABLE patterns.
---
 src/backend/rewrite/rewriteGraphTable.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 33d4e866d74..2a87eeb6d79 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -364,6 +364,7 @@ generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries,
 
 	/* Guard against stack overflow due to complex path patterns. */
 	check_stack_depth();
+	CHECK_FOR_INTERRUPTS();
 
 	foreach_ptr(struct path_element, pe, path_elems)
 	{
-- 
2.41.0

Reply via email to