From f9775e14b00768ec713d05849f1669e1ead693e1 Mon Sep 17 00:00:00 2001 From: PragmaTwice Date: Sat, 23 Mar 2024 13:56:29 +0900 Subject: [PATCH 1/3] Add Redis query parser via PEGTL for search module --- src/search/common_parser.h | 56 +++++++++++++++ src/search/common_transformer.h | 91 ++++++++++++++++++++++++ src/search/redis_query_parser.h | 65 +++++++++++++++++ src/search/redis_query_transformer.h | 70 ++++++++++++++++++ src/search/sql_parser.h | 36 ++-------- src/search/sql_transformer.h | 68 ++---------------- tests/cppunit/redis_query_parser_test.cc | 42 +++++++++++ tests/cppunit/sql_parser_test.cc | 1 - 8 files changed, 336 insertions(+), 93 deletions(-) create mode 100644 src/search/common_parser.h create mode 100644 src/search/common_transformer.h create mode 100644 src/search/redis_query_parser.h create mode 100644 src/search/redis_query_transformer.h create mode 100644 tests/cppunit/redis_query_parser_test.cc diff --git a/src/search/common_parser.h b/src/search/common_parser.h new file mode 100644 index 00000000000..15f5bc36aff --- /dev/null +++ b/src/search/common_parser.h @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#pragma once + +#include + +namespace kqir { + +namespace peg = tao::pegtl; + +struct True : peg::string<'t', 'r', 'u', 'e'> {}; +struct False : peg::string<'f', 'a', 'l', 's', 'e'> {}; +struct Boolean : peg::sor {}; + +struct Digits : peg::plus {}; +struct NumberExp : peg::seq, peg::opt>, Digits> {}; +struct NumberFrac : peg::seq, Digits> {}; +struct Number : peg::seq>, Digits, peg::opt, peg::opt> {}; + +struct UnicodeXDigit : peg::list, peg::rep<4, peg::xdigit>>, peg::one<'\\'>> {}; +struct EscapedSingleChar : peg::one<'"', '\\', 'b', 'f', 'n', 'r', 't'> {}; +struct EscapedChar : peg::sor {}; +struct UnescapedChar : peg::utf8::range<0x20, 0x10FFFF> {}; +struct Char : peg::if_then_else, EscapedChar, UnescapedChar> {}; + +struct StringContent : peg::until>, Char> {}; +struct String : peg::seq, StringContent, peg::any> {}; + +struct Identifier : peg::identifier {}; + +struct WhiteSpace : peg::one<' ', '\t', '\n', '\r'> {}; +template +struct WSPad : peg::pad {}; + +struct UnsignedInteger : Digits {}; +struct Integer : peg::seq>, Digits> {}; + +} // namespace kqir diff --git a/src/search/common_transformer.h b/src/search/common_transformer.h new file mode 100644 index 00000000000..8febbb4ce73 --- /dev/null +++ b/src/search/common_transformer.h @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#pragma once + +#include +#include +#include + +#include "common_parser.h" +#include "status.h" + +namespace kqir { + +struct TreeTransformer { + using TreeNode = std::unique_ptr; + + template + static bool Is(const TreeNode& node) { + return node->type == peg::demangle(); + } + + static bool IsRoot(const TreeNode& node) { return node->type.empty(); } + + static StatusOr UnescapeString(std::string_view str) { + str = str.substr(1, str.size() - 2); + + std::string result; + while (!str.empty()) { + if (str[0] == '\\') { + str.remove_prefix(1); + switch (str[0]) { + case '\\': + case '"': + result.push_back(str[0]); + break; + case 'b': + result.push_back('\b'); + break; + case 'f': + result.push_back('\f'); + break; + case 'n': + result.push_back('\n'); + break; + case 'r': + result.push_back('\r'); + break; + case 't': + result.push_back('\t'); + break; + case 'u': + if (!peg::unescape::utf8_append_utf32( + result, peg::unescape::unhex_string(str.data() + 1, str.data() + 5))) { + return {Status::NotOK, + fmt::format("invalid Unicode code point '{}' in string literal", std::string(str.data() + 1, 4))}; + } + str.remove_prefix(4); + break; + default: + __builtin_unreachable(); + }; + str.remove_prefix(1); + } else { + result.push_back(str[0]); + str.remove_prefix(1); + } + } + + return result; + } +}; + +} // namespace kqir diff --git a/src/search/redis_query_parser.h b/src/search/redis_query_parser.h new file mode 100644 index 00000000000..07eac327e5d --- /dev/null +++ b/src/search/redis_query_parser.h @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#pragma once + +#include + +#include "common_parser.h" + +namespace kqir { + +namespace redis_query { + +using namespace peg; + +struct Field : seq, Identifier> {}; + +struct Tag : sor {}; +struct TagList : seq, WSPad, star, WSPad>>, one<'}'>> {}; + +struct Inf : seq>, string<'i', 'n', 'f'>> {}; +struct ExclusiveNumber: seq, Number> {}; +struct NumericRangePart : sor {}; +struct NumericRange : seq, WSPad, WSPad, one<']'>> {}; + +struct FieldQuery : seq, one<':'>, WSPad>> {}; + +struct QueryExpr; + +struct ParenExpr : WSPad, QueryExpr, one<')'>>> {}; + +struct NotExpr; + +struct BooleanExpr : sor {}; + +struct NotExpr : seq>, BooleanExpr> {}; + +struct AndExpr : seq> {}; +struct AndExprP : sor {}; + +struct OrExpr : seq, AndExprP>>> {}; +struct OrExprP : sor {}; + +struct QueryExpr : seq {}; + +} // namespace redis_query + +} // namespace kqir diff --git a/src/search/redis_query_transformer.h b/src/search/redis_query_transformer.h new file mode 100644 index 00000000000..7411f7fb960 --- /dev/null +++ b/src/search/redis_query_transformer.h @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#pragma once + +#include "common_transformer.h" +#include "ir.h" +#include "parse_util.h" +#include "redis_query_parser.h" + +namespace kqir { + +namespace redis_query { + +namespace ir = kqir; + +template +using TreeSelector = parse_tree::selector< + Rule, + parse_tree::store_content::on, + parse_tree::remove_content::on>; + +template +StatusOr> ParseToTree(Input&& in) { + if (auto root = parse_tree::parse, TreeSelector>(std::forward(in))) { + return root; + } else { + // TODO: improve parse error message, with source location + return {Status::NotOK, "invalid syntax"}; + } +} + +struct Transformer : ir::TreeTransformer { + static auto Transform(const TreeNode& node) -> StatusOr> { + if (Is(node)) { + return Node::Create(*ParseFloat(node->string())); + } else if (Is(node)) { + + } else { + // UNREACHABLE CODE, just for debugging here + return {Status::NotOK, fmt::format("encountered invalid node type: {}", node->type)}; + } + } +}; + +template +StatusOr> ParseToIR(Input&& in) { + return Transformer::Transform(GET_OR_RET(ParseToTree(std::forward(in)))); +} + +} // namespace redis_query + +} // namespace kqir diff --git a/src/search/sql_parser.h b/src/search/sql_parser.h index dffe051d132..981ea0accde 100644 --- a/src/search/sql_parser.h +++ b/src/search/sql_parser.h @@ -18,40 +18,18 @@ * */ +#pragma once + #include -namespace kqir { +#include "common_parser.h" -namespace peg = tao::pegtl; +namespace kqir { namespace sql { using namespace peg; -struct True : string<'t', 'r', 'u', 'e'> {}; -struct False : string<'f', 'a', 'l', 's', 'e'> {}; -struct Boolean : sor {}; - -struct Digits : plus {}; -struct NumberExp : seq, opt>, Digits> {}; -struct NumberFrac : seq, Digits> {}; -struct Number : seq>, Digits, opt, opt> {}; - -struct UnicodeXDigit : list, rep<4, xdigit>>, one<'\\'>> {}; -struct EscapedSingleChar : one<'"', '\\', 'b', 'f', 'n', 'r', 't'> {}; -struct EscapedChar : sor {}; -struct UnescapedChar : utf8::range<0x20, 0x10FFFF> {}; -struct Char : if_then_else, EscapedChar, UnescapedChar> {}; - -struct StringContent : until>, Char> {}; -struct String : seq, StringContent, any> {}; - -struct Identifier : identifier {}; - -struct WhiteSpace : one<' ', '\t', '\n', '\r'> {}; -template -struct WSPad : pad {}; - struct HasTag : string<'h', 'a', 's', 't', 'a', 'g'> {}; struct HasTagExpr : WSPad, String>> {}; @@ -74,7 +52,7 @@ struct NotExpr : seq, BooleanExpr> {}; struct And : string<'a', 'n', 'd'> {}; // left recursion elimination -// struct AndExpr : sor, NotExpr> {}; +// struct AndExpr : sor, BooleanExpr> {}; struct AndExpr : seq>> {}; struct AndExprP : sor {}; @@ -100,12 +78,10 @@ struct Asc : string<'a', 's', 'c'> {}; struct Desc : string<'d', 'e', 's', 'c'> {}; struct Limit : string<'l', 'i', 'm', 'i', 't'> {}; -struct Integer : Digits {}; - struct WhereClause : seq {}; struct AscOrDesc : WSPad> {}; struct OrderByClause : seq, opt> {}; -struct LimitClause : seq, one<','>>>, WSPad> {}; +struct LimitClause : seq, one<','>>>, WSPad> {}; struct SearchStmt : WSPad, opt, opt>> {}; diff --git a/src/search/sql_transformer.h b/src/search/sql_transformer.h index 76fac35ce7b..5c300654c28 100644 --- a/src/search/sql_transformer.h +++ b/src/search/sql_transformer.h @@ -18,13 +18,13 @@ * */ +#pragma once + #include #include -#include -#include -#include #include +#include "common_transformer.h" #include "ir.h" #include "parse_util.h" #include "sql_parser.h" @@ -37,7 +37,8 @@ namespace ir = kqir; template using TreeSelector = parse_tree::selector< - Rule, parse_tree::store_content::on, + Rule, + parse_tree::store_content::on, parse_tree::remove_content::on>; @@ -51,64 +52,7 @@ StatusOr> ParseToTree(Input&& in) { } } -struct Transformer { - using TreeNode = std::unique_ptr; - - template - static bool Is(const TreeNode& node) { - return node->type == demangle(); - } - - static bool IsRoot(const TreeNode& node) { return node->type.empty(); } - - static StatusOr UnescapeString(std::string_view str) { - str = str.substr(1, str.size() - 2); - - std::string result; - while (!str.empty()) { - if (str[0] == '\\') { - str.remove_prefix(1); - switch (str[0]) { - case '\\': - case '"': - result.push_back(str[0]); - break; - case 'b': - result.push_back('\b'); - break; - case 'f': - result.push_back('\f'); - break; - case 'n': - result.push_back('\n'); - break; - case 'r': - result.push_back('\r'); - break; - case 't': - result.push_back('\t'); - break; - case 'u': - if (!unescape::utf8_append_utf32(result, - unescape::unhex_string(str.data() + 1, str.data() + 5))) { - return {Status::NotOK, - fmt::format("invalid Unicode code point '{}' in string literal", std::string(str.data() + 1, 4))}; - } - str.remove_prefix(4); - break; - default: - __builtin_unreachable(); - }; - str.remove_prefix(1); - } else { - result.push_back(str[0]); - str.remove_prefix(1); - } - } - - return result; - } - +struct Transformer : ir::TreeTransformer { static auto Transform(const TreeNode& node) -> StatusOr> { if (Is(node)) { return Node::Create(node->string_view() == "true"); diff --git a/tests/cppunit/redis_query_parser_test.cc b/tests/cppunit/redis_query_parser_test.cc new file mode 100644 index 00000000000..d2c7fc378d5 --- /dev/null +++ b/tests/cppunit/redis_query_parser_test.cc @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include +#include + +#include "tao/pegtl/contrib/parse_tree_to_dot.hpp" +#include "tao/pegtl/string_input.hpp" + +using namespace kqir::redis_query; + +static auto Parse(const std::string& in) { return ParseToTree(string_input(in, "test")); } + +#define AssertSyntaxError(node) ASSERT_EQ(node.Msg(), "invalid syntax"); // NOLINT + +// NOLINTNEXTLINE +#define AssertIR(node, val) \ + ASSERT_EQ(node.Msg(), Status::ok_msg); \ + ASSERT_EQ(node.GetValue()->Dump(), val); + +TEST(RedisQueryParserTest, Simple) { + if (auto root = Parse("@a:{ hello | hi } @b:[1 2] | @c:[(3 +inf]")) { + parse_tree::print_dot(std::cout, **root); + } +} diff --git a/tests/cppunit/sql_parser_test.cc b/tests/cppunit/sql_parser_test.cc index 3a99746d0f8..34fd0f3c307 100644 --- a/tests/cppunit/sql_parser_test.cc +++ b/tests/cppunit/sql_parser_test.cc @@ -21,7 +21,6 @@ #include #include -#include "tao/pegtl/contrib/parse_tree_to_dot.hpp" #include "tao/pegtl/string_input.hpp" using namespace kqir::sql; From 7bd32f2af86143d65fe8c4dea0e1d739dc53c182 Mon Sep 17 00:00:00 2001 From: PragmaTwice Date: Sat, 23 Mar 2024 15:06:51 +0900 Subject: [PATCH 2/3] finish transformer & add tests --- src/search/redis_query_parser.h | 2 +- src/search/redis_query_transformer.h | 94 +++++++++++++++++++++++- tests/cppunit/redis_query_parser_test.cc | 57 ++++++++++++-- 3 files changed, 143 insertions(+), 10 deletions(-) diff --git a/src/search/redis_query_parser.h b/src/search/redis_query_parser.h index 07eac327e5d..43e525d8a8a 100644 --- a/src/search/redis_query_parser.h +++ b/src/search/redis_query_parser.h @@ -36,7 +36,7 @@ struct Tag : sor {}; struct TagList : seq, WSPad, star, WSPad>>, one<'}'>> {}; struct Inf : seq>, string<'i', 'n', 'f'>> {}; -struct ExclusiveNumber: seq, Number> {}; +struct ExclusiveNumber : seq, Number> {}; struct NumericRangePart : sor {}; struct NumericRange : seq, WSPad, WSPad, one<']'>> {}; diff --git a/src/search/redis_query_transformer.h b/src/search/redis_query_transformer.h index 7411f7fb960..1409544b2c9 100644 --- a/src/search/redis_query_transformer.h +++ b/src/search/redis_query_transformer.h @@ -20,10 +20,13 @@ #pragma once +#include + #include "common_transformer.h" #include "ir.h" #include "parse_util.h" #include "redis_query_parser.h" +#include "search/common_parser.h" namespace kqir { @@ -33,8 +36,7 @@ namespace ir = kqir; template using TreeSelector = parse_tree::selector< - Rule, - parse_tree::store_content::on, + Rule, parse_tree::store_content::on, parse_tree::remove_content::on>; template @@ -51,8 +53,92 @@ struct Transformer : ir::TreeTransformer { static auto Transform(const TreeNode& node) -> StatusOr> { if (Is(node)) { return Node::Create(*ParseFloat(node->string())); - } else if (Is(node)) { - + } else if (Is(node)) { + CHECK(node->children.size() == 2); + + auto field = node->children[0]->string(); + const auto& query = node->children[1]; + + if (Is(query)) { + std::vector> exprs; + + for (const auto& tag : query->children) { + auto tag_str = Is(tag) ? tag->string() : GET_OR_RET(UnescapeString(tag->string())); + exprs.push_back(std::make_unique(std::make_unique(field), + std::make_unique(tag_str))); + } + + if (exprs.size() == 1) { + return std::move(exprs[0]); + } else { + return std::make_unique(std::move(exprs)); + } + } else { // NumericRange + std::vector> exprs; + + const auto& lhs = query->children[0]; + const auto& rhs = query->children[1]; + + if (Is(lhs)) { + exprs.push_back( + std::make_unique(NumericCompareExpr::GT, std::make_unique(field), + Node::As(GET_OR_RET(Transform(lhs->children[0]))))); + } else if (Is(lhs)) { + exprs.push_back(std::make_unique(NumericCompareExpr::GET, + std::make_unique(field), + Node::As(GET_OR_RET(Transform(lhs))))); + } else { // Inf + if (lhs->string_view() == "+inf") { + return {Status::NotOK, "it's not allowed to set the lower bound as positive infinity"}; + } + } + + if (Is(rhs)) { + exprs.push_back( + std::make_unique(NumericCompareExpr::LT, std::make_unique(field), + Node::As(GET_OR_RET(Transform(rhs->children[0]))))); + } else if (Is(rhs)) { + exprs.push_back(std::make_unique(NumericCompareExpr::LET, + std::make_unique(field), + Node::As(GET_OR_RET(Transform(rhs))))); + } else { // Inf + if (rhs->string_view() == "-inf") { + return {Status::NotOK, "it's not allowed to set the upper bound as negative infinity"}; + } + } + + if (exprs.empty()) { + return std::make_unique(true); + } else if (exprs.size() == 1) { + return std::move(exprs[0]); + } else { + return std::make_unique(std::move(exprs)); + } + } + } else if (Is(node)) { + CHECK(node->children.size() == 1); + + return Node::Create(Node::As(GET_OR_RET(Transform(node->children[0])))); + } else if (Is(node)) { + std::vector> exprs; + + for (const auto& child : node->children) { + exprs.push_back(Node::As(GET_OR_RET(Transform(child)))); + } + + return Node::Create(std::move(exprs)); + } else if (Is(node)) { + std::vector> exprs; + + for (const auto& child : node->children) { + exprs.push_back(Node::As(GET_OR_RET(Transform(child)))); + } + + return Node::Create(std::move(exprs)); + } else if (IsRoot(node)) { + CHECK(node->children.size() == 1); + + return Transform(node->children[0]); } else { // UNREACHABLE CODE, just for debugging here return {Status::NotOK, fmt::format("encountered invalid node type: {}", node->type)}; diff --git a/tests/cppunit/redis_query_parser_test.cc b/tests/cppunit/redis_query_parser_test.cc index d2c7fc378d5..1c794ffedcc 100644 --- a/tests/cppunit/redis_query_parser_test.cc +++ b/tests/cppunit/redis_query_parser_test.cc @@ -21,12 +21,11 @@ #include #include -#include "tao/pegtl/contrib/parse_tree_to_dot.hpp" #include "tao/pegtl/string_input.hpp" using namespace kqir::redis_query; -static auto Parse(const std::string& in) { return ParseToTree(string_input(in, "test")); } +static auto Parse(const std::string& in) { return ParseToIR(string_input(in, "test")); } #define AssertSyntaxError(node) ASSERT_EQ(node.Msg(), "invalid syntax"); // NOLINT @@ -36,7 +35,55 @@ static auto Parse(const std::string& in) { return ParseToTree(string_input(in, " ASSERT_EQ(node.GetValue()->Dump(), val); TEST(RedisQueryParserTest, Simple) { - if (auto root = Parse("@a:{ hello | hi } @b:[1 2] | @c:[(3 +inf]")) { - parse_tree::print_dot(std::cout, **root); - } + AssertSyntaxError(Parse("")); + AssertSyntaxError(Parse("a")); + AssertSyntaxError(Parse("@a")); + AssertSyntaxError(Parse("a:")); + AssertSyntaxError(Parse("@a:")); + AssertSyntaxError(Parse("@a:[]")); + AssertSyntaxError(Parse("@a:[1 2")); + AssertSyntaxError(Parse("@a:[(inf 1]")); + AssertSyntaxError(Parse("@a:[((1 2]")); + AssertSyntaxError(Parse("@a:[1]")); + AssertSyntaxError(Parse("@a:[1 2 3]")); + AssertSyntaxError(Parse("@a:{}")); + AssertSyntaxError(Parse("@a:{x")); + AssertSyntaxError(Parse("@a:{|}")); + AssertSyntaxError(Parse("@a:{x|}")); + AssertSyntaxError(Parse("@a:{|y}")); + AssertSyntaxError(Parse("@a:{x|y|}")); + AssertSyntaxError(Parse("@a:{x}|")); + AssertSyntaxError(Parse("@a:{x} -")); + AssertSyntaxError(Parse("@a:{x}|@a:{x}|")); + + AssertIR(Parse("@a:[1 2]"), "(and a >= 1, a <= 2)"); + AssertIR(Parse("@a : [1 2]"), "(and a >= 1, a <= 2)"); + AssertIR(Parse("@a:[(1 2]"), "(and a > 1, a <= 2)"); + AssertIR(Parse("@a:[1 (2]"), "(and a >= 1, a < 2)"); + AssertIR(Parse("@a:[(1 (2]"), "(and a > 1, a < 2)"); + AssertIR(Parse("@a:[inf 2]"), "a <= 2"); + AssertIR(Parse("@a:[-inf 2]"), "a <= 2"); + AssertIR(Parse("@a:[1 inf]"), "a >= 1"); + AssertIR(Parse("@a:[1 +inf]"), "a >= 1"); + AssertIR(Parse("@a:[(1 +inf]"), "a > 1"); + AssertIR(Parse("@a:[-inf +inf]"), "true"); + AssertIR(Parse("@a:{x}"), "a hastag \"x\""); + AssertIR(Parse("@a:{x|y}"), R"((or a hastag "x", a hastag "y"))"); + AssertIR(Parse("@a:{x|y|z}"), R"((or a hastag "x", a hastag "y", a hastag "z"))"); + AssertIR(Parse(R"(@a:{"x"|y})"), R"((or a hastag "x", a hastag "y"))"); + AssertIR(Parse(R"(@a:{"x" | "y"})"), R"((or a hastag "x", a hastag "y"))"); + AssertIR(Parse("@a:{x} @b:[1 inf]"), "(and a hastag \"x\", b >= 1)"); + AssertIR(Parse("@a:{x} | @b:[1 inf]"), "(or a hastag \"x\", b >= 1)"); + AssertIR(Parse("@a:{x} @b:[1 inf] @c:{y}"), "(and a hastag \"x\", b >= 1, c hastag \"y\")"); + AssertIR(Parse("@a:{x}|@b:[1 inf] | @c:{y}"), "(or a hastag \"x\", b >= 1, c hastag \"y\")"); + AssertIR(Parse("@a:[1 inf] @b:[inf 2]| @c:[(3 inf]"), "(or (and a >= 1, b <= 2), c > 3)"); + AssertIR(Parse("@a:[1 inf] | @b:[inf 2] @c:[(3 inf]"), "(or a >= 1, (and b <= 2, c > 3))"); + AssertIR(Parse("(@a:[1 inf] @b:[inf 2])| @c:[(3 inf]"), "(or (and a >= 1, b <= 2), c > 3)"); + AssertIR(Parse("@a:[1 inf] | (@b:[inf 2] @c:[(3 inf])"), "(or a >= 1, (and b <= 2, c > 3))"); + AssertIR(Parse("@a:[1 inf] (@b:[inf 2]| @c:[(3 inf])"), "(and a >= 1, (or b <= 2, c > 3))"); + AssertIR(Parse("(@a:[1 inf] | @b:[inf 2]) @c:[(3 inf]"), "(and (or a >= 1, b <= 2), c > 3)"); + AssertIR(Parse("-@a:{x}"), "not a hastag \"x\""); + AssertIR(Parse("-@a:[(1 +inf]"), "not a > 1"); + AssertIR(Parse("-@a:[1 inf] @b:[inf 2]| -@c:[(3 inf]"), "(or (and not a >= 1, b <= 2), not c > 3)"); + AssertIR(Parse("@a:[1 inf] -(@b:[inf 2]| @c:[(3 inf])"), "(and a >= 1, not (or b <= 2, c > 3))"); } From 532bb535444b4877126a474c78ad66b15c86cad4 Mon Sep 17 00:00:00 2001 From: PragmaTwice Date: Sat, 23 Mar 2024 16:39:12 +0900 Subject: [PATCH 3/3] fix --- src/search/redis_query_parser.h | 6 ++++-- src/search/redis_query_transformer.h | 11 +++++++---- tests/cppunit/redis_query_parser_test.cc | 3 +++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/search/redis_query_parser.h b/src/search/redis_query_parser.h index 43e525d8a8a..fd373830f32 100644 --- a/src/search/redis_query_parser.h +++ b/src/search/redis_query_parser.h @@ -42,17 +42,19 @@ struct NumericRange : seq, WSPad, WSPad, one<':'>, WSPad>> {}; +struct Wildcard : one<'*'> {}; + struct QueryExpr; struct ParenExpr : WSPad, QueryExpr, one<')'>>> {}; struct NotExpr; -struct BooleanExpr : sor {}; +struct BooleanExpr : sor> {}; struct NotExpr : seq>, BooleanExpr> {}; -struct AndExpr : seq> {}; +struct AndExpr : seq>> {}; struct AndExprP : sor {}; struct OrExpr : seq, AndExprP>>> {}; diff --git a/src/search/redis_query_transformer.h b/src/search/redis_query_transformer.h index 1409544b2c9..ebd051430c3 100644 --- a/src/search/redis_query_transformer.h +++ b/src/search/redis_query_transformer.h @@ -35,9 +35,10 @@ namespace redis_query { namespace ir = kqir; template -using TreeSelector = parse_tree::selector< - Rule, parse_tree::store_content::on, - parse_tree::remove_content::on>; +using TreeSelector = + parse_tree::selector, + parse_tree::remove_content::on>; template StatusOr> ParseToTree(Input&& in) { @@ -53,6 +54,8 @@ struct Transformer : ir::TreeTransformer { static auto Transform(const TreeNode& node) -> StatusOr> { if (Is(node)) { return Node::Create(*ParseFloat(node->string())); + } else if (Is(node)) { + return Node::Create(true); } else if (Is(node)) { CHECK(node->children.size() == 2); @@ -143,7 +146,7 @@ struct Transformer : ir::TreeTransformer { // UNREACHABLE CODE, just for debugging here return {Status::NotOK, fmt::format("encountered invalid node type: {}", node->type)}; } - } + } // NOLINT }; template diff --git a/tests/cppunit/redis_query_parser_test.cc b/tests/cppunit/redis_query_parser_test.cc index 1c794ffedcc..a31051a58bf 100644 --- a/tests/cppunit/redis_query_parser_test.cc +++ b/tests/cppunit/redis_query_parser_test.cc @@ -86,4 +86,7 @@ TEST(RedisQueryParserTest, Simple) { AssertIR(Parse("-@a:[(1 +inf]"), "not a > 1"); AssertIR(Parse("-@a:[1 inf] @b:[inf 2]| -@c:[(3 inf]"), "(or (and not a >= 1, b <= 2), not c > 3)"); AssertIR(Parse("@a:[1 inf] -(@b:[inf 2]| @c:[(3 inf])"), "(and a >= 1, not (or b <= 2, c > 3))"); + AssertIR(Parse("*"), "true"); + AssertIR(Parse("* *"), "(and true, true)"); + AssertIR(Parse("*|*"), "(or true, true)"); }