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 9bfb05ec Add index selection pass for KQIR planning (#2278)
9bfb05ec is described below
commit 9bfb05ecc7edf0331c71d7d66b15fcc455170a20
Author: Twice <[email protected]>
AuthorDate: Mon Apr 29 12:23:08 2024 +0900
Add index selection pass for KQIR planning (#2278)
---
CMakeLists.txt | 2 +
NOTICE | 3 +-
cmake/rangev3.cmake | 48 +++
licenses/LICENSE-range-v3.txt | 151 ++++++++++
src/search/index_info.h | 6 +
src/search/interval.h | 8 +-
src/search/ir.h | 20 ++
src/search/ir_plan.h | 20 +-
src/search/ir_sema_checker.h | 4 +-
src/search/passes/cost_model.h | 89 ++++++
src/search/passes/index_selection.h | 323 +++++++++++++++++++++
src/search/passes/interval_analysis.h | 26 +-
src/search/passes/lower_to_plan.h | 26 +-
src/search/passes/manager.h | 35 ++-
.../passes/{lower_to_plan.h => sort_limit_fuse.h} | 20 +-
tests/cppunit/ir_pass_test.cc | 110 ++++++-
tests/cppunit/ir_sema_checker_test.cc | 2 +-
17 files changed, 850 insertions(+), 43 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 944b5509..59566538 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -140,6 +140,7 @@ include(cmake/xxhash.cmake)
include(cmake/span.cmake)
include(cmake/trie.cmake)
include(cmake/pegtl.cmake)
+include(cmake/rangev3.cmake)
if (ENABLE_LUAJIT)
include(cmake/luajit.cmake)
@@ -173,6 +174,7 @@ list(APPEND EXTERNAL_LIBS xxhash)
list(APPEND EXTERNAL_LIBS span-lite)
list(APPEND EXTERNAL_LIBS tsl_hat_trie)
list(APPEND EXTERNAL_LIBS pegtl)
+list(APPEND EXTERNAL_LIBS range-v3)
# Add git sha to version.h
find_package(Git REQUIRED)
diff --git a/NOTICE b/NOTICE
index 86c357d8..8488d466 100644
--- a/NOTICE
+++ b/NOTICE
@@ -66,7 +66,7 @@ The text of each license is also included in
licenses/LICENSE-[project].txt
* LuaJIT(https://github.com/KvrocksLabs/LuaJIT)
* lua(https://github.com/KvrocksLabs/lua, alternative to LuaJIT)
* hat-trie(https://github.com/Tessil/hat-trie)
-* pegtl(https://github.com/taocpp/PEGTL, NOTE: changed to BSL-1.0 in main
branch)
+* pegtl(https://github.com/taocpp/PEGTL, NOTE: changed to Boost Software
License Version 1.0 in main branch)
================================================================
Boost Software License Version 1.0
@@ -76,6 +76,7 @@ The text of each license is also included in
licenses/LICENSE-[project].txt
* jsoncons(https://github.com/danielaparker/jsoncons)
* span-lite(https://github.com/martinmoene/span-lite)
+* range-v3(https://github.com/ericniebler/range-v3)
================================================================
zlib/libpng licenses
diff --git a/cmake/rangev3.cmake b/cmake/rangev3.cmake
new file mode 100644
index 00000000..c30a1468
--- /dev/null
+++ b/cmake/rangev3.cmake
@@ -0,0 +1,48 @@
+# 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_guard()
+
+include(cmake/utils.cmake)
+
+FetchContent_DeclareGitHubWithMirror(rangev3
+ ericniebler/range-v3 0.12.0
+ MD5=e220e3f545fdf46241b4f139822d73a1
+)
+
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(WITH_DEBUG_INFO ON)
+elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
+ set(WITH_DEBUG_INFO OFF)
+elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
+ set(WITH_DEBUG_INFO ON)
+elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
+ set(WITH_DEBUG_INFO OFF)
+endif()
+
+if (PORTABLE STREQUAL 0)
+ set(ARG_RANGES_NATIVE ON)
+else()
+ set(ARG_RANGES_NATIVE OFF)
+endif()
+
+FetchContent_MakeAvailableWithArgs(rangev3
+ RANGES_CXX_STD=17
+ RANGES_BUILD_CALENDAR_EXAMPLE=OFF
+ RANGES_DEBUG_INFO=${WITH_DEBUG_INFO}
+ RANGES_NATIVE=${ARG_RANGES_NATIVE}
+)
diff --git a/licenses/LICENSE-range-v3.txt b/licenses/LICENSE-range-v3.txt
new file mode 100644
index 00000000..698193e9
--- /dev/null
+++ b/licenses/LICENSE-range-v3.txt
@@ -0,0 +1,151 @@
+========================================================
+Boost Software License - Version 1.0 - August 17th, 2003
+========================================================
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+==============================================================================
+libc++ License
+==============================================================================
+
+The libc++ library is dual licensed under both the University of Illinois
+"BSD-Like" license and the MIT license. As a user of this code you may choose
+to use it under either license. As a contributor, you agree to allow your code
+to be used under both.
+
+Full text of the relevant licenses is included below.
+
+==============================================================================
+
+University of Illinois/NCSA
+Open Source License
+
+Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT
+http://llvm.org/svn/llvm-project/libcxx/trunk/CREDITS.TXT
+
+All rights reserved.
+
+Developed by:
+
+ LLVM Team
+
+ University of Illinois at Urbana-Champaign
+
+ http://llvm.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimers.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimers in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the names of the LLVM Team, University of Illinois at
+ Urbana-Champaign, nor the names of its contributors may be used to
+ endorse or promote products derived from this Software without specific
+ prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+
+==============================================================================
+
+Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT
+ http://llvm.org/svn/llvm-project/libcxx/trunk/CREDITS.TXT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+==============================================================================
+Stepanov and McJones, "Elements of Programming" license
+==============================================================================
+
+// Copyright (c) 2009 Alexander Stepanov and Paul McJones
+//
+// Permission to use, copy, modify, distribute and sell this software
+// and its documentation for any purpose is hereby granted without
+// fee, provided that the above copyright notice appear in all copies
+// and that both that copyright notice and this permission notice
+// appear in supporting documentation. The authors make no
+// representations about the suitability of this software for any
+// purpose. It is provided "as is" without express or implied
+// warranty.
+//
+// Algorithms from
+// Elements of Programming
+// by Alexander Stepanov and Paul McJones
+// Addison-Wesley Professional, 2009
+
+==============================================================================
+SGI C++ Standard Template Library license
+==============================================================================
+
+// Copyright (c) 1994
+// Hewlett-Packard Company
+//
+// Permission to use, copy, modify, distribute and sell this software
+// and its documentation for any purpose is hereby granted without fee,
+// provided that the above copyright notice appear in all copies and
+// that both that copyright notice and this permission notice appear
+// in supporting documentation. Hewlett-Packard Company makes no
+// representations about the suitability of this software for any
+// purpose. It is provided "as is" without express or implied warranty.
+//
+// Copyright (c) 1996
+// Silicon Graphics Computer Systems, Inc.
+//
+// Permission to use, copy, modify, distribute and sell this software
+// and its documentation for any purpose is hereby granted without fee,
+// provided that the above copyright notice appear in all copies and
+// that both that copyright notice and this permission notice appear
+// in supporting documentation. Silicon Graphics makes no
+// representations about the suitability of this software for any
+// purpose. It is provided "as is" without express or implied warranty.
+//
diff --git a/src/search/index_info.h b/src/search/index_info.h
index df059918..5b0cb707 100644
--- a/src/search/index_info.h
+++ b/src/search/index_info.h
@@ -39,6 +39,12 @@ struct FieldInfo {
: name(std::move(name)), metadata(std::move(metadata)) {}
bool IsSortable() const { return
dynamic_cast<redis::SearchSortableFieldMetadata *>(metadata.get()) != nullptr; }
+ bool HasIndex() const { return !metadata->noindex; }
+
+ template <typename T>
+ const T *MetadataAs() const {
+ return dynamic_cast<const T *>(metadata.get());
+ }
};
struct IndexInfo {
diff --git a/src/search/interval.h b/src/search/interval.h
index d6ebbf5c..5ce90a45 100644
--- a/src/search/interval.h
+++ b/src/search/interval.h
@@ -34,9 +34,13 @@ namespace kqir {
struct Interval {
double l, r; // [l, r)
+ static inline const double inf = std::numeric_limits<double>::infinity();
+ static inline const double minf = -inf;
+
Interval(double l, double r) : l(l), r(r) {}
bool IsEmpty() const { return l >= r; }
+ static Interval Full() { return {minf, inf}; }
bool operator==(const Interval &other) const { return l == other.l && r ==
other.r; }
bool operator!=(const Interval &other) const { return !(*this == other); }
@@ -69,8 +73,8 @@ struct IntervalSet {
using DataType = std::vector<std::pair<double, double>>;
DataType intervals;
- static inline const double inf = std::numeric_limits<double>::infinity();
- static inline const double minf = -inf;
+ static inline const double inf = Interval::inf;
+ static inline const double minf = Interval::minf;
static double NextNum(double val) { return std::nextafter(val, inf); }
diff --git a/src/search/ir.h b/src/search/ir.h
index 3acc5ae8..b841c0fa 100644
--- a/src/search/ir.h
+++ b/src/search/ir.h
@@ -256,6 +256,16 @@ struct AndExpr : QueryExpr {
explicit AndExpr(std::vector<std::unique_ptr<QueryExpr>> &&inners) :
inners(std::move(inners)) {}
+ static std::unique_ptr<QueryExpr>
Create(std::vector<std::unique_ptr<QueryExpr>> &&exprs) {
+ CHECK(!exprs.empty());
+
+ if (exprs.size() == 1) {
+ return std::move(exprs.front());
+ }
+
+ return std::make_unique<AndExpr>(std::move(exprs));
+ }
+
std::string_view Name() const override { return "AndExpr"; }
std::string Dump() const override {
return fmt::format("(and {})", util::StringJoin(inners, [](const auto &v)
{ return v->Dump(); }));
@@ -279,6 +289,16 @@ struct OrExpr : QueryExpr {
explicit OrExpr(std::vector<std::unique_ptr<QueryExpr>> &&inners) :
inners(std::move(inners)) {}
+ static std::unique_ptr<QueryExpr>
Create(std::vector<std::unique_ptr<QueryExpr>> &&exprs) {
+ CHECK(!exprs.empty());
+
+ if (exprs.size() == 1) {
+ return std::move(exprs.front());
+ }
+
+ return std::make_unique<OrExpr>(std::move(exprs));
+ }
+
std::string_view Name() const override { return "OrExpr"; }
std::string Dump() const override {
return fmt::format("(or {})", util::StringJoin(inners, [](const auto &v) {
return v->Dump(); }));
diff --git a/src/search/ir_plan.h b/src/search/ir_plan.h
index 94f801f4..9a81a94a 100644
--- a/src/search/ir_plan.h
+++ b/src/search/ir_plan.h
@@ -60,15 +60,19 @@ struct FieldScan : PlanOperator {
struct NumericFieldScan : FieldScan {
Interval range;
+ SortByClause::Order order;
- NumericFieldScan(std::unique_ptr<FieldRef> field, Interval range) :
FieldScan(std::move(field)), range(range) {}
+ NumericFieldScan(std::unique_ptr<FieldRef> field, Interval range,
SortByClause::Order order = SortByClause::ASC)
+ : FieldScan(std::move(field)), range(range), order(order) {}
std::string_view Name() const override { return "NumericFieldScan"; };
- std::string Content() const override { return fmt::format("{}, {}",
field->name, range.ToString()); };
+ std::string Content() const override {
+ return fmt::format("{}, {}, {}", field->name, range.ToString(),
SortByClause::OrderToString(order));
+ };
std::string Dump() const override { return fmt::format("numeric-scan {}",
Content()); }
std::unique_ptr<Node> Clone() const override {
- return std::make_unique<NumericFieldScan>(field->CloneAs<FieldRef>(),
range);
+ return std::make_unique<NumericFieldScan>(field->CloneAs<FieldRef>(),
range, order);
}
};
@@ -110,6 +114,16 @@ struct Merge : PlanOperator {
explicit Merge(std::vector<std::unique_ptr<PlanOperator>> &&ops) :
ops(std::move(ops)) {}
+ static std::unique_ptr<PlanOperator>
Create(std::vector<std::unique_ptr<PlanOperator>> &&ops) {
+ CHECK(!ops.empty());
+
+ if (ops.size() == 1) {
+ return std::move(ops.front());
+ }
+
+ return std::make_unique<Merge>(std::move(ops));
+ }
+
std::string_view Name() const override { return "Merge"; };
std::string Dump() const override {
return fmt::format("(merge {})", util::StringJoin(ops, [](const auto &v) {
return v->Dump(); }));
diff --git a/src/search/ir_sema_checker.h b/src/search/ir_sema_checker.h
index 40f316cb..d8982e5c 100644
--- a/src/search/ir_sema_checker.h
+++ b/src/search/ir_sema_checker.h
@@ -75,7 +75,7 @@ struct SemaChecker {
} else if (auto v = dynamic_cast<TagContainExpr *>(node)) {
if (auto iter = current_index->fields.find(v->field->name); iter ==
current_index->fields.end()) {
return {Status::NotOK, fmt::format("field `{}` not found in index
`{}`", v->field->name)};
- } else if (auto meta = dynamic_cast<redis::SearchTagFieldMetadata
*>(iter->second.metadata.get()); !meta) {
+ } else if (auto meta =
iter->second.MetadataAs<redis::SearchTagFieldMetadata>(); !meta) {
return {Status::NotOK, fmt::format("field `{}` is not a tag field",
v->field->name)};
} else {
v->field->info = &iter->second;
@@ -91,7 +91,7 @@ struct SemaChecker {
} else if (auto v = dynamic_cast<NumericCompareExpr *>(node)) {
if (auto iter = current_index->fields.find(v->field->name); iter ==
current_index->fields.end()) {
return {Status::NotOK, fmt::format("field `{}` not found in index
`{}`", v->field->name, current_index->name)};
- } else if (!dynamic_cast<redis::SearchNumericFieldMetadata
*>(iter->second.metadata.get())) {
+ } else if
(!iter->second.MetadataAs<redis::SearchNumericFieldMetadata>()) {
return {Status::NotOK, fmt::format("field `{}` is not a numeric
field", v->field->name)};
} else {
v->field->info = &iter->second;
diff --git a/src/search/passes/cost_model.h b/src/search/passes/cost_model.h
new file mode 100644
index 00000000..86e0e3a5
--- /dev/null
+++ b/src/search/passes/cost_model.h
@@ -0,0 +1,89 @@
+/*
+ * 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 <numeric>
+
+#include "search/interval.h"
+#include "search/ir.h"
+#include "search/ir_plan.h"
+
+namespace kqir {
+
+// TODO: collect statistical information of index in runtime
+// to optimize the cost model
+struct CostModel {
+ static size_t Transform(const PlanOperator *node) {
+ if (auto v = dynamic_cast<const FullIndexScan *>(node)) {
+ return Visit(v);
+ }
+ if (auto v = dynamic_cast<const NumericFieldScan *>(node)) {
+ return Visit(v);
+ }
+ if (auto v = dynamic_cast<const TagFieldScan *>(node)) {
+ return Visit(v);
+ }
+ if (auto v = dynamic_cast<const Filter *>(node)) {
+ return Visit(v);
+ }
+ if (auto v = dynamic_cast<const Merge *>(node)) {
+ return Visit(v);
+ }
+
+ CHECK(false) << "plan operator type not supported";
+ }
+
+ static size_t Visit(const FullIndexScan *node) { return 100; }
+
+ static size_t Visit(const NumericFieldScan *node) {
+ if (node->range.r == IntervalSet::NextNum(node->range.l)) {
+ return 5;
+ }
+
+ size_t base = 10;
+
+ if (std::isinf(node->range.l)) {
+ base += 20;
+ }
+
+ if (std::isinf(node->range.r)) {
+ base += 20;
+ }
+
+ return base;
+ }
+
+ static size_t Visit(const TagFieldScan *node) { return 10; }
+
+ static size_t Visit(const Filter *node) { return
Transform(node->source.get()) + 1; }
+
+ static size_t Visit(const Merge *node) {
+ return std::accumulate(node->ops.begin(), node->ops.end(), size_t(0),
[](size_t res, const auto &v) {
+ if (dynamic_cast<const Filter *>(v.get())) {
+ res += 9;
+ }
+ return res + Transform(v.get());
+ });
+ }
+};
+
+} // namespace kqir
diff --git a/src/search/passes/index_selection.h
b/src/search/passes/index_selection.h
new file mode 100644
index 00000000..4ced7972
--- /dev/null
+++ b/src/search/passes/index_selection.h
@@ -0,0 +1,323 @@
+/*
+ * 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 <range/v3/view.hpp>
+#include <type_traits>
+#include <variant>
+
+#include "search/index_info.h"
+#include "search/interval.h"
+#include "search/ir.h"
+#include "search/ir_pass.h"
+#include "search/ir_plan.h"
+#include "search/passes/cost_model.h"
+#include "search/passes/interval_analysis.h"
+#include "search/passes/push_down_not_expr.h"
+#include "search/passes/simplify_and_or_expr.h"
+#include "search/search_encoding.h"
+
+namespace kqir {
+
+struct IndexSelection : Visitor {
+ SortByClause *order = nullptr;
+ bool sort_removable = false;
+ IndexRef *index = nullptr;
+ IntervalAnalysis::Result intervals;
+
+ void Reset() override {
+ order = nullptr;
+ sort_removable = false;
+ index = nullptr;
+ intervals.clear();
+ }
+
+ std::unique_ptr<Node> Visit(std::unique_ptr<Projection> node) override {
+ IntervalAnalysis analysis(false);
+ node = Node::MustAs<Projection>(analysis.Transform(std::move(node)));
+ intervals = std::move(analysis.result);
+
+ return Visitor::Visit(std::move(node));
+ }
+
+ std::unique_ptr<Node> Visit(std::unique_ptr<Sort> node) override {
+ order = node->order.get();
+
+ node = Node::MustAs<Sort>(Visitor::Visit(std::move(node)));
+
+ if (sort_removable) return std::move(node->op);
+
+ return node;
+ }
+
+ bool HasGoodOrder() const { return order && order->field->info->HasIndex(); }
+
+ std::unique_ptr<PlanOperator> GenerateScanFromOrder() const {
+ if (order->field->info->MetadataAs<redis::SearchNumericFieldMetadata>()) {
+ return
std::make_unique<NumericFieldScan>(order->field->CloneAs<FieldRef>(),
Interval::Full(), order->order);
+ } else {
+ CHECK(false) << "current only numeric field is supported for ordering";
+ }
+ }
+
+ // if there's no Filter node, enter this method
+ std::unique_ptr<Node> Visit(std::unique_ptr<FullIndexScan> node) override {
+ if (HasGoodOrder()) {
+ sort_removable = true;
+ return GenerateScanFromOrder();
+ }
+
+ return node;
+ }
+
+ std::unique_ptr<Node> Visit(std::unique_ptr<Filter> node) override {
+ auto index_scan = Node::MustAs<FullIndexScan>(std::move(node->source));
+
+ if (HasGoodOrder()) {
+ // TODO: optimize plan with sorting order via the cost model
+ sort_removable = true;
+
+ auto scan = GenerateScanFromOrder();
+ return std::make_unique<Filter>(std::move(scan),
std::move(node->filter_expr));
+ } else {
+ index = index_scan->index.get();
+
+ return TransformExpr(node->filter_expr.get());
+ }
+ }
+
+ std::unique_ptr<PlanOperator> TransformExpr(QueryExpr *node) {
+ if (auto v = dynamic_cast<AndExpr *>(node)) {
+ return VisitExpr(v);
+ }
+ if (auto v = dynamic_cast<OrExpr *>(node)) {
+ return VisitExpr(v);
+ }
+ if (auto v = dynamic_cast<NumericCompareExpr *>(node)) {
+ return VisitExpr(v);
+ }
+ if (auto v = dynamic_cast<TagContainExpr *>(node)) {
+ return VisitExpr(v);
+ }
+ if (auto v = dynamic_cast<NotExpr *>(node)) {
+ return VisitExpr(v);
+ }
+
+ CHECK(false) << "unreachable";
+ }
+
+ std::unique_ptr<PlanOperator> MakeFullIndexFilter(QueryExpr *node) const {
+ return
std::make_unique<Filter>(std::make_unique<FullIndexScan>(index->CloneAs<IndexRef>()),
+ node->CloneAs<QueryExpr>());
+ }
+
+ std::unique_ptr<PlanOperator> VisitExpr(NotExpr *node) const {
+ // after PushDownNotExpr, `node->inner` should be one of TagContainExpr
and NumericCompareExpr
+ return MakeFullIndexFilter(node);
+ }
+
+ std::unique_ptr<PlanOperator> VisitExpr(TagContainExpr *node) const {
+ if (node->field->info->HasIndex()) {
+ return std::make_unique<TagFieldScan>(node->field->CloneAs<FieldRef>(),
node->tag->val);
+ }
+
+ return MakeFullIndexFilter(node);
+ }
+
+ // enter only if there's just a single NumericCompareExpr, without and/or
expression
+ std::unique_ptr<PlanOperator> VisitExpr(NumericCompareExpr *node) const {
+ if (node->field->info->HasIndex() && node->op != NumericCompareExpr::NE) {
+ IntervalSet is(node->op, node->num->val);
+ return PlanFromInterval(is, node->field.get(), SortByClause::ASC);
+ }
+
+ return MakeFullIndexFilter(node);
+ }
+
+ template <typename Expr>
+ std::unique_ptr<PlanOperator> VisitExprImpl(Expr *node) {
+ struct AggregatedNodes {
+ std::set<Node *> nodes;
+ IntervalSet intervals;
+ };
+
+ std::map<const FieldInfo *, AggregatedNodes> agg_nodes;
+ std::vector<std::variant<QueryExpr *, const FieldInfo *>> rest_nodes;
+
+ for (const auto &n : node->inners) {
+ IntervalSet is;
+ const FieldInfo *field = nullptr;
+
+ if (auto iter = intervals.find(n.get()); iter != intervals.end()) {
+ field = iter->second.field_info;
+ is = iter->second.intervals;
+ } else if (auto expr = dynamic_cast<NumericCompareExpr *>(n.get()); expr
&& expr->op != NumericCompareExpr::NE) {
+ field = expr->field->info;
+ is = IntervalSet(expr->op, expr->num->val);
+ } else {
+ rest_nodes.emplace_back(n.get());
+ continue;
+ }
+
+ if (!field->HasIndex()) {
+ rest_nodes.emplace_back(n.get());
+ continue;
+ }
+
+ if (auto jter = agg_nodes.find(field); jter != agg_nodes.end()) {
+ jter->second.nodes.emplace(n.get());
+ if constexpr (std::is_same_v<Expr, AndExpr>) {
+ jter->second.intervals = jter->second.intervals & is;
+ } else {
+ jter->second.intervals = jter->second.intervals | is;
+ }
+ } else {
+ rest_nodes.emplace_back(field);
+ agg_nodes.emplace(field, AggregatedNodes{std::set<Node *>{n.get()},
is});
+ }
+ }
+
+ if constexpr (std::is_same_v<Expr, AndExpr>) {
+ struct SelectionInfo {
+ std::unique_ptr<PlanOperator> plan;
+ std::set<Node *> selected_nodes;
+ size_t cost;
+
+ SelectionInfo(std::unique_ptr<PlanOperator> &&plan, std::set<Node *>
nodes)
+ : plan(std::move(plan)), selected_nodes(std::move(nodes)),
cost(CostModel::Transform(this->plan.get())) {}
+ };
+
+ std::vector<SelectionInfo> available_plans;
+
+
available_plans.emplace_back(std::make_unique<FullIndexScan>(index->CloneAs<IndexRef>()),
std::set<Node *>{});
+
+ for (auto v : rest_nodes) {
+ if (std::holds_alternative<QueryExpr *>(v)) {
+ auto n = std::get<QueryExpr *>(v);
+ auto op = TransformExpr(n);
+
+ available_plans.emplace_back(std::move(op), std::set<Node *>{n});
+ } else {
+ auto n = std::get<const FieldInfo *>(v);
+ const auto &agg_info = agg_nodes.at(n);
+ auto field_ref = std::make_unique<FieldRef>(n->name, n);
+ available_plans.emplace_back(PlanFromInterval(agg_info.intervals,
field_ref.get(), SortByClause::ASC),
+ agg_info.nodes);
+ }
+ }
+
+ auto &best_plan = *std::min_element(available_plans.begin(),
available_plans.end(),
+ [](const auto &l, const auto &r) {
return l.cost < r.cost; });
+
+ std::vector<std::unique_ptr<QueryExpr>> filter_nodes;
+ for (const auto &n : node->inners) {
+ if (best_plan.selected_nodes.count(n.get()) == 0)
filter_nodes.push_back(n->template CloneAs<QueryExpr>());
+ }
+
+ if (filter_nodes.empty()) {
+ return std::move(best_plan.plan);
+ } else if (filter_nodes.size() == 1) {
+ return std::make_unique<Filter>(std::move(best_plan.plan),
std::move(filter_nodes.front()));
+ } else {
+ return std::make_unique<Filter>(std::move(best_plan.plan),
std::make_unique<AndExpr>(std::move(filter_nodes)));
+ }
+ } else {
+ auto full_scan_plan = MakeFullIndexFilter(node);
+
+ std::vector<std::unique_ptr<PlanOperator>> merged_elems;
+ std::vector<std::unique_ptr<QueryExpr>> elem_filter;
+
+ auto add_filter = [&elem_filter](std::unique_ptr<PlanOperator> op) {
+ if (!elem_filter.empty()) {
+ std::unique_ptr<QueryExpr> filter =
std::make_unique<NotExpr>(OrExpr::Create(CloneExprs(elem_filter)));
+
+ PushDownNotExpr pdne;
+ filter = Node::MustAs<QueryExpr>(pdne.Transform(std::move(filter)));
+ SimplifyAndOrExpr saoe;
+ filter = Node::MustAs<QueryExpr>(saoe.Transform(std::move(filter)));
+
+ op = std::make_unique<Filter>(std::move(op), std::move(filter));
+ }
+
+ return op;
+ };
+
+ for (auto v : rest_nodes) {
+ if (std::holds_alternative<QueryExpr *>(v)) {
+ auto n = std::get<QueryExpr *>(v);
+ auto op = add_filter(TransformExpr(n));
+
+ merged_elems.push_back(std::move(op));
+ elem_filter.push_back(n->CloneAs<QueryExpr>());
+ } else {
+ auto n = std::get<const FieldInfo *>(v);
+ const auto &agg_info = agg_nodes.at(n);
+ auto field_ref = std::make_unique<FieldRef>(n->name, n);
+ auto elem = PlanFromInterval(agg_info.intervals, field_ref.get(),
SortByClause::ASC);
+ elem = add_filter(std::move(elem));
+
+ merged_elems.push_back(std::move(elem));
+ for (auto nn : agg_info.nodes) {
+ elem_filter.push_back(nn->template CloneAs<QueryExpr>());
+ }
+ }
+ }
+
+ auto merge_plan = Merge::Create(std::move(merged_elems));
+ auto &best_plan = const_cast<std::unique_ptr<PlanOperator> &>(std::min(
+ full_scan_plan, merge_plan,
+ [](const auto &l, const auto &r) { return
CostModel::Transform(l.get()) < CostModel::Transform(r.get()); }));
+
+ return std::move(best_plan);
+ }
+ }
+
+ static std::vector<std::unique_ptr<QueryExpr>> CloneExprs(const
std::vector<std::unique_ptr<QueryExpr>> &exprs) {
+ std::vector<std::unique_ptr<QueryExpr>> result;
+ result.reserve(exprs.size());
+
+ for (const auto &e : exprs) result.push_back(e->CloneAs<QueryExpr>());
+ return result;
+ }
+
+ std::unique_ptr<PlanOperator> VisitExpr(AndExpr *node) { return
VisitExprImpl(node); }
+
+ std::unique_ptr<PlanOperator> VisitExpr(OrExpr *node) { return
VisitExprImpl(node); }
+
+ static std::unique_ptr<PlanOperator> PlanFromInterval(const IntervalSet
&intervals, FieldRef *field,
+ SortByClause::Order
order) {
+ std::vector<std::unique_ptr<PlanOperator>> result;
+ if (order == SortByClause::ASC) {
+ for (const auto &[l, r] : intervals.intervals) {
+
result.push_back(std::make_unique<NumericFieldScan>(field->CloneAs<FieldRef>(),
Interval(l, r), order));
+ }
+ } else {
+ for (const auto &[l, r] : ranges::views::reverse(intervals.intervals)) {
+
result.push_back(std::make_unique<NumericFieldScan>(field->CloneAs<FieldRef>(),
Interval(l, r), order));
+ }
+ }
+
+ return Merge::Create(std::move(result));
+ }
+};
+
+} // namespace kqir
diff --git a/src/search/passes/interval_analysis.h
b/src/search/passes/interval_analysis.h
index 59959979..5ed56125 100644
--- a/src/search/passes/interval_analysis.h
+++ b/src/search/passes/interval_analysis.h
@@ -29,6 +29,7 @@
#include "search/interval.h"
#include "search/ir.h"
#include "search/ir_pass.h"
+#include "search/ir_plan.h"
#include "type_util.h"
namespace kqir {
@@ -40,7 +41,13 @@ struct IntervalAnalysis : Visitor {
IntervalSet intervals;
};
- std::map<Node *, IntervalInfo> result;
+ using Result = std::map<Node *, IntervalInfo>;
+
+ Result result;
+ const bool simplify_numeric_compare;
+
+ explicit IntervalAnalysis(bool simplify_numeric_compare = false)
+ : simplify_numeric_compare(simplify_numeric_compare) {}
void Reset() override { result.clear(); }
@@ -92,13 +99,18 @@ struct IntervalAnalysis : Visitor {
result.emplace(node.get(), IntervalInfo{elem.first, elem.second.field,
elem.second.intervals});
}
- for (const auto &[field, info] : interval_map) {
- auto iter = std::remove_if(node->inners.begin(), node->inners.end(),
- [&info = info](const auto &n) { return
info.nodes.count(n.get()) == 1; });
- node->inners.erase(iter, node->inners.end());
+ if (simplify_numeric_compare) {
+ for (const auto &[field, info] : interval_map) {
+ auto iter = std::remove_if(node->inners.begin(), node->inners.end(),
+ [&info = info](const auto &n) { return
info.nodes.count(n.get()) == 1; });
+ node->inners.erase(iter, node->inners.end());
+ for (const auto &n : info.nodes) {
+ if (auto iter = result.find(n); iter != result.end())
result.erase(iter);
+ }
- auto field_node = std::make_unique<FieldRef>(field, info.field);
- node->inners.emplace_back(GenerateFromInterval(info.intervals,
field_node.get()));
+ auto field_node = std::make_unique<FieldRef>(field, info.field);
+ node->inners.emplace_back(GenerateFromInterval(info.intervals,
field_node.get()));
+ }
}
return node;
diff --git a/src/search/passes/lower_to_plan.h
b/src/search/passes/lower_to_plan.h
index dad1db39..94828c22 100644
--- a/src/search/passes/lower_to_plan.h
+++ b/src/search/passes/lower_to_plan.h
@@ -31,17 +31,27 @@ namespace kqir {
struct LowerToPlan : Visitor {
std::unique_ptr<Node> Visit(std::unique_ptr<SearchExpr> node) override {
auto scan =
std::make_unique<FullIndexScan>(node->index->CloneAs<IndexRef>());
- auto filter = std::make_unique<Filter>(std::move(scan),
std::move(node->query_expr));
- std::unique_ptr<PlanOperator> op = std::move(filter);
-
- // order is important here, since limit(sort(op)) is different from
sort(limit(op))
- if (node->sort_by) {
- op = std::make_unique<Sort>(std::move(op), std::move(node->sort_by));
+ std::unique_ptr<PlanOperator> op;
+ if (auto b = Node::As<BoolLiteral>(std::move(node->query_expr))) {
+ if (b->val) {
+ op = std::move(scan);
+ } else {
+ op = std::make_unique<Noop>();
+ }
+ } else {
+ op = std::make_unique<Filter>(std::move(scan),
std::move(node->query_expr));
}
- if (node->limit) {
- op = std::make_unique<Limit>(std::move(op), std::move(node->limit));
+ if (!dynamic_cast<Noop *>(op.get())) {
+ // order is important here, since limit(sort(op)) is different from
sort(limit(op))
+ if (node->sort_by) {
+ op = std::make_unique<Sort>(std::move(op), std::move(node->sort_by));
+ }
+
+ if (node->limit) {
+ op = std::make_unique<Limit>(std::move(op), std::move(node->limit));
+ }
}
return std::make_unique<Projection>(std::move(op),
std::move(node->select));
diff --git a/src/search/passes/manager.h b/src/search/passes/manager.h
index 480e27a7..e94a12e9 100644
--- a/src/search/passes/manager.h
+++ b/src/search/passes/manager.h
@@ -20,14 +20,21 @@
#pragma once
+#include <iterator>
#include <memory>
+#include <type_traits>
#include <utility>
#include "search/ir.h"
#include "search/ir_pass.h"
+#include "search/passes/index_selection.h"
+#include "search/passes/interval_analysis.h"
+#include "search/passes/lower_to_plan.h"
#include "search/passes/push_down_not_expr.h"
#include "search/passes/simplify_and_or_expr.h"
#include "search/passes/simplify_boolean.h"
+#include "search/passes/sort_limit_fuse.h"
+#include "type_util.h"
namespace kqir {
@@ -43,13 +50,35 @@ struct PassManager {
}
template <typename... Passes>
- static PassSequence GeneratePasses() {
+ static PassSequence Create(Passes &&...passes) {
+
static_assert(std::conjunction_v<std::negation<std::is_reference<Passes>>...>);
+
+ PassSequence result;
+ result.reserve(sizeof...(passes));
+ (result.push_back(std::make_unique<Passes>(std::move(passes))), ...);
+
+ return result;
+ }
+
+ template <typename... PassSeqs>
+ static PassSequence Merge(PassSeqs &&...seqs) {
+
static_assert(std::conjunction_v<std::negation<std::is_reference<PassSeqs>>...>);
+ static_assert(std::conjunction_v<std::is_same<PassSequence,
RemoveCVRef<PassSeqs>>...>);
+
PassSequence result;
- (result.push_back(std::make_unique<Passes>()), ...);
+ result.reserve((seqs.size() + ...));
+ (result.insert(result.end(), std::make_move_iterator(seqs.begin()),
std::make_move_iterator(seqs.end())), ...);
+
return result;
}
- static PassSequence ExprPasses() { return GeneratePasses<SimplifyAndOrExpr,
PushDownNotExpr, SimplifyBoolean>(); }
+ static PassSequence ExprPasses() {
+ return Create(SimplifyAndOrExpr{}, PushDownNotExpr{}, SimplifyBoolean{},
SimplifyAndOrExpr{});
+ }
+ static PassSequence NumericPasses() { return Create(IntervalAnalysis{true},
SimplifyAndOrExpr{}, SimplifyBoolean{}); }
+ static PassSequence PlanPasses() { return Create(LowerToPlan{},
IndexSelection{}, SortLimitFuse{}); }
+
+ static PassSequence Default() { return Merge(ExprPasses(), NumericPasses(),
PlanPasses()); }
};
} // namespace kqir
diff --git a/src/search/passes/lower_to_plan.h
b/src/search/passes/sort_limit_fuse.h
similarity index 57%
copy from src/search/passes/lower_to_plan.h
copy to src/search/passes/sort_limit_fuse.h
index dad1db39..0e857297 100644
--- a/src/search/passes/lower_to_plan.h
+++ b/src/search/passes/sort_limit_fuse.h
@@ -28,23 +28,15 @@
namespace kqir {
-struct LowerToPlan : Visitor {
- std::unique_ptr<Node> Visit(std::unique_ptr<SearchExpr> node) override {
- auto scan =
std::make_unique<FullIndexScan>(node->index->CloneAs<IndexRef>());
- auto filter = std::make_unique<Filter>(std::move(scan),
std::move(node->query_expr));
+struct SortLimitFuse : Visitor {
+ std::unique_ptr<Node> Visit(std::unique_ptr<Limit> node) override {
+ node = Node::MustAs<Limit>(Visitor::Visit(std::move(node)));
- std::unique_ptr<PlanOperator> op = std::move(filter);
-
- // order is important here, since limit(sort(op)) is different from
sort(limit(op))
- if (node->sort_by) {
- op = std::make_unique<Sort>(std::move(op), std::move(node->sort_by));
- }
-
- if (node->limit) {
- op = std::make_unique<Limit>(std::move(op), std::move(node->limit));
+ if (auto sort = Node::As<Sort>(std::move(node->op))) {
+ return std::make_unique<TopNSort>(std::move(sort->op),
std::move(sort->order), std::move(node->limit));
}
- return std::make_unique<Projection>(std::move(op),
std::move(node->select));
+ return node;
}
};
diff --git a/tests/cppunit/ir_pass_test.cc b/tests/cppunit/ir_pass_test.cc
index bfb630a5..a02dc907 100644
--- a/tests/cppunit/ir_pass_test.cc
+++ b/tests/cppunit/ir_pass_test.cc
@@ -20,6 +20,7 @@
#include "search/ir_pass.h"
+#include "fmt/core.h"
#include "gtest/gtest.h"
#include "search/interval.h"
#include "search/ir_sema_checker.h"
@@ -113,7 +114,10 @@ TEST(IRPassTest, Manager) {
TEST(IRPassTest, LowerToPlan) {
LowerToPlan ltp;
- ASSERT_EQ(ltp.Transform(*Parse("select * from a"))->Dump(), "project *:
(filter true: full-scan a)");
+ ASSERT_EQ(ltp.Transform(*Parse("select * from a"))->Dump(), "project *:
full-scan a");
+ ASSERT_EQ(ltp.Transform(*Parse("select * from a limit 1"))->Dump(), "project
*: (limit 0, 1: full-scan a)");
+ ASSERT_EQ(ltp.Transform(*Parse("select * from a where false"))->Dump(),
"project *: noop");
+ ASSERT_EQ(ltp.Transform(*Parse("select * from a where false limit
1"))->Dump(), "project *: noop");
ASSERT_EQ(ltp.Transform(*Parse("select * from a where b > 1"))->Dump(),
"project *: (filter b > 1: full-scan a)");
ASSERT_EQ(ltp.Transform(*Parse("select a from b where c = 1 order by
d"))->Dump(),
"project a: (sort d, asc: (filter c = 1: full-scan b))");
@@ -124,7 +128,7 @@ TEST(IRPassTest, LowerToPlan) {
}
TEST(IRPassTest, IntervalAnalysis) {
- auto ia_passes = PassManager::GeneratePasses<IntervalAnalysis,
SimplifyAndOrExpr, SimplifyBoolean>();
+ auto ia_passes = PassManager::Create(IntervalAnalysis{true},
SimplifyAndOrExpr{}, SimplifyBoolean{});
ASSERT_EQ(PassManager::Execute(ia_passes, *Parse("select * from a where a >
1 or a < 3"))->Dump(),
"select * from a where true");
@@ -163,3 +167,105 @@ TEST(IRPassTest, IntervalAnalysis) {
ASSERT_EQ(PassManager::Execute(ia_passes, *Parse("select * from a where a !=
1 and b > 1 and b = 2"))->Dump(),
"select * from a where (and a != 1, b = 2)");
}
+
+static IndexMap MakeIndexMap() {
+ auto f1 = FieldInfo("t1", std::make_unique<redis::SearchTagFieldMetadata>());
+ auto f2 = FieldInfo("t2", std::make_unique<redis::SearchTagFieldMetadata>());
+ f2.metadata->noindex = true;
+ auto f3 = FieldInfo("n1",
std::make_unique<redis::SearchNumericFieldMetadata>());
+ auto f4 = FieldInfo("n2",
std::make_unique<redis::SearchNumericFieldMetadata>());
+ auto f5 = FieldInfo("n3",
std::make_unique<redis::SearchNumericFieldMetadata>());
+ f5.metadata->noindex = true;
+ auto ia = IndexInfo("ia", SearchMetadata());
+ ia.Add(std::move(f1));
+ ia.Add(std::move(f2));
+ ia.Add(std::move(f3));
+ ia.Add(std::move(f4));
+ ia.Add(std::move(f5));
+
+ auto& name = ia.name;
+ IndexMap res;
+ res.emplace(name, std::move(ia));
+ return res;
+}
+
+std::unique_ptr<Node> ParseS(SemaChecker& sc, const std::string& in) {
+ auto res = *Parse(in);
+ EXPECT_EQ(sc.Check(res.get()).Msg(), Status::ok_msg);
+ return res;
+}
+
+TEST(IRPassTest, IndexSelection) {
+ auto index_map = MakeIndexMap();
+ auto sc = SemaChecker(index_map);
+
+ auto passes = PassManager::Default();
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from
ia"))->Dump(), "project *: full-scan ia");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia order by
n1"))->Dump(),
+ "project *: numeric-scan n1, [-inf, inf), asc");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia order by
n1 limit 1"))->Dump(),
+ "project *: (limit 0, 1: numeric-scan n1, [-inf, inf), asc)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia order by
n3"))->Dump(),
+ "project *: (sort n3, asc: full-scan ia)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia order by
n3 limit 1"))->Dump(),
+ "project *: (top-n sort n3, asc, 0, 1: full-scan ia)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n2
= 1 order by n1"))->Dump(),
+ "project *: (filter n2 = 1: numeric-scan n1, [-inf, inf), asc)");
+
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
= 1"))->Dump(),
+ fmt::format("project *: numeric-scan n1, [1, {}), asc",
IntervalSet::NextNum(1)));
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
!= 1"))->Dump(),
+ "project *: (filter n1 != 1: full-scan ia)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 1"))->Dump(),
+ "project *: numeric-scan n1, [1, inf), asc");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 1 and n1 < 2"))->Dump(),
+ "project *: numeric-scan n1, [1, 2), asc");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 1 and n2 >= 2"))->Dump(),
+ "project *: (filter n2 >= 2: numeric-scan n1, [1, inf), asc)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 1 and n2 = 2"))->Dump(),
+ fmt::format("project *: (filter n1 >= 1: numeric-scan n2, [2, {}),
asc)", IntervalSet::NextNum(2)));
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 1 and n3 = 2"))->Dump(),
+ "project *: (filter n3 = 2: numeric-scan n1, [1, inf), asc)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n3
= 1"))->Dump(),
+ "project *: (filter n3 = 1: full-scan ia)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
= 1 and n3 = 2"))->Dump(),
+ fmt::format("project *: (filter n3 = 2: numeric-scan n1, [1, {}),
asc)", IntervalSet::NextNum(1)));
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
= 1 and t1 hastag \"a\""))->Dump(),
+ fmt::format("project *: (filter t1 hastag \"a\": numeric-scan n1,
[1, {}), asc)", IntervalSet::NextNum(1)));
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where t1
hastag \"a\""))->Dump(),
+ "project *: tag-scan t1, a");
+ ASSERT_EQ(
+ PassManager::Execute(passes, ParseS(sc, "select * from ia where t1
hastag \"a\" and t2 hastag \"a\""))->Dump(),
+ "project *: (filter t2 hastag \"a\": tag-scan t1, a)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where t2
hastag \"a\""))->Dump(),
+ "project *: (filter t2 hastag \"a\": full-scan ia)");
+
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 2 or n1 < 1"))->Dump(),
+ "project *: (merge numeric-scan n1, [-inf, 1), asc, numeric-scan
n1, [2, inf), asc)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 1 or n2 >= 2"))->Dump(),
+ "project *: (merge numeric-scan n1, [1, inf), asc, (filter n1 < 1:
numeric-scan n2, [2, inf), asc))");
+ ASSERT_EQ(
+ PassManager::Execute(passes, ParseS(sc, "select * from ia where n1 >= 1
or n2 = 2"))->Dump(),
+ fmt::format("project *: (merge numeric-scan n1, [1, inf), asc, (filter
n1 < 1: numeric-scan n2, [2, {}), asc))",
+ IntervalSet::NextNum(2)));
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
>= 1 or n3 = 2"))->Dump(),
+ "project *: (filter (or n1 >= 1, n3 = 2): full-scan ia)");
+ ASSERT_EQ(PassManager::Execute(passes, ParseS(sc, "select * from ia where n1
= 1 or n3 = 2"))->Dump(),
+ "project *: (filter (or n1 = 1, n3 = 2): full-scan ia)");
+ ASSERT_EQ(
+ PassManager::Execute(passes, ParseS(sc, "select * from ia where n1 = 1
or t1 hastag \"a\""))->Dump(),
+ fmt::format("project *: (merge tag-scan t1, a, (filter not t1 hastag
\"a\": numeric-scan n1, [1, {}), asc))",
+ IntervalSet::NextNum(1)));
+ ASSERT_EQ(
+ PassManager::Execute(passes, ParseS(sc, "select * from ia where t1
hastag \"a\" or t2 hastag \"a\""))->Dump(),
+ "project *: (filter (or t1 hastag \"a\", t2 hastag \"a\"): full-scan
ia)");
+ ASSERT_EQ(
+ PassManager::Execute(passes, ParseS(sc, "select * from ia where t1
hastag \"a\" or t1 hastag \"b\""))->Dump(),
+ "project *: (merge tag-scan t1, a, (filter not t1 hastag \"a\": tag-scan
t1, b))");
+
+ ASSERT_EQ(
+ PassManager::Execute(
+ passes, ParseS(sc, "select * from ia where (n1 < 2 or n1 >= 3) and
(n1 >= 1 and n1 < 4) and not n3 != 1"))
+ ->Dump(),
+ "project *: (filter n3 = 1: (merge numeric-scan n1, [1, 2), asc,
numeric-scan n1, [3, 4), asc))");
+}
diff --git a/tests/cppunit/ir_sema_checker_test.cc
b/tests/cppunit/ir_sema_checker_test.cc
index 75beb686..9068a38b 100644
--- a/tests/cppunit/ir_sema_checker_test.cc
+++ b/tests/cppunit/ir_sema_checker_test.cc
@@ -34,7 +34,7 @@ using namespace kqir;
static auto Parse(const std::string& in) { return
sql::ParseToIR(peg::string_input(in, "test")); }
-IndexMap MakeIndexMap() {
+static IndexMap MakeIndexMap() {
auto f1 = FieldInfo("f1", std::make_unique<redis::SearchTagFieldMetadata>());
auto f2 = FieldInfo("f2",
std::make_unique<redis::SearchNumericFieldMetadata>());
auto f3 = FieldInfo("f3",
std::make_unique<redis::SearchNumericFieldMetadata>());