diff --git a/regress/expected/expr.out b/regress/expected/expr.out index bdc3fd936..d82c0f2a4 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -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 diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 86e78c91c..016463b54 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -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); diff --git a/src/backend/parser/ag_scanner.l b/src/backend/parser/ag_scanner.l index 256e662dc..35eb37acc 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 4dee36060..b748993c5 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -66,6 +66,8 @@ %token IDENTIFIER %token PARAMETER +%token BQIDENT +%token CHAR /* operators that have more than 1 character */ %token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE CONCAT @@ -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)); } ; @@ -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. @@ -3358,4 +3360,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 4ec56fb9a..41ed89701 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 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; diff --git a/src/include/parser/ag_scanner.h b/src/include/parser/ag_scanner.h index 7351b89b5..3dd89abd3 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; /*