Merge "tp: rewrite OrderedIndexSearch to be truly O(logn)" into main
diff --git a/Android.bp b/Android.bp
index e83139e..972f828 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2391,6 +2391,7 @@
         ":perfetto_src_shared_lib_test_utils",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_export_json",
@@ -16061,6 +16062,7 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_export_json",
@@ -16298,6 +16300,7 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_importers_common_common",
         ":perfetto_src_trace_processor_importers_common_parser_types",
@@ -16457,6 +16460,7 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
+        ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_export_json",
diff --git a/BUILD b/BUILD
index 430f9dc..537ccc4 100644
--- a/BUILD
+++ b/BUILD
@@ -217,6 +217,7 @@
         ":src_kernel_utils_syscall_table",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
@@ -1426,6 +1427,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/db:compare
+perfetto_filegroup(
+    name = "src_trace_processor_db_compare",
+    srcs = [
+        "src/trace_processor/db/compare.h",
+    ],
+)
+
 # GN target: //src/trace_processor/db:db
 perfetto_filegroup(
     name = "src_trace_processor_db_db",
@@ -5979,6 +5988,7 @@
     srcs = [
         ":src_kernel_utils_syscall_table",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
@@ -6158,6 +6168,7 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
@@ -6397,6 +6408,7 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
+        ":src_trace_processor_db_compare",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
         ":src_trace_processor_export_json",
diff --git a/src/trace_processor/containers/string_pool.h b/src/trace_processor/containers/string_pool.h
index 75a57a2..2e03592 100644
--- a/src/trace_processor/containers/string_pool.h
+++ b/src/trace_processor/containers/string_pool.h
@@ -17,21 +17,25 @@
 #ifndef SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
 #define SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_
 
-#include <stddef.h>
-#include <stdint.h>
-
+#include <cstddef>
+#include <cstdint>
+#include <functional>
 #include <limits>
+#include <memory>
 #include <optional>
+#include <string>
+#include <utility>
 #include <vector>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/paged_memory.h"
+#include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/proto_utils.h"
 #include "src/trace_processor/containers/null_term_string_view.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // Interns strings in a string pool and hands out compact StringIds which can
 // be used to retrieve the string in O(1).
@@ -40,34 +44,38 @@
   struct Id {
     Id() = default;
 
-    bool operator==(const Id& other) const { return other.id == id; }
-    bool operator!=(const Id& other) const { return !(other == *this); }
-    bool operator<(const Id& other) const { return id < other.id; }
+    constexpr bool operator==(const Id& other) const { return other.id == id; }
+    constexpr bool operator!=(const Id& other) const {
+      return !(other == *this);
+    }
+    constexpr bool operator<(const Id& other) const { return id < other.id; }
 
-    bool is_null() const { return id == 0u; }
+    constexpr bool is_null() const { return id == 0u; }
 
-    bool is_large_string() const { return id & kLargeStringFlagBitMask; }
+    constexpr bool is_large_string() const {
+      return id & kLargeStringFlagBitMask;
+    }
 
-    uint32_t block_offset() const { return id & kBlockOffsetBitMask; }
+    constexpr uint32_t block_offset() const { return id & kBlockOffsetBitMask; }
 
-    uint32_t block_index() const {
+    constexpr uint32_t block_index() const {
       return (id & kBlockIndexBitMask) >> kNumBlockOffsetBits;
     }
 
-    uint32_t large_string_index() const {
+    constexpr uint32_t large_string_index() const {
       PERFETTO_DCHECK(is_large_string());
       return id & ~kLargeStringFlagBitMask;
     }
 
-    uint32_t raw_id() const { return id; }
+    constexpr uint32_t raw_id() const { return id; }
 
-    static Id LargeString(size_t index) {
+    static constexpr Id LargeString(size_t index) {
       PERFETTO_DCHECK(index <= static_cast<uint32_t>(index));
       PERFETTO_DCHECK(!(index & kLargeStringFlagBitMask));
       return Id(kLargeStringFlagBitMask | static_cast<uint32_t>(index));
     }
 
-    static Id BlockString(size_t index, uint32_t offset) {
+    static constexpr Id BlockString(size_t index, uint32_t offset) {
       PERFETTO_DCHECK(index < (1u << (kNumBlockIndexBits + 1)));
       PERFETTO_DCHECK(offset < (1u << (kNumBlockOffsetBits + 1)));
       return Id(~kLargeStringFlagBitMask &
@@ -80,7 +88,7 @@
     static constexpr Id Null() { return Id(0u); }
 
    private:
-    constexpr Id(uint32_t i) : id(i) {}
+    constexpr explicit Id(uint32_t i) : id(i) {}
 
     uint32_t id;
   };
@@ -88,7 +96,7 @@
   // Iterator over the strings in the pool.
   class Iterator {
    public:
-    Iterator(const StringPool*);
+    explicit Iterator(const StringPool*);
 
     explicit operator bool() const;
     Iterator& operator++();
@@ -278,7 +286,7 @@
   static NullTermStringView GetFromBlockPtr(const uint8_t* ptr) {
     uint32_t size = 0;
     const uint8_t* str_ptr = ReadSize(ptr, &size);
-    return NullTermStringView(reinterpret_cast<const char*>(str_ptr), size);
+    return {reinterpret_cast<const char*>(str_ptr), size};
   }
 
   // Lookup a string in the |large_strings_| vector. |id| should have the MSB
@@ -288,7 +296,7 @@
     size_t index = id.large_string_index();
     PERFETTO_DCHECK(index < large_strings_.size());
     const std::string* str = large_strings_[index].get();
-    return NullTermStringView(str->c_str(), str->size());
+    return {str->c_str(), str->size()};
   }
 
   // The actual memory storing the strings.
@@ -308,8 +316,7 @@
       string_index_{/*initial_capacity=*/4096u};
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 template <>
 struct std::hash<::perfetto::trace_processor::StringPool::Id> {
diff --git a/src/trace_processor/db/column/BUILD.gn b/src/trace_processor/db/column/BUILD.gn
index d53e7ae..f77887f 100644
--- a/src/trace_processor/db/column/BUILD.gn
+++ b/src/trace_processor/db/column/BUILD.gn
@@ -43,6 +43,7 @@
     "utils.h",
   ]
   deps = [
+    "..:compare",
     "../..:metatrace",
     "../../../../gn:default_deps",
     "../../../../include/perfetto/trace_processor",
diff --git a/src/trace_processor/db/column/arrangement_overlay.cc b/src/trace_processor/db/column/arrangement_overlay.cc
index c31d7c2..e7bc8ff 100644
--- a/src/trace_processor/db/column/arrangement_overlay.cc
+++ b/src/trace_processor/db/column/arrangement_overlay.cc
@@ -68,12 +68,21 @@
 
   if (does_arrangement_order_storage_ && op != FilterOp::kGlob &&
       op != FilterOp::kRegex) {
-    Range inner_res = inner_->OrderedIndexSearchValidated(
-        op, sql_val,
-        OrderedIndices{arrangement_->data() + in.start, in.size(),
-                       arrangement_state_});
+    OrderedIndices indices{arrangement_->data() + in.start, in.size(),
+                           arrangement_state_};
+    if (op == FilterOp::kNe) {
+      // Do an equality search and "invert" the range.
+      Range inner_res =
+          inner_->OrderedIndexSearchValidated(FilterOp::kEq, sql_val, indices);
+      BitVector bv(in.start);
+      bv.Resize(in.start + inner_res.start, true);
+      bv.Resize(in.start + inner_res.end, false);
+      bv.Resize(in.end, true);
+      return RangeOrBitVector(std::move(bv));
+    }
+    Range inner_res = inner_->OrderedIndexSearchValidated(op, sql_val, indices);
     return RangeOrBitVector(
-        Range(inner_res.start + in.start, inner_res.end + in.start));
+        Range(in.start + inner_res.start, in.start + inner_res.end));
   }
 
   const auto& arrangement = *arrangement_;
@@ -196,6 +205,11 @@
   return inner_->MinElement(indices);
 }
 
+SqlValue ArrangementOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return inner_->Get_AvoidUsingBecauseSlow((*arrangement_)[index]);
+}
+
 void ArrangementOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* arrangement_overlay = storage->set_arrangement_overlay();
   arrangement_overlay->set_values(
diff --git a/src/trace_processor/db/column/arrangement_overlay.h b/src/trace_processor/db/column/arrangement_overlay.h
index 3656b07..85b9ce4 100644
--- a/src/trace_processor/db/column/arrangement_overlay.h
+++ b/src/trace_processor/db/column/arrangement_overlay.h
@@ -19,10 +19,10 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
-#include "perfetto/base/logging.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/db/column/data_layer.h"
 #include "src/trace_processor/db/column/types.h"
@@ -61,13 +61,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override {
-      PERFETTO_FATAL(
-          "OrderedIndexSearch can't be called on ArrangementOverlay");
-    }
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -78,6 +71,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override {
diff --git a/src/trace_processor/db/column/arrangement_overlay_unittest.cc b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
index 55f310b..8820336 100644
--- a/src/trace_processor/db/column/arrangement_overlay_unittest.cc
+++ b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
@@ -108,15 +108,20 @@
 }
 
 TEST(ArrangementOverlay, OrderingSearch) {
-  std::vector<uint32_t> arrangement{0, 2, 4, 1, 3};
-  auto fake = FakeStorageChain::SearchSubset(5, BitVector({0, 1, 0, 1, 0}));
+  std::vector<uint32_t> numeric_data{0, 1, 2, 0, 1, 0};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
+  std::vector<uint32_t> arrangement{0, 3, 5, 1, 4, 2};
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain =
-      storage.MakeChain(std::move(fake), DataLayer::ChainCreationArgs(true));
+  auto chain = storage.MakeChain(numeric.MakeChain(),
+                                 DataLayer::ChainCreationArgs(true));
 
   RangeOrBitVector res =
-      chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 5));
+      chain->Search(FilterOp::kGe, SqlValue::Long(1u), Range(0, 5));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4));
+
+  res = chain->Search(FilterOp::kNe, SqlValue::Long(1u), Range(1, 6));
+  ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1, 2, 5));
 }
 
 TEST(ArrangementOverlay, StableSort) {
diff --git a/src/trace_processor/db/column/data_layer.cc b/src/trace_processor/db/column/data_layer.cc
index 634cbce..a570415 100644
--- a/src/trace_processor/db/column/data_layer.cc
+++ b/src/trace_processor/db/column/data_layer.cc
@@ -16,12 +16,15 @@
 
 #include "src/trace_processor/db/column/data_layer.h"
 
+#include <algorithm>
 #include <cstdint>
+#include <iterator>
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/column/arrangement_overlay.h"
@@ -35,6 +38,7 @@
 #include "src/trace_processor/db/column/set_id_storage.h"
 #include "src/trace_processor/db/column/string_storage.h"
 #include "src/trace_processor/db/column/types.h"
+#include "src/trace_processor/db/compare.h"
 
 namespace perfetto::trace_processor::column {
 
@@ -113,6 +117,53 @@
   PERFETTO_FATAL("For GCC");
 }
 
+Range DataLayerChain::OrderedIndexSearchValidated(
+    FilterOp op,
+    SqlValue value,
+    const OrderedIndices& indices) const {
+  auto lb = [&]() {
+    return static_cast<uint32_t>(std::distance(
+        indices.data,
+        std::lower_bound(indices.data, indices.data + indices.size, value,
+                         [this](uint32_t idx, const SqlValue& v) {
+                           return compare::SqlValueComparator(
+                               Get_AvoidUsingBecauseSlow(idx), v);
+                         })));
+  };
+  auto ub = [&]() {
+    return static_cast<uint32_t>(std::distance(
+        indices.data,
+        std::upper_bound(indices.data, indices.data + indices.size, value,
+                         [this](const SqlValue& v, uint32_t idx) {
+                           return compare::SqlValueComparator(
+                               v, Get_AvoidUsingBecauseSlow(idx));
+                         })));
+  };
+  switch (op) {
+    case FilterOp::kEq:
+      return {lb(), ub()};
+    case FilterOp::kLe:
+      return {0, ub()};
+    case FilterOp::kLt:
+      return {0, lb()};
+    case FilterOp::kGe:
+      return {lb(), indices.size};
+    case FilterOp::kGt:
+      return {ub(), indices.size};
+    case FilterOp::kIsNull:
+      PERFETTO_CHECK(value.is_null());
+      return {0, ub()};
+    case FilterOp::kIsNotNull:
+      PERFETTO_CHECK(value.is_null());
+      return {ub(), indices.size};
+    case FilterOp::kNe:
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      PERFETTO_FATAL("Wrong filtering operation");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
 ArrangementOverlay::ArrangementOverlay(
     const std::vector<uint32_t>* arrangement,
     DataLayerChain::Indices::State arrangement_state)
diff --git a/src/trace_processor/db/column/data_layer.h b/src/trace_processor/db/column/data_layer.h
index 5e34539..b6f9382 100644
--- a/src/trace_processor/db/column/data_layer.h
+++ b/src/trace_processor/db/column/data_layer.h
@@ -321,9 +321,26 @@
 
   // Post-validated implementation of |OrderedIndexSearch|. See
   // |OrderedIndexSearch|'s documentation.
-  virtual Range OrderedIndexSearchValidated(FilterOp,
-                                            SqlValue,
-                                            const OrderedIndices&) const = 0;
+  Range OrderedIndexSearchValidated(FilterOp op,
+                                    SqlValue value,
+                                    const OrderedIndices& indices) const;
+
+  // Returns the SqlValue representing the value at a given index.
+  //
+  // This function might be very tempting to use as it appears cheap on the
+  // surface but because of how DataLayerChains might be layered on top of each
+  // other, this might require *several* virtual function calls per index.
+  // If you're tempted to use this, please consider instead create a new
+  // "vectorized" function instead and only using this as a last resort.
+  //
+  // The correct "class" of algorithms to use this function are cases where you
+  // have a set of indices you want to lookup and based on the value returned
+  // you will only use a fraction of them. In this case, it might be worth
+  // paying the non-vectorized lookup to vastly reduce how many indices need
+  // to be translated.
+  //
+  // An example of such an algorithm is binary search on indices.
+  virtual SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const = 0;
 };
 
 }  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc
index 79ca3c0..426c6d2 100644
--- a/src/trace_processor/db/column/dense_null_overlay.cc
+++ b/src/trace_processor/db/column/dense_null_overlay.cc
@@ -219,50 +219,6 @@
   inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range DenseNullOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  // For NOT EQUAL the further analysis needs to be done by the caller.
-  PERFETTO_CHECK(op != FilterOp::kNe);
-
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "DenseNullOverlay::ChainImpl::OrderedIndexSearch");
-
-  // We assume all NULLs are ordered to be in the front. We are looking for the
-  // first index that points to non NULL value.
-  const uint32_t* first_non_null =
-      std::partition_point(indices.data, indices.data + indices.size,
-                           [this](uint32_t i) { return !non_null_->IsSet(i); });
-
-  auto non_null_offset =
-      static_cast<uint32_t>(std::distance(indices.data, first_non_null));
-  auto non_null_size = static_cast<uint32_t>(
-      std::distance(first_non_null, indices.data + indices.size));
-
-  if (op == FilterOp::kIsNull) {
-    return {0, non_null_offset};
-  }
-
-  if (op == FilterOp::kIsNotNull) {
-    switch (inner_->ValidateSearchConstraints(op, sql_val)) {
-      case SearchValidationResult::kNoData:
-        return {};
-      case SearchValidationResult::kAllData:
-        return {non_null_offset, indices.size};
-      case SearchValidationResult::kOk:
-        break;
-    }
-  }
-
-  Range inner_range = inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{first_non_null, non_null_size,
-                     Indices::State::kNonmonotonic});
-  return {inner_range.start + non_null_offset,
-          inner_range.end + non_null_offset};
-}
-
 void DenseNullOverlay::ChainImpl::StableSort(SortToken* start,
                                              SortToken* end,
                                              SortDirection direction) const {
@@ -314,6 +270,12 @@
                                                  : *first_null_it;
 }
 
