Skip to content

Commit d9afa0d

Browse files
dehowefmuhammadshoaib
authored andcommitted
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 239efc5 commit d9afa0d

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
@@ -808,6 +808,135 @@ SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE NOT 35 < u.age + 1 <=
808808
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
809809
(3 rows)
810810

811+
-- order of operations
812+
-- expressions
813+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = 1 $$) AS (result agtype);
814+
result
815+
--------
816+
true
817+
(1 row)
818+
819+
SELECT * FROM cypher('chained', $$ RETURN 1 = 2 = 1 $$) AS (result agtype);
820+
result
821+
--------
822+
false
823+
(1 row)
824+
825+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = 1 $$) AS (result agtype);
826+
result
827+
--------
828+
false
829+
(1 row)
830+
831+
SELECT * FROM cypher('chained', $$ RETURN 1 = (1 = 1) $$) AS (result agtype);
832+
result
833+
--------
834+
false
835+
(1 row)
836+
837+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = true $$) AS (result agtype);
838+
result
839+
--------
840+
false
841+
(1 row)
842+
843+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = true $$) AS (result agtype);
844+
result
845+
--------
846+
true
847+
(1 row)
848+
849+
SELECT * FROM cypher('chained', $$ RETURN true = ((1 = 1) = true) $$) AS (result agtype);
850+
result
851+
--------
852+
true
853+
(1 row)
854+
855+
SELECT * FROM cypher('chained', $$ RETURN ((1 = 1) = 1) = 1 $$) AS (result agtype);
856+
result
857+
--------
858+
false
859+
(1 row)
860+
861+
SELECT * FROM cypher('chained', $$ RETURN (1 = (1 = 1)) = 1 $$) AS (result agtype);
862+
result
863+
--------
864+
false
865+
(1 row)
866+
867+
SELECT * FROM cypher('chained', $$ RETURN ((1 = (1 = 1)) = 1) = 1 $$) AS (result agtype);
868+
result
869+
--------
870+
false
871+
(1 row)
872+
873+
-- in clause
874+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE 35 = u.age = 35 RETURN u $$) AS (result agtype);
875+
result
876+
---------------------------------------------------------------------------------------------------
877+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
878+
(1 row)
879+
880+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = u.age) = 35 RETURN u $$) AS (result agtype);
881+
result
882+
--------
883+
(0 rows)
884+
885+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = 35) = u.age RETURN u $$) AS (result agtype);
886+
result
887+
--------
888+
(0 rows)
889+
890+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = u.age = u.age RETURN u $$) AS (result agtype);
891+
result
892+
---------------------------------------------------------------------------------------------------
893+
{"id": 844424930131969, "label": "people", "properties": {"age": 50, "name": "Jason"}}::vertex
894+
{"id": 844424930131970, "label": "people", "properties": {"age": 25, "name": "Amy"}}::vertex
895+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
896+
{"id": 844424930131972, "label": "people", "properties": {"age": 40, "name": "Mark"}}::vertex
897+
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
898+
(5 rows)
899+
900+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = u.age RETURN u $$) AS (result agtype);
901+
result
902+
--------
903+
(0 rows)
904+
905+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = u.age) RETURN u $$) AS (result agtype);
906+
result
907+
--------
908+
(0 rows)
909+
910+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = (u.age = u.age) RETURN u $$) AS (result agtype);
911+
result
912+
---------------------------------------------------------------------------------------------------
913+
{"id": 844424930131969, "label": "people", "properties": {"age": 50, "name": "Jason"}}::vertex
914+
{"id": 844424930131970, "label": "people", "properties": {"age": 25, "name": "Amy"}}::vertex
915+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
916+
{"id": 844424930131972, "label": "people", "properties": {"age": 40, "name": "Mark"}}::vertex
917+
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
918+
(5 rows)
919+
920+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = (u.age = u.age)) RETURN u $$) AS (result agtype);
921+
result
922+
--------
923+
(0 rows)
924+
925+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = 35 = ((u.age = u.age) = u.age) RETURN u $$) AS (result agtype);
926+
result
927+
--------
928+
(0 rows)
929+
930+
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);
931+
result
932+
---------------------------------------------------------------------------------------------------
933+
{"id": 844424930131969, "label": "people", "properties": {"age": 50, "name": "Jason"}}::vertex
934+
{"id": 844424930131970, "label": "people", "properties": {"age": 25, "name": "Amy"}}::vertex
935+
{"id": 844424930131971, "label": "people", "properties": {"age": 35, "name": "Samantha"}}::vertex
936+
{"id": 844424930131972, "label": "people", "properties": {"age": 40, "name": "Mark"}}::vertex
937+
{"id": 844424930131973, "label": "people", "properties": {"age": 15, "name": "David"}}::vertex
938+
(5 rows)
939+
811940
--
812941
-- Test transform logic for IS NULL & IS NOT NULL
813942
--

