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