+SqlValue DenseNullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return non_null_->IsSet(index) ? inner_->Get_AvoidUsingBecauseSlow(index)
+                                 : SqlValue();
+}
+
 void DenseNullOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* null_overlay = storage->set_dense_null_overlay();
   non_null_->Serialize(null_overlay->set_bit_vector());
diff --git a/src/trace_processor/db/column/dense_null_overlay.h b/src/trace_processor/db/column/dense_null_overlay.h
index e253400..d0a95d2 100644
--- a/src/trace_processor/db/column/dense_null_overlay.h
+++ b/src/trace_processor/db/column/dense_null_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -56,10 +57,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -70,6 +67,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return non_null_->size(); }
diff --git a/src/trace_processor/db/column/dense_null_overlay_unittest.cc b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
index 24b1eee..7cd74f6 100644
--- a/src/trace_processor/db/column/dense_null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
@@ -129,11 +129,12 @@
 }
 
 TEST(DenseNullOverlay, OrderedIndexSearch) {
-  auto fake = FakeStorageChain::SearchSubset(6, BitVector({0, 1, 0, 1, 0, 1}));
+  std::vector<uint32_t> numeric_data{0, 1, 0, 1, 0, 1};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
 
   BitVector bv{0, 1, 0, 1, 0, 1};
   DenseNullOverlay storage(&bv);
-  auto chain = storage.MakeChain(std::move(fake));
+  auto chain = storage.MakeChain(numeric.MakeChain());
 
   std::vector<uint32_t> indices_vec({0, 2, 4, 1, 3, 5});
   OrderedIndices indices{indices_vec.data(), 6, Indices::State::kNonmonotonic};
@@ -146,25 +147,25 @@
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 6u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 6u);
+  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 6u);
+  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 }
 
 TEST(DenseNullOverlay, SingleSearch) {
diff --git a/src/trace_processor/db/column/dummy_storage.cc b/src/trace_processor/db/column/dummy_storage.cc
index 631cbb7..ef9cacf 100644
--- a/src/trace_processor/db/column/dummy_storage.cc
+++ b/src/trace_processor/db/column/dummy_storage.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/db/column/dummy_storage.h"
 
 #include <cstdint>
+#include <optional>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/trace_processor/basic_types.h"
@@ -49,13 +50,6 @@
   PERFETTO_FATAL("Shouldn't be called");
 }
 