regress/sql/expr.sql

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

331+
-- order of operations
332+
-- expressions
333+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = 1 $$) AS (result agtype);
334+
SELECT * FROM cypher('chained', $$ RETURN 1 = 2 = 1 $$) AS (result agtype);
335+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = 1 $$) AS (result agtype);
336+
SELECT * FROM cypher('chained', $$ RETURN 1 = (1 = 1) $$) AS (result agtype);
337+
SELECT * FROM cypher('chained', $$ RETURN 1 = 1 = true $$) AS (result agtype);
338+
SELECT * FROM cypher('chained', $$ RETURN (1 = 1) = true $$) AS (result agtype);
339+
SELECT * FROM cypher('chained', $$ RETURN true = ((1 = 1) = true) $$) AS (result agtype);
340+
SELECT * FROM cypher('chained', $$ RETURN ((1 = 1) = 1) = 1 $$) AS (result agtype);
341+
SELECT * FROM cypher('chained', $$ RETURN (1 = (1 = 1)) = 1 $$) AS (result agtype);
342+
SELECT * FROM cypher('chained', $$ RETURN ((1 = (1 = 1)) = 1) = 1 $$) AS (result agtype);
343+
344+
-- in clause
345+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE 35 = u.age = 35 RETURN u $$) AS (result agtype);
346+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = u.age) = 35 RETURN u $$) AS (result agtype);
347+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (35 = 35) = u.age RETURN u $$) AS (result agtype);
348+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = u.age = u.age RETURN u $$) AS (result agtype);
349+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = u.age RETURN u $$) AS (result agtype);
350+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = u.age) RETURN u $$) AS (result agtype);
351+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE (u.age = u.age) = (u.age = u.age) RETURN u $$) AS (result agtype);
352+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = (u.age = (u.age = u.age)) RETURN u $$) AS (result agtype);
353+
SELECT * FROM cypher('chained', $$ MATCH (u:people) WHERE u.age = 35 = ((u.age = u.age) = u.age) RETURN u $$) AS (result agtype);
354+
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);
355+
331356
--
332357
-- Test transform logic for IS NULL & IS NOT NULL
333358
--

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)));
@@ -488,6 +498,25 @@ static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a)
488498
a->location);
489499
}
490500

501+
/*
502+
* function for transforming cypher comparision A_Expr. Since this node is a
503+
* wrapper to let us know when a comparison occurs in a chained comparison,
504+
* we convert it to a regular A_expr and transform it.
505+
*/
506+
static Node *transform_cypher_comparison_aexpr_OP(cypher_parsestate *cpstate,
507+
cypher_comparison_aexpr *a)
508+
{
509+
A_Expr *n = makeNode(A_Expr);
510+
n->kind = a->kind;
511+
n->name = a->name;
512+
n->lexpr = a->lexpr;
513+
n->rexpr = a->rexpr;
514+
n->location = a->location;
515+
516+
return (Node *)transform_AEXPR_OP(cpstate, n);
517+
}
518+
519+
491520
static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a)
492521
{
493522
ParseState *pstate = (ParseState *)cpstate;
@@ -660,6 +689,24 @@ static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr)
660689
return (Node *)makeBoolExpr(expr->boolop, args, expr->location);
661690
}
662691

692+
/*
693+
* function for transforming cypher_comparison_boolexpr. Since this node is a
694+
* wrapper to let us know when a comparison occurs in a chained comparison,
695+
* we convert it to a PG BoolExpr and transform it.
696+
*/
697+
static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate,
698+
cypher_comparison_boolexpr *b)
699+
{
700+
BoolExpr *n = makeNode(BoolExpr);
701+
702+
n->boolop = b->boolop;
703+
n->args = b->args;
704+
n->location = b->location;
705+
706+
return transform_BoolExpr(cpstate, n);
707+
}
708+
709+
663710
static Node *transform_cypher_bool_const(cypher_parsestate *cpstate,
664711
cypher_bool_const *bc)
665712
{

0 commit comments

Comments
 (0)