%{
|
/*
|
* 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 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
|
|
/* 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
|
|
/* 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> 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
|
|
/* 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(Value *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);
|
%}
|
%%
|
|
/*
|
* 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;
|
Value *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;
|
Value *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
|
;
|
|
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.val.ival > uidx->val.val.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;
|
$$ = (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->props = $4;
|
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->props = $5;
|
|
$$ = (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->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;
|
Value *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;
|
Value *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;
|
Value *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);
|
|
}
|
;
|
|
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_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
|
| 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);
|
}
|
;
|
|
list:
|
'[' expr_list_opt ']'
|
{
|
cypher_list *n;
|
|
n = make_ag_node(cypher_list);
|
n->elems = $2;
|
|
$$ = (Node *)n;
|
}
|
;
|
|
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); }
|
| 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.type == T_Integer)
|
{
|
c->val.val.ival = -c->val.val.ival;
|
return n;
|
}
|
else if (c->val.type == T_Float)
|
{
|
do_negate_float(&c->val);
|
return n;
|
}
|
}
|
|
return (Node *)makeSimpleA_Expr(AEXPR_OP, "-", NULL, n, location);
|
}
|
|
static void do_negate_float(Value *v)
|
{
|
Assert(IsA(v, Float));
|
|
if (v->val.str[0] == '-')
|
v->val.str = v->val.str + 1; // just strip the '-'
|
else
|
v->val.str = psprintf("-%s", v->val.str);
|
}
|
|
/*
|
* 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;
|
|
n = makeNode(A_Const);
|
n->val.type = T_Integer;
|
n->val.val.ival = i;
|
n->location = location;
|
|
return (Node *)n;
|
}
|
|
static Node *make_float_const(char *s, int location)
|
{
|
A_Const *n;
|
|
n = makeNode(A_Const);
|
n->val.type = T_Float;
|
n->val.val.str = s;
|
n->location = location;
|
|
return (Node *)n;
|
}
|
|
static Node *make_string_const(char *s, int location)
|
{
|
A_Const *n;
|
|
n = makeNode(A_Const);
|
n->val.type = T_String;
|
n->val.val.str = s;
|
n->location = location;
|
|
return (Node *)n;
|
}
|
|
static Node *make_bool_const(bool b, int location)
|
{
|
cypher_bool_const *n;
|
|
n = make_ag_node(cypher_bool_const);
|
n->boolean = b;
|
n->location = location;
|
|
return (Node *)n;
|
}
|
|
static Node *make_null_const(int location)
|
{
|
A_Const *n;
|
|
n = makeNode(A_Const);
|
n->val.type = T_Null;
|
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 = ((Value*)linitial(func_name))->val.str;
|
|
/*
|
* 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 = ((Value*)linitial(func_name))->val.str;
|
|
/*
|
* 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 = ((Value*)linitial(func_name))->val.str;
|
|
/*
|
* 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)
|
{
|
Value *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->val.str;
|
|
/* 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;
|
}
|