blob: a0a6c35f61ef85da1350e437a66a091c828cbbe5 [file] [log] [blame]
%{
/*
* 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 "parser/parser.h"
#include "parser/cypher_gram.h"
#include "parser/cypher_parse_node.h"
#include "parser/scansup.h"
// override the default action for locations
#define YYLLOC_DEFAULT(current, rhs, n) \
do \
{ \
if ((n) > 0) \
current = (rhs)[1]; \
else \
current = -1; \
} while (0)
#define YYMALLOC palloc
#define YYFREE pfree
%}
%locations
%name-prefix="cypher_yy"
%pure-parser
%lex-param {ag_scanner_t scanner}
%parse-param {ag_scanner_t scanner}
%parse-param {cypher_yy_extra *extra}
%union {
/* types in cypher_yylex() */
int integer;
char *string;
const char *keyword;
/* extra types */
bool boolean;
Node *node;
List *list;
}
%token <integer> INTEGER
%token <string> DECIMAL STRING
%token <string> IDENTIFIER
%token <string> PARAMETER
/* operators that have more than 1 character */
%token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE CONCAT
%token ACCESS_PATH LEFT_CONTAINS RIGHT_CONTAINS ANY_EXISTS ALL_EXISTS
/* keywords in alphabetical order */
%token <keyword> ALL ANALYZE AND AS ASC ASCENDING
BY
CALL CASE COALESCE CONTAINS COUNT CREATE
DELETE DESC DESCENDING DETACH DISTINCT
ELSE END_P ENDS EXISTS EXPLAIN
FALSE_P
IN IS
LIMIT
MATCH MERGE
NOT NULL_P
OPTIONAL OR ORDER
REMOVE RETURN
SET SKIP STARTS
THEN TRUE_P
UNION UNWIND
VERBOSE
WHEN WHERE WITH
XOR
YIELD
/* query */
%type <node> stmt
%type <list> single_query query_part_init query_part_last cypher_stmt
reading_clause_list updating_clause_list_0 updating_clause_list_1
%type <node> reading_clause updating_clause
%type <list> subquery_stmt subquery_stmt_with_return subquery_stmt_no_return
single_subquery single_subquery_no_return subquery_part_init
%type <node> subquery_pattern
/* RETURN and WITH clause */
%type <node> return return_item sort_item skip_opt limit_opt with
%type <list> return_item_list order_by_opt sort_item_list
%type <integer> order_opt
/* MATCH clause */
%type <node> match cypher_varlen_opt cypher_range_opt cypher_range_idx
cypher_range_idx_opt
%type <integer> Iconst
%type <boolean> optional_opt
/* CREATE clause */
%type <node> create
/* UNWIND clause */
%type <node> unwind
/* list comprehension */
%type <node> list_comprehension
/* SET and REMOVE clause */
%type <node> set set_item remove remove_item
%type <list> set_item_list remove_item_list
/* DELETE clause */
%type <node> delete
%type <boolean> detach_opt
/* MERGE clause */
%type <node> merge
/* CALL ... YIELD clause */
%type <node> call_stmt yield_item
%type <list> yield_item_list
/* common */
%type <node> where_opt
/* pattern */
%type <list> pattern simple_path_opt_parens simple_path
%type <node> path anonymous_path
path_node path_relationship path_relationship_body
properties_opt
%type <string> label_opt
/* expression */
%type <node> expr expr_opt expr_atom expr_literal map list
%type <node> expr_case expr_case_when expr_case_default
%type <list> expr_case_when_list
%type <node> map_projection map_projection_elem
%type <list> map_projection_elem_list
%type <node> expr_var expr_func expr_func_norm expr_func_subexpr
%type <list> expr_list expr_list_opt map_keyval_list_opt map_keyval_list
%type <node> property_value
%type <node> expr_subquery
/* names */
%type <string> property_key_name var_name var_name_opt label_name
%type <string> symbolic_name schema_name
%type <keyword> reserved_keyword safe_keywords conflicted_keywords
%type <list> func_name
/* precedence: lowest to highest */
%left UNION
%left OR
%left AND
%left XOR
%right NOT
%left '=' NOT_EQ '<' LT_EQ '>' GT_EQ
%left '@' '|' '&' '?' LEFT_CONTAINS RIGHT_CONTAINS ANY_EXISTS ALL_EXISTS
%left '+' '-' CONCAT
%left '*' '/' '%'
%left '^'
%nonassoc IN IS
%right UNARY_MINUS
%nonassoc CONTAINS ENDS EQ_TILDE STARTS
%left '[' ']' '(' ')'
%left '.' ACCESS_PATH
%left TYPECAST
/*set operations*/
%type <boolean> all_or_distinct
/* utility options */
%type <list> utility_option_list
%type <node> utility_option_elem utility_option_arg
%type <string> utility_option_name
%{
//
// internal alias check
static bool has_internal_default_prefix(char *str);
// unique name generation
#define UNIQUE_NAME_NULL_PREFIX AGE_DEFAULT_PREFIX"unique_null_prefix"
static char *create_unique_name(char *prefix_name);
static unsigned long get_a_unique_number(void);
// logical operators
static Node *make_or_expr(Node *lexpr, Node *rexpr, int location);
static Node *make_and_expr(Node *lexpr, Node *rexpr, int location);
static Node *make_xor_expr(Node *lexpr, Node *rexpr, int location);
static Node *make_not_expr(Node *expr, int location);
static Node *make_comparison_and_expr(Node *lexpr, Node *rexpr, int location);
static Node *make_cypher_comparison_aexpr(A_Expr_Kind kind, char *name,
Node *lexpr, Node *rexpr,
int location);
static Node *make_cypher_comparison_boolexpr(BoolExprType boolop, List *args,
int location);
// arithmetic operators
static Node *do_negate(Node *n, int location);
static void do_negate_float(Float *v);
// indirection
static Node *append_indirection(Node *expr, Node *selector);
// literals
static Node *make_int_const(int i, int location);
static Node *make_float_const(char *s, int location);
static Node *make_string_const(char *s, int location);
static Node *make_bool_const(bool b, int location);
static Node *make_null_const(int location);
// typecast
static Node *make_typecast_expr(Node *expr, char *typecast, int location);
// functions
static Node *make_function_expr(List *func_name, List *exprs, int location);
static Node *make_star_function_expr(List *func_name, List *exprs, int location);
static Node *make_distinct_function_expr(List *func_name, List *exprs, int location);
static FuncCall *node_to_agtype(Node* fnode, char *type, int location);
// setops
static Node *make_set_op(SetOperation op, bool all_or_distinct, List *larg,
List *rarg);
// VLE
static cypher_relationship *build_VLE_relation(List *left_arg,
cypher_relationship *cr,
Node *right_arg,
int left_arg_location,
int cr_location);
// comparison
static bool is_A_Expr_a_comparison_operation(cypher_comparison_aexpr *a);
static Node *build_comparison_expression(Node *left_grammar_node,
Node *right_grammar_node,
char *opr_name, int location);
// list_comprehension
static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2,
Node *where, Node *mapping_expr,
int var_loc, int expr_loc,
int where_loc, int mapping_loc);
static Node *build_list_comprehension_node(ColumnRef *var_name, Node *expr,
Node *where, Node *mapping_expr,
int var_loc, int expr_loc,
int where_loc,int mapping_loc);
%}
%%
/*
* query
*/
stmt:
cypher_stmt semicolon_opt
{
/*
* If there is no transition for the lookahead token and the
* clauses can be reduced to single_query, the parsing is
* considered successful although it actually isn't.
*
* For example, when `MATCH ... CREATE ... MATCH ... ;` query is
* being parsed, there is no transition for the second `MATCH ...`
* because the query is wrong but `MATCH .. CREATE ...` is correct
* so it will be reduced to query_part_last anyway even if there
* are more tokens to read.
*
* Throw syntax error in this case.
*/
if (yychar != YYEOF)
yyerror(&yylloc, scanner, extra, "syntax error");
extra->result = $1;
extra->extra = NULL;
}
| EXPLAIN cypher_stmt semicolon_opt
{
ExplainStmt *estmt = NULL;
if (yychar != YYEOF)
yyerror(&yylloc, scanner, extra, "syntax error");
extra->result = $2;
estmt = makeNode(ExplainStmt);
estmt->query = NULL;
estmt->options = NIL;
extra->extra = (Node *)estmt;
}
| EXPLAIN VERBOSE cypher_stmt semicolon_opt
{
ExplainStmt *estmt = NULL;
if (yychar != YYEOF)
yyerror(&yylloc, scanner, extra, "syntax error");
extra->result = $3;
estmt = makeNode(ExplainStmt);
estmt->query = NULL;
estmt->options = list_make1(makeDefElem("verbose", NULL, @2));;
extra->extra = (Node *)estmt;
}
| EXPLAIN ANALYZE cypher_stmt semicolon_opt
{
ExplainStmt *estmt = NULL;
if (yychar != YYEOF)
yyerror(&yylloc, scanner, extra, "syntax error");
extra->result = $3;
estmt = makeNode(ExplainStmt);
estmt->query = NULL;
estmt->options = list_make1(makeDefElem("analyze", NULL, @2));;
extra->extra = (Node *)estmt;
}
| EXPLAIN ANALYZE VERBOSE cypher_stmt semicolon_opt
{
ExplainStmt *estmt = NULL;
if (yychar != YYEOF)
yyerror(&yylloc, scanner, extra, "syntax error");
extra->result = $4;
estmt = makeNode(ExplainStmt);
estmt->query = NULL;
estmt->options = list_make2(makeDefElem("analyze", NULL, @2),
makeDefElem("verbose", NULL, @3));;
extra->extra = (Node *)estmt;
}
| EXPLAIN '(' utility_option_list ')' cypher_stmt semicolon_opt
{
ExplainStmt *estmt = NULL;
if (yychar != YYEOF)
yyerror(&yylloc, scanner, extra, "syntax error");
extra->result = $5;
estmt = makeNode(ExplainStmt);
estmt->query = NULL;
estmt->options = $3;
extra->extra = (Node *)estmt;
}
;
cypher_stmt:
single_query
{
$$ = $1;
}
| cypher_stmt UNION all_or_distinct cypher_stmt
{
$$ = list_make1(make_set_op(SETOP_UNION, $3, $1, $4));
}
;
call_stmt:
CALL expr_func_norm
{
cypher_call *n = make_ag_node(cypher_call);
n->funccall = castNode (FuncCall, $2);
$$ = (Node *)n;
}
| CALL expr '.' expr
{
cypher_call *n = make_ag_node(cypher_call);
if (IsA($4, FuncCall) && IsA($2, ColumnRef))
{
FuncCall *fc = (FuncCall*)$4;
ColumnRef *cr = (ColumnRef*)$2;
List *fields = cr->fields;
String *string = linitial(fields);
/*
* A function can only be qualified with a single schema. So, we
* check to see that the function isn't already qualified. There
* may be unforeseen cases where we might need to remove this in
* the future.
*/
if (list_length(fc->funcname) == 1)
{
fc->funcname = lcons(string, fc->funcname);
$$ = (Node*)fc;
}
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("function already qualified"),
ag_scanner_errposition(@1, scanner)));
n->funccall = fc;
$$ = (Node *)n;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CALL statement must be a qualified function"),
ag_scanner_errposition(@1, scanner)));
}
}
| CALL expr_func_norm YIELD yield_item_list where_opt
{
cypher_call *n = make_ag_node(cypher_call);
n->funccall = castNode (FuncCall, $2);
n->yield_items = $4;
n->where = $5;
$$ = (Node *)n;
}
| CALL expr '.' expr YIELD yield_item_list where_opt
{
cypher_call *n = make_ag_node(cypher_call);
if (IsA($4, FuncCall) && IsA($2, ColumnRef))
{
FuncCall *fc = (FuncCall*)$4;
ColumnRef *cr = (ColumnRef*)$2;
List *fields = cr->fields;
String *string = linitial(fields);
/*
* A function can only be qualified with a single schema. So, we
* check to see that the function isn't already qualified. There
* may be unforeseen cases where we might need to remove this in
* the future.
*/
if (list_length(fc->funcname) == 1)
{
fc->funcname = lcons(string, fc->funcname);
$$ = (Node*)fc;
}
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("function already qualified"),
ag_scanner_errposition(@1, scanner)));
n->funccall = fc;
n->yield_items = $6;
n->where = $7;
$$ = (Node *)n;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CALL statement must be a qualified function"),
ag_scanner_errposition(@1, scanner)));
}
}
;
yield_item_list:
yield_item
{
$$ = list_make1($1);
}
| yield_item_list ',' yield_item
{
$$ = lappend($1, $3);
}
;
yield_item:
expr AS var_name
{
ResTarget *n;
n = makeNode(ResTarget);
n->name = $3;
n->indirection = NIL;
n->val = $1;
n->location = @1;
$$ = (Node *)n;
}
| expr
{
ResTarget *n;
n = makeNode(ResTarget);
n->name = NULL;
n->indirection = NIL;
n->val = $1;
n->location = @1;
$$ = (Node *)n;
}
;
semicolon_opt:
/* empty */
| ';'
;
all_or_distinct:
ALL
{
$$ = true;
}
| DISTINCT
{
$$ = false;
}
| /*EMPTY*/
{
$$ = false;
}
/*
* The overall structure of single_query looks like below.
*
* ( reading_clause* updating_clause* with )*
* reading_clause* ( updating_clause+ | updating_clause* return )
*/
single_query:
query_part_init query_part_last
{
$$ = list_concat($1, $2);
}
;
query_part_init:
/* empty */
{
$$ = NIL;
}
| query_part_init reading_clause_list updating_clause_list_0 with
{
$$ = lappend(list_concat(list_concat($1, $2), $3), $4);
}
;
query_part_last:
reading_clause_list updating_clause_list_1
{
$$ = list_concat($1, $2);
}
| reading_clause_list updating_clause_list_0 return
{
$$ = lappend(list_concat($1, $2), $3);
}
| reading_clause_list call_stmt
{
$$ = list_concat($1, list_make1($2));
}
;
reading_clause_list:
/* empty */
{
$$ = NIL;
}
| reading_clause_list reading_clause
{
$$ = lappend($1, $2);
}
;
reading_clause:
match
| unwind
| call_stmt
;
updating_clause_list_0:
/* empty */
{
$$ = NIL;
}
| updating_clause_list_1
;
updating_clause_list_1:
updating_clause
{
$$ = list_make1($1);
}
| updating_clause_list_1 updating_clause
{
$$ = lappend($1, $2);
}
;
updating_clause:
create
| set
| remove
| delete
| merge
;
subquery_stmt:
subquery_stmt_with_return
{
$$ = $1;
}
| subquery_stmt_no_return
{
$$ = $1;
}
;
subquery_stmt_with_return:
single_subquery
{
$$ = $1;
}
| subquery_stmt_with_return UNION all_or_distinct subquery_stmt_with_return
{
$$ = list_make1(make_set_op(SETOP_UNION, $3, $1, $4));
}
;
subquery_stmt_no_return:
single_subquery_no_return
{
$$ = $1;
}
| subquery_stmt_no_return UNION all_or_distinct subquery_stmt_no_return
{
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Subquery UNION without returns not yet implemented"),
ag_scanner_errposition(@2, scanner)));
}
;
single_subquery:
subquery_part_init reading_clause_list return
{
$$ = list_concat($1, lappend($2, $3));
}
;
single_subquery_no_return:
subquery_part_init reading_clause_list
{
ColumnRef *cr;
ResTarget *rt;
cypher_return *n;
/*
* since subqueries allow return-less clauses, we add a
* return node manually to reflect that syntax
*/
cr = makeNode(ColumnRef);
cr->fields = list_make1(makeNode(A_Star));
cr->location = @1;
rt = makeNode(ResTarget);
rt->name = NULL;
rt->indirection = NIL;
rt->val = (Node *)cr;
rt->location = @1;
n = make_ag_node(cypher_return);
n->distinct = false;
n->items = list_make1((Node *)rt);
n->order_by = NULL;
n->skip = NULL;
n->limit = NULL;
$$ = list_concat($1, lappend($2, n));
}
| subquery_pattern
{
ColumnRef *cr;
ResTarget *rt;
cypher_return *n;
cr = makeNode(ColumnRef);
cr->fields = list_make1(makeNode(A_Star));
cr->location = @1;
rt = makeNode(ResTarget);
rt->name = NULL;
rt->indirection = NIL;
rt->val = (Node *)cr;
rt->location = @1;
n = make_ag_node(cypher_return);
n->distinct = false;
n->items = list_make1((Node *)rt);
n->order_by = NULL;
n->skip = NULL;
n->limit = NULL;
$$ = lappend(list_make1($1), n);
}
;
subquery_part_init:
/* empty */
{
$$ = NIL;
}
| subquery_part_init reading_clause_list with
{
$$ = lappend(list_concat($1, $2), $3);
}
;
cypher_varlen_opt:
'*' cypher_range_opt
{
A_Indices *n = (A_Indices *) $2;
if (n->lidx == NULL)
n->lidx = make_int_const(1, @2);
if (n->uidx != NULL)
{
A_Const *lidx = (A_Const *) n->lidx;
A_Const *uidx = (A_Const *) n->uidx;
if (lidx->val.ival.ival > uidx->val.ival.ival)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid range"),
ag_scanner_errposition(@2, scanner)));
}
$$ = (Node *) n;
}
| /* EMPTY */
{
$$ = NULL;
}
;
cypher_range_opt:
cypher_range_idx
{
A_Indices *n;
n = makeNode(A_Indices);
n->lidx = copyObject($1);
n->uidx = $1;
$$ = (Node *) n;
}
| cypher_range_idx_opt DOT_DOT cypher_range_idx_opt
{
A_Indices *n;
n = makeNode(A_Indices);
n->lidx = $1;
n->uidx = $3;
$$ = (Node *) n;
}
| /* EMPTY */
{
$$ = (Node *) makeNode(A_Indices);
}
;
cypher_range_idx:
Iconst
{
$$ = make_int_const($1, @1);
}
;
cypher_range_idx_opt:
cypher_range_idx
| /* EMPTY */ { $$ = NULL; }
;
Iconst: INTEGER
/*
* RETURN and WITH clause
*/
return:
RETURN DISTINCT return_item_list order_by_opt skip_opt limit_opt
{
cypher_return *n;
n = make_ag_node(cypher_return);
n->distinct = true;
n->items = $3;
n->order_by = $4;
n->skip = $5;
n->limit = $6;
$$ = (Node *)n;
}
| RETURN return_item_list order_by_opt skip_opt limit_opt
{
cypher_return *n;
n = make_ag_node(cypher_return);
n->distinct = false;
n->items = $2;
n->order_by = $3;
n->skip = $4;
n->limit = $5;
$$ = (Node *)n;
}
;
return_item_list:
return_item
{
$$ = list_make1($1);
}
| return_item_list ',' return_item
{
$$ = lappend($1, $3);
}
;
return_item:
expr AS var_name
{
ResTarget *n;
n = makeNode(ResTarget);
n->name = $3;
n->indirection = NIL;
n->val = $1;
n->location = @1;
$$ = (Node *)n;
}
| expr
{
ResTarget *n;
n = makeNode(ResTarget);
n->name = NULL;
n->indirection = NIL;
n->val = $1;
n->location = @1;
$$ = (Node *)n;
}
| '*'
{
ColumnRef *cr;
ResTarget *rt;
cr = makeNode(ColumnRef);
cr->fields = list_make1(makeNode(A_Star));
cr->location = @1;
rt = makeNode(ResTarget);
rt->name = NULL;
rt->indirection = NIL;
rt->val = (Node *)cr;
rt->location = @1;
$$ = (Node *)rt;
}
;
order_by_opt:
/* empty */
{
$$ = NIL;
}
| ORDER BY sort_item_list
{
$$ = $3;
}
;
sort_item_list:
sort_item
{
$$ = list_make1($1);
}
| sort_item_list ',' sort_item
{
$$ = lappend($1, $3);
}
;
sort_item:
expr order_opt
{
SortBy *n;
n = makeNode(SortBy);
n->node = $1;
n->sortby_dir = $2;
n->sortby_nulls = SORTBY_NULLS_DEFAULT;
n->useOp = NIL;
n->location = -1; // no operator
$$ = (Node *)n;
}
;
order_opt:
/* empty */
{
$$ = SORTBY_DEFAULT; // is the same with SORTBY_ASC
}
| ASC
{
$$ = SORTBY_ASC;
}
| ASCENDING
{
$$ = SORTBY_ASC;
}
| DESC
{
$$ = SORTBY_DESC;
}
| DESCENDING
{
$$ = SORTBY_DESC;
}
;
skip_opt:
/* empty */
{
$$ = NULL;
}
| SKIP expr
{
$$ = $2;
}
;
limit_opt:
/* empty */
{
$$ = NULL;
}
| LIMIT expr
{
$$ = $2;
}
;
with:
WITH DISTINCT return_item_list order_by_opt skip_opt limit_opt where_opt
{
ListCell *li;
cypher_with *n;
// check expressions are aliased
foreach(li, $3)
{
ResTarget *item = lfirst(li);
// variable does not have to be aliased
if (IsA(item->val, ColumnRef) || item->name)
continue;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("expression item must be aliased"),
errhint("Items can be aliased by using AS."),
ag_scanner_errposition(item->location, scanner)));
}
n = make_ag_node(cypher_with);
n->distinct = true;
n->items = $3;
n->order_by = $4;
n->skip = $5;
n->limit = $6;
n->where = $7;
$$ = (Node *)n;
}
| WITH return_item_list order_by_opt skip_opt limit_opt
where_opt
{
ListCell *li;
cypher_with *n;
// check expressions are aliased
foreach (li, $2)
{
ResTarget *item = lfirst(li);
// variable does not have to be aliased
if (IsA(item->val, ColumnRef) || item->name)
continue;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("expression item must be aliased"),
errhint("Items can be aliased by using AS."),
ag_scanner_errposition(item->location, scanner)));
}
n = make_ag_node(cypher_with);
n->distinct = false;
n->items = $2;
n->order_by = $3;
n->skip = $4;
n->limit = $5;
n->where = $6;
$$ = (Node *)n;
}
;
/*
* MATCH clause
*/
match:
optional_opt MATCH pattern where_opt
{
cypher_match *n;
n = make_ag_node(cypher_match);
n->optional = $1;
n->pattern = $3;
n->where = $4;
$$ = (Node *)n;
}
;
optional_opt:
OPTIONAL
{
$$ = true;
}
| /* EMPTY */
{
$$ = false;
}
;
unwind:
UNWIND expr AS var_name
{
ResTarget *res;
cypher_unwind *n;
res = makeNode(ResTarget);
res->name = $4;
res->val = (Node *) $2;
res->location = @2;
n = make_ag_node(cypher_unwind);
n->target = res;
n->where = NULL;
n->collect = NULL;
$$ = (Node *) n;
}
/*
* CREATE clause
*/
create:
CREATE pattern
{
cypher_create *n;
n = make_ag_node(cypher_create);
n->pattern = $2;
$$ = (Node *)n;
}
;
/*
* SET and REMOVE clause
*/
set:
SET set_item_list
{
cypher_set *n;
n = make_ag_node(cypher_set);
n->items = $2;
n->is_remove = false;
n->location = @1;
$$ = (Node *)n;
}
;
set_item_list:
set_item
{
$$ = list_make1($1);
}
| set_item_list ',' set_item
{
$$ = lappend($1, $3);
}
;
set_item:
expr '=' expr
{
cypher_set_item *n;
n = make_ag_node(cypher_set_item);
n->prop = $1;
n->expr = $3;
n->is_add = false;
n->location = @1;
$$ = (Node *)n;
}
| expr PLUS_EQ expr
{
cypher_set_item *n;
n = make_ag_node(cypher_set_item);
n->prop = $1;
n->expr = $3;
n->is_add = true;
n->location = @1;
$$ = (Node *)n;
}
;
remove:
REMOVE remove_item_list
{
cypher_set *n;
n = make_ag_node(cypher_set);
n->items = $2;
n->is_remove = true;
n->location = @1;
$$ = (Node *)n;
}
;
remove_item_list:
remove_item
{
$$ = list_make1($1);
}
| remove_item_list ',' remove_item
{
$$ = lappend($1, $3);
}
;
remove_item:
expr
{
cypher_set_item *n;
n = make_ag_node(cypher_set_item);
n->prop = $1;
n->expr = make_null_const(-1);
n->is_add = false;
$$ = (Node *)n;
}
;
/*
* DELETE clause
*/
delete:
detach_opt DELETE expr_list
{
cypher_delete *n;
n = make_ag_node(cypher_delete);
n->detach = $1;
n->exprs = $3;
n->location = @1;
$$ = (Node *)n;
}
;
detach_opt:
DETACH
{
$$ = true;
}
| /* EMPTY */
{
$$ = false;
}
;
/*
* MERGE clause
*/
merge:
MERGE path
{
cypher_merge *n;
n = make_ag_node(cypher_merge);
n->path = $2;
$$ = (Node *)n;
}
;
/*
* common
*/
where_opt:
/* empty */
{
$$ = NULL;
}
| WHERE expr
{
$$ = $2;
}
;
utility_option_list:
utility_option_elem
{
$$ = list_make1($1);
}
| utility_option_list ',' utility_option_elem
{
$$ = lappend($1, $3);
}
;
utility_option_elem:
utility_option_name utility_option_arg
{
$$ = (Node *)makeDefElem($1, $2, @1);
}
;
utility_option_name:
IDENTIFIER
{
char *modified_name = downcase_truncate_identifier($1, strlen($1),
true);
$$ = modified_name;
}
| safe_keywords
{
char *name = pstrdup($1);
char *modified_name = downcase_truncate_identifier(name,
strlen(name),
true);
$$ = modified_name;
}
;
utility_option_arg:
IDENTIFIER
{
char *modified_val = downcase_truncate_identifier($1, strlen($1),
true);
$$ = (Node *)makeString(modified_val);
}
| INTEGER
{
$$ = (Node *)makeInteger($1);
}
| TRUE_P
{
$$ = (Node *)makeString("true");
}
| FALSE_P
{
$$ = (Node *)makeString("false");
}
| /* EMPTY */
{
$$ = NULL;
}
;
/*
* pattern
*/
/* pattern is a set of one or more paths */
pattern:
path
{
$$ = list_make1($1);
}
| pattern ',' path
{
$$ = lappend($1, $3);
}
;
/* path is a series of connected nodes and relationships */
path:
anonymous_path
| var_name '=' anonymous_path /* named path */
{
cypher_path *p;
p = (cypher_path *)$3;
p->var_name = $1;
p->parsed_var_name = $1;
p->location = @1;
$$ = (Node *)p;
}
;
anonymous_path:
simple_path_opt_parens
{
cypher_path *n;
n = make_ag_node(cypher_path);
n->path = $1;
n->var_name = NULL;
n->parsed_var_name = NULL;
n->location = @1;
$$ = (Node *)n;
}
;
simple_path_opt_parens:
simple_path
| '(' simple_path ')'
{
$$ = $2;
}
;
simple_path:
path_node
{
$$ = list_make1($1);
}
| simple_path path_relationship path_node
{
cypher_relationship *cr = NULL;
/* get the relationship */
cr = (cypher_relationship *)$2;
/* if this is a VLE relation node */
if (cr->varlen != NULL)
{
/* build the VLE relation */
cr = build_VLE_relation($1, cr, $3, @1, @2);
/* return the VLE relation in the path */
$$ = lappend(lappend($1, cr), $3);
}
/* otherwise, it is a regular relationship node */
else
{
$$ = lappend(lappend($1, $2), $3);
}
}
;
path_node:
'(' var_name_opt label_opt properties_opt ')'
{
cypher_node *n;
n = make_ag_node(cypher_node);
n->name = $2;
n->parsed_name = $2;
n->label = $3;
n->parsed_label = $3;
n->use_equals = false;
n->props = $4;
n->location = @2;
$$ = (Node *)n;
}
| '(' var_name_opt label_opt '='properties_opt ')'
{
cypher_node *n;
n = make_ag_node(cypher_node);
n->name = $2;
n->parsed_name = $2;
n->label = $3;
n->parsed_label = $3;
n->use_equals = true;
n->props = $5;
n->location = @2;
$$ = (Node *)n;
}
;
path_relationship:
'-' path_relationship_body '-'
{
cypher_relationship *n = (cypher_relationship *)$2;
n->dir = CYPHER_REL_DIR_NONE;
n->location = @2;
$$ = $2;
}
| '-' path_relationship_body '-' '>'
{
cypher_relationship *n = (cypher_relationship *)$2;
n->dir = CYPHER_REL_DIR_RIGHT;
n->location = @2;
$$ = $2;
}
| '<' '-' path_relationship_body '-'
{
cypher_relationship *n = (cypher_relationship *)$3;
n->dir = CYPHER_REL_DIR_LEFT;
n->location = @3;
$$ = $3;
}
;
path_relationship_body:
'[' var_name_opt label_opt cypher_varlen_opt properties_opt ']'
{
cypher_relationship *n;
n = make_ag_node(cypher_relationship);
n->name = $2;
n->parsed_name = $2;
n->label = $3;
n->parsed_label = $3;
n->varlen = $4;
n->use_equals = false;
n->props = $5;
$$ = (Node *)n;
}
| '[' var_name_opt label_opt cypher_varlen_opt '='properties_opt ']'
{
cypher_relationship *n;
n = make_ag_node(cypher_relationship);
n->name = $2;
n->parsed_name = $2;
n->label = $3;
n->parsed_label = $3;
n->varlen = $4;
n->use_equals = true;
n->props = $6;
$$ = (Node *)n;
}
|
/* empty */
{
cypher_relationship *n;
n = make_ag_node(cypher_relationship);
n->name = NULL;
n->parsed_name = NULL;
n->label = NULL;
n->parsed_label = NULL;
n->varlen = NULL;
n->use_equals = false;
n->props = NULL;
$$ = (Node *)n;
}
;
label_opt:
/* empty */
{
$$ = NULL;
}
| ':' label_name
{
$$ = $2;
}
;
properties_opt:
/* empty */
{
$$ = NULL;
}
| map
| PARAMETER
{
cypher_param *n;
n = make_ag_node(cypher_param);
n->name = $1;
n->location = @1;
$$ = (Node *)n;
}
;
/*
* expression
*/
expr:
expr OR expr
{
$$ = make_or_expr($1, $3, @2);
}
| expr AND expr
{
$$ = make_and_expr($1, $3, @2);
}
| expr XOR expr
{
$$ = make_xor_expr($1, $3, @2);
}
| NOT expr
{
$$ = make_not_expr($2, @1);
}
| expr '=' expr
{
$$ = build_comparison_expression($1, $3, "=", @2);
}
| expr NOT_EQ expr
{
$$ = build_comparison_expression($1, $3, "<>", @2);
}
| expr '<' expr
{
$$ = build_comparison_expression($1, $3, "<", @2);
}
| expr LT_EQ expr
{
$$ = build_comparison_expression($1, $3, "<=", @2);
}
| expr '>' expr
{
$$ = build_comparison_expression($1, $3, ">", @2);
}
| expr GT_EQ expr
{
$$ = build_comparison_expression($1, $3, ">=", @2);
}
| expr LEFT_CONTAINS expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "<@", $1, $3, @2);
}
| expr RIGHT_CONTAINS expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "@>", $1, $3, @2);
}
| expr '?' expr %prec '.'
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "?", $1, $3, @2);
}
| expr ANY_EXISTS expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "?|", $1, $3, @2);
}
| expr ALL_EXISTS expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "?&", $1, $3, @2);
}
| expr CONCAT expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "||", $1, $3, @2);
}
| expr ACCESS_PATH expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "#>", $1, $3, @2);
}
| expr '+' expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "+", $1, $3, @2);
}
| expr '-' expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "-", $1, $3, @2);
}
| expr '*' expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "*", $1, $3, @2);
}
| expr '/' expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "/", $1, $3, @2);
}
| expr '%' expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "%", $1, $3, @2);
}
| expr '^' expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "^", $1, $3, @2);
}
| expr IN expr
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
}
| expr IS NULL_P %prec IS
{
NullTest *n;
n = makeNode(NullTest);
n->arg = (Expr *)$1;
n->nulltesttype = IS_NULL;
n->location = @2;
$$ = (Node *)n;
}
| expr IS NOT NULL_P %prec IS
{
NullTest *n;
n = makeNode(NullTest);
n->arg = (Expr *)$1;
n->nulltesttype = IS_NOT_NULL;
n->location = @2;
$$ = (Node *)n;
}
| '-' expr %prec UNARY_MINUS
{
$$ = do_negate($2, @1);
}
| expr STARTS WITH expr %prec STARTS
{
cypher_string_match *n;
n = make_ag_node(cypher_string_match);
n->operation = CSMO_STARTS_WITH;
n->lhs = $1;
n->rhs = $4;
n->location = @2;
$$ = (Node *)n;
}
| expr ENDS WITH expr %prec ENDS
{
cypher_string_match *n;
n = make_ag_node(cypher_string_match);
n->operation = CSMO_ENDS_WITH;
n->lhs = $1;
n->rhs = $4;
n->location = @2;
$$ = (Node *)n;
}
| expr CONTAINS expr
{
cypher_string_match *n;
n = make_ag_node(cypher_string_match);
n->operation = CSMO_CONTAINS;
n->lhs = $1;
n->rhs = $3;
n->location = @2;
$$ = (Node *)n;
}
| expr EQ_TILDE expr
{
$$ = make_function_expr(list_make1(makeString("eq_tilde")),
list_make2($1, $3), @2);
}
| expr '[' expr ']'
{
A_Indices *i;
i = makeNode(A_Indices);
i->is_slice = false;
i->lidx = NULL;
i->uidx = $3;
$$ = append_indirection($1, (Node *)i);
}
| expr '[' expr_opt DOT_DOT expr_opt ']'
{
A_Indices *i;
i = makeNode(A_Indices);
i->is_slice = true;
i->lidx = $3;
i->uidx = $5;
$$ = append_indirection($1, (Node *)i);
}
/*
* This is a catch all grammar rule that allows us to avoid some
* shift/reduce errors between expression indirection rules by collapsing
* those rules into one generic rule. We can then inspect the expressions to
* decide what specific rule needs to be applied and then construct the
* required result.
*/
| expr '.' expr
{
/*
* This checks for the grammar rule -
* expr '.' property_key_name
* where the expr can be anything.
* Note: A property_key_name ends up as a ColumnRef.
* Note: We restrict some of what the expr can be, for now. More may
* need to be added later to loosen the restrictions. Or, it
* may need to be removed.
*/
if (IsA($3, ColumnRef) &&
(IsA($1, ExtensibleNode) ||
IsA($1, ColumnRef) ||
IsA($1, A_Indirection)))
{
ColumnRef *cr = (ColumnRef*)$3;
List *fields = cr->fields;
String *string = linitial(fields);
$$ = append_indirection($1, (Node*)string);
}
/*
* This checks for the grammar rule -
* symbolic_name '.' expr
* Where expr is a function call.
* Note: symbolic_name ends up as a ColumnRef
*/
else if (IsA($3, FuncCall) && IsA($1, ColumnRef))
{
FuncCall *fc = (FuncCall*)$3;
ColumnRef *cr = (ColumnRef*)$1;
List *fields = cr->fields;
String *string = linitial(fields);
/*
* A function can only be qualified with a single schema. So, we
* check to see that the function isn't already qualified. There
* may be unforeseen cases where we might need to remove this in
* the future.
*/
if (list_length(fc->funcname) == 1)
{
fc->funcname = lcons(string, fc->funcname);
$$ = (Node*)fc;
}
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("function already qualified"),
ag_scanner_errposition(@1, scanner)));
}
/* allow a function to be used as a parent of an indirection */
else if (IsA($1, FuncCall) && IsA($3, ColumnRef))
{
ColumnRef *cr = (ColumnRef*)$3;
List *fields = cr->fields;
String *string = linitial(fields);
$$ = append_indirection($1, (Node*)string);
}
else if (IsA($1, FuncCall) && IsA($3, A_Indirection))
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("not supported A_Indirection indirection"),
ag_scanner_errposition(@1, scanner)));
}
/*
* All other types of expression indirections are currently not
* supported
*/
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid indirection syntax"),
ag_scanner_errposition(@1, scanner)));
}
}
| expr '-' '>' expr %prec '.'
{
$$ = (Node *)makeSimpleA_Expr(AEXPR_OP, "->", $1, $4, @2);
}
| expr TYPECAST symbolic_name
{
$$ = make_typecast_expr($1, $3, @2);
}
| expr_atom
;
expr_opt:
/* empty */
{
$$ = NULL;
}
| expr
;
expr_list:
expr
{
$$ = list_make1($1);
}
| expr_list ',' expr
{
$$ = lappend($1, $3);
}
;
expr_list_opt:
/* empty */
{
$$ = NIL;
}
| expr_list
;
expr_func:
expr_func_norm
| expr_func_subexpr
;
expr_func_norm:
func_name '(' ')'
{
$$ = make_function_expr($1, NIL, @1);
}
| func_name '(' expr_list ')'
{
$$ = make_function_expr($1, $3, @2);
}
/* borrowed from PG's grammar */
| func_name '(' '*' ')'
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
* aggregate. This does the right thing for COUNT(*),
* and there are no other aggregates in SQL that accept
* '*' as parameter.
*
* The FuncCall node is marked agg_star = true by make_star_function_expr,
* so that later processing can detect what the argument
* really was.
*/
FuncCall *n = (FuncCall *)make_star_function_expr($1, NIL, @1);
$$ = (Node *)n;
}
| func_name '(' DISTINCT expr_list ')'
{
FuncCall *n = (FuncCall *)make_distinct_function_expr($1, $4, @1);
$$ = (Node *)n;
}
;
expr_func_subexpr:
COALESCE '(' expr_list ')'
{
CoalesceExpr *c;
c = makeNode(CoalesceExpr);
c->args = $3;
c->location = @1;
$$ = (Node *) c;
}
| EXISTS '(' anonymous_path ')'
{
cypher_sub_pattern *sub;
SubLink *n;
sub = make_ag_node(cypher_sub_pattern);
sub->kind = CSP_EXISTS;
sub->pattern = list_make1($3);
n = makeNode(SubLink);
n->subLinkType = EXISTS_SUBLINK;
n->subLinkId = 0;
n->testexpr = NULL;
n->operName = NIL;
n->subselect = (Node *) sub;
n->location = @1;
$$ = (Node *)node_to_agtype((Node *)n, "boolean", @1);
}
| EXISTS '(' property_value ')'
{
FuncCall *n;
n = makeFuncCall(list_make1(makeString("exists")),
list_make1($3), COERCE_SQL_SYNTAX, @2);
$$ = (Node *)node_to_agtype((Node *)n, "boolean", @2);
}
| COUNT '(' ')'
{
$$ = make_function_expr(list_make1(makeString("count")), NIL, @1);
}
| COUNT '(' expr_list ')'
{
$$ = make_function_expr(list_make1(makeString("count")), $3, @2);
}
| COUNT '(' DISTINCT expr_list ')'
{
FuncCall *n = (FuncCall *)make_distinct_function_expr(
list_make1(makeString("count")), $4, @1);
$$ = (Node *)n;
}
| COUNT '(' '*' ')'
{
FuncCall *n = (FuncCall *)make_star_function_expr(
list_make1(makeString("count")), NIL, @1);
$$ = (Node *)n;
}
;
expr_subquery:
EXISTS '{' subquery_stmt '}'
{
cypher_sub_query *sub;
SubLink *n;
sub = make_ag_node(cypher_sub_query);
sub->kind = CSP_EXISTS;
sub->query = $3;
n = makeNode(SubLink);
n->subLinkType = EXISTS_SUBLINK;
n->subLinkId = 0;
n->testexpr = NULL;
n->operName = NIL;
n->subselect = (Node *) sub;
n->location = @1;
$$ = (Node *)node_to_agtype((Node *)n, "boolean", @1);
}
| COUNT '{' subquery_stmt '}'
{
SubLink *n;
cypher_sub_query *sub;
cypher_return *r;
ResTarget *rt;
FuncCall *func;
func = (FuncCall *)make_star_function_expr(
list_make1(makeString("count")), NIL, @1);
rt = makeNode(ResTarget);
rt->name = NULL;
rt->indirection = NIL;
rt->val = (Node *)func;
rt->location = @1;
r = make_ag_node(cypher_return);
r->items = list_make1((Node *)rt);
sub = make_ag_node(cypher_sub_query);
sub->query = lappend($3, r);
n = makeNode(SubLink);
n->subLinkType = EXPR_SUBLINK;
n->subLinkId = 0;
n->testexpr = NULL;
n->operName = NIL;
n->subselect = (Node *)sub;
n->location = @1;
$$ = (Node *) n;
}
;
subquery_pattern:
anonymous_path where_opt
{
cypher_match *n;
n = make_ag_node(cypher_match);
n->pattern = list_make1($1);
n->where = $2;
$$ = (Node *)n;
}
;
property_value:
expr_var '.' property_key_name
{
$$ = append_indirection($1, (Node *)makeString($3));
}
;
expr_atom:
expr_literal
| PARAMETER
{
cypher_param *n;
n = make_ag_node(cypher_param);
n->name = $1;
n->location = @1;
$$ = (Node *)n;
}
| '(' expr ')'
{
Node *n = $2;
if (is_ag_node(n, cypher_comparison_aexpr) ||
is_ag_node(n, cypher_comparison_boolexpr))
{
n = (Node *)node_to_agtype(n, "boolean", @2);
}
$$ = n;
}
| expr_case
| expr_var
| expr_func
| expr_subquery
;
expr_literal:
INTEGER
{
$$ = make_int_const($1, @1);
}
| DECIMAL
{
$$ = make_float_const($1, @1);
}
| STRING
{
$$ = make_string_const($1, @1);
}
| TRUE_P
{
$$ = make_bool_const(true, @1);
}
| FALSE_P
{
$$ = make_bool_const(false, @1);
}
| NULL_P
{
$$ = make_null_const(@1);
}
| map
| map_projection
| list
;
map:
'{' map_keyval_list_opt '}'
{
cypher_map *n;
n = make_ag_node(cypher_map);
n->keyvals = $2;
$$ = (Node *)n;
}
;
map_keyval_list_opt:
/* empty */
{
$$ = NIL;
}
| map_keyval_list
;
map_keyval_list:
property_key_name ':' expr
{
$$ = list_make2(makeString($1), $3);
}
| map_keyval_list ',' property_key_name ':' expr
{
$$ = lappend(lappend($1, makeString($3)), $5);
}
;
map_projection:
expr_var '{' map_projection_elem_list '}'
{
cypher_map_projection *n;
n = make_ag_node(cypher_map_projection);
n->map_var = (ColumnRef *)$1;
n->map_elements = $3;
n->location = @1;
$$ = (Node *)n;
}
;
map_projection_elem_list:
map_projection_elem
{
$$ = list_make1($1);
}
| map_projection_elem_list ',' map_projection_elem
{
$$ = lappend($1, $3);
}
;
map_projection_elem:
'.' property_key_name
{
cypher_map_projection_element *n;
n = make_ag_node(cypher_map_projection_element);
n->type = PROPERTY_SELECTOR;
n->key = $2;
n->value = NULL;
n->location = @1;
$$ = (Node *)n;
}
| expr_var
{
cypher_map_projection_element *n;
n = make_ag_node(cypher_map_projection_element);
n->type = VARIABLE_SELECTOR;
n->key = NULL;
n->value = (Node *)$1;
n->location = @1;
$$ = (Node *)n;
}
| property_key_name ':' expr
{
cypher_map_projection_element *n;
n = make_ag_node(cypher_map_projection_element);
n->type = LITERAL_ENTRY;
n->key = $1;
n->value = (Node *)$3;
n->location = @1;
$$ = (Node *)n;
}
| '.' '*'
{
cypher_map_projection_element *n;
n = make_ag_node(cypher_map_projection_element);
n->type = ALL_PROPERTIES_SELECTOR;
n->key = NULL;
n->value = NULL;
n->location = @1;
$$ = (Node *)n;
}
;
list:
'[' expr_list_opt ']'
{
cypher_list *n;
n = make_ag_node(cypher_list);
n->elems = $2;
$$ = (Node *)n;
}
| list_comprehension
;
/*
* This grammar rule is generic to some extent. It can
* evaluate to either IN operator or list comprehension.
* This avoids shift/reduce errors between the two rules.
*/
list_comprehension:
'[' expr IN expr ']'
{
Node *n = $2;
Node *result = NULL;
/*
* If the first expr is a ColumnRef(variable), then the rule
* should evaluate as a list comprehension. Otherwise, it should
* evaluate as an IN operator.
*/
if (nodeTag(n) == T_ColumnRef)
{
ColumnRef *cref = (ColumnRef *)n;
result = build_list_comprehension_node(cref, $4, NULL, NULL,
@2, @4, 0, 0);
}
else
{
result = (Node *)makeSimpleA_Expr(AEXPR_IN, "=", n, $4, @3);
}
$$ = result;
}
| '[' expr IN expr WHERE expr ']'
{
$$ = verify_rule_as_list_comprehension($2, $4, $6, NULL,
@2, @4, @6, 0);
}
| '[' expr IN expr '|' expr ']'
{
$$ = verify_rule_as_list_comprehension($2, $4, NULL, $6,
@2, @4, 0, @6);
}
| '[' expr IN expr WHERE expr '|' expr ']'
{
$$ = verify_rule_as_list_comprehension($2, $4, $6, $8,
@2, @4, @6, @8);
}
;
expr_case:
CASE expr expr_case_when_list expr_case_default END_P
{
CaseExpr *n;
n = makeNode(CaseExpr);
n->casetype = InvalidOid;
n->arg = (Expr *) $2;
n->args = $3;
n->defresult = (Expr *) $4;
n->location = @1;
$$ = (Node *) n;
}
| CASE expr_case_when_list expr_case_default END_P
{
CaseExpr *n;
n = makeNode(CaseExpr);
n->casetype = InvalidOid;
n->args = $2;
n->defresult = (Expr *) $3;
n->location = @1;
$$ = (Node *) n;
}
;
expr_case_when_list:
expr_case_when
{
$$ = list_make1($1);
}
| expr_case_when_list expr_case_when
{
$$ = lappend($1, $2);
}
;
expr_case_when:
WHEN expr THEN expr
{
CaseWhen *n;
n = makeNode(CaseWhen);
n->expr = (Expr *) $2;
n->result = (Expr *) $4;
n->location = @1;
$$ = (Node *) n;
}
;
expr_case_default:
ELSE expr
{
$$ = $2;
}
| /* EMPTY */
{
$$ = NULL;
}
;
expr_var:
var_name
{
ColumnRef *n;
n = makeNode(ColumnRef);
n->fields = list_make1(makeString($1));
n->location = @1;
$$ = (Node *)n;
}
;
/*
* names
*/
func_name:
symbolic_name
{
$$ = list_make1(makeString($1));
}
/*
* symbolic_name '.' symbolic_name is already covered with the
* rule expr '.' expr above. This rule is to allow most reserved
* keywords to be used as well. So, it essentially makes the
* rule schema_name '.' symbolic_name for func_name
*/
| safe_keywords '.' symbolic_name
{
$$ = list_make2(makeString((char *)$1), makeString($3));
}
;
property_key_name:
schema_name
;
var_name:
symbolic_name
{
if (has_internal_default_prefix($1))
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("%s is only for internal use", AGE_DEFAULT_PREFIX),
ag_scanner_errposition(@1, scanner)));
}
}
;
var_name_opt:
/* empty */
{
$$ = NULL;
}
| var_name
;
label_name:
schema_name
;
symbolic_name:
IDENTIFIER
;
schema_name:
symbolic_name
| reserved_keyword
{
/* we don't need to copy it, as it already has been */
$$ = (char *) $1;
}
;
reserved_keyword:
safe_keywords
| conflicted_keywords
;
/*
* All keywords need to be copied and properly terminated with a null before
* using them, pnstrdup effectively does this for us.
*/
safe_keywords:
ALL { $$ = pnstrdup($1, 3); }
| ANALYZE { $$ = pnstrdup($1, 7); }
| AND { $$ = pnstrdup($1, 3); }
| AS { $$ = pnstrdup($1, 2); }
| ASC { $$ = pnstrdup($1, 3); }
| ASCENDING { $$ = pnstrdup($1, 9); }
| BY { $$ = pnstrdup($1, 2); }
| CALL { $$ = pnstrdup($1, 4); }
| CASE { $$ = pnstrdup($1, 4); }
| COALESCE { $$ = pnstrdup($1, 8); }
| CONTAINS { $$ = pnstrdup($1, 8); }
| COUNT { $$ = pnstrdup($1 ,5); }
| CREATE { $$ = pnstrdup($1, 6); }
| DELETE { $$ = pnstrdup($1, 6); }
| DESC { $$ = pnstrdup($1, 4); }
| DESCENDING { $$ = pnstrdup($1, 10); }
| DETACH { $$ = pnstrdup($1, 6); }
| DISTINCT { $$ = pnstrdup($1, 8); }
| ELSE { $$ = pnstrdup($1, 4); }
| ENDS { $$ = pnstrdup($1, 4); }
| EXISTS { $$ = pnstrdup($1, 6); }
| EXPLAIN { $$ = pnstrdup($1, 7); }
| IN { $$ = pnstrdup($1, 2); }
| IS { $$ = pnstrdup($1, 2); }
| LIMIT { $$ = pnstrdup($1, 6); }
| MATCH { $$ = pnstrdup($1, 6); }
| MERGE { $$ = pnstrdup($1, 6); }
| NOT { $$ = pnstrdup($1, 3); }
| OPTIONAL { $$ = pnstrdup($1, 8); }
| OR { $$ = pnstrdup($1, 2); }
| ORDER { $$ = pnstrdup($1, 5); }
| REMOVE { $$ = pnstrdup($1, 6); }
| RETURN { $$ = pnstrdup($1, 6); }
| SET { $$ = pnstrdup($1, 3); }
| SKIP { $$ = pnstrdup($1, 4); }
| STARTS { $$ = pnstrdup($1, 6); }
| THEN { $$ = pnstrdup($1, 4); }
| UNION { $$ = pnstrdup($1, 5); }
| WHEN { $$ = pnstrdup($1, 4); }
| VERBOSE { $$ = pnstrdup($1, 7); }
| WHERE { $$ = pnstrdup($1, 5); }
| WITH { $$ = pnstrdup($1, 4); }
| XOR { $$ = pnstrdup($1, 3); }
| YIELD { $$ = pnstrdup($1, 5); }
;
conflicted_keywords:
END_P { $$ = pnstrdup($1, 5); }
| FALSE_P { $$ = pnstrdup($1, 7); }
| NULL_P { $$ = pnstrdup($1, 6); }
| TRUE_P { $$ = pnstrdup($1, 6); }
;
%%
/*
* logical operators
*/
static Node *make_or_expr(Node *lexpr, Node *rexpr, int location)
{
// flatten "a OR b OR c ..." to a single BoolExpr on sight
if (IsA(lexpr, BoolExpr))
{
BoolExpr *bexpr = (BoolExpr *)lexpr;
if (bexpr->boolop == OR_EXPR)
{
bexpr->args = lappend(bexpr->args, rexpr);
return (Node *)bexpr;
}
}
return (Node *)makeBoolExpr(OR_EXPR, list_make2(lexpr, rexpr), location);
}
static Node *make_and_expr(Node *lexpr, Node *rexpr, int location)
{
// flatten "a AND b AND c ..." to a single BoolExpr on sight
if (IsA(lexpr, BoolExpr))
{
BoolExpr *bexpr = (BoolExpr *)lexpr;
if (bexpr->boolop == AND_EXPR)
{
bexpr->args = lappend(bexpr->args, rexpr);
return (Node *)bexpr;
}
}
return (Node *)makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
}
static Node *make_xor_expr(Node *lexpr, Node *rexpr, int location)
{
Expr *aorb;
Expr *notaandb;
// XOR is (A OR B) AND (NOT (A AND B))
aorb = makeBoolExpr(OR_EXPR, list_make2(lexpr, rexpr), location);
notaandb = makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
notaandb = makeBoolExpr(NOT_EXPR, list_make1(notaandb), location);
return (Node *)makeBoolExpr(AND_EXPR, list_make2(aorb, notaandb), location);
}
static Node *make_not_expr(Node *expr, int location)
{
return (Node *)makeBoolExpr(NOT_EXPR, list_make1(expr), location);
}
/*
* chained expression comparison operators
*/
static Node *make_cypher_comparison_aexpr(A_Expr_Kind kind, char *name,
Node *lexpr, Node *rexpr, int location)
{
cypher_comparison_aexpr *a = make_ag_node(cypher_comparison_aexpr);
a->kind = kind;
a->name = list_make1(makeString((char *) name));
a->lexpr = lexpr;
a->rexpr = rexpr;
a->location = location;
return (Node *)a;
}
static Node *make_cypher_comparison_boolexpr(BoolExprType boolop, List *args, int location)
{
cypher_comparison_boolexpr *b = make_ag_node(cypher_comparison_boolexpr);
b->boolop = boolop;
b->args = args;
b->location = location;
return (Node *)b;
}
static Node *make_comparison_and_expr(Node *lexpr, Node *rexpr, int location)
{
// flatten "a AND b AND c ..." to a single BoolExpr on sight
if (is_ag_node(lexpr, cypher_comparison_boolexpr))
{
cypher_comparison_boolexpr *bexpr = (cypher_comparison_boolexpr *)lexpr;
if (bexpr->boolop == AND_EXPR)
{
bexpr->args = lappend(bexpr->args, rexpr);
return (Node *)bexpr;
}
}
return (Node *)make_cypher_comparison_boolexpr(AND_EXPR, list_make2(lexpr, rexpr), location);
}
/*
* arithmetic operators
*/
static Node *do_negate(Node *n, int location)
{
if (IsA(n, A_Const))
{
A_Const *c = (A_Const *)n;
// report the constant's location as that of the '-' sign
c->location = location;
if (c->val.ival.type == T_Integer)
{
c->val.ival.ival = -c->val.ival.ival;
return n;
}
else if (c->val.fval.type == T_Float)
{
do_negate_float(&c->val.fval);
return n;
}
}
return (Node *)makeSimpleA_Expr(AEXPR_OP, "-", NULL, n, location);
}
static void do_negate_float(Float *v)
{
char *oldval = NULL;
Assert(v != NULL);
Assert(IsA(v, Float));
oldval = v->fval;
if (*oldval == '+')
{
oldval++;
}
if (*oldval == '-')
{
v->fval = oldval+1; /* just strip the '-' */
}
else
{
v->fval = psprintf("-%s", oldval);
}
}
/*
* indirection
*/
static Node *append_indirection(Node *expr, Node *selector)
{
A_Indirection *indir;
if (IsA(expr, A_Indirection))
{
indir = (A_Indirection *)expr;
indir->indirection = lappend(indir->indirection, selector);
return expr;
}
else
{
indir = makeNode(A_Indirection);
indir->arg = expr;
indir->indirection = list_make1(selector);
return (Node *)indir;
}
}
/*
* literals
*/
static Node *make_int_const(int i, int location)
{
A_Const *n = makeNode(A_Const);
n->val.ival.type = T_Integer;
n->val.ival.ival = i;
n->location = location;
return (Node *) n;
}
static Node *make_float_const(char *s, int location)
{
A_Const *n = makeNode(A_Const);
n->val.fval.type = T_Float;
n->val.fval.fval = s;
n->location = location;
return (Node *) n;
}
static Node *make_string_const(char *s, int location)
{
A_Const *n = makeNode(A_Const);
n->val.sval.type = T_String;
n->val.sval.sval = s;
n->location = location;
return (Node *) n;
}
static Node *make_bool_const(bool b, int location)
{
A_Const *n = makeNode(A_Const);
n->val.boolval.type = T_Boolean;
n->val.boolval.boolval = b;
n->location = location;
return (Node *) n;
}
static Node *make_null_const(int location)
{
A_Const *n = makeNode(A_Const);
n->isnull = true;
n->location = location;
return (Node *) n;
}
/*
* typecast
*/
static Node *make_typecast_expr(Node *expr, char *typecast, int location)
{
cypher_typecast *node;
node = make_ag_node(cypher_typecast);
node->expr = expr;
node->typecast = typecast;
node->location = location;
return (Node *)node;
}
/*
* functions
*/
static Node *make_function_expr(List *func_name, List *exprs, int location)
{
FuncCall *fnode;
/* AGE function names are unqualified. So, their list size = 1 */
if (list_length(func_name) == 1)
{
List *funcname;
char *name;
/* get the name of the function */
name = ((String*)linitial(func_name))->sval;
/*
* Check for openCypher functions that are directly mapped to PG
* functions. We may want to find a better way to do this, as there
* could be many.
*/
if (pg_strcasecmp(name, "count") == 0)
{
funcname = SystemFuncName("count");
/* build the function call */
fnode = makeFuncCall(funcname, exprs, COERCE_SQL_SYNTAX, location);
/* build the cast to wrap the function call to return agtype. */
fnode = node_to_agtype((Node *)fnode, "integer", location);
return (Node *)fnode;
}
else
{
/*
* We don't qualify AGE functions here. This is done in the
* transform layer and allows us to know which functions are ours.
*/
funcname = func_name;
/* build the function call */
fnode = makeFuncCall(funcname, exprs, COERCE_SQL_SYNTAX, location);
}
}
/* all other functions are passed as is */
else
{
fnode = makeFuncCall(func_name, exprs, COERCE_SQL_SYNTAX, location);
}
/* return the node */
return (Node *)fnode;
}
/*
* function to make a function that has received a star-argument
*/
static Node *make_star_function_expr(List *func_name, List *exprs, int location)
{
FuncCall *fnode;
/* AGE function names are unqualified. So, their list size = 1 */
if (list_length(func_name) == 1)
{
List *funcname;
char *name;
/* get the name of the function */
name = ((String*)linitial(func_name))->sval;
/*
* Check for openCypher functions that are directly mapped to PG
* functions. We may want to find a better way to do this, as there
* could be many.
*/
if (pg_strcasecmp(name, "count") == 0)
{
funcname = SystemFuncName("count");
/* build the function call */
fnode = makeFuncCall(funcname, exprs, COERCE_SQL_SYNTAX, location);
fnode->agg_star = true;
/* build the cast to wrap the function call to return agtype. */
fnode = node_to_agtype((Node *)fnode, "integer", location);
return (Node *)fnode;
}
else
{
/*
* We don't qualify AGE functions here. This is done in the
* transform layer and allows us to know which functions are ours.
*/
funcname = func_name;
/* build the function call */
fnode = makeFuncCall(funcname, exprs, COERCE_SQL_SYNTAX, location);
}
}
/* all other functions are passed as is */
else
{
fnode = makeFuncCall(func_name, exprs, COERCE_SQL_SYNTAX, location);
}
/* return the node */
fnode->agg_star = true;
return (Node *)fnode;
}
/*
* function to make a function that has received a distinct keyword
*/
static Node *make_distinct_function_expr(List *func_name, List *exprs, int location)
{
FuncCall *fnode;
/* AGE function names are unqualified. So, their list size = 1 */
if (list_length(func_name) == 1)
{
List *funcname;
char *name;
/* get the name of the function */
name = ((String*)linitial(func_name))->sval;
/*
* Check for openCypher functions that are directly mapped to PG
* functions. We may want to find a better way to do this, as there
* could be many.
*/
if (pg_strcasecmp(name, "count") == 0)
{
funcname = SystemFuncName("count");
/* build the function call */
fnode = makeFuncCall(funcname, exprs, COERCE_SQL_SYNTAX, location);
fnode->agg_order = NIL;
fnode->agg_distinct = true;
/* build the cast to wrap the function call to return agtype. */
fnode = node_to_agtype((Node *)fnode, "integer", location);
return (Node *)fnode;
}
else
{
/*
* We don't qualify AGE functions here. This is done in the
* transform layer and allows us to know which functions are ours.
*/
funcname = func_name;
/* build the function call */
fnode = makeFuncCall(funcname, exprs, COERCE_SQL_SYNTAX, location);
}
}
/* all other functions are passed as is */
else
{
fnode = makeFuncCall(func_name, exprs, COERCE_SQL_SYNTAX, location);
}
/* return the node */
fnode->agg_order = NIL;
fnode->agg_distinct = true;
return (Node *)fnode;
}
/*
* helper function to wrap pg_function in the appropiate typecast function to
* interface with AGE components
*/
static FuncCall *node_to_agtype(Node * fnode, char *type, int location)
{
List *funcname = list_make1(makeString("ag_catalog"));
if (pg_strcasecmp(type, "float") == 0)
{
funcname = lappend(funcname, makeString("float8_to_agtype"));
}
else if (pg_strcasecmp(type, "int") == 0 ||
pg_strcasecmp(type, "integer") == 0)
{
funcname = lappend(funcname, makeString("int8_to_agtype"));
}
else if (pg_strcasecmp(type, "bool") == 0 ||
pg_strcasecmp(type, "boolean") == 0)
{
funcname = lappend(funcname, makeString("bool_to_agtype"));
}
else
{
ereport(ERROR,
(errmsg_internal("type \'%s\' not supported by AGE functions",
type)));
}
return makeFuncCall(funcname, list_make1(fnode), COERCE_EXPLICIT_CAST, location);
}
/* function to create a unique name given a prefix */
static char *create_unique_name(char *prefix_name)
{
char *name = NULL;
char *prefix = NULL;
uint nlen = 0;
unsigned long unique_number = 0;
/* get a unique number */
unique_number = get_a_unique_number();
/* was a valid prefix supplied */
if (prefix_name == NULL || strlen(prefix_name) <= 0)
{
prefix = pnstrdup(UNIQUE_NAME_NULL_PREFIX,
strlen(UNIQUE_NAME_NULL_PREFIX));
}
else
{
prefix = prefix_name;
}
/* get the length of the combined string */
nlen = snprintf(NULL, 0, "%s_%lu", prefix, unique_number);
/* allocate the space */
name = palloc0(nlen + 1);
/* create the name */
snprintf(name, nlen + 1, "%s_%lu", prefix, unique_number);
/* if we created the prefix, we need to free it */
if (prefix_name == NULL || strlen(prefix_name) <= 0)
{
pfree(prefix);
}
return name;
}
/* function to check if given string has internal alias as prefix */
static bool has_internal_default_prefix(char *str)
{
return strncmp(AGE_DEFAULT_PREFIX, str, strlen(AGE_DEFAULT_PREFIX)) == 0;
}
/* function to return a unique unsigned long number */
static unsigned long get_a_unique_number(void)
{
/* STATIC VARIABLE unique_counter for number uniqueness */
static unsigned long unique_counter = 0;
return unique_counter++;
}
/*set operation function node to make a set op node*/
static Node *make_set_op(SetOperation op, bool all_or_distinct, List *larg,
List *rarg)
{
cypher_return *n = make_ag_node(cypher_return);
n->op = op;
n->all_or_distinct = all_or_distinct;
n->larg = (List *) larg;
n->rarg = (List *) rarg;
return (Node *) n;
}
/* check if A_Expr is a comparison expression */
static bool is_A_Expr_a_comparison_operation(cypher_comparison_aexpr *a)
{
String *v = NULL;
char *opr_name = NULL;
/* we don't support qualified comparison operators */
if (list_length(a->name) != 1)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("qualified comparison operator names are not permitted")));
}
/* get the value and verify that it is a string */
v = linitial(a->name);
Assert(v->type == T_String);
/* get the string value */
opr_name = v->sval;
/* verify it is a comparison operation */
if (strcmp(opr_name, "<") == 0)
{
return true;
}
if (strcmp(opr_name, ">") == 0)
{
return true;
}
if (strcmp(opr_name, "<=") == 0)
{
return true;
}
if (strcmp(opr_name, "=>") == 0)
{
return true;
}
if (strcmp(opr_name, "=") == 0)
{
return true;
}
if (strcmp(opr_name, "<>") == 0)
{
return true;
}
return false;
}
/*
* Helper function to build the comparison operator expression. It will also
* build a chained comparison operator expression if it detects a chained
* comparison.
*/
static Node *build_comparison_expression(Node *left_grammar_node,
Node *right_grammar_node,
char *opr_name, int location)
{
Node *result_expr = NULL;
Assert(left_grammar_node != NULL);
Assert(right_grammar_node != NULL);
Assert(opr_name != NULL);
/*
* Case 1:
* If the left expression is an A_Expr and it is also a
* comparison, then this is part of a chained comparison. In this
* specific case, the second chained element.
*/
if (is_ag_node(left_grammar_node, cypher_comparison_aexpr) &&
is_A_Expr_a_comparison_operation((cypher_comparison_aexpr *)left_grammar_node))
{
cypher_comparison_aexpr *aexpr = NULL;
Node *lexpr = NULL;
Node *n = NULL;
/* get the A_Expr on the left side */
aexpr = (cypher_comparison_aexpr *)left_grammar_node;
/* get its rexpr which will be our lexpr */
lexpr = aexpr->rexpr;
/* build our comparison operator */
n = (Node *)make_cypher_comparison_aexpr(AEXPR_OP, opr_name, lexpr,
right_grammar_node, location);
/* now add it (AND) to the other comparison */
result_expr = make_comparison_and_expr(left_grammar_node, n, location);
}
/*
* Case 2:
* If the left expression is a boolean AND and its right most
* expression is an A_Expr and a comparison, then this is part of
* a chained comparison. In this specific case, the third and
* beyond chained element.
*/
else if (is_ag_node(left_grammar_node, cypher_comparison_boolexpr) &&
((cypher_comparison_boolexpr*)left_grammar_node)->boolop == AND_EXPR)
{
cypher_comparison_boolexpr *bexpr = NULL;
Node *last = NULL;
/* cast the left to a boolean */
bexpr = (cypher_comparison_boolexpr *)left_grammar_node;
/* extract the last node - ANDs are chained in a flat list */
last = llast(bexpr->args);
/* is the last node an A_Expr and a comparison operator */
if (is_ag_node(last, cypher_comparison_aexpr) &&
is_A_Expr_a_comparison_operation((cypher_comparison_aexpr *)last))
{
cypher_comparison_aexpr *aexpr = NULL;
Node *lexpr = NULL;
Node *n = NULL;
/* get the last expressions right expression */
aexpr = (cypher_comparison_aexpr *) last;
lexpr = aexpr->rexpr;
/* make our comparison operator */
n = (Node *)make_cypher_comparison_aexpr(AEXPR_OP, opr_name, lexpr,
right_grammar_node, location);
/* now add it (AND) to the other comparisons */
result_expr = make_comparison_and_expr(left_grammar_node, n, location);
}
}
/*
* Case 3:
* The left expression isn't a chained comparison. So, treat
* it as a regular comparison expression. This is usually an initial
* comparison expression.
*/
else if (result_expr == NULL)
{
result_expr = (Node *)make_cypher_comparison_aexpr(AEXPR_OP, opr_name,
left_grammar_node,
right_grammar_node, location);
}
return result_expr;
}
static cypher_relationship *build_VLE_relation(List *left_arg,
cypher_relationship *cr,
Node *right_arg,
int left_arg_location,
int cr_location)
{
ColumnRef *cref = NULL;
A_Indices *ai = NULL;
List *args = NIL;
List *eargs = NIL;
List *fname = NIL;
cypher_node *cnl = NULL;
cypher_node *cnr = NULL;
Node *node = NULL;
int length = 0;
unsigned long unique_number = 0;
int location = 0;
/* get a unique number to identify this VLE node */
unique_number = get_a_unique_number();
/* get the location */
location = cr->location;
/* get the left and right cypher_nodes */
cnl = (cypher_node*)llast(left_arg);
cnr = (cypher_node*)right_arg;
/* get the length of the left path */
length = list_length(left_arg);
/*
* If the left name is NULL and the left path is greater than 1
* If the left name is NULL and the left label is not NULL
* If the left name is NULL and the left props is not NULL
* If the left name is NULL and the right name is not NULL
* If the left name is NULL and the right label is not NULL
* If the left name is NULL and the right props is not NULL
* we need to create a variable name for the left node.
*/
if ((cnl->name == NULL && length > 1) ||
(cnl->name == NULL && cnl->label != NULL) ||
(cnl->name == NULL && cnl->props != NULL) ||
(cnl->name == NULL && cnr->name != NULL) ||
(cnl->name == NULL && cnr->label != NULL) ||
(cnl->name == NULL && cnr->props != NULL))
{
cnl->name = create_unique_name(AGE_DEFAULT_PREFIX"vle_function_start_var");
}
/* add in the start vertex as a ColumnRef if necessary */
if (cnl->name != NULL)
{
cref = makeNode(ColumnRef);
cref->fields = list_make2(makeString(cnl->name), makeString("id"));
cref->location = left_arg_location;
args = lappend(args, cref);
}
/*
* If there aren't any variables in the VLE path, we can use
* the FROM_ALL algorithm.
*/
else
{
args = lappend(args, make_null_const(-1));
}
/*
* Create a variable name for the end vertex if we have a label
* name or props but we don't have a variable name.
*
* For example: ()-[*]-(:label) or ()-[*]-({name: "John"})
*
* We need this so the addition of match_vle_terminal_edge is
* done in the transform phase.
*/
if (cnr->name == NULL &&
(cnr->label != NULL || cnr->props != NULL))
{
cnr->name = create_unique_name(AGE_DEFAULT_PREFIX"vle_function_end_var");
}
/*
* We need a NULL for the target vertex in the VLE match to
* force the dfs_find_a_path_from algorithm. However, for now,
* the default will be to only do that when a target isn't
* supplied.
*
* TODO: We will likely want to force it to use
* dfs_find_a_path_from.
*/
if (cnl->name == NULL && cnr->name != NULL)
{
cref = makeNode(ColumnRef);
cref->fields = list_make2(makeString(cnr->name), makeString("id"));
cref->location = left_arg_location;
args = lappend(args, cref);
}
else
{
args = lappend(args, make_null_const(-1));
}
/* build the required edge arguments */
if (cr->label == NULL)
{
eargs = lappend(eargs, make_null_const(location));
}
else
{
eargs = lappend(eargs, make_string_const(cr->label, location));
}
if (cr->props == NULL)
{
eargs = lappend(eargs, make_null_const(location));
}
else
{
eargs = lappend(eargs, cr->props);
}
/* build the edge function name (schema.funcname) */
fname = list_make2(makeString("ag_catalog"),
makeString("age_build_vle_match_edge"));
/* build the edge function node */
node = make_function_expr(fname, eargs, location);
/* add in the edge*/
args = lappend(args, node);
/* add in the lidx and uidx range as Const */
ai = (A_Indices*)cr->varlen;
if (ai == NULL || ai->lidx == NULL)
{
args = lappend(args, make_null_const(location));
}
else
{
args = lappend(args, ai->lidx);
}
if (ai == NULL || ai->uidx == NULL)
{
args = lappend(args, make_null_const(location));
}
else
{
args = lappend(args, ai->uidx);
}
/* add in the direction as Const */
args = lappend(args, make_int_const(cr->dir, cr_location));
/* add in the unique number used to identify this VLE node */
args = lappend(args, make_int_const(unique_number, -1));
/* build the VLE function node */
cr->varlen = make_function_expr(list_make1(makeString("vle")), args,
cr_location);
/* return the VLE relation node */
return cr;
}
// Helper function to verify that the rule is a list comprehension
static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2,
Node *where, Node *mapping_expr,
int var_loc, int expr_loc,
int where_loc, int mapping_loc)
{
Node *result = NULL;
/*
* If the first expression is a ColumnRef, then we can build a
* list_comprehension node.
* Else its an invalid use of IN operator.
*/
if (nodeTag(expr) == T_ColumnRef)
{
ColumnRef *cref = (ColumnRef *)expr;
result = build_list_comprehension_node(cref, expr2, where,
mapping_expr, var_loc,
expr_loc, where_loc,
mapping_loc);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Syntax error at or near IN")));
}
return result;
}
/* helper function to build a list_comprehension grammar node */
static Node *build_list_comprehension_node(ColumnRef *cref, Node *expr,
Node *where, Node *mapping_expr,
int var_loc, int expr_loc,
int where_loc, int mapping_loc)
{
ResTarget *res = NULL;
cypher_unwind *unwind = NULL;
char *var_name = NULL;
String *val;
// Extract name from cref
val = linitial(cref->fields);
if (!IsA(val, String))
{
ereport(ERROR,
(errmsg_internal("unexpected Node for cypher_clause")));
}
var_name = val->sval;
/*
* Build the ResTarget node for the UNWIND variable var_name attached to
* expr.
*/
res = makeNode(ResTarget);
res->name = var_name;
res->val = (Node *)expr;
res->location = expr_loc;
/* build the UNWIND node */
unwind = make_ag_node(cypher_unwind);
unwind->target = res;
unwind->where = where;
/* if there is a mapping function, add its arg to collect */
if (mapping_expr != NULL)
{
unwind->collect = make_function_expr(list_make1(makeString("collect")),
list_make1(mapping_expr),
mapping_loc);
}
/*
* Otherwise, we need to add in the ColumnRef of the variable var_name as
* the arg to collect instead. This implies that the RETURN variable is
* var_name.
*/
else
{
unwind->collect = make_function_expr(list_make1(makeString("collect")),
list_make1(cref), mapping_loc);
}
/* return the UNWIND node */
return (Node *)unwind;
}