Skip to content

Commit 689b456

Browse files
committed
Implement chained expression order of operations (apache#1402)
Implements functionality for order of operations (parentheses) in chained operations. Added new nodes and functions to accomodate this feature. Added relevant regression tests for order of operations in chained expressions. Modified a previous regression test case that had its location reporting changed by this implementation.
1 parent 0c264d7 commit 689b456

File tree

10 files changed

+344
-27
lines changed

10 files changed

+344
-27
lines changed

regress/expected/cypher_call.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ HINT: No function matches the given name and argument types. You might need to
8484
/* CALL YIELD WHERE, should fail */
8585
SELECT * FROM cypher('cypher_call', $$CALL sqrt(64) YIELD sqrt WHERE sqrt > 1$$) as (sqrt agtype);
8686
ERROR: Cannot use standalone CALL with WHERE
87-
LINE 2: ...r('cypher_call', $$CALL sqrt(64) YIELD sqrt WHERE sqrt > 1$$...
88-
^
87+
LINE 2: SELECT * FROM cypher('cypher_call', $$CALL sqrt(64) YIELD sq...
88+
^
8989
HINT: Instead use `CALL ... WITH * WHERE ... RETURN *`
9090
/*
9191
* subquery

regress/expected/expr.out

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,135 @@ SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE NOT 35 < u.age + 1 <=
741741
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
742742
(3 rows)
743743

744+
-- order of operations
745+
-- expressions
746+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = 1 $$) AS (result agtype);
747+
result
748+
--------
749+
true
750+
(1 row)
751+
752+
SELECT * FROM cypher('chained', $$ RETURN 1 = 2 = 1 $$) AS (result agtype);
753+
result
754+
--------
755+
false
756+
(1 row)
757+
758+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = 1 $$) AS (result agtype);
759+
result
760+
--------
761+
false
762+
(1 row)
763+
764+
SELECT * FROM cypher('chained', $$ RETURN 1 = (1 = 1) $$) AS (result agtype);
765+
result
766+
--------
767+
false
768+
(1 row)
769+
770+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = true $$) AS (result agtype);
771+
result
772+
--------
773+
false
774+
(1 row)
775+
776+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = true $$) AS (result agtype);
777+
result
778+
--------
779+
true
780+
(1 row)
781+
782+
SELECT * FROM cypher('chained', $$ RETURN true = ((1 = 1) = true) $$) AS (result agtype);
783+
result
784+
--------
785+
true
786+
(1 row)
787+
788+
SELECT * FROM cypher('chained', $$ RETURN ((1 = 1) = 1) = 1 $$) AS (result agtype);
789+
result
790+
--------
791+
false
792+
(1 row)
793+
794+
SELECT * FROM cypher('chained', $$ RETURN (1 = (1 = 1)) = 1 $$) AS (result agtype);
795+
result
796+
--------
797+
false
798+
(1 row)
799+
800+
SELECT * FROM cypher('chained', $$ RETURN ((1 = (1 = 1)) = 1) = 1 $$) AS (result agtype);
801+
result
802+
--------
803+
false
804+
(1 row)
805+
806+
-- in clause
807+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE 35 = u.age = 35 RETURN u $$) AS (result agtype);
808+
result
809+
---------------------------------------------------------------------------------------------------
810+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
811+
(1 row)
812+
813+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = u.age) = 35 RETURN u $$) AS (result agtype);
814+
result
815+
--------
816+
(0 rows)
817+
818+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = 35) = u.age RETURN u $$) AS (result agtype);
819+
result
820+
--------
821+
(0 rows)
822+
823+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = u.age = u.age RETURN u $$) AS (result agtype);
824+
result
825+
---------------------------------------------------------------------------------------------------
826+
{"id": 844424930131969, "label": "people", "properties": {"age": 50, "name": "Jason"}}::vertex
827+
{"id": 844424930131970, "label": "people", "properties": {"age": 25, "name": "Amy"}}::vertex
828+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
829+
{"id": 844424930131972, "label": "people", "properties": {"age": 40, "name": "Mark"}}::vertex
830+
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
831+
(5 rows)
832+
833+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = u.age RETURN u $$) AS (result agtype);
834+
result
835+
--------
836+
(0 rows)
837+
838+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = u.age) RETURN u $$) AS (result agtype);
839+
result
840+
--------
841+
(0 rows)
842+
843+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = (u.age = u.age) RETURN u $$) AS (result agtype);
844+
result
845+
---------------------------------------------------------------------------------------------------
846+
{"id": 844424930131969, "label": "people", "properties": {"age": 50, "name": "Jason"}}::vertex
847+
{"id": 844424930131970, "label": "people", "properties": {"age": 25, "name": "Amy"}}::vertex
848+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
849+
{"id": 844424930131972, "label": "people", "properties": {"age": 40, "name": "Mark"}}::vertex
850+
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
851+
(5 rows)
852+
853+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = (u.age = u.age)) RETURN u $$) AS (result agtype);
854+
result
855+
--------
856+
(0 rows)
857+
858+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = 35 = ((u.age = u.age) = u.age) RETURN u $$) AS (result agtype);
859+
result
860+
--------
861+
(0 rows)
862+
863+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE ((u.age = u.age) = (u.age = u.age)) = (u.age = u.age) RETURN u $$) AS (result agtype);
864+
result
865+
---------------------------------------------------------------------------------------------------
866+
{"id": 844424930131969, "label": "people", "properties": {"age": 50, "name": "Jason"}}::vertex
867+
{"id": 844424930131970, "label": "people", "properties": {"age": 25, "name": "Amy"}}::vertex
868+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
869+
{"id": 844424930131972, "label": "people", "properties": {"age": 40, "name": "Mark"}}::vertex
870+
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
871+
(5 rows)
872+
744873
--
745874
-- Test transform logic for IS NULL & IS NOT NULL
746875
--

regress/sql/expr.sql

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,31 @@ SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE 35 < u.age + 1 <= 50 R
310310
-- should return 3
311311
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE NOT 35 < u.age + 1 <= 50 RETURN u $$) AS (result agtype);
312312

313+
-- order of operations
314+
-- expressions
315+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = 1 $$) AS (result agtype);
316+
SELECT * FROM cypher('chained', $$ RETURN 1 = 2 = 1 $$) AS (result agtype);
317+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = 1 $$) AS (result agtype);
318+
SELECT * FROM cypher('chained', $$ RETURN 1 = (1 = 1) $$) AS (result agtype);
319+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = true $$) AS (result agtype);
320+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = true $$) AS (result agtype);
321+
SELECT * FROM cypher('chained', $$ RETURN true = ((1 = 1) = true) $$) AS (result agtype);
322+
SELECT * FROM cypher('chained', $$ RETURN ((1 = 1) = 1) = 1 $$) AS (result agtype);
323+
SELECT * FROM cypher('chained', $$ RETURN (1 = (1 = 1)) = 1 $$) AS (result agtype);
324+
SELECT * FROM cypher('chained', $$ RETURN ((1 = (1 = 1)) = 1) = 1 $$) AS (result agtype);
325+
326+
-- in clause
327+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE 35 = u.age = 35 RETURN u $$) AS (result agtype);
328+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = u.age) = 35 RETURN u $$) AS (result agtype);
329+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = 35) = u.age RETURN u $$) AS (result agtype);
330+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = u.age = u.age RETURN u $$) AS (result agtype);
331+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = u.age RETURN u $$) AS (result agtype);
332+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = u.age) RETURN u $$) AS (result agtype);
333+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = (u.age = u.age) RETURN u $$) AS (result agtype);
334+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = (u.age = u.age)) RETURN u $$) AS (result agtype);
335+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = 35 = ((u.age = u.age) = u.age) RETURN u $$) AS (result agtype);
336+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE ((u.age = u.age) = (u.age = u.age)) = (u.age = u.age) RETURN u $$) AS (result agtype);
337+
313338
--
314339
-- Test transform logic for IS NULL & IS NOT NULL
315340
--

src/backend/nodes/ag_nodes.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const char *node_names[] = {
4848
"cypher_param",
4949
"cypher_map",
5050
"cypher_list",
51+
"cypher_comparison_aexpr",
52+
"cypher_comparison_boolexpr",
5153
"cypher_string_match",
5254
"cypher_typecast",
5355
"cypher_integer_const",
@@ -111,6 +113,8 @@ const ExtensibleNodeMethods node_methods[] = {
111113
DEFINE_NODE_METHODS(cypher_param),
112114
DEFINE_NODE_METHODS(cypher_map),
113115
DEFINE_NODE_METHODS(cypher_list),
116+
DEFINE_NODE_METHODS(cypher_comparison_aexpr),
117+
DEFINE_NODE_METHODS(cypher_comparison_boolexpr),
114118
DEFINE_NODE_METHODS(cypher_string_match),
115119
DEFINE_NODE_METHODS(cypher_typecast),
116120
DEFINE_NODE_METHODS(cypher_integer_const),

src/backend/nodes/cypher_outfuncs.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,28 @@ void out_cypher_list(StringInfo str, const ExtensibleNode *node)
259259
WRITE_LOCATION_FIELD(location);
260260
}
261261

262+
// serialization function for the cypher_comparison_aexpr ExtensibleNode.
263+
void out_cypher_comparison_aexpr(StringInfo str, const ExtensibleNode *node)
264+
{
265+
DEFINE_AG_NODE(cypher_comparison_aexpr);
266+
267+
WRITE_ENUM_FIELD(kind, A_Expr_Kind);
268+
WRITE_NODE_FIELD(name);
269+
WRITE_NODE_FIELD(lexpr);
270+
WRITE_NODE_FIELD(rexpr);
271+
WRITE_LOCATION_FIELD(location);
272+
}
273+
274+
// serialization function for the cypher_comparison_boolexpr ExtensibleNode.
275+
void out_cypher_comparison_boolexpr(StringInfo str, const ExtensibleNode *node)
276+
{
277+
DEFINE_AG_NODE(cypher_comparison_boolexpr);
278+
279+
WRITE_ENUM_FIELD(boolop, BoolExprType);
280+
WRITE_NODE_FIELD(args);
281+
WRITE_LOCATION_FIELD(location);
282+
}
283+
262284
// serialization function for the cypher_string_match ExtensibleNode.
263285
void out_cypher_string_match(StringInfo str, const ExtensibleNode *node)
264286
{

src/backend/parser/cypher_expr.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ static Node *transform_ColumnRef(cypher_parsestate *cpstate, ColumnRef *cref);
7373
static Node *transform_A_Indirection(cypher_parsestate *cpstate,
7474
A_Indirection *a_ind);
7575
static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a);
76+
static Node *transform_cypher_comparison_aexpr_OP(cypher_parsestate *cpstate,
77+
cypher_comparison_aexpr *a);
7678
static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr);
79+
static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate,
80+
cypher_comparison_boolexpr *b);
7781
static Node *transform_cypher_bool_const(cypher_parsestate *cpstate,
7882
cypher_bool_const *bc);
7983
static Node *transform_cypher_integer_const(cypher_parsestate *cpstate,
@@ -193,6 +197,12 @@ static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate,
193197
if (is_ag_node(expr, cypher_typecast))
194198
return transform_cypher_typecast(cpstate,
195199
(cypher_typecast *)expr);
200+
if (is_ag_node(expr, cypher_comparison_aexpr))
201+
return transform_cypher_comparison_aexpr_OP(cpstate,
202+
(cypher_comparison_aexpr *)expr);
203+
if (is_ag_node(expr, cypher_comparison_boolexpr))
204+
return transform_cypher_comparison_boolexpr(cpstate,
205+
(cypher_comparison_boolexpr *)expr);
196206
ereport(ERROR,
197207
(errmsg_internal("unrecognized ExtensibleNode: %s",
198208
((ExtensibleNode *)expr)->extnodename)));
@@ -477,6 +487,25 @@ static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a)
477487
a->location);
478488
}
479489

490+
/*
491+
* function for transforming cypher comparision A_Expr. Since this node is a
492+
* wrapper to let us know when a comparison occurs in a chained comparison,
493+
* we convert it to a regular A_expr and transform it.
494+
*/
495+
static Node *transform_cypher_comparison_aexpr_OP(cypher_parsestate *cpstate,
496+
cypher_comparison_aexpr *a)
497+
{
498+
A_Expr *n = makeNode(A_Expr);
499+
n->kind = a->kind;
500+
n->name = a->name;
501+
n->lexpr = a->lexpr;
502+
n->rexpr = a->rexpr;
503+
n->location = a->location;
504+
505+
return (Node *)transform_AEXPR_OP(cpstate, n);
506+
}
507+
508+
480509
static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a)
481510
{
482511
Oid func_in_oid;
@@ -535,6 +564,24 @@ static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr)
535564
return (Node *)makeBoolExpr(expr->boolop, args, expr->location);
536565
}
537566

567+
/*
568+
* function for transforming cypher_comparison_boolexpr. Since this node is a
569+
* wrapper to let us know when a comparison occurs in a chained comparison,
570+
* we convert it to a PG BoolExpr and transform it.
571+
*/
572+
static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate,
573+
cypher_comparison_boolexpr *b)
574+
{
575+
BoolExpr *n = makeNode(BoolExpr);
576+
577+
n->boolop = b->boolop;
578+
n->args = b->args;
579+
n->location = b->location;
580+
581+
return transform_BoolExpr(cpstate, n);
582+
}
583+
584+
538585
static Node *transform_cypher_bool_const(cypher_parsestate *cpstate,
539586
cypher_bool_const *bc)
540587
{

0 commit comments

Comments
 (0)