blob: a3ea59c1853d008a905d3ded7cab7c24cd570b7d [file] [log] [blame]
/*
* 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_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr);
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);
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);
}
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;
Assert (verify_common_type(scalar_type, allexprs));
/*
* coerce all the right-hand non-Var inputs to the common type
* and build an ArrayExpr for them.
*/
aexprs = NIL;
foreach(l, rnonvars)
{
Node *rexpr = (Node *) lfirst(l);
rexpr = coerce_to_common_type(pstate, rexpr, AGTYPEOID, "IN");
aexprs = lappend(aexprs, rexpr);
}
newa = makeNode(ArrayExpr);
newa->array_typeid = get_array_type(AGTYPEOID);
/* array_collid will be set by parse_collate.c */
newa->element_typeid = AGTYPEOID;
newa->elements = aexprs;
newa->multidims = false;
result = (Node *) make_scalar_array_op(pstate, a->name, useOr,
lexpr, (Node *) newa, a->location);
/* Consider only the Vars (if any) in the loop below */
rexprs = rvars;
}
// Must do it the hard way, with a boolean expression tree.
foreach(l, rexprs)
{
Node *rexpr = (Node *) lfirst(l);
Node *cmp;
// Ordinary scalar operator
cmp = (Node *) make_op(pstate, a->name, copyObject(lexpr), rexpr,
pstate->p_last_srf, a->location);
cmp = coerce_to_boolean(pstate, cmp, "IN");
if (result == NULL)
{
result = cmp;
}
else
{
result = (Node *) makeBoolExpr(useOr ? OR_EXPR : AND_EXPR,
list_make2(result, cmp),
a->location);
}
}
return result;
}
static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr)
{
ParseState *pstate = (ParseState *)cpstate;
List *args = NIL;
const char *opname;
ListCell *la;
switch (expr->boolop)
{
case AND_EXPR:
opname = "AND";
break;
case OR_EXPR:
opname = "OR";
break;
case NOT_EXPR:
opname = "NOT";
break;
default:
ereport(ERROR, (errmsg_internal("unrecognized boolop: %d",
(int)expr->boolop)));
return NULL;
}
foreach (la, expr->args)
{
Node *arg = lfirst(la);
arg = transform_cypher_expr_recurse(cpstate, arg);
arg = coerce_to_boolean(pstate, arg, opname);
args = lappend(args, arg);
}
return (Node *)makeBoolExpr(expr->boolop, args, expr->location);
}
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;
}