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