From 8ee187c5633d3f91114a5d4816d3e1c82405659b Mon Sep 17 00:00:00 2001 From: Dehowe Feng <8065116+dehowef@users.noreply.github.com> Date: Mon, 6 Nov 2023 16:33:57 -0800 Subject: [PATCH] Implement EXISTS subquery for CASE (#1345) 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 --- regress/expected/expr.out | 90 +++++++++++++++++++++++++++----- regress/sql/expr.sql | 50 ++++++++++++++++-- src/backend/parser/cypher_gram.y | 18 ++++--- 3 files changed, 134 insertions(+), 24 deletions(-) diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 7cf30316a..12b18d586 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -6399,8 +6399,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" @@ -6417,8 +6417,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" @@ -6435,8 +6435,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" @@ -6453,8 +6453,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" @@ -6471,8 +6471,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" @@ -6489,8 +6489,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" @@ -6500,6 +6500,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 diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 9979dc5e9..aa1f38911 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -2684,7 +2684,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', $$ @@ -2693,7 +2693,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', $$ @@ -2702,7 +2702,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', $$ @@ -2711,7 +2711,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', $$ @@ -2720,7 +2720,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', $$ @@ -2729,8 +2729,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'); diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index 1c1fef224..9f9457cc1 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -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, @@ -1730,12 +1730,16 @@ 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")), + FuncCall *n; + n = makeFuncCall(list_make1(makeString("exists")), list_make1($3), @2); + + $$ = (Node *)node_to_agtype((Node *)n, "boolean", @2); + } ; @@ -2268,7 +2272,7 @@ static Node *make_function_expr(List *func_name, List *exprs, int location) fnode = makeFuncCall(funcname, exprs, 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; } @@ -2324,7 +2328,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; } @@ -2382,7 +2386,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 @@ -2413,7 +2417,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"));