-Range DummyStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp,
-    SqlValue,
-    const OrderedIndices&) const {
-  PERFETTO_FATAL("Shouldn't be called");
-}
-
 void DummyStorage::ChainImpl::StableSort(SortToken*,
                                          SortToken*,
                                          SortDirection) const {
@@ -78,6 +72,10 @@
   PERFETTO_FATAL("Shouldn't be called");
 }
 
+SqlValue DummyStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
 void DummyStorage::ChainImpl::Serialize(StorageProto*) const {
   PERFETTO_FATAL("Shouldn't be called");
 }
diff --git a/src/trace_processor/db/column/dummy_storage.h b/src/trace_processor/db/column/dummy_storage.h
index ffa0c73..66f2497 100644
--- a/src/trace_processor/db/column/dummy_storage.h
+++ b/src/trace_processor/db/column/dummy_storage.h
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -45,10 +46,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -59,6 +56,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override;
diff --git a/src/trace_processor/db/column/fake_storage.cc b/src/trace_processor/db/column/fake_storage.cc
index 5a26961..5015bf3 100644
--- a/src/trace_processor/db/column/fake_storage.cc
+++ b/src/trace_processor/db/column/fake_storage.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <iterator>
+#include <optional>
 #include <utility>
 
 #include "perfetto/base/logging.h"
@@ -112,43 +113,6 @@
   PERFETTO_FATAL("For GCC");
 }
 
