Skip to content

Commit fab3119

Browse files
Fix shift/reduce conflict in grammar (#1719)
- The grammar had a shift/reduce conflict due to the ambiguity of the `IN` keyword. This is resolved by adding generic rule and manually resolving to the correct specific rule. - Added additional test cases.
1 parent 00e0c58 commit fab3119

File tree

3 files changed

+103
-32
lines changed

3 files changed

+103
-32
lines changed

regress/expected/list_comprehension.out

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,15 @@ SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) WHERE
571571
ERROR: could not find rte for i
572572
LINE 1: ...$$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (...
573573
^
574+
-- Invalid list comprehension
575+
SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) WHERE 2>5] $$) AS (result agtype);
576+
ERROR: Syntax error at or near IN
577+
LINE 1: SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN r...
578+
^
579+
SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) | 1] $$) AS (result agtype);
580+
ERROR: Syntax error at or near IN
581+
LINE 1: SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN r...
582+
^
574583
SELECT * FROM drop_graph('list_comprehension', true);
575584
NOTICE: drop cascades to 4 other objects
576585
DETAIL: drop cascades to table list_comprehension._ag_label_vertex

regress/sql/list_comprehension.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,8 @@ SELECT * FROM cypher('list_comprehension', $$ WITH [u IN [1,2,3]] AS u, [u IN [1
145145
SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2)],i $$) AS (result agtype, i agtype);
146146
SELECT * FROM cypher('list_comprehension', $$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (result agtype, i agtype);
147147

148+
-- Invalid list comprehension
149+
SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) WHERE 2>5] $$) AS (result agtype);
150+
SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) | 1] $$) AS (result agtype);
151+
148152
SELECT * FROM drop_graph('list_comprehension', true);

src/backend/parser/cypher_gram.y

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,6 @@
139139
/* common */
140140
%type <node> where_opt
141141

142-
/* list comprehension optional mapping expression */
143-
%type <node> mapping_expr_opt
144-
145142
/* pattern */
146143
%type <list> pattern simple_path_opt_parens simple_path
147144
%type <node> path anonymous_path
@@ -258,7 +255,12 @@ static Node *build_comparison_expression(Node *left_grammar_node,
258255
char *opr_name, int location);
259256

260257
// list_comprehension
261-
static Node *build_list_comprehension_node(char *var_name, Node *expr,
258+
static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2,
259+
Node *where, Node *mapping_expr,
260+
int var_loc, int expr_loc,
261+
int where_loc, int mapping_loc);
262+
263+
static Node *build_list_comprehension_node(ColumnRef *var_name, Node *expr,
262264
Node *where, Node *mapping_expr,
263265
int var_loc, int expr_loc,
264266
int where_loc,int mapping_loc);
@@ -2128,20 +2130,51 @@ list:
21282130

21292131
$$ = (Node *)n;
21302132
}
2131-
| '[' list_comprehension ']'
2132-
{
2133-
$$ = $2;
2134-
}
2133+
| list_comprehension
21352134
;
21362135

2137-
mapping_expr_opt:
2138-
/* empty */
2136+
/*
2137+
* This grammar rule is generic to some extent. It can
2138+
* evaluate to either IN operator or list comprehension.
2139+
* This avoids shift/reduce errors between the two rules.
2140+
*/
2141+
list_comprehension:
2142+
'[' expr IN expr ']'
21392143
{
2140-
$$ = NULL;
2144+
Node *n = $2;
2145+
Node *result = NULL;
2146+
2147+
/*
2148+
* If the first expr is a ColumnRef(variable), then the rule
2149+
* should evaluate as a list comprehension. Otherwise, it should
2150+
* evaluate as an IN operator.
2151+
*/
2152+
if (nodeTag(n) == T_ColumnRef)
2153+
{
2154+
ColumnRef *cref = (ColumnRef *)n;
2155+
result = build_list_comprehension_node(cref, $4, NULL, NULL,
2156+
@2, @4, 0, 0);
2157+
}
2158+
else
2159+
{
2160+
result = (Node *)makeSimpleA_Expr(AEXPR_IN, "=", n, $4, @3);
2161+
}
2162+
$$ = result;
21412163
}
2142-
| '|' expr
2164+
| '[' expr IN expr WHERE expr ']'
21432165
{
2144-
$$ = $2;
2166+
$$ = verify_rule_as_list_comprehension($2, $4, $6, NULL,
2167+
@2, @4, @6, 0);
2168+
}
2169+
| '[' expr IN expr '|' expr ']'
2170+
{
2171+
$$ = verify_rule_as_list_comprehension($2, $4, NULL, $6,
2172+
@2, @4, 0, @6);
2173+
}
2174+
| '[' expr IN expr WHERE expr '|' expr ']'
2175+
{
2176+
$$ = verify_rule_as_list_comprehension($2, $4, $6, $8,
2177+
@2, @4, @6, @8);
21452178
}
21462179
;
21472180

