Skip to content

Commit

Permalink
Fix Issue 1988: How to update a property which is a keyword
Browse files Browse the repository at this point in the history
Fixed issue 1988: Reserved Keyword Handling: How to update a property
which is a keyword. Basically, how to use a keyword, aka reserved
word, in a query.

Added the appropriate scanner and parser rules to allow keywords to
be back ticked and used in queries as their name. For example, like
in the following commands -

    MATCH (n:Person { id: 123 }) SET n.`match` = 'matched' RETURN n;
    MATCH (n:Person { id: 123 }) SET n.`create` = 'created' RETURN n;

The changes did not break any regression tests.
Added additional regression tests.
  • Loading branch information
jrgemignani committed Aug 6, 2024
1 parent ee1bb95 commit 00f62e5
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 5 deletions.
137 changes: 137 additions & 0 deletions regress/expected/expr.out
Original file line number Diff line number Diff line change
Expand Up @@ -8463,9 +8463,146 @@ SELECT * FROM cypher('issue_1953', $$ RETURN is_valid_label_name('issue_1953')[0
ERROR: A_indirection could not convert type boolean to agtype
LINE 1: ...cypher('issue_1953', $$ RETURN is_valid_label_name('issue_19...
^
--
-- Issue 1988: How to update a property which is a keyword.
--
SELECT * FROM create_graph('issue_1988');
NOTICE: graph "issue_1988" has been created
create_graph
--------------

(1 row)

SELECT * from cypher('issue_1988', $$
CREATE (p1:Part {part_num: 123}),
(p2:Part {part_num: 345}),
(p3:Part {part_num: 456}),
(p4:Part {part_num: 789}) $$) as (a agtype);
a
---
(0 rows)

SELECT * FROM cypher('issue_1988', $$
MATCH (p) RETURN p $$) as (p agtype);
p
-----------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"part_num": 123}}::vertex
{"id": 844424930131970, "label": "Part", "properties": {"part_num": 345}}::vertex
{"id": 844424930131971, "label": "Part", "properties": {"part_num": 456}}::vertex
{"id": 844424930131972, "label": "Part", "properties": {"part_num": 789}}::vertex
(4 rows)

SELECT * from cypher('issue_1988', $$
MATCH (p1:Part {part_num: 123}), (p2:Part {part_num: 345})
CREATE (p1)-[u:used_by { quantity: 1 }]->(p2) RETURN p1, u, p2 $$) as (p1 agtype, u agtype, p2 agtype);
p1 | u | p2
-----------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"part_num": 123}}::vertex | {"id": 1125899906842625, "label": "used_by", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {"quantity": 1}}::edge | {"id": 844424930131970, "label": "Part", "properties": {"part_num": 345}}::vertex
(1 row)

-- should fail
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.match = 'xyz' RETURN p $$) as (p agtype);
ERROR: syntax error at or near "="
LINE 2: MATCH (p:Part { part_num: 123 }) SET p.match = 'xyz' RET...
^
-- should succeed
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`match` = 'xyz' RETURN p $$) as (p agtype);
p
---------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"match": "xyz", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`set` = 'xyz' RETURN p $$) as (p agtype);
p
-----------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "xyz", "match": "xyz", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`delete` = 'xyz' RETURN p $$) as (p agtype);
p
----------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "xyz", "match": "xyz", "delete": "xyz", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`merge` = 'xyz' RETURN p $$) as (p agtype);
p
--------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "xyz", "match": "xyz", "merge": "xyz", "delete": "xyz", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`create` = 'xyz' RETURN p $$) as (p agtype);
p
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "xyz", "match": "xyz", "merge": "xyz", "create": "xyz", "delete": "xyz", "part_num": 123}}::vertex
(1 row)

-- should succeed
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`match` = 'match' RETURN p $$) as (p agtype);
p
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "xyz", "match": "match", "merge": "xyz", "create": "xyz", "delete": "xyz", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`set` = 'set' RETURN p $$) as (p agtype);
p
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "set", "match": "match", "merge": "xyz", "create": "xyz", "delete": "xyz", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`delete` = 'delete' RETURN p $$) as (p agtype);
p
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "set", "match": "match", "merge": "xyz", "create": "xyz", "delete": "delete", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`merge` = 'merge' RETURN p $$) as (p agtype);
p
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "set", "match": "match", "merge": "merge", "create": "xyz", "delete": "delete", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`create` = 'create' RETURN p $$) as (p agtype);
p
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Part", "properties": {"set": "set", "match": "match", "merge": "merge", "create": "create", "delete": "delete", "part_num": 123}}::vertex
(1 row)

SELECT * FROM cypher('issue_1988', $$
MATCH (p) RETURN p $$) as (p agtype);
p
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131970, "label": "Part", "properties": {"part_num": 345}}::vertex
{"id": 844424930131971, "label": "Part", "properties": {"part_num": 456}}::vertex
{"id": 844424930131972, "label": "Part", "properties": {"part_num": 789}}::vertex
{"id": 844424930131969, "label": "Part", "properties": {"set": "set", "match": "match", "merge": "merge", "create": "create", "delete": "delete", "part_num": 123}}::vertex
(4 rows)

