| /* |
| * 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 "catalog/pg_type.h" |
| #include "miscadmin.h" |
| #include "nodes/makefuncs.h" |
| #include "nodes/nodeFuncs.h" |
| #include "nodes/nodes.h" |
| #include "nodes/parsenodes.h" |
| #include "nodes/value.h" |
| #include "optimizer/optimizer.h" |
| #include "optimizer/tlist.h" |
| #include "parser/parse_coerce.h" |
| #include "parser/parse_collate.h" |
| #include "parser/parse_expr.h" |
| #include "parser/parse_func.h" |
| #include "parser/cypher_clause.h" |
| #include "parser/parse_node.h" |
| #include "parser/parse_oper.h" |
| #include "parser/parse_relation.h" |
| #include "utils/builtins.h" |
| #include "utils/float.h" |
| #include "utils/lsyscache.h" |
| #include "utils/syscache.h" |
| |
| #include "commands/label_commands.h" |
| #include "nodes/cypher_nodes.h" |
| #include "parser/cypher_expr.h" |
| #include "parser/cypher_item.h" |
| #include "parser/cypher_parse_node.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; |
| |
| Datum d = (Datum)0; |
| bool is_null = false; |
| Const *c; |
| |
| setup_parser_errposition_callback(&pcbstate, pstate, ac->location); |
| switch (nodeTag(&ac->val)) |
| { |
| case T_Integer: |
| d = integer_to_agtype((int64)intVal(&ac->val)); |
| break; |
| case T_Float: |
| { |
| char *n = ac->val.sval.sval; |
| char *endptr; |
| int64 i; |
| errno = 0; |
| |
| i = strtoi64(ac->val.fval.fval, &endptr, 10); |
| |
| if (errno == 0 && *endptr == '\0') |
| { |
| 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(&ac->val)); |
| break; |
| case T_Boolean: |
| d = boolean_to_agtype(boolVal(&ac->val)); |
| break; |
| default: |
| if (ac->isnull) |
| { |
| is_null = true; |
| } |
| else |
| { |
| ereport(ERROR, (errmsg_internal("unrecognized node type: %d", |
| nodeTag(&ac->val)))); |
| 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; |
| |
| /* Check for null arguments in the list to return NULL*/ |
| if (!is_ag_node(a->rexpr, cypher_list)) |
| { |
| if (nodeTag(a->rexpr) == T_A_Const) |
| { |
| A_Const *r_a_const = (A_Const*)a->rexpr; |
| if (r_a_const->isnull == true) |
| { |
| return (Node *)makeConst(AGTYPEOID, -1, InvalidOid, -1, (Datum)NULL, true, false); |
| } |
| } |
| |
| ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("object of IN must be a list"))); |
| } |
| |
| 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; |
| |
| /* verify they are a common type */ |
| if (!verify_common_type(scalar_type, allexprs)) |
| { |
| ereport(ERROR, |
| errmsg_internal("not a common type: %d", scalar_type)); |
| } |
| |
| /* |
| * 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, return NULL */ |
| if (!pnsi) |
| { |
| return NULL; |
| } |
| |
| /* 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->isnull = true; |
| 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->isnull = true; |
| 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; |
| |
| 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 = ((String*)linitial(fn->funcname))->sval; |
| 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) |
| { |
| /* 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; |
| 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->isnull = true; |
| 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; |
| } |