-Range FakeStorageChain::OrderedIndexSearchValidated(
-    FilterOp,
-    SqlValue,
-    const OrderedIndices& indices) const {
-  switch (strategy_) {
-    case kAll:
-      return {0, indices.size};
-    case kNone:
-      return {};
-    case kRange: {
-      // We are looking at intersection of |range_| and |indices_|.
-      const uint32_t* first_in_range = std::partition_point(
-          indices.data, indices.data + indices.size,
-          [this](uint32_t i) { return !range_.Contains(i); });
-      const uint32_t* first_outside_range = std::partition_point(
-          first_in_range, indices.data + indices.size,
-          [this](uint32_t i) { return range_.Contains(i); });
-      return {
-          static_cast<uint32_t>(std::distance(indices.data, first_in_range)),
-          static_cast<uint32_t>(
-              std::distance(indices.data, first_outside_range))};
-    }
-    case kBitVector:
-      // We are looking at intersection of |range_| and |bit_vector_|.
-      const uint32_t* first_set = std::partition_point(
-          indices.data, indices.data + indices.size,
-          [this](uint32_t i) { return !bit_vector_.IsSet(i); });
-      const uint32_t* first_non_set = std::partition_point(
-          first_set, indices.data + indices.size,
-          [this](uint32_t i) { return bit_vector_.IsSet(i); });
-      return {
-          static_cast<uint32_t>(std::distance(indices.data, first_set)),
-          static_cast<uint32_t>(std::distance(indices.data, first_non_set))};
-  }
-  PERFETTO_FATAL("For GCC");
-}
-
 void FakeStorageChain::Distinct(Indices&) const {
   // Fake storage shouldn't implement Distinct as it's not a binary (this index
   // passes or not) operation on a column.
@@ -166,6 +130,10 @@
   PERFETTO_FATAL("Not implemented");
 }
 
+SqlValue FakeStorageChain::Get_AvoidUsingBecauseSlow(uint32_t) const {
+  PERFETTO_FATAL("Not implemented");
+}
+
 void FakeStorageChain::Serialize(StorageProto*) const {
   // FakeStorage doesn't really make sense to serialize.
   PERFETTO_FATAL("Not implemented");
diff --git a/src/trace_processor/db/column/fake_storage.h b/src/trace_processor/db/column/fake_storage.h
index 7adf786..980a951 100644
--- a/src/trace_processor/db/column/fake_storage.h
+++ b/src/trace_processor/db/column/fake_storage.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -84,10 +85,6 @@
 
   void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-  Range OrderedIndexSearchValidated(FilterOp,
-                                    SqlValue,
-                                    const OrderedIndices&) const override;
-
   void StableSort(SortToken* start,
                   SortToken* end,
                   SortDirection) const override;
@@ -95,8 +92,11 @@
   void Distinct(Indices&) const override;
 
   std::optional<Token> MaxElement(Indices&) const override;
+
   std::optional<Token> MinElement(Indices&) const override;
 
+  SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
   void Serialize(StorageProto*) const override;
 
   uint32_t size() const override { return size_; }
diff --git a/src/trace_processor/db/column/fake_storage_unittest.cc b/src/trace_processor/db/column/fake_storage_unittest.cc
index 908b7e0..28c497f 100644
--- a/src/trace_processor/db/column/fake_storage_unittest.cc
+++ b/src/trace_processor/db/column/fake_storage_unittest.cc
@@ -43,7 +43,6 @@
 using testing::IsEmpty;
 
 using Indices = DataLayerChain::Indices;
-using OrderedIndices = DataLayerChain::OrderedIndices;
 
 TEST(FakeStorage, ValidateSearchConstraints) {
   {
@@ -197,48 +196,6 @@
   }
 }
 
-TEST(FakeStorage, OrderedIndexSearchValidated) {
-  std::vector<uint32_t> table_idx{4, 3, 2, 1};
-  OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()),
-                         Indices::State::kNonmonotonic};
-  {
-    // All passes
-    auto fake = FakeStorageChain::SearchAll(5);
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(0, 4));
-  }
-  {
-    // None passes
-    auto fake = FakeStorageChain::SearchNone(5);
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(0, 0));
-  }
-  {
-    // BitVector
-    auto fake = FakeStorageChain::SearchSubset(5, BitVector{0, 0, 1, 1, 1});
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(0, 3));
-  }
-  {
-    // Index vector
-    auto fake =
-        FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2, 3});
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(1, 4));
-  }
-  {
-    // Range
-    auto fake = FakeStorageChain::SearchSubset(5, Range(1, 4));
-    Range ret =
-        fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
-    EXPECT_EQ(ret, Range(1, 4));
-  }
-}
-
 }  // namespace
 }  // namespace column
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/column/id_storage.cc b/src/trace_processor/db/column/id_storage.cc
index ea1e909..1ebe459 100644
--- a/src/trace_processor/db/column/id_storage.cc
+++ b/src/trace_processor/db/column/id_storage.cc
@@ -246,47 +246,6 @@
   PERFETTO_FATAL("FilterOp not matched");
 }
 
-Range IdStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_DCHECK(op != FilterOp::kNe);
-
-  PERFETTO_TP_TRACE(
-      metatrace::Category::DB, "IdStorage::ChainImpl::OrderedIndexSearch",
-      [indices, op](metatrace::Record* r) {
-        r->AddArg("Count", std::to_string(indices.size));
-        r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
-      });
-
-  // It's a valid filter operation if |sql_val| is a double, although it
-  // requires special logic.
-  if (sql_val.type == SqlValue::kDouble) {
-    switch (utils::CompareIntColumnWithDouble(op, &sql_val)) {
-      case SearchValidationResult::kOk:
-        break;
-      case SearchValidationResult::kAllData:
-        return {0, indices.size};
-      case SearchValidationResult::kNoData:
-        return {};
-    }
-  }
-  auto val = static_cast<uint32_t>(sql_val.AsLong());
-
-  // OrderedIndices are monotonic non contiguous values if OrderedIndexSearch
-  // was called. Look for the first and last index and find the result of
-  // looking for this range in IdStorage.
-  Range indices_range(indices.data[0], indices.data[indices.size - 1] + 1);
-  Range bin_search_ret = BinarySearchIntrinsic(op, val, indices_range);
-
-  const auto* start_ptr = std::lower_bound(
-      indices.data, indices.data + indices.size, bin_search_ret.start);
-  const auto* end_ptr = std::lower_bound(start_ptr, indices.data + indices.size,
-                                         bin_search_ret.end);
-  return {static_cast<uint32_t>(std::distance(indices.data, start_ptr)),
-          static_cast<uint32_t>(std::distance(indices.data, end_ptr))};
-}
-
 Range IdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op,
                                                   Id val,
                                                   Range range) {
@@ -363,6 +322,10 @@
   return *tok;
 }
 
+SqlValue IdStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t index) const {
+  return SqlValue::Long(index);
+}
+
 void IdStorage::ChainImpl::Serialize(StorageProto* storage) const {
   storage->set_id_storage();
 }
diff --git a/src/trace_processor/db/column/id_storage.h b/src/trace_processor/db/column/id_storage.h
index 420568d..c3aeadf 100644
--- a/src/trace_processor/db/column/id_storage.h
+++ b/src/trace_processor/db/column/id_storage.h
@@ -19,6 +19,7 @@
 #include <cstdint>
 #include <limits>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -55,10 +56,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -69,6 +66,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override {
diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc
index 128d3ff..372d3b0 100644
--- a/src/trace_processor/db/column/null_overlay.cc
+++ b/src/trace_processor/db/column/null_overlay.cc
@@ -256,56 +256,6 @@
   inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range NullOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  // For NOT EQUAL the translation or results from EQUAL needs to be done by the
-  // caller.
-  PERFETTO_CHECK(op != FilterOp::kNe);
-
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "NullOverlay::ChainImpl::OrderedIndexSearch");
-
-  // We assume all NULLs are ordered to be in the front. We are looking for the
-  // first index that points to non NULL value.
-  const uint32_t* first_non_null =
-      std::partition_point(indices.data, indices.data + indices.size,
-                           [this](uint32_t i) { return !non_null_->IsSet(i); });
-  auto non_null_offset =
-      static_cast<uint32_t>(std::distance(indices.data, first_non_null));
-  auto non_null_size = static_cast<uint32_t>(
-      std::distance(first_non_null, indices.data + indices.size));
-
-  if (op == FilterOp::kIsNull) {
-    return {0, non_null_offset};
-  }
-
-  if (op == FilterOp::kIsNotNull) {
-    switch (inner_->ValidateSearchConstraints(op, sql_val)) {
-      case SearchValidationResult::kNoData:
-        return {};
-      case SearchValidationResult::kAllData:
-        return {non_null_offset, indices.size};
-      case SearchValidationResult::kOk:
-        break;
-    }
-  }
-
-  std::vector<uint32_t> storage_iv;
-  storage_iv.reserve(non_null_size);
-  for (const uint32_t* it = first_non_null;
-       it != first_non_null + non_null_size; it++) {
-    storage_iv.push_back(non_null_->CountSetBits(*it));
-  }
-
-  Range inner_range = inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{storage_iv.data(), non_null_size, indices.state});
-  return {inner_range.start + non_null_offset,
-          inner_range.end + non_null_offset};
-}
-
 void NullOverlay::ChainImpl::StableSort(SortToken* start,
                                         SortToken* end,
                                         SortDirection direction) const {
@@ -369,6 +319,13 @@
   return inner_->MinElement(indices);
 }
 
+SqlValue NullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return non_null_->IsSet(index)
+             ? inner_->Get_AvoidUsingBecauseSlow(non_null_->CountSetBits(index))
+             : SqlValue();
+}
+
 void NullOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* null_storage = storage->set_null_overlay();
   non_null_->Serialize(null_storage->set_bit_vector());
diff --git a/src/trace_processor/db/column/null_overlay.h b/src/trace_processor/db/column/null_overlay.h
index 187c056..a74f211 100644
--- a/src/trace_processor/db/column/null_overlay.h
+++ b/src/trace_processor/db/column/null_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -55,10 +56,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -69,6 +66,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return non_null_->size(); }
diff --git a/src/trace_processor/db/column/null_overlay_unittest.cc b/src/trace_processor/db/column/null_overlay_unittest.cc
index 981c7bd..9cff095 100644
--- a/src/trace_processor/db/column/null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/null_overlay_unittest.cc
@@ -200,15 +200,15 @@
 }
 
 TEST(NullOverlay, OrderedIndexSearch) {
+  std::vector<uint32_t> numeric_data{1, 0, 1, 0};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
   BitVector bv{0, 1, 1, 1, 0, 1};
-  // Passing values in final storage (on normal operations)
-  // 0, 1, 0, 1, 0, 0
-  auto fake = FakeStorageChain::SearchSubset(4, BitVector{1, 0, 1, 0});
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(std::move(fake));
+  auto chain = storage.MakeChain(numeric.MakeChain());
 
   // Passing values on final data
-  // NULL, NULL, 0, 1, 1
+  // NULL, NULL, 0, 0, 1, 1
   std::vector<uint32_t> table_idx{0, 4, 5, 1, 3};
   OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()),
                          Indices::State::kNonmonotonic};
@@ -218,28 +218,28 @@
   ASSERT_EQ(res.end, 2u);
 
   res = chain->OrderedIndexSearch(FilterOp::kIsNotNull, SqlValue(), indices);
+  ASSERT_EQ(res.start, 2u);
+  ASSERT_EQ(res.end, 5u);
+
+  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 5u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 5u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices);
+  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices);
   ASSERT_EQ(res.start, 3u);
   ASSERT_EQ(res.end, 5u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 5u);
+  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 
-  res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 5u);
-
-  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices);
-  ASSERT_EQ(res.start, 3u);
-  ASSERT_EQ(res.end, 5u);
+  res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices);
+  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.end, 3u);
 }
 
 TEST(NullOverlay, StableSort) {
diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc
index b39b4cb..3612356 100644
--- a/src/trace_processor/db/column/numeric_storage.cc
+++ b/src/trace_processor/db/column/numeric_storage.cc
@@ -138,60 +138,6 @@
 }
 
 template <typename T>
-uint32_t TypedLowerBoundExtrinsic(T val,
-                                  const T* data,
-                                  OrderedIndices indices) {
-  const auto* lower = std::lower_bound(
-      indices.data, indices.data + indices.size, val,
-      [data](uint32_t index, T value) { return data[index] < value; });
-  return static_cast<uint32_t>(std::distance(indices.data, lower));
-}
-
-uint32_t LowerBoundExtrinsic(const void* vector_ptr,
-                             NumericValue val,
-                             OrderedIndices indices) {
-  if (const auto* u32 = std::get_if<uint32_t>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<uint32_t>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*u32, start, indices);
-  }
-  if (const auto* i64 = std::get_if<int64_t>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<int64_t>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*i64, start, indices);
-  }
-  if (const auto* i32 = std::get_if<int32_t>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<int32_t>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*i32, start, indices);
-  }
-  if (const auto* db = std::get_if<double>(&val)) {
-    const auto* start =
-        static_cast<const std::vector<double>*>(vector_ptr)->data();
-    return TypedLowerBoundExtrinsic(*db, start, indices);
-  }
-  PERFETTO_FATAL("Type not handled");
-}
-
-uint32_t UpperBoundExtrinsic(const void* vector_ptr,
-                             NumericValue val,
-                             OrderedIndices indices) {
-  return std::visit(
-      [vector_ptr, indices](auto val_data) {
-        using T = decltype(val_data);
-        const T* typed_start =
-            static_cast<const std::vector<T>*>(vector_ptr)->data();
-        const auto* upper =
-            std::upper_bound(indices.data, indices.data + indices.size,
-                             val_data, [typed_start](T value, uint32_t index) {
-                               return value < typed_start[index];
-                             });
-        return static_cast<uint32_t>(std::distance(indices.data, upper));
-      },
-      val);
-}
-
-template <typename T>
 void TypedLinearSearch(T typed_val,
                        const T* start,
                        FilterOp op,
@@ -504,79 +450,6 @@
       val);
 }
 
