This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 03a332b6 Add Redis query parser via PEGTL for search module (#2192)
03a332b6 is described below

commit 03a332b621013cf1d1b8cdfa1f83af6406a3a192
Author: Twice <[email protected]>
AuthorDate: Sat Mar 23 17:49:21 2024 +0900

    Add Redis query parser via PEGTL for search module (#2192)
---
 src/search/common_parser.h               |  56 +++++++++++
 src/search/common_transformer.h          |  91 ++++++++++++++++++
 src/search/redis_query_parser.h          |  67 +++++++++++++
 src/search/redis_query_transformer.h     | 159 +++++++++++++++++++++++++++++++
 src/search/sql_parser.h                  |  36 ++-----
 src/search/sql_transformer.h             |  68 ++-----------
 tests/cppunit/redis_query_parser_test.cc |  92 ++++++++++++++++++
 tests/cppunit/sql_parser_test.cc         |   1 -
 8 files changed, 477 insertions(+), 93 deletions(-)

diff --git a/src/search/common_parser.h b/src/search/common_parser.h
new file mode 100644
index 00000000..15f5bc36
--- /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 <tao/pegtl.hpp>
+
+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<True, False> {};
+
+struct Digits : peg::plus<peg::digit> {};
+struct NumberExp : peg::seq<peg::one<'e', 'E'>, peg::opt<peg::one<'-', '+'>>, 
Digits> {};
+struct NumberFrac : peg::seq<peg::one<'.'>, Digits> {};
+struct Number : peg::seq<peg::opt<peg::one<'-'>>, Digits, 
peg::opt<NumberFrac>, peg::opt<NumberExp>> {};
+
+struct UnicodeXDigit : peg::list<peg::seq<peg::one<'u'>, peg::rep<4, 
peg::xdigit>>, peg::one<'\\'>> {};
+struct EscapedSingleChar : peg::one<'"', '\\', 'b', 'f', 'n', 'r', 't'> {};
+struct EscapedChar : peg::sor<EscapedSingleChar, UnicodeXDigit> {};
+struct UnescapedChar : peg::utf8::range<0x20, 0x10FFFF> {};
+struct Char : peg::if_then_else<peg::one<'\\'>, EscapedChar, UnescapedChar> {};
+
+struct StringContent : peg::until<peg::at<peg::one<'"'>>, Char> {};
+struct String : peg::seq<peg::one<'"'>, StringContent, peg::any> {};
+
+struct Identifier : peg::identifier {};
+
+struct WhiteSpace : peg::one<' ', '\t', '\n', '\r'> {};
+template <typename T>
+struct WSPad : peg::pad<T, WhiteSpace> {};
+
+struct UnsignedInteger : Digits {};
+struct Integer : peg::seq<peg::opt<peg::one<'-'>>, Digits> {};
+
+}  // namespace kqir
diff --git a/src/search/common_transformer.h b/src/search/common_transformer.h
new file mode 100644
index 00000000..8febbb4c
--- /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 <tao/pegtl/contrib/parse_tree.hpp>
+#include <tao/pegtl/contrib/unescape.hpp>
+#include <tao/pegtl/demangle.hpp>
+
+#include "common_parser.h"
+#include "status.h"
+
+namespace kqir {
+
+struct TreeTransformer {
+  using TreeNode = std::unique_ptr<peg::parse_tree::node>;
+
+  template <typename T>
+  static bool Is(const TreeNode& node) {
+    return node->type == peg::demangle<T>();
+  }
+
+  static bool IsRoot(const TreeNode& node) { return node->type.empty(); }
+
+  static StatusOr<std::string> 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<unsigned>(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 00000000..fd373830
--- /dev/null
+++ b/src/search/redis_query_parser.h
@@ -0,0 +1,67 @@
+/*
+ * 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 <tao/pegtl.hpp>
+
+#include "common_parser.h"
+
+namespace kqir {
+
+namespace redis_query {
+
+using namespace peg;
+
+struct Field : seq<one<'@'>, Identifier> {};
+
+struct Tag : sor<Identifier, String> {};
+struct TagList : seq<one<'{'>, WSPad<Tag>, star<seq<one<'|'>, WSPad<Tag>>>, 
one<'}'>> {};
+
+struct Inf : seq<opt<one<'+', '-'>>, string<'i', 'n', 'f'>> {};
+struct ExclusiveNumber : seq<one<'('>, Number> {};
+struct NumericRangePart : sor<Inf, ExclusiveNumber, Number> {};
+struct NumericRange : seq<one<'['>, WSPad<NumericRangePart>, 
WSPad<NumericRangePart>, one<']'>> {};
+
+struct FieldQuery : seq<WSPad<Field>, one<':'>, WSPad<sor<TagList, 
NumericRange>>> {};
+
+struct Wildcard : one<'*'> {};
+
+struct QueryExpr;
+
+struct ParenExpr : WSPad<seq<one<'('>, QueryExpr, one<')'>>> {};
+
+struct NotExpr;
+
+struct BooleanExpr : sor<FieldQuery, ParenExpr, NotExpr, WSPad<Wildcard>> {};
+
+struct NotExpr : seq<WSPad<one<'-'>>, BooleanExpr> {};
+
+struct AndExpr : seq<BooleanExpr, plus<seq<BooleanExpr>>> {};
+struct AndExprP : sor<AndExpr, BooleanExpr> {};
+
+struct OrExpr : seq<AndExprP, plus<seq<one<'|'>, AndExprP>>> {};
+struct OrExprP : sor<OrExpr, AndExprP> {};
+
+struct QueryExpr : seq<OrExprP> {};
+
+}  // 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 00000000..ebd05143
--- /dev/null
+++ b/src/search/redis_query_transformer.h
@@ -0,0 +1,159 @@
+/*
+ * 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 <memory>
+
+#include "common_transformer.h"
+#include "ir.h"
+#include "parse_util.h"
+#include "redis_query_parser.h"
+#include "search/common_parser.h"
+
+namespace kqir {
+
+namespace redis_query {
+
+namespace ir = kqir;
+
+template <typename Rule>
+using TreeSelector =
+    parse_tree::selector<Rule, parse_tree::store_content::on<Number, String, 
Identifier, Inf>,
+                         parse_tree::remove_content::on<TagList, NumericRange, 
ExclusiveNumber, FieldQuery, NotExpr,
+                                                        AndExpr, OrExpr, 
Wildcard>>;
+
+template <typename Input>
+StatusOr<std::unique_ptr<parse_tree::node>> ParseToTree(Input&& in) {
+  if (auto root = parse_tree::parse<seq<QueryExpr, eof>, 
TreeSelector>(std::forward<Input>(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<std::unique_ptr<Node>> {
+    if (Is<Number>(node)) {
+      return Node::Create<ir::NumericLiteral>(*ParseFloat(node->string()));
+    } else if (Is<Wildcard>(node)) {
+      return Node::Create<ir::BoolLiteral>(true);
+    } else if (Is<FieldQuery>(node)) {
+      CHECK(node->children.size() == 2);
+
+      auto field = node->children[0]->string();
+      const auto& query = node->children[1];
+
+      if (Is<TagList>(query)) {
+        std::vector<std::unique_ptr<ir::QueryExpr>> exprs;
+
+        for (const auto& tag : query->children) {
+          auto tag_str = Is<Identifier>(tag) ? tag->string() : 
GET_OR_RET(UnescapeString(tag->string()));
+          
exprs.push_back(std::make_unique<ir::TagContainExpr>(std::make_unique<FieldRef>(field),
+                                                               
std::make_unique<StringLiteral>(tag_str)));
+        }
+
+        if (exprs.size() == 1) {
+          return std::move(exprs[0]);
+        } else {
+          return std::make_unique<ir::OrExpr>(std::move(exprs));
+        }
+      } else {  // NumericRange
+        std::vector<std::unique_ptr<ir::QueryExpr>> exprs;
+
+        const auto& lhs = query->children[0];
+        const auto& rhs = query->children[1];
+
+        if (Is<ExclusiveNumber>(lhs)) {
+          exprs.push_back(
+              std::make_unique<NumericCompareExpr>(NumericCompareExpr::GT, 
std::make_unique<FieldRef>(field),
+                                                   
Node::As<NumericLiteral>(GET_OR_RET(Transform(lhs->children[0])))));
+        } else if (Is<Number>(lhs)) {
+          
exprs.push_back(std::make_unique<NumericCompareExpr>(NumericCompareExpr::GET,
+                                                               
std::make_unique<FieldRef>(field),
+                                                               
Node::As<NumericLiteral>(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<ExclusiveNumber>(rhs)) {
+          exprs.push_back(
+              std::make_unique<NumericCompareExpr>(NumericCompareExpr::LT, 
std::make_unique<FieldRef>(field),
+                                                   
Node::As<NumericLiteral>(GET_OR_RET(Transform(rhs->children[0])))));
+        } else if (Is<Number>(rhs)) {
+          
exprs.push_back(std::make_unique<NumericCompareExpr>(NumericCompareExpr::LET,
+                                                               
std::make_unique<FieldRef>(field),
+                                                               
Node::As<NumericLiteral>(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<BoolLiteral>(true);
+        } else if (exprs.size() == 1) {
+          return std::move(exprs[0]);
+        } else {
+          return std::make_unique<ir::AndExpr>(std::move(exprs));
+        }
+      }
+    } else if (Is<NotExpr>(node)) {
+      CHECK(node->children.size() == 1);
+
+      return 
Node::Create<ir::NotExpr>(Node::As<ir::QueryExpr>(GET_OR_RET(Transform(node->children[0]))));
+    } else if (Is<AndExpr>(node)) {
+      std::vector<std::unique_ptr<ir::QueryExpr>> exprs;
+
+      for (const auto& child : node->children) {
+        exprs.push_back(Node::As<ir::QueryExpr>(GET_OR_RET(Transform(child))));
+      }
+
+      return Node::Create<ir::AndExpr>(std::move(exprs));
+    } else if (Is<OrExpr>(node)) {
+      std::vector<std::unique_ptr<ir::QueryExpr>> exprs;
+
+      for (const auto& child : node->children) {
+        exprs.push_back(Node::As<ir::QueryExpr>(GET_OR_RET(Transform(child))));
+      }
+
+      return Node::Create<ir::OrExpr>(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)};
+    }
+  }  // NOLINT
+};
+
+template <typename Input>
+StatusOr<std::unique_ptr<ir::Node>> ParseToIR(Input&& in) {
+  return 
Transformer::Transform(GET_OR_RET(ParseToTree(std::forward<Input>(in))));
+}
+
+}  // namespace redis_query
+
+}  // namespace kqir
diff --git a/src/search/sql_parser.h b/src/search/sql_parser.h
index dffe051d..981ea0ac 100644
--- a/src/search/sql_parser.h
+++ b/src/search/sql_parser.h
@@ -18,40 +18,18 @@
  *
  */
 
+#pragma once
+
 #include <tao/pegtl.hpp>
 
-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<True, False> {};
-
-struct Digits : plus<digit> {};
-struct NumberExp : seq<one<'e', 'E'>, opt<one<'-', '+'>>, Digits> {};
-struct NumberFrac : seq<one<'.'>, Digits> {};
-struct Number : seq<opt<one<'-'>>, Digits, opt<NumberFrac>, opt<NumberExp>> {};
-
-struct UnicodeXDigit : list<seq<one<'u'>, rep<4, xdigit>>, one<'\\'>> {};
-struct EscapedSingleChar : one<'"', '\\', 'b', 'f', 'n', 'r', 't'> {};
-struct EscapedChar : sor<EscapedSingleChar, UnicodeXDigit> {};
-struct UnescapedChar : utf8::range<0x20, 0x10FFFF> {};
-struct Char : if_then_else<one<'\\'>, EscapedChar, UnescapedChar> {};
-
-struct StringContent : until<at<one<'"'>>, Char> {};
-struct String : seq<one<'"'>, StringContent, any> {};
-
-struct Identifier : identifier {};
-
-struct WhiteSpace : one<' ', '\t', '\n', '\r'> {};
-template <typename T>
-struct WSPad : pad<T, WhiteSpace> {};
-
 struct HasTag : string<'h', 'a', 's', 't', 'a', 'g'> {};
 struct HasTagExpr : WSPad<seq<Identifier, WSPad<HasTag>, String>> {};
 
@@ -74,7 +52,7 @@ struct NotExpr : seq<WSPad<Not>, BooleanExpr> {};
 
 struct And : string<'a', 'n', 'd'> {};
 // left recursion elimination
-// struct AndExpr : sor<seq<AndExpr, And, NotExpr>, NotExpr> {};
+// struct AndExpr : sor<seq<AndExpr, And, BooleanExpr>, BooleanExpr> {};
 struct AndExpr : seq<BooleanExpr, plus<seq<And, BooleanExpr>>> {};
 struct AndExprP : sor<AndExpr, BooleanExpr> {};
 
@@ -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<Where, QueryExpr> {};
 struct AscOrDesc : WSPad<sor<Asc, Desc>> {};
 struct OrderByClause : seq<OrderBy, WSPad<Identifier>, opt<AscOrDesc>> {};
-struct LimitClause : seq<Limit, opt<seq<WSPad<Integer>, one<','>>>, 
WSPad<Integer>> {};
+struct LimitClause : seq<Limit, opt<seq<WSPad<UnsignedInteger>, one<','>>>, 
WSPad<UnsignedInteger>> {};
 
 struct SearchStmt
     : WSPad<seq<Select, SelectExpr, From, FromExpr, opt<WhereClause>, 
opt<OrderByClause>, opt<LimitClause>>> {};
diff --git a/src/search/sql_transformer.h b/src/search/sql_transformer.h
index 76fac35c..5c300654 100644
--- a/src/search/sql_transformer.h
+++ b/src/search/sql_transformer.h
@@ -18,13 +18,13 @@
  *
  */
 
+#pragma once
+
 #include <limits>
 #include <memory>
-#include <tao/pegtl/contrib/parse_tree.hpp>
-#include <tao/pegtl/contrib/unescape.hpp>
-#include <tao/pegtl/demangle.hpp>
 #include <variant>
 
+#include "common_transformer.h"
 #include "ir.h"
 #include "parse_util.h"
 #include "sql_parser.h"
@@ -37,7 +37,8 @@ namespace ir = kqir;
 
 template <typename Rule>
 using TreeSelector = parse_tree::selector<
-    Rule, parse_tree::store_content::on<Boolean, Number, String, Identifier, 
NumericCompareOp, AscOrDesc, Integer>,
+    Rule,
+    parse_tree::store_content::on<Boolean, Number, String, Identifier, 
NumericCompareOp, AscOrDesc, UnsignedInteger>,
     parse_tree::remove_content::on<HasTagExpr, NumericCompareExpr, NotExpr, 
AndExpr, OrExpr, Wildcard, SelectExpr,
                                    FromExpr, WhereClause, OrderByClause, 
LimitClause, SearchStmt>>;
 
@@ -51,64 +52,7 @@ StatusOr<std::unique_ptr<parse_tree::node>> 
ParseToTree(Input&& in) {
   }
 }
 
-struct Transformer {
-  using TreeNode = std::unique_ptr<parse_tree::node>;
-
-  template <typename T>
-  static bool Is(const TreeNode& node) {
-    return node->type == demangle<T>();
-  }
-
-  static bool IsRoot(const TreeNode& node) { return node->type.empty(); }
-
-  static StatusOr<std::string> 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<unsigned>(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<std::unique_ptr<Node>> {
     if (Is<Boolean>(node)) {
       return Node::Create<ir::BoolLiteral>(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 00000000..a31051a5
--- /dev/null
+++ b/tests/cppunit/redis_query_parser_test.cc
@@ -0,0 +1,92 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <search/redis_query_transformer.h>
+
+#include "tao/pegtl/string_input.hpp"
+
+using namespace kqir::redis_query;
+
+static auto Parse(const std::string& in) { return ParseToIR(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) {
+  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))");
+  AssertIR(Parse("*"), "true");
+  AssertIR(Parse("* *"), "(and true, true)");
+  AssertIR(Parse("*|*"), "(or true, true)");
+}
diff --git a/tests/cppunit/sql_parser_test.cc b/tests/cppunit/sql_parser_test.cc
index 3a99746d..34fd0f3c 100644
--- a/tests/cppunit/sql_parser_test.cc
+++ b/tests/cppunit/sql_parser_test.cc
@@ -21,7 +21,6 @@
 #include <gtest/gtest.h>
 #include <search/sql_transformer.h>
 
-#include "tao/pegtl/contrib/parse_tree_to_dot.hpp"
 #include "tao/pegtl/string_input.hpp"
 
 using namespace kqir::sql;

Reply via email to