Skip to content

Commit

Permalink
Implement EXISTS subquery for CASE
Browse files Browse the repository at this point in the history
Modified logic of EXISTS to wrap itself in a agtype cast. This
allows EXISTS to work with in CASE statements, among other
implementations that may require an agtype.

Also renamed the wrapper function to be more generic to denote
its usage in other implementations if need be.

Added regression tests for EXISTS
  • Loading branch information
dehowef committed Nov 2, 2023
1 parent b25f6f7 commit bed9159
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 25 deletions.
90 changes: 78 additions & 12 deletions regress/expected/expr.out
Original file line number Diff line number Diff line change
Expand Up @@ -6514,8 +6514,8 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(*)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+----------------
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count"
Expand All @@ -6532,8 +6532,8 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(*)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+----------------
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count"
Expand All @@ -6550,8 +6550,8 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(n)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+----------------
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count"
Expand All @@ -6568,8 +6568,8 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(n)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+----------------
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count"
Expand All @@ -6586,8 +6586,8 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(1)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+----------------
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count"
Expand All @@ -6604,8 +6604,8 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(1)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+----------------
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count"
Expand All @@ -6615,6 +6615,72 @@ $$ ) AS (j agtype, case_statement agtype);
{"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "not count"
(6 rows)

--CASE with EXISTS()
--exists(n.property)
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE exists(n.j)
WHEN true THEN 'property j exists'
ELSE 'property j does not exist'
END
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+-----------------------------
{"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "property j does not exist"
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "property j exists"
{"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | "property j exists"
{"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "property j exists"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "property j exists"
{"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "property j exists"
(6 rows)

--CASE evaluates to boolean true, is not a boolean, should hit ELSE
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE exists(n.j)
WHEN 1 THEN 'should not output me'
ELSE '1 is not a boolean'
END
$$ ) AS (n agtype, case_statement agtype);
n | case_statement
------------------------------------------------------------------------------------------------+----------------------
{"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "1 is not a boolean"
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "1 is not a boolean"
{"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | "1 is not a boolean"
{"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "1 is not a boolean"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "1 is not a boolean"
{"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "1 is not a boolean"
(6 rows)

--exists in WHEN, vacuously false because exists(n.j) evaluates to a boolean, n is a vertex
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE n
WHEN exists(n.j) THEN 'should not output me'
ELSE 'n is a vertex, not a boolean'
END
$$ ) AS (j agtype, case_statement agtype);
j | case_statement
------------------------------------------------------------------------------------------------+--------------------------------
{"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "n is a vertex, not a boolean"
{"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "n is a vertex, not a boolean"
{"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | "n is a vertex, not a boolean"
{"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "n is a vertex, not a boolean"
{"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "n is a vertex, not a boolean"
{"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "n is a vertex, not a boolean"
(6 rows)

--exists(*) (should fail)
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE n.j
WHEN 1 THEN exists(*)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
ERROR: syntax error at or near "*"
LINE 4: WHEN 1 THEN exists(*)
^
-- RETURN * and (u)--(v) optional forms
SELECT create_graph('opt_forms');
NOTICE: graph "opt_forms" has been created
Expand Down
50 changes: 45 additions & 5 deletions regress/sql/expr.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2726,7 +2726,7 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(*)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
$$ ) AS (n agtype, case_statement agtype);

--concatenated
SELECT * FROM cypher('case_statement', $$
Expand All @@ -2735,7 +2735,7 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(*)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
$$ ) AS (n agtype, case_statement agtype);

--count(n)
SELECT * FROM cypher('case_statement', $$
Expand All @@ -2744,7 +2744,7 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(n)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
$$ ) AS (n agtype, case_statement agtype);

--concatenated
SELECT * FROM cypher('case_statement', $$
Expand All @@ -2753,7 +2753,7 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(n)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
$$ ) AS (n agtype, case_statement agtype);

--count(1)
SELECT * FROM cypher('case_statement', $$
Expand All @@ -2762,7 +2762,7 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(1)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);
$$ ) AS (n agtype, case_statement agtype);

--concatenated
SELECT * FROM cypher('case_statement', $$
Expand All @@ -2771,8 +2771,48 @@ SELECT * FROM cypher('case_statement', $$
WHEN 1 THEN count(1)
ELSE 'not count'
END
$$ ) AS (n agtype, case_statement agtype);

--CASE with EXISTS()

--exists(n.property)
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE exists(n.j)
WHEN true THEN 'property j exists'
ELSE 'property j does not exist'
END
$$ ) AS (n agtype, case_statement agtype);

--CASE evaluates to boolean true, is not a boolean, should hit ELSE
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE exists(n.j)
WHEN 1 THEN 'should not output me'
ELSE '1 is not a boolean'
END
$$ ) AS (n agtype, case_statement agtype);

--exists in WHEN, vacuously false because exists(n.j) evaluates to a boolean, n is a vertex
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE n
WHEN exists(n.j) THEN 'should not output me'
ELSE 'n is a vertex, not a boolean'
END
$$ ) AS (j agtype, case_statement agtype);

--exists(*) (should fail)
SELECT * FROM cypher('case_statement', $$
MATCH (n)
RETURN n, CASE n.j
WHEN 1 THEN exists(*)
ELSE 'not count'
END
$$ ) AS (j agtype, case_statement agtype);




-- RETURN * and (u)--(v) optional forms
SELECT create_graph('opt_forms');
Expand Down
21 changes: 13 additions & 8 deletions src/backend/parser/cypher_gram.y
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ static Node *make_typecast_expr(Node *expr, char *typecast, int location);
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 *wrap_pg_funccall_to_agtype(Node* fnode, char *type, 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,
Expand Down Expand Up @@ -1742,12 +1742,17 @@ expr_func_subexpr:
n->operName = NIL;
n->subselect = (Node *) sub;
n->location = @1;
$$ = (Node *) n;
$$ = (Node *)node_to_agtype((Node *)n, "boolean", @1);
}
| EXISTS '(' property_value ')'
{
$$ = make_function_expr(list_make1(makeString("exists")),
list_make1($3), @2);
FuncCall *n;
n = makeFuncCall(list_make1(makeString("exists")),
list_make1($3), COERCE_SQL_SYNTAX, @2);

$$ = (Node *)node_to_agtype((Node *)n, "boolean", @2);


}
;

Expand Down Expand Up @@ -2289,7 +2294,7 @@ static Node *make_function_expr(List *func_name, List *exprs, int location)
fnode = makeFuncCall(funcname, exprs, COERCE_SQL_SYNTAX, location);

/* build the cast to wrap the function call to return agtype. */
fnode = wrap_pg_funccall_to_agtype((Node *)fnode, "integer", location);
fnode = node_to_agtype((Node *)fnode, "integer", location);

return (Node *)fnode;
}
Expand Down Expand Up @@ -2345,7 +2350,7 @@ static Node *make_star_function_expr(List *func_name, List *exprs, int location)
fnode->agg_star = true;

/* build the cast to wrap the function call to return agtype. */
fnode = wrap_pg_funccall_to_agtype((Node *)fnode, "integer", location);
fnode = node_to_agtype((Node *)fnode, "integer", location);

return (Node *)fnode;
}
Expand Down Expand Up @@ -2403,7 +2408,7 @@ static Node *make_distinct_function_expr(List *func_name, List *exprs, int locat
fnode->agg_distinct = true;

/* build the cast to wrap the function call to return agtype. */
fnode = wrap_pg_funccall_to_agtype((Node *)fnode, "integer", location);
fnode = node_to_agtype((Node *)fnode, "integer", location);
return (Node *)fnode;
}
else
Expand Down Expand Up @@ -2434,7 +2439,7 @@ static Node *make_distinct_function_expr(List *func_name, List *exprs, int locat
* helper function to wrap pg_function in the appropiate typecast function to
* interface with AGE components
*/
static FuncCall *wrap_pg_funccall_to_agtype(Node * fnode, char *type, int location)
static FuncCall *node_to_agtype(Node * fnode, char *type, int location)
{
List *funcname = list_make1(makeString("ag_catalog"));

Expand Down

0 comments on commit bed9159

Please sign in to comment.