| /* |
| * For PostgreSQL Database Management System: |
| * (formerly known as Postgres, then as Postgres95) |
| * |
| * Portions Copyright (c) 1996-2010, The PostgreSQL Global Development Group |
| * |
| * Portions Copyright (c) 1994, The Regents of the University of California |
| * |
| * Permission to use, copy, modify, and distribute this software and its documentation for any purpose, |
| * without fee, and without a written agreement is hereby granted, provided that the above copyright notice |
| * and this paragraph and the following two paragraphs appear in all copies. |
| * |
| * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, |
| * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY |
| * OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
| * |
| * THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA |
| * HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| */ |
| |
| #include "postgres.h" |
| |
| #include "access/heapam.h" |
| #include "miscadmin.h" |
| #include "nodes/nodeFuncs.h" |
| #include "optimizer/optimizer.h" |
| #include "parser/parse_clause.h" |
| #include "parser/parse_coerce.h" |
| #include "parser/parse_collate.h" |
| #include "parser/parse_expr.h" |
| #include "parser/parse_func.h" |
| #include "parser/parse_oper.h" |
| #include "parser/parse_target.h" |
| #include "parser/parsetree.h" |
| #include "parser/parse_relation.h" |
| #include "rewrite/rewriteHandler.h" |
| |
| #include "catalog/ag_graph.h" |
| #include "catalog/ag_label.h" |
| #include "commands/label_commands.h" |
| #include "parser/cypher_clause.h" |
| #include "parser/cypher_expr.h" |
| #include "parser/cypher_item.h" |
| #include "parser/cypher_parse_agg.h" |
| #include "parser/cypher_transform_entity.h" |
| #include "utils/ag_cache.h" |
| #include "utils/ag_func.h" |
| #include "utils/ag_guc.h" |
| |
| /* |
| * Variable string names for makeTargetEntry. As they are going to be variable |
| * names that will be hidden from the user, we need to do our best to make sure |
| * they won't be picked by mistake. Additionally, their form needs to be easily |
| * determined as ours. For now, prefix them as follows - |
| * |
| * #define AGE_VARNAME_SOMETHING AGE_DEFAULT_VARNAME_PREFIX"something" |
| * |
| * We should probably make an automated variable generator, like for aliases, |
| * for this. |
| * |
| * Also, keep these here as nothing outside of this file needs to know these. |
| */ |
| #define AGE_VARNAME_CREATE_CLAUSE AGE_DEFAULT_VARNAME_PREFIX"create_clause" |
| #define AGE_VARNAME_CREATE_NULL_VALUE AGE_DEFAULT_VARNAME_PREFIX"create_null_value" |
| #define AGE_VARNAME_DELETE_CLAUSE AGE_DEFAULT_VARNAME_PREFIX"delete_clause" |
| #define AGE_VARNAME_MERGE_CLAUSE AGE_DEFAULT_VARNAME_PREFIX"merge_clause" |
| #define AGE_VARNAME_ID AGE_DEFAULT_VARNAME_PREFIX"id" |
| #define AGE_VARNAME_SET_CLAUSE AGE_DEFAULT_VARNAME_PREFIX"set_clause" |
| |
| /* |
| * In the transformation stage, we need to track |
| * where a variable came from. When moving between |
| * clauses, Postgres parsestate and Query data structures |
| * are insufficient for some of the information we |
| * need. |
| */ |
| |
| /* |
| * Rules to determine if a node must be included: |
| * |
| * 1. the node is in a path variable |
| * 2. the node is a variable |
| * 3. the node contains filter properties |
| */ |
| #define INCLUDE_NODE_IN_JOIN_TREE(path, node) \ |
| (path->var_name || node->name || node->props) |
| |
| typedef Query *(*transform_method)(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| |
| // projection |
| static Query *transform_cypher_return(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static List *transform_cypher_order_by(cypher_parsestate *cpstate, |
| List *sort_items, List **target_list, |
| ParseExprKind expr_kind); |
| static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, |
| Node *node, List **target_list, |
| ParseExprKind expr_kind); |
| static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, |
| ParseExprKind expr_kind, |
| const char *construct_name); |
| static Query *transform_cypher_with(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, |
| transform_method transform, |
| cypher_clause *clause, |
| Node *where); |
| // match clause |
| static Query *transform_cypher_match(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, |
| cypher_path *path); |
| static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, |
| List *pattern, Node *where); |
| static List *transform_match_path(cypher_parsestate *cpstate, Query *query, |
| cypher_path *path); |
| static Expr *transform_cypher_edge(cypher_parsestate *cpstate, |
| cypher_relationship *rel, |
| List **target_list, bool valid_label); |
| static Expr *transform_cypher_node(cypher_parsestate *cpstate, |
| cypher_node *node, List **target_list, |
| bool output_node, bool valid_label); |
| static bool match_check_valid_label(cypher_match *match, |
| cypher_parsestate *cpstate); |
| static Node *make_vertex_expr(cypher_parsestate *cpstate, |
| ParseNamespaceItem *pnsi); |
| static Node *make_edge_expr(cypher_parsestate *cpstate, |
| ParseNamespaceItem *pnsi); |
| static Node *make_qual(cypher_parsestate *cpstate, |
| transform_entity *entity, char *name); |
| static TargetEntry * |
| transform_match_create_path_variable(cypher_parsestate *cpstate, |
| cypher_path *path, List *entities); |
| static List *make_path_join_quals(cypher_parsestate *cpstate, List *entities); |
| static List *make_directed_edge_join_conditions( |
| cypher_parsestate *cpstate, transform_entity *prev_entity, |
| transform_entity *next_entity, Node *prev_qual, Node *next_qual, |
| char *prev_node_label, char *next_node_label); |
| static List *join_to_entity(cypher_parsestate *cpstate, |
| transform_entity *entity, Node *qual, |
| enum transform_entity_join_side side); |
| static List *make_join_condition_for_edge(cypher_parsestate *cpstate, |
| transform_entity *prev_edge, |
| transform_entity *prev_node, |
| transform_entity *entity, |
| transform_entity *next_node, |
| transform_entity *next_edge); |
| static List *make_edge_quals(cypher_parsestate *cpstate, |
| transform_entity *edge, |
| enum transform_entity_join_side side); |
| static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, |
| Node *id_field, char *label); |
| static Node *transform_map_to_ind(cypher_parsestate *cpstate, |
| transform_entity *entity, cypher_map *map); |
| static List *transform_map_to_ind_recursive(cypher_parsestate *cpstate, |
| transform_entity *entity, |
| cypher_map *map, |
| List *parent_fields); |
| static Node *create_property_constraints(cypher_parsestate *cpstate, |
| transform_entity *entity, |
| Node *property_constraints, |
| Node *prop_expr); |
| static TargetEntry *findTarget(List *targetList, char *resname); |
| static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate, |
| cypher_relationship *rel, |
| Query *query); |
| // create clause |
| static Query *transform_cypher_create(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static List *transform_cypher_create_pattern(cypher_parsestate *cpstate, |
| Query *query, List *pattern); |
| static cypher_create_path * |
| transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, |
| cypher_path *cp); |
| static cypher_target_node * |
| transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, |
| cypher_node *node, bool has_edge); |
| static cypher_target_node * |
| transform_create_cypher_new_node(cypher_parsestate *cpstate, |
| List **target_list, cypher_node *node); |
| static cypher_target_node *transform_create_cypher_existing_node( |
| cypher_parsestate *cpstate, List **target_list, bool declared_in_current_clause, |
| cypher_node *node); |
| static cypher_target_node * |
| transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, |
| cypher_relationship *edge); |
| static Expr *cypher_create_properties(cypher_parsestate *cpstate, |
| cypher_target_node *rel, |
| Relation label_relation, Node *props, |
| enum transform_entity_type type); |
| static Expr *add_volatile_wrapper(Expr *node); |
| static bool variable_exists(cypher_parsestate *cpstate, char *name); |
| static void add_volatile_wrapper_to_target_entry(List *target_list, int resno); |
| static int get_target_entry_resno(List *target_list, char *name); |
| static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, |
| cypher_clause *clause, bool first_rte); |
| static TargetEntry *placeholder_target_entry(cypher_parsestate *cpstate, |
| char *name); |
| static Query *transform_cypher_sub_pattern(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| // set and remove clause |
| static Query *transform_cypher_set(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static cypher_update_information *transform_cypher_set_item_list(cypher_parsestate *cpstate, |
| List *set_item_list, |
| Query *query); |
| static cypher_update_information *transform_cypher_remove_item_list(cypher_parsestate *cpstate, |
| List *remove_item_list, |
| Query *query); |
| // delete |
| static Query *transform_cypher_delete(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate, |
| List *delete_item_list, |
| Query *query); |
| //set operators |
| static Query *transform_cypher_union(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| |
| static Node * transform_cypher_union_tree(cypher_parsestate *cpstate, |
| cypher_clause *clause, |
| bool isTopLevel, |
| List **targetlist); |
| |
| Query *cypher_parse_sub_analyze_union(cypher_clause *clause, |
| cypher_parsestate *cpstate, |
| CommonTableExpr *parentCTE, |
| bool locked_from_parent, |
| bool resolve_unknowns); |
| static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, |
| ParseNamespaceItem *r_pnsi, List **res_colnames, |
| List **res_colvars); |
| // unwind |
| static Query *transform_cypher_unwind(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| // merge |
| static Query *transform_cypher_merge(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static cypher_create_path * |
| transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, |
| cypher_clause *clause, |
| cypher_clause *isolated_merge_clause); |
| static cypher_create_path * |
| transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, |
| cypher_path *path); |
| static cypher_target_node * |
| transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, |
| cypher_relationship *edge); |
| static cypher_target_node * |
| transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, |
| cypher_node *node, bool has_edge); |
| static Node *transform_clause_for_join(cypher_parsestate *cpstate, |
| cypher_clause *clause, |
| RangeTblEntry **rte, |
| ParseNamespaceItem **nsitem, |
| Alias* alias); |
| static cypher_clause *convert_merge_to_match(cypher_merge *merge); |
| static void |
| transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, |
| List *target_list, |
| cypher_create_path *path); |
| static cypher_target_node *get_referenced_variable(ParseState *pstate, |
| Node *node, |
| List *transformed_path); |
| |
| //call...[yield] |
| static Query *transform_cypher_call_stmt(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| static Query *transform_cypher_call_subquery(cypher_parsestate *cpstate, |
| cypher_clause *clause); |
| |
| // transform |
| #define PREV_CYPHER_CLAUSE_ALIAS AGE_DEFAULT_ALIAS_PREFIX"previous_cypher_clause" |
| #define CYPHER_OPT_RIGHT_ALIAS AGE_DEFAULT_ALIAS_PREFIX"cypher_optional_right" |
| #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 |
| *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, |
| transform_method transform, |
| cypher_clause *clause, |
| Alias *alias, |
| bool add_rte_to_query); |
| static Query *analyze_cypher_clause(transform_method transform, |
| cypher_clause *clause, |
| cypher_parsestate *parent_cpstate); |
| static List *transform_group_clause(cypher_parsestate *cpstate, |
| List *grouplist, List **groupingSets, |
| List **targetlist, List *sortClause, |
| ParseExprKind exprKind); |
| static Node *flatten_grouping_sets(Node *expr, bool toplevel, |
| bool *hasGroupingSets); |
| static Index transform_group_clause_expr(List **flatresult, |
| Bitmapset *seen_local, |
| cypher_parsestate *cpstate, |
| Node *gexpr, List **targetlist, |
| List *sortClause, |
| ParseExprKind exprKind, |
| bool toplevel); |
| static List *add_target_to_group_list(cypher_parsestate *cpstate, |
| TargetEntry *tle, List *grouplist, |
| List *targetlist, int location); |
| static void advance_transform_entities_to_next_clause(List *entities); |
| |
| static ParseNamespaceItem *get_namespace_item(ParseState *pstate, |
| RangeTblEntry *rte); |
| static List *make_target_list_from_join(ParseState *pstate, |
| RangeTblEntry *rte); |
| static FuncExpr *make_clause_func_expr(char *function_name, |
| Node *clause_information); |
| static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex); |
| |
| /* for VLE support */ |
| static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, |
| RangeFunction *r); |
| static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, |
| RangeTblEntry **top_rte, int *top_rti, |
| List **namespace); |
| static ParseNamespaceItem *append_VLE_Func_to_FromClause(cypher_parsestate *cpstate, |
| Node *n); |
| static void setNamespaceLateralState(List *namespace, bool lateral_only, |
| bool lateral_ok); |
| static bool isa_special_VLE_case(cypher_path *path); |
| |
| static ParseNamespaceItem *find_pnsi(cypher_parsestate *cpstate, char *varname); |
| |
| /* |
| * transform a cypher_clause |
| */ |
| Query *transform_cypher_clause(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| Node *self = clause->self; |
| Query *result; |
| |
| // examine the type of clause and call the transform logic for it |
| if (is_ag_node(self, cypher_return)) |
| { |
| cypher_return *n = (cypher_return *) self; |
| |
| if (n->op == SETOP_NONE) |
| { |
| result = transform_cypher_return(cpstate, clause); |
| } |
| else if (n->op == SETOP_UNION) |
| { |
| result = transform_cypher_union(cpstate, clause); |
| } |
| else |
| { |
| ereport(ERROR, (errmsg_internal("unexpected Node for cypher_return"))); |
| } |
| } |
| else if (is_ag_node(self, cypher_with)) |
| { |
| result = transform_cypher_with(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_match)) |
| { |
| result = transform_cypher_match(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_create)) |
| { |
| result = transform_cypher_create(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_set)) |
| { |
| result = transform_cypher_set(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_delete)) |
| { |
| result = transform_cypher_delete(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_merge)) |
| { |
| result = transform_cypher_merge(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_sub_pattern)) |
| { |
| result = transform_cypher_sub_pattern(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_unwind)) |
| { |
| result = transform_cypher_unwind(cpstate, clause); |
| } |
| else if (is_ag_node(self, cypher_call)) |
| { |
| result = transform_cypher_call_stmt(cpstate, clause); |
| } |
| else |
| { |
| ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause"))); |
| } |
| |
| result->querySource = QSRC_ORIGINAL; |
| result->canSetTag = true; |
| |
| return result; |
| } |
| |
| /* |
| * Transform the UNION operator/clause. Creates a cypher_union |
| * node and the necessary information needed in the execution |
| * phase |
| */ |
| |
| static cypher_clause *make_cypher_clause(List *stmt) |
| { |
| cypher_clause *clause; |
| ListCell *lc; |
| |
| /* |
| * Since the first clause in stmt is the innermost subquery, the order of |
| * the clauses is inverted. |
| */ |
| clause = NULL; |
| foreach (lc, stmt) |
| { |
| cypher_clause *next; |
| |
| next = palloc(sizeof(*next)); |
| next->next = NULL; |
| next->self = lfirst(lc); |
| next->prev = clause; |
| |
| if (clause != NULL) |
| { |
| clause->next = next; |
| } |
| clause = next; |
| } |
| return clause; |
| } |
| |
| /* |
| * transform_cypher_union - |
| * transforms a union tree, derived from postgresql's |
| * transformSetOperationStmt. A lot of the general logic is similar, |
| * with adjustments made for AGE. |
| * |
| * A union tree is just a return, but with UNION structure to it. |
| * We must transform each leaf SELECT and build up a top-level Query |
| * that contains the leaf SELECTs as subqueries in its rangetable. |
| * The tree of unions is converted into the setOperations field of |
| * the top-level Query. |
| */ |
| |
| static Query *transform_cypher_union(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Query *qry = makeNode(Query); |
| int leftmostRTI; |
| Query *leftmostQuery; |
| SetOperationStmt *cypher_union_statement; |
| Node *skip = NULL; /* equivalent to postgres limitOffset */ |
| Node *limit = NULL; /* equivalent to postgres limitCount */ |
| List *order_by = NIL; |
| Node *node; |
| cypher_return *self = (cypher_return *)clause->self; |
| ListCell *left_tlist, *lct, *lcm, *lcc; |
| List *targetvars, *targetnames, *sv_namespace; |
| int sv_rtable_length; |
| int tllen; |
| ParseNamespaceItem *nsitem; |
| ParseNamespaceColumn *sortnscolumns; |
| int sortcolindex; |
| |
| qry->commandType = CMD_SELECT; |
| |
| /* |
| * Union is a node that should never have a previous node because |
| * of where it is used in the parse logic. The query parts around it |
| * are children located in larg or rarg. Something went wrong if the |
| * previous clause field is not null. |
| */ |
| if (clause->prev) |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Union is a parent node, there are no previous"), |
| parser_errposition(&cpstate->pstate, 0))); |
| } |
| |
| order_by = self->order_by; |
| skip = self->skip; |
| limit = self->limit; |
| |
| self->order_by = NIL; |
| self->skip = NULL; |
| self->limit = NULL; |
| |
| /* |
| * Recursively transform the components of the tree. |
| */ |
| cypher_union_statement = (SetOperationStmt *) transform_cypher_union_tree(cpstate, |
| clause, true, NULL); |
| |
| Assert(cypher_union_statement); |
| qry->setOperations = (Node *) cypher_union_statement; |
| |
| /* |
| * Re-find leftmost return (now it's a sub-query in rangetable) |
| */ |
| node = cypher_union_statement->larg; |
| while (node && IsA(node, SetOperationStmt)) |
| { |
| node = ((SetOperationStmt *) node)->larg; |
| } |
| Assert(node && IsA(node, RangeTblRef)); |
| leftmostRTI = ((RangeTblRef *) node)->rtindex; |
| leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery; |
| Assert(leftmostQuery != NULL); |
| |
| /* |
| * Generate dummy targetlist for outer query using column names of |
| * leftmost return and common datatypes/collations of topmost set |
| * operation. Also make lists of the dummy vars and their names for use |
| * in parsing ORDER BY. |
| * |
| * Note: we use leftmostRTI as the varno of the dummy variables. It |
| * shouldn't matter too much which RT index they have, as long as they |
| * have one that corresponds to a real RT entry; else funny things may |
| * happen when the tree is mashed by rule rewriting. |
| */ |
| qry->targetList = NIL; |
| targetvars = NIL; |
| targetnames = NIL; |
| sortnscolumns = (ParseNamespaceColumn *) |
| palloc0(list_length(cypher_union_statement->colTypes) * sizeof(ParseNamespaceColumn)); |
| sortcolindex = 0; |
| |
| forfour(lct, cypher_union_statement->colTypes, |
| lcm, cypher_union_statement->colTypmods, |
| lcc, cypher_union_statement->colCollations, |
| left_tlist, leftmostQuery->targetList) |
| { |
| Oid colType = lfirst_oid(lct); |
| int32 colTypmod = lfirst_int(lcm); |
| Oid colCollation = lfirst_oid(lcc); |
| TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist); |
| char *colName; |
| TargetEntry *tle; |
| Var *var; |
| |
| Assert(!lefttle->resjunk); |
| colName = pstrdup(lefttle->resname); |
| var = makeVar(leftmostRTI, |
| lefttle->resno, |
| colType, |
| colTypmod, |
| colCollation, |
| 0); |
| var->location = exprLocation((Node *) lefttle->expr); |
| tle = makeTargetEntry((Expr *) var, |
| (AttrNumber) pstate->p_next_resno++, |
| colName, |
| false); |
| qry->targetList = lappend(qry->targetList, tle); |
| targetvars = lappend(targetvars, var); |
| targetnames = lappend(targetnames, makeString(colName)); |
| sortnscolumns[sortcolindex].p_varno = leftmostRTI; |
| sortnscolumns[sortcolindex].p_varattno = lefttle->resno; |
| sortnscolumns[sortcolindex].p_vartype = colType; |
| sortnscolumns[sortcolindex].p_vartypmod = colTypmod; |
| sortnscolumns[sortcolindex].p_varcollid = colCollation; |
| sortnscolumns[sortcolindex].p_varnosyn = leftmostRTI; |
| sortnscolumns[sortcolindex].p_varattnosyn = lefttle->resno; |
| sortcolindex++; |
| } |
| |
| /* |
| * As a first step towards supporting sort clauses that are expressions |
| * using the output columns, generate a namespace entry that makes the |
| * output columns visible. A Join RTE node is handy for this, since we |
| * can easily control the Vars generated upon matches. |
| * |
| * Note: we don't yet do anything useful with such cases, but at least |
| * "ORDER BY upper(foo)" will draw the right error message rather than |
| * "foo not found". |
| */ |
| sv_rtable_length = list_length(pstate->p_rtable); |
| |
| nsitem = addRangeTableEntryForJoin(pstate, targetnames, sortnscolumns, |
| JOIN_INNER, 0, targetvars, NIL, NIL, |
| NULL, NULL, false); |
| |
| sv_namespace = pstate->p_namespace; |
| pstate->p_namespace = NIL; |
| |
| /* add jrte to column namespace only */ |
| addNSItemToQuery(pstate, nsitem, false, false, true); |
| |
| tllen = list_length(qry->targetList); |
| |
| qry->sortClause = transformSortClause(pstate, |
| order_by, |
| &qry->targetList, |
| EXPR_KIND_ORDER_BY, |
| false /* allow SQL92 rules */ ); |
| |
| /* restore namespace, remove jrte from rtable */ |
| pstate->p_namespace = sv_namespace; |
| pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length); |
| |
| if (tllen != list_length(qry->targetList)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("invalid UNION ORDER BY clause"), |
| errdetail("Only result column names can be used, not expressions or functions."), |
| parser_errposition(pstate, |
| exprLocation(list_nth(qry->targetList, tllen))))); |
| } |
| |
| qry->limitOffset = transform_cypher_limit(cpstate, skip, |
| EXPR_KIND_OFFSET, "OFFSET"); |
| qry->limitCount = transform_cypher_limit(cpstate, limit, |
| EXPR_KIND_LIMIT, "LIMIT"); |
| |
| qry->rtable = pstate->p_rtable; |
| qry->rteperminfos = pstate->p_rteperminfos; |
| qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| qry->hasAggs = pstate->p_hasAggs; |
| |
| assign_query_collations(pstate, qry); |
| |
| /* this must be done after collations, for reliable comparison of exprs */ |
| if (pstate->p_hasAggs || |
| qry->groupClause || qry->groupingSets || qry->havingQual) |
| { |
| parse_check_aggregates(pstate, qry); |
| } |
| |
| return qry; |
| |
| } |
| |
| /* |
| * transform_cypher_union_tree |
| * Recursively transform leaves and internal nodes of a set-op tree, |
| * derived from postgresql's transformSetOperationTree. A lot of |
| * the general logic is similar, with adjustments made for AGE. |
| * |
| * In addition to returning the transformed node, if targetlist isn't NULL |
| * then we return a list of its non-resjunk TargetEntry nodes. For a leaf |
| * set-op node these are the actual targetlist entries; otherwise they are |
| * dummy entries created to carry the type, typmod, collation, and location |
| * (for error messages) of each output column of the set-op node. This info |
| * is needed only during the internal recursion of this function, so outside |
| * callers pass NULL for targetlist. Note: the reason for passing the |
| * actual targetlist entries of a leaf node is so that upper levels can |
| * replace UNKNOWN Consts with properly-coerced constants. |
| */ |
| static Node * |
| transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause, |
| bool isTopLevel, List **targetlist) |
| { |
| bool isLeaf; |
| |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_return *cmp; |
| ParseNamespaceItem *pnsi; |
| |
| /* Guard against stack overflow due to overly complex set-expressions */ |
| check_stack_depth(); |
| |
| if (IsA(clause, List)) |
| { |
| clause = make_cypher_clause((List *)clause); |
| } |
| |
| if (is_ag_node(clause->self, cypher_return)) |
| { |
| cmp = (cypher_return *) clause->self; |
| } |
| else |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Cypher found an unsupported node"), |
| parser_errposition(pstate, 0))); |
| } |
| |
| |
| if (cmp->op == SETOP_NONE) |
| { |
| Assert(cmp->larg == NULL && cmp->rarg == NULL); |
| isLeaf = true; |
| } |
| else if (cmp->op == SETOP_UNION) |
| { |
| Assert(cmp->larg != NULL && cmp->rarg != NULL); |
| if (cmp->order_by || cmp->limit || cmp->skip) |
| { |
| isLeaf = true; |
| } |
| else |
| { |
| isLeaf = false; |
| } |
| } |
| else |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Cypher found an unsupported SETOP"), |
| parser_errposition(pstate, 0))); |
| } |
| |
| if (isLeaf) |
| { |
| /*process leaf return */ |
| Query *returnQuery; |
| char returnName[32]; |
| RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY; |
| RangeTblRef *rtr; |
| ListCell *tl; |
| |
| /* |
| * Transform SelectStmt into a Query. |
| * |
| * This works the same as RETURN transformation normally would, except |
| * that we prevent resolving unknown-type outputs as TEXT. This does |
| * not change the subquery's semantics since if the column type |
| * matters semantically, it would have been resolved to something else |
| * anyway. Doing this lets us resolve such outputs using |
| * select_common_type(), below. |
| * |
| * Note: previously transformed sub-queries don't affect the parsing |
| * of this sub-query, because they are not in the toplevel pstate's |
| * namespace list. |
| */ |
| |
| /* |
| * Convert the List * that the grammar gave us to a cypher_clause. |
| * cypher_analyze doesn't do this because the cypher_union clause |
| * is hiding it. |
| */ |
| |
| returnQuery = cypher_parse_sub_analyze_union((cypher_clause *) clause, cpstate, |
| NULL, false, false); |
| /* |
| * Check for bogus references to Vars on the current query level (but |
| * upper-level references are okay). Normally this can't happen |
| * because the namespace will be empty, but it could happen if we are |
| * inside a rule. |
| */ |
| if (pstate->p_namespace) |
| { |
| if (contain_vars_of_level((Node *) returnQuery, 1)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("UNION member statement cannot refer to other relations of same query level"), |
| parser_errposition(pstate, |
| locate_var_of_level((Node *) returnQuery, 1)))); |
| } |
| } |
| |
| /* |
| * Extract a list of the non-junk TLEs for upper-level processing. |
| */ |
| if (targetlist) |
| { |
| *targetlist = NIL; |
| foreach(tl, returnQuery->targetList) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(tl); |
| |
| if (!tle->resjunk) |
| { |
| *targetlist = lappend(*targetlist, tle); |
| } |
| } |
| } |
| |
| /* |
| * Make the leaf query be a subquery in the top-level rangetable. |
| */ |
| snprintf(returnName, sizeof(returnName), "*SELECT* %d ", |
| list_length(pstate->p_rtable) + 1); |
| pnsi = addRangeTableEntryForSubquery(pstate, |
| returnQuery, |
| makeAlias(returnName, NIL), |
| false, |
| false); |
| rte = pnsi->p_rte; |
| rtr = makeNode(RangeTblRef); |
| /* assume new rte is at end */ |
| rtr->rtindex = list_length(pstate->p_rtable); |
| Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); |
| return (Node *) rtr; |
| } |
| else /*is not a leaf */ |
| { |
| /* Process an internal node (set operation node) */ |
| SetOperationStmt *op = makeNode(SetOperationStmt); |
| List *ltargetlist; |
| List *rtargetlist; |
| ListCell *ltl; |
| ListCell *rtl; |
| cypher_return *self = (cypher_return *) clause->self; |
| const char *context; |
| |
| context = "UNION"; |
| |
| op->op = self->op; |
| op->all = self->all_or_distinct; |
| |
| /* |
| * Recursively transform the left child node. |
| */ |
| op->larg = transform_cypher_union_tree(cpstate, |
| (cypher_clause *) self->larg, |
| false, |
| <argetlist); |
| |
| /* |
| * If we find ourselves processing a recursive CTE here something |
| * went horribly wrong. That is an SQL construct with no parallel in |
| * cypher. |
| */ |
| if (isTopLevel && |
| pstate->p_parent_cte && |
| pstate->p_parent_cte->cterecursive) |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Cypher does not support recursive CTEs"), |
| parser_errposition(pstate, 0))); |
| } |
| |
| /* |
| * Recursively transform the right child node. |
| */ |
| op->rarg = transform_cypher_union_tree(cpstate, |
| (cypher_clause *) self->rarg, |
| false, |
| &rtargetlist); |
| |
| /* |
| * Verify that the two children have the same number of non-junk |
| * columns, and determine the types of the merged output columns. |
| */ |
| if (list_length(ltargetlist) != list_length(rtargetlist)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("each %s query must have the same number of columns", |
| context), |
| parser_errposition(pstate, |
| exprLocation((Node *) rtargetlist)))); |
| } |
| |
| if (targetlist) |
| { |
| *targetlist = NIL; |
| } |
| |
| op->colTypes = NIL; |
| op->colTypmods = NIL; |
| op->colCollations = NIL; |
| op->groupClauses = NIL; |
| |
| forboth(ltl, ltargetlist, rtl, rtargetlist) |
| { |
| TargetEntry *ltle = (TargetEntry *) lfirst(ltl); |
| TargetEntry *rtle = (TargetEntry *) lfirst(rtl); |
| Node *lcolnode = (Node *) ltle->expr; |
| Node *rcolnode = (Node *) rtle->expr; |
| Oid lcoltype = exprType(lcolnode); |
| Oid rcoltype = exprType(rcolnode); |
| int32 lcoltypmod = exprTypmod(lcolnode); |
| int32 rcoltypmod = exprTypmod(rcolnode); |
| Node *bestexpr; |
| int bestlocation; |
| Oid rescoltype; |
| int32 rescoltypmod; |
| Oid rescolcoll; |
| |
| /* select common type, same as CASE et al */ |
| rescoltype = select_common_type(pstate, |
| list_make2(lcolnode, rcolnode), |
| context, |
| &bestexpr); |
| bestlocation = exprLocation(bestexpr); |
| /* if same type and same typmod, use typmod; else default */ |
| if (lcoltype == rcoltype && lcoltypmod == rcoltypmod) |
| { |
| rescoltypmod = lcoltypmod; |
| } |
| else |
| { |
| rescoltypmod = -1; |
| } |
| |
| /* |
| * Verify the coercions are actually possible. If not, we'd fail |
| * later anyway, but we want to fail now while we have sufficient |
| * context to produce an error cursor position. |
| * |
| * For all non-UNKNOWN-type cases, we verify coercibility but we |
| * don't modify the child's expression, for fear of changing the |
| * child query's semantics. |
| * |
| * If a child expression is an UNKNOWN-type Const or Param, we |
| * want to replace it with the coerced expression. This can only |
| * happen when the child is a leaf set-op node. It's safe to |
| * replace the expression because if the child query's semantics |
| * depended on the type of this output column, it'd have already |
| * coerced the UNKNOWN to something else. We want to do this |
| * because (a) we want to verify that a Const is valid for the |
| * target type, or resolve the actual type of an UNKNOWN Param, |
| * and (b) we want to avoid unnecessary discrepancies between the |
| * output type of the child query and the resolved target type. |
| * Such a discrepancy would disable optimization in the planner. |
| * |
| * If it's some other UNKNOWN-type node, eg a Var, we do nothing |
| * (knowing that coerce_to_common_type would fail). The planner |
| * is sometimes able to fold an UNKNOWN Var to a constant before |
| * it has to coerce the type, so failing now would just break |
| * cases that might work. |
| */ |
| if (lcoltype != UNKNOWNOID) |
| { |
| lcolnode = coerce_to_common_type(pstate, lcolnode, |
| rescoltype, context); |
| } |
| else if (IsA(lcolnode, Const) || IsA(lcolnode, Param)) |
| { |
| lcolnode = coerce_to_common_type(pstate, lcolnode, |
| rescoltype, context); |
| ltle->expr = (Expr *) lcolnode; |
| } |
| |
| if (rcoltype != UNKNOWNOID) |
| { |
| rcolnode = coerce_to_common_type(pstate, rcolnode, |
| rescoltype, context); |
| } |
| else if (IsA(rcolnode, Const) || IsA(rcolnode, Param)) |
| { |
| rcolnode = coerce_to_common_type(pstate, rcolnode, |
| rescoltype, context); |
| rtle->expr = (Expr *) rcolnode; |
| } |
| |
| /* |
| * Select common collation. A common collation is required for |
| * all set operators except UNION ALL; see SQL:2008 7.13 <query |
| * expression> Syntax Rule 15c. (If we fail to identify a common |
| * collation for a UNION ALL column, the curCollations element |
| * will be set to InvalidOid, which may result in a runtime error |
| * if something at a higher query level wants to use the column's |
| * collation.) |
| */ |
| rescolcoll = select_common_collation(pstate, |
| list_make2(lcolnode, rcolnode), |
| (op->op == SETOP_UNION && op->all)); |
| |
| /* emit results */ |
| op->colTypes = lappend_oid(op->colTypes, rescoltype); |
| op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); |
| op->colCollations = lappend_oid(op->colCollations, rescolcoll); |
| |
| /* |
| * For all cases except UNION ALL, identify the grouping operators |
| * (and, if available, sorting operators) that will be used to |
| * eliminate duplicates. |
| */ |
| if (op->op != SETOP_UNION || !op->all) |
| { |
| SortGroupClause *grpcl = makeNode(SortGroupClause); |
| Oid sortop; |
| Oid eqop; |
| bool hashable = false; |
| ParseCallbackState pcbstate; |
| |
| setup_parser_errposition_callback(&pcbstate, pstate, |
| bestlocation); |
| |
| /* |
| * determine the eqop and optional sortop |
| * |
| * NOTE: for UNION, we set hashable to false and pass a NULL to |
| * isHashable in get_sort_group_operators to prevent a logic error |
| * where UNION fails to exclude duplicate results. |
| * |
| */ |
| get_sort_group_operators(rescoltype, |
| false, true, false, |
| &sortop, &eqop, NULL, |
| NULL); |
| |
| cancel_parser_errposition_callback(&pcbstate); |
| |
| /* we don't have a tlist yet, so can't assign sortgrouprefs */ |
| grpcl->tleSortGroupRef = 0; |
| grpcl->eqop = eqop; |
| grpcl->sortop = sortop; |
| grpcl->nulls_first = false; /* OK with or without sortop */ |
| grpcl->hashable = hashable; |
| |
| op->groupClauses = lappend(op->groupClauses, grpcl); |
| } |
| |
| /* |
| * Construct a dummy tlist entry to return. We use a SetToDefault |
| * node for the expression, since it carries exactly the fields |
| * needed, but any other expression node type would do as well. |
| */ |
| if (targetlist) |
| { |
| SetToDefault *rescolnode = makeNode(SetToDefault); |
| TargetEntry *restle; |
| |
| rescolnode->typeId = rescoltype; |
| rescolnode->typeMod = rescoltypmod; |
| rescolnode->collation = rescolcoll; |
| rescolnode->location = bestlocation; |
| restle = makeTargetEntry((Expr *) rescolnode, |
| 0, /* no need to set resno */ |
| NULL, |
| false); |
| *targetlist = lappend(*targetlist, restle); |
| } |
| } |
| |
| return (Node *)op; |
| }//end else (is not leaf) |
| } |
| |
| /* |
| * Function that takes a cypher call and returns the yielded result |
| * This function also catches some cases that should fail that could not |
| * be picked up by the grammar. transform_cypher_call_subquery handles the |
| * call transformation itself. |
| */ |
| static Query *transform_cypher_call_stmt(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_call *self = (cypher_call *)clause->self; |
| |
| if (!clause->prev && !clause->next) /* CALL [YIELD] -- the most simple call */ |
| { |
| if (self->where) /* Error check for WHERE clause after YIELD without RETURN */ |
| { |
| Assert(self->yield_items); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("Cannot use standalone CALL with WHERE"), |
| errhint("Instead use `CALL ... WITH * WHERE ... RETURN *`"), |
| parser_errposition(pstate, |
| exprLocation((Node *) self->where)))); |
| } |
| |
| return transform_cypher_call_subquery(cpstate, clause); |
| } |
| else /* subqueries */ |
| { |
| if (!self->yield_items) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("Procedure call inside a query does not support naming results implicitly"), |
| errhint("Name explicitly using `YIELD` instead"), |
| parser_errposition(pstate, |
| exprLocation((Node *) self)))); |
| } |
| Assert(self->yield_items); |
| |
| if (!clause->next) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("Query cannot conclude with CALL"), |
| errhint("Must be RETURN or an update clause"), |
| parser_errposition(pstate, |
| exprLocation((Node *) self)))); |
| } |
| |
| return transform_cypher_clause_with_where(cpstate, |
| transform_cypher_call_subquery, |
| clause, self->where); |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Helper routine for transform_cypher_call_stmt. This routine transforms the |
| * call statement and handles the YIELD clause. |
| */ |
| static Query *transform_cypher_call_subquery(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| ParseState *p_child_parse_state = make_parsestate(NULL); |
| cypher_call *self = (cypher_call *)clause->self; |
| Query *query; |
| char *colName; |
| FuncExpr *node = NULL; |
| TargetEntry *tle; |
| |
| Expr *where_qual = NULL; |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| |
| if (clause->prev) |
| { |
| /* we want to retain all previous range table entries */ |
| handle_prev_clause(cpstate, query, clause->prev, false); |
| } |
| |
| /* transform the funccall and store it in a funcexpr node */ |
| node = castNode( FuncExpr, transform_cypher_expr(cpstate, (Node *) self->funccall, |
| EXPR_KIND_FROM_FUNCTION)); |
| |
| /* retrieve the column name from funccall */ |
| colName = strVal(linitial(self->funccall->funcname)); |
| |
| /* make a targetentry from the funcexpr node */ |
| tle = makeTargetEntry((Expr *) node, |
| (AttrNumber) p_child_parse_state->p_next_resno++, |
| colName, |
| false); |
| |
| if (self->yield_items) /* if there are yield items, we need to check them */ |
| { |
| List *yield_targetList; |
| ListCell *lc; |
| |
| yield_targetList = list_make1(tle); |
| |
| foreach (lc, self->yield_items) |
| { |
| ResTarget *target = NULL; |
| ColumnRef *var = NULL; |
| TargetEntry *yielded_tle = NULL; |
| |
| target = (ResTarget *) lfirst(lc); |
| |
| if (!IsA(target->val, ColumnRef)) |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("YIELD item must be ColumnRef"), |
| parser_errposition(&cpstate->pstate, 0))); |
| } |
| |
| var = (ColumnRef *) target->val; |
| |
| /* check if the restarget variable exists in the yield_targetList*/ |
| if (findTarget(yield_targetList, strVal(linitial(var->fields))) != NULL) |
| { |
| /* check if an alias exists. if one does, we check if it is |
| already declared in the targetlist */ |
| if (target->name) |
| { |
| if (findTarget(query->targetList, target->name) != NULL) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("duplicate variable \"%s\"", target->name), |
| parser_errposition((ParseState *) cpstate, exprLocation((Node *) target)))); |
| } |
| else |
| { |
| yielded_tle = makeTargetEntry((Expr *) node, |
| (AttrNumber) pstate->p_next_resno++, |
| target->name, |
| false); |
| query->targetList = lappend(query->targetList, yielded_tle); |
| } |
| } |
| else/* if there is no alias, we check if the variable is already declared */ |
| { |
| if (findTarget(query->targetList, strVal(linitial(var->fields))) != NULL) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("duplicate variable \"%s\"", colName), |
| parser_errposition((ParseState *) cpstate, exprLocation((Node *) target)))); |
| } |
| else |
| { |
| yielded_tle = makeTargetEntry((Expr *) node, |
| (AttrNumber) pstate->p_next_resno++, |
| colName, |
| false); |
| query->targetList = lappend(query->targetList, yielded_tle); |
| } |
| } |
| } |
| else |
| { |
| /* if the yield_item is not found and we return an error */ |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("Unknown CALL output"), |
| parser_errposition(pstate, exprLocation((Node *) target)))); |
| } |
| } |
| } |
| else /* if there are no yield items this must be a solo call */ |
| { |
| tle = makeTargetEntry((Expr *) node, |
| (AttrNumber) pstate->p_next_resno++, |
| colName, |
| false); |
| query->targetList = list_make1(tle); |
| } |
| |
| |
| |
| markTargetListOrigins(pstate, query->targetList); |
| |
| query->rtable = cpstate->pstate.p_rtable; |
| query->rteperminfos = cpstate->pstate.p_rteperminfos; |
| query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)where_qual); |
| query->hasAggs = pstate->p_hasAggs; |
| |
| assign_query_collations(pstate, query); |
| |
| /* this must be done after collations, for reliable comparison of exprs */ |
| if (pstate->p_hasAggs || |
| query->groupClause || query->groupingSets || query->havingQual) |
| { |
| parse_check_aggregates(pstate, query); |
| } |
| |
| free_parsestate(p_child_parse_state); |
| |
| return query; |
| } |
| |
| /* |
| * Transform the Delete clause. Creates a _cypher_delete_clause |
| * and passes the necessary information that is needed in the |
| * execution phase. |
| */ |
| static Query *transform_cypher_delete(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_delete *self = (cypher_delete *)clause->self; |
| Query *query; |
| TargetEntry *tle; |
| FuncExpr *func_expr; |
| |
| cypher_delete_information *delete_data; |
| |
| delete_data = make_ag_node(cypher_delete_information); |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| query->targetList = NIL; |
| |
| if (!clause->prev) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("DELETE cannot be the first clause in a Cypher query"), |
| parser_errposition(pstate, self->location))); |
| } |
| else |
| { |
| handle_prev_clause(cpstate, query, clause->prev, true); |
| } |
| |
| delete_data->delete_items = transform_cypher_delete_item_list(cpstate, |
| self->exprs, |
| query); |
| delete_data->graph_name = cpstate->graph_name; |
| delete_data->graph_oid = cpstate->graph_oid; |
| delete_data->detach = self->detach; |
| |
| if (!clause->next) |
| { |
| delete_data->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; |
| } |
| |
| func_expr = make_clause_func_expr(DELETE_CLAUSE_FUNCTION_NAME, |
| (Node *)delete_data); |
| |
| // Create the target entry |
| tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, |
| AGE_VARNAME_DELETE_CLAUSE, false); |
| query->targetList = lappend(query->targetList, tle); |
| |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| |
| return query; |
| } |
| |
| /* |
| * transform_cypher_unwind |
| * It contains logic to convert the form of an array into a row. Here, we |
| * are simply calling `age_unnest` function, and the actual transformation |
| * is handled by `age_unnest` function. |
| */ |
| static Query *transform_cypher_unwind(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *) cpstate; |
| cypher_unwind *self = (cypher_unwind *) clause->self; |
| int target_syntax_loc; |
| Query *query; |
| Node *expr; |
| FuncCall *unwind; |
| ParseExprKind old_expr_kind; |
| Node *funcexpr; |
| TargetEntry *te; |
| ParseNamespaceItem *pnsi; |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| |
| if (clause->prev) |
| { |
| int rtindex; |
| |
| pnsi = transform_prev_cypher_clause(cpstate, clause->prev, true); |
| rtindex = list_length(pstate->p_rtable); |
| // rte is the first RangeTblEntry in pstate |
| if (rtindex != 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("invalid value for rtindex"))); |
| } |
| |
| query->targetList = expandNSItemAttrs(pstate, pnsi, 0, true, -1); |
| } |
| |
| target_syntax_loc = exprLocation((const Node *) self->target); |
| |
| if (findTarget(query->targetList, self->target->name) != NULL) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("duplicate variable \"%s\"", self->target->name), |
| parser_errposition((ParseState *) cpstate, |
| target_syntax_loc))); |
| } |
| |
| expr = transform_cypher_expr(cpstate, self->target->val, |
| EXPR_KIND_SELECT_TARGET); |
| |
| unwind = makeFuncCall(list_make1(makeString("age_unnest")), NIL, |
| COERCE_SQL_SYNTAX, -1); |
| |
| old_expr_kind = pstate->p_expr_kind; |
| pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET; |
| funcexpr = ParseFuncOrColumn(pstate, unwind->funcname, |
| list_make1(expr), |
| pstate->p_last_srf, unwind, false, |
| target_syntax_loc); |
| |
| pstate->p_expr_kind = old_expr_kind; |
| |
| te = makeTargetEntry((Expr *) funcexpr, |
| (AttrNumber) pstate->p_next_resno++, |
| self->target->name, false); |
| |
| query->targetList = lappend(query->targetList, te); |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| query->hasTargetSRFs = pstate->p_hasTargetSRFs; |
| |
| assign_query_collations(pstate, query); |
| |
| return query; |
| } |
| |
| /* |
| * Iterate through the list of items to delete and extract the variable name. |
| * Then find the resno that the variable name belongs to. |
| */ |
| static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate, |
| List *delete_item_list, |
| Query *query) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| List *items = NIL; |
| ListCell *lc; |
| |
| foreach(lc, delete_item_list) |
| { |
| Node *expr = lfirst(lc); |
| ColumnRef *col; |
| String *val; |
| Integer *pos; |
| int resno; |
| |
| cypher_delete_item *item = make_ag_node(cypher_delete_item); |
| |
| if (!IsA(expr, ColumnRef)) |
| { |
| ereport(ERROR, |
| (errmsg_internal("unexpected Node for cypher_clause"))); |
| } |
| |
| col = (ColumnRef *)expr; |
| |
| if (list_length(col->fields) != 1) |
| { |
| ereport(ERROR, |
| (errmsg_internal("unexpected Node for cypher_clause"))); |
| } |
| |
| val = linitial(col->fields); |
| |
| if (!IsA(val, String)) |
| { |
| ereport(ERROR, |
| (errmsg_internal("unexpected Node for cypher_clause"))); |
| } |
| |
| resno = get_target_entry_resno(query->targetList, val->sval); |
| if (resno == -1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("undefined reference to variable %s in DELETE clause", |
| val->sval), |
| parser_errposition(pstate, col->location))); |
| } |
| |
| add_volatile_wrapper_to_target_entry(query->targetList, resno); |
| |
| pos = makeInteger(resno); |
| |
| item->var_name = val->sval; |
| item->entity_position = pos; |
| |
| items = lappend(items, item); |
| } |
| |
| return items; |
| } |
| |
| static Query *transform_cypher_set(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_set *self = (cypher_set *)clause->self; |
| Query *query; |
| cypher_update_information *set_items_target_list; |
| TargetEntry *tle; |
| FuncExpr *func_expr; |
| char *clause_name; |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| query->targetList = NIL; |
| |
| if (self->is_remove == true) |
| { |
| clause_name = UPDATE_CLAUSE_REMOVE; |
| } |
| else |
| { |
| clause_name = UPDATE_CLAUSE_SET; |
| } |
| |
| if (!clause->prev) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("%s cannot be the first clause in a Cypher query", |
| clause_name), |
| parser_errposition(pstate, self->location))); |
| } |
| else |
| { |
| handle_prev_clause(cpstate, query, clause->prev, true); |
| } |
| |
| if (self->is_remove == true) |
| { |
| set_items_target_list = transform_cypher_remove_item_list(cpstate, |
| self->items, |
| query); |
| } |
| else |
| { |
| set_items_target_list = transform_cypher_set_item_list(cpstate, |
| self->items, |
| query); |
| } |
| |
| set_items_target_list->clause_name = clause_name; |
| set_items_target_list->graph_name = cpstate->graph_name; |
| |
| if (!clause->next) |
| { |
| set_items_target_list->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; |
| } |
| |
| func_expr = make_clause_func_expr(SET_CLAUSE_FUNCTION_NAME, |
| (Node *)set_items_target_list); |
| |
| // Create the target entry |
| tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, |
| AGE_VARNAME_SET_CLAUSE, false); |
| query->targetList = lappend(query->targetList, tle); |
| |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| |
| return query; |
| } |
| |
| cypher_update_information *transform_cypher_remove_item_list( |
| cypher_parsestate *cpstate, List *remove_item_list, Query *query) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| ListCell *li; |
| cypher_update_information *info = make_ag_node(cypher_update_information); |
| |
| info->set_items = NIL; |
| info->flags = 0; |
| |
| foreach (li, remove_item_list) |
| { |
| cypher_set_item *set_item = lfirst(li); |
| cypher_update_item *item; |
| ColumnRef *ref; |
| A_Indirection *ind; |
| char *variable_name, *property_name; |
| String *property_node, *variable_node; |
| |
| item = make_ag_node(cypher_update_item); |
| |
| if (!is_ag_node(lfirst(li), cypher_set_item)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("unexpected node in cypher update list"))); |
| } |
| |
| if (set_item->is_add) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("REMOVE clause does not support adding properties from maps"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| set_item->is_add = false; |
| |
| item->remove_item = true; |
| |
| |
| if (!IsA(set_item->prop, A_Indirection)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("REMOVE clause must be in the format: REMOVE variable.property_name"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| ind = (A_Indirection *)set_item->prop; |
| |
| // extract variable name |
| if (!IsA(ind->arg, ColumnRef)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("REMOVE clause must be in the format: REMOVE variable.property_name"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| ref = (ColumnRef *)ind->arg; |
| |
| variable_node = linitial(ref->fields); |
| |
| variable_name = variable_node->sval; |
| item->var_name = variable_name; |
| |
| item->entity_position = get_target_entry_resno(query->targetList, |
| variable_name); |
| if (item->entity_position == -1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("undefined reference to variable %s in REMOVE clause", |
| variable_name), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| add_volatile_wrapper_to_target_entry(query->targetList, |
| item->entity_position); |
| |
| // extract property name |
| if (list_length(ind->indirection) != 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("REMOVE clause must be in the format: REMOVE variable.property_name"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| property_node = linitial(ind->indirection); |
| |
| if (!IsA(property_node, String)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("REMOVE clause expects a property name"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| property_name = property_node->sval; |
| item->prop_name = property_name; |
| |
| info->set_items = lappend(info->set_items, item); |
| } |
| |
| return info; |
| } |
| |
| |
| cypher_update_information *transform_cypher_set_item_list( |
| cypher_parsestate *cpstate, List *set_item_list, Query *query) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| ListCell *li; |
| cypher_update_information *info = make_ag_node(cypher_update_information); |
| |
| info->set_items = NIL; |
| info->flags = 0; |
| |
| foreach (li, set_item_list) |
| { |
| cypher_set_item *set_item = lfirst(li); |
| TargetEntry *target_item; |
| cypher_update_item *item; |
| ColumnRef *ref; |
| A_Indirection *ind; |
| char *variable_name, *property_name; |
| String *property_node, *variable_node; |
| int is_entire_prop_update = 0; // true if a map is assigned to variable |
| |
| // LHS of set_item must be a variable or an indirection. |
| if (IsA(set_item->prop, ColumnRef)) |
| { |
| /* |
| * A variable can only be assigned a map, a function call that |
| * evaluates to a map, or a variable. |
| * |
| * In case of a function call, whether it actually evaluates to |
| * map is checked in the execution stage. |
| */ |
| if (!is_ag_node(set_item->expr, cypher_map) && |
| !IsA(set_item->expr, FuncCall) && |
| !IsA(set_item->expr, ColumnRef)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("SET clause expects a map"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| is_entire_prop_update = 1; |
| |
| /* |
| * In case of a variable, it is wrapped as an argument to |
| * the 'properties' function. |
| */ |
| if (IsA(set_item->expr, ColumnRef)) |
| { |
| List *qualified_name, *args; |
| |
| qualified_name = list_make2(makeString("ag_catalog"), |
| makeString("age_properties")); |
| args = list_make1(set_item->expr); |
| set_item->expr = (Node *)makeFuncCall(qualified_name, args, |
| COERCE_SQL_SYNTAX, -1); |
| } |
| } |
| else if (!IsA(set_item->prop, A_Indirection)) |
| { |
| ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("SET clause expects a variable name"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| item = make_ag_node(cypher_update_item); |
| |
| if (!is_ag_node(lfirst(li), cypher_set_item)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("unexpected node in cypher update list"))); |
| } |
| |
| item->remove_item = false; |
| |
| // set variable, is_add and extract property name |
| if (is_entire_prop_update) |
| { |
| ref = (ColumnRef *)set_item->prop; |
| item->is_add = set_item->is_add; |
| item->prop_name = NULL; |
| } |
| else |
| { |
| ind = (A_Indirection *)set_item->prop; |
| ref = (ColumnRef *)ind->arg; |
| |
| if (set_item->is_add) |
| { |
| ereport( |
| ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg( |
| "SET clause does not yet support incrementing a specific property"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| set_item->is_add = false; |
| |
| // extract property name |
| if (list_length(ind->indirection) != 1) |
| { |
| ereport( |
| ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("SET clause doesn't not support updating maps or lists in a property"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| property_node = linitial(ind->indirection); |
| if (!IsA(property_node, String)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("SET clause expects a property name"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| property_name = property_node->sval; |
| item->prop_name = property_name; |
| } |
| |
| // extract variable name |
| variable_node = linitial(ref->fields); |
| if (!IsA(variable_node, String)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("SET clause expects a variable name"), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| variable_name = variable_node->sval; |
| item->var_name = variable_name; |
| |
| item->entity_position = get_target_entry_resno(query->targetList, |
| variable_name); |
| if (item->entity_position == -1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("undefined reference to variable %s in SET clause", |
| variable_name), |
| parser_errposition(pstate, set_item->location))); |
| } |
| |
| add_volatile_wrapper_to_target_entry(query->targetList, |
| item->entity_position); |
| |
| // set keep_null property |
| if (is_ag_node(set_item->expr, cypher_map)) |
| { |
| ((cypher_map*)set_item->expr)->keep_null = set_item->is_add; |
| } |
| |
| // create target entry for the new property value |
| item->prop_position = (AttrNumber)pstate->p_next_resno; |
| target_item = transform_cypher_item(cpstate, set_item->expr, NULL, |
| EXPR_KIND_SELECT_TARGET, NULL, |
| false); |
| target_item->expr = add_volatile_wrapper(target_item->expr); |
| |
| query->targetList = lappend(query->targetList, target_item); |
| info->set_items = lappend(info->set_items, item); |
| } |
| |
| return info; |
| } |
| |
| /* from PG's static helper function */ |
| static Node *flatten_grouping_sets(Node *expr, bool toplevel, |
| bool *hasGroupingSets) |
| { |
| /* just in case of pathological input */ |
| check_stack_depth(); |
| |
| if (expr == (Node *) NIL) |
| { |
| return (Node *) NIL; |
| } |
| |
| switch (expr->type) |
| { |
| case T_RowExpr: |
| { |
| RowExpr *r = (RowExpr *) expr; |
| |
| if (r->row_format == COERCE_IMPLICIT_CAST) |
| { |
| return flatten_grouping_sets((Node *) r->args, false, NULL); |
| } |
| break; |
| } |
| case T_GroupingSet: |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("flattening of GroupingSet is not implemented"))); |
| break; |
| case T_List: |
| { |
| List *result = NIL; |
| ListCell *l; |
| |
| foreach(l, (List *) expr) |
| { |
| Node *n = NULL; |
| |
| n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets); |
| |
| if (n != (Node *) NIL) |
| { |
| if (IsA(n, List)) |
| { |
| result = list_concat(result, (List *) n); |
| } |
| else |
| { |
| result = lappend(result, n); |
| } |
| } |
| } |
| return (Node *) result; |
| } |
| default: |
| break; |
| } |
| return expr; |
| } |
| |
| /* from PG's addTargetToGroupList */ |
| static List *add_target_to_group_list(cypher_parsestate *cpstate, |
| TargetEntry *tle, List *grouplist, |
| List *targetlist, int location) |
| { |
| ParseState *pstate = &cpstate->pstate; |
| Oid restype = exprType((Node *) tle->expr); |
| |
| /* if tlist item is an UNKNOWN literal, change it to TEXT */ |
| if (restype == UNKNOWNOID) |
| { |
| tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, restype, |
| TEXTOID, -1, COERCION_IMPLICIT, |
| COERCE_IMPLICIT_CAST, -1); |
| restype = TEXTOID; |
| } |
| |
| /* avoid making duplicate grouplist entries */ |
| if (!targetIsInSortList(tle, InvalidOid, grouplist)) |
| { |
| SortGroupClause *grpcl = makeNode(SortGroupClause); |
| Oid sortop; |
| Oid eqop; |
| bool hashable; |
| ParseCallbackState pcbstate; |
| |
| setup_parser_errposition_callback(&pcbstate, pstate, location); |
| |
| /* determine the eqop and optional sortop */ |
| get_sort_group_operators(restype, false, true, false, &sortop, &eqop, |
| NULL, &hashable); |
| |
| cancel_parser_errposition_callback(&pcbstate); |
| |
| grpcl->tleSortGroupRef = assignSortGroupRef(tle, targetlist); |
| grpcl->eqop = eqop; |
| grpcl->sortop = sortop; |
| grpcl->nulls_first = false; /* OK with or without sortop */ |
| grpcl->hashable = hashable; |
| |
| grouplist = lappend(grouplist, grpcl); |
| } |
| |
| return grouplist; |
| } |
| |
| /* from PG's transformGroupClauseExpr */ |
| static Index transform_group_clause_expr(List **flatresult, |
| Bitmapset *seen_local, |
| cypher_parsestate *cpstate, |
| Node *gexpr, List **targetlist, |
| List *sortClause, |
| ParseExprKind exprKind, bool toplevel) |
| { |
| TargetEntry *tle = NULL; |
| bool found = false; |
| |
| tle = find_target_list_entry(cpstate, gexpr, targetlist, exprKind); |
| |
| if (tle->ressortgroupref > 0) |
| { |
| ListCell *sl; |
| |
| /* |
| * Eliminate duplicates (GROUP BY x, x) but only at local level. |
| * (Duplicates in grouping sets can affect the number of returned |
| * rows, so can't be dropped indiscriminately.) |
| * |
| * Since we don't care about anything except the sortgroupref, we can |
| * use a bitmapset rather than scanning lists. |
| */ |
| if (bms_is_member(tle->ressortgroupref, seen_local)) |
| { |
| return 0; |
| } |
| |
| /* |
| * If we're already in the flat clause list, we don't need to consider |
| * adding ourselves again. |
| */ |
| found = targetIsInSortList(tle, InvalidOid, *flatresult); |
| if (found) |
| { |
| return tle->ressortgroupref; |
| } |
| |
| /* |
| * If the GROUP BY tlist entry also appears in ORDER BY, copy operator |
| * info from the (first) matching ORDER BY item. This means that if |
| * you write something like "GROUP BY foo ORDER BY foo USING <<<", the |
| * GROUP BY operation silently takes on the equality semantics implied |
| * by the ORDER BY. There are two reasons to do this: it improves the |
| * odds that we can implement both GROUP BY and ORDER BY with a single |
| * sort step, and it allows the user to choose the equality semantics |
| * used by GROUP BY, should she be working with a datatype that has |
| * more than one equality operator. |
| * |
| * If we're in a grouping set, though, we force our requested ordering |
| * to be NULLS LAST, because if we have any hope of using a sorted agg |
| * for the job, we're going to be tacking on generated NULL values |
| * after the corresponding groups. If the user demands nulls first, |
| * another sort step is going to be inevitable, but that's the |
| * planner's problem. |
| */ |
| |
| |
| foreach(sl, sortClause) |
| { |
| SortGroupClause *sc = (SortGroupClause *) lfirst(sl); |
| |
| if (sc->tleSortGroupRef == tle->ressortgroupref) |
| { |
| SortGroupClause *grpc = copyObject(sc); |
| |
| if (!toplevel) |
| { |
| grpc->nulls_first = false; |
| } |
| *flatresult = lappend(*flatresult, grpc); |
| found = true; |
| break; |
| } |
| } |
| } |
| |
| /* |
| * If no match in ORDER BY, just add it to the result using default |
| * sort/group semantics. |
| */ |
| if (!found) |
| { |
| *flatresult = add_target_to_group_list(cpstate, tle, *flatresult, |
| *targetlist, exprLocation(gexpr)); |
| } |
| |
| /* _something_ must have assigned us a sortgroupref by now... */ |
| |
| return tle->ressortgroupref; |
| } |
| |
| /* from PG's transformGroupClause */ |
| static List * transform_group_clause(cypher_parsestate *cpstate, |
| List *grouplist, List **groupingSets, |
| List **targetlist, List *sortClause, |
| ParseExprKind exprKind) |
| { |
| List *result = NIL; |
| List *flat_grouplist; |
| List *gsets = NIL; |
| ListCell *gl; |
| bool hasGroupingSets = false; |
| Bitmapset *seen_local = NULL; |
| |
| /* |
| * Recursively flatten implicit RowExprs. (Technically this is only needed |
| * for GROUP BY, per the syntax rules for grouping sets, but we do it |
| * anyway.) |
| */ |
| flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist, true, |
| &hasGroupingSets); |
| |
| foreach(gl, flat_grouplist) |
| { |
| Node *gexpr = (Node *) lfirst(gl); |
| |
| if (IsA(gexpr, GroupingSet)) |
| { |
| ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("GroupingSet is not implemented"))); |
| break; |
| } |
| else |
| { |
| Index ref = transform_group_clause_expr(&result, seen_local, |
| cpstate, gexpr, targetlist, |
| sortClause, exprKind, true); |
| if (ref > 0) |
| { |
| seen_local = bms_add_member(seen_local, ref); |
| if (hasGroupingSets) |
| { |
| ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("GroupingSet is not implemented"))); |
| } |
| } |
| } |
| } |
| |
| /* parser should prevent this */ |
| Assert(gsets == NIL || groupingSets != NULL); |
| |
| if (groupingSets) |
| { |
| *groupingSets = gsets; |
| } |
| |
| return result; |
| } |
| |
| static Query *transform_cypher_return(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_return *self = (cypher_return *)clause->self; |
| Query *query; |
| List *groupClause = NIL; |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| |
| if (clause->prev) |
| { |
| transform_prev_cypher_clause(cpstate, clause->prev, true); |
| } |
| |
| query->targetList = transform_cypher_item_list(cpstate, self->items, |
| &groupClause, |
| EXPR_KIND_SELECT_TARGET); |
| |
| markTargetListOrigins(pstate, query->targetList); |
| |
| // ORDER BY |
| query->sortClause = transform_cypher_order_by(cpstate, self->order_by, |
| &query->targetList, |
| EXPR_KIND_ORDER_BY); |
| |
| /* 'auto' GROUP BY (from PG's transformGroupClause) */ |
| query->groupClause = transform_group_clause(cpstate, groupClause, |
| &query->groupingSets, |
| &query->targetList, |
| query->sortClause, |
| EXPR_KIND_GROUP_BY); |
| |
| // DISTINCT |
| if (self->distinct) |
| { |
| query->distinctClause = transformDistinctClause( |
| pstate, &query->targetList, query->sortClause, false); |
| query->hasDistinctOn = false; |
| } |
| else |
| { |
| query->distinctClause = NIL; |
| query->hasDistinctOn = false; |
| } |
| |
| // SKIP and LIMIT |
| query->limitOffset = transform_cypher_limit(cpstate, self->skip, |
| EXPR_KIND_OFFSET, "SKIP"); |
| query->limitCount = transform_cypher_limit(cpstate, self->limit, |
| EXPR_KIND_LIMIT, "LIMIT"); |
| |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| query->hasAggs = pstate->p_hasAggs; |
| |
| assign_query_collations(pstate, query); |
| |
| /* this must be done after collations, for reliable comparison of exprs */ |
| if (pstate->p_hasAggs || |
| query->groupClause || query->groupingSets || query->havingQual) |
| { |
| parse_check_aggregates(pstate, query); |
| } |
| |
| return query; |
| } |
| |
| // see transformSortClause() |
| static List *transform_cypher_order_by(cypher_parsestate *cpstate, |
| List *sort_items, List **target_list, |
| ParseExprKind expr_kind) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| List *sort_list = NIL; |
| ListCell *li; |
| |
| foreach (li, sort_items) |
| { |
| SortBy *sort_by = lfirst(li); |
| TargetEntry *te; |
| |
| te = find_target_list_entry(cpstate, sort_by->node, target_list, |
| expr_kind); |
| |
| sort_list = addTargetToSortList(pstate, te, sort_list, *target_list, |
| sort_by); |
| } |
| |
| return sort_list; |
| } |
| |
| // see findTargetlistEntrySQL99() |
| static TargetEntry *find_target_list_entry(cypher_parsestate *cpstate, |
| Node *node, List **target_list, |
| ParseExprKind expr_kind) |
| { |
| Node *expr; |
| ListCell *lt; |
| TargetEntry *te; |
| |
| expr = transform_cypher_expr(cpstate, node, expr_kind); |
| |
| foreach (lt, *target_list) |
| { |
| Node *te_expr; |
| |
| te = lfirst(lt); |
| te_expr = strip_implicit_coercions((Node *)te->expr); |
| |
| if (equal(expr, te_expr)) |
| { |
| return te; |
| } |
| } |
| |
| te = transform_cypher_item(cpstate, node, expr, expr_kind, NULL, true); |
| |
| *target_list = lappend(*target_list, te); |
| |
| return te; |
| } |
| |
| // see transformLimitClause() |
| static Node *transform_cypher_limit(cypher_parsestate *cpstate, Node *node, |
| ParseExprKind expr_kind, |
| const char *construct_name) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Node *qual; |
| |
| if (!node) |
| { |
| return NULL; |
| } |
| |
| qual = transform_cypher_expr(cpstate, node, expr_kind); |
| |
| qual = coerce_to_specific_type(pstate, qual, INT8OID, construct_name); |
| |
| // LIMIT can't refer to any variables of the current query. |
| if (contain_vars_of_level(qual, 0)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("argument of %s must not contain variables", |
| construct_name), |
| parser_errposition(pstate, locate_var_of_level(qual, 0)))); |
| } |
| |
| return qual; |
| } |
| |
| static Query *transform_cypher_with(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| cypher_with *self = (cypher_with *)clause->self; |
| cypher_return *return_clause; |
| cypher_clause *wrapper; |
| |
| // TODO: check that all items have an alias for each |
| |
| // WITH clause is basically RETURN clause with optional WHERE subclause |
| return_clause = make_ag_node(cypher_return); |
| return_clause->distinct = self->distinct; |
| return_clause->items = self->items; |
| return_clause->order_by = self->order_by; |
| return_clause->skip = self->skip; |
| return_clause->limit = self->limit; |
| |
| wrapper = palloc(sizeof(*wrapper)); |
| wrapper->self = (Node *)return_clause; |
| wrapper->prev = clause->prev; |
| |
| return transform_cypher_clause_with_where(cpstate, transform_cypher_return, |
| wrapper, self->where); |
| } |
| |
| static bool match_check_valid_label(cypher_match *match, |
| cypher_parsestate *cpstate) |
| { |
| ListCell *cell1; |
| ListCell *cell2; |
| cypher_path *path; |
| |
| foreach(cell1, match->pattern) |
| { |
| int i = 0; |
| path = (cypher_path*) lfirst(cell1); |
| |
| foreach(cell2, path->path) |
| { |
| if (i % 2 == 0) |
| { |
| cypher_node *node = NULL; |
| |
| node = lfirst(cell2); |
| |
| if (node->label) |
| { |
| label_cache_data *lcd = |
| search_label_name_graph_cache(node->label, |
| cpstate->graph_oid); |
| |
| if (lcd == NULL || |
| lcd->kind != LABEL_KIND_VERTEX) |
| { |
| return false; |
| } |
| } |
| } |
| else |
| { |
| cypher_relationship *rel = NULL; |
| |
| rel = lfirst(cell2); |
| |
| if (rel->label) |
| { |
| label_cache_data *lcd = |
| search_label_name_graph_cache(rel->label, |
| cpstate->graph_oid); |
| |
| if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) |
| { |
| return false; |
| } |
| } |
| } |
| i++; |
| } |
| } |
| |
| return true; |
| } |
| |
| static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, |
| transform_method transform, |
| cypher_clause *clause, |
| Node *where) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Query *query; |
| Node *self = clause->self; |
| Node *where_qual = NULL; |
| |
| if (where) |
| { |
| int rtindex; |
| ParseNamespaceItem *pnsi; |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| |
| 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 |
| if (rtindex != 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("invalid value for rtindex"))); |
| } |
| |
| /* |
| * add all the target entries in pnsi to the current target list to pass |
| * all the variables that are introduced in the previous clause to the |
| * next clause |
| */ |
| query->targetList = expandNSItemAttrs(pstate, pnsi, 0, true, -1); |
| |
| markTargetListOrigins(pstate, query->targetList); |
| |
| 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"); |
| } |
| |
| query->jointree = makeFromExpr(pstate->p_joinlist, where_qual); |
| assign_query_collations(pstate, query); |
| } |
| else |
| { |
| query = transform(cpstate, clause); |
| } |
| |
| query->hasSubLinks = pstate->p_hasSubLinks; |
| query->hasTargetSRFs = pstate->p_hasTargetSRFs; |
| query->hasAggs = pstate->p_hasAggs; |
| |
| return query; |
| } |
| |
| static Query *transform_cypher_match(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| cypher_match *match_self = (cypher_match*) clause->self; |
| |
| if(!match_check_valid_label(match_self, cpstate)) |
| { |
| cypher_bool_const *l = make_ag_node(cypher_bool_const); |
| cypher_bool_const *r = make_ag_node(cypher_bool_const); |
| |
| l->boolean = true; |
| l->location = -1; |
| r->boolean = false; |
| r->location = -1; |
| |
| /*if the label is invalid, create a paradoxical where to get null*/ |
| match_self->where = (Node *)makeSimpleA_Expr(AEXPR_OP, "=", |
| (Node *)l, |
| (Node *)r, -1); |
| } |
| |
| return transform_cypher_clause_with_where( |
| cpstate, transform_cypher_match_pattern, clause, |
| match_self->where); |
| } |
| |
| /* |
| * Transform the clause into a subquery. This subquery will be used |
| * in a join so setup the namespace item and the created the rtr |
| * for the join to use. |
| */ |
| static Node *transform_clause_for_join(cypher_parsestate *cpstate, |
| cypher_clause *clause, |
| RangeTblEntry **rte, |
| ParseNamespaceItem **nsitem, |
| Alias* alias) |
| { |
| RangeTblRef *rtr; |
| |
| *nsitem = transform_cypher_clause_as_subquery(cpstate, |
| transform_cypher_clause, |
| clause, alias, false); |
| *rte = (*nsitem)->p_rte; |
| |
| rtr = makeNode(RangeTblRef); |
| rtr->rtindex = (*nsitem)->p_rtindex; |
| |
| return (Node *) rtr; |
| } |
| |
| /* |
| * For cases where we need to join two subqueries together (OPTIONAL MATCH and |
| * MERGE) we need to take the columns available in each rte and merge them |
| * together. The l_rte has precedence when there is a conflict, because that |
| * means that the pattern create in the current clause is referencing a |
| * variable declared in a previous clause (the l_rte). The output is the |
| * res_colnames and res_colvars that are passed in. |
| */ |
| static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, |
| ParseNamespaceItem *r_pnsi, List **res_colnames, |
| List **res_colvars) |
| { |
| List *l_colnames, *l_colvars; |
| List *r_colnames, *r_colvars; |
| ListCell *r_lname, *r_lvar; |
| List *colnames = NIL; |
| List *colvars = NIL; |
| |
| expandRTE(l_pnsi->p_rte, l_pnsi->p_rtindex, 0, -1, false, |
| &l_colnames, &l_colvars); |
| expandRTE(r_pnsi->p_rte, r_pnsi->p_rtindex, 0, -1, false, |
| &r_colnames, &r_colvars); |
| |
| // add in all colnames and colvars from the l_rte. |
| *res_colnames = list_concat(*res_colnames, l_colnames); |
| *res_colvars = list_concat(*res_colvars, l_colvars); |
| |
| // find new columns and if they are a var, pass them in. |
| forboth(r_lname, r_colnames, r_lvar, r_colvars) |
| { |
| char *r_colname = strVal(lfirst(r_lname)); |
| ListCell *lname; |
| ListCell *lvar; |
| Var *var = NULL; |
| |
| forboth(lname, *res_colnames, lvar, *res_colvars) |
| { |
| char *colname = strVal(lfirst(lname)); |
| |
| if (strcmp(r_colname, colname) == 0) |
| { |
| var = lfirst(lvar); |
| break; |
| } |
| } |
| |
| if (var == NULL) |
| { |
| Var *v; |
| |
| /* |
| * Each join (left) RTE's Var, that references a column of the |
| * right RTE, needs to be marked 'nullable'. |
| */ |
| v = lfirst(r_lvar); |
| markNullableIfNeeded(pstate, v); |
| |
| colnames = lappend(colnames, lfirst(r_lname)); |
| colvars = lappend(colvars, v); |
| } |
| } |
| |
| *res_colnames = list_concat(*res_colnames, colnames); |
| *res_colvars = list_concat(*res_colvars, colvars); |
| } |
| |
| /* |
| * transform_cypher_optional_match_clause |
| * Transform the previous clauses and OPTIONAL MATCH clauses to be LATERAL LEFT JOIN |
| * transform_cypher_optional_match_clause to construct a result value. |
| */ |
| static RangeTblEntry *transform_cypher_optional_match_clause(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| cypher_clause *prevclause; |
| RangeTblEntry *l_rte, *r_rte; |
| ParseNamespaceItem *l_nsitem, *r_nsitem; |
| ParseState *pstate = (ParseState *) cpstate; |
| JoinExpr* j = makeNode(JoinExpr); |
| List *res_colnames = NIL, *res_colvars = NIL; |
| Alias *l_alias, *r_alias; |
| ParseNamespaceItem *jnsitem; |
| int i = 0; |
| |
| j->jointype = JOIN_LEFT; |
| |
| l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); |
| r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); |
| |
| j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, |
| &l_nsitem, l_alias); |
| pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); |
| |
| /* |
| * Remove the previous clause so when the transform_clause_for_join function |
| * transforms the OPTIONAL MATCH, the previous clause will not be transformed |
| * again. |
| */ |
| prevclause = clause->prev; |
| clause->prev = NULL; |
| |
| //set the lateral flag to true |
| pstate->p_lateral_active = true; |
| |
| j->rarg = transform_clause_for_join(cpstate, clause, &r_rte, |
| &r_nsitem, r_alias); |
| |
| /* |
| * Since this is a left join, we need to mark j->rarg as it may potentially |
| * emit NULL. The jindex argument holds rtindex of the join's RTE, which is |
| * created right after j->arg's RTE in this case. |
| */ |
| markRelsAsNulledBy(pstate, j->rarg, r_nsitem->p_rtindex + 1); |
| |
| // we are done transform the lateral left join |
| pstate->p_lateral_active = false; |
| |
| /* |
| * We are done with the previous clause in the transform phase, but |
| * reattach the previous clause for semantics. |
| */ |
| clause->prev = prevclause; |
| |
| pstate->p_namespace = NIL; |
| |
| // get the colnames and colvars from the rtes |
| get_res_cols(pstate, l_nsitem, r_nsitem, &res_colnames, &res_colvars); |
| |
| jnsitem = addRangeTableEntryForJoin(pstate, |
| res_colnames, |
| NULL, |
| j->jointype, |
| 0, |
| res_colvars, |
| NIL, |
| NIL, |
| j->alias, |
| NULL, |
| false); |
| |
| j->rtindex = jnsitem->p_rtindex; |
| |
| for (i = list_length(pstate->p_joinexprs) + 1; i < j->rtindex; i++) |
| { |
| pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL); |
| } |
| pstate->p_joinexprs = lappend(pstate->p_joinexprs, j); |
| Assert(list_length(pstate->p_joinexprs) == j->rtindex); |
| |
| pstate->p_joinlist = lappend(pstate->p_joinlist, j); |
| |
| /* add jrte to column namespace only */ |
| addNSItemToQuery(pstate, jnsitem, false, false, true); |
| |
| return jnsitem->p_rte; |
| } |
| |
| static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_match *self = (cypher_match *)clause->self; |
| Query *query; |
| Node *where = self->where; |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| |
| if(self->optional == true && clause->next) |
| { |
| cypher_clause *next = clause->next; |
| if (is_ag_node(next->self, cypher_match)) |
| { |
| cypher_match *next_self = (cypher_match *)next->self; |
| if (!next_self->optional) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("MATCH cannot follow OPTIONAL MATCH"), |
| parser_errposition(pstate, |
| exprLocation((Node *) next_self)))); |
| } |
| } |
| } |
| |
| // If there is no previous clause, transform to a general MATCH clause. |
| if (self->optional == true && clause->prev != NULL) |
| { |
| RangeTblEntry *rte = transform_cypher_optional_match_clause(cpstate, clause); |
| |
| query->targetList = make_target_list_from_join(pstate, rte); |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| } |
| else |
| { |
| if (clause->prev) |
| { |
| RangeTblEntry *rte; |
| int rtindex; |
| ParseNamespaceItem *pnsi; |
| |
| pnsi = transform_prev_cypher_clause(cpstate, clause->prev, true); |
| rte = pnsi->p_rte; |
| rtindex = list_length(pstate->p_rtable); |
| // rte is the first RangeTblEntry in pstate |
| if (rtindex != 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("invalid value for rtindex"))); |
| } |
| |
| /* |
| * add all the target entries in rte to the current target list to pass |
| * all the variables that are introduced in the previous clause to the |
| * next clause |
| */ |
| pnsi = get_namespace_item(pstate, rte); |
| query->targetList = expandNSItemAttrs(pstate, pnsi, 0, true, -1); |
| } |
| |
| transform_match_pattern(cpstate, query, self->pattern, where); |
| } |
| |
| markTargetListOrigins(pstate, query->targetList); |
| |
| query->hasSubLinks = pstate->p_hasSubLinks; |
| query->hasWindowFuncs = pstate->p_hasWindowFuncs; |
| query->hasTargetSRFs = pstate->p_hasTargetSRFs; |
| query->hasAggs = pstate->p_hasAggs; |
| |
| assign_query_collations(pstate, query); |
| |
| return query; |
| } |
| |
| /* |
| * Function to make a target list from an RTE. Taken from AgensGraph and PG |
| */ |
| static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte) |
| { |
| List *targetlist = NIL; |
| ListCell *lt; |
| ListCell *ln; |
| |
| Assert(rte->rtekind == RTE_JOIN); |
| |
| forboth(lt, rte->joinaliasvars, ln, rte->eref->colnames) |
| { |
| Var *varnode = lfirst(lt); |
| char *resname = strVal(lfirst(ln)); |
| TargetEntry *tmp; |
| |
| tmp = makeTargetEntry((Expr *) varnode, |
| (AttrNumber) pstate->p_next_resno++, |
| pstrdup(resname), |
| false); |
| targetlist = lappend(targetlist, tmp); |
| } |
| |
| return targetlist; |
| } |
| |
| /* |
| * Function to make a target list from an RTE. Borrowed from AgensGraph and PG |
| */ |
| static List *makeTargetListFromPNSItem(ParseState *pstate, ParseNamespaceItem *pnsi) |
| { |
| List *targetlist = NIL; |
| int rtindex; |
| int varattno; |
| ListCell *ln; |
| ListCell *lt; |
| RangeTblEntry *rte; |
| Assert(pnsi->p_rte); |
| rte = pnsi->p_rte; |
| |
| /* right now this is only for subqueries */ |
| Assert(rte->rtekind == RTE_SUBQUERY); |
| |
| rtindex = pnsi->p_rtindex; |
| |
| varattno = 1; |
| ln = list_head(rte->eref->colnames); |
| foreach(lt, rte->subquery->targetList) |
| { |
| TargetEntry *te = lfirst(lt); |
| Var *varnode; |
| char *resname; |
| TargetEntry *tmp; |
| |
| if (te->resjunk) |
| { |
| continue; |
| } |
| |
| Assert(varattno == te->resno); |
| |
| /* no transform here, just use `te->expr` */ |
| varnode = makeVar(rtindex, varattno, exprType((Node *) te->expr), |
| exprTypmod((Node *) te->expr), |
| exprCollation((Node *) te->expr), 0); |
| |
| resname = strVal(lfirst(ln)); |
| |
| tmp = makeTargetEntry((Expr *)varnode, |
| (AttrNumber)pstate->p_next_resno++, resname, |
| false); |
| targetlist = lappend(targetlist, tmp); |
| |
| varattno++; |
| ln = lnext(rte->eref->colnames, ln); |
| } |
| |
| return targetlist; |
| } |
| |
| /* |
| * Transform a cypher sub pattern. This is put here because it is a sub clause. |
| * This works in tandem with transform_Sublink in cypher_expr.c |
| */ |
| static Query *transform_cypher_sub_pattern(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| cypher_match *match; |
| cypher_clause *c; |
| Query *qry; |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_sub_pattern *subpat = (cypher_sub_pattern*)clause->self; |
| ParseNamespaceItem *pnsi; |
| cypher_parsestate *child_parse_state = make_cypher_parsestate(cpstate); |
| ParseState *p_child_parse_state = (ParseState *) child_parse_state; |
| p_child_parse_state->p_expr_kind = pstate->p_expr_kind; |
| |
| /* create a cypher match node and assign it the sub pattern */ |
| match = make_ag_node(cypher_match); |
| match->pattern = subpat->pattern; |
| match->where = NULL; |
| /* wrap it in a clause */ |
| c = palloc(sizeof(cypher_clause)); |
| c->self = (Node *)match; |
| c->prev = NULL; |
| c->next = NULL; |
| |
| /* set up a select query and run it as a sub query to the parent match */ |
| qry = makeNode(Query); |
| qry->commandType = CMD_SELECT; |
| |
| pnsi = transform_cypher_clause_as_subquery(child_parse_state, |
| transform_cypher_clause, c, |
| NULL, true); |
| |
| qry->targetList = makeTargetListFromPNSItem(p_child_parse_state, pnsi); |
| |
| markTargetListOrigins(p_child_parse_state, qry->targetList); |
| |
| qry->rtable = p_child_parse_state->p_rtable; |
| qry->rteperminfos = p_child_parse_state->p_rteperminfos; |
| qry->jointree = makeFromExpr(p_child_parse_state->p_joinlist, NULL); |
| |
| /* the state will be destroyed so copy the data we need */ |
| qry->hasSubLinks = p_child_parse_state->p_hasSubLinks; |
| qry->hasTargetSRFs = p_child_parse_state->p_hasTargetSRFs; |
| qry->hasAggs = p_child_parse_state->p_hasAggs; |
| |
| if (qry->hasAggs) |
| { |
| parse_check_aggregates(p_child_parse_state, qry); |
| } |
| |
| assign_query_collations(p_child_parse_state, qry); |
| |
| free_cypher_parsestate(child_parse_state); |
| |
| return qry; |
| } |
| |
| /* |
| * Code borrowed and inspired by PG's transformFromClauseItem. This function |
| * will transform the VLE function, depending on type. Currently, only |
| * RangeFunctions are supported. But, others may be in the future. |
| */ |
| static Node *transform_VLE_Function(cypher_parsestate *cpstate, Node *n, |
| RangeTblEntry **top_rte, int *top_rti, |
| List **namespace) |
| { |
| ParseState *pstate = &cpstate->pstate; |
| |
| /* we only care about RangeFunctions at this time */ |
| Assert(IsA(n, RangeFunction)); |
| |
| if (IsA(n, RangeFunction)) |
| { |
| /* function is like a plain relation */ |
| RangeTblRef *rtr; |
| RangeTblEntry *rte; |
| ParseNamespaceItem *nsitem; |
| int rtindex; |
| |
| nsitem = transform_RangeFunction(cpstate, (RangeFunction *) n); |
| rte = nsitem->p_rte; |
| /* assume new rte is at end */ |
| rtindex = list_length(pstate->p_rtable); |
| Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); |
| *top_rte = rte; |
| *top_rti = rtindex; |
| *namespace = list_make1(nsitem); |
| rtr = makeNode(RangeTblRef); |
| rtr->rtindex = rtindex; |
| return (Node *) rtr; |
| } |
| |
| /* if it isn't a RangeFunction, return NULL */ |
| return NULL; |
| } |
| |
| /* |
| * static function borrowed from PG. |
| * |
| * setNamespaceLateralState - |
| * Convenience subroutine to update LATERAL flags in a namespace list. |
| */ |
| static void setNamespaceLateralState(List *namespace, bool lateral_only, |
| bool lateral_ok) |
| { |
| ListCell *lc; |
| |
| foreach(lc, namespace) |
| { |
| ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc); |
| |
| nsitem->p_lateral_only = lateral_only; |
| nsitem->p_lateral_ok = lateral_ok; |
| } |
| } |
| |
| /* |
| * Code borrowed and inspired by PG's transformFromClauseItem. Static function |
| * to add in the VLE function as a FROM clause entry. |
| */ |
| static ParseNamespaceItem *append_VLE_Func_to_FromClause(cypher_parsestate *cpstate, |
| Node *n) |
| { |
| ParseState *pstate = &cpstate->pstate; |
| RangeTblEntry *rte = NULL; |
| List *namespace = NULL; |
| int rtindex; |
| |
| /* |
| * Following PG's FROM clause logic, just in case we need to expand it in |
| * the future, we process the items in another function. |
| */ |
| n = transform_VLE_Function(cpstate, n, &rte, &rtindex, &namespace); |
| |
| /* this should not happen */ |
| Assert(n != NULL); |
| |
| /* verify there aren't any conflicts */ |
| checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); |
| |
| /* mark the new namespace items as visible only to LATERAL */ |
| setNamespaceLateralState(namespace, true, true); |
| |
| /* add the entry to the joinlist and namespace */ |
| pstate->p_joinlist = lappend(pstate->p_joinlist, n); |
| pstate->p_namespace = list_concat(pstate->p_namespace, namespace); |
| |
| /* make all namespace items unconditionally visible */ |
| setNamespaceLateralState(pstate->p_namespace, false, true); |
| |
| return lfirst(list_head(namespace)); |
| } |
| |
| /* |
| * Code borrowed from PG's transformRangeFunction |
| * |
| * --- transform a function call appearing in FROM |
| */ |
| static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, |
| RangeFunction *r) |
| { |
| ParseState *pstate = NULL; |
| List *funcexprs = NIL; |
| List *funcnames = NIL; |
| List *coldeflists = NIL; |
| bool is_lateral = false; |
| ListCell *lc = NULL; |
| ParseNamespaceItem *pnsi; |
| |
| pstate = &cpstate->pstate; |
| |
| Assert(!pstate->p_lateral_active); |
| pstate->p_lateral_active = true; |
| |
| /* transform the raw expressions */ |
| foreach(lc, r->functions) |
| { |
| List *pair = (List*)lfirst(lc); |
| Node *fexpr; |
| List *coldeflist; |
| Node *newfexpr; |
| Node *last_srf; |
| |
| /* Disassemble the function-call/column-def-list pairs */ |
| Assert(list_length(pair) == 2); |
| fexpr = (Node*) linitial(pair); |
| coldeflist = (List*) lsecond(pair); |
| |
| /* normal case ... */ |
| last_srf = pstate->p_last_srf; |
| |
| /* transform the function expression */ |
| newfexpr = transform_cypher_expr(cpstate, fexpr, |
| EXPR_KIND_FROM_FUNCTION); |
| |
| /* nodeFunctionscan.c requires SRFs to be at top level */ |
| if (pstate->p_last_srf != last_srf && |
| pstate->p_last_srf != newfexpr) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("set-returning functions must appear at top level of FROM"), |
| parser_errposition(pstate, |
| exprLocation(pstate->p_last_srf)))); |
| } |
| |
| funcexprs = lappend(funcexprs, newfexpr); |
| funcnames = lappend(funcnames, FigureColname(fexpr)); |
| |
| if (coldeflist && r->coldeflist) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("multiple column definition lists are not allowed for the same function"), |
| parser_errposition(pstate, |
| exprLocation((Node *) r->coldeflist)))); |
| } |
| |
| coldeflists = lappend(coldeflists, coldeflist); |
| } |
| |
| pstate->p_lateral_active = false; |
| |
| /* |
| * We must assign collations now so that the RTE exposes correct collation |
| * info for Vars created from it. |
| */ |
| assign_list_collations(pstate, funcexprs); |
| |
| /* currently this is not used by the VLE */ |
| Assert(r->coldeflist == NULL); |
| |
| /* mark the RTE as LATERAL */ |
| is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0); |
| |
| /* build an RTE for the function */ |
| pnsi = addRangeTableEntryForFunction(pstate, funcnames, funcexprs, |
| coldeflists, r, is_lateral, |
| true); |
| |
| return pnsi; |
| } |
| |
| static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, |
| List *pattern, Node *where) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| ListCell *lc; |
| List *quals = NIL; |
| Expr *q = NULL; |
| Expr *expr = NULL; |
| |
| /* |
| * Loop through a comma separated list of paths like (u)-[e]-(v), (w), (x) |
| */ |
| foreach (lc, pattern) |
| { |
| List *qual = NULL; |
| cypher_path *path = NULL; |
| |
| /* get the path and transform it */ |
| path = (cypher_path *) lfirst(lc); |
| |
| qual = transform_match_path(cpstate, query, path); |
| |
| quals = list_concat(quals, qual); |
| } |
| |
| if (quals != NIL) |
| { |
| q = makeBoolExpr(AND_EXPR, quals, -1); |
| expr = (Expr *)transformExpr(&cpstate->pstate, (Node *)q, |
| EXPR_KIND_WHERE); |
| } |
| |
| if (cpstate->property_constraint_quals != NIL) |
| { |
| Expr *prop_qual = makeBoolExpr(AND_EXPR, |
| cpstate->property_constraint_quals, -1); |
| |
| if (expr == NULL) |
| { |
| expr = prop_qual; |
| } |
| else |
| { |
| expr = makeBoolExpr(AND_EXPR, list_make2(expr, prop_qual), -1); |
| } |
| } |
| |
| // transform the where clause quals and add to the quals, |
| if (where != NULL) |
| { |
| Expr *where_qual; |
| |
| where_qual = (Expr *)transform_cypher_expr(cpstate, where, |
| EXPR_KIND_WHERE); |
| if (expr == NULL) |
| { |
| expr = where_qual; |
| } |
| else |
| { |
| /* |
| * coerce the WHERE clause to a boolean before AND with the property |
| * constraints, otherwise there could be evaluation issues. |
| */ |
| where_qual = (Expr *)coerce_to_boolean(pstate, (Node *)where_qual, |
| "WHERE"); |
| |
| expr = makeBoolExpr(AND_EXPR, list_make2(expr, where_qual), -1); |
| } |
| } |
| |
| /* |
| * Coerce to WHERE clause to a bool, denoting whether the constructed |
| * clause is true or false. |
| */ |
| if (expr != NULL) |
| { |
| expr = (Expr *)coerce_to_boolean(pstate, (Node *)expr, "WHERE"); |
| } |
| |
| query->rtable = cpstate->pstate.p_rtable; |
| query->rteperminfos = cpstate->pstate.p_rteperminfos; |
| query->jointree = makeFromExpr(cpstate->pstate.p_joinlist, (Node *)expr); |
| } |
| |
| /* |
| * Creates a FuncCall node that will prevent an edge from being joined |
| * to twice. |
| */ |
| static FuncCall *prevent_duplicate_edges(cypher_parsestate *cpstate, |
| List *entities) |
| { |
| List *edges = NIL; |
| ListCell *lc; |
| List *qualified_function_name; |
| String *ag_catalog, *edge_fn; |
| |
| ag_catalog = makeString("ag_catalog"); |
| edge_fn = makeString("_ag_enforce_edge_uniqueness"); |
| |
| qualified_function_name = list_make2(ag_catalog, edge_fn); |
| |
| // iterate through each entity, collecting the access node for each edge |
| foreach (lc, entities) |
| { |
| transform_entity *entity = lfirst(lc); |
| Node *edge; |
| |
| if (entity->type == ENT_EDGE) |
| { |
| edge = make_qual(cpstate, entity, AG_EDGE_COLNAME_ID); |
| |
| edges = lappend(edges, edge); |
| } |
| else if (entity->type == ENT_VLE_EDGE) |
| { |
| edges = lappend(edges, entity->expr); |
| } |
| } |
| |
| return makeFuncCall(qualified_function_name, edges, COERCE_SQL_SYNTAX, -1); |
| } |
| |
| /* |
| * For any given edge, the previous entity is joined with the edge |
| * via the prev_qual node, and the next entity is join with the |
| * next_qual node. If there is a filter on the previous vertex label, |
| * create a filter, same with the next node. |
| */ |
| static List *make_directed_edge_join_conditions( |
| cypher_parsestate *cpstate, transform_entity *prev_entity, |
| transform_entity *next_entity, Node *prev_qual, Node *next_qual, |
| char *prev_node_filter, char *next_node_filter) |
| { |
| List *quals = NIL; |
| |
| if (prev_entity->in_join_tree) |
| { |
| quals = list_concat(quals, join_to_entity(cpstate, prev_entity, |
| prev_qual, JOIN_SIDE_LEFT)); |
| } |
| |
| if (next_entity->in_join_tree && next_entity->type != ENT_VLE_EDGE) |
| { |
| quals = list_concat(quals, join_to_entity(cpstate, next_entity, |
| next_qual, JOIN_SIDE_RIGHT)); |
| } |
| |
| if (prev_node_filter != NULL && !IS_DEFAULT_LABEL_VERTEX(prev_node_filter)) |
| { |
| A_Expr *qual; |
| qual = filter_vertices_on_label_id(cpstate, prev_qual, |
| prev_node_filter); |
| |
| quals = lappend(quals, qual); |
| } |
| |
| if (next_node_filter != NULL && !IS_DEFAULT_LABEL_VERTEX(next_node_filter)) |
| { |
| A_Expr *qual; |
| qual = filter_vertices_on_label_id(cpstate, next_qual, |
| next_node_filter); |
| |
| quals = lappend(quals, qual); |
| } |
| |
| return quals; |
| } |
| |
| /* |
| * The joins are driven by edges. Under specific conditions, it becomes |
| * necessary to have knowledge about the previous edge and vertex and |
| * the next vertex and edge. |
| * |
| * [prev_edge]-(prev_node)-[edge]-(next_node)-[next_edge] |
| * |
| * prev_edge and next_edge are allowed to be null. |
| * prev_node and next_node are not allowed to be null. |
| */ |
| static List *make_join_condition_for_edge(cypher_parsestate *cpstate, |
| transform_entity *prev_edge, |
| transform_entity *prev_node, |
| transform_entity *entity, |
| transform_entity *next_node, |
| transform_entity *next_edge) |
| { |
| char *next_label_name_to_filter = NULL; |
| char *prev_label_name_to_filter = NULL; |
| transform_entity *next_entity; |
| transform_entity *prev_entity; |
| |
| /* |
| * When this edge is a vle edge, pass the prev and next |
| * node to the match_vle_terminal_edge function to process |
| * which rows match. |
| */ |
| if (entity->type == ENT_VLE_EDGE) |
| { |
| Node *left_id = NULL; |
| Node *right_id = NULL; |
| String *ag_catalog = makeString("ag_catalog"); |
| String *func_name; |
| List *qualified_func_name; |
| List *args = NIL; |
| List *quals = NIL; |
| |
| /* |
| * If the next node is not in the join tree, we don't need to make any |
| * quals. |
| */ |
| if (!next_node->in_join_tree) |
| { |
| return NIL; |
| } |
| |
| /* |
| * If the previous node and the next node are in the join tree, we need |
| * to create the age_match_vle_terminal_edge to compare the vle returned |
| * results against the two nodes. |
| */ |
| if (prev_node->in_join_tree) |
| { |
| func_name = makeString("age_match_vle_terminal_edge"); |
| qualified_func_name = list_make2(ag_catalog, func_name); |
| |
| /* |
| * Get the vertex's id and pass to the function. Pass in NULL |
| * otherwise. |
| */ |
| left_id = (Node *)make_qual(cpstate, prev_node, "id"); |
| right_id = (Node *)make_qual(cpstate, next_node, "id"); |
| |
| // create the argument list |
| args = list_make3(left_id, right_id, entity->expr); |
| |
| // add to quals |
| quals = lappend(quals, makeFuncCall(qualified_func_name, args, |
| COERCE_EXPLICIT_CALL, -1)); |
| } |
| |
| /* |
| * When the previous node is not in the join tree, but there is a vle |
| * edge before that join, then we need to compare this vle's start node |
| * against the previous vle's end node. No need to check the next edge, |
| * because that would be redundant. |
| */ |
| if (!prev_node->in_join_tree && |
| prev_edge != NULL && |
| prev_edge->type == ENT_VLE_EDGE) |
| { |
| List *qualified_name; |
| String *match_qual; |
| FuncCall *fc; |
| |
| match_qual = makeString("age_match_two_vle_edges"); |
| |
| // make the qualified function name |
| qualified_name = list_make2(ag_catalog, match_qual); |
| |
| // make the args |
| args = list_make2(prev_edge->expr, entity->expr); |
| |
| // create the function call |
| fc = makeFuncCall(qualified_name, args, COERCE_EXPLICIT_CALL, -1); |
| |
| quals = lappend(quals, fc); |
| } |
| |
| return quals; |
| } |
| |
| /* |
| * If the previous node is not in the join tree, set the previous |
| * label filter. |
| */ |
| if (!prev_node->in_join_tree) |
| { |
| prev_label_name_to_filter = prev_node->entity.node->label; |
| } |
| |
| /* |
| * If the next node is not in the join tree and there is not |
| * another edge, set the label filter. When there is another |
| * edge, we don't need to set it, because that edge will set the |
| * filter for that node. |
| */ |
| if (!next_node->in_join_tree && next_edge == NULL) |
| { |
| next_label_name_to_filter = next_node->entity.node->label; |
| } |
| |
| /* |
| * When the previous node is not in the join tree, and there |
| * is a previous edge, set the previous entity to that edge. |
| * Otherwise, use the previous node/ |
| */ |
| if (!prev_node->in_join_tree && prev_edge != NULL) |
| { |
| prev_entity = prev_edge; |
| } |
| else |
| { |
| prev_entity = prev_node; |
| } |
| |
| /* |
| * When the next node is not in the join tree, and there |
| * is a next edge, set the next entity to that edge. |
| * Otherwise, use the next node. |
| */ |
| if (!next_node->in_join_tree && next_edge != NULL) |
| { |
| next_entity = next_edge; |
| } |
| else |
| { |
| next_entity = next_node; |
| } |
| |
| switch (entity->entity.rel->dir) |
| { |
| case CYPHER_REL_DIR_RIGHT: |
| { |
| Node *prev_qual = make_qual(cpstate, entity, |
| AG_EDGE_COLNAME_START_ID); |
| Node *next_qual = make_qual(cpstate, entity, |
| AG_EDGE_COLNAME_END_ID); |
| |
| return make_directed_edge_join_conditions(cpstate, prev_entity, |
| next_node, prev_qual, |
| next_qual, |
| prev_label_name_to_filter, |
| next_label_name_to_filter); |
| } |
| case CYPHER_REL_DIR_LEFT: |
| { |
| Node *prev_qual = make_qual(cpstate, entity, |
| AG_EDGE_COLNAME_END_ID); |
| Node *next_qual = make_qual(cpstate, entity, |
| AG_EDGE_COLNAME_START_ID); |
| |
| return make_directed_edge_join_conditions(cpstate, prev_entity, |
| next_node, prev_qual, |
| next_qual, |
| prev_label_name_to_filter, |
| next_label_name_to_filter); |
| } |
| case CYPHER_REL_DIR_NONE: |
| { |
| /* |
| * For undirected relationships, we can use the left directed |
| * relationship OR'd by the right directed relationship. |
| */ |
| Node *start_id_expr = make_qual(cpstate, entity, |
| AG_EDGE_COLNAME_START_ID); |
| Node *end_id_expr = make_qual(cpstate, entity, |
| AG_EDGE_COLNAME_END_ID); |
| List *first_join_quals = NIL, *second_join_quals = NIL; |
| Expr *first_qual, *second_qual; |
| Expr *or_qual; |
| |
| first_join_quals = make_directed_edge_join_conditions(cpstate, |
| prev_entity, |
| next_entity, |
| start_id_expr, |
| end_id_expr, |
| prev_label_name_to_filter, |
| next_label_name_to_filter); |
| |
| second_join_quals = make_directed_edge_join_conditions(cpstate, |
| prev_entity, |
| next_entity, |
| end_id_expr, |
| start_id_expr, |
| prev_label_name_to_filter, |
| next_label_name_to_filter); |
| |
| first_qual = makeBoolExpr(AND_EXPR, first_join_quals, -1); |
| second_qual = makeBoolExpr(AND_EXPR, second_join_quals, -1); |
| |
| or_qual = makeBoolExpr(OR_EXPR, list_make2(first_qual, second_qual), |
| -1); |
| |
| return list_make1(or_qual); |
| } |
| default: |
| return NULL; |
| } |
| } |
| |
| // creates a type cast node to agtype |
| static Node *make_type_cast_to_agtype(Node *arg) |
| { |
| TypeCast *n = makeNode(TypeCast); |
| String *ag_catalog = makeString("ag_catalog"); |
| String *agtype_str = makeString("agtype"); |
| List *qualified_name = list_make2(ag_catalog, agtype_str); |
| |
| n->arg = arg; |
| n->typeName = makeTypeNameFromNameList(qualified_name); |
| n->location = -1; |
| return (Node *) n; |
| } |
| |
| /* |
| * Makes an agtype bool node that Postgres' transform expression logic |
| * can handle. Used when constructed the join quals for building the paths |
| */ |
| static Node *make_bool_a_const(bool state) |
| { |
| A_Const *n = makeNode(A_Const); |
| |
| n->val.sval.type = T_String; |
| n->val.sval.sval = (state ? "true" : "false"); |
| n->location = -1; |
| |
| // typecast to agtype |
| return make_type_cast_to_agtype((Node *)n); |
| } |
| |
| /* |
| * For the given entity, join it to the current edge, via the passed |
| * qual node. The side denotes if the entity is on the right |
| * or left of the current edge. Which we will need to know if the |
| * passed entity is a directed edge. |
| */ |
| static List *join_to_entity(cypher_parsestate *cpstate, |
| transform_entity *entity, Node *qual, |
| enum transform_entity_join_side side) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| A_Expr *expr; |
| List *quals = NIL; |
| |
| if (entity->type == ENT_VERTEX) |
| { |
| Node *id_qual = make_qual(cpstate, entity, AG_EDGE_COLNAME_ID); |
| |
| expr = makeSimpleA_Expr(AEXPR_OP, "=", qual, (Node *)id_qual, -1); |
| |
| quals = lappend(quals, expr); |
| } |
| else if (entity->type == ENT_EDGE) |
| { |
| List *edge_quals = make_edge_quals(cpstate, entity, side); |
| |
| if (list_length(edge_quals) > 1) |
| { |
| expr = makeSimpleA_Expr(AEXPR_IN, "=", qual, |
| (Node *)edge_quals, -1); |
| } |
| else |
| { |
| expr = makeSimpleA_Expr(AEXPR_OP, "=", qual, |
| linitial(edge_quals), -1); |
| } |
| |
| quals = lappend(quals, expr); |
| } |
| else if (entity->type == ENT_VLE_EDGE) |
| { |
| List *qualified_name, *args; |
| String *ag_catalog, *match_qual; |
| bool is_left_side; |
| FuncCall *fc; |
| |
| ag_catalog = makeString("ag_catalog"); |
| match_qual = makeString("age_match_vle_edge_to_id_qual"); |
| |
| /* |
| * tells the function the location of the vle relative to the |
| * edge we are joining it against. |
| */ |
| if (side == JOIN_SIDE_LEFT) |
| { |
| // [vle_edge]-()-[regular_edge] |
| is_left_side = true; |
| } |
| else if (side == JOIN_SIDE_RIGHT) |
| { |
| // [edge]-()-[vle_edge] |
| is_left_side = false; |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("unknown join side found"), |
| parser_errposition(pstate, entity->entity.rel->location))); |
| } |
| |
| // make the qualified function name |
| qualified_name = list_make2(ag_catalog, match_qual); |
| |
| // make the args |
| args = list_make3(entity->expr, qual, make_bool_a_const(is_left_side)); |
| |
| // create the function call |
| fc = makeFuncCall(qualified_name, args, COERCE_EXPLICIT_CALL, -1); |
| |
| quals = lappend(quals, fc); |
| |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("unknown entity type to join to"))); |
| } |
| |
| return quals; |
| } |
| |
| // makes the quals necessary when an edge is joining to another edge. |
| static List *make_edge_quals(cypher_parsestate *cpstate, |
| transform_entity *edge, |
| enum transform_entity_join_side side) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| char *left_dir; |
| char *right_dir; |
| |
| Assert(edge->type == ENT_EDGE); |
| |
| /* |
| * When the rel is on the left side in a pattern, then a left directed path |
| * is concerned with the start id and a right directed path is concerned |
| * with the end id. When the rel is on the right side of a pattern, the |
| * above statement is inverted. |
| */ |
| switch (side) |
| { |
| case JOIN_SIDE_LEFT: |
| { |
| left_dir = AG_EDGE_COLNAME_START_ID; |
| right_dir = AG_EDGE_COLNAME_END_ID; |
| break; |
| } |
| case JOIN_SIDE_RIGHT: |
| { |
| left_dir = AG_EDGE_COLNAME_END_ID; |
| right_dir = AG_EDGE_COLNAME_START_ID; |
| break; |
| } |
| default: |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("unknown join type found"), |
| parser_errposition(pstate, edge->entity.rel->location))); |
| } |
| |
| switch (edge->entity.rel->dir) |
| { |
| case CYPHER_REL_DIR_LEFT: |
| { |
| return list_make1(make_qual(cpstate, edge, left_dir)); |
| } |
| case CYPHER_REL_DIR_RIGHT: |
| { |
| return list_make1(make_qual(cpstate, edge, right_dir)); |
| } |
| case CYPHER_REL_DIR_NONE: |
| { |
| return list_make2(make_qual(cpstate, edge, left_dir), |
| make_qual(cpstate, edge, right_dir)); |
| } |
| default: |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Unknown relationship direction"))); |
| } |
| return NIL; |
| } |
| |
| /* |
| * Creates a node that will create a filter on the passed field node |
| * that removes all labels that do not have the same label_id |
| */ |
| static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, |
| Node *id_field, char *label) |
| { |
| label_cache_data *lcd = search_label_name_graph_cache(label, |
| cpstate->graph_oid); |
| A_Const *n; |
| FuncCall *fc; |
| String *ag_catalog, *extract_label_id; |
| int32 label_id = lcd->id; |
| |
| n = makeNode(A_Const); |
| n->val.ival.type = T_Integer; |
| n->val.ival.ival = label_id; |
| n->location = -1; |
| |
| ag_catalog = makeString("ag_catalog"); |
| extract_label_id = makeString("_extract_label_id"); |
| |
| fc = makeFuncCall(list_make2(ag_catalog, extract_label_id), |
| list_make1(id_field), COERCE_EXPLICIT_CALL, -1); |
| |
| return makeSimpleA_Expr(AEXPR_OP, "=", (Node *)fc, (Node *)n, -1); |
| } |
| |
| /* |
| * Makes property constraint using indirection(s). This is an |
| * alternative to using the containment operator (@>). |
| * |
| * In case of array and empty map, containment is used instead of equality. |
| * |
| * For example, the following query |
| * |
| * MATCH (x:Label{ |
| * name: 'xyz', |
| * address: { |
| * city: 'abc', |
| * street: { |
| * name: 'pqr', |
| * number: 123 |
| * } |
| * }, |
| * phone: [9, 8, 7], |
| * parents: {} |
| * }) |
| * |
| * is transformed to- |
| * |
| * x.name = 'xyz' AND |
| * x.address.city = 'abc' AND |
| * x.address.street.name = 'pqr' AND |
| * x.address.street.number = 123 AND |
| * x.phone @> [6, 4, 3] AND |
| * x.parents @> {} |
| */ |
| static Node *transform_map_to_ind(cypher_parsestate *cpstate, |
| transform_entity *entity, cypher_map *map) |
| { |
| List *quals; // list of equality and/or containment qual node |
| |
| quals = transform_map_to_ind_recursive(cpstate, entity, map, NIL); |
| Assert(quals != NIL); |
| |
| if (list_length(quals) > 1) |
| { |
| return (Node *)makeBoolExpr(AND_EXPR, quals, -1); |
| } |
| else |
| { |
| return (Node *)linitial(quals); |
| } |
| } |
| |
| /* |
| * Helper function of `transform_map_to_ind`. |
| * |
| * This function is called when a value of the `map` is a non-empty map. |
| * For example, the key `address.street` has a non-empty map. The |
| * `parent_fields` parameter will be set to the list of parents of the |
| * key `street` in order. In this case, only `address`. If no parent |
| * fields, set it to NIL. |
| */ |
| static List *transform_map_to_ind_recursive(cypher_parsestate *cpstate, |
| transform_entity *entity, |
| cypher_map *map, |
| List *parent_fields) |
| { |
| int i; |
| ParseState *pstate; |
| Node *last_srf; |
| List *quals; |
| |
| pstate = (ParseState *)cpstate; |
| last_srf = pstate->p_last_srf; |
| quals = NIL; |
| |
| /* since this function recurses, it could be driven to stack overflow */ |
| check_stack_depth(); |
| |
| Assert(list_length(map->keyvals) != 0); |
| |
| for (i = 0; i < map->keyvals->length; i += 2) |
| { |
| Node *key; |
| Node *val; |
| char *keystr; |
| |
| key = (Node *)map->keyvals->elements[i].ptr_value; |
| val = (Node *)map->keyvals->elements[i + 1].ptr_value; |
| Assert(IsA(key, String)); |
| keystr = ((String *)key)->sval; |
| |
| if (is_ag_node(val, cypher_map) && |
| list_length(((cypher_map *)val)->keyvals) != 0) |
| { |
| List *new_parent_fields; |
| List *recursive_quals; |
| |
| new_parent_fields = lappend(list_copy(parent_fields), |
| makeString(keystr)); |
| |
| recursive_quals = transform_map_to_ind_recursive( |
| cpstate, entity, (cypher_map *)val, new_parent_fields); |
| |
| quals = list_concat(quals, recursive_quals); |
| |
| list_free(new_parent_fields); |
| list_free(recursive_quals); |
| } |
| else |
| { |
| Node *qual; |
| Node *lhs; |
| Node *rhs; |
| List *op; |
| A_Indirection *indir; |
| ColumnRef *variable; |
| |
| /* |
| * Lists and empty maps are transformed to containment. If a map |
| * makes it here, then it must be empty. Because non-empty maps |
| * are processed in the upper if-block. |
| */ |
| if (is_ag_node(val, cypher_list) || is_ag_node(val, cypher_map)) |
| { |
| op = list_make1(makeString("@>")); |
| } |
| else |
| { |
| op = list_make1(makeString("=")); |
| } |
| |
| variable = makeNode(ColumnRef); |
| variable->fields = |
| list_make1(makeString(entity->entity.node->name)); |
| variable->location = -1; |
| |
| indir = makeNode(A_Indirection); |
| indir->arg = (Node *)variable; |
| indir->indirection = lappend(list_copy(parent_fields), |
| makeString(keystr)); |
| |
| lhs = transform_cypher_expr(cpstate, (Node *)indir, |
| EXPR_KIND_WHERE); |
| rhs = transform_cypher_expr(cpstate, val, EXPR_KIND_WHERE); |
| |
| qual = (Node *)make_op(pstate, op, lhs, rhs, last_srf, -1); |
| quals = lappend(quals, qual); |
| } |
| } |
| |
| return quals; |
| } |
| |
| /* |
| * Creates the property constraints for a vertex/edge in a MATCH clause. |
| */ |
| static Node *create_property_constraints(cypher_parsestate *cpstate, |
| transform_entity *entity, |
| Node *property_constraints, |
| Node *prop_expr) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| char *entity_name; |
| Node *const_expr; |
| Node *last_srf = pstate->p_last_srf; |
| ParseNamespaceItem *pnsi; |
| |
| /* |
| * If the prop_expr node wasn't passed in, create it. Otherwise, skip |
| * the creation step. |
| */ |
| if (prop_expr == NULL) |
| { |
| ColumnRef *cr = NULL; |
| |
| cr = makeNode(ColumnRef); |
| entity_name = get_entity_name(entity); |
| cr->fields = list_make2(makeString(entity_name), |
| makeString("properties")); |
| |
| /* use Postgres to get the properties' transform node */ |
| pnsi = find_pnsi(cpstate, entity_name); |
| if (pnsi != NULL) |
| { |
| prop_expr = scanNSItemForColumn(pstate, pnsi, 0, |
| AG_VERTEX_COLNAME_PROPERTIES, -1); |
| } |
| else |
| { |
| prop_expr = transformExpr(pstate, (Node *)cr, EXPR_KIND_WHERE); |
| } |
| } |
| |
| /* use cypher to get the constraints' transform node */ |
| const_expr = transform_cypher_expr(cpstate, property_constraints, |
| EXPR_KIND_WHERE); |
| |
| if (age_enable_containment) |
| { |
| return (Node *)make_op(pstate, list_make1(makeString("@>")), prop_expr, |
| const_expr, last_srf, -1); |
| } |
| else |
| { |
| return (Node *)transform_map_to_ind( |
| cpstate, entity, (cypher_map *)property_constraints); |
| } |
| } |
| |
| /* |
| * For the given path, transform each entity within the path, create |
| * the path variable if needed, and construct the quals to enforce the |
| * correct join tree, and enforce edge uniqueness. |
| */ |
| static List *transform_match_path(cypher_parsestate *cpstate, Query *query, |
| cypher_path *path) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| List *qual = NIL; |
| List *entities = NIL; |
| FuncCall *duplicate_edge_qual; |
| List *join_quals; |
| |
| // transform the entities in the path |
| entities = transform_match_entities(cpstate, query, path); |
| |
| // create the path variable, if needed. |
| if (path->var_name != NULL) |
| { |
| TargetEntry *path_te; |
| |
| if (findTarget(query->targetList, path->var_name) != NULL) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable \"%s\" already exists", |
| path->var_name), |
| parser_errposition(pstate, path->location))); |
| } |
| |
| path_te = transform_match_create_path_variable(cpstate, path, |
| entities); |
| query->targetList = lappend(query->targetList, path_te); |
| } |
| |
| // construct the quals for the join tree |
| join_quals = make_path_join_quals(cpstate, entities); |
| qual = list_concat(qual, join_quals); |
| |
| // construct the qual to prevent duplicate edges |
| if (list_length(entities) > 3) |
| { |
| duplicate_edge_qual = prevent_duplicate_edges(cpstate, entities); |
| qual = lappend(qual, duplicate_edge_qual); |
| } |
| |
| return qual; |
| } |
| |
| static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate, |
| cypher_relationship *rel, |
| Query *query) |
| { |
| ParseState *pstate = NULL; |
| TargetEntry *te = NULL; |
| RangeFunction *rf = NULL; |
| FuncCall *func = NULL; |
| Alias *alias = NULL; |
| Node *var = NULL; |
| transform_entity *vle_entity = NULL; |
| ParseNamespaceItem *pnsi; |
| |
| /* it better be a function call node */ |
| Assert(IsA(rel->varlen, FuncCall)); |
| |
| /* get the function */ |
| func = (FuncCall*)rel->varlen; |
| |
| /* only our functions are supported here */ |
| if (list_length(func->funcname) != 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("only AGE functions are supported here"))); |
| } |
| |
| /* set the pstate */ |
| pstate = &cpstate->pstate; |
| |
| /* make a RangeFunction node */ |
| rf = makeNode(RangeFunction); |
| rf->lateral = false; |
| rf->ordinality = false; |
| rf->is_rowsfrom = false; |
| rf->functions = list_make1(list_make2(rel->varlen, NIL)); |
| |
| /* |
| * Build an alias for the RangeFunction. This is needed so we |
| * can chain VLEs together |
| */ |
| alias = makeNode(Alias); |
| alias->aliasname = get_next_default_alias(cpstate); |
| alias->colnames = NIL; |
| rf->alias = alias; |
| |
| /* |
| * Add the RangeFunction to the FROM clause |
| */ |
| pnsi = append_VLE_Func_to_FromClause(cpstate, (Node*)rf); |
| Assert(pnsi != NULL); |
| |
| /* Get the var node for the VLE functions column name. */ |
| var = scanNSItemForColumn(pstate, pnsi, 0, "edges", -1); |
| Assert(var != NULL); |
| |
| /* |
| * If we have a variable name (rel name), make the target entry. Otherwise, |
| * there isn't a reason to create one. Additionally, verify that it is not |
| * reused. |
| */ |
| if (rel->name != NULL) |
| { |
| FuncExpr *fexpr; |
| List *args = list_make1(var); |
| Oid func_oid = InvalidOid; |
| transform_entity *entity = NULL; |
| |
| te = findTarget(query->targetList, rel->name); |
| entity = find_variable(cpstate, rel->name); |
| |
| /* If the variable already exists, error out */ |
| if (te && entity) |
| { |
| if (entity->type == ENT_VERTEX) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for a vertex", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| else if (entity->type == ENT_EDGE) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for an edge", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| else if (entity->type == ENT_PATH) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for a path", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("duplicate variable '%s'", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| } |
| else if (te && !entity) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' already exists", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| |
| /* |
| * Get the oid for the materialize function that returns a list of |
| * edges. For a VLE edge variable we need to return a list of edges, |
| * not a path. |
| */ |
| func_oid = get_ag_func_oid("age_materialize_vle_edges", 1, AGTYPEOID); |
| |
| /* build the expr node for the function */ |
| fexpr = makeFuncExpr(func_oid, AGTYPEOID, args, InvalidOid, InvalidOid, |
| COERCE_EXPLICIT_CALL); |
| |
| /* make the target entry and apply the provided variable */ |
| te = makeTargetEntry((Expr*)fexpr, pstate->p_next_resno++, rel->name, |
| false); |
| /* add it to the query */ |
| query->targetList = lappend(query->targetList, te); |
| } |
| |
| /* Make a transform entity for the vle. */ |
| vle_entity = make_transform_entity(cpstate, ENT_VLE_EDGE, (Node *)rel, |
| (Expr *)var); |
| |
| /* return the vle entity */ |
| return vle_entity; |
| } |
| |
| /* helper function to check for specific VLE cases */ |
| static bool isa_special_VLE_case(cypher_path *path) |
| { |
| cypher_relationship *cr = NULL; |
| |
| if (path->var_name == NULL) |
| { |
| return false; |
| } |
| |
| if (list_length(path->path) != 3) |
| { |
| return false; |
| } |
| |
| cr = (cypher_relationship*)lfirst(lnext(path->path, list_head(path->path))); |
| |
| if (cr->varlen != NULL) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool path_check_valid_label(cypher_path *path, |
| cypher_parsestate *cpstate) |
| { |
| ListCell *lc = NULL; |
| int i = 0; |
| |
| foreach (lc, path->path) |
| { |
| if (i % 2 == 0) |
| { |
| cypher_node *node = NULL; |
| |
| node = lfirst(lc); |
| |
| if (node->label) |
| { |
| label_cache_data *lcd = |
| search_label_name_graph_cache(node->label, |
| cpstate->graph_oid); |
| |
| if (lcd == NULL || lcd->kind != LABEL_KIND_VERTEX) |
| { |
| return false; |
| } |
| } |
| } |
| else |
| { |
| cypher_relationship *rel = NULL; |
| |
| rel = lfirst(lc); |
| |
| if (rel->label) |
| { |
| label_cache_data *lcd = |
| search_label_name_graph_cache(rel->label, |
| cpstate->graph_oid); |
| |
| if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) |
| { |
| return false; |
| } |
| } |
| } |
| i++; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Iterate through the path and construct all edges and necessary vertices |
| */ |
| static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, |
| cypher_path *path) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| ListCell *lc = NULL; |
| List *entities = NIL; |
| int i = 0; |
| bool node_declared_in_prev_clause = false; |
| transform_entity *prev_entity = NULL; |
| bool special_VLE_case = false; |
| bool valid_label = true; |
| |
| special_VLE_case = isa_special_VLE_case(path); |
| valid_label = path_check_valid_label(path, cpstate); |
| |
| /* |
| * Iterate through every node in the path, construct the expr node |
| * that is needed for the remaining steps |
| */ |
| |
| foreach (lc, path->path) |
| { |
| Expr *expr = NULL; |
| transform_entity *entity = NULL; |
| |
| |
| /* even increments of i are vertices */ |
| if (i % 2 == 0) |
| { |
| cypher_node *node = NULL; |
| bool output_node = false; |
| |
| node = lfirst(lc); |
| |
| /* |
| * The vle needs to know if the start vertex was |
| * created in a previous clause. Check to see if it |
| * was so the edge logic can handle changing its argument |
| * if necessary. |
| */ |
| if (node->name != NULL) |
| { |
| Node *expr; |
| |
| if (path->var_name && strcmp(node->name, path->var_name) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable \"%s\" is for a path", |
| node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| |
| /* |
| * Checks the previous clauses to see if the variable already |
| * exists. |
| */ |
| expr = colNameToVar(pstate, node->name, false, |
| node->location); |
| if (expr != NULL) |
| { |
| node_declared_in_prev_clause = true; |
| } |
| } |
| |
| /* should we make the node available */ |
| output_node = (special_VLE_case && !node->name && !node->props) ? |
| false : |
| INCLUDE_NODE_IN_JOIN_TREE(path, node); |
| /* |
| * TODO |
| * |
| * We need to re-evaluate if we want to use output_node or not. |
| * If output_node is set to false, then it basically short circuits |
| * the match for instances where a variable isn't specified. While, |
| * on the surface, this appears to be a good way to improve |
| * execution time of commands that won't do anything, it also |
| * causes chained commands to not work correctly. This is because |
| * a match without a variable will still feed its tuples to the next |
| * stage(s). With this set to false, it won't. So we likely need to |
| * remove all of the output_node logic. This needs to be reviewed, |
| * though. For now, we just set it to true and update the output of |
| * the regression tests. |
| */ |
| output_node = true; |
| |
| /* transform vertex */ |
| expr = transform_cypher_node(cpstate, node, &query->targetList, |
| output_node, valid_label); |
| |
| entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, |
| expr); |
| |
| /* |
| * We want to add transformed entity to entities before transforming props |
| * so that props referencing currently transformed entity can be resolved. |
| */ |
| cpstate->entities = lappend(cpstate->entities, entity); |
| entities = lappend(entities, entity); |
| |
| /* transform the properties if they exist */ |
| if (node->props) |
| { |
| Node *n = NULL; |
| Node *prop_var = NULL; |
| Node *prop_expr = NULL; |
| |
| /* |
| * We need to build a transformed properties(prop_var) |
| * expression IF the properties variable already exists from a |
| * previous clause. Please note that the "found" prop_var was |
| * previously transformed. |
| */ |
| |
| /* get the prop_var if it was previously resolved */ |
| if (node->name != NULL) |
| { |
| prop_var = colNameToVar(pstate, node->name, false, |
| node->location); |
| } |
| |
| /* |
| * If prop_var exists and is an alias, just pass it through by |
| * assigning the prop_expr the prop_var. |
| */ |
| if (prop_var != NULL && |
| pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX, |
| strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) |
| { |
| prop_expr = prop_var; |
| } |
| /* |
| * Else, if it exists and is not an alias, create the prop_expr |
| * as a transformed properties(prop_var) function node. |
| */ |
| else if (prop_var != NULL) |
| { |
| /* |
| * Remember that prop_var is already transformed. We need |
| * to built the transform manually. |
| */ |
| FuncCall *fc = NULL; |
| List *targs = NIL; |
| List *fname = NIL; |
| |
| targs = lappend(targs, prop_var); |
| fname = list_make2(makeString("ag_catalog"), |
| makeString("age_properties")); |
| fc = makeFuncCall(fname, targs, COERCE_SQL_SYNTAX, -1); |
| |
| /* |
| * Hand off to ParseFuncOrColumn to create the function |
| * expression for properties(prop_var) |
| */ |
| prop_expr = ParseFuncOrColumn(pstate, fname, targs, |
| pstate->p_last_srf, fc, false, |
| -1); |
| } |
| |
| ((cypher_map*)node->props)->keep_null = true; |
| n = create_property_constraints(cpstate, entity, node->props, |
| prop_expr); |
| |
| cpstate->property_constraint_quals = |
| lappend(cpstate->property_constraint_quals, n); |
| } |
| |
| prev_entity = entity; |
| } |
| /* odd increments of i are edges */ |
| else |
| { |
| cypher_relationship *rel = NULL; |
| |
| rel = lfirst(lc); |
| |
| if (rel->name && path->var_name && |
| strcmp(rel->name, path->var_name) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable \"%s\" is for a path", |
| rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| |
| /* |
| * There are 2 edge cases - 1) a regular edge and 2) a VLE edge. |
| * A VLE edge is not added like a regular edge - it is a function. |
| */ |
| |
| /* if it is a regular edge */ |
| if (rel->varlen == NULL) |
| { |
| /* |
| * In the case where the MATCH is one edge and two vertices, the |
| * edge is bidirectional, and neither vertex is included in the |
| * join tree, we need to force one of the vertices into the join |
| * tree to ensure the output is generated correctly. |
| */ |
| if (list_length(path->path) == 3 && |
| rel->dir == CYPHER_REL_DIR_NONE && |
| !prev_entity->in_join_tree) |
| { |
| cypher_node *node = (cypher_node *)lfirst(lnext(path->path, lc)); |
| |
| if (!INCLUDE_NODE_IN_JOIN_TREE(path, node)) |
| { |
| /* |
| * Assigning a variable name here will ensure that when |
| * the next vertex is processed, the vertex will be |
| * included in the join tree. |
| */ |
| node->name = get_next_default_alias(cpstate); |
| } |
| } |
| |
| expr = transform_cypher_edge(cpstate, rel, &query->targetList, |
| valid_label); |
| |
| entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)rel, |
| expr); |
| |
| /* |
| * We want to add transformed entity to entities before transforming props |
| * so that props referencing currently transformed entity can be resolved. |
| */ |
| cpstate->entities = lappend(cpstate->entities, entity); |
| entities = lappend(entities, entity); |
| |
| if (rel->props) |
| { |
| Node *r = NULL; |
| Node *prop_var = NULL; |
| Node *prop_expr = NULL; |
| |
| /* |
| * We need to build a transformed properties(prop_var) |
| * expression IF the properties variable already exists from |
| * a previous clause. Please note that the "found" prop_var |
| * was previously transformed. |
| */ |
| |
| /* get the prop_var if it was previously resolved */ |
| if (rel->name != NULL) |
| { |
| prop_var = colNameToVar(pstate, rel->name, false, |
| rel->location); |
| } |
| |
| /* |
| * If prop_var exists and is an alias, just pass it through by |
| * assigning the prop_expr the prop_var. |
| */ |
| if (prop_var != NULL && |
| pg_strncasecmp(rel->name, AGE_DEFAULT_ALIAS_PREFIX, |
| strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) |
| { |
| prop_expr = prop_var; |
| } |
| /* |
| * Else, if it exists and is not an alias, create the prop_expr |
| * as a transformed properties(prop_var) function node. |
| */ |
| else if (prop_var != NULL) |
| { |
| /* |
| * Remember that prop_var is already transformed. We need |
| * to built the transform manually. |
| */ |
| FuncCall *fc = NULL; |
| List *targs = NIL; |
| List *fname = NIL; |
| |
| targs = lappend(targs, prop_var); |
| fname = list_make2(makeString("ag_catalog"), |
| makeString("age_properties")); |
| fc = makeFuncCall(fname, targs, COERCE_SQL_SYNTAX, -1); |
| |
| /* |
| * Hand off to ParseFuncOrColumn to create the function |
| * expression for properties(prop_var) |
| */ |
| prop_expr = ParseFuncOrColumn(pstate, fname, targs, |
| pstate->p_last_srf, fc, |
| false, -1); |
| } |
| |
| ((cypher_map*)rel->props)->keep_null = true; |
| r = create_property_constraints(cpstate, entity, rel->props, |
| prop_expr); |
| |
| cpstate->property_constraint_quals = |
| lappend(cpstate->property_constraint_quals, r); |
| } |
| |
| prev_entity = entity; |
| } |
| /* if we have a VLE edge */ |
| else |
| { |
| transform_entity *vle_entity = NULL; |
| |
| /* |
| * Check to see if the previous node was originally created |
| * in a preceding clause. If it was, then remove the id field |
| * from the column ref. Just reference the agtype vertex |
| * variable that the prev clause created and the vle will handle |
| * extracting the id. |
| */ |
| if (node_declared_in_prev_clause) |
| { |
| FuncCall *func = (FuncCall*)rel->varlen; |
| |
| ColumnRef *cr = linitial(func->args); |
| |
| Assert(IsA(cr, ColumnRef)); |
| Assert(list_length(cr->fields) == 2); |
| |
| cr->fields = list_make1(linitial(cr->fields)); |
| } |
| |
| /* make a transform entity for the vle */ |
| vle_entity = transform_VLE_edge_entity(cpstate, rel, query); |
| |
| /* add the entity in */ |
| cpstate->entities = lappend(cpstate->entities, vle_entity); |
| entities = lappend(entities, vle_entity); |
| |
| prev_entity = entity; |
| } |
| |
| node_declared_in_prev_clause = false; |
| } |
| |
| i++; |
| } |
| return entities; |
| } |
| |
| /* |
| * Iterate through the list of entities setup the join conditions. Joins |
| * are driven through edges. To correctly setup the joins, we must |
| * acquire information about the previous edge and vertex, and the next |
| * edge and vertex. |
| */ |
| static List *make_path_join_quals(cypher_parsestate *cpstate, List *entities) |
| { |
| transform_entity *prev_node = NULL, *prev_edge = NULL, *edge = NULL, |
| *next_node = NULL, *next_edge = NULL; |
| ListCell *lc; |
| List *quals = NIL; |
| List *join_quals; |
| |
| // for vertex only queries, there is no work to do |
| if (list_length(entities) < 3) |
| { |
| return NIL; |
| } |
| |
| lc = list_head(entities); |
| for (;;) |
| { |
| /* |
| * Initial setup, set the initial vertex as the previous vertex |
| * and get the first edge |
| */ |
| if (prev_node == NULL) |
| { |
| prev_node = lfirst(lc); |
| lc = lnext(entities, lc); |
| edge = lfirst(lc); |
| } |
| |
| // Retrieve the next node and edge in the pattern. |
| if (lnext(entities, lc) != NULL) |
| { |
| lc = lnext(entities, lc); |
| next_node = lfirst(lc); |
| |
| if (lnext(entities, lc) != NULL) |
| { |
| lc = lnext(entities, lc); |
| next_edge = lfirst(lc); |
| } |
| } |
| |
| // create the join quals for the node |
| join_quals = make_join_condition_for_edge(cpstate, prev_edge, prev_node, |
| edge, next_node, next_edge); |
| |
| quals = list_concat(quals, join_quals); |
| |
| /* Set the edge as the previous edge and the next edge as |
| * the current edge. If there is not a new edge, exit the |
| * for loop. |
| */ |
| prev_edge = edge; |
| prev_node = next_node; |
| edge = next_edge; |
| next_node = NULL; |
| next_edge = NULL; |
| |
| if (edge == NULL) |
| { |
| return quals; |
| } |
| } |
| } |
| |
| /* |
| * Create the path variable. Takes the list of entities, extracts the variable |
| * and passes as the argument list for the _agtype_build_path function. |
| */ |
| static TargetEntry * |
| transform_match_create_path_variable(cypher_parsestate *cpstate, |
| cypher_path *path, List *entities) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Oid build_path_oid = InvalidOid; |
| Expr *expr = NULL; |
| int resno = -1; |
| List *entity_exprs = NIL; |
| ListCell *lc = NULL; |
| bool null_path_entity = false; |
| |
| if (list_length(entities) < 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("paths require at least 1 vertex"), |
| parser_errposition(pstate, path->location))); |
| } |
| |
| /* extract the expr for each entity */ |
| foreach (lc, entities) |
| { |
| transform_entity *entity = lfirst(lc); |
| |
| if (entity->expr != NULL) |
| { |
| /* |
| * Is it a NULL constant, meaning there was an invalid label? |
| * If so, flag it for later |
| */ |
| if (IsA(entity->expr, Const) && |
| ((Const*)(entity->expr))->constisnull) |
| { |
| null_path_entity = true; |
| } |
| |
| entity_exprs = lappend(entity_exprs, entity->expr); |
| } |
| } |
| |
| /* get the oid for the path creation function */ |
| build_path_oid = get_ag_func_oid("_agtype_build_path", 1, ANYOID); |
| |
| /* |
| * If we have a NULL in the path, there is an invalid label, so there aren't |
| * any paths to be selected - the path variable will be NULL. In this case |
| * we need to return a NULL constant instead. |
| */ |
| if (null_path_entity) |
| { |
| expr = (Expr*)makeNullConst(AGTYPEOID, -1, InvalidOid); |
| } |
| /* otherwise, build the expr node for the function */ |
| else |
| { |
| transform_entity *entity; |
| expr = (Expr*)makeFuncExpr(build_path_oid, AGTYPEOID, entity_exprs, |
| InvalidOid, InvalidOid, |
| COERCE_EXPLICIT_CALL); |
| |
| entity = make_transform_entity(cpstate, ENT_PATH, (Node *)path, expr); |
| cpstate->entities = lappend(cpstate->entities, entity); |
| } |
| |
| resno = cpstate->pstate.p_next_resno++; |
| |
| /* create the target entry */ |
| return makeTargetEntry(expr, resno, path->var_name, false); |
| } |
| |
| /* |
| * Maps a column name to the a function access name. In others word when |
| * passed the name for the vertex's id column name, return the function name |
| * for the vertex's agtype id element, etc. |
| */ |
| static char *get_accessor_function_name(enum transform_entity_type type, |
| char *name) |
| { |
| if (type == ENT_VERTEX) |
| { |
| // id |
| if (!strcmp(AG_VERTEX_COLNAME_ID, name)) |
| { |
| return AG_VERTEX_ACCESS_FUNCTION_ID; |
| } |
| // props |
| else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) |
| { |
| return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; |
| } |
| } |
| if (type == ENT_EDGE) |
| { |
| // id |
| if (!strcmp(AG_EDGE_COLNAME_ID, name)) |
| { |
| return AG_EDGE_ACCESS_FUNCTION_ID; |
| } |
| // start id |
| else if (!strcmp(AG_EDGE_COLNAME_START_ID, name)) |
| { |
| return AG_EDGE_ACCESS_FUNCTION_START_ID; |
| } |
| // end id |
| else if (!strcmp(AG_EDGE_COLNAME_END_ID, name)) |
| { |
| return AG_EDGE_ACCESS_FUNCTION_END_ID; |
| } |
| // props |
| else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) |
| { |
| return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; |
| } |
| } |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| errmsg("column %s does not have an accessor function", name))); |
| |
| // keeps compiler silent |
| return NULL; |
| } |
| |
| /* |
| * For the given entity and column name, construct an expression that will |
| * access the column or get the access function if the entity is a variable. |
| */ |
| static Node *make_qual(cypher_parsestate *cpstate, |
| transform_entity *entity, char *col_name) |
| { |
| List *qualified_name, *args; |
| Node *node; |
| |
| if (IsA(entity->expr, Var)) |
| { |
| char *function_name; |
| |
| function_name = get_accessor_function_name(entity->type, col_name); |
| |
| qualified_name = list_make2(makeString("ag_catalog"), |
| makeString(function_name)); |
| |
| |
| args = list_make1(entity->expr); |
| node = (Node *)makeFuncCall(qualified_name, args, COERCE_EXPLICIT_CALL, |
| -1); |
| } |
| else |
| { |
| char *entity_name; |
| ColumnRef *cr = makeNode(ColumnRef); |
| |
| if (entity->type == ENT_EDGE) |
| { |
| entity_name = entity->entity.node->name; |
| } |
| else if (entity->type == ENT_VERTEX) |
| { |
| entity_name = entity->entity.rel->name; |
| } |
| else |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("unknown entity type"))); |
| } |
| |
| cr->fields = list_make2(makeString(entity_name), makeString(col_name)); |
| |
| node = (Node *)cr; |
| } |
| |
| return node; |
| } |
| |
| static Expr *transform_cypher_edge(cypher_parsestate *cpstate, |
| cypher_relationship *rel, |
| List **target_list, |
| bool valid_label) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| char *schema_name = NULL; |
| char *rel_name = NULL; |
| RangeVar *label_range_var = NULL; |
| Alias *alias = NULL; |
| int resno = -1; |
| TargetEntry *te = NULL; |
| transform_entity *entity = NULL; |
| cypher_relationship *cr = NULL; |
| Node *expr = NULL; |
| Var *previous_clause_var = NULL; |
| bool refs_var = false; |
| ParseNamespaceItem *pnsi = NULL; |
| |
| /* |
| * If we have an edge name, get any potential variable or column |
| * references. Additionally, verify that they are for edges. |
| */ |
| if (rel->name != NULL) |
| { |
| te = findTarget(*target_list, rel->name); |
| entity = find_variable(cpstate, rel->name); |
| previous_clause_var = (Var *)colNameToVar(pstate, rel->name, false, |
| rel->location); |
| |
| /* |
| * If we have a valid entity and te for this rel name, go ahead and get |
| * the cypher relationship as we will need this for later and flag that |
| * we have a variable reference. |
| */ |
| if ((te != NULL && entity != NULL) || |
| (entity != NULL && previous_clause_var != NULL)) |
| { |
| cr = (cypher_relationship *)entity->entity.rel; |
| refs_var = true; |
| } |
| |
| /* If the variable already exists, verify that it is for an edge */ |
| if (refs_var) |
| { |
| if (entity->type == ENT_VERTEX) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for a vertex", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| else if (entity->type == ENT_VLE_EDGE) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for a VLE edge", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| else if (entity->type == ENT_PATH && |
| pstate->p_expr_kind != EXPR_KIND_SELECT_TARGET) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for a path", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| } |
| |
| else if (te && !entity) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' already exists", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| } |
| |
| /* |
| * If we do not have a label for this edge, we either need to find one |
| * from a referenced variable or we need to set it to the default label. |
| */ |
| if (rel->label == NULL) |
| { |
| /* if there is a variable for this rel name */ |
| if (refs_var) |
| { |
| /* |
| * If the referenced var has a non NULL label, copy it. This is |
| * usually the case when it uses a variable that is already defined. |
| * Fx - |
| * |
| * MATCH (u:people)-[e:knows]->(v:people), (v)-[e]->(u) RETURN e |
| * MATCH (u:people)-[]->()-[]->(u) RETURN u |
| * |
| * We copy it so that we know what label it is referencing. |
| */ |
| if (cr->parsed_label != NULL) |
| { |
| rel->parsed_label = cr->parsed_label; |
| rel->label = cr->label; |
| } |
| else |
| { |
| rel->label = AG_DEFAULT_LABEL_EDGE; |
| } |
| } |
| /* otherwise, just give it the default label */ |
| else |
| { |
| rel->label = AG_DEFAULT_LABEL_EDGE; |
| } |
| } |
| /* if we do have a label, is it valid */ |
| else if (!valid_label) |
| { |
| /* |
| * XXX: Need to determine proper rules, for when label does not exist |
| * or is for an vertex. Maybe labels and edges should share names, like |
| * in openCypher. But these are stand in errors, to prevent |
| * segmentation faults, and other errors. |
| * |
| * Update: Nonexistent and mismatched labels now return a NULL value to |
| * prevent segmentation faults, and other errors. We can also consider |
| * if an all-purpose label would be useful. |
| */ |
| rel->label = NULL; |
| } |
| |
| /* |
| * Variables for edges are not allowed to be used multiple times within the |
| * same clause. |
| */ |
| if (previous_clause_var == NULL && refs_var) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("duplicate edge variable '%s' within a clause", |
| rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| |
| /* |
| * If this edge uses a variable that already exists, verify that the label |
| * names are the same. |
| */ |
| if (refs_var && |
| (cr->parsed_label != NULL || rel->parsed_label != NULL) && |
| (cr->parsed_label == NULL || rel->parsed_label == NULL || |
| (strcmp(cr->parsed_label, rel->parsed_label) != 0))) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("multiple labels for variable '%s' are not supported", |
| rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| |
| /* |
| * Now we need to do a few checks and either return the existing var or |
| * or build a new edge. |
| */ |
| if (rel->name != NULL) |
| { |
| /* |
| * If we are in a WHERE clause transform, we don't want to create new |
| * variables, we want to use the existing ones. So, error if otherwise. |
| */ |
| if (pstate->p_expr_kind == EXPR_KIND_WHERE) |
| { |
| cypher_parsestate *parent_cpstate = |
| (cypher_parsestate *)pstate->parentParseState->parentParseState; |
| /* |
| * If expr_kind is WHERE, the expressions are in the parent's |
| * parent's parsestate, due to the way we transform sublinks. |
| */ |
| transform_entity *tentity = NULL; |
| |
| /* if we have the referenced var, just return it */ |
| if (previous_clause_var != NULL) |
| { |
| return (Expr *)previous_clause_var; |
| } |
| |
| tentity = find_variable(parent_cpstate, rel->name); |
| if (tentity != NULL) |
| { |
| return get_relative_expr(tentity, 2); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable `%s` does not exist", rel->name), |
| parser_errposition(pstate, rel->location))); |
| } |
| } |
| |
| /* if this vertex is referencing an existing te var, return its expr */ |
| if (refs_var) |
| { |
| return (te != NULL) ? te->expr : (Expr *)previous_clause_var; |
| } |
| } |
| |
| /* if we aren't using a variable, build the edge */ |
| if (!rel->name) |
| { |
| rel->name = get_next_default_alias(cpstate); |
| } |
| |
| schema_name = get_graph_namespace_name(cpstate->graph_name); |
| |
| if (valid_label) |
| { |
| rel_name = get_label_relation_name(rel->label, cpstate->graph_oid); |
| } |
| else |
| { |
| rel_name = AG_DEFAULT_LABEL_EDGE; |
| } |
| |
| label_range_var = makeRangeVar(schema_name, rel_name, -1); |
| alias = makeAlias(rel->name, NIL); |
| |
| pnsi = addRangeTableEntry(pstate, label_range_var, alias, |
| label_range_var->inh, true); |
| Assert(pnsi != NULL); |
| |
| /* |
| * relation is visible (r.a in expression works) but attributes in the |
| * relation are not visible (a in expression doesn't work) |
| */ |
| addNSItemToQuery(pstate, pnsi, true, true, false); |
| |
| resno = pstate->p_next_resno++; |
| |
| if (valid_label) |
| { |
| expr = make_edge_expr(cpstate, pnsi); |
| } |
| else |
| { |
| expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); |
| } |
| |
| if (rel->name) |
| { |
| te = makeTargetEntry((Expr *)expr, resno, rel->name, false); |
| *target_list = lappend(*target_list, te); |
| } |
| |
| return (Expr *)expr; |
| } |
| |
| static Expr *transform_cypher_node(cypher_parsestate *cpstate, |
| cypher_node *node, List **target_list, |
| bool output_node, bool valid_label) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| char *schema_name = NULL; |
| char *rel_name = NULL; |
| RangeVar *label_range_var = NULL; |
| Alias *alias = NULL; |
| int resno = -1; |
| TargetEntry *te = NULL; |
| Expr *expr = NULL; |
| transform_entity *entity = NULL; |
| cypher_node *cn = NULL; |
| bool refs_var = false; |
| ParseNamespaceItem *pnsi = NULL; |
| Var *previous_clause_var = NULL; |
| |
| /* if we have a node name, get any potential variable references */ |
| if (node->name != NULL) |
| { |
| te = findTarget(*target_list, node->name); |
| entity = find_variable(cpstate, node->name); |
| previous_clause_var = (Var *)colNameToVar(pstate, node->name, false, |
| node->location); |
| |
| /* |
| * If we have a valid entity and te or a valid entity and a previous var |
| * ref for this rel name, go ahead and get the cypher relationship. We |
| * will need this information for later. Additionally, flag that we have |
| * a variable reference. |
| */ |
| if ((te != NULL && entity != NULL) || |
| (entity != NULL && previous_clause_var != NULL)) |
| { |
| cn = (cypher_node *)entity->entity.node; |
| refs_var = true; |
| } |
| |
| /* If the variable already exists, verify that it is for a vertex */ |
| if (refs_var) |
| { |
| if (entity->type == ENT_EDGE) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for an edge", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| else if (entity->type == ENT_VLE_EDGE) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for a VLE edge", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| /* gets non EXISTS cases */ |
| else if (entity->type == ENT_PATH && |
| pstate->p_expr_kind != EXPR_KIND_SELECT_TARGET) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' is for a path", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| /* gets EXISTS cases */ |
| else if (entity->type == ENT_PATH && |
| pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("a path variable '%s' is not allowed here", |
| node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| } |
| |
| /* If their is a te but no entity, it implies that their is |
| * some variable that exists but not an edge,vle or a vertex |
| */ |
| else if (te && !entity) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable '%s' already exists", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| } |
| |
| /* |
| * If we do not have a label for this vertex, we either need to find one |
| * from a referenced variable or we need to set it to the default label. |
| */ |
| if (node->label == NULL) |
| { |
| if (refs_var) |
| { |
| /* |
| * If the referenced var has a non NULL label, copy it. This is |
| * usually the case when it uses a variable that is already defined. |
| * Fx - |
| * |
| * MATCH (u:people)-[e:knows]->(v:people), (v)-[e]->(u) RETURN e |
| * MATCH (u:people)-[]->()-[]->(u) RETURN u |
| * |
| * We copy it so that we know what label it is referencing. |
| */ |
| if (cn->parsed_label != NULL) |
| { |
| node->parsed_label = cn->parsed_label; |
| node->label = cn->label; |
| } |
| else |
| { |
| node->label = AG_DEFAULT_LABEL_VERTEX; |
| } |
| } |
| /* otherwise, just give it the default label */ |
| else |
| { |
| node->label = AG_DEFAULT_LABEL_VERTEX; |
| } |
| } |
| /* if we do have a label, is it valid */ |
| else if (!valid_label) |
| { |
| /* |
| * XXX: Need to determine proper rules, for when label does not exist |
| * or is for an edge. Maybe labels and edges should share names, like |
| * in openCypher. But these are stand in errors, to prevent |
| * segmentation faults, and other errors. |
| * |
| * Update: Nonexistent and mismatched labels now return a NULL value to |
| * prevent segmentation faults, and other errors. We can also consider |
| * if an all-purpose label would be useful. |
| */ |
| node->label = NULL; |
| } |
| |
| /* |
| * If this vertex uses a variable that already exists, verify that the label |
| * being used is of the same name. |
| */ |
| if (refs_var && |
| (cn->parsed_label != NULL || node->parsed_label != NULL) && |
| (cn->parsed_label == NULL || node->parsed_label == NULL || |
| (strcmp(cn->parsed_label, node->parsed_label) != 0))) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("multiple labels for variable '%s' are not supported", |
| node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| |
| /* if it is not an output node, just return null */ |
| if (!output_node) |
| { |
| return NULL; |
| } |
| |
| /* |
| * Now we need to do a few checks and either return the existing var or |
| * or build a new vertex. |
| */ |
| if (node->name != NULL) |
| { |
| Node *expr = NULL; |
| |
| /* |
| * If we are in a WHERE clause transform, we don't want to create new |
| * variables, we want to use the existing ones. So, error if otherwise. |
| */ |
| if (pstate->p_expr_kind == EXPR_KIND_WHERE) |
| { |
| cypher_parsestate *parent_cpstate = |
| (cypher_parsestate *)pstate->parentParseState->parentParseState; |
| /* |
| * If expr_kind is WHERE, the expressions are in the parent's |
| * parent's parsestate, due to the way we transform sublinks. |
| */ |
| transform_entity *tentity = NULL; |
| |
| /* if we have the referenced var, just return it */ |
| if (previous_clause_var != NULL) |
| { |
| return (Expr *)previous_clause_var; |
| } |
| |
| tentity = find_variable(parent_cpstate, node->name); |
| if (tentity != NULL) |
| { |
| return get_relative_expr(tentity, 2); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable `%s` does not exist", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| } |
| |
| /* if this vertex is referencing an existing var, return its expr */ |
| if (refs_var) |
| { |
| return (te != NULL) ? te->expr : (Expr *)previous_clause_var; |
| } |
| |
| /* if this vertex is referencing an existing col var, return its expr */ |
| expr = colNameToVar(pstate, node->name, false, node->location); |
| if (expr != NULL) |
| { |
| return (Expr*)expr; |
| } |
| } |
| else |
| { |
| node->name = get_next_default_alias(cpstate); |
| } |
| |
| /* now build a new vertex */ |
| schema_name = get_graph_namespace_name(cpstate->graph_name); |
| |
| if (valid_label) |
| { |
| rel_name = get_label_relation_name(node->label, cpstate->graph_oid); |
| } |
| else |
| { |
| rel_name = AG_DEFAULT_LABEL_VERTEX; |
| } |
| |
| label_range_var = makeRangeVar(schema_name, rel_name, -1); |
| alias = makeAlias(node->name, NIL); |
| |
| pnsi = addRangeTableEntry(pstate, label_range_var, alias, |
| label_range_var->inh, true); |
| |
| Assert(pnsi != NULL); |
| |
| /* |
| * relation is visible (r.a in expression works) but attributes in the |
| * relation are not visible (a in expression doesn't work) |
| */ |
| addNSItemToQuery(pstate, pnsi, true, true, true); |
| |
| resno = pstate->p_next_resno++; |
| |
| if (valid_label) |
| { |
| expr = (Expr *)make_vertex_expr(cpstate, pnsi); |
| } |
| else |
| { |
| expr = (Expr*)makeNullConst(AGTYPEOID, -1, InvalidOid); |
| } |
| |
| /* make target entry and add it */ |
| te = makeTargetEntry(expr, resno, node->name, false); |
| *target_list = lappend(*target_list, te); |
| |
| return expr; |
| } |
| |
| static Node *make_edge_expr(cypher_parsestate *cpstate, |
| ParseNamespaceItem *pnsi) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Oid label_name_func_oid; |
| Oid func_oid; |
| Node *id, *start_id, *end_id; |
| Const *graph_oid_const; |
| Node *props; |
| List *args, *label_name_args; |
| FuncExpr *func_expr; |
| FuncExpr *label_name_func_expr; |
| |
| func_oid = get_ag_func_oid("_agtype_build_edge", 5, GRAPHIDOID, GRAPHIDOID, |
| GRAPHIDOID, CSTRINGOID, AGTYPEOID); |
| |
| id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); |
| |
| start_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); |
| |
| end_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); |
| |
| label_name_func_oid = get_ag_func_oid("_label_name", 2, OIDOID, |
| GRAPHIDOID); |
| |
| graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), |
| ObjectIdGetDatum(cpstate->graph_oid), false, |
| true); |
| |
| label_name_args = list_make2(graph_oid_const, id); |
| |
| label_name_func_expr = makeFuncExpr(label_name_func_oid, CSTRINGOID, |
| label_name_args, InvalidOid, |
| InvalidOid, COERCE_EXPLICIT_CALL); |
| label_name_func_expr->location = -1; |
| |
| props = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); |
| |
| args = list_make4(id, start_id, end_id, label_name_func_expr); |
| args = lappend(args, props); |
| |
| func_expr = makeFuncExpr(func_oid, AGTYPEOID, args, InvalidOid, InvalidOid, |
| COERCE_EXPLICIT_CALL); |
| func_expr->location = -1; |
| |
| return (Node *)func_expr; |
| } |
| static Node *make_vertex_expr(cypher_parsestate *cpstate, |
| ParseNamespaceItem *pnsi) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Oid label_name_func_oid; |
| Oid func_oid; |
| Node *id; |
| Const *graph_oid_const; |
| Node *props; |
| List *args, *label_name_args; |
| FuncExpr *func_expr; |
| FuncExpr *label_name_func_expr; |
| |
| Assert(pnsi != NULL); |
| |
| func_oid = get_ag_func_oid("_agtype_build_vertex", 3, GRAPHIDOID, |
| CSTRINGOID, AGTYPEOID); |
| |
| id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); |
| |
| label_name_func_oid = get_ag_func_oid("_label_name", 2, OIDOID, |
| GRAPHIDOID); |
| |
| graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), |
| ObjectIdGetDatum(cpstate->graph_oid), false, |
| true); |
| |
| label_name_args = list_make2(graph_oid_const, id); |
| |
| label_name_func_expr = makeFuncExpr(label_name_func_oid, CSTRINGOID, |
| label_name_args, InvalidOid, |
| InvalidOid, COERCE_EXPLICIT_CALL); |
| label_name_func_expr->location = -1; |
| |
| props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, |
| -1); |
| |
| args = list_make3(id, label_name_func_expr, props); |
| |
| func_expr = makeFuncExpr(func_oid, AGTYPEOID, args, InvalidOid, InvalidOid, |
| COERCE_EXPLICIT_CALL); |
| func_expr->location = -1; |
| |
| return (Node *)func_expr; |
| } |
| |
| static Query *transform_cypher_create(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_create *self = (cypher_create *)clause->self; |
| cypher_create_target_nodes *target_nodes; |
| Const *null_const; |
| List *transformed_pattern; |
| FuncExpr *func_expr; |
| Query *query; |
| TargetEntry *tle; |
| |
| target_nodes = make_ag_node(cypher_create_target_nodes); |
| target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE; |
| target_nodes->graph_oid = cpstate->graph_oid; |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| query->targetList = NIL; |
| |
| if (clause->prev) |
| { |
| handle_prev_clause(cpstate, query, clause->prev, true); |
| |
| target_nodes->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; |
| } |
| |
| null_const = makeNullConst(AGTYPEOID, -1, InvalidOid); |
| tle = makeTargetEntry((Expr *)null_const, pstate->p_next_resno++, |
| AGE_VARNAME_CREATE_NULL_VALUE, false); |
| query->targetList = lappend(query->targetList, tle); |
| |
| /* |
| * Create the Const Node to hold the pattern. skip the parse node, |
| * because we would not be able to control how our pointer to the |
| * internal type is copied. |
| */ |
| transformed_pattern = transform_cypher_create_pattern(cpstate, query, |
| self->pattern); |
| |
| target_nodes->paths = transformed_pattern; |
| if (!clause->next) |
| { |
| target_nodes->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; |
| } |
| |
| |
| func_expr = make_clause_func_expr(CREATE_CLAUSE_FUNCTION_NAME, |
| (Node *)target_nodes); |
| |
| // Create the target entry |
| tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, |
| AGE_VARNAME_CREATE_CLAUSE, false); |
| query->targetList = lappend(query->targetList, tle); |
| |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| |
| return query; |
| } |
| |
| static List *transform_cypher_create_pattern(cypher_parsestate *cpstate, |
| Query *query, List *pattern) |
| { |
| ListCell *lc; |
| List *transformed_pattern = NIL; |
| |
| foreach (lc, pattern) |
| { |
| cypher_create_path *transformed_path; |
| |
| transformed_path = transform_cypher_create_path(cpstate, |
| &query->targetList, |
| lfirst(lc)); |
| |
| transformed_pattern = lappend(transformed_pattern, transformed_path); |
| } |
| |
| return transformed_pattern; |
| } |
| |
| static cypher_create_path * |
| transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, |
| cypher_path *path) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| ListCell *lc, *prev_node = NULL; |
| List *transformed_path = NIL; |
| cypher_create_path *ccp = make_ag_node(cypher_create_path); |
| bool in_path = path->var_name != NULL; |
| |
| ccp->path_attr_num = InvalidAttrNumber; |
| |
| if (in_path) |
| { |
| if (findTarget(*target_list, path->var_name) != NULL) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable \"%s\" already exists", |
| path->var_name), |
| parser_errposition(pstate, path->location))); |
| } |
| } |
| |
| foreach (lc, path->path) |
| { |
| if (is_ag_node(lfirst(lc), cypher_node)) |
| { |
| cypher_node *node = lfirst(lc); |
| transform_entity *entity; |
| ListCell *next_node = lnext(path->path, lc); |
| |
| cypher_target_node *rel = |
| transform_create_cypher_node(cpstate, target_list, node, |
| (next_node || prev_node)); |
| |
| if (in_path) |
| { |
| if (node->name && strcmp(node->name, path->var_name) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable \"%s\" already exists", |
| path->var_name), |
| parser_errposition(pstate, path->location))); |
| } |
| rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; |
| } |
| |
| transformed_path = lappend(transformed_path, rel); |
| |
| entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, |
| NULL); |
| |
| cpstate->entities = lappend(cpstate->entities, entity); |
| } |
| else if (is_ag_node(lfirst(lc), cypher_relationship)) |
| { |
| cypher_relationship *edge = lfirst(lc); |
| transform_entity *entity; |
| |
| cypher_target_node *rel = |
| transform_create_cypher_edge(cpstate, target_list, edge); |
| |
| if (in_path) |
| { |
| if (edge->name && strcmp(edge->name, path->var_name) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("variable \"%s\" already exists", |
| path->var_name), |
| parser_errposition(pstate, path->location))); |
| } |
| rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; |
| } |
| |
| transformed_path = lappend(transformed_path, rel); |
| |
| entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)edge, |
| NULL); |
| |
| cpstate->entities = lappend(cpstate->entities, entity); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errmsg_internal("unrecognized node in create pattern"))); |
| } |
| prev_node = lc; |
| } |
| |
| ccp->target_nodes = transformed_path; |
| |
| /* |
| * If this path a variable, create a placeholder entry that we can fill |
| * in with during the execution phase. |
| */ |
| if (path->var_name) |
| { |
| TargetEntry *te; |
| |
| if (list_length(transformed_path) < 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("paths require at least 1 vertex"), |
| parser_errposition(pstate, path->location))); |
| } |
| |
| te = placeholder_target_entry(cpstate, path->var_name); |
| |
| ccp->path_attr_num = te->resno; |
| |
| *target_list = lappend(*target_list, te); |
| } |
| |
| return ccp; |
| } |
| |
| static cypher_target_node * |
| transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, |
| cypher_relationship *edge) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_target_node *rel = make_ag_node(cypher_target_node); |
| Expr *props; |
| Relation label_relation; |
| RangeVar *rv; |
| RTEPermissionInfo *rte_pi; |
| TargetEntry *te; |
| char *alias; |
| AttrNumber resno; |
| ParseNamespaceItem *pnsi; |
| |
| if (edge->label) |
| { |
| if (get_label_kind(edge->label, cpstate->graph_oid) == LABEL_KIND_VERTEX) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("label %s is for vertices, not edges", edge->label), |
| parser_errposition(pstate, edge->location))); |
| } |
| } |
| |
| rel->type = LABEL_KIND_EDGE; |
| rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; |
| rel->label_name = edge->label; |
| rel->resultRelInfo = NULL; |
| |
| if (edge->name) |
| { |
| /* |
| * Variables can be declared in a CREATE clause, but not used if |
| * it already exists. |
| */ |
| transform_entity *entity; |
| |
| entity = find_variable(cpstate, edge->name); |
| |
| if (entity || variable_exists(cpstate, edge->name)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable %s already exists", edge->name), |
| parser_errposition(pstate, edge->location))); |
| } |
| |
| rel->variable_name = edge->name; |
| te = placeholder_target_entry(cpstate, edge->name); |
| rel->tuple_position = te->resno; |
| *target_list = lappend(*target_list, te); |
| |
| rel->flags |= CYPHER_TARGET_NODE_IS_VAR; |
| } |
| else |
| { |
| rel->variable_name = NULL; |
| rel->tuple_position = 0; |
| } |
| |
| if (edge->dir == CYPHER_REL_DIR_NONE) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("only directed relationships are allowed in CREATE"), |
| parser_errposition(&cpstate->pstate, edge->location))); |
| } |
| |
| rel->dir = edge->dir; |
| |
| if (!edge->label) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("relationships must be specify a label in CREATE."), |
| parser_errposition(&cpstate->pstate, edge->location))); |
| } |
| |
| // create the label entry if it does not exist |
| if (!label_exists(edge->label, cpstate->graph_oid)) |
| { |
| List *parent; |
| |
| rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, |
| AG_DEFAULT_LABEL_EDGE); |
| |
| parent = list_make1(rv); |
| |
| create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, |
| parent); |
| } |
| |
| // lock the relation of the label |
| rv = makeRangeVar(cpstate->graph_name, edge->label, -1); |
| label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); |
| |
| // Store the relid |
| rel->relid = RelationGetRelid(label_relation); |
| |
| pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, |
| AccessShareLock, NULL, false, false); |
| |
| rte_pi = pnsi->p_perminfo; |
| rte_pi->requiredPerms = ACL_INSERT; |
| |
| // Build Id expression, always use the default logic |
| rel->id_expr = (Expr *)build_column_default(label_relation, |
| Anum_ag_label_edge_table_id); |
| |
| // Build properties expression, if no map is given, use the default logic |
| alias = get_next_default_alias(cpstate); |
| resno = pstate->p_next_resno++; |
| |
| props = cypher_create_properties(cpstate, rel, label_relation, edge->props, |
| ENT_EDGE); |
| |
| rel->prop_attr_num = resno - 1; |
| te = makeTargetEntry(props, resno, alias, false); |
| |
| *target_list = lappend(*target_list, te); |
| |
| // Keep the lock |
| table_close(label_relation, NoLock); |
| |
| return rel; |
| } |
| |
| static bool variable_exists(cypher_parsestate *cpstate, char *name) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Node *id; |
| ParseNamespaceItem *pnsi; |
| |
| if (name == NULL) |
| { |
| return false; |
| } |
| |
| pnsi = find_pnsi(cpstate, PREV_CYPHER_CLAUSE_ALIAS); |
| if (pnsi) |
| { |
| id = scanNSItemForColumn(pstate, pnsi, 0, name, -1); |
| |
| return id != NULL; |
| } |
| |
| return false; |
| } |
| |
| // transform nodes, check to see if the variable name already exists. |
| static cypher_target_node * |
| transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, |
| cypher_node *node, bool has_edge) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| |
| if (node->label) |
| { |
| if (get_label_kind(node->label, cpstate->graph_oid) == LABEL_KIND_EDGE) |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("label %s is for edges, not vertices", |
| node->label), |
| parser_errposition(pstate, node->location))); |
| } |
| } |
| |
| /* |
| * Check if the variable already exists, if so find the entity and |
| * setup the target node |
| */ |
| if (node->name) |
| { |
| transform_entity *entity; |
| |
| TargetEntry *te = findTarget(*target_list, node->name); |
| entity = find_variable(cpstate, node->name); |
| |
| /* |
| * If we find an entity as well as a target Entry with same name, |
| * that means that the variable is either vertex, edge or vle. |
| * but if we find a target entry but not an entity that means |
| * that the variable can be other than vertex, edge or vle e.g path. |
| */ |
| if (entity && te) |
| { |
| if (entity->type != ENT_VERTEX || !has_edge) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable %s already exists", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| |
| return transform_create_cypher_existing_node(cpstate, target_list, |
| entity->declared_in_current_clause, node); |
| } |
| else if (te) |
| { |
| if (!has_edge) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable %s already exists", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| /* |
| * Here we are not sure if the te is a vertex, path or something |
| * else. So we will let it pass and the execution stage will catch |
| * the error if variable was not vertex. |
| */ |
| return transform_create_cypher_existing_node(cpstate, target_list, |
| te, node); |
| } |
| } |
| |
| // otherwise transform the target node as a new node |
| return transform_create_cypher_new_node(cpstate, target_list, node); |
| } |
| |
| /* |
| * Returns the resno for the TargetEntry with the resname equal to the name |
| * passed. Returns -1 otherwise. |
| */ |
| static int get_target_entry_resno(List *target_list, char *name) |
| { |
| ListCell *lc; |
| |
| foreach (lc, target_list) |
| { |
| TargetEntry *te = (TargetEntry *)lfirst(lc); |
| if (!strcmp(te->resname, name)) |
| { |
| return te->resno; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /* adds the volatile wrapper to the specified target entry */ |
| static void add_volatile_wrapper_to_target_entry(List *target_list, int resno) |
| { |
| ListCell *lc; |
| |
| Assert(target_list != NULL); |
| Assert(resno >= 0); |
| |
| /* find the resource */ |
| foreach (lc, target_list) |
| { |
| TargetEntry *te = (TargetEntry *)lfirst(lc); |
| if (te->resno == resno) |
| { |
| /* wrap it */ |
| te->expr = add_volatile_wrapper(te->expr); |
| return; |
| } |
| } |
| |
| /* if we didn't find anything, there was a problem */ |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("add_volatile_wrapper_to_target_entry: resno not found"))); |
| } |
| |
| /* |
| * Transform logic for a previously declared variable in a CREATE clause. |
| * All we need from the variable node is its id, and whether we can skip |
| * some tests in the execution phase.. |
| */ |
| static cypher_target_node *transform_create_cypher_existing_node( |
| cypher_parsestate *cpstate, List **target_list, bool declared_in_current_clause, |
| cypher_node *node) |
| { |
| cypher_target_node *rel = make_ag_node(cypher_target_node); |
| ParseState *pstate = (ParseState *)cpstate; |
| |
| rel->type = LABEL_KIND_VERTEX; |
| rel->flags = CYPHER_TARGET_NODE_FLAG_NONE; |
| rel->resultRelInfo = NULL; |
| rel->variable_name = node->name; |
| |
| if (node->props) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("previously declared nodes in a create clause cannot have properties"), |
| parser_errposition(pstate, node->location))); |
| } |
| if (node->label) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("previously declared variables cannot have a label"), |
| parser_errposition(pstate, node->location))); |
| } |
| /* |
| * When the variable is declared in the same clause this vertex is a part of |
| * we can skip some expensive checks in the execution phase. |
| */ |
| if (declared_in_current_clause) |
| { |
| rel->flags |= EXISTING_VARIABLE_DECLARED_SAME_CLAUSE; |
| } |
| |
| /* |
| * Get the AttrNumber the variable is stored in, so we can extract the id |
| * later. |
| */ |
| rel->tuple_position = get_target_entry_resno(*target_list, node->name); |
| |
| add_volatile_wrapper_to_target_entry(*target_list, rel->tuple_position); |
| |
| return rel; |
| } |
| |
| /* |
| * Transform logic for a node in a create clause that was not previously |
| * declared. |
| */ |
| static cypher_target_node * |
| transform_create_cypher_new_node(cypher_parsestate *cpstate, |
| List **target_list, cypher_node *node) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_target_node *rel = make_ag_node(cypher_target_node); |
| Relation label_relation; |
| RangeVar *rv; |
| RTEPermissionInfo *rte_pi; |
| TargetEntry *te; |
| Expr *props; |
| char *alias; |
| int resno; |
| ParseNamespaceItem *pnsi; |
| |
| rel->type = LABEL_KIND_VERTEX; |
| rel->tuple_position = InvalidAttrNumber; |
| rel->variable_name = NULL; |
| rel->resultRelInfo = NULL; |
| |
| if (!node->label) |
| { |
| rel->label_name = ""; |
| /* |
| * If no label is specified, assign the generic label name that |
| * all labels are descendents of. |
| */ |
| node->label = AG_DEFAULT_LABEL_VERTEX; |
| } |
| else |
| { |
| rel->label_name = node->label; |
| } |
| |
| // create the label entry if it does not exist |
| if (!label_exists(node->label, cpstate->graph_oid)) |
| { |
| List *parent; |
| |
| rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, |
| AG_DEFAULT_LABEL_VERTEX); |
| |
| parent = list_make1(rv); |
| |
| create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, |
| parent); |
| } |
| |
| rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; |
| |
| rv = makeRangeVar(cpstate->graph_name, node->label, -1); |
| label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); |
| |
| // Store the relid |
| rel->relid = RelationGetRelid(label_relation); |
| |
| pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, |
| AccessShareLock, NULL, false, false); |
| |
| rte_pi = pnsi->p_perminfo; |
| rte_pi->requiredPerms = ACL_INSERT; |
| |
| // id |
| rel->id_expr = (Expr *)build_column_default(label_relation, |
| Anum_ag_label_vertex_table_id); |
| |
| // properties |
| alias = get_next_default_alias(cpstate); |
| resno = pstate->p_next_resno++; |
| |
| props = cypher_create_properties(cpstate, rel, label_relation, node->props, |
| ENT_VERTEX); |
| |
| rel->prop_attr_num = resno - 1; |
| te = makeTargetEntry(props, resno, alias, false); |
| *target_list = lappend(*target_list, te); |
| |
| table_close(label_relation, NoLock); |
| |
| if (node->name) |
| { |
| rel->variable_name = node->name; |
| te = placeholder_target_entry(cpstate, node->name); |
| rel->tuple_position = te->resno; |
| *target_list = lappend(*target_list, te); |
| rel->flags |= CYPHER_TARGET_NODE_IS_VAR; |
| } |
| else |
| { |
| node->name = get_next_default_alias(cpstate); |
| } |
| |
| return rel; |
| } |
| |
| /* |
| * Variable Edges cannot be created until the executor phase, because we |
| * don't know what their start and end node ids will be. Therefore, path |
| * variables cannot be created either. Create a placeholder entry that we |
| * will replace in the execution phase. Do this for nodes too, to be |
| * consistent. |
| */ |
| static TargetEntry *placeholder_target_entry(cypher_parsestate *cpstate, |
| char *name) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Expr *n; |
| int resno; |
| |
| n = (Expr *)makeNullConst(AGTYPEOID, -1, InvalidOid); |
| n = add_volatile_wrapper(n); |
| |
| resno = pstate->p_next_resno++; |
| |
| return makeTargetEntry(n, resno, name, false); |
| } |
| |
| /* |
| * Build the target list for an entity that is not a previously declared |
| * variable. |
| */ |
| static Expr *cypher_create_properties(cypher_parsestate *cpstate, |
| cypher_target_node *rel, |
| Relation label_relation, Node *props, |
| enum transform_entity_type type) |
| { |
| Expr *properties; |
| |
| if (props != NULL && is_ag_node(props, cypher_param)) |
| { |
| ParseState *pstate = (ParseState *) cpstate; |
| cypher_param *param = (cypher_param *)props; |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("properties in a CREATE clause as a parameter is not supported"), |
| parser_errposition(pstate, param->location))); |
| } |
| |
| if (props) |
| { |
| ((cypher_map*)props)->keep_null = false; |
| properties = (Expr *)transform_cypher_expr(cpstate, props, |
| EXPR_KIND_INSERT_TARGET); |
| } |
| else if (type == ENT_VERTEX) |
| { |
| properties = (Expr *)build_column_default( |
| label_relation, Anum_ag_label_vertex_table_properties); |
| } |
| else if (type == ENT_EDGE) |
| { |
| properties = (Expr *)build_column_default( |
| label_relation, Anum_ag_label_edge_table_properties); |
| } |
| else |
| { |
| ereport(ERROR, (errmsg_internal("unrecognized entity type"))); |
| } |
| |
| // add a volatile wrapper call to prevent the optimizer from removing it |
| return (Expr *)add_volatile_wrapper(properties); |
| } |
| |
| /* |
| * This function is similar to transformFromClause() that is called with a |
| * single RangeSubselect. |
| */ |
| static ParseNamespaceItem * |
| transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, |
| transform_method transform, |
| cypher_clause *clause, |
| Alias *alias, |
| bool add_rte_to_query) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| Query *query; |
| RangeTblEntry *rte; |
| ParseExprKind old_expr_kind = pstate->p_expr_kind; |
| bool lateral = pstate->p_lateral_active; |
| ParseNamespaceItem *pnsi; |
| |
| /* |
| * We allow expression kinds of none, where, and subselect. Others MAY need |
| * to be added depending. However, at this time, only these are needed. |
| */ |
| Assert(pstate->p_expr_kind == EXPR_KIND_NONE || |
| 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); |
| |
| /* |
| * As these are all sub queries, if this is just of type NONE, note it as a |
| * SUBSELECT. Other types will be dealt with as needed. |
| */ |
| if (pstate->p_expr_kind == EXPR_KIND_NONE) |
| { |
| pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; |
| } |
| else if (pstate->p_expr_kind == EXPR_KIND_OTHER) |
| { |
| // this is a lateral subselect for the MERGE |
| pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; |
| lateral = true; |
| } |
| /* |
| * If this is a WHERE, pass it through and set lateral to true because it |
| * needs to see what comes before it. |
| */ |
| |
| query = analyze_cypher_clause(transform, clause, cpstate); |
| |
| /* set pstate kind back */ |
| pstate->p_expr_kind = old_expr_kind; |
| |
| if (alias == NULL) |
| { |
| alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); |
| } |
| |
| pnsi = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true); |
| rte = pnsi->p_rte; |
| |
| /* |
| * NOTE: skip namespace conflicts check if the rte will be the only |
| * RangeTblEntry in pstate |
| */ |
| if (list_length(pstate->p_rtable) > 1) |
| { |
| List *namespace = NULL; |
| int rtindex = 0; |
| |
| /* get the index of the last entry */ |
| rtindex = list_length(pstate->p_rtable); |
| |
| /* the rte at the end should be the rte just added */ |
| if (rte != rt_fetch(rtindex, pstate->p_rtable)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("rte must be last entry in p_rtable"))); |
| } |
| |
| namespace = list_make1(pnsi); |
| |
| checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); |
| } |
| |
| if (add_rte_to_query) |
| { |
| // all variables(attributes) from the previous clause(subquery) are visible |
| addNSItemToQuery(pstate, pnsi, true, false, true); |
| } |
| |
| return pnsi; |
| } |
| |
| /* |
| * When we are done transforming a clause, before transforming the next clause |
| * iterate through the transform entities and mark them as not belonging to |
| * the clause that is currently being transformed. |
| */ |
| static void advance_transform_entities_to_next_clause(List *entities) |
| { |
| ListCell *lc; |
| |
| foreach (lc, entities) |
| { |
| transform_entity *entity = lfirst(lc); |
| |
| entity->declared_in_current_clause = false; |
| } |
| } |
| |
| static Query *analyze_cypher_clause(transform_method transform, |
| cypher_clause *clause, |
| cypher_parsestate *parent_cpstate) |
| { |
| cypher_parsestate *cpstate; |
| Query *query; |
| ParseState *parent_pstate = (ParseState*)parent_cpstate; |
| ParseState *pstate; |
| |
| cpstate = make_cypher_parsestate(parent_cpstate); |
| pstate = (ParseState*)cpstate; |
| |
| /* copy the expr_kind down to the child */ |
| pstate->p_expr_kind = parent_pstate->p_expr_kind; |
| |
| query = transform(cpstate, clause); |
| |
| advance_transform_entities_to_next_clause(cpstate->entities); |
| |
| parent_cpstate->entities = list_concat(parent_cpstate->entities, |
| cpstate->entities); |
| |
| free_cypher_parsestate(cpstate); |
| |
| return query; |
| } |
| |
| static TargetEntry *findTarget(List *targetList, char *resname) |
| { |
| ListCell *lt; |
| TargetEntry *te = NULL; |
| |
| if (resname == NULL) |
| { |
| return NULL; |
| } |
| |
| foreach (lt, targetList) |
| { |
| te = lfirst(lt); |
| |
| if (te->resjunk) |
| { |
| continue; |
| } |
| |
| if (strcmp(te->resname, resname) == 0) |
| { |
| return te; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Wrap the expression with a volatile function, to prevent the optimizer from |
| * eliminating the expression. |
| */ |
| static Expr *add_volatile_wrapper(Expr *node) |
| { |
| Oid oid; |
| |
| /* if the passed Expr node is NULL it will cause a crash, so notify us */ |
| if (node == NULL) |
| { |
| ereport(ERROR, (errmsg_internal("add_volatile_wrapper: NULL expr"))); |
| } |
| |
| oid = get_ag_func_oid("agtype_volatile_wrapper", 1, ANYOID); |
| |
| /* if the passed Expr node is already wrapped, just return it */ |
| if (IsA(node, FuncExpr) && oid == ((FuncExpr*)node)->funcid) |
| { |
| return node; |
| } |
| |
| return (Expr *)makeFuncExpr(oid, AGTYPEOID, list_make1(node), InvalidOid, |
| InvalidOid, COERCE_EXPLICIT_CALL); |
| } |
| |
| /* |
| * from postgresql parse_sub_analyze |
| * Modified entry point for recursively analyzing a sub-statement in union. |
| */ |
| Query *cypher_parse_sub_analyze_union(cypher_clause *clause, |
| cypher_parsestate *cpstate, |
| CommonTableExpr *parentCTE, |
| bool locked_from_parent, |
| bool resolve_unknowns) |
| { |
| cypher_parsestate *state = make_cypher_parsestate(cpstate); |
| Query *query; |
| |
| state->pstate.p_parent_cte = parentCTE; |
| state->pstate.p_locked_from_parent = locked_from_parent; |
| state->pstate.p_resolve_unknowns = resolve_unknowns; |
| |
| query = transform_cypher_clause(state, clause); |
| |
| free_cypher_parsestate(state); |
| |
| return query; |
| } |
| |
| /* |
| * from postgresql parse_sub_analyze |
| * Entry point for recursively analyzing a sub-statement. |
| */ |
| Query *cypher_parse_sub_analyze(Node *parseTree, |
| cypher_parsestate *cpstate, |
| CommonTableExpr *parentCTE, |
| bool locked_from_parent, |
| bool resolve_unknowns) |
| { |
| ParseState *pstate = make_parsestate((ParseState*)cpstate); |
| cypher_clause *clause; |
| Query *query; |
| |
| pstate->p_parent_cte = parentCTE; |
| pstate->p_locked_from_parent = locked_from_parent; |
| pstate->p_resolve_unknowns = resolve_unknowns; |
| |
| clause = palloc0(sizeof(cypher_clause)); |
| clause->self = parseTree; |
| query = transform_cypher_clause(cpstate, clause); |
| |
| free_parsestate(pstate); |
| |
| return query; |
| } |
| |
| /* |
| * Function for transforming MERGE. |
| * |
| * There are two cases for the form Query that is returned from here will |
| * take: |
| * |
| * 1. If there is no previous clause, the query will have a subquery that |
| * represents the path as a select statement, similar to match with a targetList |
| * that is all declared variables and the FuncExpr that represents the MERGE |
| * clause with its needed metadata information, that will be caught in the |
| * planner phase and converted into a path. |
| * |
| * 2. If there is a previous clause then the query will have two subqueries. |
| * The first query will be for the previous clause that we recursively handle. |
| * The second query will be for the path that this MERGE clause defines. The |
| * two subqueries will be joined together using a LATERAL LEFT JOIN with the |
| * previous query on the left and the MERGE path subquery on the right. Like |
| * case 1 the targetList will have all the declared variables and a FuncExpr |
| * that represents the MERGE clause with its needed metadata information, that |
| * will be caught in the planner phase and converted into a path. |
| * |
| * This will allow us to be capable of handling the 2 cases that exist with a |
| * MERGE clause correctly. |
| * |
| * Case 1: the path already exists. In this case we do not need to create |
| * the path and MERGE will simply pass the tuple information up the execution |
| * tree. |
| * |
| * Case 2: the path does not exist. In this case the LEFT part of the join |
| * will not prevent the tuples from the previous clause from being emitted. We |
| * can catch when this happens in the execution phase and create the missing |
| * data, before passing up the execution tree. |
| * |
| * It should be noted that both cases can happen in the same query. If the |
| * MERGE clause references a variable from a previous clause, it could be that |
| * for one tuple the path exists (or there is multiple paths that exist and all |
| * paths must be emitted) and for another the path does not exist. This is |
| * similar to OPTIONAL MATCH, however with the added feature of creating the |
| * path if not there, rather than just emitting NULL. |
| */ |
| static Query *transform_cypher_merge(cypher_parsestate *cpstate, |
| cypher_clause *clause) |
| { |
| ParseState *pstate = (ParseState *) cpstate; |
| cypher_clause *merge_clause_as_match; |
| cypher_create_path *merge_path; |
| cypher_merge *self = (cypher_merge *)clause->self; |
| cypher_merge_information *merge_information; |
| Query *query; |
| FuncExpr *func_expr; |
| TargetEntry *tle; |
| |
| Assert(is_ag_node(self->path, cypher_path)); |
| |
| merge_information = make_ag_node(cypher_merge_information); |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| query->targetList = NIL; |
| |
| merge_information->flags = CYPHER_CLAUSE_FLAG_NONE; |
| |
| // make the merge node into a match node |
| merge_clause_as_match = convert_merge_to_match(self); |
| |
| /* |
| * If there is a previous clause we need to turn this query into a lateral |
| * join. See the function transform_merge_make_lateral_join for details. |
| */ |
| if (clause->prev != NULL) |
| { |
| merge_path = transform_merge_make_lateral_join(cpstate, query, clause, |
| merge_clause_as_match); |
| |
| merge_information->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE; |
| } |
| else |
| { |
| // make the merge node into a match node |
| |
| // TODO this is called above and appears redundant but needs to be |
| // looked into |
| //cypher_clause *merge_clause_as_match = convert_merge_to_match(self); |
| |
| /* |
| * Create the metadata needed for creating missing paths. |
| */ |
| merge_path = transform_cypher_merge_path(cpstate, &query->targetList, |
| (cypher_path *)self->path); |
| |
| /* |
| * If there is not a previous clause, then treat the MERGE's path |
| * itself as the previous clause. We need to do this because if the |
| * pattern exists, then we need to path all paths that match the |
| * query patterns in the execution phase. WE way to do that by |
| * converting the merge to a match and have the match logic create the |
| * query. the merge execution phase will just pass the results up the |
| * execution tree if the path exists. |
| */ |
| handle_prev_clause(cpstate, query, merge_clause_as_match, false); |
| |
| /* |
| * For the metadata need to create paths, find the tuple position that |
| * will represent the entity in the execution phase. |
| */ |
| transform_cypher_merge_mark_tuple_position(cpstate, query->targetList, |
| merge_path); |
| } |
| |
| merge_information->graph_oid = cpstate->graph_oid; |
| merge_information->path = merge_path; |
| |
| if (!clause->next) |
| { |
| merge_information->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; |
| } |
| |
| /* |
| * Creates the function expression that the planner will find and |
| * convert to a MERGE path. |
| */ |
| func_expr = make_clause_func_expr(MERGE_CLAUSE_FUNCTION_NAME, |
| (Node *)merge_information); |
| |
| // Create the target entry |
| tle = makeTargetEntry((Expr *)func_expr, pstate->p_next_resno++, |
| AGE_VARNAME_MERGE_CLAUSE, false); |
| |
| merge_information->merge_function_attr = tle->resno; |
| query->targetList = lappend(query->targetList, tle); |
| |
| markTargetListOrigins(pstate, query->targetList); |
| |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| |
| query->hasSubLinks = pstate->p_hasSubLinks; |
| |
| assign_query_collations(pstate, query); |
| |
| return query; |
| } |
| |
| /* |
| * This function does the heavy lifting of transforming a MERGE clause that has |
| * a clause before it in the query of turning that into a lateral left join. |
| * The previous clause will still be able to emit tuples if the path defined in |
| * MERGE clause is not found. In that case variable assigned in the MERGE |
| * clause will be emitted as NULL (same as OPTIONAL MATCH). |
| */ |
| static cypher_create_path * |
| transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, |
| cypher_clause *clause, |
| cypher_clause *isolated_merge_clause) |
| { |
| cypher_create_path *merge_path; |
| ParseState *pstate = (ParseState *) cpstate; |
| int i; |
| Alias *l_alias; |
| Alias *r_alias; |
| RangeTblEntry *l_rte, *r_rte; |
| ParseNamespaceItem *l_nsitem, *r_nsitem; |
| JoinExpr *j = makeNode(JoinExpr); |
| List *res_colnames = NIL, *res_colvars = NIL; |
| ParseNamespaceItem *jnsitem; |
| ParseExprKind tmp; |
| cypher_merge *self = (cypher_merge *)clause->self; |
| cypher_path *path; |
| |
| Assert(is_ag_node(self->path, cypher_path)); |
| |
| path = (cypher_path *)self->path; |
| |
| r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL); |
| l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL); |
| |
| j->jointype = JOIN_LEFT; |
| |
| /* |
| * transform the previous clause |
| */ |
| j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, |
| &l_nsitem, l_alias); |
| pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); |
| |
| /* |
| * Get the merge path now. This is the only moment where it is simple |
| * to know if a variable was declared in the MERGE clause or a previous |
| * clause. Unlike create, we do not add these missing variables to the |
| * targetList, we just create all the metadata necessary to make the |
| * potentially missing parts of the path. |
| */ |
| merge_path = transform_cypher_merge_path(cpstate, &query->targetList, |
| path); |
| |
| /* |
| * Transform this MERGE clause as a match clause, mark the parsestate |
| * with the flag that a lateral join is active |
| */ |
| pstate->p_lateral_active = true; |
| tmp = pstate->p_expr_kind; |
| pstate->p_expr_kind = EXPR_KIND_OTHER; |
| |
| // transform MERGE |
| j->rarg = transform_clause_for_join(cpstate, isolated_merge_clause, &r_rte, |
| &r_nsitem, r_alias); |
| |
| /* |
| * Since this is a left join, we need to mark j->rarg as it may potentially |
| * emit NULL. The jindex argument holds rtindex of the join's RTE, which is |
| * created right after j->arg's RTE in this case. |
| */ |
| markRelsAsNulledBy(pstate, j->rarg, r_nsitem->p_rtindex + 1); |
| |
| // deactivate the lateral flag |
| pstate->p_lateral_active = false; |
| |
| pstate->p_namespace = NIL; |
| |
| /* |
| * Resolve the column names and variables between the two subqueries, |
| * in most cases, we can expect there to be overlap |
| */ |
| get_res_cols(pstate, l_nsitem, r_nsitem, &res_colnames, &res_colvars); |
| |
| // make the RTE for the join |
| jnsitem = addRangeTableEntryForJoin(pstate, res_colnames, NULL, j->jointype, |
| 0, res_colvars, NIL, NIL, j->alias, |
| NULL, true); |
| |
| j->rtindex = jnsitem->p_rtindex; |
| |
| /* |
| * The index of a node in the p_joinexpr list is expected to match the |
| * rtindex the join expression is for. Add NULLs for all the previous |
| * rtindexes and add the JoinExpr. |
| */ |
| for (i = list_length(pstate->p_joinexprs) + 1; i < j->rtindex; i++) |
| { |
| pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL); |
| } |
| |
| pstate->p_joinexprs = lappend(pstate->p_joinexprs, j); |
| |
| Assert(list_length(pstate->p_joinexprs) == j->rtindex); |
| |
| pstate->p_joinlist = lappend(pstate->p_joinlist, j); |
| |
| pstate->p_expr_kind = tmp; |
| |
| /* add jnsitem to column namespace only */ |
| addNSItemToQuery(pstate, jnsitem, false, true, true); |
| |
| /* |
| * Create the targetList from the joined subqueries, add everything. |
| */ |
| query->targetList = list_concat(query->targetList, |
| make_target_list_from_join(pstate, jnsitem->p_rte)); |
| |
| /* |
| * For the metadata need to create paths, find the tuple position that |
| * will represent the entity in the execution phase. |
| */ |
| transform_cypher_merge_mark_tuple_position(cpstate, query->targetList, |
| merge_path); |
| |
| return merge_path; |
| } |
| |
| /* |
| * Iterate through the path and find the TargetEntry in the target_list |
| * that each cypher_target_node is referencing. Add the volatile wrapper |
| * function to keep the optimizer from removing the TargetEntry. |
| */ |
| static void |
| transform_cypher_merge_mark_tuple_position(cypher_parsestate *cpstate, |
| List *target_list, |
| cypher_create_path *path) |
| { |
| ListCell *lc = NULL; |
| |
| if (path->var_name) |
| { |
| TargetEntry *te = findTarget(target_list, path->var_name); |
| |
| /* |
| * Add the volatile wrapper function around the expression, this |
| * ensures the optimizer will not remove the expression, if nothing |
| * other than a private data structure needs it. |
| */ |
| te->expr = add_volatile_wrapper(te->expr); |
| |
| // Mark the tuple position the target_node is for. |
| path->path_attr_num = te->resno; |
| } |
| |
| foreach (lc, path->target_nodes) |
| { |
| cypher_target_node *node = lfirst(lc); |
| |
| TargetEntry *te = findTarget(target_list, node->variable_name); |
| |
| /* |
| * Add the volatile wrapper function around the expression, this |
| * ensures the optimizer will not remove the expression, if nothing |
| * other than a private data structure needs it. |
| */ |
| te->expr = add_volatile_wrapper(te->expr); |
| |
| // Mark the tuple position the target_node is for. |
| node->tuple_position = te->resno; |
| } |
| |
| /* Iterate through the entities wrapping Var nodes with the volatile |
| * wrapper, if not already done. |
| * |
| * NOTE: add_volatile_wrapper function will not wrap itself so the following |
| * is safe to do. |
| * |
| * TODO: This ideally needs to be rewritten using a walker, to be more |
| * selective. However, walkers are tricky and take time to set up. For |
| * now, we brute force it. It is already restricted to explicitly |
| * named variables. |
| * |
| * TODO: We need to understand why add_volatile_wrapper is needed. Meaning, |
| * we need to understand why variables are removed by the function |
| * remove_unused_subquery_outputs. It "appears" that some linkage may |
| * not be set up properly, not allowing the PG logic to see that a |
| * variable is used from a previous clause. Right now, the volatile |
| * wrapper will suffice, but it is still a hack imho. |
| * |
| * TODO: There may be other locations where something similar may need to be |
| * done. This needs to be researched. |
| */ |
| foreach (lc, cpstate->entities) |
| { |
| transform_entity *te = lfirst(lc); |
| Node *node = (Node*) te->entity.node; |
| char *name = NULL; |
| |
| if (is_ag_node(node, cypher_node)) |
| { |
| name = te->entity.node->parsed_name; |
| } |
| else if (is_ag_node(node, cypher_relationship)) |
| { |
| name = te->entity.rel->parsed_name; |
| } |
| else if (is_ag_node(node, cypher_path)) |
| { |
| name = te->entity.path->parsed_var_name; |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DATA_EXCEPTION), |
| errmsg("unexpected transform_entity entity type"))); |
| } |
| |
| /* node needs to have a parsed_name, meaning a name from the query */ |
| if (name != NULL) |
| { |
| TargetEntry *tle = findTarget(target_list, name); |
| |
| if (tle != NULL && IsA(tle->expr, Var)) |
| { |
| tle->expr = add_volatile_wrapper(tle->expr); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Helper function to return a shallow copy of an existing, already transformed, |
| * matching variable. The copy returned will be flagged as such. If none are |
| * found, it will return NULL. If it finds a mismatched type, it will error |
| * stating that. |
| */ |
| static cypher_target_node *get_referenced_variable(ParseState *pstate, |
| Node *node, |
| List *transformed_path) |
| { |
| ListCell *lc = NULL; |
| char *node_name = NULL; |
| char *node_label = NULL; |
| char node_type = 0; |
| int node_loc = -1; |
| |
| /* passed node should only be a vertex or an edge */ |
| Assert(is_ag_node(node, cypher_node) || |
| is_ag_node(node, cypher_relationship)); |
| |
| /* set up our search based on our input type */ |
| if (is_ag_node(node, cypher_node)) |
| { |
| node_name = ((cypher_node *)node)->name; |
| node_label = ((cypher_node *)node)->label; |
| node_loc = ((cypher_node *)node)->location; |
| node_type = 'v'; |
| } |
| else |
| { |
| node_name = ((cypher_relationship *)node)->name; |
| node_label = ((cypher_relationship *)node)->label; |
| node_loc = ((cypher_relationship *)node)->location; |
| node_type = 'e'; |
| } |
| |
| /* look through the list of previously transformed nodes and edges */ |
| foreach (lc, transformed_path) |
| { |
| cypher_target_node *ctn = NULL; |
| bool is_name = false; |
| bool is_label = false; |
| |
| /* list items should be of type cypher_target_node */ |
| Assert(is_ag_node(lfirst(lc), cypher_target_node)); |
| ctn = lfirst(lc); |
| |
| /* do they have names? if so, do they match? */ |
| is_name = (node_name == NULL || ctn->variable_name == NULL) ? |
| false : strcmp(node_name, ctn->variable_name) == 0; |
| |
| /* do they have labels? if so, do they match? */ |
| is_label = (ctn->label_name != NULL) ? |
| ((node_label == NULL) ? true : strcmp(ctn->label_name, node_label) == 0) |
| : false; |
| |
| /* if the types don't match, error or skip */ |
| if (node_type != ctn->type) |
| { |
| /* is the name a match, generate an error. otherwise, skip it. */ |
| if (is_name) |
| { |
| if (node_type == 'v') |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("variable \"%s\" is for a edge", |
| node_name), |
| parser_errposition(pstate, node_loc))); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("variable \"%s\" is for an vertex", |
| node_name), |
| parser_errposition(pstate, node_loc))); |
| } |
| } |
| else |
| { |
| continue; |
| } |
| } |
| |
| if (is_name && !is_label) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("multiple labels for variable '%s' are not supported", |
| node_name), |
| parser_errposition(pstate, node_loc))); |
| } |
| |
| /* |
| * If this is a match, make a shallow copy of it, modify the copy to be |
| * flagged as a previously declared variable, and then return it. |
| */ |
| if (is_name && is_label) |
| { |
| cypher_target_node *_cpy = make_ag_node(cypher_target_node); |
| |
| /* make a shallow copy */ |
| _cpy->type = ctn->type; |
| _cpy->flags = ctn->flags; |
| _cpy->dir = ctn->dir; |
| _cpy->id_expr = ctn->id_expr; |
| _cpy->id_expr_state = ctn->id_expr_state; |
| _cpy->prop_expr = ctn->prop_expr; |
| _cpy->prop_expr_state = ctn->prop_expr_state; |
| _cpy->prop_attr_num = ctn->prop_attr_num; |
| _cpy->resultRelInfo = ctn->resultRelInfo; |
| _cpy->elemTupleSlot = ctn->elemTupleSlot; |
| _cpy->relid = ctn->relid; |
| _cpy->label_name = ctn->label_name; |
| _cpy->variable_name = ctn->variable_name; |
| _cpy->tuple_position = ctn->tuple_position; |
| |
| /* set it to a declared variable */ |
| _cpy->flags &= 0xfffffffe; |
| _cpy->flags |= EXISTING_VARIABLE_DECLARED_SAME_CLAUSE; |
| |
| return _cpy; |
| } |
| } |
| return NULL; |
| } |
| |
| /* |
| * Creates the target nodes for a merge path. If MERGE has a path that doesn't |
| * exist then in the MERGE clause we act like a CREATE clause. This function |
| * sets up the metadata needed for that process. |
| */ |
| static cypher_create_path * |
| transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, |
| cypher_path *path) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| ListCell *lc, *prev_node = NULL; |
| List *transformed_path = NIL; |
| cypher_create_path *ccp = make_ag_node(cypher_create_path); |
| bool in_path = path->var_name != NULL; |
| |
| ccp->path_attr_num = InvalidAttrNumber; |
| |
| foreach (lc, path->path) |
| { |
| if (is_ag_node(lfirst(lc), cypher_node)) |
| { |
| cypher_node *node = lfirst(lc); |
| cypher_target_node *rel = NULL; |
| ListCell *next_node = lnext(path->path, lc); |
| |
| if (path->var_name != NULL && node->name != NULL && |
| strcmp(path->var_name, node->name) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("variable \"%s\" is for a path", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| |
| /* |
| * If the variable was already transformed, get a referenced copy of |
| * it. This copy will make sure the executor phase doesn't create a |
| * new node from it. |
| */ |
| rel = get_referenced_variable(pstate, (Node *)node, |
| transformed_path); |
| |
| /* if there wasn't a transformed variable, transform the node */ |
| if (rel == NULL) |
| { |
| rel = transform_merge_cypher_node(cpstate, target_list, node, |
| (next_node || prev_node)); |
| } |
| |
| if (in_path) |
| { |
| rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; |
| } |
| |
| transformed_path = lappend(transformed_path, rel); |
| } |
| else if (is_ag_node(lfirst(lc), cypher_relationship)) |
| { |
| cypher_relationship *edge = NULL; |
| cypher_target_node *rel = NULL; |
| |
| edge = lfirst(lc); |
| |
| if (path->var_name != NULL && edge->name != NULL && |
| strcmp(path->var_name, edge->name) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("variable \"%s\" is for a path", edge->name), |
| parser_errposition(pstate, edge->location))); |
| } |
| |
| /* |
| * Get a referenced edge variable. This should not happen as edges |
| * can not be duplicated within a path. |
| */ |
| rel = get_referenced_variable(pstate, (Node *)edge, |
| transformed_path); |
| if (rel != NULL) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("a duplicate edge variable \"%s\" is not permitted within a path", |
| edge->name), |
| parser_errposition(pstate, edge->location))); |
| } |
| |
| /* transform the edge */ |
| rel = transform_merge_cypher_edge(cpstate, target_list, edge); |
| |
| if (in_path) |
| { |
| rel->flags |= CYPHER_TARGET_NODE_IN_PATH_VAR; |
| } |
| |
| transformed_path = lappend(transformed_path, rel); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errmsg_internal("unrecognized node in create pattern"))); |
| } |
| prev_node = lc; |
| } |
| |
| // store the path's variable name |
| if (path->var_name) |
| { |
| ccp->var_name = path->var_name; |
| } |
| |
| ccp->target_nodes = transformed_path; |
| |
| return ccp; |
| } |
| |
| /* |
| * Transforms the parse cypher_relationship to a target_entry for merge. |
| * All edges that have variables assigned in a merge must be declared in |
| * the merge. Throw an error otherwise. |
| */ |
| static cypher_target_node * |
| transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, |
| cypher_relationship *edge) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_target_node *rel = make_ag_node(cypher_target_node); |
| Relation label_relation; |
| RangeVar *rv; |
| RTEPermissionInfo *rte_pi; |
| ParseNamespaceItem *pnsi; |
| |
| if (edge->name != NULL) |
| { |
| transform_entity *entity = find_transform_entity(cpstate, edge->name, |
| ENT_EDGE); |
| |
| // We found a variable with this variable name, throw an error. |
| if (entity || variable_exists(cpstate, edge->name)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable %s already exists", edge->name), |
| parser_errposition(pstate, edge->location))); |
| } |
| |
| rel->flags |= CYPHER_TARGET_NODE_IS_VAR; |
| } |
| else |
| { |
| // assign a default variable name. |
| edge->name = get_next_default_alias(cpstate); |
| } |
| |
| rel->type = LABEL_KIND_EDGE; |
| |
| // all edges are marked with insert |
| rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; |
| rel->label_name = edge->label; |
| rel->variable_name = edge->name; |
| rel->resultRelInfo = NULL; |
| |
| rel->dir = edge->dir; |
| |
| if (!edge->label) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("edges declared in a MERGE clause must have a label"), |
| parser_errposition(&cpstate->pstate, edge->location))); |
| } |
| |
| |
| // check to see if the label exists, create the label entry if it does not. |
| if (edge->label && !label_exists(edge->label, cpstate->graph_oid)) |
| { |
| List *parent; |
| /* |
| * setup the default edge table as the parent table, that we |
| * will inherit from. |
| */ |
| rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, |
| AG_DEFAULT_LABEL_EDGE); |
| |
| parent = list_make1(rv); |
| |
| // create the label |
| create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, |
| parent); |
| } |
| |
| // lock the relation of the label |
| rv = makeRangeVar(cpstate->graph_name, edge->label, -1); |
| label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); |
| |
| /* |
| * TODO |
| * It is possible for a vertex label to be retrieved, instead of an edge, |
| * due to the above logic. So, we need to check if it is a vertex label. |
| * This whole section needs to be fixed because it could be a relation that |
| * isn't either and has the correct number of columns. However, for now, |
| * we just check the number of columns. |
| */ |
| if (label_relation->rd_att->natts == 2) // TODO temporarily hardcoded |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Expecting edge label, found existing vertex label"), |
| parser_errposition(&cpstate->pstate, edge->location))); |
| } |
| |
| // Store the relid |
| rel->relid = RelationGetRelid(label_relation); |
| |
| pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, |
| AccessShareLock, NULL, false, false); |
| rte_pi = pnsi->p_perminfo; |
| rte_pi->requiredPerms = ACL_INSERT; |
| |
| // Build Id expression, always use the default logic |
| rel->id_expr = (Expr *)build_column_default(label_relation, |
| Anum_ag_label_edge_table_id); |
| |
| rel->prop_expr = cypher_create_properties(cpstate, rel, label_relation, |
| edge->props, ENT_EDGE); |
| |
| // Keep the lock |
| table_close(label_relation, NoLock); |
| |
| return rel; |
| } |
| |
| /* |
| * Function for creating the metadata MERGE will need if MERGE does not find |
| * a path to exist |
| */ |
| static cypher_target_node * |
| transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, |
| cypher_node *node, bool has_edge) |
| { |
| ParseState *pstate = (ParseState *)cpstate; |
| cypher_target_node *rel = make_ag_node(cypher_target_node); |
| Relation label_relation; |
| RangeVar *rv; |
| RTEPermissionInfo *rte_pi; |
| ParseNamespaceItem *pnsi; |
| |
| if (node->name != NULL) |
| { |
| transform_entity *entity = find_transform_entity(cpstate, node->name, |
| ENT_VERTEX); |
| bool var_exists = variable_exists(cpstate, node->name); |
| /* |
| * the vertex was previously declared, we do not need to do any setup |
| * to create the node. |
| */ |
| if (entity && var_exists) |
| { |
| if (!has_edge) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable %s already exists", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| |
| rel->type = LABEL_KIND_VERTEX; |
| rel->tuple_position = InvalidAttrNumber; |
| rel->variable_name = node->name; |
| rel->resultRelInfo = NULL; |
| |
| rel->flags |= CYPHER_TARGET_NODE_MERGE_EXISTS; |
| return rel; |
| } |
| else if (var_exists && !has_edge) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("variable %s already exists", node->name), |
| parser_errposition(pstate, node->location))); |
| } |
| rel->flags |= CYPHER_TARGET_NODE_IS_VAR; |
| } |
| else |
| { |
| // assign a default variable name. |
| node->name = get_next_default_alias(cpstate); |
| } |
| |
| rel->type = LABEL_KIND_VERTEX; |
| rel->tuple_position = InvalidAttrNumber; |
| rel->variable_name = node->name; |
| rel->resultRelInfo = NULL; |
| |
| if (!node->label) |
| { |
| rel->label_name = ""; |
| /* |
| * If no label is specified, assign the generic label name that |
| * all labels are descendents of. |
| */ |
| node->label = AG_DEFAULT_LABEL_VERTEX; |
| } |
| else |
| { |
| rel->label_name = node->label; |
| } |
| |
| // check to see if the label exists, create the label entry if it does not. |
| if (node->label && !label_exists(node->label, cpstate->graph_oid)) |
| { |
| List *parent; |
| |
| /* |
| * setup the default vertex table as the parent table, that we |
| * will inherit from. |
| */ |
| rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, |
| AG_DEFAULT_LABEL_VERTEX); |
| |
| parent = list_make1(rv); |
| |
| // create the label |
| create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, |
| parent); |
| } |
| |
| rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; |
| |
| rv = makeRangeVar(cpstate->graph_name, node->label, -1); |
| label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); |
| |
| /* |
| * TODO |
| * It is possible for an edge label to be retrieved, instead of a vertex, |
| * due to the above logic. So, we need to check if it is an edge label. |
| * This whole section needs to be fixed because it could be a relation that |
| * isn't either and has the correct number of columns. However, for now, |
| * we just check the number of columns. |
| */ |
| if (label_relation->rd_att->natts == 4) // TODO temporarily hardcoded |
| { |
| ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Expecting vertex label, found existing edge label"), |
| parser_errposition(&cpstate->pstate, node->location))); |
| } |
| |
| // Store the relid |
| rel->relid = RelationGetRelid(label_relation); |
| |
| pnsi = addRangeTableEntryForRelation((ParseState *)cpstate, label_relation, |
| AccessShareLock, NULL, false, false); |
| |
| rte_pi = pnsi->p_perminfo; |
| rte_pi->requiredPerms = ACL_INSERT; |
| |
| // id |
| rel->id_expr = (Expr *)build_column_default(label_relation, |
| Anum_ag_label_vertex_table_id); |
| |
| rel->prop_expr = cypher_create_properties(cpstate, rel, label_relation, |
| node->props, ENT_VERTEX); |
| |
| table_close(label_relation, NoLock); |
| |
| return rel; |
| } |
| |
| /* |
| * Takes a MERGE parse node and converts it to a MATCH parse node |
| */ |
| static cypher_clause *convert_merge_to_match(cypher_merge *merge) |
| { |
| cypher_match *match = make_ag_node(cypher_match); |
| cypher_clause *clause = palloc(sizeof(cypher_clause)); |
| |
| // match supports multiple paths, whereas merge only supports one. |
| match->pattern = list_make1(merge->path); |
| // MERGE does not support where |
| match->where = NULL; |
| |
| /* |
| * We do not want the transform logic to transform the previous clauses |
| * with this, just handle this one clause. |
| */ |
| clause->prev = NULL; |
| clause->self = (Node *)match; |
| clause->next = NULL; |
| |
| return clause; |
| } |
| |
| /* |
| * Creates a namespace item for the given rte. boolean arguments will |
| * let the rest of the ParseState know if the relation and/or columns are |
| * visible, whether the rte is only usable in lateral joins, and if the rte |
| * is accessible in lateral joins. |
| */ |
| static ParseNamespaceItem *get_namespace_item(ParseState *pstate, |
| RangeTblEntry *rte) |
| { |
| ParseNamespaceItem *nsitem = NULL; |
| ListCell *l; |
| |
| foreach(l, pstate->p_namespace) |
| { |
| nsitem = lfirst(l); |
| if (rte == nsitem->p_rte) |
| { |
| return nsitem; |
| } |
| } |
| Assert(nsitem != NULL); |
| return NULL; |
| } |
| |
| /* |
| * Creates the function expression that represents the clause. Adds the |
| * extensible node that represents the metadata that the clause needs to |
| * handle the clause in the execution phase. |
| */ |
| static FuncExpr *make_clause_func_expr(char *function_name, |
| Node *clause_information) |
| { |
| Const *clause_information_const; |
| Oid func_oid; |
| FuncExpr *func_expr; |
| |
| StringInfo str = makeStringInfo(); |
| /* |
| * Serialize the clause_information data structure. In certain |
| * cases (Prepared Statements and PL/pgsql), the MemoryContext that |
| * it is stored in will be destroyed. We need to get it into a format |
| * that Postgres' can copy between MemoryContexts. Just making it into |
| * an ExtensibleNode does not work, because there are certain parts of |
| * Postgres that cannot handle an ExtensibleNode in a function call. |
| * So we serialize the data structure and place it into a Const node |
| * that can handle these situations AND be copied correctly. |
| */ |
| outNode(str, clause_information); |
| |
| clause_information_const = makeConst(INTERNALOID, -1, InvalidOid, str->len, |
| PointerGetDatum(str->data), false, false); |
| |
| func_oid = get_ag_func_oid(function_name, 1, INTERNALOID); |
| |
| func_expr = makeFuncExpr(func_oid, AGTYPEOID, |
| list_make1(clause_information_const), InvalidOid, |
| InvalidOid, COERCE_EXPLICIT_CALL); |
| |
| return func_expr; |
| } |
| |
| /* |
| * This function is borrowed from PG version 16.1. |
| * |
| * It is used in transformations involving left join in Optional Match and |
| * Merge in a similar way PG16's transformFromClauseItem() uses it. |
| */ |
| static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex) |
| { |
| int varno; |
| ListCell *lc; |
| |
| /* Note: we can't see FromExpr here */ |
| if (IsA(n, RangeTblRef)) |
| { |
| varno = ((RangeTblRef *) n)->rtindex; |
| } |
| else if (IsA(n, JoinExpr)) |
| { |
| JoinExpr *j = (JoinExpr *) n; |
| |
| /* recurse to children */ |
| markRelsAsNulledBy(pstate, j->larg, jindex); |
| markRelsAsNulledBy(pstate, j->rarg, jindex); |
| varno = j->rtindex; |
| } |
| else |
| { |
| elog(ERROR, "unrecognized node type: %d", (int) nodeTag(n)); |
| varno = 0; /* keep compiler quiet */ |
| } |
| |
| /* |
| * Now add jindex to the p_nullingrels set for relation varno. Since we |
| * maintain the p_nullingrels list lazily, we might need to extend it to |
| * make the varno'th entry exist. |
| */ |
| while (list_length(pstate->p_nullingrels) < varno) |
| { |
| pstate->p_nullingrels = lappend(pstate->p_nullingrels, NULL); |
| } |
| lc = list_nth_cell(pstate->p_nullingrels, varno - 1); |
| lfirst(lc) = bms_add_member((Bitmapset *) lfirst(lc), jindex); |
| } |
| |
| /* |
| * Utility function that helps a clause add the information needed to |
| * the query from the previous clause. |
| */ |
| static void handle_prev_clause(cypher_parsestate *cpstate, Query *query, |
| cypher_clause *clause, bool first_rte) |
| { |
| ParseState *pstate = (ParseState *) cpstate; |
| int rtindex; |
| ParseNamespaceItem *pnsi; |
| |
| pnsi = transform_prev_cypher_clause(cpstate, clause, true); |
| |
| rtindex = list_length(pstate->p_rtable); |
| |
| // rte is the first RangeTblEntry in pstate |
| if (first_rte) |
| { |
| if (rtindex != 1) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("invalid value for rtindex"))); |
| } |
| } |
| |
| // add all the rte's attributes to the current queries targetlist |
| query->targetList = expandNSItemAttrs(pstate, pnsi, 0, true, -1); |
| } |
| |
| ParseNamespaceItem *find_pnsi(cypher_parsestate *cpstate, char *varname) |
| { |
| ParseState *pstate = (ParseState *) cpstate; |
| ListCell *lc; |
| |
| foreach (lc, pstate->p_namespace) |
| { |
| ParseNamespaceItem *pnsi = (ParseNamespaceItem *)lfirst(lc); |
| Alias *alias = pnsi->p_rte->alias; |
| if (!alias) |
| { |
| continue; |
| } |
| |
| if (!strcmp(alias->aliasname, varname)) |
| { |
| return pnsi; |
| } |
| } |
| |
| return NULL; |
| } |