From 8872973692e654ee8f147c8deb1368bbf3dd4312 Mon Sep 17 00:00:00 2001 From: "jie.wang" <38901892+jievince@users.noreply.github.com> Date: Tue, 30 Nov 2021 10:00:51 +0800 Subject: [PATCH] support gql list/set/map (#3302) Co-authored-by: kyle.cao Co-authored-by: cpw <13495049+CPWstatic@users.noreply.github.com> --- .linters/cpp/checkKeyword.py | 2 + src/common/expression/ContainerExpression.cpp | 27 ++++++---- .../validator/test/YieldValidatorTest.cpp | 4 +- src/parser/parser.yy | 32 ++++++++++- src/parser/scanner.lex | 3 +- src/parser/test/ParserTest.cpp | 2 +- tests/tck/features/basic/data.feature | 53 +++++++++++++++++++ .../expression/ListComprehension.feature | 4 +- .../features/expressions/list/List2.feature | 44 +++++++-------- .../features/expressions/map/Map1.feature | 16 +++--- 10 files changed, 140 insertions(+), 47 deletions(-) create mode 100644 tests/tck/features/basic/data.feature diff --git a/.linters/cpp/checkKeyword.py b/.linters/cpp/checkKeyword.py index b16ce0ba2b8..7d07dda87e4 100755 --- a/.linters/cpp/checkKeyword.py +++ b/.linters/cpp/checkKeyword.py @@ -20,6 +20,8 @@ 'KW_XOR', 'KW_USE', 'KW_SET', + 'KW_LIST', + 'KW_MAP', 'KW_FROM', 'KW_WHERE', 'KW_MATCH', diff --git a/src/common/expression/ContainerExpression.cpp b/src/common/expression/ContainerExpression.cpp index 7cf6934d003..9a1c15070b8 100644 --- a/src/common/expression/ContainerExpression.cpp +++ b/src/common/expression/ContainerExpression.cpp @@ -12,9 +12,8 @@ namespace nebula { +// TODO(jie): toString of list should add `LIST` prefix std::string ListExpression::toString() const { - // list *expression* is not allowed to be empty - DCHECK(!items_.empty()); std::string buf; buf.reserve(256); @@ -23,7 +22,11 @@ std::string ListExpression::toString() const { buf += expr->toString(); buf += ","; } - buf.back() = ']'; + if (items_.empty()) { + buf += "]"; + } else { + buf.back() = ']'; + } return buf; } @@ -78,9 +81,8 @@ void ListExpression::resetFrom(Decoder &decoder) { void ListExpression::accept(ExprVisitor *visitor) { visitor->visit(this); } +// TODO(jie): toString of set should add `SET` prefix std::string SetExpression::toString() const { - // set *expression* is not allowed to be empty - DCHECK(!items_.empty()); std::string buf; buf.reserve(256); @@ -89,7 +91,11 @@ std::string SetExpression::toString() const { buf += expr->toString(); buf += ","; } - buf.back() = '}'; + if (items_.empty()) { + buf += "}"; + } else { + buf.back() = '}'; + } return buf; } @@ -144,9 +150,8 @@ void SetExpression::resetFrom(Decoder &decoder) { void SetExpression::accept(ExprVisitor *visitor) { visitor->visit(this); } +// TODO(jie): toString of map should add `MAP` prefix std::string MapExpression::toString() const { - // map *expression* is not allowed to be empty - DCHECK(!items_.empty()); std::string buf; buf.reserve(256); @@ -157,7 +162,11 @@ std::string MapExpression::toString() const { buf += kv.second->toString(); buf += ","; } - buf.back() = '}'; + if (items_.empty()) { + buf += "}"; + } else { + buf.back() = '}'; + } return buf; } diff --git a/src/graph/validator/test/YieldValidatorTest.cpp b/src/graph/validator/test/YieldValidatorTest.cpp index 3ccb89d0bd7..b436933ff14 100644 --- a/src/graph/validator/test/YieldValidatorTest.cpp +++ b/src/graph/validator/test/YieldValidatorTest.cpp @@ -213,12 +213,12 @@ TEST_F(YieldValidatorTest, TypeCastTest) { { std::string query = "YIELD (MAP)(\"12\")"; auto result = checkResult(query); - EXPECT_EQ(std::string(result.message()), "SyntaxError: syntax error near `(\"12\")'"); + EXPECT_EQ(std::string(result.message()), "SyntaxError: syntax error near `)(\"12\")'"); } { std::string query = "YIELD (SET)12"; auto result = checkResult(query); - EXPECT_EQ(std::string(result.message()), "SyntaxError: syntax error near `SET'"); + EXPECT_EQ(std::string(result.message()), "SyntaxError: syntax error near `)12'"); } { std::string query = "YIELD (PATH)true"; diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 3a5aab1f84b..04f1bf1699d 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -201,6 +201,7 @@ static constexpr size_t kCommentLengthLimit = 256; %token KW_SESSIONS KW_SESSION %token KW_KILL KW_QUERY KW_QUERIES KW_TOP %token KW_GEOGRAPHY KW_POINT KW_LINESTRING KW_POLYGON +%token KW_LIST KW_MAP /* symbols */ %token L_PAREN R_PAREN L_BRACKET R_BRACKET L_BRACE R_BRACE COMMA @@ -293,8 +294,8 @@ static constexpr size_t kCommentLengthLimit = 256; %type in_bound_clause %type out_bound_clause %type both_in_out_clause -%type expression_list -%type map_item_list +%type expression_list opt_expression_list +%type map_item_list opt_map_item_list %type when_then_list %type case_condition %type case_default @@ -1191,12 +1192,27 @@ list_expression : L_BRACKET expression_list R_BRACKET { $$ = ListExpression::make(qctx->objPool(), $2); } + | KW_LIST L_BRACKET opt_expression_list R_BRACKET { + $$ = ListExpression::make(qctx->objPool(), $3); + } ; set_expression : L_BRACE expression_list R_BRACE { $$ = SetExpression::make(qctx->objPool(), $2); } + | KW_SET L_BRACE opt_expression_list R_BRACE { + $$ = SetExpression::make(qctx->objPool(), $3); + } + ; + +opt_expression_list + : %empty { + $$ = ExpressionList::make(qctx->objPool()); + } + | expression_list { + $$ = $1; + } ; expression_list @@ -1214,6 +1230,18 @@ map_expression : L_BRACE map_item_list R_BRACE { $$ = MapExpression::make(qctx->objPool(), $2); } + | KW_MAP L_BRACE opt_map_item_list R_BRACE { + $$ = MapExpression::make(qctx->objPool(), $3); + } + ; + +opt_map_item_list + : %empty { + $$ = MapItemList::make(qctx->objPool()); + } + | map_item_list { + $$ = $1; + } ; map_item_list diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index 87be1d017eb..007a1f93153 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -56,6 +56,8 @@ IP_OCTET ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) "XOR" { return TokenType::KW_XOR; } "USE" { return TokenType::KW_USE; } "SET" { return TokenType::KW_SET; } +"LIST" { return TokenType::KW_LIST; } +"MAP" { return TokenType::KW_MAP; } "FROM" { return TokenType::KW_FROM; } "WHERE" { return TokenType::KW_WHERE; } "MATCH" { return TokenType::KW_MATCH; } @@ -261,7 +263,6 @@ IP_OCTET ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) "POINT" { return TokenType::KW_POINT; } "LINESTRING" { return TokenType::KW_LINESTRING; } "POLYGON" { return TokenType::KW_POLYGON; } - "TRUE" { yylval->boolval = true; return TokenType::BOOL; } "FALSE" { yylval->boolval = false; return TokenType::BOOL; } diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index f1249614e67..940686fe6b7 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -2693,7 +2693,7 @@ TEST_F(ParserTest, MatchMultipleTags) { TEST_F(ParserTest, MatchListSubscriptRange) { { - std::string query = "WITH [0, 1, 2] AS list RETURN list[0..] AS l"; + std::string query = "WITH [0, 1, 2] AS l RETURN l[0..] AS l2"; auto result = parse(query); ASSERT_TRUE(result.ok()) << result.status(); } diff --git a/tests/tck/features/basic/data.feature b/tests/tck/features/basic/data.feature new file mode 100644 index 00000000000..2268c63de3e --- /dev/null +++ b/tests/tck/features/basic/data.feature @@ -0,0 +1,53 @@ +Feature: data + + Background: Prepare space + Given a graph with space named "nba" + + Scenario: list, set, map + When executing query: + """ + RETURN size(LIST[]) AS a, size(SET{}) AS b, size(MAP{}) AS c + """ + Then the result should be, in any order, with relax comparison: + | a | b | c | + | 0 | 0 | 0 | + When executing query: + """ + RETURN 1 IN LIST[] AS a, "Tony" IN SET{} AS b, "a" IN MAP{} AS c + """ + Then the result should be, in any order, with relax comparison: + | a | b | c | + | false | false | false | + When executing query: + """ + RETURN LIST[1, 2] AS a, SET{1, 2, 1} AS b, MAP{a:1, b:2} AS c, MAP{a: LIST[1,2], b: SET{1,2,1}, c: "hee"} AS d + """ + Then the result should be, in any order, with relax comparison: + | a | b | c | d | + | [1, 2] | {1, 2} | {a:1, b:2} | {a: [1, 2], b: {2, 1}, c: "hee"} | + When executing query: + """ + RETURN 1 IN LIST[1, 2] AS a, 2 IN SET{1, 2, 1} AS b, "a" IN MAP{a:1, b:2} AS c, MAP{a: LIST[1,2], b: SET{1,2,1}, c: "hee"}["b"] AS d + """ + Then the result should be, in any order, with relax comparison: + | a | b | c | d | + | true | true | true | {2, 1} | + When executing query: + """ + RETURN [], {}, {} + """ + Then a SyntaxError should be raised at runtime: + When executing query: + """ + RETURN [1, 2] AS a, {1, 2, 1} AS b, {a:1, b:2} AS c + """ + Then the result should be, in any order, with relax comparison: + | a | b | c | + | [1, 2] | {1, 2} | {a:1, b:2} | + When executing query: + """ + RETURN 2 IN [1, 2] AS a, 2 IN {1, 2, 1} AS b, "b" IN MAP{a:1, b:2} AS c + """ + Then the result should be, in any order, with relax comparison: + | a | b | c | + | true | true | true | diff --git a/tests/tck/features/expression/ListComprehension.feature b/tests/tck/features/expression/ListComprehension.feature index 3c77be7855f..8dd7a623f91 100644 --- a/tests/tck/features/expression/ListComprehension.feature +++ b/tests/tck/features/expression/ListComprehension.feature @@ -100,8 +100,8 @@ Feature: ListComprehension When executing query: """ UNWIND [1, 2, 3, 4, 5] AS a RETURN a * 2 AS x - | RETURN [n in collect($-.x) WHERE n > 5 | n + 1] AS list + | RETURN [n in collect($-.x) WHERE n > 5 | n + 1] AS l """ Then the result should be, in any order: - | list | + | l | | [7, 9, 11] | diff --git a/tests/tck/openCypher/features/expressions/list/List2.feature b/tests/tck/openCypher/features/expressions/list/List2.feature index 4f5949242ed..9297968769d 100644 --- a/tests/tck/openCypher/features/expressions/list/List2.feature +++ b/tests/tck/openCypher/features/expressions/list/List2.feature @@ -37,8 +37,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3, 4, 5] AS list - RETURN list[1..3] AS r + WITH [1, 2, 3, 4, 5] AS l + RETURN l[1..3] AS r """ Then the result should be, in any order: | r | @@ -49,8 +49,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[1..] AS r + WITH [1, 2, 3] AS l + RETURN l[1..] AS r """ Then the result should be, in any order: | r | @@ -61,8 +61,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[..2] AS r + WITH [1, 2, 3] AS l + RETURN l[..2] AS r """ Then the result should be, in any order: | r | @@ -73,8 +73,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[0..1] AS r + WITH [1, 2, 3] AS l + RETURN l[0..1] AS r """ Then the result should be, in any order: | r | @@ -85,8 +85,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[0..0] AS r + WITH [1, 2, 3] AS l + RETURN l[0..0] AS r """ Then the result should be, in any order: | r | @@ -97,8 +97,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[-3..-1] AS r + WITH [1, 2, 3] AS l + RETURN l[-3..-1] AS r """ Then the result should be, in any order: | r | @@ -109,8 +109,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[3..1] AS r + WITH [1, 2, 3] AS l + RETURN l[3..1] AS r """ Then the result should be, in any order: | r | @@ -121,8 +121,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[-5..5] AS r + WITH [1, 2, 3] AS l + RETURN l[-5..5] AS r """ Then the result should be, in any order: | r | @@ -133,8 +133,8 @@ Feature: List2 - List Slicing Given any graph When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[..] AS r + WITH [1, 2, 3] AS l + RETURN l[..] AS r """ Then the result should be, in any order: | r | @@ -157,8 +157,8 @@ Feature: List2 - List Slicing | to | 3 | When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[$from..$to] AS r + WITH [1, 2, 3] AS l + RETURN l[$from..$to] AS r """ Then the result should be, in any order: | r | @@ -173,8 +173,8 @@ Feature: List2 - List Slicing | to | 1 | When executing query: """ - WITH [1, 2, 3] AS list - RETURN list[$from..$to] AS r + WITH [1, 2, 3] AS l + RETURN l[$from..$to] AS r """ Then the result should be, in any order: | r | diff --git a/tests/tck/openCypher/features/expressions/map/Map1.feature b/tests/tck/openCypher/features/expressions/map/Map1.feature index 3ca97a3271a..9d4e04edb0c 100644 --- a/tests/tck/openCypher/features/expressions/map/Map1.feature +++ b/tests/tck/openCypher/features/expressions/map/Map1.feature @@ -10,21 +10,21 @@ Feature: Map1 - Static value access Scenario: [1] Statically access field of a map resulting from an expression When executing query: """ - WITH [{num: 0}, 1] AS list - RETURN (list[0]).num + WITH [{num: 0}, 1] AS l + RETURN (l[0]).num """ Then the result should be, in any order: - | list[0].num | - | 0 | + | l[0].num | + | 0 | @uncompatible Scenario: [2] Fail when performing property access on a non-map # openCypher return : TypeError should be raised at runtime: PropertyAccessOnNonMap When executing query: """ - WITH [{num: 0}, 1] AS list - RETURN (list[1]).num + WITH [{num: 0}, 1] AS l + RETURN (l[1]).num """ Then the result should be, in any order: - | list[1].num | - | BAD_TYPE | + | l[1].num | + | BAD_TYPE |