From 5476fb8e5ce11f8d15d3de2deb1a17248624c196 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Sat, 15 Aug 2026 15:08:13 -0400 Subject: [PATCH v7 1/1] Rework GRAPH_TABLE aggregate/window/SRF rejection using ParseExprKind. Commit f58567105 disallowed aggregates, window functions, and set-returning functions in a GRAPH_TABLE COLUMNS list by inspecting the parse state after transforming the list. This way is not capable of reporting a parse location for the misplaced construct; worse, the patch missed checking for aggregates etc. in GRAPH_TABLE WHERE. Instead give the COLUMNS list and the graph pattern WHERE clause their own ParseExprKind values and enforce the restriction within the parser's transformation functions check_agglevels_and_constraints(), transformWindowFuncCall(), and check_srf_call_placement(). (This reverts the code changes of f58567105, though we keep the test cases and add some more.) This is more consistent with how the parser implements other misplaced-construct checks, and it allows delivery of better error messages. Subqueries are likewise disallowed within a GRAPH_TABLE COLUMNS list or graph pattern WHERE clause; transformSubLink now rejects a SubLink under these ParseExprKinds. The parsing check rejects only aggregates having level zero. This is intentional: an outer-level aggregate is effectively a constant within the subquery containing GRAPH_TABLE, so there's no reason not to allow it. The case did not work before because GraphPropertyRef did not carry query-level information explicitly and the rewrite path did not consistently adjust already-outer references before nesting the expression under a generated subquery. Rejecting only level-zero aggregates therefore requires GraphPropertyRef to carry an explicit levelsup field and for check_agg_arguments_walker to account for GraphPropertyRef nodes directly. Since GraphPropertyRef is not a Var, teach the relevant query-level walkers to treat it as a Var-like node where needed, and prepare GRAPH_TABLE expressions for the added query level before replace_property_refs() resolves property refs. Note that this changes the SQLSTATE for a rejected aggregate from ERRCODE_FEATURE_NOT_SUPPORTED to ERRCODE_GROUPING_ERROR, consistent with how misplaced aggregates are rejected in other cases. Author: Sami Imseih Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAA5RZ0tvdYODLQvYwVzAxUPe5=E3vSe8Zy7TvrQq+syvGKpHSQ@mail.gmail.com Backpatch-through: 19 --- src/backend/optimizer/util/var.c | 22 ++++++- src/backend/parser/parse_agg.c | 25 ++++++++ src/backend/parser/parse_clause.c | 47 +------------- src/backend/parser/parse_expr.c | 10 +++ src/backend/parser/parse_func.c | 4 ++ src/backend/parser/parse_graphtable.c | 10 +-- src/backend/rewrite/rewriteGraphTable.c | 52 ++++++++-------- src/backend/rewrite/rewriteManip.c | 13 ++++ src/include/nodes/primnodes.h | 15 +++++ src/include/parser/parse_node.h | 2 + src/test/regress/expected/graph_table.out | 75 +++++++++++++++++++++-- src/test/regress/sql/graph_table.sql | 14 ++++- 12 files changed, 202 insertions(+), 87 deletions(-) diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 907a255c36f..929cb740874 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -6,7 +6,9 @@ * Note: for most purposes, PlaceHolderVar is considered a Var too, * even if its contained expression is variable-free. Also, CurrentOfExpr * is treated as a Var for purposes of determining whether an expression - * contains variables. + * contains variables. GraphPropertyRef carries a levelsup and is likewise + * treated as a Var when determining which query level a reference belongs to + * (see primnodes.h). * * * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group @@ -462,6 +464,12 @@ contain_vars_of_level_walker(Node *node, int *sublevels_up) return true; /* abort tree traversal and return true */ return false; } + if (IsA(node, GraphPropertyRef)) + { + if (((GraphPropertyRef *) node)->gprlevelsup == *sublevels_up) + return true; /* abort tree traversal and return true */ + return false; + } if (IsA(node, CurrentOfExpr)) { if (*sublevels_up == 0) @@ -585,6 +593,18 @@ locate_var_of_level_walker(Node *node, } return false; } + if (IsA(node, GraphPropertyRef)) + { + GraphPropertyRef *gpr = (GraphPropertyRef *) node; + + if (gpr->gprlevelsup == context->sublevels_up && + gpr->location >= 0) + { + context->var_location = gpr->location; + return true; /* abort tree traversal and return true */ + } + return false; + } if (IsA(node, CurrentOfExpr)) { /* since CurrentOfExpr doesn't carry location, nothing we can do */ diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 754a20507d0..ebc6dff2d0e 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -600,6 +600,12 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) break; + case EXPR_KIND_GRAPH_TABLE_COLUMNS: + case EXPR_KIND_GRAPH_TABLE_WHERE: + errkind = true; + + break; + /* * There is intentionally no default: case here, so that the * compiler will warn if we add a new ParseExprKind without @@ -784,6 +790,21 @@ check_agg_arguments_walker(Node *node, } return false; } + if (IsA(node, GraphPropertyRef)) + { + int gprlevelsup = ((GraphPropertyRef *) node)->gprlevelsup; + + /* convert levelsup to frame of reference of original query */ + gprlevelsup -= context->sublevels_up; + /* ignore local vars of subqueries */ + if (gprlevelsup >= 0) + { + if (context->min_varlevel < 0 || + context->min_varlevel > gprlevelsup) + context->min_varlevel = gprlevelsup; + } + return false; + } if (IsA(node, Aggref)) { int agglevelsup = ((Aggref *) node)->agglevelsup; @@ -1045,6 +1066,10 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_FOR_PORTION: err = _("window functions are not allowed in FOR PORTION OF expressions"); break; + case EXPR_KIND_GRAPH_TABLE_COLUMNS: + case EXPR_KIND_GRAPH_TABLE_WHERE: + errkind = true; + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 67de2733d8a..34a4fa0672b 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -945,10 +945,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt) List *colnames = NIL; ListCell *lc; int resno = 0; - bool saved_hasSublinks; - bool saved_hasAggs; - bool saved_hasWindowFuncs; - bool saved_hasTargetSRFs; rel = parserOpenPropGraph(pstate, rgt->graph_name, AccessShareLock); @@ -967,16 +963,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt) Assert(!pstate->p_lateral_active); pstate->p_lateral_active = true; - saved_hasSublinks = pstate->p_hasSubLinks; - pstate->p_hasSubLinks = false; - - saved_hasAggs = pstate->p_hasAggs; - pstate->p_hasAggs = false; - saved_hasWindowFuncs = pstate->p_hasWindowFuncs; - pstate->p_hasWindowFuncs = false; - saved_hasTargetSRFs = pstate->p_hasTargetSRFs; - pstate->p_hasTargetSRFs = false; - gp = transformGraphPattern(pstate, rgt->graph_pattern); /* @@ -991,7 +977,7 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt) TargetEntry *te; char *colname; - colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET); + colexpr = transformExpr(pstate, rt->val, EXPR_KIND_GRAPH_TABLE_COLUMNS); if (rt->name) colname = rt->name; @@ -1030,37 +1016,6 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt) pstate->p_graph_table_pstate = NULL; pstate->p_lateral_active = false; - /* - * If we support subqueries within GRAPH_TABLE, those need to be - * propagated to the queries resulting from rewriting graph table RTE. We - * don't do that right now, hence prohibit it for now. - */ - if (pstate->p_hasSubLinks) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("subqueries within GRAPH_TABLE reference are not supported"))); - pstate->p_hasSubLinks = saved_hasSublinks; - - /* - * GRAPH_TABLE cannot yet evaluate aggregate, window, or set-returning - * functions in its COLUMNS list, so prohibit them for now. - */ - if (pstate->p_hasAggs) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("aggregate functions in GRAPH_TABLE COLUMNS are not supported")); - if (pstate->p_hasWindowFuncs) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("window functions in GRAPH_TABLE COLUMNS are not supported")); - if (pstate->p_hasTargetSRFs) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-returning functions in GRAPH_TABLE COLUMNS are not supported")); - pstate->p_hasAggs = saved_hasAggs; - pstate->p_hasWindowFuncs = saved_hasWindowFuncs; - pstate->p_hasTargetSRFs = saved_hasTargetSRFs; - return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true); } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 30c889f505f..d69a0a79f48 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -579,6 +579,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_GENERATED_COLUMN: case EXPR_KIND_CYCLE_MARK: case EXPR_KIND_PROPGRAPH_PROPERTY: + case EXPR_KIND_GRAPH_TABLE_COLUMNS: + case EXPR_KIND_GRAPH_TABLE_WHERE: /* okay */ break; @@ -1845,6 +1847,10 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_CYCLE_MARK: /* okay */ break; + case EXPR_KIND_GRAPH_TABLE_COLUMNS: + case EXPR_KIND_GRAPH_TABLE_WHERE: + err = _("cannot use subquery in GRAPH_TABLE reference"); + break; case EXPR_KIND_CHECK_CONSTRAINT: case EXPR_KIND_DOMAIN_CHECK: err = _("cannot use subquery in check constraint"); @@ -3255,6 +3261,10 @@ ParseExprKindName(ParseExprKind exprKind) return "property definition expression"; case EXPR_KIND_FOR_PORTION: return "FOR PORTION OF"; + case EXPR_KIND_GRAPH_TABLE_COLUMNS: + return "GRAPH_TABLE COLUMNS"; + case EXPR_KIND_GRAPH_TABLE_WHERE: + return "GRAPH_TABLE WHERE"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index c87804f5d41..0c72b95e4a5 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2848,6 +2848,10 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) case EXPR_KIND_FOR_PORTION: err = _("set-returning functions are not allowed in FOR PORTION OF expressions"); break; + case EXPR_KIND_GRAPH_TABLE_COLUMNS: + case EXPR_KIND_GRAPH_TABLE_WHERE: + errkind = true; + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c index 73fbfb541f7..c8e230379a5 100644 --- a/src/backend/parser/parse_graphtable.c +++ b/src/backend/parser/parse_graphtable.c @@ -92,7 +92,7 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref) if (IsA(field1, A_Star) || IsA(field2, A_Star)) { - if (pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET) + if (pstate->p_expr_kind == EXPR_KIND_GRAPH_TABLE_COLUMNS) ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("\"*\" is not supported here"), @@ -106,7 +106,6 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref) elvarname = strVal(field1); propname = strVal(field2); - if (list_member(gpstate->variables, field1)) { GraphPropertyRef *gpr; @@ -137,6 +136,8 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref) gpr->location = cref->location; gpr->elvarname = elvarname; + + gpr->gprlevelsup = 0; gpr->propid = pgpform->oid; gpr->typeId = pgpform->pgptypid; gpr->typmod = pgpform->pgptypmod; @@ -250,8 +251,7 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep) gpstate->cur_gep = gep; gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr); - - gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE); + gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_GRAPH_TABLE_WHERE); /* * Assign collations here for the reason mentioned in the prologue of @@ -387,7 +387,7 @@ transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern) transformPathPatternList(pstate, graph_pattern->path_pattern_list)); graph_pattern->path_pattern_list = path_pattern_list; - graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_WHERE); + graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_GRAPH_TABLE_WHERE); assign_expr_collations(pstate, graph_pattern->whereClause); return (Node *) graph_pattern; diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c index 0eaf28b3de5..ce32a97b4f2 100644 --- a/src/backend/rewrite/rewriteGraphTable.c +++ b/src/backend/rewrite/rewriteGraphTable.c @@ -34,7 +34,6 @@ #include "parser/parse_oper.h" #include "parser/parse_relation.h" #include "parser/parsetree.h" -#include "parser/parse_graphtable.h" #include "rewrite/rewriteGraphTable.h" #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" @@ -100,7 +99,8 @@ static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte); 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 Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex, + Index gprlevelsup); /* * Convert GRAPH_TABLE clause into a subquery using relational @@ -530,9 +530,10 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path) if (pf->whereClause) { - Node *tr; + Node *tr = copyObject(pf->whereClause); - tr = replace_property_refs(rte->relid, pf->whereClause, list_make1(pe)); + IncrementVarSublevelsUp(tr, 1, 0); + tr = replace_property_refs(rte->relid, tr, list_make1(pe)); qual_exprs = lappend(qual_exprs, tr); } @@ -540,9 +541,10 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path) if (rte->graph_pattern->whereClause) { - Node *path_quals = replace_property_refs(rte->relid, - (Node *) rte->graph_pattern->whereClause, - graph_path); + Node *path_quals = copyObject(rte->graph_pattern->whereClause); + + IncrementVarSublevelsUp(path_quals, 1, 0); + path_quals = replace_property_refs(rte->relid, path_quals, graph_path); qual_exprs = lappend(qual_exprs, path_quals); } @@ -551,9 +553,11 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path) qual_exprs ? (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1) : NULL); /* Construct query targetlist from COLUMNS specification of GRAPH_TABLE. */ + path_query->targetList = copyObject(rte->graph_table_columns); + IncrementVarSublevelsUp((Node *) path_query->targetList, 1, 0); path_query->targetList = castNode(List, replace_property_refs(rte->relid, - (Node *) rte->graph_table_columns, + (Node *) path_query->targetList, graph_path)); /* @@ -1025,21 +1029,7 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context * { if (node == NULL) return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) node; - Var *newvar = copyObject(var); - - /* - * If it's already a Var, then it was a lateral reference. Since we - * are in a subquery after the rewrite, we have to increase the level - * by one. - */ - newvar->varlevelsup++; - - return (Node *) newvar; - } - else if (IsA(node, GraphPropertyRef)) + if (IsA(node, GraphPropertyRef)) { GraphPropertyRef *gpr = (GraphPropertyRef *) node; Node *n = NULL; @@ -1063,6 +1053,8 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context * */ Assert(found_mapping); + Assert(gpr->gprlevelsup == 0); + mapping_factor = found_mapping->path_factor; /* @@ -1093,7 +1085,8 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context * n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP, tup, Anum_pg_propgraph_label_property_plpexpr))); - ChangeVarNodes(n, 1, mapping_factor->factorpos + 1, 0); + ChangeVarNodes(n, 1, mapping_factor->factorpos + 1, + gpr->gprlevelsup); ReleaseSysCache(tup); } @@ -1132,8 +1125,10 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context * * SQL/PGQ standard section 6.5 Property Reference, General * Rule 2.b. */ - n = get_element_property_expr(found_mapping->elemoid, gpr->propid, - mapping_factor->factorpos + 1); + n = get_element_property_expr(found_mapping->elemoid, + gpr->propid, + mapping_factor->factorpos + 1, + gpr->gprlevelsup); if (!n) n = (Node *) makeNullConst(gpr->typeId, gpr->typmod, gpr->collation); @@ -1304,7 +1299,8 @@ is_property_associated_with_label(Oid labeloid, Oid propoid) * NULL. */ static Node * -get_element_property_expr(Oid elemoid, Oid propoid, int rtindex) +get_element_property_expr(Oid elemoid, Oid propoid, int rtindex, + Index gprlevelsup) { Relation rel; SysScanDesc scan; @@ -1331,7 +1327,7 @@ get_element_property_expr(Oid elemoid, Oid propoid, int rtindex) continue; n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP, proptup, Anum_pg_propgraph_label_property_plpexpr))); - ChangeVarNodes(n, 1, rtindex, 0); + ChangeVarNodes(n, 1, rtindex, gprlevelsup); ReleaseSysCache(proptup); break; diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 3653f00d383..f147d64c0c3 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -815,6 +815,19 @@ IncrementVarSublevelsUp_walker(Node *node, var->varlevelsup += context->delta_sublevels_up; return false; /* done here */ } + if (IsA(node, GraphPropertyRef)) + { + /* + * Unlike a Var, we must not adjust a GraphPropertyRef's level here. + * This walker runs over a GRAPH_TABLE COLUMNS/WHERE expression before + * replace_property_refs() resolves each GraphPropertyRef to a Var + * local to the generated path query, setting its final level via + * ChangeVarNodes(). As we don't yet allow correlated subqueries + * inside a GRAPH_TABLE reference, gprlevelsup is always 0; assert it. + */ + Assert(((GraphPropertyRef *) node)->gprlevelsup == 0); + return false; /* done here */ + } if (IsA(node, CurrentOfExpr)) { /* this should not happen */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 5a636d1f179..aa7c7841b2b 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -2196,11 +2196,26 @@ typedef struct GraphLabelRef /* * GraphPropertyRef - property reference inside GRAPH_TABLE clause + * + * A GraphPropertyRef is generated during transformation and resolved away by + * the rewriter, so only a few places need to know about it. It carries a + * levelsup and is treated like a Var by contain_vars_of_level() and + * locate_var_of_level(); IncrementVarSublevelsUp() leaves it alone, since + * replace_property_refs() passes its gprlevelsup through when resolving it to + * a Var. + * We don't yet allow correlated subqueries inside a GRAPH_TABLE reference, so + * gprlevelsup is always 0 today. */ typedef struct GraphPropertyRef { Expr xpr; const char *elvarname; + + /* + * would be > 0 for an outer-query reference, but see above: always 0 + * today + */ + Index gprlevelsup; Oid propid; Oid typeId; int32 typmod; diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index f7f4ba6c2a8..34f061df0fa 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -84,6 +84,8 @@ typedef enum ParseExprKind EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */ EXPR_KIND_CYCLE_MARK, /* cycle mark value */ EXPR_KIND_PROPGRAPH_PROPERTY, /* derived property expression */ + EXPR_KIND_GRAPH_TABLE_COLUMNS, /* GRAPH_TABLE COLUMNS list item */ + EXPR_KIND_GRAPH_TABLE_WHERE, /* WHERE in a GRAPH_TABLE */ } ParseExprKind; diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out index 2e862a82ba0..eb2bd1b0220 100644 --- a/src/test/regress/expected/graph_table.out +++ b/src/test/regress/expected/graph_table.out @@ -471,13 +471,72 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[ ERROR: "*" not allowed here LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT... ^ --- aggregate, window, and set-returning functions are not supported in COLUMNS +-- aggregate, grouping, window, and set-returning functions are not allowed +-- in the COLUMNS list or the graph pattern WHERE, except for outer-level aggs SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(*) AS num)); -ERROR: aggregate functions in GRAPH_TABLE COLUMNS are not supported +ERROR: aggregate functions are not allowed in GRAPH_TABLE COLUMNS +LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(*) A... + ^ +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (GROUPING(c.customer_id) AS g)); +ERROR: grouping operations are not allowed in GRAPH_TABLE COLUMNS +LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (GROUPING(c... + ^ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (row_number() OVER () AS rn)); -ERROR: window functions in GRAPH_TABLE COLUMNS are not supported +ERROR: window functions are not allowed in GRAPH_TABLE COLUMNS +LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (row_number... + ^ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (generate_series(1, 2) AS gs)); -ERROR: set-returning functions in GRAPH_TABLE COLUMNS are not supported +ERROR: set-returning functions are not allowed in GRAPH_TABLE COLUMNS +LINE 1: ...APH_TABLE (myshop MATCH (c IS customers) COLUMNS (generate_s... + ^ +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(c.customer_id) > 0) COLUMNS (c.name AS nm)); +ERROR: aggregate functions are not allowed in GRAPH_TABLE WHERE +LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(c.cu... + ^ +SELECT count(*) FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE 1 IN (c.customer_id, 1000)) COLUMNS (c.name AS nm)) t; + count +------- + 1 +(1 row) + +SELECT EXISTS(SELECT num FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(o.customer_id) AS num)) t) FROM customers o; + exists +-------- + t +(1 row) + +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers) WHERE count(o.customer_id) > 0 COLUMNS (c.name AS nm)) t) FROM customers o; + exists +-------- + t +(1 row) + +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(o.customer_id) > 0) COLUMNS (c.name AS nm)) t) FROM customers o; + exists +-------- + t +(1 row) + +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(c.customer_id + o.customer_id) > 0) COLUMNS (c.name AS nm)) t) FROM customers o; +ERROR: aggregate functions are not allowed in GRAPH_TABLE WHERE +LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(c.cu... + ^ +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE GROUPING(o.customer_id) = 1) COLUMNS (c.name AS nm)) t) FROM customers o GROUP BY customer_id; + exists +-------- + f + f + f +(3 rows) + +-- a property in a GRAPH_TABLE subquery below the aggregate is not local to it, +-- so the aggregate is allowed +SELECT count(o.customer_id + (SELECT count(n) FROM GRAPH_TABLE (g1 MATCH (src) COLUMNS (src.vprop1 AS n)) x)) FROM customers o; + count +------- + 3 +(1 row) + -- consecutive element patterns with same kind SELECT * FROM GRAPH_TABLE (g1 MATCH ()() COLUMNS (1 as one)); ERROR: adjacent vertex patterns are not supported @@ -1118,9 +1177,13 @@ SELECT * FROM customers co WHERE co.customer_id = (SELECT customer_id FROM GRAPH -- query within graph table SELECT sname, dname FROM GRAPH_TABLE (g1 MATCH (src)->(dest) WHERE src.vprop1 > (SELECT max(v1.vprop1) FROM v1) COLUMNS(src.vname AS sname, dest.vname AS dname)); -ERROR: subqueries within GRAPH_TABLE reference are not supported +ERROR: cannot use subquery in GRAPH_TABLE reference +LINE 1: ..._TABLE (g1 MATCH (src)->(dest) WHERE src.vprop1 > (SELECT ma... + ^ SELECT sname, dname FROM GRAPH_TABLE (g1 MATCH (src)->(dest) WHERE out_degree(src.vname) > (SELECT max(out_degree(nname)) FROM GRAPH_TABLE (g1 MATCH (node) COLUMNS (node.vname AS nname))) COLUMNS(src.vname AS sname, dest.vname AS dname)); -ERROR: subqueries within GRAPH_TABLE reference are not supported +ERROR: cannot use subquery in GRAPH_TABLE reference +LINE 1: ...MATCH (src)->(dest) WHERE out_degree(src.vname) > (SELECT ma... + ^ -- GRAPH_TABLE subquery in HAVING clause (tests expression mutator) SELECT src.vname, count(*) FROM v1 AS src GROUP BY src.vname diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql index 21e70015f6e..240288ce962 100644 --- a/src/test/regress/sql/graph_table.sql +++ b/src/test/regress/sql/graph_table.sql @@ -306,10 +306,22 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1 | vl1)-[conn]->(dest) COLUMNS (c SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*)); -- star anywhere else is not allowed as a property reference SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[IS customer_orders]->(o IS orders) COLUMNS (c.name)); --- aggregate, window, and set-returning functions are not supported in COLUMNS +-- aggregate, grouping, window, and set-returning functions are not allowed +-- in the COLUMNS list or the graph pattern WHERE, except for outer-level aggs SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(*) AS num)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (GROUPING(c.customer_id) AS g)); SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (row_number() OVER () AS rn)); SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (generate_series(1, 2) AS gs)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(c.customer_id) > 0) COLUMNS (c.name AS nm)); +SELECT count(*) FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE 1 IN (c.customer_id, 1000)) COLUMNS (c.name AS nm)) t; +SELECT EXISTS(SELECT num FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (count(o.customer_id) AS num)) t) FROM customers o; +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers) WHERE count(o.customer_id) > 0 COLUMNS (c.name AS nm)) t) FROM customers o; +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(o.customer_id) > 0) COLUMNS (c.name AS nm)) t) FROM customers o; +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE count(c.customer_id + o.customer_id) > 0) COLUMNS (c.name AS nm)) t) FROM customers o; +SELECT EXISTS(SELECT nm FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE GROUPING(o.customer_id) = 1) COLUMNS (c.name AS nm)) t) FROM customers o GROUP BY customer_id; +-- a property in a GRAPH_TABLE subquery below the aggregate is not local to it, +-- so the aggregate is allowed +SELECT count(o.customer_id + (SELECT count(n) FROM GRAPH_TABLE (g1 MATCH (src) COLUMNS (src.vprop1 AS n)) x)) FROM customers o; -- consecutive element patterns with same kind SELECT * FROM GRAPH_TABLE (g1 MATCH ()() COLUMNS (1 as one)); SELECT * FROM GRAPH_TABLE (g1 MATCH -> COLUMNS (1 AS one)); -- 2.47.3