-Range NumericStorageBase::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(
-      metatrace::Category::DB, "NumericStorage::ChainImpl::OrderedIndexSearch",
-      [indices, op](metatrace::Record* r) {
-        r->AddArg("Count", std::to_string(indices.size));
-        r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
-      });
-
-  // Mismatched types - value is double and column is int.
-  if (sql_val.type == SqlValue::kDouble &&
-      storage_type_ != ColumnType::kDouble) {
-    if (auto ret = utils::CanReturnEarly(IntColumnWithDouble(op, &sql_val),
-                                         indices.size);
-        ret) {
-      return *ret;
-    }
-  }
-
-  // Mismatched types - column is double and value is int.
-  if (sql_val.type != SqlValue::kDouble &&
-      storage_type_ == ColumnType::kDouble) {
-    if (auto ret = utils::CanReturnEarly(DoubleColumnWithInt(op, &sql_val),
-                                         indices.size);
-        ret) {
-      return *ret;
-    }
-  }
-
-  NumericValue val;
-  switch (storage_type_) {
-    case ColumnType::kDouble:
-      val = sql_val.AsDouble();
-      break;
-    case ColumnType::kInt64:
-      val = sql_val.AsLong();
-      break;
-    case ColumnType::kInt32:
-      val = static_cast<int32_t>(sql_val.AsLong());
-      break;
-    case ColumnType::kUint32:
-      val = static_cast<uint32_t>(sql_val.AsLong());
-      break;
-    case ColumnType::kString:
-    case ColumnType::kDummy:
-    case ColumnType::kId:
-      PERFETTO_FATAL("Invalid type");
-  }
-
-  switch (op) {
-    case FilterOp::kEq:
-      return {LowerBoundExtrinsic(vector_ptr_, val, indices),
-              UpperBoundExtrinsic(vector_ptr_, val, indices)};
-    case FilterOp::kLe:
-      return {0, UpperBoundExtrinsic(vector_ptr_, val, indices)};
-    case FilterOp::kLt:
-      return {0, LowerBoundExtrinsic(vector_ptr_, val, indices)};
-    case FilterOp::kGe:
-      return {LowerBoundExtrinsic(vector_ptr_, val, indices), indices.size};
-    case FilterOp::kGt:
-      return {UpperBoundExtrinsic(vector_ptr_, val, indices), indices.size};
-    case FilterOp::kNe:
-    case FilterOp::kIsNull:
-    case FilterOp::kIsNotNull:
-    case FilterOp::kGlob:
-    case FilterOp::kRegex:
-      PERFETTO_FATAL("Wrong filtering operation");
-  }
-  PERFETTO_FATAL("For GCC");
-}
-
 BitVector NumericStorageBase::ChainImpl::LinearSearchInternal(
     FilterOp op,
     NumericValue val,
diff --git a/src/trace_processor/db/column/numeric_storage.h b/src/trace_processor/db/column/numeric_storage.h
index fd27d26..3c4416b 100644
--- a/src/trace_processor/db/column/numeric_storage.h
+++ b/src/trace_processor/db/column/numeric_storage.h
@@ -23,6 +23,7 @@
 #include <optional>
 #include <set>
 #include <string>
+#include <type_traits>
 #include <unordered_set>
 #include <variant>
 #include <vector>
@@ -49,10 +50,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void Serialize(StorageProto*) const override;
 
     std::string DebugString() const override { return "NumericStorage"; }
@@ -145,6 +142,13 @@
       return *tok;
     }
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override {
+      if constexpr (std::is_same_v<T, double>) {
+        return SqlValue::Double((*vector_)[index]);
+      }
+      return SqlValue::Long((*vector_)[index]);
+    }
+
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection direction) const override {
diff --git a/src/trace_processor/db/column/range_overlay.cc b/src/trace_processor/db/column/range_overlay.cc
index 81bb7ae..dee7838 100644
--- a/src/trace_processor/db/column/range_overlay.cc
+++ b/src/trace_processor/db/column/range_overlay.cc
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -118,22 +119,6 @@
   inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range RangeOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::IndexSearch");
-
-  // Should be SIMD optimized.
-  std::vector<uint32_t> storage_iv(indices.size);
-  for (uint32_t i = 0; i < indices.size; ++i) {
-    storage_iv[i] = indices.data[i] + range_->start;
-  }
-  return inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{storage_iv.data(), indices.size, indices.state});
-}
-
 void RangeOverlay::ChainImpl::StableSort(SortToken* start,
                                          SortToken* end,
                                          SortDirection direction) const {
@@ -160,6 +145,11 @@
   return inner_->MaxElement(indices);
 }
 
+SqlValue RangeOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return inner_->Get_AvoidUsingBecauseSlow(index + range_->start);
+}
+
 std::optional<Token> RangeOverlay::ChainImpl::MinElement(
     Indices& indices) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::MinElement");
diff --git a/src/trace_processor/db/column/range_overlay.h b/src/trace_processor/db/column/range_overlay.h
index f15e8dc..a58df40 100644
--- a/src/trace_processor/db/column/range_overlay.h
+++ b/src/trace_processor/db/column/range_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -52,10 +53,6 @@
 
     void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -66,6 +63,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return range_->size(); }
diff --git a/src/trace_processor/db/column/selector_overlay.cc b/src/trace_processor/db/column/selector_overlay.cc
index b2cbc43..9e957cb 100644
--- a/src/trace_processor/db/column/selector_overlay.cc
+++ b/src/trace_processor/db/column/selector_overlay.cc
@@ -16,9 +16,9 @@
 
 #include "src/trace_processor/db/column/selector_overlay.h"
 
-#include <algorithm>
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -35,7 +35,7 @@
 namespace perfetto::trace_processor::column {
 namespace {
 
-static constexpr uint32_t kIndexOfNthSetRatio = 32;
+constexpr uint32_t kIndexOfNthSetRatio = 32;
 
 }  // namespace
 
@@ -97,23 +97,6 @@
   return inner_->IndexSearchValidated(op, sql_val, indices);
 }
 
-Range SelectorOverlay::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  // To go from TableIndexVector to StorageIndexVector we need to find index in
-  // |selector_| by looking only into set bits.
-  std::vector<uint32_t> inner_indices(indices.size);
-  for (uint32_t i = 0; i < indices.size; ++i) {
-    inner_indices[i] = selector_->IndexOfNthSet(indices.data[i]);
-  }
-  return inner_->OrderedIndexSearchValidated(
-      op, sql_val,
-      OrderedIndices{inner_indices.data(),
-                     static_cast<uint32_t>(inner_indices.size()),
-                     indices.state});
-}
-
 void SelectorOverlay::ChainImpl::StableSort(SortToken* start,
                                             SortToken* end,
                                             SortDirection direction) const {
@@ -148,6 +131,11 @@
   return inner_->MinElement(indices);
 }
 
