From 21a8812b29fee2d7c66c080b57ff60010bfe2c07 Mon Sep 17 00:00:00 2001 From: Henri Gasc Date: Tue, 8 Sep 2026 14:39:15 +0200 Subject: [PATCH 2/8] Add GUC enable_native and max_depth --- src/backend/optimizer/path/costsize.c | 8 ++ src/backend/parser/parse_graphtable.c | 146 ++++++++++++++++++++++ src/backend/rewrite/rewriteGraphTable.c | 73 +++-------- src/backend/utils/misc/guc_parameters.dat | 18 +++ src/include/optimizer/cost.h | 2 + src/include/parser/parse_graphtable.h | 43 +++++++ src/test/regress/expected/sysviews.out | 3 +- 7 files changed, 237 insertions(+), 56 deletions(-) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 7bbddb8bee4..67935089900 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -143,6 +143,13 @@ Cost disable_cost = 1.0e10; int max_parallel_workers_per_gather = 2; +/* + * Maximum total number of edges in any path of a native graph traversal, + * across the whole graph pattern (not just a single variable-length hop). + * Guard against runaway execution on cyclic or excessively long patterns. + */ +int max_graph_stack_depth = 1000; + bool enable_seqscan = true; bool enable_indexscan = true; bool enable_indexonlyscan = true; @@ -155,6 +162,7 @@ bool enable_groupagg = true; bool enable_nestloop = true; bool enable_material = true; bool enable_memoize = true; +bool enable_native_graphtable = false; bool enable_mergejoin = true; bool enable_hashjoin = true; bool enable_gathermerge = true; diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c index b44f7ccd0a7..c5ca15b1977 100644 --- a/src/backend/parser/parse_graphtable.c +++ b/src/backend/parser/parse_graphtable.c @@ -395,3 +395,149 @@ transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern) return (Node *) graph_pattern; } + +/* + * Collect label OIDs from a label expression (a single GraphLabelRef or an + * OR tree of GraphLabelRef nodes) into a list. Returns NIL if labelexpr is + * NULL; callers decide what a label-less element pattern means (the graph + * rewrite fallback treats it as "all labels", see + * get_graph_all_label_oids()). Shared by the parser, the native planner and + * executor, and the graph rewrite fallback. + */ +List * +get_label_oids_for_labelexpr(Node *labelexpr) +{ + List *result = NIL; + + if (labelexpr == NULL) + return NIL; + + if (IsA(labelexpr, GraphLabelRef)) + { + GraphLabelRef *lref = (GraphLabelRef *) labelexpr; + + result = lappend_oid(result, lref->labelid); + } + else if (IsA(labelexpr, BoolExpr)) + { + BoolExpr *b = (BoolExpr *) labelexpr; + + foreach_ptr(Node, arg, b->args) + { + List *sub = get_label_oids_for_labelexpr(arg); + + if (sub != NIL) + result = list_concat(result, sub); + } + } + else + { + /* + * Should not reach here: gram.y only generates label expressions + * built from GraphLabelRef and OR. + */ + elog(ERROR, "unsupported label expression node: %d", + (int) nodeTag(labelexpr)); + } + + return result; +} + +/* + * Return the OIDs of all labels belonging to the given property graph. + * + * A graph element pattern without a label expression is equivalent to + * "%|!%" (SQL/PGQ 9.2 subclause 2.a.ii), i.e. it matches every label of the + * graph. + */ +List * +get_graph_all_label_oids(Oid propgraphid) +{ + List *label_oids = NIL; + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tup; + + rel = table_open(PropgraphLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_pglpgid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(propgraphid)); + scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, + true, NULL, 1, key); + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup); + + label_oids = lappend_oid(label_oids, label->oid); + } + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return label_oids; +} + +/* + * Map a graph element pattern kind to the element-kind character ('v' for + * vertex, 'e' for edge) and the human-readable class name ("vertex"/"edge"). + * Returns false for pattern kinds that do not denote a vertex or edge (e.g. + * PAREN_EXPR), leaving the outputs untouched. kind_str may be NULL if the + * caller only needs the character. Shared by the parser validators, the + * native planner, and the native executor. + */ +bool +graph_element_kind_info(GraphElementPatternKind kind, + char *element_kind, const char **kind_str) +{ + switch (kind) + { + case VERTEX_PATTERN: + *element_kind = 'v'; + if (kind_str) + *kind_str = "vertex"; + return true; + case EDGE_PATTERN_ANY: + case EDGE_PATTERN_RIGHT: + case EDGE_PATTERN_LEFT: + *element_kind = 'e'; + if (kind_str) + *kind_str = "edge"; + return true; + default: + return false; + } +} + +/* + * Match a label expression against an element, using the supplied + * membership callback. A label expression is a single GraphLabelRef or a + * BoolExpr (OR) tree of GraphLabelRef nodes; the element matches if it + * carries any of the referenced labels (OR semantics). A NULL labelexpr + * matches everything. + * + * See graph_label_expr_matches() in parse_graphtable.h for the shared API. + */ +bool +graph_label_expr_matches(Node *labelexpr, GraphLabelHasFn has_label, + void *arg) +{ + if (labelexpr == NULL) + return true; + if (IsA(labelexpr, GraphLabelRef)) + return has_label(((GraphLabelRef *) labelexpr)->labelid, arg); + if (IsA(labelexpr, BoolExpr)) + { + BoolExpr *b = (BoolExpr *) labelexpr; + + foreach_ptr(Node, sub, b->args) + { + if (graph_label_expr_matches(sub, has_label, arg)) + return true; + } + return false; + } + elog(ERROR, "unsupported label expression node: %d", + (int) nodeTag(labelexpr)); + return false; /* keep compiler quiet */ +} diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c index 0eaf28b3de5..149563def77 100644 --- a/src/backend/rewrite/rewriteGraphTable.c +++ b/src/backend/rewrite/rewriteGraphTable.c @@ -26,6 +26,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "parser/analyze.h" #include "parser/parse_collate.h" @@ -105,6 +106,11 @@ static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex); /* * Convert GRAPH_TABLE clause into a subquery using relational * operators. + * + * If enable_native_graphtable is true, the rewriting is bypassed and the + * RTE_GRAPH_TABLE is left intact for the planner to decompose natively (the + * native planner support is not yet implemented; when enabled before it + * lands, planning of such a query fails until the native path arrives). */ Query * rewriteGraphTable(Query *parsetree, int rt_index) @@ -116,6 +122,10 @@ rewriteGraphTable(Query *parsetree, int rt_index) rte = rt_fetch(rt_index, parsetree->rtable); + /* Native mode: leave RTE_GRAPH_TABLE intact for the planner */ + if (enable_native_graphtable) + return parsetree; + Assert(list_length(rte->graph_pattern->path_pattern_list) == 1); path_pattern = linitial(rte->graph_pattern->path_pattern_list); @@ -830,63 +840,16 @@ create_pe_for_element(struct path_factor *pf, Oid elemoid) static List * get_labels_for_expr(Oid propgraphid, Node *labelexpr) { - List *label_oids; - + /* + * According to section 9.2 "Contextual inference of a set of labels" + * subclause 2.a.ii of SQL/PGQ standard, an element pattern which does not + * have a label expression is considered to have label expression + * equivalent to '%|!%' which is the set of all labels. + */ if (!labelexpr) - { - Relation rel; - SysScanDesc scan; - ScanKeyData key[1]; - HeapTuple tup; - - /* - * According to section 9.2 "Contextual inference of a set of labels" - * subclause 2.a.ii of SQL/PGQ standard, element pattern which does - * not have a label expression is considered to have label expression - * equivalent to '%|!%' which is set of all labels. - */ - label_oids = NIL; - rel = table_open(PropgraphLabelRelationId, AccessShareLock); - ScanKeyInit(&key[0], - Anum_pg_propgraph_label_pglpgid, - BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(propgraphid)); - scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, - true, NULL, 1, key); - while (HeapTupleIsValid(tup = systable_getnext(scan))) - { - Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup); - - label_oids = lappend_oid(label_oids, label->oid); - } - systable_endscan(scan); - table_close(rel, AccessShareLock); - } - else if (IsA(labelexpr, GraphLabelRef)) - { - GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr); - - label_oids = list_make1_oid(glr->labelid); - } - else if (IsA(labelexpr, BoolExpr)) - { - BoolExpr *be = castNode(BoolExpr, labelexpr); - List *label_exprs = be->args; - - label_oids = NIL; - foreach_node(GraphLabelRef, glr, label_exprs) - label_oids = lappend_oid(label_oids, glr->labelid); - } - else - { - /* - * should not reach here since gram.y will not generate a label - * expression with other node types. - */ - elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr)); - } + return get_graph_all_label_oids(propgraphid); - return label_oids; + return get_label_oids_for_labelexpr(labelexpr); } /* diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index c57441f7d98..9b37eb56f66 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -967,6 +967,14 @@ boot_val => 'true', }, +{ name => 'enable_native_graphtable', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD', + short_desc => 'Enables the native graph table executor.', + long_desc => 'When enabled, graph queries are decomposed and executed natively by the planner and the graph scan executor instead of being rewritten into relational subqueries by the rewriter. The default is currently off because the native planner support is not yet implemented; it will become on by default once the native path lands.', + flags => 'GUC_EXPLAIN', + variable => 'enable_native_graphtable', + boot_val => 'false', +}, + { name => 'enable_nestloop', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD', short_desc => 'Enables the planner\'s use of nested-loop join plans.', flags => 'GUC_EXPLAIN', @@ -2009,6 +2017,16 @@ max => 'FUNC_MAX_ARGS', }, +{ name => 'max_graph_stack_depth', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD', + short_desc => 'Sets the maximum total path length in a native graph traversal.', + long_desc => 'Limits the total number of edges in any path of a native graph traversal across the whole graph pattern, not just a single variable-length hop, to prevent runaway execution on cyclic or excessively long patterns.', + flags => 'GUC_EXPLAIN', + variable => 'max_graph_stack_depth', + boot_val => '1000', + min => '1', + max => '1000000', +}, + { name => 'max_identifier_length', type => 'int', context => 'PGC_INTERNAL', group => 'PRESET_OPTIONS', short_desc => 'Shows the maximum identifier length.', flags => 'GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE', diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 14255c900fc..a865a3b6de4 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -61,6 +61,7 @@ extern PGDLLIMPORT bool enable_groupagg; extern PGDLLIMPORT bool enable_nestloop; extern PGDLLIMPORT bool enable_material; extern PGDLLIMPORT bool enable_memoize; +extern PGDLLIMPORT bool enable_native_graphtable; extern PGDLLIMPORT bool enable_mergejoin; extern PGDLLIMPORT bool enable_hashjoin; extern PGDLLIMPORT bool enable_gathermerge; @@ -72,6 +73,7 @@ extern PGDLLIMPORT bool enable_partition_pruning; extern PGDLLIMPORT bool enable_presorted_aggregate; extern PGDLLIMPORT bool enable_async_append; extern PGDLLIMPORT int constraint_exclusion; +extern PGDLLIMPORT int max_graph_stack_depth; extern double index_pages_fetched(double tuples_fetched, BlockNumber pages, double index_pages, PlannerInfo *root); diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h index e52e21512aa..f53b4ff5f2d 100644 --- a/src/include/parser/parse_graphtable.h +++ b/src/include/parser/parse_graphtable.h @@ -21,4 +21,47 @@ extern Node *transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref) extern Node *transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern); +/* + * Collect the OIDs of the labels referenced by a label expression (a single + * GraphLabelRef or a BoolExpr OR of GraphLabelRef nodes). Returns NIL if + * labelexpr is NULL or references no labels. A NULL label expression on an + * element pattern means the pattern matches every label of the graph; call + * get_graph_all_label_oids() for that set. + */ +extern List *get_label_oids_for_labelexpr(Node *labelexpr); + +/* + * Return the OIDs of all labels belonging to the given property graph. + * Used for a graph element pattern without a label expression, which + * matches every label of the graph (SQL/PGQ "%|!%" semantics). + */ +extern List *get_graph_all_label_oids(Oid propgraphid); + +/* + * Map a graph element pattern kind to the element-kind character ('v' for + * vertex, 'e' for edge) and the human-readable class name ("vertex"/"edge"). + * Returns false for pattern kinds that do not denote a vertex or edge (e.g. + * PAREN_EXPR), leaving the outputs untouched. kind_str may be NULL if the + * caller only needs the character. Shared by the graph rewrite fallback, + * the native planner, and the native executor. + */ +extern bool graph_element_kind_info(GraphElementPatternKind kind, + char *element_kind, const char **kind_str); + +/* + * Callback used by graph_label_expr_matches(): does the element described + * by arg carry the given label? + */ +typedef bool (*GraphLabelHasFn) (Oid labelid, void *arg); + +/* + * Match a label expression (a single GraphLabelRef, or a BoolExpr OR tree of + * GraphLabelRef nodes) against an element, invoking has_label for each label + * the expression references. A NULL labelexpr matches everything. Shared by + * the native planner (syscache-backed) and the native executor (cached + * element model) so the OR / GraphLabelRef traversal is single-sourced. + */ +extern bool graph_label_expr_matches(Node *labelexpr, + GraphLabelHasFn has_label, void *arg); + #endif /* PARSE_GRAPHTABLE_H */ diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 1e327c2afa4..4f00c8d20a7 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -170,6 +170,7 @@ select name, setting from pg_settings where name like 'enable%'; enable_material | on enable_memoize | on enable_mergejoin | on + enable_native_graphtable | off enable_nestloop | on enable_parallel_append | on enable_parallel_hash | on @@ -181,7 +182,7 @@ select name, setting from pg_settings where name like 'enable%'; enable_seqscan | on enable_sort | on enable_tidscan | on -(26 rows) +(27 rows) -- There are always wait event descriptions for various types. InjectionPoint -- may be present or absent, depending on history since last postmaster start. -- 2.39.2