@@ -2206,14 +2239,6 @@ expr_case_default:
22062239
}
22072240
;
22082241

2209-
list_comprehension:
2210-
var_name IN expr where_opt mapping_expr_opt
2211-
{
2212-
$$ = build_list_comprehension_node($1, $3, $4, $5,
2213-
@1, @3, @4, @5);
2214-
}
2215-
;
2216-
22172242
expr_var:
22182243
var_name
22192244
{
@@ -3179,15 +3204,57 @@ static cypher_relationship *build_VLE_relation(List *left_arg,
31793204
return cr;
31803205
}
31813206

3207+
// Helper function to verify that the rule is a list comprehension
3208+
static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2,
3209+
Node *where, Node *mapping_expr,
3210+
int var_loc, int expr_loc,
3211+
int where_loc, int mapping_loc)
3212+
{
3213+
Node *result = NULL;
3214+
3215+
/*
3216+
* If the first expression is a ColumnRef, then we can build a
3217+
* list_comprehension node.
3218+
* Else its an invalid use of IN operator.
3219+
*/
3220+
if (nodeTag(expr) == T_ColumnRef)
3221+
{
3222+
ColumnRef *cref = (ColumnRef *)expr;
3223+
result = build_list_comprehension_node(cref, expr2, where,
3224+
mapping_expr, var_loc,
3225+
expr_loc, where_loc,
3226+
mapping_loc);
3227+
}
3228+
else
3229+
{
3230+
ereport(ERROR,
3231+
(errcode(ERRCODE_SYNTAX_ERROR),
3232+
errmsg("Syntax error at or near IN")));
3233+
}
3234+
return result;
3235+
}
3236+
31823237
/* helper function to build a list_comprehension grammar node */
3183-
static Node *build_list_comprehension_node(char *var_name, Node *expr,
3238+
static Node *build_list_comprehension_node(ColumnRef *cref, Node *expr,
31843239
Node *where, Node *mapping_expr,
31853240
int var_loc, int expr_loc,
31863241
int where_loc, int mapping_loc)
31873242
{
31883243
ResTarget *res = NULL;
31893244
cypher_unwind *unwind = NULL;
3190-
ColumnRef *cref = NULL;
3245+
char *var_name = NULL;
3246+
String *val;
3247+
3248+
// Extract name from cref
3249+
val = linitial(cref->fields);
3250+
3251+
if (!IsA(val, String))
3252+
{
3253+
ereport(ERROR,
3254+
(errmsg_internal("unexpected Node for cypher_clause")));
3255+
}
3256+
3257+
var_name = val->sval;
31913258

31923259
/*
31933260
* Build the ResTarget node for the UNWIND variable var_name attached to
@@ -3201,15 +3268,6 @@ static Node *build_list_comprehension_node(char *var_name, Node *expr,
32013268
/* build the UNWIND node */
32023269
unwind = make_ag_node(cypher_unwind);
32033270
unwind->target = res;
3204-
3205-
/*
3206-
* We need to make a ColumnRef of var_name so that it can be used as an expr
3207-
* for the where clause part of unwind.
3208-
*/
3209-
cref = makeNode(ColumnRef);
3210-
cref->fields = list_make1(makeString(var_name));
3211-
cref->location = var_loc;
3212-
32133271
unwind->where = where;
32143272

32153273
/* if there is a mapping function, add its arg to collect */

0 commit comments

Comments
 (0)