You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by jg...@apache.org on 2022/01/26 01:46:49 UTC
[incubator-age] branch master updated: feat: Implement `OPTIONAL MATCH` (#175)
This is an automated email from the ASF dual-hosted git repository.
jgemignani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git
The following commit(s) were added to refs/heads/master by this push:
new 8a660da feat: Implement `OPTIONAL MATCH` (#175)
8a660da is described below
commit 8a660da40fe6bf2a6facb8e94d49319a9a5f0d6b
Author: Alex Kwak <ta...@kakao.com>
AuthorDate: Wed Jan 26 10:46:44 2022 +0900
feat: Implement `OPTIONAL MATCH` (#175)
* feat: Implement `OPTIONAL MATCH`
* Reflect review.
* Reflect review
* Reflect review
* Reflect review
* Reflect review
* Reflect Review
* Reflect review
* Reflect review
* Fix broken
---
regress/expected/cypher_match.out | 66 +++++++-
regress/sql/cypher_match.sql | 33 ++++
src/backend/nodes/cypher_outfuncs.c | 1 +
src/backend/parser/cypher_analyze.c | 14 +-
src/backend/parser/cypher_clause.c | 268 +++++++++++++++++++++++++++------
src/backend/parser/cypher_gram.y | 24 ++-
src/backend/parser/cypher_keywords.c | 1 +
src/include/nodes/cypher_nodes.h | 1 +
src/include/parser/cypher_parse_node.h | 1 +
9 files changed, 359 insertions(+), 50 deletions(-)
diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out
index f1bb3d7..e334523 100644
--- a/regress/expected/cypher_match.out
+++ b/regress/expected/cypher_match.out
@@ -890,6 +890,68 @@ $$) AS (i agtype);
(3 rows)
--
+-- Optional Match
+--
+SELECT * FROM cypher('cypher_match', $$
+ CREATE (:opt_match_v {name: 'someone'})-[:opt_match_e]->(:opt_match_v {name: 'somebody'}),
+ (:opt_match_v {name: 'anybody'})-[:opt_match_e]->(:opt_match_v {name: 'nobody'})
+$$) AS (u agtype);
+ u
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+ MATCH (u:opt_match_v)
+ OPTIONAL MATCH (u)-[m]-(l)
+ RETURN u.name as u, type(m), l.name as l
+ ORDER BY u, m, l
+$$) AS (u agtype, m agtype, l agtype);
+ u | m | l
+------------+---------------+------------
+ "someone" | "opt_match_e" | "somebody"
+ "somebody" | "opt_match_e" | "someone"
+ "anybody" | "opt_match_e" | "nobody"
+ "nobody" | "opt_match_e" | "anybody"
+(4 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+ OPTIONAL MATCH (n:opt_match_v)-[r]->(p), (m:opt_match_v)-[s]->(q)
+ WHERE id(n) <> id(m)
+ RETURN n.name as n, type(r) AS r, p.name as p,
+ m.name AS m, type(s) AS s, q.name AS q
+ ORDER BY n, p, m, q
+$$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype);
+ n | r | p | m | s | q
+-----------+---------------+------------+-----------+---------------+------------
+ "someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody"
+ "anybody" | "opt_match_e" | "nobody" | "someone" | "opt_match_e" | "somebody"
+(2 rows)
+
+SELECT * FROM cypher('cypher_match', $$
+ MATCH (n:opt_match_v), (m:opt_match_v)
+ WHERE id(n) <> id(m)
+ OPTIONAL MATCH (n)-[r]->(p), (m)-[s]->(q)
+ RETURN n.name AS n, type(r) AS r, p.name AS p,
+ m.name AS m, type(s) AS s, q.name AS q
+ ORDER BY n, p, m, q
+ $$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype);
+ n | r | p | m | s | q
+------------+---------------+------------+------------+---------------+------------
+ "someone" | "opt_match_e" | "somebody" | "anybody" | "opt_match_e" | "nobody"
+ "someone" | | | "somebody" | |
+ "someone" | | | "nobody" | |
+ "somebody" | | | "someone" | |
+ "somebody" | | | "anybody" | |
+ "somebody" | | | "nobody" | |
+ "anybody" | "opt_match_e" | "nobody" | "someone" | "opt_match_e" | "somebody"
+ "anybody" | | | "somebody" | |
+ "anybody" | | | "nobody" | |
+ "nobody" | | | "someone" | |
+ "nobody" | | | "somebody" | |
+ "nobody" | | | "anybody" | |
+(12 rows)
+
+--
-- JIRA: AGE2-544
--
-- Clean up
@@ -981,7 +1043,7 @@ $$) as (f agtype, t agtype);
-- Clean up
--
SELECT drop_graph('cypher_match', true);
-NOTICE: drop cascades to 14 other objects
+NOTICE: drop cascades to 16 other objects
DETAIL: drop cascades to table cypher_match._ag_label_vertex
drop cascades to table cypher_match._ag_label_edge
drop cascades to table cypher_match.v
@@ -996,6 +1058,8 @@ drop cascades to table cypher_match.self
drop cascades to table cypher_match.duplicate
drop cascades to table cypher_match.dup_edge
drop cascades to table cypher_match.other_v
+drop cascades to table cypher_match.opt_match_v
+drop cascades to table cypher_match.opt_match_e
NOTICE: graph "cypher_match" has been dropped
drop_graph
------------
diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql
index 686d6ee..accf0ed 100644
--- a/regress/sql/cypher_match.sql
+++ b/regress/sql/cypher_match.sql
@@ -456,6 +456,39 @@ SELECT * FROM cypher('cypher_match', $$
RETURN u SKIP 7 LIMIT 3
$$) AS (i agtype);
+
+--
+-- Optional Match
+--
+SELECT * FROM cypher('cypher_match', $$
+ CREATE (:opt_match_v {name: 'someone'})-[:opt_match_e]->(:opt_match_v {name: 'somebody'}),
+ (:opt_match_v {name: 'anybody'})-[:opt_match_e]->(:opt_match_v {name: 'nobody'})
+$$) AS (u agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+ MATCH (u:opt_match_v)
+ OPTIONAL MATCH (u)-[m]-(l)
+ RETURN u.name as u, type(m), l.name as l
+ ORDER BY u, m, l
+$$) AS (u agtype, m agtype, l agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+ OPTIONAL MATCH (n:opt_match_v)-[r]->(p), (m:opt_match_v)-[s]->(q)
+ WHERE id(n) <> id(m)
+ RETURN n.name as n, type(r) AS r, p.name as p,
+ m.name AS m, type(s) AS s, q.name AS q
+ ORDER BY n, p, m, q
+$$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype);
+
+SELECT * FROM cypher('cypher_match', $$
+ MATCH (n:opt_match_v), (m:opt_match_v)
+ WHERE id(n) <> id(m)
+ OPTIONAL MATCH (n)-[r]->(p), (m)-[s]->(q)
+ RETURN n.name AS n, type(r) AS r, p.name AS p,
+ m.name AS m, type(s) AS s, q.name AS q
+ ORDER BY n, p, m, q
+ $$) AS (n agtype, r agtype, p agtype, m agtype, s agtype, q agtype);
+
--
-- JIRA: AGE2-544
--
diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c
index 5e78b67..a1b3913 100644
--- a/src/backend/nodes/cypher_outfuncs.c
+++ b/src/backend/nodes/cypher_outfuncs.c
@@ -129,6 +129,7 @@ void out_cypher_match(StringInfo str, const ExtensibleNode *node)
WRITE_NODE_FIELD(pattern);
WRITE_NODE_FIELD(where);
+ WRITE_BOOL_FIELD(optional);
}
// serialization function for the cypher_create ExtensibleNode.
diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c
index b6e158c..4bc9ea7 100644
--- a/src/backend/parser/cypher_analyze.c
+++ b/src/backend/parser/cypher_analyze.c
@@ -578,6 +578,8 @@ static Query *analyze_cypher_and_coerce(List *stmt, RangeTblFunction *rtfunc,
ListCell *lc2;
ListCell *lc3;
+ int attr_count = 0;
+
pstate = make_parsestate(parent_pstate);
query = makeNode(Query);
@@ -597,8 +599,18 @@ static Query *analyze_cypher_and_coerce(List *stmt, RangeTblFunction *rtfunc,
pstate->p_lateral_active = false;
pstate->p_expr_kind = EXPR_KIND_NONE;
+ // ALIAS Syntax makes `RESJUNK`. So, It must be skipping.
+ foreach(lt, subquery->targetList)
+ {
+ TargetEntry *te = lfirst(lt);
+ if (!te->resjunk)
+ {
+ attr_count++;
+ }
+ }
+
// check the number of attributes first
- if (list_length(subquery->targetList) != rtfunc->funccolcount)
+ if (attr_count != rtfunc->funccolcount)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index 2e0622b..3d04f5a 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -296,14 +296,17 @@ Query *cypher_parse_sub_analyze_union(cypher_clause *clause,
static Query *transform_cypher_unwind(cypher_parsestate *cpstate,
cypher_clause *clause);
// transform
-#define PREV_CYPHER_CLAUSE_ALIAS "_"
-#define transform_prev_cypher_clause(cpstate, prev_clause) \
+#define PREV_CYPHER_CLAUSE_ALIAS "_"
+#define CYPHER_OPT_RIGHT_ALIAS "_R"
+#define transform_prev_cypher_clause(cpstate, prev_clause, add_rte_to_query) \
transform_cypher_clause_as_subquery(cpstate, transform_cypher_clause, \
- prev_clause)
+ prev_clause, NULL, add_rte_to_query)
static RangeTblEntry *transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
transform_method transform,
- cypher_clause *clause);
+ cypher_clause *clause,
+ Alias *alias,
+ bool add_rte_to_query);
static Query *analyze_cypher_clause(transform_method transform,
cypher_clause *clause,
cypher_parsestate *parent_cpstate);
@@ -329,6 +332,7 @@ static List *add_target_to_group_list(cypher_parsestate *cpstate,
TargetEntry *tle, List *grouplist,
List *targetlist, int location);
static void advance_transform_entities_to_next_clause(List *entities);
+static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte);
/* for VLE support */
static RangeTblEntry *transform_RangeFunction(cypher_parsestate *cpstate,
@@ -1002,7 +1006,7 @@ static Query *transform_cypher_delete(cypher_parsestate *cpstate,
parser_errposition(pstate, self->location)));
}
- rte = transform_prev_cypher_clause(cpstate, clause->prev);
+ rte = transform_prev_cypher_clause(cpstate, clause->prev, true);
rtindex = list_length(pstate->p_rtable);
// rte is the first RangeTblEntry in pstate
@@ -1081,7 +1085,7 @@ static Query *transform_cypher_unwind(cypher_parsestate *cpstate,
RangeTblEntry *rte;
int rtindex;
- rte = transform_prev_cypher_clause(cpstate, clause->prev);
+ rte = transform_prev_cypher_clause(cpstate, clause->prev, true);
rtindex = list_length(pstate->p_rtable);
Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate
query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
@@ -1229,7 +1233,7 @@ static Query *transform_cypher_set(cypher_parsestate *cpstate,
RangeTblEntry *rte;
int rtindex;
- rte = transform_prev_cypher_clause(cpstate, clause->prev);
+ rte = transform_prev_cypher_clause(cpstate, clause->prev, true);
rtindex = list_length(pstate->p_rtable);
Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate
query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
@@ -1779,7 +1783,7 @@ static Query *transform_cypher_return(cypher_parsestate *cpstate,
if (clause->prev)
{
- transform_prev_cypher_clause(cpstate, clause->prev);
+ transform_prev_cypher_clause(cpstate, clause->prev, true);
}
query->targetList = transform_cypher_item_list(cpstate, self->items,
@@ -1962,7 +1966,7 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate,
query = makeNode(Query);
query->commandType = CMD_SELECT;
- rte = transform_cypher_clause_as_subquery(cpstate, transform, clause);
+ rte = transform_cypher_clause_as_subquery(cpstate, transform, clause, NULL, true);
rtindex = list_length(pstate->p_rtable);
Assert(rtindex == 1); // rte is the only RangeTblEntry in pstate
@@ -2001,6 +2005,138 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate,
cpstate, transform_cypher_match_pattern, clause, self->where);
}
+static Node *transform_clause_for_join(cypher_parsestate *cpstate,
+ cypher_clause *clause,
+ RangeTblEntry **rte,
+ ParseNamespaceItem **nsitem,
+ Alias* alias)
+{
+ ParseState *pstate = (ParseState *)cpstate;
+ RangeTblRef *rtr;
+
+ *rte = transform_cypher_clause_as_subquery(cpstate,
+ transform_cypher_clause,
+ clause, alias, false);
+
+ *nsitem = makeNamespaceItem(*rte, false, true, false, true);
+
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = RTERangeTablePosn(pstate, *rte, NULL);
+
+ return (Node *) rtr;
+}
+
+static void get_res_cols(ParseState *pstate, RangeTblEntry *l_rte,
+ RangeTblEntry *r_rte, List **res_colnames,
+ List **res_colvars)
+{
+ List *l_colnames, *l_colvars;
+ List *r_colnames, *r_colvars;
+ ListCell *r_lname, *r_lvar;
+ List *colnames = NIL;
+ List *colvars = NIL;
+
+ expandRTE(l_rte, RTERangeTablePosn(pstate, l_rte, NULL), 0, -1, false,
+ &l_colnames, &l_colvars);
+ expandRTE(r_rte, RTERangeTablePosn(pstate, r_rte, NULL), 0, -1, false,
+ &r_colnames, &r_colvars);
+
+ *res_colnames = list_concat(*res_colnames, l_colnames);
+ *res_colvars = list_concat(*res_colvars, l_colvars);
+
+ forboth(r_lname, r_colnames, r_lvar, r_colvars)
+ {
+ char *r_colname = strVal(lfirst(r_lname));
+ ListCell *lname;
+ ListCell *lvar;
+ Var *var = NULL;
+
+ forboth(lname, *res_colnames, lvar, *res_colvars)
+ {
+ char *colname = strVal(lfirst(lname));
+
+ if (strcmp(r_colname, colname) == 0)
+ {
+ var = lfirst(lvar);
+ break;
+ }
+ }
+
+ if (var == NULL)
+ {
+ colnames = lappend(colnames, lfirst(r_lname));
+ colvars = lappend(colvars, lfirst(r_lvar));
+ }
+ }
+
+ *res_colnames = list_concat(*res_colnames, colnames);
+ *res_colvars = list_concat(*res_colvars, colvars);
+}
+
+/*
+ * transform_cypher_optional_match_clause
+ * Transform the previous clauses and OPTIONAL MATCH clauses to be LATERAL LEFT JOIN
+ * to construct a result value.
+ */
+static RangeTblEntry *transform_cypher_optional_match_clause(cypher_parsestate *cpstate,
+ cypher_clause *clause)
+{
+ cypher_clause *prevclause;
+ cypher_match *self = (cypher_match *)clause->self;
+ RangeTblEntry *rte;
+ RangeTblEntry *l_rte, *r_rte;
+ ParseNamespaceItem *l_nsitem, *r_nsitem;
+ ParseState *pstate = (ParseState *) cpstate;
+ JoinExpr* j = makeNode(JoinExpr);
+ List *res_colnames = NIL, *res_colvars = NIL;
+ Alias *l_alias, *r_alias;
+ ParseNamespaceItem *nsitem;
+
+ j->jointype = JOIN_LEFT;
+
+ l_alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL);
+ r_alias = makeAlias(CYPHER_OPT_RIGHT_ALIAS, NIL);
+
+ j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte,
+ &l_nsitem, l_alias);
+ pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem);
+
+ prevclause = clause->prev;
+ clause->prev = NULL;
+ self->optional = true;
+ cpstate->p_opt_match = true;
+ pstate->p_lateral_active = true;
+
+ j->rarg = transform_clause_for_join(cpstate, clause, &r_rte,
+ &r_nsitem, r_alias);
+
+ cpstate->p_opt_match = false;
+ pstate->p_lateral_active = false;
+ self->optional = true;
+ clause->prev = prevclause;
+
+ pstate->p_namespace = NIL;
+
+ get_res_cols(pstate, l_rte, r_rte, &res_colnames, &res_colvars);
+
+ rte = addRangeTableEntryForJoin(pstate, res_colnames, j->jointype,
+ res_colvars, j->alias, true);
+
+ j->rtindex = RTERangeTablePosn(pstate, rte, NULL);
+
+ for (int i = list_length(pstate->p_joinexprs) + 1; i < j->rtindex; i++)
+ pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL);
+ pstate->p_joinexprs = lappend(pstate->p_joinexprs, j);
+ Assert(list_length(pstate->p_joinexprs) == j->rtindex);
+
+ pstate->p_joinlist = lappend(pstate->p_joinlist, j);
+
+ nsitem = makeNamespaceItem(rte, false, true, false, true);
+ pstate->p_namespace = lappend(pstate->p_namespace, nsitem);
+
+ return rte;
+}
+
static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
cypher_clause *clause)
{
@@ -2011,24 +2147,37 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
query = makeNode(Query);
query->commandType = CMD_SELECT;
- if (clause->prev)
+ // If there is no previous clause, transform to a general MATCH clause.
+ if (self->optional == true && clause->prev != NULL)
{
- RangeTblEntry *rte;
- int rtindex;
-
- rte = transform_prev_cypher_clause(cpstate, clause->prev);
- rtindex = list_length(pstate->p_rtable);
- Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate
+ RangeTblEntry *rte = transform_cypher_optional_match_clause(cpstate, clause);
- /*
- * add all the target entries in rte to the current target list to pass
- * all the variables that are introduced in the previous clause to the
- * next clause
- */
- query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
+ query->targetList = make_target_list_from_join(pstate, rte);
+ query->rtable = pstate->p_rtable;
+ query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ query->hasSubLinks = pstate->p_hasSubLinks;
}
+ else
+ {
+ if (clause->prev)
+ {
+ RangeTblEntry *rte;
+ int rtindex;
+
+ rte = transform_prev_cypher_clause(cpstate, clause->prev, true);
+ rtindex = list_length(pstate->p_rtable);
+ Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate
+
+ /*
+ * add all the target entries in rte to the current target list to pass
+ * all the variables that are introduced in the previous clause to the
+ * next clause
+ */
+ query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
+ }
- transform_match_pattern(cpstate, query, self->pattern);
+ transform_match_pattern(cpstate, query, self->pattern);
+ }
markTargetListOrigins(pstate, query->targetList);
@@ -2040,6 +2189,33 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate,
/*
* Function to make a target list from an RTE. Borrowed from AgensGraph and PG
*/
+static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte)
+{
+ List *targetlist = NIL;
+ ListCell *lt;
+ ListCell *ln;
+
+ AssertArg(rte->rtekind == RTE_JOIN);
+
+ forboth(lt, rte->joinaliasvars, ln, rte->eref->colnames)
+ {
+ Var *varnode = lfirst(lt);
+ char *resname = strVal(lfirst(ln));
+ TargetEntry *tmp;
+
+ tmp = makeTargetEntry((Expr *) varnode,
+ (AttrNumber) pstate->p_next_resno++,
+ pstrdup(resname),
+ false);
+ targetlist = lappend(targetlist, tmp);
+ }
+
+ return targetlist;
+}
+
+/*
+ * Function to make a target list from an RTE. Borrowed from AgensGraph and PG
+ */
static List *makeTargetListFromRTE(ParseState *pstate, RangeTblEntry *rte)
{
List *targetlist = NIL;
@@ -2121,7 +2297,8 @@ static Query *transform_cypher_sub_pattern(cypher_parsestate *cpstate,
qry->commandType = CMD_SELECT;
rte = transform_cypher_clause_as_subquery(child_parse_state,
- transform_cypher_clause, c);
+ transform_cypher_clause, c,
+ NULL, true);
qry->targetList = makeTargetListFromRTE(p_child_parse_state, rte);
@@ -3044,9 +3221,9 @@ static Node *create_property_constraint_function(cypher_parsestate *cpstate,
}
else
{
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot create a property constraint on non vertex or edge agtype")));
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create a property constraint on non vertex or edge agtype")));
}
cr->fields = list_make2(makeString(entity_name), makeString("properties"));
@@ -3748,6 +3925,11 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate,
if (te != NULL)
{
transform_entity *entity = find_variable(cpstate, node->name);
+
+ if (cpstate->p_opt_match)
+ {
+ return te->expr;
+ }
/*
* TODO: openCypher allows a variable to be used before it
* is properly declared. This logic is not satifactory
@@ -3932,7 +4114,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate,
RangeTblEntry *rte;
int rtindex;
- rte = transform_prev_cypher_clause(cpstate, clause->prev);
+ rte = transform_prev_cypher_clause(cpstate, clause->prev, true);
rtindex = list_length(pstate->p_rtable);
Assert(rtindex == 1); // rte is the first RangeTblEntry in pstate
query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
@@ -4522,7 +4704,7 @@ static Expr *cypher_create_properties(cypher_parsestate *cpstate,
}
else
{
- ereport(ERROR, (errmsg_internal("unreconized entity type")));
+ ereport(ERROR, (errmsg_internal("unreconized entity type")));
}
// add a volatile wrapper call to prevent the optimizer from removing it
@@ -4556,14 +4738,15 @@ static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
static RangeTblEntry *
transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
transform_method transform,
- cypher_clause *clause)
+ cypher_clause *clause,
+ Alias *alias,
+ bool add_rte_to_query)
{
ParseState *pstate = (ParseState *)cpstate;
- bool lateral = false;
Query *query;
RangeTblEntry *rte;
- Alias *alias;
ParseExprKind old_expr_kind = pstate->p_expr_kind;
+ bool lateral = pstate->p_lateral_active;
/*
* We allow expression kinds of none, where, and subselect. Others MAY need
@@ -4585,19 +4768,16 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
* If this is a WHERE, pass it through and set lateral to true because it
* needs to see what comes before it.
*/
- if (pstate->p_expr_kind == EXPR_KIND_WHERE)
- {
- lateral = true;
- }
-
- pstate->p_lateral_active = lateral;
query = analyze_cypher_clause(transform, clause, cpstate);
/* set pstate kind back */
pstate->p_expr_kind = old_expr_kind;
- alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL);
+ if (alias == NULL)
+ {
+ alias = makeAlias(PREV_CYPHER_CLAUSE_ALIAS, NIL);
+ }
rte = addRangeTableEntryForSubquery(pstate, query, alias, lateral, true);
@@ -4626,11 +4806,11 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
}
- // all variables(attributes) from the previous clause(subquery) are visible
- addRTEtoQuery(pstate, rte, true, false, true);
-
- /* set pstate lateral back */
- pstate->p_lateral_active = false;
+ if(add_rte_to_query)
+ {
+ // all variables(attributes) from the previous clause(subquery) are visible
+ addRTEtoQuery(pstate, rte, true, false, true);
+ }
return rte;
}
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 7b00a30..34b5b2f 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -87,7 +87,7 @@
LIMIT
MATCH
NOT NULL_P
- OR ORDER
+ OPTIONAL OR ORDER
REMOVE RETURN
SET SKIP STARTS
THEN TRUE_P
@@ -111,6 +111,8 @@
%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
@@ -713,18 +715,31 @@ with:
*/
match:
- MATCH pattern where_opt
+ optional_opt MATCH pattern where_opt
{
cypher_match *n;
n = make_ag_node(cypher_match);
- n->pattern = $2;
- n->where = $3;
+ n->optional = $1;
+ n->pattern = $3;
+ n->where = $4;
$$ = (Node *)n;
}
;
+optional_opt:
+ OPTIONAL
+ {
+ $$ = true;
+ }
+ | /* EMPTY */
+ {
+ $$ = false;
+ }
+ ;
+
+
unwind:
UNWIND expr AS var_name
{
@@ -1823,6 +1838,7 @@ safe_keywords:
| LIMIT { $$ = pnstrdup($1, 6); }
| MATCH { $$ = pnstrdup($1, 6); }
| NOT { $$ = pnstrdup($1, 3); }
+ | OPTIONAL { $$ = pnstrdup($1, 8); }
| OR { $$ = pnstrdup($1, 2); }
| ORDER { $$ = pnstrdup($1, 5); }
| REMOVE { $$ = pnstrdup($1, 6); }
diff --git a/src/backend/parser/cypher_keywords.c b/src/backend/parser/cypher_keywords.c
index 51e5f72..d0ad0bf 100644
--- a/src/backend/parser/cypher_keywords.c
+++ b/src/backend/parser/cypher_keywords.c
@@ -67,6 +67,7 @@ const ScanKeyword cypher_keywords[] = {
{"match", MATCH, RESERVED_KEYWORD},
{"not", NOT, RESERVED_KEYWORD},
{"null", NULL_P, RESERVED_KEYWORD},
+ {"optional", OPTIONAL, RESERVED_KEYWORD},
{"or", OR, RESERVED_KEYWORD},
{"order", ORDER, RESERVED_KEYWORD},
{"remove", REMOVE, RESERVED_KEYWORD},
diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h
index 0b0852e..03a28b4 100644
--- a/src/include/nodes/cypher_nodes.h
+++ b/src/include/nodes/cypher_nodes.h
@@ -73,6 +73,7 @@ typedef struct cypher_match
ExtensibleNode extensible;
List *pattern; // a list of cypher_paths
Node *where; // optional WHERE subclause (expression)
+ bool optional; // OPTIONAL MATCH
} cypher_match;
typedef struct cypher_create
diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h
index dc7581b..f9da121 100644
--- a/src/include/parser/cypher_parse_node.h
+++ b/src/include/parser/cypher_parse_node.h
@@ -43,6 +43,7 @@ typedef struct cypher_parsestate
* else). It is only used by transform_cypher_item_list.
*/
bool exprHasAgg;
+ bool p_opt_match;
} cypher_parsestate;
typedef struct errpos_ecb_state