tp: DBv2 - QueryExecutor
Bug:283763282
Change-Id: I1bc438e9232924cada020aa4fd9ec5b133e8d24d
diff --git a/Android.bp b/Android.bp
index 4265016..b1fa451 100644
--- a/Android.bp
+++ b/Android.bp
@@ -9402,6 +9402,7 @@
"src/trace_processor/db/column.cc",
"src/trace_processor/db/column_storage.cc",
"src/trace_processor/db/null_overlay.cc",
+ "src/trace_processor/db/query_executor.cc",
"src/trace_processor/db/table.cc",
"src/trace_processor/db/view.cc",
],
@@ -9449,6 +9450,7 @@
srcs: [
"src/trace_processor/db/column_storage_overlay_unittest.cc",
"src/trace_processor/db/compare_unittest.cc",
+ "src/trace_processor/db/query_executor_unittest.cc",
"src/trace_processor/db/view_unittest.cc",
],
}
diff --git a/BUILD b/BUILD
index f217e0a..6b1e011 100644
--- a/BUILD
+++ b/BUILD
@@ -1312,6 +1312,8 @@
"src/trace_processor/db/compare.h",
"src/trace_processor/db/null_overlay.cc",
"src/trace_processor/db/null_overlay.h",
+ "src/trace_processor/db/query_executor.cc",
+ "src/trace_processor/db/query_executor.h",
"src/trace_processor/db/table.cc",
"src/trace_processor/db/table.h",
"src/trace_processor/db/typed_column.h",
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index 1ef081a..de4dc7f 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -26,6 +26,8 @@
"compare.h",
"null_overlay.cc",
"null_overlay.h",
+ "query_executor.cc",
+ "query_executor.h",
"table.cc",
"table.h",
"typed_column.h",
@@ -54,6 +56,7 @@
sources = [
"column_storage_overlay_unittest.cc",
"compare_unittest.cc",
+ "query_executor_unittest.cc",
"view_unittest.cc",
]
deps = [
@@ -64,6 +67,8 @@
"../../base",
"../tables",
"../views",
+ "overlays",
+ "storage",
]
}
diff --git a/src/trace_processor/db/overlays/BUILD.gn b/src/trace_processor/db/overlays/BUILD.gn
index c35891f..21615b0 100644
--- a/src/trace_processor/db/overlays/BUILD.gn
+++ b/src/trace_processor/db/overlays/BUILD.gn
@@ -29,6 +29,7 @@
"../../../../gn:default_deps",
"../../../base",
"../../containers",
+ "../storage",
]
}
diff --git a/src/trace_processor/db/overlays/null_overlay.cc b/src/trace_processor/db/overlays/null_overlay.cc
index ca44204..3ecb317 100644
--- a/src/trace_processor/db/overlays/null_overlay.cc
+++ b/src/trace_processor/db/overlays/null_overlay.cc
@@ -39,6 +39,9 @@
if (op != OverlayOp::kIsNull)
return {std::move(res)};
+ if (res.CountSetBits() == 0)
+ return {non_null_->Not()};
+
BitVector not_non_null = non_null_->Not();
res.Or(not_non_null);
return {std::move(res)};
@@ -50,7 +53,7 @@
PERFETTO_DCHECK(t_iv.indices.size() <= non_null_->size());
if (op != OverlayOp::kOther)
- return BitVector();
+ return BitVector(t_iv.size(), false);
BitVector in_storage(static_cast<uint32_t>(t_iv.indices.size()), false);
@@ -81,7 +84,7 @@
OverlayOp op,
const TableIndexVector& t_iv_overlay_idx) const {
if (op == OverlayOp::kOther)
- return BitVector();
+ return BitVector(t_iv_overlay_idx.size(), false);
BitVector res(static_cast<uint32_t>(t_iv_overlay_idx.indices.size()), false);
if (op == OverlayOp::kIsNull) {
diff --git a/src/trace_processor/db/overlays/null_overlay_unittest.cc b/src/trace_processor/db/overlays/null_overlay_unittest.cc
index 9bec414..0c02ad7 100644
--- a/src/trace_processor/db/overlays/null_overlay_unittest.cc
+++ b/src/trace_processor/db/overlays/null_overlay_unittest.cc
@@ -77,7 +77,7 @@
BitVector lookup_bv =
overlay.IsStorageLookupRequired(OverlayOp::kIsNull, {table_idx});
- ASSERT_EQ(lookup_bv.size(), 0u);
+ ASSERT_EQ(lookup_bv.CountSetBits(), 0u);
}
TEST(NullOverlay, IsStorageLookupRequiredOtherOp) {
@@ -112,7 +112,7 @@
std::vector<uint32_t> table_idx{0, 3, 4};
BitVector idx_search_bv = overlay.IndexSearch(OverlayOp::kOther, {table_idx});
- ASSERT_EQ(idx_search_bv.size(), 0u);
+ ASSERT_EQ(idx_search_bv.CountSetBits(), 0u);
}
TEST(NullOverlay, IndexSearchIsNullOp) {
diff --git a/src/trace_processor/db/overlays/selector_overlay.cc b/src/trace_processor/db/overlays/selector_overlay.cc
index ddcb8c1..bc6fcfb 100644
--- a/src/trace_processor/db/overlays/selector_overlay.cc
+++ b/src/trace_processor/db/overlays/selector_overlay.cc
@@ -15,6 +15,7 @@
*/
#include "src/trace_processor/db/overlays/selector_overlay.h"
+#include "src/trace_processor/containers/bit_vector.h"
namespace perfetto {
namespace trace_processor {
@@ -31,7 +32,7 @@
TableBitVector SelectorOverlay::MapToTableBitVector(StorageBitVector s_bv,
OverlayOp) const {
- PERFETTO_DCHECK(s_bv.bv.size() == selected_->size());
+ PERFETTO_DCHECK(selected_->size() >= s_bv.bv.size());
BitVector res(selected_->CountSetBits());
// TODO(b/283763282): Implement this variation of |UpdateSetBits| in
// BitVector.
diff --git a/src/trace_processor/db/overlays/types.h b/src/trace_processor/db/overlays/types.h
index 4e72a41..7978ada 100644
--- a/src/trace_processor/db/overlays/types.h
+++ b/src/trace_processor/db/overlays/types.h
@@ -18,6 +18,7 @@
#include "src/trace_processor/containers/bit_vector.h"
#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/types.h"
namespace perfetto {
namespace trace_processor {
@@ -46,11 +47,15 @@
// Represents a vector of indices in the table space.
struct TableIndexVector {
std::vector<uint32_t> indices;
+
+ uint32_t size() const { return static_cast<uint32_t>(indices.size()); }
};
// Represents a vector of indices in the storage space.
struct StorageIndexVector {
std::vector<uint32_t> indices;
+
+ uint32_t size() const { return static_cast<uint32_t>(indices.size()); }
};
// A subset of FilterOp containing operations which can be handled by
@@ -61,6 +66,16 @@
kOther,
};
+inline OverlayOp FilterOpToOverlayOp(FilterOp op) {
+ if (op == FilterOp::kIsNull) {
+ return OverlayOp::kIsNull;
+ }
+ if (op == FilterOp::kIsNotNull) {
+ return OverlayOp::kIsNotNull;
+ }
+ return OverlayOp::kOther;
+}
+
// Contains estimates of the cost for each of method in this class per row.
struct CostEstimatePerRow {
uint32_t to_storage_range;
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
new file mode 100644
index 0000000..bab60bb
--- /dev/null
+++ b/src/trace_processor/db/query_executor.cc
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed 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 <array>
+#include <numeric>
+#include <vector>
+
+#include "src/trace_processor/db/query_executor.h"
+#include "src/trace_processor/db/table.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+
+using Range = RowMap::Range;
+using OverlayOp = overlays::OverlayOp;
+using StorageRange = overlays::StorageRange;
+using TableRange = overlays::TableRange;
+using Storage = storage::Storage;
+using StorageOverlay = overlays::StorageOverlay;
+using TableIndexVector = overlays::TableIndexVector;
+using StorageIndexVector = overlays::StorageIndexVector;
+using TableBitVector = overlays::TableBitVector;
+using StorageBitVector = overlays::StorageBitVector;
+
+// Helper struct to simplify operations on |global| and |current| sets of
+// indices. Having this coupling enables efficient implementation of
+// IndexedColumnFilter.
+struct IndexFilterHelper {
+ explicit IndexFilterHelper(std::vector<uint32_t> indices)
+ : IndexFilterHelper(indices, std::move(indices)) {}
+
+ // Removes pairs of elements that are not set in the |bv| and returns
+ // Indices made of them.
+ static std::pair<IndexFilterHelper, IndexFilterHelper> Partition(
+ IndexFilterHelper indices,
+ const BitVector& bv) {
+ if (bv.CountSetBits() == 0) {
+ return {IndexFilterHelper(), indices};
+ }
+
+ IndexFilterHelper set_partition;
+ IndexFilterHelper non_set_partition;
+ for (auto it = bv.IterateAllBits(); it; it.Next()) {
+ uint32_t idx = it.index();
+ if (it.IsSet()) {
+ set_partition.PushBack({indices.current_[idx], indices.global_[idx]});
+ } else {
+ non_set_partition.PushBack(
+ {indices.current_[idx], indices.global_[idx]});
+ }
+ }
+ return {set_partition, non_set_partition};
+ }
+
+ // Removes pairs of elements that are not set in the |bv|. Returns count of
+ // removed elements.
+ uint32_t KeepAtSet(BitVector filter_nulls) {
+ PERFETTO_CHECK(filter_nulls.size() == current_.size() ||
+ filter_nulls.CountSetBits() == 0);
+ uint32_t count_removed =
+ static_cast<uint32_t>(current_.size()) - filter_nulls.CountSetBits();
+
+ uint32_t i = 0;
+ auto filter = [&i, &filter_nulls](uint32_t) {
+ return !filter_nulls.IsSet(i++);
+ };
+
+ auto current_it = std::remove_if(current_.begin(), current_.end(), filter);
+ current_.erase(current_it, current_.end());
+
+ i = 0;
+ auto global_it = std::remove_if(global_.begin(), global_.end(), filter);
+ global_.erase(global_it, global_.end());
+
+ return count_removed;
+ }
+
+ std::vector<uint32_t>& current() { return current_; }
+
+ std::vector<uint32_t>& global() { return global_; }
+
+ private:
+ IndexFilterHelper() = default;
+ IndexFilterHelper(std::vector<uint32_t> current, std::vector<uint32_t> global)
+ : current_(std::move(current)), global_(std::move(global)) {
+ PERFETTO_CHECK(current_.size() == global_.size());
+ }
+
+ void PushBack(std::pair<uint32_t, uint32_t> cur_and_global_idx) {
+ current_.push_back(cur_and_global_idx.first);
+ global_.push_back(cur_and_global_idx.second);
+ }
+
+ std::vector<uint32_t> current_;
+ std::vector<uint32_t> global_;
+};
+} // namespace
+
+void QueryExecutor::FilterColumn(const Constraint& c,
+ const SimpleColumn& col,
+ RowMap* rm) {
+ uint32_t rm_first = rm->Get(0);
+ uint32_t rm_last = rm->Get(rm->size() - 1);
+ uint32_t range_size = rm_last - rm_first;
+ // If the range is less than 50% full and size() < 1024, choose the index
+ // algorithm.
+ // TODO(b/283763282):Use Overlay estimations.
+ if (rm->size() < 1024 &&
+ (static_cast<double>(rm->size()) / range_size < 0.5)) {
+ *rm = IndexedColumnFilter(c, col, rm);
+ return;
+ }
+ rm->Intersect(BoundedColumnFilter(c, col, rm));
+}
+
+RowMap QueryExecutor::BoundedColumnFilter(const Constraint& c,
+ const SimpleColumn& col,
+ RowMap* rm) {
+ // TODO(b/283763282): We should align these to word boundaries.
+ TableRange table_range{Range(rm->Get(0), rm->Get(rm->size() - 1) + 1)};
+ base::SmallVector<Range, kMaxOverlayCount> overlay_bounds;
+
+ for (const auto& overlay : col.overlays) {
+ StorageRange storage_range = overlay->MapToStorageRange(table_range);
+ overlay_bounds.emplace_back(storage_range.range);
+ table_range = TableRange({storage_range.range});
+ }
+
+ // Use linear search algorithm on storage.
+ overlays::StorageBitVector filtered_storage{
+ col.storage->LinearSearch(c.op, c.value, table_range.range)};
+
+ for (uint32_t i = 0; i < col.overlays.size(); ++i) {
+ uint32_t rev_i = static_cast<uint32_t>(col.overlays.size()) - 1 - i;
+ TableBitVector mapped_to_table = col.overlays[rev_i]->MapToTableBitVector(
+ std::move(filtered_storage), overlays::FilterOpToOverlayOp(c.op));
+ filtered_storage = StorageBitVector({std::move(mapped_to_table.bv)});
+ }
+ return RowMap(std::move(filtered_storage.bv));
+}
+
+RowMap QueryExecutor::IndexedColumnFilter(const Constraint& c,
+ const SimpleColumn& col,
+ RowMap* rm) {
+ // Create outmost TableIndexVector.
+ std::vector<uint32_t> table_indices;
+ table_indices.reserve(rm->size());
+ for (auto it = rm->IterateRows(); it; it.Next()) {
+ table_indices.push_back(it.index());
+ }
+
+ // Datastructures for storing data across overlays.
+ IndexFilterHelper to_filter(std::move(table_indices));
+ std::vector<uint32_t> valid;
+ uint32_t count_removed = 0;
+
+ // Fetch the list of indices that require storage lookup and deal with all
+ // of the indices that can be compared before it.
+ OverlayOp op = overlays::FilterOpToOverlayOp(c.op);
+ for (auto overlay : col.overlays) {
+ BitVector partition =
+ overlay->IsStorageLookupRequired(op, {to_filter.current()});
+
+ // Most overlays don't require partitioning.
+ if (partition.CountSetBits() == partition.size()) {
+ to_filter.current() =
+ overlay->MapToStorageIndexVector({to_filter.current()}).indices;
+ continue;
+ }
+
+ // Separate indices that don't require storage lookup. Those can be dealt
+ // with in each pass.
+ auto [storage_lookup, no_storage_lookup] =
+ IndexFilterHelper::Partition(to_filter, partition);
+ to_filter = storage_lookup;
+
+ // Erase the values which don't match the constraint and add the
+ // remaining ones to the result.
+ BitVector valid_bv =
+ overlay->IndexSearch(op, {no_storage_lookup.current()});
+ count_removed += no_storage_lookup.KeepAtSet(std::move(valid_bv));
+ valid.insert(valid.end(), no_storage_lookup.global().begin(),
+ no_storage_lookup.global().end());
+
+ // Update the current indices to the next storage overlay.
+ to_filter.current() =
+ overlay->MapToStorageIndexVector({to_filter.current()}).indices;
+ }
+
+ BitVector matched_in_storage = col.storage->IndexSearch(
+ c.op, c.value, to_filter.current().data(),
+ static_cast<uint32_t>(to_filter.current().size()));
+ count_removed += to_filter.KeepAtSet(std::move(matched_in_storage));
+ valid.insert(valid.end(), to_filter.global().begin(),
+ to_filter.global().end());
+
+ PERFETTO_CHECK(rm->size() == valid.size() + count_removed);
+
+ std::sort(valid.begin(), valid.end());
+ return RowMap(std::move(valid));
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/db/query_executor.h b/src/trace_processor/db/query_executor.h
new file mode 100644
index 0000000..d70053f
--- /dev/null
+++ b/src/trace_processor/db/query_executor.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+#ifndef SRC_TRACE_PROCESSOR_DB_QUERY_EXECUTOR_H_
+#define SRC_TRACE_PROCESSOR_DB_QUERY_EXECUTOR_H_
+
+#include <array>
+#include <numeric>
+#include <vector>
+
+#include "perfetto/ext/base/small_vector.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/overlays/storage_overlay.h"
+#include "src/trace_processor/db/overlays/types.h"
+#include "src/trace_processor/db/storage/storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Responsible for executing filtering/sorting operations on a single Table.
+// TODO(b/283763282): Introduce sorting.
+class QueryExecutor {
+ public:
+ static constexpr uint32_t kMaxOverlayCount = 8;
+
+ // Overlay-based definition of the column.
+ struct SimpleColumn {
+ base::SmallVector<const overlays::StorageOverlay*, kMaxOverlayCount>
+ overlays;
+ const storage::Storage* storage;
+ };
+
+ // |row_count| is the size of the last overlay.
+ QueryExecutor(const std::vector<SimpleColumn>& columns, uint32_t row_count)
+ : columns_(columns), row_count_(row_count) {}
+
+ // Apply all the constraints on the data and return the filtered RowMap.
+ RowMap Filter(const std::vector<Constraint>& cs) {
+ RowMap rm(0, row_count_);
+ for (const auto& c : cs) {
+ FilterColumn(c, columns_[c.col_idx], &rm);
+ }
+ return rm;
+ }
+
+ // Sorts using vector of Order.
+ // TODO(b/283763282): Implement.
+ RowMap Sort(const std::vector<Order>&) { PERFETTO_FATAL("Not implemented."); }
+
+ // Enables QueryExecutor::Filter on Table columns.
+ // TODO(b/283763282): Implement.
+ static RowMap FilterLegacy(const Table*, const std::vector<Constraint>&) {
+ PERFETTO_FATAL("Not implemented.");
+ }
+
+ // Enables QueryExecutor::Sort on Table columns.
+ // TODO(b/283763282): Implement.
+ static RowMap SortLegacy(const Table*, const std::vector<Order>&) {
+ PERFETTO_FATAL("Not implemented.");
+ }
+
+ // Used only in unittests. Exposes private function.
+ static RowMap BoundedColumnFilterForTesting(const Constraint& c,
+ const SimpleColumn& col,
+ RowMap* rm) {
+ return BoundedColumnFilter(c, col, rm);
+ }
+
+ // Used only in unittests. Exposes private function.
+ static RowMap IndexedColumnFilterForTesting(const Constraint& c,
+ const SimpleColumn& col,
+ RowMap* rm) {
+ return IndexedColumnFilter(c, col, rm);
+ }
+
+ private:
+ // Updates RowMap with result of filtering single column using the Constraint.
+ static void FilterColumn(const Constraint&, const SimpleColumn&, RowMap*);
+
+ // Filters the column using Range algorithm - tries to find the smallest Range
+ // to filter the storage with.
+ static RowMap BoundedColumnFilter(const Constraint&,
+ const SimpleColumn&,
+ RowMap*);
+
+ // Filters the column using Index algorithm - finds the indices to filter the
+ // storage with.
+ static RowMap IndexedColumnFilter(const Constraint&,
+ const SimpleColumn&,
+ RowMap*);
+
+ std::vector<SimpleColumn> columns_;
+
+ // Number of rows in the outmost overlay.
+ uint32_t row_count_ = 0;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_DB_QUERY_EXECUTOR_H_
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
new file mode 100644
index 0000000..01fff7a
--- /dev/null
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed 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 "src/trace_processor/db/query_executor.h"
+#include "src/trace_processor/db/overlays/null_overlay.h"
+#include "src/trace_processor/db/overlays/selector_overlay.h"
+#include "src/trace_processor/db/storage/numeric_storage.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using OverlaysVec = base::SmallVector<const overlays::StorageOverlay*,
+ QueryExecutor::kMaxOverlayCount>;
+using NumericStorage = storage::NumericStorage;
+using SimpleColumn = QueryExecutor::SimpleColumn;
+using NullOverlay = overlays::NullOverlay;
+using SelectorOverlay = overlays::SelectorOverlay;
+
+TEST(QueryExecutor, OnlyStorageRange) {
+ std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
+ NumericStorage storage(storage_data.data(), 5, ColumnType::kInt64);
+ SimpleColumn col{OverlaysVec(), &storage};
+
+ Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
+ RowMap rm(0, 5);
+ RowMap res = QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 3u);
+ ASSERT_EQ(res.Get(0), 2u);
+}
+
+TEST(QueryExecutor, OnlyStorageRangeIsNull) {
+ std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
+ NumericStorage storage(storage_data.data(), 5, ColumnType::kInt64);
+ SimpleColumn col{OverlaysVec(), &storage};
+
+ Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+ RowMap rm(0, 5);
+ RowMap res = QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 0u);
+}
+
+TEST(QueryExecutor, OnlyStorageIndex) {
+ // Setup storage
+ std::vector<int64_t> storage_data(10);
+ std::iota(storage_data.begin(), storage_data.end(), 0);
+ std::transform(storage_data.begin(), storage_data.end(), storage_data.begin(),
+ [](int64_t n) { return n % 5; });
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+
+ SimpleColumn col{OverlaysVec(), &storage};
+ Constraint c{0, FilterOp::kLt, SqlValue::Long(2)};
+ RowMap rm(0, 10);
+ RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 4u);
+ ASSERT_EQ(res.Get(0), 0u);
+ ASSERT_EQ(res.Get(1), 1u);
+ ASSERT_EQ(res.Get(2), 5u);
+ ASSERT_EQ(res.Get(3), 6u);
+}
+
+TEST(QueryExecutor, OnlyStorageIndexIsNull) {
+ std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
+ NumericStorage storage(storage_data.data(), 5, ColumnType::kInt64);
+ SimpleColumn col{OverlaysVec(), &storage};
+
+ Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+ RowMap rm(0, 5);
+ RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 0u);
+}
+
+TEST(QueryExecutor, NullOverlayBounds) {
+ std::vector<int64_t> storage_data(5);
+ std::iota(storage_data.begin(), storage_data.end(), 0);
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+ BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
+ overlays::NullOverlay overlay(&bv);
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&overlay);
+
+ SimpleColumn col{overlays_vec, &storage};
+
+ Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
+ RowMap rm(0, 10);
+ RowMap res = QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 2u);
+ ASSERT_EQ(res.Get(0), 4u);
+ ASSERT_EQ(res.Get(1), 8u);
+}
+
+TEST(QueryExecutor, NullOverlayRangeIsNull) {
+ std::vector<int64_t> storage_data(5);
+ std::iota(storage_data.begin(), storage_data.end(), 0);
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+ BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
+ overlays::NullOverlay overlay(&bv);
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&overlay);
+
+ SimpleColumn col{overlays_vec, &storage};
+
+ Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+ RowMap rm(0, 10);
+ RowMap res = QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 5u);
+ ASSERT_EQ(res.Get(0), 2u);
+ ASSERT_EQ(res.Get(1), 5u);
+ ASSERT_EQ(res.Get(2), 6u);
+ ASSERT_EQ(res.Get(3), 7u);
+ ASSERT_EQ(res.Get(4), 9u);
+}
+
+TEST(QueryExecutor, NullOverlayIndex) {
+ std::vector<int64_t> storage_data(6);
+ std::iota(storage_data.begin(), storage_data.end(), 0);
+ std::transform(storage_data.begin(), storage_data.end(), storage_data.begin(),
+ [](int64_t n) { return n % 3; });
+ NumericStorage storage(storage_data.data(), 6, ColumnType::kInt64);
+
+ BitVector bv{1, 1, 0, 1, 1, 0, 1, 0, 0, 1};
+ NullOverlay overlay(&bv);
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&overlay);
+
+ SimpleColumn col{overlays_vec, &storage};
+
+ Constraint c{0, FilterOp::kGe, SqlValue::Long(1)};
+ RowMap rm(0, 10);
+ RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 4u);
+ ASSERT_EQ(res.Get(0), 1u);
+ ASSERT_EQ(res.Get(1), 3u);
+ ASSERT_EQ(res.Get(2), 6u);
+ ASSERT_EQ(res.Get(3), 9u);
+}
+
+TEST(QueryExecutor, NullOverlayIndexIsNull) {
+ std::vector<int64_t> storage_data(5);
+ std::iota(storage_data.begin(), storage_data.end(), 0);
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+ BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
+ overlays::NullOverlay overlay(&bv);
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&overlay);
+
+ SimpleColumn col{overlays_vec, &storage};
+
+ Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+ RowMap rm(0, 10);
+ RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 5u);
+ ASSERT_EQ(res.Get(0), 2u);
+ ASSERT_EQ(res.Get(1), 5u);
+ ASSERT_EQ(res.Get(2), 6u);
+ ASSERT_EQ(res.Get(3), 7u);
+ ASSERT_EQ(res.Get(4), 9u);
+}
+
+TEST(QueryExecutor, SelectorOverlayBounds) {
+ std::vector<int64_t> storage_data(5);
+ std::iota(storage_data.begin(), storage_data.end(), 0);
+ NumericStorage storage(storage_data.data(), 5, ColumnType::kInt64);
+
+ BitVector bv{1, 1, 0, 0, 1};
+ SelectorOverlay overlay(&bv);
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&overlay);
+
+ SimpleColumn col{overlays_vec, &storage};
+
+ Constraint c{0, FilterOp::kGt, SqlValue::Long(1)};
+ RowMap rm(0, 3);
+ RowMap res = QueryExecutor::BoundedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 1u);
+ ASSERT_EQ(res.Get(0), 2u);
+}
+
+TEST(QueryExecutor, SelectorOverlayIndex) {
+ std::vector<int64_t> storage_data(10);
+ std::iota(storage_data.begin(), storage_data.end(), 0);
+ std::transform(storage_data.begin(), storage_data.end(), storage_data.begin(),
+ [](int64_t n) { return n % 5; });
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+
+ BitVector bv{1, 1, 0, 1, 1, 0, 1, 0, 0, 1};
+ SelectorOverlay overlay(&bv);
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&overlay);
+
+ SimpleColumn col{overlays_vec, &storage};
+
+ Constraint c{0, FilterOp::kGe, SqlValue::Long(2)};
+ RowMap rm(0, 6);
+ RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, col, &rm);
+
+ ASSERT_EQ(res.size(), 3u);
+ ASSERT_EQ(res.Get(0), 2u);
+ ASSERT_EQ(res.Get(1), 3u);
+ ASSERT_EQ(res.Get(2), 5u);
+}
+
+TEST(QueryExecutor, SingleConstraintWithNullAndSelector) {
+ std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 0, 1, 2, 3, 4};
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+
+ // Select 6 elements from storage, resulting in a vector {0, 1, 3, 4, 1, 2}.
+ BitVector selector_bv{1, 1, 0, 1, 1, 0, 1, 1, 0, 0};
+ SelectorOverlay selector_overlay(&selector_bv);
+
+ // Add nulls, final vector {0, 1, NULL, 3, 4, NULL, 1, 2, NULL}.
+ BitVector null_bv{1, 1, 0, 1, 1, 0, 1, 1, 0};
+ NullOverlay null_overlay(&null_bv);
+
+ // Create the column.
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&null_overlay);
+ overlays_vec.emplace_back(&selector_overlay);
+ SimpleColumn col{overlays_vec, &storage};
+
+ // Filter.
+ Constraint c{0, FilterOp::kGe, SqlValue::Long(2)};
+ QueryExecutor exec({col}, 9);
+ RowMap res = exec.Filter({c});
+
+ ASSERT_EQ(res.size(), 3u);
+ ASSERT_EQ(res.Get(0), 3u);
+ ASSERT_EQ(res.Get(1), 4u);
+ ASSERT_EQ(res.Get(2), 7u);
+}
+
+TEST(QueryExecutor, IsNull) {
+ std::vector<int64_t> storage_data{0, 1, 2, 3, 4, 0, 1, 2, 3, 4};
+ NumericStorage storage(storage_data.data(), 10, ColumnType::kInt64);
+
+ // Select 6 elements from storage, resulting in a vector {0, 1, 3, 4, 1, 2}.
+ BitVector selector_bv{1, 1, 0, 1, 1, 0, 1, 1, 0, 0};
+ SelectorOverlay selector_overlay(&selector_bv);
+
+ // Add nulls, final vector {0, 1, NULL, 3, 4, NULL, 1, 2, NULL}.
+ BitVector null_bv{1, 1, 0, 1, 1, 0, 1, 1, 0};
+ NullOverlay null_overlay(&null_bv);
+
+ // Create the column.
+ OverlaysVec overlays_vec;
+ overlays_vec.emplace_back(&null_overlay);
+ overlays_vec.emplace_back(&selector_overlay);
+ SimpleColumn col{overlays_vec, &storage};
+
+ // Filter.
+ Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
+ QueryExecutor exec({col}, 9);
+ RowMap res = exec.Filter({c});
+
+ ASSERT_EQ(res.size(), 3u);
+ ASSERT_EQ(res.Get(0), 2u);
+ ASSERT_EQ(res.Get(1), 5u);
+ ASSERT_EQ(res.Get(2), 8u);
+}
+
+} // namespace
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
index eb1dd08..3a074af 100644
--- a/src/trace_processor/db/storage/numeric_storage.cc
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -240,7 +240,7 @@
return BitVector(size(), true);
if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
- return BitVector();
+ return BitVector(size(), false);
BitVector::Builder builder(range.end);
builder.Skip(range.start);
@@ -267,7 +267,7 @@
return BitVector(size(), true);
if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
- return BitVector();
+ return BitVector(size(), false);
BitVector::Builder builder(indices_count);
std::visit(