static void convert_cypher_to_subquery()

in src/backend/parser/cypher_analyze.c [365:636]


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;
}