Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions regress/expected/cypher_subquery.out
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,73 @@ MATCH (a) WHERE EXISTS {MATCH (a)-[]-()} RETURN a}]] RETURN a $$) AS (result agt
{"id": 1688849860263940, "label": "pet", "properties": {"name": "Garfield"}}::vertex
(4 rows)

--
-- issue 2396: returnless UNION with VLE in EXISTS
--
SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:knows*1..2]->(:pet)
UNION
MATCH (a)-[:loved]->(:person)
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);
name
-----------
"Calvin"
"Charlie"
"Jon"
"Tony"
(4 rows)

SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:loved]->(:person)
UNION
MATCH (a)-[:knows*1..2]->(:pet)
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);
name
-----------
"Calvin"
"Charlie"
"Jon"
"Tony"
(4 rows)

SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:knows*1..2]->(:pet)
UNION ALL
MATCH (a)-[:loved]->(:person)
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);
name
-----------
"Calvin"
"Charlie"
"Jon"
"Tony"
(4 rows)

SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:loved]->(:person)
UNION
MATCH (a)-[:knows*1..2]->(:pet)
UNION
MATCH (a)-[:knows]->(:person)
WHERE a.name = 'Faye'
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);
name
-----------
"Calvin"
"Charlie"
"Faye"
"Jon"
"Tony"
(5 rows)

--
-- Cleanup
--
Expand Down
38 changes: 38 additions & 0 deletions regress/sql/cypher_subquery.sql
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,44 @@ MATCH (a) WHERE a.name = 'Hobbes' RETURN a}]] RETURN a $$) AS (result agtype);
SELECT * FROM cypher('subquery', $$ MATCH (a:pet) WHERE [true] IN [[EXISTS {
MATCH (a) WHERE EXISTS {MATCH (a)-[]-()} RETURN a}]] RETURN a $$) AS (result agtype);

--
-- issue 2396: returnless UNION with VLE in EXISTS
--
SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:knows*1..2]->(:pet)
UNION
MATCH (a)-[:loved]->(:person)
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);

SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:loved]->(:person)
UNION
MATCH (a)-[:knows*1..2]->(:pet)
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);

SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:knows*1..2]->(:pet)
UNION ALL
MATCH (a)-[:loved]->(:person)
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);

SELECT * FROM cypher('subquery', $$ MATCH (a:person)
WHERE EXISTS {
MATCH (a)-[:loved]->(:person)
UNION
MATCH (a)-[:knows*1..2]->(:pet)
UNION
MATCH (a)-[:knows]->(:person)
WHERE a.name = 'Faye'
}
RETURN a.name ORDER BY a.name $$) AS (name agtype);

--
-- Cleanup
--
Expand Down
40 changes: 37 additions & 3 deletions src/backend/parser/cypher_clause.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
#define AGE_VARNAME_ID AGE_DEFAULT_VARNAME_PREFIX"id"
#define AGE_VARNAME_SET_CLAUSE AGE_DEFAULT_VARNAME_PREFIX"set_clause"
#define AGE_VARNAME_SET_VALUE AGE_DEFAULT_VARNAME_PREFIX"set_value"
#define AGE_VARNAME_RETURNLESS_UNION AGE_DEFAULT_VARNAME_PREFIX"returnless_union"

/*
* In the transformation stage, we need to track
Expand Down Expand Up @@ -295,9 +296,12 @@ static cypher_clause *make_cypher_clause(List *stmt);
static Query *transform_cypher_union(cypher_parsestate *cpstate,
cypher_clause *clause);

static void project_returnless_union_leaf(Query *query);

static Node * transform_cypher_union_tree(cypher_parsestate *cpstate,
cypher_clause *clause,
bool isTopLevel,
bool returnless_union,
List **targetlist);

Query *cypher_parse_sub_analyze_union(cypher_clause *clause,
Expand Down Expand Up @@ -649,6 +653,18 @@ static cypher_clause *make_cypher_clause(List *stmt)
return clause;
}

static void project_returnless_union_leaf(Query *query)
{
TargetEntry *tle;

tle = makeTargetEntry((Expr *) makeBoolConst(true, false),
1,
AGE_VARNAME_RETURNLESS_UNION,
false);

query->targetList = list_make1(tle);
}

/*
* transform_cypher_union -
* transforms a union tree, derived from postgresql's
Expand Down Expand Up @@ -710,7 +726,9 @@ static Query *transform_cypher_union(cypher_parsestate *cpstate,
* Recursively transform the components of the tree.
*/
cypher_union_statement = (SetOperationStmt *) transform_cypher_union_tree(cpstate,
clause, true, NULL);
clause, true,
self->returnless_union,
NULL);

Assert(cypher_union_statement);
qry->setOperations = (Node *) cypher_union_statement;
Expand Down Expand Up @@ -871,7 +889,8 @@ static Query *transform_cypher_union(cypher_parsestate *cpstate,
*/
static Node *
transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause,
bool isTopLevel, List **targetlist)
bool isTopLevel, bool returnless_union,
List **targetlist)
{
bool isLeaf;

Expand Down Expand Up @@ -973,6 +992,17 @@ transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause,
}
}

/*
* Returnless UNION branches use a parser-injected RETURN * only as a
* syntactic carrier. The branch output is unobservable, so expose one
* stable column to PostgreSQL's set-op planner instead of leaking the
* branch's current variable list into set-op metadata.
*/
if (returnless_union)
{
project_returnless_union_leaf(returnQuery);
}
Comment on lines +995 to +1004

/*
* Extract a list of the non-junk TLEs for upper-level processing.
*/
Expand Down Expand Up @@ -1019,8 +1049,10 @@ transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause,
ListCell *rtl;
cypher_return *self = (cypher_return *) clause->self;
const char *context;
bool child_returnless_union;

context = "UNION";
child_returnless_union = returnless_union || self->returnless_union;

op->op = self->op;
op->all = self->all_or_distinct;
Expand All @@ -1031,6 +1063,7 @@ transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause,
op->larg = transform_cypher_union_tree(cpstate,
(cypher_clause *) self->larg,
false,
child_returnless_union,
&ltargetlist);

/*
Expand All @@ -1053,6 +1086,7 @@ transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause,
op->rarg = transform_cypher_union_tree(cpstate,
(cypher_clause *) self->rarg,
false,
child_returnless_union,
&rtargetlist);

/*
Expand All @@ -1062,7 +1096,7 @@ transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause,
* matching, because they are not relevant to the end result.
*/
if (list_length(ltargetlist) != list_length(rtargetlist) &&
self->returnless_union == false)
returnless_union == false)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
Expand Down
Loading