| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "postgres.h" |
| |
| #include "nodes/makefuncs.h" |
| #include "nodes/nodeFuncs.h" |
| #include "parser/analyze.h" |
| #include "parser/parse_coerce.h" |
| #include "parser/parse_collate.h" |
| #include "parser/parse_relation.h" |
| #include "parser/parse_target.h" |
| #include "utils/builtins.h" |
| |
| #include "catalog/ag_graph.h" |
| #include "parser/cypher_analyze.h" |
| #include "parser/cypher_clause.h" |
| #include "parser/cypher_parser.h" |
| #include "utils/ag_func.h" |
| #include "utils/age_session_info.h" |
| |
| typedef bool (*cypher_expression_condition)(Node *expr); |
| |
| /* |
| * extra_node is a global variable to this source to store, at the moment, the |
| * explain stmt node passed up by the parser. The return value from the parser |
| * contains an 'extra' value, hence the name. |
| */ |
| static Node *extra_node = NULL; |
| /* |
| * Takes a query node and builds an explain stmt query node. It then replaces |
| * the passed query node with the new explain stmt query node. |
| */ |
| static void build_explain_query(Query *query, Node *explain_node); |
| |
| static post_parse_analyze_hook_type prev_post_parse_analyze_hook; |
| |
| static void post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate); |
| static bool convert_cypher_walker(Node *node, ParseState *pstate); |
| static bool is_rte_cypher(RangeTblEntry *rte); |
| static bool is_func_cypher(FuncExpr *funcexpr); |
| static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate); |
| static Name expr_get_const_name(Node *expr); |
| static const char *expr_get_const_cstring(Node *expr, const char *source_str); |
| static int get_query_location(const int location, const char *source_str); |
| static Query *analyze_cypher(List *stmt, ParseState *parent_pstate, |
| const char *query_str, int query_loc, |
| char *graph_name, uint32 graph_oid, Param *params); |
| static Query *analyze_cypher_and_coerce(List *stmt, RangeTblFunction *rtfunc, |
| ParseState *parent_pstate, |
| const char *query_str, int query_loc, |
| char *graph_name, uint32 graph_oid, |
| Param *params); |
| cypher_clause *build_subquery_node(cypher_clause *next); |
| |
| /* expr tree walker */ |
| bool expr_contains_node(cypher_expression_condition is_expr, Node *expr); |
| bool expr_has_subquery(Node * expr); |
| |
| |
| void post_parse_analyze_init(void) |
| { |
| prev_post_parse_analyze_hook = post_parse_analyze_hook; |
| post_parse_analyze_hook = post_parse_analyze; |
| } |
| |
| void post_parse_analyze_fini(void) |
| { |
| post_parse_analyze_hook = prev_post_parse_analyze_hook; |
| } |
| |
| static void post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) |
| { |
| if (prev_post_parse_analyze_hook) |
| { |
| prev_post_parse_analyze_hook(pstate, query, jstate); |
| } |
| |
| /* |
| * extra_node is set in the parsing stage to keep track of EXPLAIN. |
| * So it needs to be set to NULL prior to any cypher parsing. |
| */ |
| extra_node = NULL; |
| |
| convert_cypher_walker((Node *)query, pstate); |
| |
| /* |
| * If there is an extra_node returned, we need to check to see if |
| * it is an EXPLAIN. |
| */ |
| if (extra_node != NULL) |
| { |
| /* process the EXPLAIN node */ |
| if (nodeTag(extra_node) == T_ExplainStmt) |
| { |
| build_explain_query(query, extra_node); |
| } |
| |
| /* reset extra_node */ |
| pfree(extra_node); |
| extra_node = NULL; |
| } |
| } |
| |
| // find cypher() calls in FROM clauses and convert them to SELECT subqueries |
| static bool convert_cypher_walker(Node *node, ParseState *pstate) |
| { |
| if (!node) |
| return false; |
| |
| if (IsA(node, RangeTblEntry)) |
| { |
| RangeTblEntry *rte = (RangeTblEntry *)node; |
| |
| switch (rte->rtekind) |
| { |
| case RTE_SUBQUERY: |
| // traverse other RTE_SUBQUERYs |
| return convert_cypher_walker((Node *)rte->subquery, pstate); |
| case RTE_FUNCTION: |
| if (is_rte_cypher(rte)) |
| convert_cypher_to_subquery(rte, pstate); |
| return false; |
| default: |
| return false; |
| } |
| } |
| |
| /* |
| * This handles a cypher() call with other function calls in a ROWS FROM |
| * expression. We can let the FuncExpr case below handle it but do this |
| * here to throw a better error message. |
| */ |
| if (IsA(node, RangeTblFunction)) |
| { |
| RangeTblFunction *rtfunc = (RangeTblFunction *)node; |
| FuncExpr *funcexpr = (FuncExpr *)rtfunc->funcexpr; |
| |
| /* |
| * It is better to throw a kind error message here instead of the |
| * internal error message that cypher() throws later when it is called. |
| */ |
| if (is_func_cypher(funcexpr)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("cypher(...) in ROWS FROM is not supported"), |
| parser_errposition(pstate, exprLocation((Node *)funcexpr)))); |
| } |
| |
| /* |
| * From PG - |
| * SQLValueFunction - parameterless functions with special grammar |
| * productions. |
| * CoerceViaIO - represents a type coercion between two types whose textual |
| * representations are compatible |
| * Var - expression node representing a variable (ie, a table column) |
| * OpExpr - expression node for an operator invocation |
| * Const - constant value or expression node |
| * BoolExpr - expression node for the basic Boolean operators AND, OR, NOT |
| * |
| * These are a special case that needs to be ignored. |
| * |
| */ |
| if (IsA(funcexpr, SQLValueFunction) |
| || IsA(funcexpr, CoerceViaIO) |
| || IsA(funcexpr, Var) || IsA(funcexpr, OpExpr) |
| || IsA(funcexpr, Const) || IsA(funcexpr, BoolExpr)) |
| { |
| return false; |
| } |
| |
| return expression_tree_walker((Node *)funcexpr->args, |
| convert_cypher_walker, pstate); |
| } |
| |
| /* |
| * This handles cypher() calls in expressions. Those in RTE_FUNCTIONs are |
| * handled by either convert_cypher_to_subquery() or the RangeTblFunction |
| * case above. |
| */ |
| if (IsA(node, FuncExpr)) |
| { |
| FuncExpr *funcexpr = (FuncExpr *)node; |
| |
| if (is_func_cypher(funcexpr)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("cypher(...) in expressions is not supported"), |
| errhint("Use subquery instead if possible."), |
| parser_errposition(pstate, exprLocation(node)))); |
| } |
| |
| return expression_tree_walker((Node *)funcexpr->args, |
| convert_cypher_walker, pstate); |
| } |
| |
| if (IsA(node, Query)) |
| { |
| int flags; |
| bool result = false; |
| Query *query = (Query *)node; |
| |
| /* |
| * If this is a utility command, we need to unwrap the internal query |
| * and pass it as the query. |
| * |
| * NOTE: This code only "knows" about the following utility commands - |
| * |
| * CREATE TABLE AS |
| * |
| * Others need to be added on a case by case basis. |
| */ |
| if (query->utilityStmt != NULL && |
| IsA(query->utilityStmt, CreateTableAsStmt)) |
| { |
| CreateTableAsStmt *ctas = (CreateTableAsStmt *)query->utilityStmt; |
| |
| if (IsA(ctas->query, Query)) |
| { |
| query = (Query *)ctas->query; |
| } |
| } |
| |
| /* |
| * QTW_EXAMINE_RTES |
| * We convert RTE_FUNCTION (cypher()) to RTE_SUBQUERY (SELECT) |
| * in-place. |
| * |
| * QTW_IGNORE_RT_SUBQUERIES |
| * After the conversion, we don't need to traverse the resulting |
| * RTE_SUBQUERY. However, we need to traverse other RTE_SUBQUERYs. |
| * This is done manually by the RTE_SUBQUERY case above. |
| * |
| * QTW_IGNORE_JOINALIASES |
| * We are not interested in this. |
| */ |
| flags = QTW_EXAMINE_RTES_BEFORE | QTW_IGNORE_RT_SUBQUERIES | |
| QTW_IGNORE_JOINALIASES; |
| |
| /* recurse on query */ |
| result = query_tree_walker(query, convert_cypher_walker, pstate, flags); |
| |
| return result; |
| } |
| |
| return expression_tree_walker(node, convert_cypher_walker, pstate); |
| } |
| |
| /* |
| * Takes a query node and builds an explain stmt query node. It then replaces |
| * the passed query node with the new explain stmt query node. |
| */ |
| static void build_explain_query(Query *query, Node *explain_node) |
| { |
| ExplainStmt *estmt = NULL; |
| Query *query_copy = NULL; |
| Query *query_node = NULL; |
| |
| /* |
| * Create a copy of the query node. This is purposely a shallow copy |
| * because we are only moving the contents to another pointer. |
| */ |
| query_copy = (Query *) palloc(sizeof(Query)); |
| memcpy(query_copy, query, sizeof(Query)); |
| |
| /* build our Explain node and store the query node copy in it */ |
| estmt = makeNode(ExplainStmt); |
| estmt->query = (Node *)query_copy; |
| estmt->options = ((ExplainStmt *)explain_node)->options; |
| |
| /* build our replacement query node */ |
| query_node = makeNode(Query); |
| query_node->commandType = CMD_UTILITY; |
| query_node->utilityStmt = (Node *)estmt; |
| query_node->canSetTag = true; |
| |
| /* now replace the top query node with our replacement query node */ |
| memcpy(query, query_node, sizeof(Query)); |
| |
| /* |
| * We need to free and clear the global variable when done. But, not |
| * the ExplainStmt options. Those will get freed by PG when the |
| * query is deleted. |
| */ |
| ((ExplainStmt *)explain_node)->options = NULL; |
| |
| /* we need to free query_node as it is no longer needed */ |
| pfree(query_node); |
| } |
| |
| static bool is_rte_cypher(RangeTblEntry *rte) |
| { |
| RangeTblFunction *rtfunc; |
| FuncExpr *funcexpr; |
| |
| /* |
| * The planner expects RangeTblFunction nodes in rte->functions list. |
| * We cannot replace one of them to a SELECT subquery. |
| */ |
| if (list_length(rte->functions) != 1) |
| return false; |
| |
| /* |
| * A plain function call or a ROWS FROM expression with one function call |
| * reaches here. At this point, it is impossible to distinguish between the |
| * two. However, it doesn't matter because they are identical in terms of |
| * their meaning. |
| */ |
| |
| rtfunc = linitial(rte->functions); |
| funcexpr = (FuncExpr *)rtfunc->funcexpr; |
| return is_func_cypher(funcexpr); |
| } |
| |
| /* |
| * Return true if the qualified name of the given function is |
| * <"ag_catalog"."cypher">. Otherwise, return false. |
| */ |
| static bool is_func_cypher(FuncExpr *funcexpr) |
| { |
| /* |
| * From PG - |
| * SQLValueFunction - parameterless functions with special grammar |
| * productions. |
| * CoerceViaIO - represents a type coercion between two types whose textual |
| * representations are compatible |
| * Var - expression node representing a variable (ie, a table column) |
| * OpExpr - expression node for an operator invocation |
| * Const - constant value or expression node |
| * BoolExpr - expression node for the basic Boolean operators AND, OR, NOT |
| * |
| * These are a special case that needs to be ignored. |
| * |
| */ |
| if (IsA(funcexpr, SQLValueFunction) |
| || IsA(funcexpr, CoerceViaIO) |
| || IsA(funcexpr, Var) || IsA(funcexpr, OpExpr) |
| || IsA(funcexpr, Const) || IsA(funcexpr, BoolExpr)) |
| { |
| return false; |
| } |
| |
| return is_oid_ag_func(funcexpr->funcid, "cypher"); |
| } |
| |
| // convert cypher() call to SELECT subquery in-place |
| static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) |
| { |
| RangeTblFunction *rtfunc = linitial(rte->functions); |
| FuncExpr *funcexpr = (FuncExpr *)rtfunc->funcexpr; |
| Node *arg1 = NULL; |
| Node *arg2 = NULL; |
| Node *arg3 = NULL; |
| Name graph_name = NULL; |
| char *graph_name_str = NULL; |
| Oid graph_oid = InvalidOid; |
| const char *query_str = NULL; |
| int query_loc = -1; |
| Param *params = NULL; |
| errpos_ecb_state ecb_state = {{0}}; |
| List *stmt = NULL; |
| Query *query = NULL; |
| |
| /* |
| * We cannot apply this feature directly to SELECT subquery because the |
| * planner does not support it. Adding a "row_number() OVER ()" expression |
| * to the subquery as a result target might be a workaround but we throw an |
| * error for now. |
| */ |
| if (rte->funcordinality) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("WITH ORDINALITY is not supported"), |
| parser_errposition(pstate, exprLocation((Node *)funcexpr)))); |
| } |
| |
| /* verify that we have 2 input parameters as it is possible to get 1 or 0 */ |
| if (funcexpr->args == NULL || list_length(funcexpr->args) < 2) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cypher function requires a minimum of 2 arguments"), |
| parser_errposition(pstate, -1))); |
| } |
| |
| /* get our first 2 arguments */ |
| arg1 = linitial(funcexpr->args); |
| arg2 = lsecond(funcexpr->args); |
| |
| Assert(exprType(arg1) == NAMEOID); |
| Assert(exprType(arg2) == CSTRINGOID); |
| |
| graph_name = expr_get_const_name(arg1); |
| |
| /* |
| * Since cypher() function is nothing but an interface to get a Cypher |
| * query, it must take a string constant as an argument so that the query |
| * can be parsed and analyzed at this point to create a Query tree of it. |
| * |
| * Also, only dollar-quoted string constants are allowed because of the |
| * following reasons. |
| * |
| * * If other kinds of string constants are used, the actual values of them |
| * may differ from what they are shown. This will confuse users. |
| * * In the case above, the error position may not be accurate. |
| */ |
| query_str = expr_get_const_cstring(arg2, pstate->p_sourcetext); |
| |
| /* |
| * Validate appropriate cypher function usage - |
| * |
| * Session info OVERRIDES ANY INPUT PASSED and if any is passed, it will |
| * cause the cypher function to error out. |
| * |
| * If this is using session info, both of the first 2 input parameters need |
| * to be NULL, in addition to the session info being set up. Furthermore, |
| * the input parameters passed in by session info need to both be non-NULL. |
| * |
| * If this is not using session info, both input parameters need to be |
| * non-NULL. |
| * |
| */ |
| if (is_session_info_prepared()) |
| { |
| /* check to see if either input parameter is non-NULL*/ |
| if (graph_name != NULL || query_str != NULL) |
| { |
| Node *arg = (graph_name == NULL) ? arg1 : arg2; |
| |
| /* |
| * Make sure to clean up session info because the ereport will |
| * cause the function to exit. |
| */ |
| reset_session_info(); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("session info requires cypher(NULL, NULL) to be passed"), |
| parser_errposition(pstate, exprLocation(arg)))); |
| } |
| /* get our input parameters from session info */ |
| else |
| { |
| graph_name_str = get_session_info_graph_name(); |
| query_str = get_session_info_cypher_statement(); |
| |
| /* check to see if either are NULL */ |
| if (graph_name_str == NULL || query_str == NULL) |
| { |
| /* |
| * Make sure to clean up session info because the ereport will |
| * cause the function to exit. |
| */ |
| reset_session_info(); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("both session info parameters need to be non-NULL"), |
| parser_errposition(pstate, -1))); |
| } |
| } |
| } |
| /* otherwise, we get the parameters from the passed function input */ |
| else |
| { |
| /* get the graph name string from the passed parameters */ |
| if (!graph_name) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("a name constant is expected"), |
| parser_errposition(pstate, exprLocation(arg1)))); |
| } |
| else |
| { |
| graph_name_str = NameStr(*graph_name); |
| } |
| /* get the query string from the passed parameters */ |
| if (!query_str) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("a dollar-quoted string constant is expected"), |
| parser_errposition(pstate, exprLocation(arg2)))); |
| } |
| } |
| |
| /* |
| * The session info is only valid for one cypher call. Now that we are done |
| * with it, if it was used, we need to reset it to free the memory used. |
| * Additionally, the query location is dependent on how we got the query |
| * string, so set the location accordingly. |
| */ |
| if (is_session_info_prepared()) |
| { |
| reset_session_info(); |
| query_loc = 0; |
| } |
| else |
| { |
| /* this call will crash if we use session info */ |
| query_loc = get_query_location(((Const *)arg2)->location, |
| pstate->p_sourcetext); |
| } |
| |
| /* validate the graph exists */ |
| graph_oid = get_graph_oid(graph_name_str); |
| if (!OidIsValid(graph_oid)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_SCHEMA), |
| errmsg("graph \"%s\" does not exist", graph_name_str), |
| parser_errposition(pstate, exprLocation(arg1)))); |
| } |
| |
| /* |
| * Check to see if the cypher function had a third parameter passed to it, |
| * if so make sure Postgres parsed the second argument to a Param node. |
| */ |
| if (list_length(funcexpr->args) == 3) |
| { |
| arg3 = lthird(funcexpr->args); |
| if (!IsA(arg3, Param)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("third argument of cypher function must be a parameter"), |
| parser_errposition(pstate, exprLocation(arg3)))); |
| } |
| |
| params = (Param *)arg3; |
| } |
| else |
| { |
| params = NULL; |
| } |
| |
| /* |
| * install error context callback to adjust an error position for |
| * parse_cypher() since locations that parse_cypher() stores are 0 based |
| */ |
| setup_errpos_ecb(&ecb_state, pstate, query_loc); |
| |
| stmt = parse_cypher(query_str); |
| |
| /* |
| * Extract any extra node passed up and assign it to the global variable |
| * 'extra_node' - if it wasn't already set. It will be at the end of the |
| * stmt list and needs to be removed for normal processing, regardless. |
| * It is done this way to allow utility commands to be processed against the |
| * AGE query tree. Currently, only EXPLAIN is passed here. But, it need not |
| * just be EXPLAIN - so long as it is carefully documented and carefully |
| * done. |
| */ |
| if (extra_node == NULL) |
| { |
| extra_node = llast(stmt); |
| stmt = list_delete_ptr(stmt, extra_node); |
| } |
| else |
| { |
| Node *temp = llast(stmt); |
| |
| ereport(WARNING, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("too many extra_nodes passed from parser"))); |
| |
| stmt = list_delete_ptr(stmt, temp); |
| } |
| |
| cancel_errpos_ecb(&ecb_state); |
| |
| Assert(pstate->p_expr_kind == EXPR_KIND_NONE); |
| pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; |
| // transformRangeFunction() always sets p_lateral_active to true. |
| // FYI, rte is RTE_FUNCTION and is being converted to RTE_SUBQUERY here. |
| pstate->p_lateral_active = true; |
| |
| /* |
| * Cypher queries that end with CREATE clause do not need to have the |
| * coercion logic applied to them because we are forcing the column |
| * definition list to be a particular way in this case. |
| */ |
| if (is_ag_node(llast(stmt), cypher_create) || is_ag_node(llast(stmt), cypher_set) || |
| is_ag_node(llast(stmt), cypher_delete) || is_ag_node(llast(stmt), cypher_merge)) |
| { |
| // column definition list must be ... AS relname(colname agtype) ... |
| if (!(rtfunc->funccolcount == 1 && |
| linitial_oid(rtfunc->funccoltypes) == AGTYPEOID)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("column definition list for CREATE clause must contain a single agtype attribute"), |
| errhint("... cypher($$ ... CREATE ... $$) AS t(c agtype) ..."), |
| parser_errposition(pstate, exprLocation(rtfunc->funcexpr)))); |
| } |
| |
| query = analyze_cypher(stmt, pstate, query_str, query_loc, |
| graph_name_str, graph_oid, params); |
| } |
| else |
| { |
| query = analyze_cypher_and_coerce(stmt, rtfunc, pstate, query_str, |
| query_loc, graph_name_str, graph_oid, |
| params); |
| } |
| |
| pstate->p_lateral_active = false; |
| pstate->p_expr_kind = EXPR_KIND_NONE; |
| |
| // rte->functions and rte->funcordinality are kept for debugging. |
| // rte->alias, rte->eref, and rte->lateral need to be the same. |
| // rte->inh is always false for both RTE_FUNCTION and RTE_SUBQUERY. |
| // rte->inFromCl is always true for RTE_FUNCTION. |
| rte->rtekind = RTE_SUBQUERY; |
| rte->subquery = query; |
| } |
| |
| static Name expr_get_const_name(Node *expr) |
| { |
| Const *con; |
| |
| if (!IsA(expr, Const)) |
| return NULL; |
| |
| con = (Const *)expr; |
| if (con->constisnull) |
| return NULL; |
| |
| return DatumGetName(con->constvalue); |
| } |
| |
| static const char *expr_get_const_cstring(Node *expr, const char *source_str) |
| { |
| Const *con; |
| const char *p; |
| |
| if (!IsA(expr, Const)) |
| return NULL; |
| |
| con = (Const *)expr; |
| if (con->constisnull) |
| return NULL; |
| |
| Assert(con->location > -1); |
| p = source_str + con->location; |
| if (*p != '$') |
| return NULL; |
| |
| return DatumGetCString(con->constvalue); |
| } |
| |
| static int get_query_location(const int location, const char *source_str) |
| { |
| const char *p; |
| |
| Assert(location > -1); |
| |
| p = source_str + location; |
| Assert(*p == '$'); |
| |
| return strchr(p + 1, '$') - source_str + 1; |
| } |
| |
| /* |
| * This is a specialized expression tree walker for finding exprs of a specified |
| * type. Create a function that checks for the type you want, and this function |
| * will iterate through the tree. |
| */ |
| |
| bool expr_contains_node(cypher_expression_condition is_expr, Node *expr) |
| { |
| if (!expr) |
| { |
| return false; |
| } |
| |
| switch (nodeTag(expr)) |
| { |
| case T_A_Const: |
| case T_ColumnRef: |
| case T_A_Indirection: |
| { |
| break; |
| } |
| case T_A_Expr: |
| { |
| A_Expr *a = (A_Expr *)expr; |
| |
| switch (a->kind) |
| { |
| case AEXPR_OP: |
| case AEXPR_IN: |
| { |
| if (expr_contains_node(is_expr, a->lexpr) || |
| expr_contains_node(is_expr, a->rexpr)) |
| { |
| return true; |
| } |
| break; |
| } |
| default: |
| ereport(ERROR, (errmsg_internal("unrecognized A_Expr kind: %d", |
| a->kind))); |
| } |
| break; |
| } |
| case T_BoolExpr: |
| { |
| BoolExpr *b = (BoolExpr *)expr; |
| ListCell *la; |
| |
| foreach(la, b->args) |
| { |
| Node *arg = lfirst(la); |
| |
| if (expr_contains_node(is_expr, arg)) |
| { |
| return true; |
| } |
| } |
| break; |
| } |
| case T_NullTest: |
| { |
| NullTest *n = (NullTest *)expr; |
| |
| if (expr_contains_node(is_expr, (Node *)n->arg)) |
| { |
| return true; |
| } |
| break; |
| } |
| case T_CaseExpr: |
| { |
| CaseExpr *cexpr = (CaseExpr *)expr; |
| ListCell *l; |
| |
| if (cexpr->arg && expr_contains_node(is_expr, (Node *) cexpr->arg)) |
| { |
| return true; |
| } |
| |
| foreach(l, cexpr->args) |
| { |
| CaseWhen *w = lfirst_node(CaseWhen, l); |
| Node *warg; |
| |
| warg = (Node *) w->expr; |
| |
| if (expr_contains_node(is_expr, warg)) |
| { |
| return true; |
| } |
| warg = (Node *)w->result; |
| |
| if (expr_contains_node(is_expr, warg)) |
| { |
| return true; |
| } |
| } |
| |
| if (expr_contains_node(is_expr , (Node *)cexpr->defresult)) |
| { |
| return true; |
| } |
| |
| break; |
| } |
| case T_CaseTestExpr: |
| { |
| break; |
| } |
| case T_CoalesceExpr: |
| { |
| CoalesceExpr *cexpr = (CoalesceExpr *) expr; |
| ListCell *args; |
| |
| foreach(args, cexpr->args) |
| { |
| Node *e = (Node *)lfirst(args); |
| |
| if (expr_contains_node(is_expr, e)) |
| { |
| return true; |
| } |
| } |
| break; |
| } |
| case T_ExtensibleNode: |
| { |
| if (is_ag_node(expr, cypher_bool_const)) |
| { |
| return is_expr(expr); |
| } |
| if (is_ag_node(expr, cypher_integer_const)) |
| { |
| return is_expr(expr); |
| } |
| if (is_ag_node(expr, cypher_param)) |
| { |
| return is_expr(expr); |
| } |
| if (is_ag_node(expr, cypher_map)) |
| { |
| cypher_map *cm = (cypher_map *)expr; |
| ListCell *le; |
| |
| Assert(list_length(cm->keyvals) % 2 == 0); |
| |
| le = list_head(cm->keyvals); |
| |
| while(le != NULL) |
| { |
| Node *val; |
| |
| le = lnext(cm->keyvals, le); |
| |
| val = lfirst(le); |
| |
| if (expr_contains_node(is_expr, val)) |
| { |
| return true; |
| } |
| |
| le = lnext(cm->keyvals, le); |
| |
| } |
| break; |
| } |
| if (is_ag_node(expr, cypher_map_projection)) |
| { |
| cypher_map_projection *cmp = (cypher_map_projection *)expr; |
| ListCell *lc; |
| |
| foreach(lc, cmp->map_elements) |
| { |
| cypher_map_projection_element *elem; |
| |
| elem = lfirst(lc); |
| |
| if (expr_contains_node(is_expr, elem->value)) |
| { |
| return true; |
| } |
| } |
| |
| break; |
| } |
| if (is_ag_node(expr, cypher_list)) |
| { |
| cypher_list *cl = (cypher_list *)expr; |
| ListCell *le = NULL; |
| |
| foreach(le, cl->elems) |
| { |
| Node *texpr = lfirst(le); |
| |
| if (expr_contains_node(is_expr, texpr)) |
| { |
| return true; |
| } |
| } |
| break; |
| } |
| if (is_ag_node(expr, cypher_string_match)) |
| { |
| cypher_string_match *csm = (cypher_string_match *)expr; |
| |
| if (expr_contains_node(is_expr, csm->lhs) || |
| expr_contains_node(is_expr, csm->rhs)) |
| { |
| return true; |
| } |
| break; |
| } |
| if (is_ag_node(expr, cypher_typecast)) |
| { |
| cypher_typecast *t = (cypher_typecast *) expr; |
| |
| if (expr_contains_node(is_expr, t->expr)) |
| { |
| return true; |
| } |
| break; |
| } |
| if (is_ag_node(expr, cypher_comparison_aexpr)) |
| { |
| cypher_comparison_aexpr *a = (cypher_comparison_aexpr *) expr; |
| |
| if (expr_contains_node(is_expr, a->lexpr) || |
| expr_contains_node(is_expr, a->rexpr)) |
| { |
| return true; |
| } |
| break; |
| } |
| if (is_ag_node(expr, cypher_comparison_boolexpr)) |
| { |
| cypher_comparison_boolexpr *b = (cypher_comparison_boolexpr *) expr; |
| ListCell *la; |
| |
| foreach(la, b->args) |
| { |
| Node *arg = lfirst(la); |
| |
| if (expr_contains_node(is_expr, arg)) |
| { |
| return true; |
| } |
| } |
| break; |
| } |
| if (is_ag_node(expr, cypher_unwind)) |
| { |
| cypher_unwind* lc = (cypher_unwind *)expr; |
| |
| if (expr_contains_node(is_expr, lc->where) || |
| expr_contains_node(is_expr, lc->collect)) |
| { |
| return true; |
| } |
| break; |
| } |
| |
| if (is_ag_node(expr, cypher_sub_pattern)) |
| { |
| break; |
| } |
| |
| if (is_ag_node(expr, cypher_sub_query)) |
| { |
| break; |
| } |
| |
| ereport(ERROR, |
| (errmsg_internal("unrecognized ExtensibleNode: %s", |
| ((ExtensibleNode *)expr)->extnodename))); |
| |
| break; |
| } |
| case T_FuncCall: |
| { |
| FuncCall *fn = (FuncCall *)expr; |
| ListCell *arg; |
| |
| foreach(arg, fn->args) |
| { |
| Node *farg = NULL; |
| |
| farg = (Node *)lfirst(arg); |
| |
| if (expr_contains_node(is_expr, farg)) |
| { |
| return true; |
| } |
| } |
| break; |
| } |
| case T_SubLink: |
| { |
| SubLink *s = (SubLink *)expr; |
| |
| if (expr_contains_node(is_expr, s->subselect)) |
| { |
| return true; |
| } |
| break; |
| } |
| default: |
| ereport(ERROR, (errmsg_internal("unrecognized node type: %d", |
| nodeTag(expr)))); |
| } |
| |
| return (is_expr(expr)); |
| } |
| |
| /* |
| * Function that checks if an expr is a cypher_sub_query. Used in tandem with |
| * expr_contains_node. Can write more similar to this to find similar nodes. |
| */ |
| |
| bool expr_has_subquery(Node * expr) |
| { |
| if (expr == NULL) |
| { |
| return false; |
| } |
| |
| if (IsA(expr, ExtensibleNode)) |
| { |
| if (is_ag_node(expr, cypher_sub_query)) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * This function constructs an intermediate WITH node for processing subqueries |
| */ |
| cypher_clause *build_subquery_node(cypher_clause *next) |
| { |
| cypher_match *match = (cypher_match *)next->self; |
| cypher_clause *where_container_clause; |
| cypher_with *with_clause = make_ag_node(cypher_with); |
| ColumnRef *cr; |
| ResTarget *rt; |
| |
| /* construct the column ref star */ |
| cr = makeNode(ColumnRef); |
| cr->fields = list_make1(makeNode(A_Star)); |
| cr->location = exprLocation((Node *)next); |
| |
| /*construct the restarget */ |
| rt = makeNode(ResTarget); |
| rt->name = NULL; |
| rt->indirection = NIL; |
| rt->val = (Node *)cr; |
| rt->location = exprLocation((Node *)next); |
| |
| |
| /* construct the with_clause */ |
| with_clause->items = list_make1(rt); |
| with_clause->where = match->where; |
| with_clause->subquery_intermediate = true; |
| |
| /* |
| * create the where container, and set the match (next) as the |
| * prev of the where container |
| */ |
| where_container_clause = palloc(sizeof(*where_container_clause)); |
| where_container_clause->self = (Node *)with_clause; |
| where_container_clause->next = NULL; |
| where_container_clause->prev = next; |
| |
| return where_container_clause; |
| } |
| |
| static Query *analyze_cypher(List *stmt, ParseState *parent_pstate, |
| const char *query_str, int query_loc, |
| char *graph_name, uint32 graph_oid, Param *params) |
| { |
| cypher_clause *clause; |
| ListCell *lc; |
| cypher_parsestate parent_cpstate; |
| cypher_parsestate *cpstate; |
| ParseState *pstate; |
| errpos_ecb_state ecb_state; |
| Query *query; |
| |
| /* |
| * 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; |
| |
| /* check for subqueries in match */ |
| if (is_ag_node(next->self, cypher_match)) |
| { |
| cypher_match *match = (cypher_match *)next->self; |
| |
| if (match->where != NULL && expr_contains_node(expr_has_subquery, match->where)) |
| { |
| /* advance the clause iterator to the intermediate clause position */ |
| clause = build_subquery_node(next); |
| |
| /* set the next of the match to the where_container_clause */ |
| match->where = NULL; |
| next->next = clause; |
| continue; |
| } |
| } |
| |
| if (clause != NULL) |
| { |
| clause->next = next; |
| } |
| clause = next; |
| } |
| |
| /* |
| * convert ParseState into cypher_parsestate temporarily to pass it to |
| * make_cypher_parsestate() |
| */ |
| parent_cpstate.pstate = *parent_pstate; |
| parent_cpstate.graph_name = NULL; |
| parent_cpstate.params = NULL; |
| |
| cpstate = make_cypher_parsestate(&parent_cpstate); |
| |
| pstate = (ParseState *)cpstate; |
| |
| /* we don't want functions that go up the pstate parent chain to access the |
| * original SQL query pstate. |
| */ |
| pstate->parentParseState = NULL; |
| /* |
| * override p_sourcetext with query_str to make parser_errposition() work |
| * correctly with errpos_ecb() |
| */ |
| pstate->p_sourcetext = query_str; |
| |
| cpstate->graph_name = graph_name; |
| cpstate->graph_oid = graph_oid; |
| cpstate->params = params; |
| cpstate->default_alias_num = 0; |
| cpstate->entities = NIL; |
| cpstate->subquery_where_flag = false; |
| /* |
| * install error context callback to adjust an error position since |
| * locations in stmt are 0 based |
| */ |
| setup_errpos_ecb(&ecb_state, parent_pstate, query_loc); |
| |
| query = transform_cypher_clause(cpstate, clause); |
| |
| cancel_errpos_ecb(&ecb_state); |
| |
| free_cypher_parsestate(cpstate); |
| |
| return query; |
| } |
| |
| /* |
| * Since some target entries of subquery may be referenced for sorting (ORDER |
| * BY), we cannot apply the coercion directly to the expressions of the target |
| * entries. Therefore, we do the coercion by doing SELECT over subquery. |
| */ |
| static Query *analyze_cypher_and_coerce(List *stmt, RangeTblFunction *rtfunc, |
| ParseState *parent_pstate, |
| const char *query_str, int query_loc, |
| char *graph_name, uint32 graph_oid, |
| Param *params) |
| { |
| ParseState *pstate; |
| Query *query; |
| const bool lateral = false; |
| Query *subquery; |
| ParseNamespaceItem *pnsi; |
| int rtindex; |
| ListCell *lt; |
| ListCell *lc1; |
| ListCell *lc2; |
| ListCell *lc3; |
| |
| int attr_count = 0; |
| |
| pstate = make_parsestate(parent_pstate); |
| |
| query = makeNode(Query); |
| query->commandType = CMD_SELECT; |
| |
| /* |
| * Below is similar to transform_prev_cypher_clause(). |
| */ |
| |
| Assert(pstate->p_expr_kind == EXPR_KIND_NONE); |
| pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT; |
| pstate->p_lateral_active = lateral; |
| |
| subquery = analyze_cypher(stmt, pstate, query_str, query_loc, graph_name, |
| graph_oid, (Param *)params); |
| |
| pstate->p_lateral_active = false; |
| pstate->p_expr_kind = EXPR_KIND_NONE; |
| |
| // ALIAS Syntax makes `RESJUNK`. So, It must be skipping. |
| foreach(lt, subquery->targetList) |
| { |
| TargetEntry *te = lfirst(lt); |
| if (!te->resjunk) |
| { |
| attr_count++; |
| } |
| } |
| |
| // check the number of attributes first |
| if (attr_count != rtfunc->funccolcount) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("return row and column definition list do not match"), |
| parser_errposition(pstate, exprLocation(rtfunc->funcexpr)))); |
| } |
| |
| pnsi = addRangeTableEntryForSubquery(pstate, subquery, makeAlias("_", NIL), |
| lateral, true); |
| |
| 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"))); |
| } |
| |
| |
| addNSItemToQuery(pstate, pnsi, true, true, true); |
| query->targetList = expandNSItemAttrs(pstate, pnsi, 0, true, -1); |
| |
| markTargetListOrigins(pstate, query->targetList); |
| |
| // do the type coercion for each target entry |
| lc1 = list_head(rtfunc->funccolnames); |
| lc2 = list_head(rtfunc->funccoltypes); |
| lc3 = list_head(rtfunc->funccoltypmods); |
| foreach (lt, query->targetList) |
| { |
| TargetEntry *te = lfirst(lt); |
| Node *expr = (Node *)te->expr; |
| Oid current_type; |
| Oid target_type; |
| |
| Assert(!te->resjunk); |
| |
| current_type = exprType(expr); |
| target_type = lfirst_oid(lc2); |
| if (current_type != target_type) |
| { |
| int32 target_typmod = lfirst_int(lc3); |
| Node *new_expr; |
| |
| /* |
| * The coercion context of this coercion is COERCION_EXPLICIT |
| * because the target type is explicitly mentioned in the column |
| * definition list and we need to do this by looking up all |
| * possible coercion. |
| */ |
| new_expr = coerce_to_target_type(pstate, expr, current_type, |
| target_type, target_typmod, |
| COERCION_EXPLICIT, |
| COERCE_EXPLICIT_CAST, -1); |
| if (!new_expr) |
| { |
| char *colname = strVal(lfirst(lc1)); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_CANNOT_COERCE), |
| errmsg("cannot cast type %s to %s for column \"%s\"", |
| format_type_be(current_type), |
| format_type_be(target_type), colname), |
| parser_errposition(pstate, |
| exprLocation(rtfunc->funcexpr)))); |
| } |
| |
| te->expr = (Expr *)new_expr; |
| } |
| |
| lc1 = lnext(rtfunc->funccolnames, lc1); |
| lc2 = lnext(rtfunc->funccoltypes, lc2); |
| lc3 = lnext(rtfunc->funccoltypmods, lc3); |
| } |
| |
| query->rtable = pstate->p_rtable; |
| query->rteperminfos = pstate->p_rteperminfos; |
| query->jointree = makeFromExpr(pstate->p_joinlist, NULL); |
| |
| assign_query_collations(pstate, query); |
| |
| free_parsestate(pstate); |
| |
| return query; |
| } |