--
-- Cleanup
--
SELECT * FROM drop_graph('issue_1988', true);
NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to table issue_1988._ag_label_vertex
drop cascades to table issue_1988._ag_label_edge
drop cascades to table issue_1988."Part"
drop cascades to table issue_1988.used_by
NOTICE: graph "issue_1988" has been dropped
drop_graph
------------

(1 row)

SELECT * FROM drop_graph('issue_1953', true);
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table issue_1953._ag_label_vertex
Expand Down
47 changes: 47 additions & 0 deletions regress/sql/expr.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3444,9 +3444,56 @@ SELECT * FROM cypher('issue_1953', $$ RETURN is_valid_label_name('issue_1953')[{
SELECT * FROM cypher('issue_1953', $$ RETURN is_valid_label_name('issue_1953')[0] $$) AS (result agtype);
SELECT * FROM cypher('issue_1953', $$ RETURN is_valid_label_name('issue_1953')[0..1] $$) AS (result agtype);

--
-- Issue 1988: How to update a property which is a keyword.
--
SELECT * FROM create_graph('issue_1988');
SELECT * from cypher('issue_1988', $$
CREATE (p1:Part {part_num: 123}),
(p2:Part {part_num: 345}),
(p3:Part {part_num: 456}),
(p4:Part {part_num: 789}) $$) as (a agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p) RETURN p $$) as (p agtype);

SELECT * from cypher('issue_1988', $$
MATCH (p1:Part {part_num: 123}), (p2:Part {part_num: 345})
CREATE (p1)-[u:used_by { quantity: 1 }]->(p2) RETURN p1, u, p2 $$) as (p1 agtype, u agtype, p2 agtype);

-- should fail
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.match = 'xyz' RETURN p $$) as (p agtype);

-- should succeed
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`match` = 'xyz' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`set` = 'xyz' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`delete` = 'xyz' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`merge` = 'xyz' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`create` = 'xyz' RETURN p $$) as (p agtype);
-- should succeed
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`match` = 'match' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`set` = 'set' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`delete` = 'delete' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`merge` = 'merge' RETURN p $$) as (p agtype);
SELECT * FROM cypher('issue_1988', $$
MATCH (p:Part { part_num: 123 }) SET p.`create` = 'create' RETURN p $$) as (p agtype);

SELECT * FROM cypher('issue_1988', $$
MATCH (p) RETURN p $$) as (p agtype);

--
-- Cleanup
--
SELECT * FROM drop_graph('issue_1988', true);
SELECT * FROM drop_graph('issue_1953', true);
SELECT * FROM drop_graph('expanded_map', true);
SELECT * FROM drop_graph('issue_1124', true);
Expand Down
2 changes: 1 addition & 1 deletion src/backend/parser/ag_scanner.l
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ ag_token token;
scan_errposition()));
}

token.type = AG_TOKEN_IDENTIFIER;
token.type = AG_TOKEN_BQIDENT;
token.value.s = strbuf_get_str(&yyextra.literal_buf);
token.location = get_location();
return token;
Expand Down
8 changes: 5 additions & 3 deletions src/backend/parser/cypher_gram.y
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@

%token <string> IDENTIFIER
%token <string> PARAMETER
%token <string> BQIDENT
%token <character> CHAR

/* operators that have more than 1 character */
%token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE CONCAT
Expand Down Expand Up @@ -640,7 +642,7 @@ subquery_stmt_no_return:
single_subquery:
subquery_part_init reading_clause_list return
{
$$ = list_concat($1, lappend($2, $3));
$$ = list_concat($1, lappend($2, $3));
}
;

Expand Down Expand Up @@ -3279,7 +3281,7 @@ static Node *verify_rule_as_list_comprehension(Node *expr, Node *expr2,
int where_loc, int mapping_loc)
{
Node *result = NULL;

/*
* If the first expression is a ColumnRef, then we can build a
* list_comprehension node.
Expand Down Expand Up @@ -3358,4 +3360,4 @@ static Node *build_list_comprehension_node(ColumnRef *cref, Node *expr,

/* return the UNWIND node */
return (Node *)unwind;
}
}
16 changes: 15 additions & 1 deletion src/backend/parser/cypher_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ int cypher_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, ag_scanner_t scanner)
ACCESS_PATH,
ANY_EXISTS,
ALL_EXISTS,
CONCAT
CONCAT,
CHAR,
BQIDENT
};

ag_token token;
Expand Down Expand Up @@ -93,6 +95,18 @@ int cypher_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, ag_scanner_t scanner)
lvalp->string = ident;
break;
}
case AG_TOKEN_BQIDENT:
{
char *ident;

/* these are identitiers, just back ticked */
token.type = AG_TOKEN_IDENTIFIER;

ident = pstrdup(token.value.s);
truncate_identifier(ident, strlen(ident), true);
lvalp->string = ident;
break;
}
case AG_TOKEN_PARAMETER:
lvalp->string = pstrdup(token.value.s);
break;
Expand Down
1 change: 1 addition & 0 deletions src/include/parser/ag_scanner.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ typedef enum ag_token_type
AG_TOKEN_ALL_EXISTS,
AG_TOKEN_CONCAT,
AG_TOKEN_CHAR,
AG_TOKEN_BQIDENT
} ag_token_type;

/*
Expand Down

0 comments on commit 00f62e5

Please sign in to comment.