/* * 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 "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_func.h" #include "parser/cypher_clause.h" #include "parser/parse_oper.h" #include "parser/parse_relation.h" #include "utils/builtins.h" #include "utils/float.h" #include "utils/int8.h" #include "utils/lsyscache.h" #include "parser/cypher_expr.h" #include "parser/cypher_transform_entity.h" #include "utils/ag_func.h" #include "utils/agtype.h" /* names of typecast functions */ #define FUNC_AGTYPE_TYPECAST_EDGE "agtype_typecast_edge" #define FUNC_AGTYPE_TYPECAST_PATH "agtype_typecast_path" #define FUNC_AGTYPE_TYPECAST_VERTEX "agtype_typecast_vertex" #define FUNC_AGTYPE_TYPECAST_NUMERIC "agtype_typecast_numeric" #define FUNC_AGTYPE_TYPECAST_FLOAT "agtype_typecast_float" #define FUNC_AGTYPE_TYPECAST_INT "agtype_typecast_int" #define FUNC_AGTYPE_TYPECAST_PG_FLOAT8 "agtype_to_float8" #define FUNC_AGTYPE_TYPECAST_PG_BIGINT "agtype_to_int8" #define FUNC_AGTYPE_TYPECAST_BOOL "agtype_typecast_bool" static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr); static Node *transform_A_Const(cypher_parsestate *cpstate, A_Const *ac); static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref); static Node *transform_A_Indirection(cypher_parsestate *cpstate, A_Indirection *a_ind); static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a); static Node *transform_cypher_comparison_aexpr_OP(cypher_parsestate *cpstate, cypher_comparison_aexpr *a); static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr); static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate, cypher_comparison_boolexpr *b); static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, cypher_bool_const *bc); static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, cypher_integer_const *ic); static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a); static Node *transform_cypher_param(cypher_parsestate *cpstate, cypher_param *cp); static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm); static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl); static Node *transform_cypher_string_match(cypher_parsestate *cpstate, cypher_string_match *csm_node); static Node *transform_cypher_typecast(cypher_parsestate *cpstate, cypher_typecast *ctypecast); static Node *transform_CaseExpr(cypher_parsestate *cpstate, CaseExpr *cexpr); static Node *transform_CoalesceExpr(cypher_parsestate *cpstate, CoalesceExpr *cexpr); static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink); static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn); static Node *transform_WholeRowRef(ParseState *pstate, ParseNamespaceItem *pnsi, int location, int sublevels_up); static ArrayExpr *make_agtype_array_expr(List *args); static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, ColumnRef *cr); /* transform a cypher expression */ Node *transform_cypher_expr(cypher_parsestate *cpstate, Node *expr, ParseExprKind expr_kind) { ParseState *pstate = (ParseState *)cpstate; ParseExprKind old_expr_kind; Node *result; // save and restore identity of expression type we're parsing Assert(expr_kind != EXPR_KIND_NONE); old_expr_kind = pstate->p_expr_kind; pstate->p_expr_kind = expr_kind; result = transform_cypher_expr_recurse(cpstate, expr); pstate->p_expr_kind = old_expr_kind; return result; } static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr) { if (!expr) return NULL; // guard against stack overflow due to overly complex expressions check_stack_depth(); switch (nodeTag(expr)) { case T_A_Const: return transform_A_Const(cpstate, (A_Const *)expr); case T_ColumnRef: return transform_ColumnRef(cpstate, (ColumnRef *)expr); case T_A_Indirection: return transform_A_Indirection(cpstate, (A_Indirection *)expr); case T_A_Expr: { A_Expr *a = (A_Expr *)expr; switch (a->kind) { case AEXPR_OP: return transform_AEXPR_OP(cpstate, a); case AEXPR_IN: return transform_AEXPR_IN(cpstate, a); default: ereport(ERROR, (errmsg_internal("unrecognized A_Expr kind: %d", a->kind))); } break; } case T_BoolExpr: return transform_BoolExpr(cpstate, (BoolExpr *)expr); case T_NullTest: { NullTest *n = (NullTest *)expr; NullTest *transformed_expr = makeNode(NullTest); transformed_expr->arg = (Expr *)transform_cypher_expr_recurse(cpstate, (Node *)n->arg); transformed_expr->nulltesttype = n->nulltesttype; transformed_expr->argisrow = type_is_rowtype(exprType((Node *)transformed_expr->arg)); transformed_expr->location = n->location; return (Node *) transformed_expr; } case T_CaseExpr: return transform_CaseExpr(cpstate, (CaseExpr *) expr); case T_CaseTestExpr: return expr; case T_CoalesceExpr: return transform_CoalesceExpr(cpstate, (CoalesceExpr *) expr); case T_ExtensibleNode: if (is_ag_node(expr, cypher_bool_const)) return transform_cypher_bool_const(cpstate, (cypher_bool_const *)expr); if (is_ag_node(expr, cypher_integer_const)) return transform_cypher_integer_const(cpstate, (cypher_integer_const *)expr); if (is_ag_node(expr, cypher_param)) return transform_cypher_param(cpstate, (cypher_param *)expr); if (is_ag_node(expr, cypher_map)) return transform_cypher_map(cpstate, (cypher_map *)expr); if (is_ag_node(expr, cypher_list)) return transform_cypher_list(cpstate, (cypher_list *)expr); if (is_ag_node(expr, cypher_string_match)) return transform_cypher_string_match(cpstate, (cypher_string_match *)expr); if (is_ag_node(expr, cypher_typecast)) return transform_cypher_typecast(cpstate, (cypher_typecast *)expr); if (is_ag_node(expr, cypher_comparison_aexpr)) return transform_cypher_comparison_aexpr_OP(cpstate, (cypher_comparison_aexpr *)expr); if (is_ag_node(expr, cypher_comparison_boolexpr)) return transform_cypher_comparison_boolexpr(cpstate, (cypher_comparison_boolexpr *)expr); ereport(ERROR, (errmsg_internal("unrecognized ExtensibleNode: %s", ((ExtensibleNode *)expr)->extnodename))); return NULL; case T_FuncCall: return transform_FuncCall(cpstate, (FuncCall *)expr); case T_SubLink: return transform_SubLink(cpstate, (SubLink *)expr); break; default: ereport(ERROR, (errmsg_internal("unrecognized node type: %d", nodeTag(expr)))); } return NULL; } static Node *transform_A_Const(cypher_parsestate *cpstate, A_Const *ac) { ParseState *pstate = (ParseState *)cpstate; ParseCallbackState pcbstate; Value *v = &ac->val; Datum d = (Datum)0; bool is_null = false; Const *c; setup_parser_errposition_callback(&pcbstate, pstate, ac->location); switch (nodeTag(v)) { case T_Integer: d = integer_to_agtype((int64)intVal(v)); break; case T_Float: { char *n = strVal(v); int64 i; if (scanint8(n, true, &i)) { d = integer_to_agtype(i); } else { float8 f = float8in_internal(n, NULL, "double precision", n); d = float_to_agtype(f); } } break; case T_String: d = string_to_agtype(strVal(v)); break; case T_Null: is_null = true; break; default: ereport(ERROR, (errmsg_internal("unrecognized node type: %d", nodeTag(v)))); return NULL; } cancel_parser_errposition_callback(&pcbstate); // typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. c = makeConst(AGTYPEOID, -1, InvalidOid, -1, d, is_null, false); c->location = ac->location; return (Node *)c; } /* * Private function borrowed from PG's transformWholeRowRef. * Construct a whole-row reference to represent the notation "relation.*". */ static Node *transform_WholeRowRef(ParseState *pstate, ParseNamespaceItem *pnsi, int location, int sublevels_up) { Var *result; int vnum; RangeTblEntry *rte; Assert(pnsi->p_rte != NULL); rte = pnsi->p_rte; /* Find the RTE's rangetable location */ vnum = pnsi->p_rtindex; /* * Build the appropriate referencing node. Note that if the RTE is a * function returning scalar, we create just a plain reference to the * function value, not a composite containing a single column. This is * pretty inconsistent at first sight, but it's what we've done * historically. One argument for it is that "rel" and "rel.*" mean the * same thing for composite relations, so why not for scalar functions... */ result = makeWholeRowVar(rte, vnum, sublevels_up, true); /* location is not filled in by makeWholeRowVar */ result->location = location; /* mark relation as requiring whole-row SELECT access */ markVarForSelectPriv(pstate, result); return (Node *)result; } /* * Function to transform a ColumnRef node from the grammar into a Var node * Code borrowed from PG's transformColumnRef. */ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref) { ParseState *pstate = (ParseState *)cpstate; Node *field1 = NULL; Node *field2 = NULL; char *colname = NULL; char *nspname = NULL; char *relname = NULL; Node *node = NULL; ParseNamespaceItem *pnsi = NULL; int levels_up; switch (list_length(cref->fields)) { case 1: { transform_entity *te; field1 = (Node*)linitial(cref->fields); Assert(IsA(field1, String)); colname = strVal(field1); /* Try to identify as an unqualified column */ node = colNameToVar(pstate, colname, false, cref->location); if (node != NULL) { break; } /* * Try to find the columnRef as a transform_entity * and extract the expr. */ te = find_variable(cpstate, colname) ; if (te != NULL && te->expr != NULL && te->declared_in_current_clause) { node = (Node *)te->expr; break; } /* * Not known as a column of any range-table entry. * Try to find the name as a relation. Note that only * relations already entered into the rangetable will be * recognized. * * This is a hack for backwards compatibility with * PostQUEL-inspired syntax. The preferred form now is * "rel.*". */ pnsi = refnameNamespaceItem(pstate, NULL, colname, cref->location, &levels_up); if (pnsi) { node = transform_WholeRowRef(pstate, pnsi, cref->location, levels_up); } else { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("could not find rte for %s", colname), parser_errposition(pstate, cref->location))); } if (node == NULL) { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unable to transform whole row for %s", colname), parser_errposition(pstate, cref->location))); } break; } case 2: { Oid inputTypeId = InvalidOid; Oid targetTypeId = InvalidOid; field1 = (Node*)linitial(cref->fields); field2 = (Node*)lsecond(cref->fields); Assert(IsA(field1, String)); relname = strVal(field1); if (IsA(field2, String)) { colname = strVal(field2); } /* locate the referenced RTE */ pnsi = refnameNamespaceItem(pstate, nspname, relname, cref->location, &levels_up); if (pnsi == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("could not find rte for %s.%s", relname, colname), parser_errposition(pstate, cref->location))); break; } /* * TODO: Left in for potential future use. * Is it a whole-row reference? */ if (IsA(field2, A_Star)) { node = transform_WholeRowRef(pstate, pnsi, cref->location, levels_up); break; } Assert(IsA(field2, String)); /* try to identify as a column of the RTE */ node = scanNSItemForColumn(pstate, pnsi, 0, colname, cref->location); if (node == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("could not find column %s in rel %s of rte", colname, relname), parser_errposition(pstate, cref->location))); } /* coerce it to AGTYPE if possible */ inputTypeId = exprType(node); targetTypeId = AGTYPEOID; if (can_coerce_type(1, &inputTypeId, &targetTypeId, COERCION_EXPLICIT)) { node = coerce_type(pstate, node, inputTypeId, targetTypeId, -1, COERCION_EXPLICIT, COERCE_EXPLICIT_CAST, -1); } break; } default: { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("improper qualified name (too many dotted names): %s", NameListToString(cref->fields)), parser_errposition(pstate, cref->location))); break; } } if (node == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("variable `%s` does not exist", colname), parser_errposition(pstate, cref->location))); } return node; } static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a) { ParseState *pstate = (ParseState *)cpstate; Node *last_srf = pstate->p_last_srf; Node *lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); Node *rexpr = transform_cypher_expr_recurse(cpstate, a->rexpr); return (Node *)make_op(pstate, a->name, lexpr, rexpr, last_srf, a->location); } /* * function for transforming cypher comparision A_Expr. Since this node is a * wrapper to let us know when a comparison occurs in a chained comparison, * we convert it to a regular A_expr and transform it. */ static Node *transform_cypher_comparison_aexpr_OP(cypher_parsestate *cpstate, cypher_comparison_aexpr *a) { A_Expr *n = makeNode(A_Expr); n->kind = a->kind; n->name = a->name; n->lexpr = a->lexpr; n->rexpr = a->rexpr; n->location = a->location; return (Node *)transform_AEXPR_OP(cpstate, n); } static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) { ParseState *pstate = (ParseState *)cpstate; cypher_list *rexpr; Node *result = NULL; Node *lexpr; List *rexprs; List *rvars; List *rnonvars; bool useOr; ListCell *l; if (!is_ag_node(a->rexpr, cypher_list)) { /* * We need to build a function call here if the rexpr is already * tranformed. It can be already tranformed cypher_list as columnref. */ Oid func_in_oid; FuncExpr *func_in_expr; List *args = NIL; args = lappend(args, transform_cypher_expr_recurse(cpstate, a->rexpr)); args = lappend(args, transform_cypher_expr_recurse(cpstate, a->lexpr)); /* get the agtype_in_operator function */ func_in_oid = get_ag_func_oid("agtype_in_operator", 2, AGTYPEOID, AGTYPEOID); func_in_expr = makeFuncExpr(func_in_oid, AGTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); func_in_expr->location = exprLocation(a->lexpr); return (Node *)func_in_expr; } Assert(is_ag_node(a->rexpr, cypher_list)); // If the operator is <>, combine with AND not OR. if (strcmp(strVal(linitial(a->name)), "<>") == 0) { useOr = false; } else { useOr = true; } lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); rexprs = rvars = rnonvars = NIL; rexpr = (cypher_list *)a->rexpr; foreach(l, (List *) rexpr->elems) { Node *rexpr = transform_cypher_expr_recurse(cpstate, lfirst(l)); rexprs = lappend(rexprs, rexpr); if (contain_vars_of_level(rexpr, 0)) { rvars = lappend(rvars, rexpr); } else { rnonvars = lappend(rnonvars, rexpr); } } /* * ScalarArrayOpExpr is only going to be useful if there's more than one * non-Var righthand item. */ if (list_length(rnonvars) > 1) { List *allexprs; Oid scalar_type; List *aexprs; ArrayExpr *newa; allexprs = list_concat(list_make1(lexpr), rnonvars); scalar_type = AGTYPEOID; Assert (verify_common_type(scalar_type, allexprs)); /* * coerce all the right-hand non-Var inputs to the common type * and build an ArrayExpr for them. */ aexprs = NIL; foreach(l, rnonvars) { Node *rexpr = (Node *) lfirst(l); rexpr = coerce_to_common_type(pstate, rexpr, AGTYPEOID, "IN"); aexprs = lappend(aexprs, rexpr); } newa = makeNode(ArrayExpr); newa->array_typeid = get_array_type(AGTYPEOID); /* array_collid will be set by parse_collate.c */ newa->element_typeid = AGTYPEOID; newa->elements = aexprs; newa->multidims = false; result = (Node *) make_scalar_array_op(pstate, a->name, useOr, lexpr, (Node *) newa, a->location); /* Consider only the Vars (if any) in the loop below */ rexprs = rvars; } // Must do it the hard way, with a boolean expression tree. foreach(l, rexprs) { Node *rexpr = (Node *) lfirst(l); Node *cmp; // Ordinary scalar operator cmp = (Node *) make_op(pstate, a->name, copyObject(lexpr), rexpr, pstate->p_last_srf, a->location); cmp = coerce_to_boolean(pstate, cmp, "IN"); if (result == NULL) { result = cmp; } else { result = (Node *) makeBoolExpr(useOr ? OR_EXPR : AND_EXPR, list_make2(result, cmp), a->location); } } return result; } static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr) { ParseState *pstate = (ParseState *)cpstate; List *args = NIL; const char *opname; ListCell *la; switch (expr->boolop) { case AND_EXPR: opname = "AND"; break; case OR_EXPR: opname = "OR"; break; case NOT_EXPR: opname = "NOT"; break; default: ereport(ERROR, (errmsg_internal("unrecognized boolop: %d", (int)expr->boolop))); return NULL; } foreach (la, expr->args) { Node *arg = lfirst(la); arg = transform_cypher_expr_recurse(cpstate, arg); arg = coerce_to_boolean(pstate, arg, opname); args = lappend(args, arg); } return (Node *)makeBoolExpr(expr->boolop, args, expr->location); } /* * function for transforming cypher_comparison_boolexpr. Since this node is a * wrapper to let us know when a comparison occurs in a chained comparison, * we convert it to a PG BoolExpr and transform it. */ static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate, cypher_comparison_boolexpr *b) { BoolExpr *n = makeNode(BoolExpr); n->boolop = b->boolop; n->args = b->args; n->location = b->location; return transform_BoolExpr(cpstate, n); } static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, cypher_bool_const *bc) { ParseState *pstate = (ParseState *)cpstate; ParseCallbackState pcbstate; Datum agt; Const *c; setup_parser_errposition_callback(&pcbstate, pstate, bc->location); agt = boolean_to_agtype(bc->boolean); cancel_parser_errposition_callback(&pcbstate); // typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); c->location = bc->location; return (Node *)c; } static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, cypher_integer_const *ic) { ParseState *pstate = (ParseState *)cpstate; ParseCallbackState pcbstate; Datum agt; Const *c; setup_parser_errposition_callback(&pcbstate, pstate, ic->location); agt = integer_to_agtype(ic->integer); cancel_parser_errposition_callback(&pcbstate); // typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); c->location = ic->location; return (Node *)c; } static Node *transform_cypher_param(cypher_parsestate *cpstate, cypher_param *cp) { ParseState *pstate = (ParseState *)cpstate; Const *const_str; FuncExpr *func_expr; Oid func_access_oid; List *args = NIL; if (!cpstate->params) { ereport( ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg( "parameters argument is missing from cypher() function call"), parser_errposition(pstate, cp->location))); } /* get the agtype_access_operator function */ func_access_oid = get_ag_func_oid("agtype_access_operator", 1, AGTYPEARRAYOID); args = lappend(args, copyObject(cpstate->params)); const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, string_to_agtype(cp->name), false, false); args = lappend(args, const_str); func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); func_expr->location = cp->location; return (Node *)func_expr; } static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm) { ParseState *pstate = (ParseState *)cpstate; List *newkeyvals = NIL; ListCell *le; FuncExpr *fexpr; Oid func_oid; Assert(list_length(cm->keyvals) % 2 == 0); le = list_head(cm->keyvals); while (le != NULL) { Node *key; Node *val; Node *newval; ParseCallbackState pcbstate; Const *newkey; key = lfirst(le); le = lnext(cm->keyvals, le); val = lfirst(le); le = lnext(cm->keyvals, le); newval = transform_cypher_expr_recurse(cpstate, val); setup_parser_errposition_callback(&pcbstate, pstate, cm->location); // typtypmod, typcollation, typlen, and typbyval of agtype are // hard-coded. newkey = makeConst(TEXTOID, -1, InvalidOid, -1, CStringGetTextDatum(strVal(key)), false, false); cancel_parser_errposition_callback(&pcbstate); newkeyvals = lappend(lappend(newkeyvals, newkey), newval); } if (list_length(newkeyvals) == 0) { func_oid = get_ag_func_oid("agtype_build_map", 0); } else if (!cm->keep_null) { func_oid = get_ag_func_oid("agtype_build_map_nonull", 1, ANYOID); } else { func_oid = get_ag_func_oid("agtype_build_map", 1, ANYOID); } fexpr = makeFuncExpr(func_oid, AGTYPEOID, newkeyvals, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); fexpr->location = cm->location; return (Node *)fexpr; } /* * Helper function to transform a cypher list into an agtype list. The function * will use agtype_add to concatenate lists when the number of parameters * exceeds 100, a PG limitation. */ static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) { List *abl_args = NIL; ListCell *le = NULL; FuncExpr *aa_lhs_arg = NULL; FuncExpr *fexpr = NULL; Oid abl_func_oid = InvalidOid; Oid aa_func_oid = InvalidOid; int nelems = 0; int i = 0; /* determine which build function we need */ nelems = list_length(cl->elems); if (nelems == 0) { abl_func_oid = get_ag_func_oid("agtype_build_list", 0); } else { abl_func_oid = get_ag_func_oid("agtype_build_list", 1, ANYOID); } /* get the concat function oid, if necessary */ if (nelems > 100) { aa_func_oid = get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); } /* iterate through the list of elements */ foreach (le, cl->elems) { Node *texpr = NULL; /* transform the argument */ texpr = transform_cypher_expr_recurse(cpstate, lfirst(le)); /* * If we have more than 100 elements we will need to add in the list * concatenation function. */ if (i >= 100) { /* build the list function node argument for concatenate */ fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); fexpr->location = cl->location; /* initial case, set up for concatenating 2 lists */ if (aa_lhs_arg == NULL) { aa_lhs_arg = fexpr; } /* * For every other case, concatenate the list on to the previous * concatenate operation. */ else { List *aa_args = list_make2(aa_lhs_arg, fexpr); fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); fexpr->location = cl->location; /* set the lhs to the concatenation operation */ aa_lhs_arg = fexpr; } /* reset */ abl_args = NIL; i = 0; fexpr = NULL; } /* now add the latest transformed expression to the list */ abl_args = lappend(abl_args, texpr); i++; } /* now build the final list function */ fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); fexpr->location = cl->location; /* * If there was a previous concatenation or list function, build a final * concatenation function node */ if (aa_lhs_arg != NULL) { List *aa_args = list_make2(aa_lhs_arg, fexpr); fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } return (Node *)fexpr; } // makes a VARIADIC agtype array static ArrayExpr *make_agtype_array_expr(List *args) { ArrayExpr *newa = makeNode(ArrayExpr); newa->elements = args; /* assume all the variadic arguments were coerced to the same type */ newa->element_typeid = AGTYPEOID; newa->array_typeid = AGTYPEARRAYOID; if (!OidIsValid(newa->array_typeid)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not find array type for data type %s", format_type_be(newa->element_typeid)))); } /* array_collid will be set by parse_collate.c */ newa->multidims = false; return newa; } /* * Transform a ColumnRef for indirection. Try to find the rte that the ColumnRef * references and pass the properties of that rte as what the ColumnRef is * referencing. Otherwise, reference the Var. */ static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, ColumnRef *cr) { ParseState *pstate = (ParseState *)cpstate; ParseNamespaceItem *pnsi = NULL; Node *field1 = linitial(cr->fields); char *relname = NULL; Node *node = NULL; int levels_up = 0; Assert(IsA(field1, String)); relname = strVal(field1); /* locate the referenced RTE (used to be find_rte(cpstate, relname)) */ pnsi = refnameNamespaceItem(pstate, NULL, relname, cr->location, &levels_up); /* * If we didn't find anything, try looking for a previous variable * reference. Otherwise, return NULL (colNameToVar will return NULL * if nothing is found). */ if (!pnsi) { Node *prev_var = colNameToVar(pstate, relname, false, cr->location); return prev_var; } /* find the properties column of the NSI and return a var for it */ node = scanNSItemForColumn(pstate, pnsi, 0, "properties", cr->location); /* * Error out if we couldn't find it. * * TODO: Should we error out or return NULL for further processing? * For now, just error out. */ if (!node) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not find properties for %s", relname))); } return node; } static Node *transform_A_Indirection(cypher_parsestate *cpstate, A_Indirection *a_ind) { int location; ListCell *lc = NULL; Node *ind_arg_expr = NULL; FuncExpr *func_expr = NULL; Oid func_access_oid = InvalidOid; Oid func_slice_oid = InvalidOid; List *args = NIL; bool is_access = false; /* validate that we have an indirection with at least 1 entry */ Assert(a_ind != NULL && list_length(a_ind->indirection)); /* get the agtype_access_operator function */ func_access_oid = get_ag_func_oid("agtype_access_operator", 1, AGTYPEARRAYOID); /* get the agtype_access_slice function */ func_slice_oid = get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID, AGTYPEOID, AGTYPEOID); /* * If the indirection argument is a ColumnRef, we want to pull out the * properties, as a var node, if possible. */ if (IsA(a_ind->arg, ColumnRef)) { ColumnRef *cr = (ColumnRef *)a_ind->arg; ind_arg_expr = transform_column_ref_for_indirection(cpstate, cr); } /* * If we didn't get the properties from a ColumnRef, just transform the * indirection argument. */ if (ind_arg_expr == NULL) { ind_arg_expr = transform_cypher_expr_recurse(cpstate, a_ind->arg); } /* get the location of the expression */ location = exprLocation(ind_arg_expr); /* add the expression as the first entry */ args = lappend(args, ind_arg_expr); /* iterate through the indirections */ foreach (lc, a_ind->indirection) { Node *node = lfirst(lc); /* is this a slice? */ if (IsA(node, A_Indices) && ((A_Indices *)node)->is_slice) { A_Indices *indices = (A_Indices *)node; /* were we working on an access? if so, wrap and close it */ if (is_access) { ArrayExpr *newa = make_agtype_array_expr(args); func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, list_make1(newa), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); func_expr->funcvariadic = true; func_expr->location = location; /* * The result of this function is the input to the next access * or slice operator. So we need to start out with a new arg * list with this function expression. */ args = lappend(NIL, func_expr); /* we are no longer working on an access */ is_access = false; } /* add slice bounds to args */ if (!indices->lidx) { A_Const *n = makeNode(A_Const); n->val.type = T_Null; n->location = -1; node = transform_cypher_expr_recurse(cpstate, (Node *)n); } else { node = transform_cypher_expr_recurse(cpstate, indices->lidx); } args = lappend(args, node); if (!indices->uidx) { A_Const *n = makeNode(A_Const); n->val.type = T_Null; n->location = -1; node = transform_cypher_expr_recurse(cpstate, (Node *)n); } else { node = transform_cypher_expr_recurse(cpstate, indices->uidx); } args = lappend(args, node); /* wrap and close it */ func_expr = makeFuncExpr(func_slice_oid, AGTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); func_expr->location = location; /* * The result of this function is the input to the next access * or slice operator. So we need to start out with a new arg * list with this function expression. */ args = lappend(NIL, func_expr); } /* is this a string or index?*/ else if (IsA(node, String) || IsA(node, A_Indices)) { /* we are working on an access */ is_access = true; /* is this an index? */ if (IsA(node, A_Indices)) { A_Indices *indices = (A_Indices *)node; node = transform_cypher_expr_recurse(cpstate, indices->uidx); args = lappend(args, node); } /* it must be a string */ else { Const *const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, string_to_agtype(strVal(node)), false, false); args = lappend(args, const_str); } } /* not an indirection we understand */ else { ereport(ERROR, (errmsg("invalid indirection node %d", nodeTag(node)))); } } /* if we were doing an access, we need wrap the args with access func. */ if (is_access) { ArrayExpr *newa = make_agtype_array_expr(args); func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, list_make1(newa), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); func_expr->funcvariadic = true; } Assert(func_expr != NULL); func_expr->location = location; return (Node *)func_expr; } static Node *transform_cypher_string_match(cypher_parsestate *cpstate, cypher_string_match *csm_node) { Node *expr; FuncExpr *func_expr; Oid func_access_oid; List *args = NIL; const char *func_name = NULL; switch (csm_node->operation) { case CSMO_STARTS_WITH: func_name = "agtype_string_match_starts_with"; break; case CSMO_ENDS_WITH: func_name = "agtype_string_match_ends_with"; break; case CSMO_CONTAINS: func_name = "agtype_string_match_contains"; break; default: ereport(ERROR, (errmsg_internal("unknown Cypher string match operation"))); } func_access_oid = get_ag_func_oid(func_name, 2, AGTYPEOID, AGTYPEOID); expr = transform_cypher_expr_recurse(cpstate, csm_node->lhs); args = lappend(args, expr); expr = transform_cypher_expr_recurse(cpstate, csm_node->rhs); args = lappend(args, expr); func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); func_expr->location = csm_node->location; return (Node *)func_expr; } /* * Function to create a typecasting node */ static Node *transform_cypher_typecast(cypher_parsestate *cpstate, cypher_typecast *ctypecast) { List *fname; FuncCall *fnode; /* verify input parameter */ Assert (cpstate != NULL); Assert (ctypecast != NULL); /* create the qualified function name, schema first */ fname = list_make1(makeString("ag_catalog")); /* append the name of the requested typecast function */ if (pg_strcasecmp(ctypecast->typecast, "edge") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_EDGE)); } else if (pg_strcasecmp(ctypecast->typecast, "path") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PATH)); } else if (pg_strcasecmp(ctypecast->typecast, "vertex") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_VERTEX)); } else if (pg_strcasecmp(ctypecast->typecast, "numeric") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_NUMERIC)); } else if (pg_strcasecmp(ctypecast->typecast, "float") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_FLOAT)); } else if (pg_strcasecmp(ctypecast->typecast, "int") == 0 || pg_strcasecmp(ctypecast->typecast, "integer") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_INT)); } else if (pg_strcasecmp(ctypecast->typecast, "pg_float8") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_FLOAT8)); } else if (pg_strcasecmp(ctypecast->typecast, "pg_bigint") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_BIGINT)); } else if ((pg_strcasecmp(ctypecast->typecast, "bool") == 0 || pg_strcasecmp(ctypecast->typecast, "boolean") == 0)) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_BOOL)); } /* if none was found, error out */ else { ereport(ERROR, (errmsg_internal("typecast \'%s\' not supported", ctypecast->typecast))); } /* make a function call node */ fnode = makeFuncCall(fname, list_make1(ctypecast->expr), COERCE_SQL_SYNTAX, ctypecast->location); /* return the transformed function */ return transform_FuncCall(cpstate, fnode); } /* * Code borrowed from PG's transformFuncCall and updated for AGE */ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) { ParseState *pstate = &cpstate->pstate; Node *last_srf = pstate->p_last_srf; List *targs = NIL; List *fname = NIL; ListCell *arg; Node *retval = NULL; /* Transform the list of arguments ... */ foreach(arg, fn->args) { Node *farg = NULL; farg = (Node *)lfirst(arg); targs = lappend(targs, transform_cypher_expr_recurse(cpstate, farg)); } /* within group should not happen */ Assert(!fn->agg_within_group); /* * If the function name is not qualified, then it is one of ours. We need to * construct its name, and qualify it, so that PG can find it. */ if (list_length(fn->funcname) == 1) { /* get the name, size, and the ag name allocated */ char *name = ((Value*)linitial(fn->funcname))->val.str; int pnlen = strlen(name); char *ag_name = palloc(pnlen + 5); int i; /* copy in the prefix - all AGE functions are prefixed with age_ */ strncpy(ag_name, "age_", 4); /* * All AGE function names are in lower case. So, copy in the name * in lower case. */ for (i = 0; i < pnlen; i++) ag_name[i + 4] = tolower(name[i]); /* terminate it with 0 */ ag_name[i + 4] = 0; /* qualify the name with our schema name */ fname = list_make2(makeString("ag_catalog"), makeString(ag_name)); /* * Currently 3 functions need the graph name passed in as the first * argument - in addition to the other arguments: startNode, endNode, * and vle. So, check for those 3 functions here and that the arg list * is not empty. Then prepend the graph name if necessary. */ if ((list_length(targs) != 0) && (strcmp("startNode", name) == 0 || strcmp("endNode", name) == 0 || strcmp("vle", name) == 0 || strcmp("vertex_stats", name) == 0)) { char *graph_name = cpstate->graph_name; Datum d = string_to_agtype(graph_name); Const *c = makeConst(AGTYPEOID, -1, InvalidOid, -1, d, false, false); targs = lcons(c, targs); } } /* If it is not one of our functions, pass the name list through */ else { fname = fn->funcname; } /* ... and hand off to ParseFuncOrColumn */ retval = ParseFuncOrColumn(pstate, fname, targs, last_srf, fn, false, fn->location); /* flag that an aggregate was found during a transform */ if (retval != NULL && retval->type == T_Aggref) { cpstate->exprHasAgg = true; } return retval; } /* * Code borrowed from PG's transformCoalesceExpr and updated for AGE */ static Node *transform_CoalesceExpr(cypher_parsestate *cpstate, CoalesceExpr *cexpr) { ParseState *pstate = &cpstate->pstate; CoalesceExpr *newcexpr = makeNode(CoalesceExpr); Node *last_srf = pstate->p_last_srf; List *newargs = NIL; List *newcoercedargs = NIL; ListCell *args; foreach(args, cexpr->args) { Node *e = (Node *)lfirst(args); Node *newe; newe = transform_cypher_expr_recurse(cpstate, e); newargs = lappend(newargs, newe); } newcexpr->coalescetype = select_common_type(pstate, newargs, "COALESCE", NULL); /* coalescecollid will be set by parse_collate.c */ /* Convert arguments if necessary */ foreach(args, newargs) { Node *e = (Node *)lfirst(args); Node *newe; newe = coerce_to_common_type(pstate, e, newcexpr->coalescetype, "COALESCE"); newcoercedargs = lappend(newcoercedargs, newe); } /* if any subexpression contained a SRF, complain */ if (pstate->p_last_srf != last_srf) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is name of a SQL construct, eg GROUP BY */ errmsg("set-returning functions are not allowed in %s", "COALESCE"), parser_errposition(pstate, exprLocation(pstate->p_last_srf)))); } newcexpr->args = newcoercedargs; newcexpr->location = cexpr->location; return (Node *) newcexpr; } /* * Code borrowed from PG's transformCaseExpr and updated for AGE */ static Node *transform_CaseExpr(cypher_parsestate *cpstate, CaseExpr *cexpr) { ParseState *pstate = &cpstate->pstate; CaseExpr *newcexpr = makeNode(CaseExpr); Node *last_srf = pstate->p_last_srf; Node *arg; CaseTestExpr *placeholder; List *newargs; List *resultexprs; ListCell *l; Node *defresult; Oid ptype; /* transform the test expression, if any */ arg = transform_cypher_expr_recurse(cpstate, (Node *) cexpr->arg); /* generate placeholder for test expression */ if (arg) { if (exprType(arg) == UNKNOWNOID) arg = coerce_to_common_type(pstate, arg, TEXTOID, "CASE"); assign_expr_collations(pstate, arg); placeholder = makeNode(CaseTestExpr); placeholder->typeId = exprType(arg); placeholder->typeMod = exprTypmod(arg); placeholder->collation = exprCollation(arg); } else { placeholder = NULL; } newcexpr->arg = (Expr *) arg; /* transform the list of arguments */ newargs = NIL; resultexprs = NIL; foreach(l, cexpr->args) { CaseWhen *w = lfirst_node(CaseWhen, l); CaseWhen *neww = makeNode(CaseWhen); Node *warg; warg = (Node *) w->expr; if (placeholder) { if(is_ag_node(warg, cypher_comparison_aexpr) || is_ag_node(warg, cypher_comparison_boolexpr) ) { List *funcname = list_make1(makeString("ag_catalog")); funcname = lappend(funcname, makeString("bool_to_agtype")); warg = (Node *) makeFuncCall(funcname, list_make1(warg), COERCE_EXPLICIT_CAST, cexpr->location); } /* shorthand form was specified, so expand... */ warg = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) placeholder, warg, w->location); } neww->expr = (Expr *) transform_cypher_expr_recurse(cpstate, warg); neww->expr = (Expr *) coerce_to_boolean(pstate, (Node *) neww->expr, "CASE/WHEN"); warg = (Node *) w->result; if(is_ag_node(warg, cypher_comparison_aexpr) || is_ag_node(warg, cypher_comparison_boolexpr) ) { List *funcname = list_make1(makeString("ag_catalog")); funcname = lappend(funcname, makeString("bool_to_agtype")); warg = (Node *) makeFuncCall(funcname, list_make1(warg), COERCE_EXPLICIT_CAST, cexpr->location); } neww->result = (Expr *) transform_cypher_expr_recurse(cpstate, warg); neww->location = w->location; newargs = lappend(newargs, neww); resultexprs = lappend(resultexprs, neww->result); } newcexpr->args = newargs; /* transform the default clause */ defresult = (Node *) cexpr->defresult; if (defresult == NULL) { A_Const *n = makeNode(A_Const); n->val.type = T_Null; n->location = -1; defresult = (Node *) n; } newcexpr->defresult = (Expr *) transform_cypher_expr_recurse(cpstate, defresult); resultexprs = lcons(newcexpr->defresult, resultexprs); /* * we pass a NULL context to select_common_type because the common types can * only be AGTYPEOID or BOOLOID. If it returns invalidoid, we know there is a * boolean involved. */ ptype = select_common_type(pstate, resultexprs, NULL, NULL); //InvalidOid shows that there is a boolean in the result expr. if (ptype == InvalidOid) { //we manually set the type to boolean here to handle the bool casting. ptype = BOOLOID; } Assert(OidIsValid(ptype)); newcexpr->casetype = ptype; /* casecollid will be set by parse_collate.c */ /* Convert default result clause, if necessary */ newcexpr->defresult = (Expr *) coerce_to_common_type(pstate, (Node *) newcexpr->defresult, ptype, "CASE/ELSE"); /* Convert when-clause results, if necessary */ foreach(l, newcexpr->args) { CaseWhen *w = (CaseWhen *) lfirst(l); w->result = (Expr *) coerce_to_common_type(pstate, (Node *) w->result, ptype, "CASE/WHEN"); } /* if any subexpression contained a SRF, complain */ if (pstate->p_last_srf != last_srf) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is name of a SQL construct, eg GROUP BY */ errmsg("set-returning functions are not allowed in %s", "CASE"), errhint("You might be able to move the set-returning function into a LATERAL FROM item."), parser_errposition(pstate, exprLocation(pstate->p_last_srf)))); newcexpr->location = cexpr->location; return (Node *) newcexpr; } /* from PG's transformSubLink but reduced and hooked into our parser */ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink) { Node *result = (Node*)sublink; Query *qtree; ParseState *pstate = (ParseState*)cpstate; const char *err = NULL; /* * Check to see if the sublink is in an invalid place within the query. We * allow sublinks everywhere in SELECT/INSERT/UPDATE/DELETE, but generally * not in utility statements. */ switch (pstate->p_expr_kind) { case EXPR_KIND_NONE: Assert(false); /* can't happen */ break; case EXPR_KIND_OTHER: /* Accept sublink here; caller must throw error if wanted */ break; case EXPR_KIND_SELECT_TARGET: case EXPR_KIND_FROM_SUBSELECT: case EXPR_KIND_WHERE: /* okay */ break; default: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("unsupported SubLink"), parser_errposition(pstate, sublink->location))); } if (err) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg_internal("%s", err), parser_errposition(pstate, sublink->location))); pstate->p_hasSubLinks = true; /* * OK, let's transform the sub-SELECT. */ qtree = cypher_parse_sub_analyze(sublink->subselect, cpstate, NULL, false, true); /* * Check that we got a SELECT. Anything else should be impossible given * restrictions of the grammar, but check anyway. */ if (!IsA(qtree, Query) || qtree->commandType != CMD_SELECT) elog(ERROR, "unexpected non-SELECT command in SubLink"); sublink->subselect = (Node *)qtree; if (sublink->subLinkType == EXISTS_SUBLINK) { /* * EXISTS needs no test expression or combining operator. These fields * should be null already, but make sure. */ sublink->testexpr = NULL; sublink->operName = NIL; } else elog(ERROR, "unsupported SubLink type"); return result; }