+SqlValue SelectorOverlay::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return inner_->Get_AvoidUsingBecauseSlow(selector_->IndexOfNthSet(index));
+}
+
 void SelectorOverlay::ChainImpl::Serialize(StorageProto* storage) const {
   auto* selector_overlay = storage->set_selector_overlay();
   inner_->Serialize(selector_overlay->set_storage());
@@ -164,15 +152,15 @@
     for (auto& token : indices.tokens) {
       token.index = selector_->IndexOfNthSet(token.index);
     }
-  } else {
-    // TODO(mayzner): once we have a reverse index for IndexOfNthSet in
-    // BitVector, this should no longer be necessary.
-    std::vector<uint32_t> lookup = selector_->GetSetBitIndices();
-    for (auto& token : indices.tokens) {
-      token.index = lookup[token.index];
-    }
+    return;
   }
-  return;
+
+  // TODO(mayzner): once we have a reverse index for IndexOfNthSet in
+  // BitVector, this should no longer be necessary.
+  std::vector<uint32_t> lookup = selector_->GetSetBitIndices();
+  for (auto& token : indices.tokens) {
+    token.index = lookup[token.index];
+  }
 }
 
 }  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/selector_overlay.h b/src/trace_processor/db/column/selector_overlay.h
index d54bdf5..3726f9f 100644
--- a/src/trace_processor/db/column/selector_overlay.h
+++ b/src/trace_processor/db/column/selector_overlay.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -56,10 +57,6 @@
 
     void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection) const override;
@@ -70,6 +67,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override { return selector_->size(); }
diff --git a/src/trace_processor/db/column/selector_overlay_unittest.cc b/src/trace_processor/db/column/selector_overlay_unittest.cc
index 0abae4c..def2a77 100644
--- a/src/trace_processor/db/column/selector_overlay_unittest.cc
+++ b/src/trace_processor/db/column/selector_overlay_unittest.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/db/column/selector_overlay.h"
 
 #include <cstdint>
+#include <utility>
 #include <vector>
 
 #include "data_layer.h"
@@ -103,35 +104,23 @@
   ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(1u));
 }
 
-TEST(SelectorOverlay, OrderedIndexSearchTrivial) {
+TEST(SelectorOverlay, OrderedIndexSearch) {
+  std::vector<uint32_t> numeric_data{1, 0, 0, 1, 1};
+  NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
+
   BitVector selector{1, 0, 1, 0, 1};
-  auto fake = FakeStorageChain::SearchAll(5);
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(std::move(fake));
+  auto chain = storage.MakeChain(numeric.MakeChain());
 
   std::vector<uint32_t> table_idx{1u, 0u, 2u};
   Range res = chain->OrderedIndexSearch(
-      FilterOp::kGe, SqlValue::Long(0u),
+      FilterOp::kGe, SqlValue::Long(1u),
       OrderedIndices{table_idx.data(), static_cast<uint32_t>(table_idx.size()),
                      Indices::State::kNonmonotonic});
-  ASSERT_EQ(res.start, 0u);
+  ASSERT_EQ(res.start, 1u);
   ASSERT_EQ(res.end, 3u);
 }
 
-TEST(SelectorOverlay, OrderedIndexSearchNone) {
-  BitVector selector{1, 0, 1, 0, 1};
-  auto fake = FakeStorageChain::SearchNone(5);
-  SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(std::move(fake));
-
-  std::vector<uint32_t> table_idx{1u, 0u, 2u};
-  Range res = chain->OrderedIndexSearch(
-      FilterOp::kGe, SqlValue::Long(0u),
-      OrderedIndices{table_idx.data(), static_cast<uint32_t>(table_idx.size()),
-                     Indices::State::kNonmonotonic});
-  ASSERT_EQ(res.size(), 0u);
-}
-
 TEST(SelectorOverlay, StableSort) {
   std::vector<uint32_t> numeric_data{3, 1, 0, 0, 2, 4, 3, 4};
   NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc
index 207b649..42ab4ea 100644
--- a/src/trace_processor/db/column/set_id_storage.cc
+++ b/src/trace_processor/db/column/set_id_storage.cc
@@ -19,10 +19,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <functional>
-#include <iterator>
-#include <limits>
-#include <memory>
-#include <set>
+#include <optional>
 #include <string>
 #include <unordered_set>
 #include <utility>
@@ -250,38 +247,17 @@
   }
 }
 
