This is an automated email from the ASF dual-hosted git repository.
rafsun42 pushed a commit to branch PG13
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/PG13 by this push:
new b1a21053 Fix Issue 1988: How to update a property which is a keyword
(#2005) (#2017)
b1a21053 is described below
commit b1a21053784e9e127810a0f5caecc344e99c3009
Author: John Gemignani <[email protected]>
AuthorDate: Tue Aug 6 16:13:43 2024 -0700
Fix Issue 1988: How to update a property which is a keyword (#2005) (#2017)
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.
---
regress/expected/expr.out | 137 +++++++++++++++++++++++++++++++++++++
regress/sql/expr.sql | 47 +++++++++++++
src/backend/parser/ag_scanner.l | 2 +-
src/backend/parser/cypher_gram.y | 8 ++-
src/backend/parser/cypher_parser.c | 16 ++++-
src/include/parser/ag_scanner.h | 1 +
6 files changed, 206 insertions(+), 5 deletions(-)
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index ea43a625..45fdfd25 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -8392,9 +8392,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
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index a0a5e0c5..a4e3712e 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -3417,9 +3417,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);
diff --git a/src/backend/parser/ag_scanner.l b/src/backend/parser/ag_scanner.l
index 256e662d..35eb37ac 100644
--- a/src/backend/parser/ag_scanner.l
+++ b/src/backend/parser/ag_scanner.l
@@ -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;
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 9b4caf21..790c069b 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -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
@@ -656,7 +658,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));
}
;
@@ -3270,7 +3272,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.
@@ -3349,4 +3351,4 @@ static Node *build_list_comprehension_node(ColumnRef
*cref, Node *expr,
/* return the UNWIND node */
return (Node *)unwind;
-}
\ No newline at end of file
+}
diff --git a/src/backend/parser/cypher_parser.c
b/src/backend/parser/cypher_parser.c
index 4ec56fb9..d2b64ffe 100644
--- a/src/backend/parser/cypher_parser.c
+++ b/src/backend/parser/cypher_parser.c
@@ -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;
@@ -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 identifiers, 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;
diff --git a/src/include/parser/ag_scanner.h b/src/include/parser/ag_scanner.h
index 7351b89b..3dd89abd 100644
--- a/src/include/parser/ag_scanner.h
+++ b/src/include/parser/ag_scanner.h
@@ -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;
/*