From 0fa76949c322d588c2b8934047d27cdf4cc1681a Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Tue, 6 Feb 2024 16:27:36 +0500 Subject: [PATCH 1/9] Add initial code for list comprehension - Additionally, fixed a bug in nested queries. --- sql/agtype_typecast.sql | 3 +- src/backend/parser/cypher_clause.c | 21 ++++-- src/backend/parser/cypher_expr.c | 53 +++++++++++++++ src/backend/parser/cypher_gram.y | 93 +++++++++++++++++++++++++- src/backend/parser/cypher_item.c | 102 ++++++++++++++++++++++++++++- src/backend/utils/adt/agtype.c | 54 ++++++++++++++- src/include/nodes/cypher_nodes.h | 4 ++ src/include/parser/cypher_clause.h | 9 +++ src/include/parser/cypher_kwlist.h | 1 + 9 files changed, 329 insertions(+), 11 deletions(-) diff --git a/sql/agtype_typecast.sql b/sql/agtype_typecast.sql index 08e27fb33..5326182fb 100644 --- a/sql/agtype_typecast.sql +++ b/sql/agtype_typecast.sql @@ -181,7 +181,8 @@ CREATE FUNCTION ag_catalog.age_range(variadic "any") PARALLEL SAFE AS 'MODULE_PATHNAME'; -CREATE FUNCTION ag_catalog.age_unnest(agtype) +CREATE FUNCTION ag_catalog.age_unnest(agtype, + list_comprehension boolean = false) RETURNS SETOF agtype LANGUAGE c IMMUTABLE diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index c164e194d..6d6d8c8a3 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -282,7 +282,7 @@ static Query *transform_cypher_call_subquery(cypher_parsestate *cpstate, #define transform_prev_cypher_clause(cpstate, prev_clause, add_rte_to_query) \ transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, \ prev_clause, NULL, add_rte_to_query) -static ParseNamespaceItem +ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, @@ -388,7 +388,10 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, } else if (is_ag_node(self, cypher_unwind)) { - result = transform_cypher_unwind(cpstate, clause); + cypher_unwind *n = (cypher_unwind *) self; + result = transform_cypher_clause_with_where(cpstate, + transform_cypher_unwind, + clause, n->where); } else if (is_ag_node(self, cypher_call)) { @@ -1320,6 +1323,7 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, Node *funcexpr; TargetEntry *te; ParseNamespaceItem *pnsi; + bool is_list_comp = self->collect != NULL; query = makeNode(Query); query->commandType = CMD_SELECT; @@ -1348,8 +1352,7 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, ereport(ERROR, (errcode(ERRCODE_DUPLICATE_ALIAS), errmsg("duplicate variable \"%s\"", self->target->name), - parser_errposition((ParseState *) cpstate, - target_syntax_loc))); + parser_errposition(pstate, target_syntax_loc))); } expr = transform_cypher_expr(cpstate, self->target->val, @@ -1361,11 +1364,12 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, old_expr_kind = pstate->p_expr_kind; pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; funcexpr = ParseFuncOrColumn(pstate, unwind->funcname, - list_make1(expr), + list_make2(expr, makeBoolConst(is_list_comp, false)), pstate->p_last_srf, unwind, false, target_syntax_loc); pstate->p_expr_kind = old_expr_kind; + pstate->p_hasAggs = is_list_comp; te = makeTargetEntry((Expr *) funcexpr, (AttrNumber) pstate->p_next_resno++, @@ -1376,6 +1380,7 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, query->rteperminfos = pstate->p_rteperminfos; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); query->hasTargetSRFs = pstate->p_hasTargetSRFs; + query->hasAggs = pstate->p_hasAggs; assign_query_collations(pstate, query); @@ -2316,6 +2321,7 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, pnsi = transform_cypher_clause_as_subquery(cpstate, transform, clause, NULL, true); + Assert(pnsi != NULL); rtindex = list_length(pstate->p_rtable); // rte is the only RangeTblEntry in pstate @@ -2346,7 +2352,6 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, } query->jointree = makeFromExpr(pstate->p_joinlist, where_qual); - assign_query_collations(pstate, query); } else { @@ -2357,6 +2362,8 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, query->hasTargetSRFs = pstate->p_hasTargetSRFs; query->hasAggs = pstate->p_hasAggs; + assign_query_collations(pstate, query); + return query; } @@ -5942,7 +5949,7 @@ static Expr *cypher_create_properties(cypher_parsestate *cpstate, * This function is similar to transformFromClause() that is called with a * single RangeSubselect. */ -static ParseNamespaceItem * +ParseNamespaceItem * transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index f110e8f53..15ba19b61 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -90,6 +90,8 @@ static Node *transform_WholeRowRef(ParseState *pstate, ParseNamespaceItem *pnsi, static ArrayExpr *make_agtype_array_expr(List *args); static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, ColumnRef *cr); +static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, + cypher_unwind *expr); /* transform a cypher expression */ Node *transform_cypher_expr(cypher_parsestate *cpstate, Node *expr, @@ -190,6 +192,10 @@ static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, if (is_ag_node(expr, cypher_comparison_boolexpr)) return transform_cypher_comparison_boolexpr(cpstate, (cypher_comparison_boolexpr *)expr); + if (is_ag_node(expr, cypher_unwind)) + return transform_cypher_list_comprehension(cpstate, + (cypher_unwind *) expr); + ereport(ERROR, (errmsg_internal("unrecognized ExtensibleNode: %s", ((ExtensibleNode *)expr)->extnodename))); @@ -1629,6 +1635,9 @@ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink) /* Accept sublink here; caller must throw error if wanted */ break; + case EXPR_KIND_JOIN_ON: + case EXPR_KIND_JOIN_USING: + case EXPR_KIND_FROM_FUNCTION: case EXPR_KIND_SELECT_TARGET: case EXPR_KIND_FROM_SUBSELECT: case EXPR_KIND_WHERE: @@ -1670,8 +1679,52 @@ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink) sublink->testexpr = NULL; sublink->operName = NIL; } + else if (sublink->subLinkType == EXPR_SUBLINK || + sublink->subLinkType == ARRAY_SUBLINK) + { + /* + * Make sure the subselect delivers a single column (ignoring resjunk + * targets). + */ + if (count_nonjunk_tlist_entries(qtree->targetList) != 1) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery must return only one column"), + parser_errposition(pstate, sublink->location))); + } + + /* + * EXPR and ARRAY need no test expression or combining operator. These + * fields should be null already, but make sure. + */ + sublink->testexpr = NULL; + sublink->operName = NIL; + } + else if (sublink->subLinkType == MULTIEXPR_SUBLINK) + { + /* Same as EXPR case, except no restriction on number of columns */ + sublink->testexpr = NULL; + sublink->operName = NIL; + } else elog(ERROR, "unsupported SubLink type"); return result; } + +static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, + cypher_unwind *unwind) +{ + cypher_clause cc; + + cc.prev = NULL; + cc.next = NULL; + cc.self = (Node *)unwind; + + transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, &cc, + NULL, true); + + return transform_cypher_expr(cpstate, unwind->collect, + EXPR_KIND_SELECT_TARGET); +} \ No newline at end of file diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index a579fe76f..4cceb2363 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -77,7 +77,7 @@ CALL CASE COALESCE CONTAINS CREATE DELETE DESC DESCENDING DETACH DISTINCT ELSE END_P ENDS EXISTS EXPLAIN - FALSE_P + FALSE_P FOR IN IS LIMIT MATCH MERGE @@ -115,6 +115,9 @@ /* UNWIND clause */ %type unwind +/* list comprehension */ +%type list_comprehension + /* SET and REMOVE clause */ %type set set_item remove remove_item %type set_item_list remove_item_list @@ -133,6 +136,9 @@ /* common */ %type where_opt +/* list comprehension optional mapping expression */ +%type mapping_expr_opt + /* pattern */ %type pattern simple_path_opt_parens simple_path %type path anonymous_path @@ -242,6 +248,13 @@ static bool is_A_Expr_a_comparison_operation(cypher_comparison_aexpr *a); static Node *build_comparison_expression(Node *left_grammar_node, Node *right_grammar_node, char *opr_name, int location); + +// list_comprehension +static Node *build_list_comprehension_node(char *var_name, Node *expr, + Node *where, Node *mapping_expr, + int var_loc, int expr_loc, + int where_loc,int mapping_loc); + %} %% @@ -940,6 +953,8 @@ unwind: n = make_ag_node(cypher_unwind); n->target = res; + n->where = NULL; + n->collect = NULL; $$ = (Node *) n; } @@ -1653,6 +1668,7 @@ expr: $$ = make_typecast_expr($1, $3, @2); } | expr_atom + | list_comprehension ; expr_opt: @@ -1865,6 +1881,25 @@ list: } ; +list_comprehension: + '[' FOR var_name IN expr where_opt mapping_expr_opt ']' + { + $$ = build_list_comprehension_node($3, $5, $6, $7, + @3, @5, @6, @7); + } + ; + +mapping_expr_opt: + /* empty */ + { + $$ = NULL; + } + | '|' expr + { + $$ = $2; + } + ; + expr_case: CASE expr expr_case_when_list expr_case_default END_P { @@ -2033,6 +2068,7 @@ safe_keywords: | ENDS { $$ = pnstrdup($1, 4); } | EXISTS { $$ = pnstrdup($1, 6); } | EXPLAIN { $$ = pnstrdup($1, 7); } + | FOR { $$ = pnstrdup($1, 3); } | IN { $$ = pnstrdup($1, 2); } | IS { $$ = pnstrdup($1, 2); } | LIMIT { $$ = pnstrdup($1, 6); } @@ -2890,3 +2926,58 @@ static cypher_relationship *build_VLE_relation(List *left_arg, /* return the VLE relation node */ return cr; } + +/* helper function to build a list_comprehension grammar node */ +static Node *build_list_comprehension_node(char *var_name, Node *expr, + Node *where, Node *mapping_expr, + int var_loc, int expr_loc, + int where_loc, int mapping_loc) +{ + ResTarget *res = NULL; + cypher_unwind *unwind = NULL; + ColumnRef *cref = NULL; + + /* + * Build the ResTarget node for the UNWIND variable var_name attached to + * expr. + */ + res = makeNode(ResTarget); + res->name = var_name; + res->val = (Node *)expr; + res->location = expr_loc; + + /* build the UNWIND node */ + unwind = make_ag_node(cypher_unwind); + unwind->target = res; + + /* + * We need to make a ColumnRef of var_name so that it can be used as an expr + * for the where clause part of unwind. + */ + cref = makeNode(ColumnRef); + cref->fields = list_make1(makeString(var_name)); + cref->location = var_loc; + + unwind->where = where; + + /* if there is a mapping function, add its arg to collect */ + if (mapping_expr != NULL) + { + unwind->collect = make_function_expr(list_make1(makeString("collect")), + list_make1(mapping_expr), + mapping_loc); + } + /* + * Otherwise, we need to add in the ColumnRef of the variable var_name as + * the arg to collect instead. This implies that the RETURN variable is + * var_name. + */ + else + { + unwind->collect = make_function_expr(list_make1(makeString("collect")), + list_make1(cref), mapping_loc); + } + + /* return the UNWIND node */ + return (Node *)unwind; +} \ No newline at end of file diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index e88478c3d..50cd1bd27 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -33,8 +33,9 @@ static List *ExpandAllTables(ParseState *pstate, int location); static List *expand_pnsi_attrs(ParseState *pstate, ParseNamespaceItem *pnsi, - int sublevels_up, bool require_col_privs, + int sublevels_up, bool require_col_privs, int location); +bool has_a_cypher_list_comprehension_node(Node *expr); // see transformTargetEntry() TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, @@ -42,10 +43,17 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, char *colname, bool resjunk) { ParseState *pstate = (ParseState *)cpstate; + bool old_p_lateral_active = pstate->p_lateral_active; + + /* we want to see lateral variables */ + pstate->p_lateral_active = true; if (!expr) expr = transform_cypher_expr(cpstate, node, expr_kind); + /* set lateral back to what it was */ + pstate->p_lateral_active = old_p_lateral_active; + if (!colname && !resjunk) colname = FigureColname(node); @@ -53,6 +61,44 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, colname, resjunk); } +/* + * Helper function to determine if the passed node has a list_comprehension + * node embedded in it. + */ +static bool has_a_cypher_list_comprehension_node(Node *expr) +{ + /* return false on NULL input */ + if (expr == NULL) + { + return false; + } + + /* if this is an A_Indirection, because they can operate on lists */ + if (nodeTag(expr) == T_A_Indirection) + { + /* set expr to the object of the indirection */ + expr = ((A_Indirection *)expr)->arg; + } + + /* if expr is a cypher_unwind */ + if (nodeTag(expr) == T_ExtensibleNode && + is_ag_node(expr, cypher_unwind)) + { + cypher_unwind *cu = NULL; + + cu = (cypher_unwind *)expr; + + /* if it is a list comprehension node, return true */ + if (cu->collect != NULL) + { + return true; + } + } + + /* otherwise, return false */ + return false; +} + // see transformTargetList() List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, List **groupClause, ParseExprKind expr_kind) @@ -118,6 +164,60 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, { hasAgg = true; } + + /* + * This is for a special case with list comprehension, which is embedded + * in a cypher_unwind node. We need to group the results but not expose + * the grouping expression. + */ + + /* verify that val has an embedded list_comprehension node in it */ + if (has_a_cypher_list_comprehension_node(item->val)) + { + ParseState *pstate = &cpstate->pstate; + ParseNamespaceItem *nsitem = NULL; + RangeTblEntry *rte = NULL; + // hasAgg = true; + /* + * There should be at least 2 entries in p_namespace. One for the + * variable in the reading clause and one for the variable in the + * list_comprehension expression. Otherwise, there is nothing to + * group with. + */ + if (list_length(pstate->p_namespace) > 1) + { + /* + * Get the first namespace item which should be the first + * variable from the reading clause. + */ + nsitem = lfirst(list_head(pstate->p_namespace)); + /* extract the rte */ + rte = nsitem->p_rte; + + /* + * If we have a non-null column name make a ColumnRef to it. + * Otherwise, there wasn't a variable specified in the reading + * clause. If that is the case don't. Because there isn't + * anything to group with. + */ + if (rte->eref->colnames != NULL) + { + ColumnRef *cref = NULL; + char *colname = NULL; + + /* get the name of the column (varname) */ + colname = strVal(lfirst(list_head(rte->eref->colnames))); + + /* create the ColumnRef */ + cref = makeNode(ColumnRef); + cref->fields = list_make1(makeString(colname)); + cref->location = -1; + + /* add the expression for grouping */ + group_clause = lappend(group_clause, cref); + } + } + } } /* diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index e65cca410..63b1e150c 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -10574,6 +10574,16 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) { agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); } + + /* if agtv_value is null, we have an empty list [], so add it */ + // // we need this for list_comprehension + // if (agtv_value == NULL) + // { + // castate->res = push_agtype_value(&castate->parse_state, + // WAGT_BEGIN_ARRAY, NULL); + // castate->res = push_agtype_value(&castate->parse_state, + // WAGT_END_ARRAY, NULL); + // } } /* skip the arg if agtype null */ @@ -11411,6 +11421,7 @@ PG_FUNCTION_INFO_V1(age_unnest); Datum age_unnest(PG_FUNCTION_ARGS) { agtype *agtype_arg = NULL; + bool list_comprehension = false; ReturnSetInfo *rsi; Tuplestorestate *tuple_store; TupleDesc tupdesc; @@ -11421,13 +11432,35 @@ Datum age_unnest(PG_FUNCTION_ARGS) agtype_value v; agtype_iterator_token r; - // check for null + /* verify that we have the correct number of args */ + if (PG_NARGS() != 2) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid number of arguments to unnest"))); + } + + /* verify that our flags are not null */ + if (PG_ARGISNULL(1)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid unnest boolean flags passed"))); + } + + /* check for a NULL expr */ if (PG_ARGISNULL(0)) { PG_RETURN_NULL(); } + /* get our flags */ + list_comprehension = PG_GETARG_BOOL(1); + + /* get the input expression */ agtype_arg = AG_GET_ARG_AGTYPE_P(0); + + /* verify that it resolves to an array */ if (!AGT_ROOT_IS_ARRAY(agtype_arg)) { ereport(ERROR, @@ -11484,6 +11517,25 @@ Datum age_unnest(PG_FUNCTION_ARGS) } } + /* + * If this is for list_comprehension, we need to add a NULL as the last row. + * This NULL will allow empty lists (either filtered out by where, creating + * an empty list, or just a generic empty list) to be preserved. + */ + if (list_comprehension) + { + Datum values[1] = {0}; + bool nulls[1] = {true}; + + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + tuplestore_puttuple(tuple_store, + heap_form_tuple(ret_tdesc, values, nulls)); + + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + } + MemoryContextDelete(tmp_cxt); rsi->setResult = tuple_store; diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index a3022fea1..c95e84941 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -110,6 +110,10 @@ typedef struct cypher_unwind { ExtensibleNode extensible; ResTarget *target; + + /* for list comprehension */ + Node *where; + Node *collect; } cypher_unwind; typedef struct cypher_merge diff --git a/src/include/parser/cypher_clause.h b/src/include/parser/cypher_clause.h index dd554650b..6ce76c737 100644 --- a/src/include/parser/cypher_clause.h +++ b/src/include/parser/cypher_clause.h @@ -39,4 +39,13 @@ Query *cypher_parse_sub_analyze(Node *parseTree, CommonTableExpr *parentCTE, bool locked_from_parent, bool resolve_unknowns); + +typedef Query *(*transform_method)(cypher_parsestate *cpstate, + cypher_clause *clause); + +ParseNamespaceItem *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, + transform_method transform, + cypher_clause *clause, + Alias *alias, + bool add_rte_to_query); #endif diff --git a/src/include/parser/cypher_kwlist.h b/src/include/parser/cypher_kwlist.h index d15a0e34a..6381822a5 100644 --- a/src/include/parser/cypher_kwlist.h +++ b/src/include/parser/cypher_kwlist.h @@ -21,6 +21,7 @@ PG_KEYWORD("ends", ENDS, RESERVED_KEYWORD) PG_KEYWORD("exists", EXISTS, RESERVED_KEYWORD) PG_KEYWORD("explain", EXPLAIN, RESERVED_KEYWORD) PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD) +PG_KEYWORD("for", FOR, RESERVED_KEYWORD) PG_KEYWORD("in", IN, RESERVED_KEYWORD) PG_KEYWORD("is", IS, RESERVED_KEYWORD) PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD) From 76e98d0753d04074f39cd2451ce0a162ef58ef67 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Sat, 16 Dec 2023 18:04:05 +0500 Subject: [PATCH 2/9] Resolve variable scope and ambigous variable issue - For scoping the variable, remove the pnsi from namespace list after being used by the list comprehension. - To resolve the ambigous variable issue, only scan the pnsi of list_comp subquery. --- src/backend/parser/cypher_expr.c | 39 ++++++++++++++++++++++---- src/include/parser/cypher_parse_node.h | 1 + 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 15ba19b61..068a0513b 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -337,8 +337,23 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref) Assert(IsA(field1, String)); colname = strVal(field1); - /* Try to identify as an unqualified column */ - node = colNameToVar(pstate, colname, false, cref->location); + if (cpstate->p_list_comp) + { + /* + * Just scan through the last pnsi(that is for list comp) + * to find the column. + */ + node = scanNSItemForColumn(pstate, + llast(pstate->p_namespace), + 0, colname, cref->location); + } + else + { + /* Try to identify as an unqualified column */ + node = colNameToVar(pstate, colname, false, + cref->location); + } + if (node != NULL) { break; @@ -1717,14 +1732,28 @@ static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, cypher_unwind *unwind) { cypher_clause cc; + Node* expr; + ParseNamespaceItem *pnsi; + ParseState *pstate = (ParseState *)cpstate; + + cpstate->p_list_comp = true; cc.prev = NULL; cc.next = NULL; cc.self = (Node *)unwind; - transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, &cc, - NULL, true); + pnsi = transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, &cc, + NULL, true); - return transform_cypher_expr(cpstate, unwind->collect, + expr = transform_cypher_expr(cpstate, unwind->collect, EXPR_KIND_SELECT_TARGET); + + /* + * Remove the namespace item for listcomp subquery + * because it should not be visible outside + */ + pstate->p_namespace = list_delete_ptr(pstate->p_namespace, pnsi); + cpstate->p_list_comp = false; + + return expr; } \ No newline at end of file diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h index c0eb5a7d7..9f81b2d4c 100644 --- a/src/include/parser/cypher_parse_node.h +++ b/src/include/parser/cypher_parse_node.h @@ -49,6 +49,7 @@ typedef struct cypher_parsestate */ bool exprHasAgg; bool p_opt_match; + bool p_list_comp; } cypher_parsestate; typedef struct errpos_ecb_state From 50bc4b1cfb9fd50fbe314f70dc9572904b94d5f2 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Sun, 21 Jan 2024 22:17:35 +0500 Subject: [PATCH 3/9] Fix non agg plan node issue in list comprehension - Resolved the issue 'aggref found in non-agg plan node' when the list comprehension is used as a property constraint in MATCH and other clauses or when used in WHERE clause. Intead of adding qual in the jointree, we are now adding the qual in having node of query. e.g. MATCH (n {list: [for i in [1,2,3]]}) MATCH (n) WHERE n.list=[for i in [1,2,3]] WITH n WHERE n.list=[for i in [1,2,3]] - Removed redundant call to function tranform_cypher_clause_with_where by tranform_match_clause. --- src/backend/nodes/cypher_outfuncs.c | 1 + src/backend/parser/cypher_clause.c | 78 +++++++++++++++++++++---- src/backend/parser/cypher_expr.c | 1 - src/backend/parser/cypher_item.c | 89 +++++++++++++++++++++++------ src/include/nodes/ag_nodes.h | 1 + src/include/parser/cypher_item.h | 2 + 6 files changed, 143 insertions(+), 29 deletions(-) diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index 0cf8e8158..a7547286c 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -174,6 +174,7 @@ void out_cypher_unwind(StringInfo str, const ExtensibleNode *node) DEFINE_AG_NODE(cypher_unwind); WRITE_NODE_FIELD(target); + WRITE_NODE_FIELD(collect); } // serialization function for the cypher_delete ExtensibleNode. diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 6d6d8c8a3..eec67e2ac 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -389,9 +389,13 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate, else if (is_ag_node(self, cypher_unwind)) { cypher_unwind *n = (cypher_unwind *) self; + if (n->collect != NULL) + { + cpstate->p_list_comp = true; + } result = transform_cypher_clause_with_where(cpstate, - transform_cypher_unwind, - clause, n->where); + transform_cypher_unwind, + clause, n->where); } else if (is_ag_node(self, cypher_call)) { @@ -2344,14 +2348,35 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->rteperminfos = pstate->p_rteperminfos; - if (!is_ag_node(self, cypher_match)) + where_qual = transform_cypher_expr(cpstate, where, EXPR_KIND_WHERE); + + where_qual = coerce_to_boolean(pstate, where_qual, "WHERE"); + + // check if we have a list comprehension in the where clause + if (cpstate->p_list_comp) { - where_qual = transform_cypher_expr(cpstate, where, EXPR_KIND_WHERE); + List *groupClause = NIL; + ListCell *li; + query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + query->havingQual = where_qual; - where_qual = coerce_to_boolean(pstate, where_qual, "WHERE"); - } + foreach (li, ((cypher_return *)self)->items) + { + ResTarget *item = lfirst(li); - query->jointree = makeFromExpr(pstate->p_joinlist, where_qual); + groupClause = lappend(groupClause, item->val); + } + query->groupClause = transform_group_clause(cpstate, groupClause, + &query->groupingSets, + &query->targetList, + query->sortClause, + EXPR_KIND_GROUP_BY); + + } + else + { + query->jointree = makeFromExpr(pstate->p_joinlist, where_qual); + } } else { @@ -2388,9 +2413,7 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, (Node *)r, -1); } - return transform_cypher_clause_with_where( - cpstate, transform_cypher_match_pattern, clause, - match_self->where); + return transform_cypher_match_pattern(cpstate, clause); } /* @@ -3064,7 +3087,36 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, query->rtable = cpstate->pstate.p_rtable; query->rteperminfos = cpstate->pstate.p_rteperminfos; - query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); + + if (cpstate->p_list_comp) + { + List *groupList = NIL; + + query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, NULL); + query->havingQual = (Node *)expr; + + foreach (lc, query->targetList) + { + TargetEntry *te = lfirst(lc); + ColumnRef *cref = makeNode(ColumnRef); + + cref->fields = list_make1(makeString(te->resname)); + cref->location = exprLocation((Node *)te->expr); + + groupList = lappend(groupList, cref); + } + + query->groupClause = transform_group_clause(cpstate, groupList, + &query->groupingSets, + &query->targetList, + query->sortClause, + EXPR_KIND_GROUP_BY); + } + else + { + query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, + (Node *)expr); + } } /* @@ -5314,6 +5366,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->rteperminfos = pstate->p_rteperminfos; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + query->hasAggs = pstate->p_hasAggs; return query; } @@ -5971,7 +6024,8 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, pstate->p_expr_kind == EXPR_KIND_OTHER || pstate->p_expr_kind == EXPR_KIND_WHERE || pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET || - pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT); + pstate->p_expr_kind == EXPR_KIND_FROM_SUBSELECT || + pstate->p_expr_kind == EXPR_KIND_INSERT_TARGET); /* * As these are all sub queries, if this is just of type NONE, note it as a diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 068a0513b..c5a30aba3 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -1753,7 +1753,6 @@ static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, * because it should not be visible outside */ pstate->p_namespace = list_delete_ptr(pstate->p_namespace, pnsi); - cpstate->p_list_comp = false; return expr; } \ No newline at end of file diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index 50cd1bd27..1afc57e63 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -27,6 +27,7 @@ #include "nodes/makefuncs.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" +#include "miscadmin.h" #include "parser/cypher_expr.h" #include "parser/cypher_item.h" @@ -65,37 +66,93 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, * Helper function to determine if the passed node has a list_comprehension * node embedded in it. */ -static bool has_a_cypher_list_comprehension_node(Node *expr) +bool has_a_cypher_list_comprehension_node(Node *expr) { - /* return false on NULL input */ + // return false on NULL input if (expr == NULL) { return false; } - /* if this is an A_Indirection, because they can operate on lists */ - if (nodeTag(expr) == T_A_Indirection) - { - /* set expr to the object of the indirection */ - expr = ((A_Indirection *)expr)->arg; - } + // since this function recurses, it could be driven to stack overflow + check_stack_depth(); - /* if expr is a cypher_unwind */ - if (nodeTag(expr) == T_ExtensibleNode && - is_ag_node(expr, cypher_unwind)) + switch (nodeTag(expr)) { - cypher_unwind *cu = NULL; + case T_A_Expr: + { + /* + * We need to recurse into the left and right nodes + * to check if there is an unwind node in there + */ + A_Expr *a_expr = (A_Expr *)expr; - cu = (cypher_unwind *)expr; + // check the left node + if (has_a_cypher_list_comprehension_node(a_expr->lexpr)) + { + return true; + } - /* if it is a list comprehension node, return true */ - if (cu->collect != NULL) + // check the right node + if (has_a_cypher_list_comprehension_node(a_expr->rexpr)) { return true; } + break; } + case T_A_Indirection: + { + // set expr to the object of the indirection + expr = ((A_Indirection *)expr)->arg; + + // check the object of the indirection + return has_a_cypher_list_comprehension_node(expr); + } + case T_ExtensibleNode: + { + if (is_ag_node(expr, cypher_unwind)) + { + cypher_unwind *cu = (cypher_unwind *)expr; - /* otherwise, return false */ + // if it has a collect node, return true + if (cu->collect != NULL) + { + return true; + } + } + if (is_ag_node(expr, cypher_map)) + { + cypher_map *map; + int i; + + map = (cypher_map *)expr; + + if (map->keyvals == NULL || map->keyvals->length == 0) + { + return false; + } + + // check each key and value for a list comprehension + for (i = 0; i < map->keyvals->length; i += 2) + { + Node *val; + + // get the value + val = (Node *)map->keyvals->elements[i + 1].ptr_value; + + // check the value + if (has_a_cypher_list_comprehension_node(val)) + { + return true; + } + } + } + break; + } + default: + break; + } + // otherwise, return false return false; } diff --git a/src/include/nodes/ag_nodes.h b/src/include/nodes/ag_nodes.h index f74cd135d..598871899 100644 --- a/src/include/nodes/ag_nodes.h +++ b/src/include/nodes/ag_nodes.h @@ -103,5 +103,6 @@ static inline bool _is_ag_node(Node *node, const char *extnodename) } #define is_ag_node(node, type) _is_ag_node((Node *)(node), CppAsString(type)) +#define get_ag_node_tag(node) ((ag_node_tag)(((ExtensibleNode *)(node))->extnodename)) #endif diff --git a/src/include/parser/cypher_item.h b/src/include/parser/cypher_item.h index 92b6c95f4..ba4e7e9af 100644 --- a/src/include/parser/cypher_item.h +++ b/src/include/parser/cypher_item.h @@ -26,4 +26,6 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, List **groupClause, ParseExprKind expr_kind); +bool has_a_cypher_list_comprehension_node(Node *expr); + #endif From 58874846fbe8a35ebbcfd17379a33705e15b86b8 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Wed, 7 Feb 2024 17:57:17 +0500 Subject: [PATCH 4/9] Resolve variable reuse issue in list comprehension - Also, for scoping list comprehension variable, instead of removing the pnsi of list comp from namespace, now we just mark the cols of pnsi as not visible. --- src/backend/parser/cypher_clause.c | 4 +++- src/backend/parser/cypher_expr.c | 11 +++++------ src/backend/parser/cypher_item.c | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index eec67e2ac..40934074a 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -2328,6 +2328,7 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, Assert(pnsi != NULL); rtindex = list_length(pstate->p_rtable); + // rte is the only RangeTblEntry in pstate if (rtindex != 1) { @@ -2353,7 +2354,8 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, where_qual = coerce_to_boolean(pstate, where_qual, "WHERE"); // check if we have a list comprehension in the where clause - if (cpstate->p_list_comp) + if (cpstate->p_list_comp && + has_a_cypher_list_comprehension_node(where)) { List *groupClause = NIL; ListCell *li; diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index c5a30aba3..e24b9ae11 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -337,7 +337,8 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref) Assert(IsA(field1, String)); colname = strVal(field1); - if (cpstate->p_list_comp) + if (cpstate->p_list_comp && + (pstate->p_expr_kind == EXPR_KIND_WHERE)) { /* * Just scan through the last pnsi(that is for list comp) @@ -1737,6 +1738,7 @@ static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, ParseState *pstate = (ParseState *)cpstate; cpstate->p_list_comp = true; + pstate->p_lateral_active = true; cc.prev = NULL; cc.next = NULL; @@ -1748,11 +1750,8 @@ static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, expr = transform_cypher_expr(cpstate, unwind->collect, EXPR_KIND_SELECT_TARGET); - /* - * Remove the namespace item for listcomp subquery - * because it should not be visible outside - */ - pstate->p_namespace = list_delete_ptr(pstate->p_namespace, pnsi); + pnsi->p_cols_visible = false; + pstate->p_lateral_active = false; return expr; } \ No newline at end of file diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index 1afc57e63..f4af4b576 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -257,7 +257,7 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, * clause. If that is the case don't. Because there isn't * anything to group with. */ - if (rte->eref->colnames != NULL) + if (rte->eref->colnames != NULL && nsitem->p_cols_visible) { ColumnRef *cref = NULL; char *colname = NULL; From fcc7cb1280500984f2073c7e4dd3630a0fd42b37 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Wed, 7 Feb 2024 19:31:39 +0500 Subject: [PATCH 5/9] Modify grammer to match opencypher list comprehension grammer - Also added initial regression tests for list comprehension --- Makefile | 1 + regress/expected/list_comprehension.out | 225 ++++++++++++++++++++++++ regress/sql/list_comprehension.sql | 59 +++++++ src/backend/parser/cypher_expr.c | 3 +- src/backend/parser/cypher_gram.y | 18 +- src/include/parser/cypher_kwlist.h | 1 - 6 files changed, 297 insertions(+), 10 deletions(-) create mode 100644 regress/expected/list_comprehension.out create mode 100644 regress/sql/list_comprehension.sql diff --git a/Makefile b/Makefile index b405ff61a..1a176af8e 100644 --- a/Makefile +++ b/Makefile @@ -109,6 +109,7 @@ REGRESS = scan \ graph_generation \ name_validation \ jsonb_operators \ + list_comprehension \ drop srcdir=`pwd` diff --git a/regress/expected/list_comprehension.out b/regress/expected/list_comprehension.out new file mode 100644 index 000000000..f8765aeef --- /dev/null +++ b/regress/expected/list_comprehension.out @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +LOAD 'age'; +SET search_path TO ag_catalog; +SELECT create_graph('list_comprehension'); +NOTICE: graph "list_comprehension" has been created + create_graph +-------------- + +(1 row) + +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) RETURN u $$) as (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 11, 13]}) RETURN u $$) as (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex +(1 row) + +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u $$) as (result agtype); + result +-------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)] $$) AS (result agtype); + result +--------------------------------------------------------- + [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][2] $$) AS (result agtype); + result +-------- + 5 +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][1..4] $$) AS (result agtype); + result +----------- + [3, 5, 7] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0] $$) AS (result agtype); + result +-------------------- + [3, 9, 15, 21, 27] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][2] $$) AS (result agtype); + result +-------- + 15 +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][0..4] $$) AS (result agtype); + result +---------------- + [3, 9, 15, 21] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ] $$) AS (result agtype); + result +---------------------------------- + [9.0, 81.0, 225.0, 441.0, 729.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][3] $$) AS (result agtype); + result +-------- + 441.0 +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype); + result +----------------------------- + [81.0, 225.0, 441.0, 729.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ] $$) AS (result agtype); + result +---------------------------------------------------------------------------------------------------- + [1.0, 9.0, 25.0, 49.0, 81.0, 121.0, 169.0, 225.0, 289.0, 361.0, 441.0, 529.0, 625.0, 729.0, 841.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ][0] $$) AS (result agtype); + result +-------- + 1.0 +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ][0..2] $$) AS (result agtype); + result +------------ + [1.0, 9.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN (u) $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex +(3 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 ] $$) AS (result agtype); + result +---------------- + [0, 6, 12, 18] + [0, 6, 12, 18] + [0, 6, 12, 18] +(3 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); + result +--------------------------- + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] +(3 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); + result +--------------------------- + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] +(3 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); + result +--------------------------- + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] +(3 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); + result +--------------------------- + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] + [0.0, 36.0, 144.0, 324.0] +(3 rows) + +SELECT * from cypher('list_comprehension', $$ CREATE (u) RETURN u $$) as (result agtype); + result +---------------------------------------------------------------- + {"id": 281474976710660, "label": "", "properties": {}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] $$) AS (result agtype); + result +------------------------- + [1, 3, 5, 7, 9, 11, 13] + [] + [] + [0, 2, 4, 6, 8, 10, 12] +(4 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0] $$) AS (result agtype); + result +------------ + [0, 6, 12] + [3, 9] +(2 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3] $$) AS (result agtype); + result +----------- + [0, 2, 4] + [1, 3] +(2 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][1] $$) AS (result agtype); + result +-------- + 2 + 3 +(2 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2] $$) AS (result agtype); + result +-------- + [0, 2] + [1, 3] +(2 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); + result +-------- + 2 + 3 +(2 rows) + +SELECT * FROM drop_graph('list_comprehension', true); +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table list_comprehension._ag_label_vertex +drop cascades to table list_comprehension._ag_label_edge +NOTICE: graph "list_comprehension" has been dropped + drop_graph +------------ + +(1 row) + diff --git a/regress/sql/list_comprehension.sql b/regress/sql/list_comprehension.sql new file mode 100644 index 000000000..7ec008057 --- /dev/null +++ b/regress/sql/list_comprehension.sql @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +LOAD 'age'; +SET search_path TO ag_catalog; + +SELECT create_graph('list_comprehension'); + +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) RETURN u $$) as (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 11, 13]}) RETURN u $$) as (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u $$) as (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][1..4] $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][0..4] $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][3] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ][0] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ][0..2] $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN (u) $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u) RETURN u $$) as (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); + +SELECT * FROM drop_graph('list_comprehension', true); \ No newline at end of file diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index e24b9ae11..c5c358c8d 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -338,7 +338,8 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref) colname = strVal(field1); if (cpstate->p_list_comp && - (pstate->p_expr_kind == EXPR_KIND_WHERE)) + (pstate->p_expr_kind == EXPR_KIND_WHERE || + pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)) { /* * Just scan through the last pnsi(that is for list comp) diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index 4cceb2363..f905e95b7 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -1668,7 +1668,6 @@ expr: $$ = make_typecast_expr($1, $3, @2); } | expr_atom - | list_comprehension ; expr_opt: @@ -1879,13 +1878,9 @@ list: $$ = (Node *)n; } - ; - -list_comprehension: - '[' FOR var_name IN expr where_opt mapping_expr_opt ']' + | '[' list_comprehension ']' { - $$ = build_list_comprehension_node($3, $5, $6, $7, - @3, @5, @6, @7); + $$ = $2; } ; @@ -1961,6 +1956,14 @@ expr_case_default: } ; +list_comprehension: + var_name IN expr where_opt mapping_expr_opt + { + $$ = build_list_comprehension_node($1, $3, $4, $5, + @1, @3, @4, @5); + } +; + expr_var: var_name { @@ -2068,7 +2071,6 @@ safe_keywords: | ENDS { $$ = pnstrdup($1, 4); } | EXISTS { $$ = pnstrdup($1, 6); } | EXPLAIN { $$ = pnstrdup($1, 7); } - | FOR { $$ = pnstrdup($1, 3); } | IN { $$ = pnstrdup($1, 2); } | IS { $$ = pnstrdup($1, 2); } | LIMIT { $$ = pnstrdup($1, 6); } diff --git a/src/include/parser/cypher_kwlist.h b/src/include/parser/cypher_kwlist.h index 6381822a5..d15a0e34a 100644 --- a/src/include/parser/cypher_kwlist.h +++ b/src/include/parser/cypher_kwlist.h @@ -21,7 +21,6 @@ PG_KEYWORD("ends", ENDS, RESERVED_KEYWORD) PG_KEYWORD("exists", EXISTS, RESERVED_KEYWORD) PG_KEYWORD("explain", EXPLAIN, RESERVED_KEYWORD) PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD) -PG_KEYWORD("for", FOR, RESERVED_KEYWORD) PG_KEYWORD("in", IN, RESERVED_KEYWORD) PG_KEYWORD("is", IS, RESERVED_KEYWORD) PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD) From 912a73cffc5b9f6a914c17f94253498b51622d1b Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Fri, 9 Feb 2024 22:35:35 +0500 Subject: [PATCH 6/9] Add regression tests for bugs resolved - Includes tests for nested cases, ambigous variable issue, list comprehension variable scoping and planner node aggref issue. - Should be added more. --- regress/expected/list_comprehension.out | 143 ++++++++++++++++++++++-- regress/sql/list_comprehension.sql | 37 +++++- 2 files changed, 171 insertions(+), 9 deletions(-) diff --git a/regress/expected/list_comprehension.out b/regress/expected/list_comprehension.out index f8765aeef..9ba31e0de 100644 --- a/regress/expected/list_comprehension.out +++ b/regress/expected/list_comprehension.out @@ -163,20 +163,13 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, [0.0, 36.0, 144.0, 324.0] (3 rows) -SELECT * from cypher('list_comprehension', $$ CREATE (u) RETURN u $$) as (result agtype); - result ----------------------------------------------------------------- - {"id": 281474976710660, "label": "", "properties": {}}::vertex -(1 row) - SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] $$) AS (result agtype); result ------------------------- [1, 3, 5, 7, 9, 11, 13] [] - [] [0, 2, 4, 6, 8, 10, 12] -(4 rows) +(3 rows) SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0] $$) AS (result agtype); result @@ -213,6 +206,140 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list wher 3 (2 rows) +-- Nested cases +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]]] $$) AS (result agtype); + result +----------- + [1, 2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i in [1,2,3]]]] $$) AS (result agtype); + result +----------- + [1, 2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1]] $$) AS (result agtype); + result +-------- + [2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1] $$) AS (result agtype); + result +-------- + [2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2] $$) AS (result agtype); + result +-------- + [3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2]] $$) AS (result agtype); + result +------------ + [4.0, 9.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1 | i^2] $$) AS (result agtype); + result +------------ + [4.0, 9.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2 | i^2] $$) AS (result agtype); + result +-------- + [9.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4] $$) AS (result agtype); + result +-------- + [9.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4 | i^2] $$) AS (result agtype); + result +-------- + [81.0] +(1 row) + +-- .... will add more tests +-- List comprehension inside where and property constraints +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(0,12,2)] RETURN u $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(1,13,2)] RETURN u $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i in range(0,6,2)] RETURN u $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE v.list=[i in u.list] RETURN v $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex +(3 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(0,12,2)]}) RETURN u $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(1,13,2)]}) RETURN u $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i in u.list]}) RETURN v $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex +(5 rows) + +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(12,24,2)]}) RETURN u $$) AS (result agtype); + result +---------------------------------------------------------------------------------------------------- + {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::vertex +(1 row) + +-- .... will add more tests +-- List comprehension variable scoping +SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n RETURN [m IN [1, 2, 3]] $$) AS (result agtype); + result +----------- + [1, 2, 3] +(1 row) + +-- Should error out +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (result agtype, i agtype); +ERROR: could not find rte for i +LINE 1: ..._comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (... + ^ +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) where i>5 | i^2],i $$) AS (result agtype, i agtype); +ERROR: could not find rte for i +LINE 1: ... $$ RETURN [i IN range(0, 10, 2) where i>5 | i^2],i $$) AS (... + ^ SELECT * FROM drop_graph('list_comprehension', true); NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table list_comprehension._ag_label_vertex diff --git a/regress/sql/list_comprehension.sql b/regress/sql/list_comprehension.sql index 7ec008057..1921ad890 100644 --- a/regress/sql/list_comprehension.sql +++ b/regress/sql/list_comprehension.sql @@ -48,7 +48,6 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); -SELECT * from cypher('list_comprehension', $$ CREATE (u) RETURN u $$) as (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3] $$) AS (result agtype); @@ -56,4 +55,40 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list wher SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); +-- Nested cases +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i in [1,2,3]]]] $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2] $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1 | i^2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2 | i^2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4 | i^2] $$) AS (result agtype); +-- .... will add more tests + +-- List comprehension inside where and property constraints +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(0,12,2)] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(1,13,2)] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i in range(0,6,2)] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE v.list=[i in u.list] RETURN v $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(0,12,2)]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(1,13,2)]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i in u.list]}) RETURN v $$) AS (result agtype); + +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(12,24,2)]}) RETURN u $$) AS (result agtype); + +-- .... will add more tests + +-- List comprehension variable scoping +SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n RETURN [m IN [1, 2, 3]] $$) AS (result agtype); + +-- Should error out +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (result agtype, i agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) where i>5 | i^2],i $$) AS (result agtype, i agtype); + SELECT * FROM drop_graph('list_comprehension', true); \ No newline at end of file From b816ef16f5583bd06bf5dee5a0e647024bb6d377 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Sat, 10 Feb 2024 15:35:12 +0500 Subject: [PATCH 7/9] Resolve invalid output on multiple list comprehensions - Fix the output of multiple list comprehensions in the RETURN clause and the WHERE clause. - Added regression tests for the above and some for the previous bugs resolved. --- regress/expected/list_comprehension.out | 183 +++++++++++++++++++----- regress/sql/list_comprehension.sql | 92 +++++++----- src/backend/parser/cypher_clause.c | 1 + src/backend/parser/cypher_item.c | 54 +++++-- 4 files changed, 251 insertions(+), 79 deletions(-) diff --git a/regress/expected/list_comprehension.out b/regress/expected/list_comprehension.out index 9ba31e0de..c2c49b49b 100644 --- a/regress/expected/list_comprehension.out +++ b/regress/expected/list_comprehension.out @@ -25,19 +25,19 @@ NOTICE: graph "list_comprehension" has been created (1 row) -SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) RETURN u $$) as (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) RETURN u $$) AS (result agtype); result ----------------------------------------------------------------------------------------------- {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex (1 row) -SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 11, 13]}) RETURN u $$) as (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 11, 13]}) RETURN u $$) AS (result agtype); result ----------------------------------------------------------------------------------------------- {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex (1 row) -SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u $$) as (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u $$) AS (result agtype); result -------------------------------------------------------------------------- {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex @@ -61,37 +61,37 @@ SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][1..4 [3, 5, 7] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0] $$) AS (result agtype); result -------------------- [3, 9, 15, 21, 27] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0][2] $$) AS (result agtype); result -------- 15 (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][0..4] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0][0..4] $$) AS (result agtype); result ---------------- [3, 9, 15, 21] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0 | u^2 ] $$) AS (result agtype); result ---------------------------------- [9.0, 81.0, 225.0, 441.0, 729.0] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][3] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0 | u^2 ][3] $$) AS (result agtype); result -------- 441.0 (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype); result ----------------------------- [81.0, 225.0, 441.0, 729.0] @@ -123,7 +123,7 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN (u) $$) AS (resul {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex (3 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 ] $$) AS (result agtype); result ---------------- [0, 6, 12, 18] @@ -131,7 +131,7 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, [0, 6, 12, 18] (3 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); result --------------------------- [0.0, 36.0, 144.0, 324.0] @@ -139,7 +139,7 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, [0.0, 36.0, 144.0, 324.0] (3 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); result --------------------------- [0.0, 36.0, 144.0, 324.0] @@ -147,7 +147,7 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20 [0.0, 36.0, 144.0, 324.0] (3 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); result --------------------------- [0.0, 36.0, 144.0, 324.0] @@ -155,7 +155,7 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 2 [0.0, 36.0, 144.0, 324.0] (3 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); result --------------------------- [0.0, 36.0, 144.0, 324.0] @@ -171,35 +171,35 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] $$) [0, 2, 4, 6, 8, 10, 12] (3 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0] $$) AS (result agtype); result ------------ [0, 6, 12] [3, 9] (2 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3] $$) AS (result agtype); result ----------- [0, 2, 4] [1, 3] (2 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][1] $$) AS (result agtype); result -------- 2 3 (2 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][0..2] $$) AS (result agtype); result -------- [0, 2] [1, 3] (2 rows) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); result -------- 2 @@ -213,61 +213,60 @@ SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]]] $$) A [1, 2, 3] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i in [1,2,3]]]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i IN [1,2,3]]]] $$) AS (result agtype); result ----------- [1, 2, 3] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1]] $$) AS (result agtype); result -------- [2, 3] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] WHERE i>1] $$) AS (result agtype); result -------- [2, 3] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1] WHERE i>2] $$) AS (result agtype); result -------- [3] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1 | i^2]] $$) AS (result agtype); result ------------ [4.0, 9.0] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1 | i^2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] WHERE i>1 | i^2] $$) AS (result agtype); result ------------ [4.0, 9.0] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2 | i^2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1] WHERE i>2 | i^2] $$) AS (result agtype); result -------- [9.0] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1 | i^2] WHERE i>4] $$) AS (result agtype); result -------- [9.0] (1 row) -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4 | i^2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1 | i^2] WHERE i>4 | i^2] $$) AS (result agtype); result -------- [81.0] (1 row) --- .... will add more tests -- List comprehension inside where and property constraints SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(0,12,2)] RETURN u $$) AS (result agtype); result @@ -281,13 +280,23 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex (1 row) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i in range(0,6,2)] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(0,12,2) WHERE i>4] RETURN u $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(1,13,2) WHERE i>4 | i^1] RETURN u $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i IN range(0,6,2)] RETURN u $$) AS (result agtype); result ----------------------------------------------------------------------------------------------- {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex (1 row) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE v.list=[i in u.list] RETURN v $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE v.list=[i IN u.list] RETURN v $$) AS (result agtype); result ----------------------------------------------------------------------------------------------- {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex @@ -307,7 +316,18 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(1,13,2) {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex (1 row) -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i in u.list]}) RETURN v $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype); + result +----------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(1,13,2) WHERE i>4 | i^1]}) RETURN u $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i IN u.list]}) RETURN v $$) AS (result agtype); result ----------------------------------------------------------------------------------------------- {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex @@ -323,7 +343,27 @@ SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(12,24, {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::vertex (1 row) --- .... will add more tests +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype); + result +-------------------------------------------------------------------------------------- + {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(1,13,2) WHERE i>4 | i^2]}) RETURN u $$) AS (result agtype); + result +-------------------------------------------------------------------------------------------------------- + {"id": 281474976710662, "label": "", "properties": {"list": [25.0, 49.0, 81.0, 121.0, 169.0]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS a CREATE (u {list:a}) RETURN u $$) AS (result agtype); + result +--------------------------------------------------------------------------------- + {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex +(1 row) + +-- Known issue +SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); +ERROR: Aggref found in non-Agg plan node -- List comprehension variable scoping SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n RETURN [m IN [1, 2, 3]] $$) AS (result agtype); result @@ -331,14 +371,87 @@ SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n [1, 2, 3] (1 row) +SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); + result | result2 +-----------+--------- + [1, 2, 3] | 1 +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ WITH [m IN [1,2,3]] AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); + result | result2 +-----------+----------- + [1, 2, 3] | [1, 2, 3] +(1 row) + +-- Multiple list comprehensions in RETURN and WITH clause +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN [1,2,3]] $$) AS (result agtype, result2 agtype); + result | result2 +-----------+----------- + [1, 2, 3] | [1, 2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1], [u IN [1,2,3] WHERE u>2] $$) AS (result agtype, result2 agtype); + result | result2 +--------+--------- + [2, 3] | [3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | u^3], [u IN [1,2,3] WHERE u>2 | u^3] $$) AS (result agtype, result2 agtype); + result | result2 +-------------+--------- + [8.0, 27.0] | [27.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]] AS u, [u IN [1,2,3]] AS i $$) AS (result agtype, result2 agtype); + result | result2 +-----------+----------- + [1, 2, 3] | [1, 2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1] AS u, [u IN [1,2,3] WHERE u>2] AS i $$) AS (result agtype, result2 agtype); + result | result2 +--------+--------- + [2, 3] | [3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | u^3] AS u, [u IN [1,2,3] WHERE u>2 | u^3] AS i $$) AS (result agtype, result2 agtype); + result | result2 +-------------+--------- + [8.0, 27.0] | [27.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3]]], [u IN [u IN [1,2,3]]] $$) AS (result agtype, result2 agtype); + result | result2 +-----------+----------- + [1, 2, 3] | [1, 2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2] $$) AS (result agtype, result2 agtype); + result | result2 +--------+--------- + [3] | [3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2 | u^3], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2 | u^3] $$) AS (result agtype, result2 agtype); + result | result2 +--------+--------- + [27.0] | [27.0] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS u, [u IN [1,2,3]] AS i RETURN u, i $$) AS (result agtype, result2 agtype); + result | result2 +-----------+----------- + [1, 2, 3] | [1, 2, 3] +(1 row) + -- Should error out SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (result agtype, i agtype); ERROR: could not find rte for i LINE 1: ..._comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (... ^ -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) where i>5 | i^2],i $$) AS (result agtype, i agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (result agtype, i agtype); ERROR: could not find rte for i -LINE 1: ... $$ RETURN [i IN range(0, 10, 2) where i>5 | i^2],i $$) AS (... +LINE 1: ...$$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (... ^ SELECT * FROM drop_graph('list_comprehension', true); NOTICE: drop cascades to 2 other objects diff --git a/regress/sql/list_comprehension.sql b/regress/sql/list_comprehension.sql index 1921ad890..669c43f0c 100644 --- a/regress/sql/list_comprehension.sql +++ b/regress/sql/list_comprehension.sql @@ -22,73 +22,97 @@ SET search_path TO ag_catalog; SELECT create_graph('list_comprehension'); -SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) RETURN u $$) as (result agtype); -SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 11, 13]}) RETURN u $$) as (result agtype); -SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u $$) as (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [0, 2, 4, 6, 8, 10, 12]}) RETURN u $$) AS (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: [1, 3, 5, 7, 9, 11, 13]}) RETURN u $$) AS (result agtype); +SELECT * from cypher('list_comprehension', $$ CREATE (u {list: []}) RETURN u $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][2] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2)][1..4] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][2] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0][0..4] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0][2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0][0..4] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][3] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) where u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0 | u^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0 | u^2 ][3] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) WHERE u % 3 = 0 | u^2 ][1..5] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ][0] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN range(1, 30, 2) | u^2 ][0..2] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN (u) $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 ] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, 20, 2) where i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH () RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH p=() RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH p=(u) RETURN [i IN range(0, 20, 2) WHERE i % 3 = 0 | i^2 ] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][1] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list where i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][0..2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) RETURN [i IN u.list WHERE i % 3 = 0 | i/3][0..2][1] $$) AS (result agtype); -- Nested cases SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]]] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i in [1,2,3]]]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [i IN [1,2,3]]]] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1]] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] WHERE i>1] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1] WHERE i>2] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2]] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] where i>1 | i^2] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1] where i>2 | i^2] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4] $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] where i>1 | i^2] where i>4 | i^2] $$) AS (result agtype); --- .... will add more tests +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1 | i^2]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3]] WHERE i>1 | i^2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1] WHERE i>2 | i^2] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1 | i^2] WHERE i>4] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN [i IN [1,2,3] WHERE i>1 | i^2] WHERE i>4 | i^2] $$) AS (result agtype); -- List comprehension inside where and property constraints SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(0,12,2)] RETURN u $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(1,13,2)] RETURN u $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i in range(0,6,2)] RETURN u $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE v.list=[i in u.list] RETURN v $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(0,12,2) WHERE i>4] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list=[i IN range(1,13,2) WHERE i>4 | i^1] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) WHERE u.list@>[i IN range(0,6,2)] RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v) WHERE v.list=[i IN u.list] RETURN v $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(0,12,2)]}) RETURN u $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(1,13,2)]}) RETURN u $$) AS (result agtype); -SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i in u.list]}) RETURN v $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list:[i IN range(1,13,2) WHERE i>4 | i^1]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u) MATCH (v {list:[i IN u.list]}) RETURN v $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(12,24,2)]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(0,12,2) WHERE i>4]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(1,13,2) WHERE i>4 | i^2]}) RETURN u $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS a CREATE (u {list:a}) RETURN u $$) AS (result agtype); --- .... will add more tests +-- Known issue +SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); -- List comprehension variable scoping SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n RETURN [m IN [1, 2, 3]] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ WITH [m IN [1,2,3]] AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); + +-- Multiple list comprehensions in RETURN and WITH clause +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN [1,2,3]] $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1], [u IN [1,2,3] WHERE u>2] $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | u^3], [u IN [1,2,3] WHERE u>2 | u^3] $$) AS (result agtype, result2 agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]] AS u, [u IN [1,2,3]] AS i $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1] AS u, [u IN [1,2,3] WHERE u>2] AS i $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1 | u^3] AS u, [u IN [1,2,3] WHERE u>2 | u^3] AS i $$) AS (result agtype, result2 agtype); + +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3]]], [u IN [u IN [1,2,3]]] $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2] $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2 | u^3], [u IN [u IN [1,2,3] WHERE u>1] WHERE u>2 | u^3] $$) AS (result agtype, result2 agtype); + +SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS u, [u IN [1,2,3]] AS i RETURN u, i $$) AS (result agtype, result2 agtype); -- Should error out SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (result agtype, i agtype); -SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) where i>5 | i^2],i $$) AS (result agtype, i agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (result agtype, i agtype); SELECT * FROM drop_graph('list_comprehension', true); \ No newline at end of file diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 40934074a..c4950a93c 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -6380,6 +6380,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, query->rtable = pstate->p_rtable; query->rteperminfos = pstate->p_rteperminfos; query->jointree = makeFromExpr(pstate->p_joinlist, NULL); + query->hasAggs = pstate->p_hasAggs; query->hasSubLinks = pstate->p_hasSubLinks; diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index f4af4b576..23a7f313a 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -31,6 +31,7 @@ #include "parser/cypher_expr.h" #include "parser/cypher_item.h" +#include "parser/cypher_clause.h" static List *ExpandAllTables(ParseState *pstate, int location); static List *expand_pnsi_attrs(ParseState *pstate, ParseNamespaceItem *pnsi, @@ -172,6 +173,7 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, { ResTarget *item = lfirst(li); TargetEntry *te; + bool has_list_comp = false; if (expand_star) { @@ -199,14 +201,48 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, } } } - /* clear the exprHasAgg flag to check transform for an aggregate */ + + // Check if we have a list comprehension + has_list_comp = has_a_cypher_list_comprehension_node(item->val); + + // Clear the exprHasAgg flag to check transform for an aggregate cpstate->exprHasAgg = false; - /* transform the item */ - te = transform_cypher_item(cpstate, item->val, NULL, expr_kind, - item->name, false); + if (has_list_comp && item_list->length > 1) + { + /* + * Create a subquery for the list comprehension and transform it + * as a subquery. Then expand the target list of the subquery. + * This is to avoid multiple unnest functions in the same query + * level and collect not able to distinguish correctly. + */ + ParseNamespaceItem *pnsi; + cypher_return *cr; + cypher_clause cc; + + cr = make_ag_node(cypher_return); + cr->items = list_make1(item); + + cc.prev = NULL; + cc.next = NULL; + cc.self = (Node *)cr; - target_list = lappend(target_list, te); + pnsi = transform_cypher_clause_as_subquery(cpstate, + transform_cypher_clause, + &cc, NULL, true); + + target_list = list_concat(target_list, + expandNSItemAttrs(&cpstate->pstate, pnsi, + 0, true, -1)); + } + else + { + /* transform the item */ + te = transform_cypher_item(cpstate, item->val, NULL, expr_kind, + item->name, false); + + target_list = lappend(target_list, te); + } /* * Did the transformed item contain an aggregate function? If it didn't, @@ -227,9 +263,7 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, * in a cypher_unwind node. We need to group the results but not expose * the grouping expression. */ - - /* verify that val has an embedded list_comprehension node in it */ - if (has_a_cypher_list_comprehension_node(item->val)) + if (has_list_comp) { ParseState *pstate = &cpstate->pstate; ParseNamespaceItem *nsitem = NULL; @@ -265,12 +299,12 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, /* get the name of the column (varname) */ colname = strVal(lfirst(list_head(rte->eref->colnames))); - /* create the ColumnRef */ + // create the ColumnRef cref = makeNode(ColumnRef); cref->fields = list_make1(makeString(colname)); cref->location = -1; - /* add the expression for grouping */ + // add the expression for grouping group_clause = lappend(group_clause, cref); } } From fa5725c79ef2dd42fdbf216e2e4f2415b2bc0d20 Mon Sep 17 00:00:00 2001 From: Zainab Saad <105385638+Zainab-Saad@users.noreply.github.com> Date: Tue, 13 Feb 2024 22:18:13 +0500 Subject: [PATCH 8/9] Fix aggref found where not expected issue (#189) - Resolved the 'aggref found where not expected' error thrown when using list comprehensions in UNWIND, or as a property update expression in SET or in the comparison expressions, typecasts or string matching in WITH - WHERE clause - Added an error to be thrown when using aggregation functions in UNWIND and property update expr in SET clause - Added regression tests --- regress/expected/list_comprehension.out | 122 +++++++++++++++++++++++- regress/sql/list_comprehension.sql | 30 ++++++ src/backend/parser/cypher_clause.c | 24 ++++- src/backend/parser/cypher_expr.c | 3 +- src/backend/parser/cypher_gram.y | 2 +- src/backend/parser/cypher_item.c | 71 +++++++++++--- 6 files changed, 234 insertions(+), 18 deletions(-) diff --git a/regress/expected/list_comprehension.out b/regress/expected/list_comprehension.out index c2c49b49b..8b7a64531 100644 --- a/regress/expected/list_comprehension.out +++ b/regress/expected/list_comprehension.out @@ -361,6 +361,118 @@ SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS a CREATE (u {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex (1 row) +-- List comprehension in the WITH WHERE clause +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] RETURN u $$) AS (u agtype); + u +----------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR size(u.list) = 0 RETURN u $$) AS (u agtype); + u +----------------------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(2 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR size(u.list)::bool RETURN u $$) AS (u agtype); + u +-------------------------------------------------------------------------------------------------------- + {"id": 281474976710658, "label": "", "properties": {"list": [1, 3, 5, 7, 9, 11, 13]}}::vertex + {"id": 281474976710661, "label": "", "properties": {"list": [6, 8, 10, 12]}}::vertex + {"id": 281474976710660, "label": "", "properties": {"list": [12, 14, 16, 18, 20, 22, 24]}}::vertex + {"id": 281474976710662, "label": "", "properties": {"list": [25.0, 49.0, 81.0, 121.0, 169.0]}}::vertex + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex + {"id": 281474976710663, "label": "", "properties": {"list": [1, 2, 3]}}::vertex +(6 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR NOT size(u.list)::bool RETURN u $$) AS (u agtype); + u +----------------------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"list": []}}::vertex + {"id": 281474976710657, "label": "", "properties": {"list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(2 rows) + +SELECT * FROM cypher('list_comprehension', $$ CREATE(u:csm_match {list: ['abc', 'def', 'ghi']}) $$) AS (u agtype); + u +--- +(0 rows) + +SELECT * FROM cypher('list_comprehension', $$ MATCH(u: csm_match) WITH u WHERE [u IN u.list][0] STARTS WITH "ab" RETURN u $$) AS (u agtype); + u +------------------------------------------------------------------------------------------------------ + {"id": 844424930131969, "label": "csm_match", "properties": {"list": ["abc", "def", "ghi"]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u WHERE u = [u IN [1, 2, 3]] RETURN u $$) AS (u agtype); + u +----------- + [1, 2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS u WHERE u = [u IN [1, 2, 3]][0] RETURN u $$) AS (u agtype); + u +--- + 1 +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ WITH ['abc', 'defgh'] AS u WHERE [v In u][1] STARTS WITH 'de' RETURN u $$) AS (u agtype); + u +------------------ + ["abc", "defgh"] +(1 row) + +-- List comprehension in UNWIND +SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3]] AS u RETURN u $$) AS (u agtype); + u +--- + 1 + 2 + 3 +(3 rows) + +SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3] WHERE u > 1 | u*2] AS u RETURN u $$) AS (u agtype); + u +--- + 4 + 6 +(2 rows) + +-- invalid use of aggregation in UNWIND +SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u UNWIND collect(u) AS v RETURN v $$) AS (u agtype); +ERROR: Invalid use of aggregation in this context +LINE 1: ...ist_comprehension', $$ WITH [1, 2, 3] AS u UNWIND collect(u)... + ^ +-- List comprehension in SET +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.a = [u IN range(0, 5)] RETURN u $$) AS (u agtype); + u +------------------------------------------------------------------------------------------------------------------------ + {"id": 281474976710657, "label": "", "properties": {"a": [0, 1, 2, 3, 4, 5], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.a = [u IN []] RETURN u $$) AS (u agtype); + u +-------------------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"a": [], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u += {b: [u IN range(0, 5)]} RETURN u $$) AS (u agtype); + u +--------------------------------------------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"a": [], "b": [0, 1, 2, 3, 4, 5], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) WITh u, collect(u.list) AS v SET u += {b: [u IN range(0, 5)]} SET u.c = [u IN v[0]] RETURN u $$) AS (u agtype); + u +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"id": 281474976710657, "label": "", "properties": {"a": [], "b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex +(1 row) + +-- invalid use of aggregation in SET +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = collect(u.list) RETURN u $$) AS (u agtype); +ERROR: Invalid use of aggregation in this context +LINE 1: ..., $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = coll... + ^ -- Known issue SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); ERROR: Aggref found in non-Agg plan node @@ -383,6 +495,12 @@ SELECT * FROM cypher('list_comprehension', $$ WITH [m IN [1,2,3]] AS m RETURN [m [1, 2, 3] | [1, 2, 3] (1 row) +SELECT * FROM cypher('list_comprehension', $$ CREATE n=()-[:edge]->() RETURN [n IN nodes(n)] $$) AS (u agtype); + u +---------------------------------------------------------------------------------------------------------------------------------- + [{"id": 281474976710664, "label": "", "properties": {}}::vertex, {"id": 281474976710665, "label": "", "properties": {}}::vertex] +(1 row) + -- Multiple list comprehensions in RETURN and WITH clause SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN [1,2,3]] $$) AS (result agtype, result2 agtype); result | result2 @@ -454,9 +572,11 @@ ERROR: could not find rte for i LINE 1: ...$$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (... ^ SELECT * FROM drop_graph('list_comprehension', true); -NOTICE: drop cascades to 2 other objects +NOTICE: drop cascades to 4 other objects DETAIL: drop cascades to table list_comprehension._ag_label_vertex drop cascades to table list_comprehension._ag_label_edge +drop cascades to table list_comprehension.csm_match +drop cascades to table list_comprehension.edge NOTICE: graph "list_comprehension" has been dropped drop_graph ------------ diff --git a/regress/sql/list_comprehension.sql b/regress/sql/list_comprehension.sql index 669c43f0c..b4596e55c 100644 --- a/regress/sql/list_comprehension.sql +++ b/regress/sql/list_comprehension.sql @@ -88,6 +88,35 @@ SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(0,12,2 SELECT * FROM cypher('list_comprehension', $$ CREATE (u {list:[i IN range(1,13,2) WHERE i>4 | i^2]}) RETURN u $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS a CREATE (u {list:a}) RETURN u $$) AS (result agtype); +-- List comprehension in the WITH WHERE clause +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR size(u.list) = 0 RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR size(u.list)::bool RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH(u) WITH u WHERE u.list = [u IN [0, 2, 4, 6, 8, 10, 12]] OR NOT size(u.list)::bool RETURN u $$) AS (u agtype); + +SELECT * FROM cypher('list_comprehension', $$ CREATE(u:csm_match {list: ['abc', 'def', 'ghi']}) $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH(u: csm_match) WITH u WHERE [u IN u.list][0] STARTS WITH "ab" RETURN u $$) AS (u agtype); + +SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u WHERE u = [u IN [1, 2, 3]] RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS u WHERE u = [u IN [1, 2, 3]][0] RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ WITH ['abc', 'defgh'] AS u WHERE [v In u][1] STARTS WITH 'de' RETURN u $$) AS (u agtype); + +-- List comprehension in UNWIND +SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3]] AS u RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ UNWIND [u IN [1, 2, 3] WHERE u > 1 | u*2] AS u RETURN u $$) AS (u agtype); + +-- invalid use of aggregation in UNWIND +SELECT * FROM cypher('list_comprehension', $$ WITH [1, 2, 3] AS u UNWIND collect(u) AS v RETURN v $$) AS (u agtype); + +-- List comprehension in SET +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.a = [u IN range(0, 5)] RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.a = [u IN []] RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u += {b: [u IN range(0, 5)]} RETURN u $$) AS (u agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) WITh u, collect(u.list) AS v SET u += {b: [u IN range(0, 5)]} SET u.c = [u IN v[0]] RETURN u $$) AS (u agtype); + +-- invalid use of aggregation in SET +SELECT * FROM cypher('list_comprehension', $$ MATCH(u {list: [0, 2, 4, 6, 8, 10, 12]}) SET u.c = collect(u.list) RETURN u $$) AS (u agtype); + -- Known issue SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RETURN u $$) AS (result agtype); @@ -95,6 +124,7 @@ SELECT * FROM cypher('list_comprehension', $$ MERGE (u {list:[i IN [1,2,3]]}) RE SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m, [m IN [1, 2, 3]] AS n RETURN [m IN [1, 2, 3]] $$) AS (result agtype); SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); SELECT * FROM cypher('list_comprehension', $$ WITH [m IN [1,2,3]] AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); +SELECT * FROM cypher('list_comprehension', $$ CREATE n=()-[:edge]->() RETURN [n IN nodes(n)] $$) AS (u agtype); -- Multiple list comprehensions in RETURN and WITH clause SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN [1,2,3]] $$) AS (result agtype, result2 agtype); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index c4950a93c..5d95b229e 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1328,6 +1328,8 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, TargetEntry *te; ParseNamespaceItem *pnsi; bool is_list_comp = self->collect != NULL; + bool has_agg = + is_list_comp || has_a_cypher_list_comprehension_node(self->target->val); query = makeNode(Query); query->commandType = CMD_SELECT; @@ -1362,6 +1364,13 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, expr = transform_cypher_expr(cpstate, self->target->val, EXPR_KIND_SELECT_TARGET); + if (!has_agg && nodeTag(expr) == T_Aggref) + { + ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Invalid use of aggregation in this context"), + parser_errposition(pstate, self->target->location)); + } + unwind = makeFuncCall(list_make1(makeString("age_unnest")), NIL, COERCE_SQL_SYNTAX, -1); @@ -1373,7 +1382,7 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate, target_syntax_loc); pstate->p_expr_kind = old_expr_kind; - pstate->p_hasAggs = is_list_comp; + pstate->p_hasAggs = has_agg; te = makeTargetEntry((Expr *) funcexpr, (AttrNumber) pstate->p_next_resno++, @@ -1799,6 +1808,19 @@ cypher_update_information *transform_cypher_set_item_list( target_item = transform_cypher_item(cpstate, set_item->expr, NULL, EXPR_KIND_SELECT_TARGET, NULL, false); + + if (has_a_cypher_list_comprehension_node(set_item->expr)) + { + query->hasAggs = true; + } + + if (!query->hasAggs && nodeTag(target_item->expr) == T_Aggref) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Invalid use of aggregation in this context"), + parser_errposition(pstate, set_item->location))); + } + target_item->expr = add_volatile_wrapper(target_item->expr); query->targetList = lappend(query->targetList, target_item); diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index c5c358c8d..649e1e4de 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -339,7 +339,8 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref) if (cpstate->p_list_comp && (pstate->p_expr_kind == EXPR_KIND_WHERE || - pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)) + pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET) && + list_length(pstate->p_namespace) > 0) { /* * Just scan through the last pnsi(that is for list comp) diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index f905e95b7..0720a50d6 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -77,7 +77,7 @@ CALL CASE COALESCE CONTAINS CREATE DELETE DESC DESCENDING DETACH DISTINCT ELSE END_P ENDS EXISTS EXPLAIN - FALSE_P FOR + FALSE_P IN IS LIMIT MATCH MERGE diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index 23a7f313a..647c7d8a1 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -88,16 +88,23 @@ bool has_a_cypher_list_comprehension_node(Node *expr) */ A_Expr *a_expr = (A_Expr *)expr; - // check the left node - if (has_a_cypher_list_comprehension_node(a_expr->lexpr)) - { - return true; - } + return (has_a_cypher_list_comprehension_node(a_expr->lexpr) || + has_a_cypher_list_comprehension_node(a_expr->rexpr)); + } + case T_BoolExpr: + { + BoolExpr *bexpr = (BoolExpr *)expr; + ListCell *lc; - // check the right node - if (has_a_cypher_list_comprehension_node(a_expr->rexpr)) + // is any of the boolean expression argument a list comprehension? + foreach(lc, bexpr->args) { - return true; + Node *arg = lfirst(lc); + + if (has_a_cypher_list_comprehension_node(arg)) + { + return true; + } } break; } @@ -115,13 +122,10 @@ bool has_a_cypher_list_comprehension_node(Node *expr) { cypher_unwind *cu = (cypher_unwind *)expr; - // if it has a collect node, return true - if (cu->collect != NULL) - { - return true; - } + // it is a list comprehension if it has a collect node + return cu->collect != NULL; } - if (is_ag_node(expr, cypher_map)) + else if (is_ag_node(expr, cypher_map)) { cypher_map *map; int i; @@ -148,6 +152,45 @@ bool has_a_cypher_list_comprehension_node(Node *expr) } } } + else if (is_ag_node(expr, cypher_string_match)) + { + cypher_string_match *csm_match = (cypher_string_match *)expr; + + // is lhs or rhs of the string match a list comprehension? + return (has_a_cypher_list_comprehension_node(csm_match->lhs) || + has_a_cypher_list_comprehension_node(csm_match->rhs)); + } + else if (is_ag_node(expr, cypher_typecast)) + { + cypher_typecast *ctypecast = (cypher_typecast *)expr; + + // is expr being typecasted a list comprehension? + return has_a_cypher_list_comprehension_node(ctypecast->expr); + } + else if (is_ag_node(expr, cypher_comparison_aexpr)) + { + cypher_comparison_aexpr *aexpr = (cypher_comparison_aexpr *)expr; + + // is left or right argument a list comprehension? + return (has_a_cypher_list_comprehension_node(aexpr->lexpr) || + has_a_cypher_list_comprehension_node(aexpr->rexpr)); + } + else if (is_ag_node(expr, cypher_comparison_boolexpr)) + { + cypher_comparison_boolexpr *bexpr = (cypher_comparison_boolexpr *)expr; + ListCell *lc; + + // is any of the boolean expression argument a list comprehension? + foreach(lc, bexpr->args) + { + Node *arg = lfirst(lc); + + if (has_a_cypher_list_comprehension_node(arg)) + { + return true; + } + } + } break; } default: From 082df3bb96bee4ef4fab99c0c8c360ec364367e7 Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Wed, 14 Feb 2024 23:04:38 +0500 Subject: [PATCH 9/9] cleanup: Remove dead code and formatting changes --- src/backend/parser/cypher_expr.c | 28 ++++++++++++++++++++++++++-- src/backend/parser/cypher_item.c | 12 ++++++------ src/backend/utils/adt/agtype.c | 10 ---------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 649e1e4de..631446586 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -168,38 +168,61 @@ static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, case T_CoalesceExpr: return transform_CoalesceExpr(cpstate, (CoalesceExpr *) expr); case T_ExtensibleNode: + { if (is_ag_node(expr, cypher_bool_const)) + { return transform_cypher_bool_const(cpstate, (cypher_bool_const *)expr); + } if (is_ag_node(expr, cypher_integer_const)) + { return transform_cypher_integer_const(cpstate, (cypher_integer_const *)expr); + } if (is_ag_node(expr, cypher_param)) + { return transform_cypher_param(cpstate, (cypher_param *)expr); + } if (is_ag_node(expr, cypher_map)) + { return transform_cypher_map(cpstate, (cypher_map *)expr); + } if (is_ag_node(expr, cypher_list)) + { return transform_cypher_list(cpstate, (cypher_list *)expr); + } if (is_ag_node(expr, cypher_string_match)) + { return transform_cypher_string_match(cpstate, (cypher_string_match *)expr); + } if (is_ag_node(expr, cypher_typecast)) + { return transform_cypher_typecast(cpstate, (cypher_typecast *)expr); + } if (is_ag_node(expr, cypher_comparison_aexpr)) + { return transform_cypher_comparison_aexpr_OP(cpstate, (cypher_comparison_aexpr *)expr); + } if (is_ag_node(expr, cypher_comparison_boolexpr)) + { return transform_cypher_comparison_boolexpr(cpstate, (cypher_comparison_boolexpr *)expr); + } if (is_ag_node(expr, cypher_unwind)) + { return transform_cypher_list_comprehension(cpstate, (cypher_unwind *) expr); + } ereport(ERROR, (errmsg_internal("unrecognized ExtensibleNode: %s", ((ExtensibleNode *)expr)->extnodename))); + return NULL; + } case T_FuncCall: return transform_FuncCall(cpstate, (FuncCall *)expr); case T_SubLink: @@ -1746,8 +1769,9 @@ static Node *transform_cypher_list_comprehension(cypher_parsestate *cpstate, cc.next = NULL; cc.self = (Node *)unwind; - pnsi = transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, &cc, - NULL, true); + pnsi = transform_cypher_clause_as_subquery(cpstate, + transform_cypher_clause, + &cc, NULL, true); expr = transform_cypher_expr(cpstate, unwind->collect, EXPR_KIND_SELECT_TARGET); diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index 647c7d8a1..3045d6c05 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -35,7 +35,7 @@ static List *ExpandAllTables(ParseState *pstate, int location); static List *expand_pnsi_attrs(ParseState *pstate, ParseNamespaceItem *pnsi, - int sublevels_up, bool require_col_privs, + int sublevels_up, bool require_col_privs, int location); bool has_a_cypher_list_comprehension_node(Node *expr); @@ -47,13 +47,13 @@ TargetEntry *transform_cypher_item(cypher_parsestate *cpstate, Node *node, ParseState *pstate = (ParseState *)cpstate; bool old_p_lateral_active = pstate->p_lateral_active; - /* we want to see lateral variables */ + // we want to see lateral variables pstate->p_lateral_active = true; if (!expr) expr = transform_cypher_expr(cpstate, node, expr_kind); - /* set lateral back to what it was */ + // set lateral back to what it was pstate->p_lateral_active = old_p_lateral_active; if (!colname && !resjunk) @@ -280,7 +280,7 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, } else { - /* transform the item */ + // transform the item te = transform_cypher_item(cpstate, item->val, NULL, expr_kind, item->name, false); @@ -311,7 +311,7 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, ParseState *pstate = &cpstate->pstate; ParseNamespaceItem *nsitem = NULL; RangeTblEntry *rte = NULL; - // hasAgg = true; + /* * There should be at least 2 entries in p_namespace. One for the * variable in the reading clause and one for the variable in the @@ -339,7 +339,7 @@ List *transform_cypher_item_list(cypher_parsestate *cpstate, List *item_list, ColumnRef *cref = NULL; char *colname = NULL; - /* get the name of the column (varname) */ + // get the name of the column (varname) colname = strVal(lfirst(list_head(rte->eref->colnames))); // create the ColumnRef diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 63b1e150c..d0cde4c8f 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -10574,16 +10574,6 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) { agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); } - - /* if agtv_value is null, we have an empty list [], so add it */ - // // we need this for list_comprehension - // if (agtv_value == NULL) - // { - // castate->res = push_agtype_value(&castate->parse_state, - // WAGT_BEGIN_ARRAY, NULL); - // castate->res = push_agtype_value(&castate->parse_state, - // WAGT_END_ARRAY, NULL); - // } } /* skip the arg if agtype null */