Skip to content

Commit a154e57

Browse files
Fix shift/reduce conflict in grammar (apache#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 eeb1301 commit a154e57

3 files changed

Lines changed: 103 additions & 32 deletions

File tree

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
@@ -255,7 +252,12 @@ static Node *build_comparison_expression(Node *left_grammar_node,
255252
char *opr_name, int location);
256253

257254
// list_comprehension
258-
static Node *build_list_comprehension_node(char *var_name, Node *expr,
255+
static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2,
256+
Node *where, Node *mapping_expr,
257+
int var_loc, int expr_loc,
258+
int where_loc, int mapping_loc);
259+
260+
static Node *build_list_comprehension_node(ColumnRef *var_name, Node *expr,
259261
Node *where, Node *mapping_expr,
260262
int var_loc, int expr_loc,
261263
int where_loc,int mapping_loc);
@@ -2048,20 +2050,51 @@ list:
20482050

20492051
$$ = (Node *)n;
20502052
}
2051-
| '[' list_comprehension ']'
2052-
{
2053-
$$ = $2;
2054-
}
2053+
| list_comprehension
20552054
;
20562055

2057-
mapping_expr_opt:
2058-
/* empty */
2056+
/*
2057+
* This grammar rule is generic to some extent. It can
2058+
* evaluate to either IN operator or list comprehension.
2059+
* This avoids shift/reduce errors between the two rules.
2060+
*/
2061+
list_comprehension:
2062+
'[' expr IN expr ']'
20592063
{
2060-
$$ = NULL;
2064+
Node *n = $2;
2065+
Node *result = NULL;
2066+
2067+
/*
2068+
* If the first expr is a ColumnRef(variable), then the rule
2069+
* should evaluate as a list comprehension. Otherwise, it should
2070+
* evaluate as an IN operator.
2071+
*/
2072+
if (nodeTag(n) == T_ColumnRef)
2073+
{
2074+
ColumnRef *cref = (ColumnRef *)n;
2075+
result = build_list_comprehension_node(cref, $4, NULL, NULL,
2076+
@2, @4, 0, 0);
2077+
}
2078+
else
2079+
{
2080+
result = (Node *)makeSimpleA_Expr(AEXPR_IN, "=", n, $4, @3);
2081+
}
2082+
$$ = result;
20612083
}
2062-
| '|' expr
2084+
| '[' expr IN expr WHERE expr ']'
20632085
{
2064-
$$ = $2;
2086+
$$ = verify_rule_as_list_comprehension($2, $4, $6, NULL,
2087+
@2, @4, @6, 0);
2088+
}
2089+
| '[' expr IN expr '|' expr ']'
2090+
{
2091+
$$ = verify_rule_as_list_comprehension($2, $4, NULL, $6,
2092+
@2, @4, 0, @6);
2093+
}
2094+
| '[' expr IN expr WHERE expr '|' expr ']'
2095+
{
2096+
$$ = verify_rule_as_list_comprehension($2, $4, $6, $8,
2097+
@2, @4, @6, @8);
20652098
}
20662099
;
20672100

@@ -2126,14 +2159,6 @@ expr_case_default:
21262159
}
21272160
;
21282161

2129-
list_comprehension:
2130-
var_name IN expr where_opt mapping_expr_opt
2131-
{
2132-
$$ = build_list_comprehension_node($1, $3, $4, $5,
2133-
@1, @3, @4, @5);
2134-
}
2135-
;
2136-
21372162
expr_var:
21382163
var_name
21392164
{
@@ -3099,15 +3124,57 @@ static cypher_relationship *build_VLE_relation(List *left_arg,
30993124
return cr;
31003125
}
31013126

3127+
// Helper function to verify that the rule is a list comprehension
3128+
static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2,
3129+
Node *where, Node *mapping_expr,
3130+
int var_loc, int expr_loc,
3131+
int where_loc, int mapping_loc)
3132+
{
3133+
Node *result = NULL;
3134+
3135+
/*
3136+
* If the first expression is a ColumnRef, then we can build a
3137+
* list_comprehension node.
3138+
* Else its an invalid use of IN operator.
3139+
*/
3140+
if (nodeTag(expr) == T_ColumnRef)
3141+
{
3142+
ColumnRef *cref = (ColumnRef *)expr;
3143+
result = build_list_comprehension_node(cref, expr2, where,
3144+
mapping_expr, var_loc,
3145+
expr_loc, where_loc,
3146+
mapping_loc);
3147+
}
3148+
else
3149+
{
3150+
ereport(ERROR,
3151+
(errcode(ERRCODE_SYNTAX_ERROR),
3152+
errmsg("Syntax error at or near IN")));
3153+
}
3154+
return result;
3155+
}
3156+
31023157
/* helper function to build a list_comprehension grammar node */
3103-
static Node *build_list_comprehension_node(char *var_name, Node *expr,
3158+
static Node *build_list_comprehension_node(ColumnRef *cref, Node *expr,
31043159
Node *where, Node *mapping_expr,
31053160
int var_loc, int expr_loc,
31063161
int where_loc, int mapping_loc)
31073162
{
31083163
ResTarget *res = NULL;
31093164
cypher_unwind *unwind = NULL;
3110-
ColumnRef *cref = NULL;
3165+
char *var_name = NULL;
3166+
String *val;
3167+
3168+
// Extract name from cref
3169+
val = linitial(cref->fields);
3170+
3171+
if (!IsA(val, String))
3172+
{
3173+
ereport(ERROR,
3174+
(errmsg_internal("unexpected Node for cypher_clause")));
3175+
}
3176+
3177+
var_name = val->sval;
31113178

31123179
/*
31133180
* Build the ResTarget node for the UNWIND variable var_name attached to
@@ -3121,15 +3188,6 @@ static Node *build_list_comprehension_node(char *var_name, Node *expr,
31213188
/* build the UNWIND node */
31223189
unwind = make_ag_node(cypher_unwind);
31233190
unwind->target = res;
3124-
3125-
/*
3126-
* We need to make a ColumnRef of var_name so that it can be used as an expr
3127-
* for the where clause part of unwind.
3128-
*/
3129-
cref = makeNode(ColumnRef);
3130-
cref->fields = list_make1(makeString(var_name));
3131-
cref->location = var_loc;
3132-
31333191
unwind->where = where;
31343192

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

0 commit comments

Comments
 (0)