-Range SetIdStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "SetIdStorage::ChainImpl::OrderedIndexSearch");
-  // OrderedIndices are monotonic non-contiguous values.
-  auto res = SearchValidated(
-      op, sql_val, Range(indices.data[0], indices.data[indices.size - 1] + 1));
-  PERFETTO_CHECK(res.IsRange());
-  Range res_range = std::move(res).TakeIfRange();
-
-  const auto* start_ptr = std::lower_bound(
-      indices.data, indices.data + indices.size, res_range.start);
-  const auto* end_ptr =
-      std::lower_bound(start_ptr, indices.data + indices.size, res_range.end);
-
-  return {static_cast<uint32_t>(std::distance(indices.data, start_ptr)),
-          static_cast<uint32_t>(std::distance(indices.data, end_ptr))};
-}
-
 Range SetIdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op,
                                                      SetId val,
                                                      Range range) const {
   switch (op) {
     case FilterOp::kEq: {
-      if (values_->data()[val] != val) {
-        return Range();
+      if ((*values_)[val] != val) {
+        return {};
       }
       uint32_t start = std::max(val, range.start);
       uint32_t end = UpperBoundIntrinsic(values_->data(), val, range);
-      return Range(std::min(start, end), end);
+      return {std::min(start, end), end};
     }
     case FilterOp::kLe: {
       return {range.start, UpperBoundIntrinsic(values_->data(), val, range)};
@@ -370,6 +346,11 @@
   return *tok;
 }
 
+SqlValue SetIdStorage::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  return SqlValue::Long((*values_)[index]);
+}
+
 void SetIdStorage::ChainImpl::Serialize(StorageProto* msg) const {
   auto* vec_msg = msg->set_set_id_storage();
   vec_msg->set_values(reinterpret_cast<const uint8_t*>(values_->data()),
diff --git a/src/trace_processor/db/column/set_id_storage.h b/src/trace_processor/db/column/set_id_storage.h
index 2fa6c81..e459106 100644
--- a/src/trace_processor/db/column/set_id_storage.h
+++ b/src/trace_processor/db/column/set_id_storage.h
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -54,10 +55,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection direction) const override;
@@ -70,6 +67,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     uint32_t size() const override {
       return static_cast<uint32_t>(values_->size());
     }
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index adf3eda..1d25dbb 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -163,36 +163,6 @@
   return static_cast<uint32_t>(std::distance(data, upper));
 }
 
-uint32_t LowerBoundExtrinsic(StringPool* pool,
-                             const StringPool::Id* data,
-                             NullTermStringView val,
-                             const uint32_t* indices,
-                             uint32_t indices_count,
-                             uint32_t offset) {
-  Less comp{pool};
-  const auto* lower =
-      std::lower_bound(indices + offset, indices + indices_count, val,
-                       [comp, data](uint32_t index, NullTermStringView val) {
-                         return comp(data[index], val);
-                       });
-  return static_cast<uint32_t>(std::distance(indices, lower));
-}
-
-uint32_t UpperBoundExtrinsic(StringPool* pool,
-                             const StringPool::Id* data,
-                             NullTermStringView val,
-                             const uint32_t* indices,
-                             uint32_t indices_count,
-                             uint32_t offset) {
-  Greater comp{pool};
-  const auto* upper =
-      std::upper_bound(indices + offset, indices + indices_count, val,
-                       [comp, data](NullTermStringView val, uint32_t index) {
-                         return comp(data[index], val);
-                       });
-  return static_cast<uint32_t>(std::distance(indices, upper));
-}
-
 }  // namespace
 
 StringStorage::ChainImpl::ChainImpl(StringPool* string_pool,
@@ -518,64 +488,6 @@
   return std::move(builder).Build();
 }
 
-Range StringStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp op,
-    SqlValue sql_val,
-    const OrderedIndices& indices) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB,
-                    "StringStorage::ChainImpl::OrderedIndexSearch");
-  StringPool::Id val =
-      (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull)
-          ? StringPool::Id::Null()
-          : string_pool_->InternString(base::StringView(sql_val.AsString()));
-  NullTermStringView val_str = string_pool_->Get(val);
-
-  auto first_non_null = static_cast<uint32_t>(std::distance(
-      indices.data,
-      std::partition_point(indices.data, indices.data + indices.size,
-                           [this](uint32_t i) {
-                             return (*data_)[i] == StringPool::Id::Null();
-                           })));
-
-  switch (op) {
-    case FilterOp::kEq:
-      return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null),
-              UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null)};
-    case FilterOp::kLe:
-      return {first_non_null,
-              UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null)};
-    case FilterOp::kLt:
-      return {first_non_null,
-              LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null)};
-    case FilterOp::kGe:
-      return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null),
-              indices.size};
-    case FilterOp::kGt:
-      return {UpperBoundExtrinsic(string_pool_, data_->data(), val_str,
-                                  indices.data, indices.size, first_non_null),
-              indices.size};
-    case FilterOp::kIsNull: {
-      // Assuming nulls are at the front.
-      return Range(0, first_non_null);
-    }
-    case FilterOp::kIsNotNull: {
-      // Assuming nulls are at the front.
-      return Range(first_non_null, indices.size);
-    }
-
-    case FilterOp::kNe:
-    case FilterOp::kGlob:
-    case FilterOp::kRegex:
-      PERFETTO_FATAL("Not supported for OrderedIndexSearch");
-  }
-  PERFETTO_FATAL("For GCC");
-}
-
 Range StringStorage::ChainImpl::BinarySearchIntrinsic(
     FilterOp op,
     SqlValue sql_val,
@@ -715,6 +627,14 @@
   return *tok;
 }
 
+SqlValue StringStorage::ChainImpl::Get_AvoidUsingBecauseSlow(
+    uint32_t index) const {
+  StringPool::Id id = (*data_)[index];
+  return id == StringPool::Id::Null()
+             ? SqlValue()
+             : SqlValue::String(string_pool_->Get(id).c_str());
+}
+
 void StringStorage::ChainImpl::Serialize(StorageProto* msg) const {
   auto* string_storage_msg = msg->set_string_storage();
   string_storage_msg->set_is_sorted(is_sorted_);
diff --git a/src/trace_processor/db/column/string_storage.h b/src/trace_processor/db/column/string_storage.h
index 470ccd1..1c05dd7 100644
--- a/src/trace_processor/db/column/string_storage.h
+++ b/src/trace_processor/db/column/string_storage.h
@@ -18,6 +18,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -57,10 +58,6 @@
 
     void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override;
 
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      const OrderedIndices&) const override;
-
     void StableSort(SortToken* start,
                     SortToken* end,
                     SortDirection direction) const override;
@@ -71,6 +68,8 @@
 
     std::optional<Token> MinElement(Indices&) const override;
 
+    SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override;
+
     void Serialize(StorageProto*) const override;
 
     uint32_t size() const override {
diff --git a/src/trace_processor/db/column/string_storage_unittest.cc b/src/trace_processor/db/column/string_storage_unittest.cc
index 4474ea9..7e7cadb 100644
--- a/src/trace_processor/db/column/string_storage_unittest.cc
+++ b/src/trace_processor/db/column/string_storage_unittest.cc
@@ -383,12 +383,12 @@
 
   op = FilterOp::kLt;
   res = chain->OrderedIndexSearch(op, val, indices);
-  ASSERT_EQ(res.start, 1u);
+  ASSERT_EQ(res.start, 0u);
   ASSERT_EQ(res.end, 4u);
 
   op = FilterOp::kLe;
   res = chain->OrderedIndexSearch(op, val, indices);
-  ASSERT_EQ(res.start, 1u);
+  ASSERT_EQ(res.start, 0u);
   ASSERT_EQ(res.end, 5u);
 
   op = FilterOp::kGt;
@@ -402,6 +402,7 @@
   ASSERT_EQ(res.end, 6u);
 
   op = FilterOp::kIsNull;
+  val = SqlValue();
   res = chain->OrderedIndexSearch(op, val, indices);
   ASSERT_EQ(res.start, 0u);
   ASSERT_EQ(res.end, 1u);
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index d6cc57f..dcd75f0 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -513,6 +513,29 @@
 }
 BENCHMARK(BM_QEFilterOrderedArrangement);
 
+void BM_QEFilterNullOrderedArrangement(benchmark::State& state) {
+  SliceTableForBenchmark table(state);
+  Order order{table.table_.parent_id().index_in_table(), false};
+  Table slice_sorted_with_parent_id = table.table_.Sort({order});
+
+  Constraint c{table.table_.parent_id().index_in_table(), FilterOp::kGt,
+               SqlValue::Long(26091)};
+  Query q;
+  q.constraints = {c};
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(slice_sorted_with_parent_id.QueryToRowMap(q));
+  }
+  state.counters["s/row"] = benchmark::Counter(
+      static_cast<double>(slice_sorted_with_parent_id.row_count()),
+      benchmark::Counter::kIsIterationInvariantRate |
+          benchmark::Counter::kInvert);
+  state.counters["s/out"] = benchmark::Counter(
+      static_cast<double>(table.table_.QueryToRowMap(q).size()),
+      benchmark::Counter::kIsIterationInvariantRate |
+          benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_QEFilterNullOrderedArrangement);
+
 void BM_QESliceFilterIndexSearchOneElement(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableFilter(