Merge "tp: Parse OEM atoms generically where possible" into main
diff --git a/Android.bp b/Android.bp
index 9ceeed2..328fc3d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5944,6 +5944,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6362,6 +6363,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6443,6 +6445,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.gen.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/power.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.cc",
@@ -6524,6 +6527,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.gen.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/power.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.gen.h",
@@ -6601,6 +6605,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6681,6 +6686,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pb.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.cc",
@@ -6761,6 +6767,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pb.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pb.h",
@@ -6838,6 +6845,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
@@ -6919,6 +6927,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.cc",
@@ -7000,6 +7009,7 @@
         "external/perfetto/protos/perfetto/trace/ftrace/net.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/oom.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/panel.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/perf_trace_counters.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/power.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/printk.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/raw_syscalls.pbzero.h",
@@ -13367,6 +13377,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
diff --git a/BUILD b/BUILD
index eada055..cddab6b 100644
--- a/BUILD
+++ b/BUILD
@@ -4460,6 +4460,7 @@
         "protos/perfetto/trace/ftrace/net.proto",
         "protos/perfetto/trace/ftrace/oom.proto",
         "protos/perfetto/trace/ftrace/panel.proto",
+        "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
         "protos/perfetto/trace/ftrace/power.proto",
         "protos/perfetto/trace/ftrace/printk.proto",
         "protos/perfetto/trace/ftrace/raw_syscalls.proto",
diff --git a/include/perfetto/ext/base/thread_annotations.h b/include/perfetto/ext/base/thread_annotations.h
index aaf291f..6aee16a 100644
--- a/include/perfetto/ext/base/thread_annotations.h
+++ b/include/perfetto/ext/base/thread_annotations.h
@@ -29,10 +29,8 @@
                              const char* description);
 }
 
-#define PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(pointer, size, description)   \
-  AnnotateBenignRaceSized(__FILE__, __LINE__,                             \
-                          reinterpret_cast<unsigned long>(pointer), size, \
-                          description);
+#define PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(pointer, size, description) \
+  AnnotateBenignRaceSized(__FILE__, __LINE__, pointer, size, description);
 #else  // defined(ADDRESS_SANITIZER)
 #define PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(pointer, size, description)
 #endif  // defined(ADDRESS_SANITIZER)
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index bd055ab..2237aef 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -57,6 +57,7 @@
   "net.proto",
   "oom.proto",
   "panel.proto",
+  "perf_trace_counters.proto",
   "power.proto",
   "printk.proto",
   "raw_syscalls.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index a068c9d..f7e314b 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -57,6 +57,7 @@
 import "protos/perfetto/trace/ftrace/net.proto";
 import "protos/perfetto/trace/ftrace/oom.proto";
 import "protos/perfetto/trace/ftrace/panel.proto";
+import "protos/perfetto/trace/ftrace/perf_trace_counters.proto";
 import "protos/perfetto/trace/ftrace/power.proto";
 import "protos/perfetto/trace/ftrace/printk.proto";
 import "protos/perfetto/trace/ftrace/raw_syscalls.proto";
@@ -601,5 +602,6 @@
     SamsungTracingMarkWriteFtraceEvent samsung_tracing_mark_write = 484;
     BinderCommandFtraceEvent binder_command = 485;
     BinderReturnFtraceEvent binder_return = 486;
+    SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/perf_trace_counters.proto b/protos/perfetto/trace/ftrace/perf_trace_counters.proto
new file mode 100644
index 0000000..0e3531c
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/perf_trace_counters.proto
@@ -0,0 +1,26 @@
+// Autogenerated by:
+// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message SchedSwitchWithCtrsFtraceEvent {
+  optional int32 old_pid = 1;
+  optional int32 new_pid = 2;
+  optional uint32 cctr = 3;
+  optional uint32 ctr0 = 4;
+  optional uint32 ctr1 = 5;
+  optional uint32 ctr2 = 6;
+  optional uint32 ctr3 = 7;
+  optional uint32 lctr0 = 8;
+  optional uint32 lctr1 = 9;
+  optional uint32 ctr4 = 10;
+  optional uint32 ctr5 = 11;
+  optional string prev_comm = 12;
+  optional int32 prev_pid = 13;
+  optional uint32 cyc = 14;
+  optional uint32 inst = 15;
+  optional uint32 stallbm = 16;
+  optional uint32 l3dm = 17;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index fb3bd6a..8cfa67e 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -8486,6 +8486,30 @@
 
 // End of protos/perfetto/trace/ftrace/panel.proto
 
+// Begin of protos/perfetto/trace/ftrace/perf_trace_counters.proto
+
+message SchedSwitchWithCtrsFtraceEvent {
+  optional int32 old_pid = 1;
+  optional int32 new_pid = 2;
+  optional uint32 cctr = 3;
+  optional uint32 ctr0 = 4;
+  optional uint32 ctr1 = 5;
+  optional uint32 ctr2 = 6;
+  optional uint32 ctr3 = 7;
+  optional uint32 lctr0 = 8;
+  optional uint32 lctr1 = 9;
+  optional uint32 ctr4 = 10;
+  optional uint32 ctr5 = 11;
+  optional string prev_comm = 12;
+  optional int32 prev_pid = 13;
+  optional uint32 cyc = 14;
+  optional uint32 inst = 15;
+  optional uint32 stallbm = 16;
+  optional uint32 l3dm = 17;
+}
+
+// End of protos/perfetto/trace/ftrace/perf_trace_counters.proto
+
 // Begin of protos/perfetto/trace/ftrace/power.proto
 
 message CpuFrequencyFtraceEvent {
@@ -9768,6 +9792,7 @@
     SamsungTracingMarkWriteFtraceEvent samsung_tracing_mark_write = 484;
     BinderCommandFtraceEvent binder_command = 485;
     BinderReturnFtraceEvent binder_return = 486;
+    SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
   }
 }
 
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index f2dcbb3..68bb2ba 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -481,3 +481,4 @@
 samsung/tracing_mark_write
 binder/binder_command
 binder/binder_return
+perf_trace_counters/sched_switch_with_ctrs
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index bd04fa0..1151079 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -50,9 +50,18 @@
 void QueryExecutor::FilterColumn(const Constraint& c,
                                  const storage::Storage& storage,
                                  RowMap* rm) {
+  // Shortcut of empty row map.
   if (rm->empty())
     return;
 
+  // Comparison of NULL with any operation apart from |IS_NULL| and
+  // |IS_NOT_NULL| should return no rows.
+  if (c.value.is_null() && c.op != FilterOp::kIsNull &&
+      c.op != FilterOp::kIsNotNull) {
+    rm->Clear();
+    return;
+  }
+
   uint32_t rm_size = rm->size();
   uint32_t rm_first = rm->Get(0);
   uint32_t rm_last = rm->Get(rm_size - 1);
@@ -64,8 +73,9 @@
   bool disallows_index_search = rm->IsRange();
   bool prefers_index_search =
       rm->IsIndexVector() || rm_size < 1024 || rm_size * 10 < range_size;
+
   if (!disallows_index_search && prefers_index_search) {
-    *rm = IndexSearch(c, storage, rm);
+    IndexSearch(c, storage, rm);
     return;
   }
   LinearSearch(c, storage, rm);
@@ -99,9 +109,9 @@
   rm->Intersect(RowMap(std::move(res).TakeIfBitVector()));
 }
 
-RowMap QueryExecutor::IndexSearch(const Constraint& c,
-                                  const storage::Storage& storage,
-                                  RowMap* rm) {
+void QueryExecutor::IndexSearch(const Constraint& c,
+                                const storage::Storage& storage,
+                                RowMap* rm) {
   // Create outmost TableIndexVector.
   std::vector<uint32_t> table_indices = std::move(*rm).TakeAsIndexVector();
 
@@ -109,16 +119,27 @@
       c.op, c.value, table_indices.data(),
       static_cast<uint32_t>(table_indices.size()), false /* sorted */);
 
-  // TODO(b/283763282): Remove after implementing extrinsic binary search.
-  PERFETTO_CHECK(matched.IsBitVector());
+  if (matched.IsBitVector()) {
+    BitVector res = std::move(matched).TakeIfBitVector();
+    uint32_t i = 0;
+    table_indices.erase(
+        std::remove_if(table_indices.begin(), table_indices.end(),
+                       [&i, &res](uint32_t) { return !res.IsSet(i++); }),
+        table_indices.end());
+    *rm = RowMap(std::move(table_indices));
+    return;
+  }
 
-  BitVector res = std::move(matched).TakeIfBitVector();
-  uint32_t i = 0;
-  table_indices.erase(
-      std::remove_if(table_indices.begin(), table_indices.end(),
-                     [&i, &res](uint32_t) { return !res.IsSet(i++); }),
-      table_indices.end());
-  return RowMap(std::move(table_indices));
+  Range res = std::move(matched).TakeIfRange();
+  if (res.size() == 0) {
+    rm->Clear();
+    return;
+  }
+  if (res.size() == table_indices.size()) {
+    return;
+  }
+  // TODO(b/283763282): Remove after implementing extrinsic binary search.
+  PERFETTO_FATAL("Extrinsic binary search is not implemented.");
 }
 
 RowMap QueryExecutor::FilterLegacy(const Table* table,
@@ -142,7 +163,7 @@
     // Mismatched types.
     use_legacy = use_legacy ||
                  (c.op != FilterOp::kIsNull && c.op != FilterOp::kIsNotNull &&
-                  col.type() != c.value.type);
+                  col.type() != c.value.type && !c.value.is_null());
 
     // Dense columns.
     use_legacy = use_legacy || col.IsDense();
@@ -226,8 +247,8 @@
       }
     }
     if (col.IsNullable()) {
-      // String columns are inherently nullable: null values are signified with
-      // Id::Null().
+      // String columns are inherently nullable: null values are signified
+      // with Id::Null().
       PERFETTO_CHECK(col.col_type() != ColumnType::kString);
       storage = std::make_unique<storage::NullStorage>(std::move(storage),
                                                        col.storage_base().bv());
diff --git a/src/trace_processor/db/query_executor.h b/src/trace_processor/db/query_executor.h
index 07281f7..013dbf2 100644
--- a/src/trace_processor/db/query_executor.h
+++ b/src/trace_processor/db/query_executor.h
@@ -70,10 +70,10 @@
   }
 
   // Used only in unittests. Exposes private function.
-  static RowMap IndexedColumnFilterForTesting(const Constraint& c,
-                                              const storage::Storage& col,
-                                              RowMap* rm) {
-    return IndexSearch(c, col, rm);
+  static void IndexedColumnFilterForTesting(const Constraint& c,
+                                            const storage::Storage& col,
+                                            RowMap* rm) {
+    IndexSearch(c, col, rm);
   }
 
  private:
@@ -86,9 +86,7 @@
 
   // Filters the column using Index algorithm - finds the indices to filter the
   // storage with.
-  static RowMap IndexSearch(const Constraint&,
-                            const storage::Storage&,
-                            RowMap*);
+  static void IndexSearch(const Constraint&, const storage::Storage&, RowMap*);
 
   std::vector<storage::Storage*> columns_;
 
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
index e5022c0..95ce5ef 100644
--- a/src/trace_processor/db/query_executor_unittest.cc
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -56,7 +56,7 @@
   std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
 
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 5);
   QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
@@ -73,24 +73,24 @@
 
   Constraint c{0, FilterOp::kLt, SqlValue::Long(2)};
   RowMap rm(0, 10);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &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);
+  ASSERT_EQ(rm.size(), 4u);
+  ASSERT_EQ(rm.Get(0), 0u);
+  ASSERT_EQ(rm.Get(1), 1u);
+  ASSERT_EQ(rm.Get(2), 5u);
+  ASSERT_EQ(rm.Get(3), 6u);
 }
 
 TEST(QueryExecutor, OnlyStorageIndexIsNull) {
   std::vector<int64_t> storage_data{1, 2, 3, 4, 5};
   storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
 
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 5);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_EQ(res.size(), 0u);
+  ASSERT_EQ(rm.size(), 0u);
 }
 
 TEST(QueryExecutor, NullBounds) {
@@ -118,7 +118,7 @@
   BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
   storage::NullStorage storage(std::move(numeric), &bv);
 
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 10);
   QueryExecutor::BoundedColumnFilterForTesting(c, storage, &rm);
 
@@ -143,13 +143,13 @@
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(1)};
   RowMap rm(0, 10);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &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);
+  ASSERT_EQ(rm.size(), 4u);
+  ASSERT_EQ(rm.Get(0), 1u);
+  ASSERT_EQ(rm.Get(1), 3u);
+  ASSERT_EQ(rm.Get(2), 6u);
+  ASSERT_EQ(rm.Get(3), 9u);
 }
 
 TEST(QueryExecutor, NullIndexIsNull) {
@@ -161,16 +161,16 @@
   BitVector bv{1, 1, 0, 1, 1, 0, 0, 0, 1, 0};
   storage::NullStorage storage(std::move(numeric), &bv);
 
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(3)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   RowMap rm(0, 10);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &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);
+  ASSERT_EQ(rm.size(), 5u);
+  ASSERT_EQ(rm.Get(0), 2u);
+  ASSERT_EQ(rm.Get(1), 5u);
+  ASSERT_EQ(rm.Get(2), 6u);
+  ASSERT_EQ(rm.Get(3), 7u);
+  ASSERT_EQ(rm.Get(4), 9u);
 }
 
 TEST(QueryExecutor, SelectorStorageBounds) {
@@ -202,9 +202,9 @@
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(2)};
   RowMap rm(0, 6);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_THAT(res.GetAllIndices(), ElementsAre(2u, 3u, 5u));
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(2u, 3u, 5u));
 }
 
 TEST(QueryExecutor, ArrangementStorageBounds) {
@@ -223,7 +223,7 @@
   ASSERT_THAT(rm.GetAllIndices(), ElementsAre(0u, 4u));
 }
 
-TEST(QueryExecutor, ArrangementOverlaySubsetInputRange) {
+TEST(QueryExecutor, ArrangementStorageSubsetInputRange) {
   std::unique_ptr<storage::Storage> fake =
       storage::FakeStorage::SearchSubset(5u, RowMap::Range(2u, 4u));
 
@@ -237,7 +237,7 @@
   ASSERT_THAT(rm.GetAllIndices(), ElementsAre(2u));
 }
 
-TEST(QueryExecutor, ArrangementOverlaySubsetInputBitvector) {
+TEST(QueryExecutor, ArrangementStorageSubsetInputBitvector) {
   std::unique_ptr<storage::Storage> fake =
       storage::FakeStorage::SearchSubset(5u, BitVector({0, 0, 1, 1, 0}));
 
@@ -262,9 +262,21 @@
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(3)};
   RowMap rm(0, 5);
-  RowMap res = QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
+  QueryExecutor::IndexedColumnFilterForTesting(c, storage, &rm);
 
-  ASSERT_THAT(res.GetAllIndices(), ElementsAre(0u, 4u));
+  ASSERT_THAT(rm.GetAllIndices(), ElementsAre(0u, 4u));
+}
+
+TEST(QueryExecutor, MismatchedTypeNullWithOtherOperations) {
+  std::vector<int64_t> storage_data{0, 1, 2, 3, 0, 1, 2, 3};
+  storage::NumericStorage<int64_t> storage(&storage_data, ColumnType::kInt64);
+
+  // Filter.
+  Constraint c{0, FilterOp::kGe, SqlValue()};
+  QueryExecutor exec({&storage}, 6);
+  RowMap res = exec.Filter({c});
+
+  ASSERT_TRUE(res.empty());
 }
 
 TEST(QueryExecutor, SingleConstraintWithNullAndSelector) {
@@ -336,7 +348,7 @@
   SelectorStorage storage(std::move(null), &selector_bv);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   QueryExecutor exec({&storage}, 6);
   RowMap res = exec.Filter({c});
 
@@ -436,7 +448,7 @@
   IdStorage storage(5);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
@@ -447,7 +459,7 @@
   IdStorage storage(5);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNotNull, SqlValue::Long(0)};
+  Constraint c{0, FilterOp::kIsNotNull, SqlValue()};
   QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
@@ -481,7 +493,7 @@
   SelectorStorage storage(std::move(string), &selector_bv);
 
   // Filter.
-  Constraint c{0, FilterOp::kIsNull, SqlValue::Long(0)};
+  Constraint c{0, FilterOp::kIsNull, SqlValue()};
   QueryExecutor exec({&storage}, 5);
   RowMap res = exec.Filter({c});
 
diff --git a/src/trace_processor/db/storage/arrangement_storage.cc b/src/trace_processor/db/storage/arrangement_storage.cc
index ee777f4..fac7f68 100644
--- a/src/trace_processor/db/storage/arrangement_storage.cc
+++ b/src/trace_processor/db/storage/arrangement_storage.cc
@@ -40,6 +40,12 @@
                   inner_->size());
 }
 
+Storage::SearchValidationResult ArrangementStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
 RangeOrBitVector ArrangementStorage::Search(FilterOp op,
                                             SqlValue sql_val,
                                             Range in) const {
@@ -60,6 +66,7 @@
     }
   } else {
     BitVector storage_bitvector = std::move(storage_result).TakeIfBitVector();
+    PERFETTO_DCHECK(storage_bitvector.size() == *max_i + 1);
 
     // After benchmarking, it turns out this complexity *is* actually worthwhile
     // and has a noticable impact on the performance of this function in real
@@ -67,13 +74,13 @@
 
     // Fast path: we compare as many groups of 64 elements as we can.
     // This should be very easy for the compiler to auto-vectorize.
+    const uint32_t* arrangement_idx = arrangement.data() + in.start;
     uint32_t fast_path_elements = builder.BitsInCompleteWordsUntilFull();
-    uint32_t cur_idx = 0;
     for (uint32_t i = 0; i < fast_path_elements; i += BitVector::kBitsInWord) {
       uint64_t word = 0;
       // This part should be optimised by SIMD and is expected to be fast.
-      for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k, ++cur_idx) {
-        bool comp_result = storage_bitvector.IsSet((*arrangement_)[cur_idx]);
+      for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k, ++arrangement_idx) {
+        bool comp_result = storage_bitvector.IsSet(*arrangement_idx);
         word |= static_cast<uint64_t>(comp_result) << k;
       }
       builder.AppendWord(word);
@@ -81,8 +88,8 @@
 
     // Slow path: we compare <64 elements and append to fill the Builder.
     uint32_t back_elements = builder.BitsUntilFull();
-    for (uint32_t i = 0; i < back_elements; ++i, ++cur_idx) {
-      builder.Append(storage_bitvector.IsSet((*arrangement_)[cur_idx]));
+    for (uint32_t i = 0; i < back_elements; ++i, ++arrangement_idx) {
+      builder.Append(storage_bitvector.IsSet(*arrangement_idx));
     }
   }
   return RangeOrBitVector(std::move(builder).Build());
diff --git a/src/trace_processor/db/storage/arrangement_storage.h b/src/trace_processor/db/storage/arrangement_storage.h
index cfc10c9..97a944e 100644
--- a/src/trace_processor/db/storage/arrangement_storage.h
+++ b/src/trace_processor/db/storage/arrangement_storage.h
@@ -32,6 +32,9 @@
   explicit ArrangementStorage(std::unique_ptr<Storage> inner,
                               const std::vector<uint32_t>* arrangement);
 
+  Storage::SearchValidationResult ValidateSearchConstraints(SqlValue, FilterOp)
+      const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
diff --git a/src/trace_processor/db/storage/dummy_storage.cc b/src/trace_processor/db/storage/dummy_storage.cc
index 4c6eb05..b4f0be5 100644
--- a/src/trace_processor/db/storage/dummy_storage.cc
+++ b/src/trace_processor/db/storage/dummy_storage.cc
@@ -21,6 +21,12 @@
 namespace trace_processor {
 namespace storage {
 
+DummyStorage::SearchValidationResult DummyStorage::ValidateSearchConstraints(
+    SqlValue,
+    FilterOp) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
 RangeOrBitVector DummyStorage::Search(FilterOp, SqlValue, RowMap::Range) const {
   PERFETTO_FATAL("Shouldn't be called");
 }
diff --git a/src/trace_processor/db/storage/dummy_storage.h b/src/trace_processor/db/storage/dummy_storage.h
index 8d76d2f..7fd3a40 100644
--- a/src/trace_processor/db/storage/dummy_storage.h
+++ b/src/trace_processor/db/storage/dummy_storage.h
@@ -36,6 +36,9 @@
 
   RangeOrBitVector Search(FilterOp, SqlValue, RowMap::Range) const override;
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector IndexSearch(FilterOp,
                                SqlValue,
                                uint32_t*,
diff --git a/src/trace_processor/db/storage/fake_storage.cc b/src/trace_processor/db/storage/fake_storage.cc
index 8d2550c..fd9ac2c 100644
--- a/src/trace_processor/db/storage/fake_storage.cc
+++ b/src/trace_processor/db/storage/fake_storage.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/db/storage/fake_storage.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 
 namespace perfetto {
@@ -26,6 +27,12 @@
 FakeStorage::FakeStorage(uint32_t size, SearchStrategy strategy)
     : size_(size), strategy_(strategy) {}
 
+FakeStorage::SearchValidationResult FakeStorage::ValidateSearchConstraints(
+    SqlValue,
+    FilterOp) const {
+  return SearchValidationResult::kOk;
+}
+
 RangeOrBitVector FakeStorage::Search(FilterOp,
                                      SqlValue,
                                      RowMap::Range in) const {
diff --git a/src/trace_processor/db/storage/fake_storage.h b/src/trace_processor/db/storage/fake_storage.h
index 942be1a..2320269 100644
--- a/src/trace_processor/db/storage/fake_storage.h
+++ b/src/trace_processor/db/storage/fake_storage.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -28,6 +29,9 @@
 // Fake implementation of Storage for use in tests.
 class FakeStorage final : public Storage {
  public:
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
diff --git a/src/trace_processor/db/storage/id_storage.cc b/src/trace_processor/db/storage/id_storage.cc
index 0493f97..bec9b041 100644
--- a/src/trace_processor/db/storage/id_storage.cc
+++ b/src/trace_processor/db/storage/id_storage.cc
@@ -16,12 +16,15 @@
 
 #include "src/trace_processor/db/storage/id_storage.h"
 
+#include <optional>
+
 #include "perfetto/base/logging.h"
-#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/public/compiler.h"
 #include "protos/perfetto/trace_processor/serialization.pbzero.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/storage/types.h"
+#include "src/trace_processor/db/storage/utils.h"
 #include "src/trace_processor/tp_metatrace.h"
 
 namespace perfetto {
@@ -68,34 +71,112 @@
   }
   return RangeOrBitVector(std::move(builder).Build());
 }
+
 }  // namespace
 
+IdStorage::SearchValidationResult IdStorage::ValidateSearchConstraints(
+    SqlValue val,
+    FilterOp op) const {
+  // NULL checks.
+  if (PERFETTO_UNLIKELY(val.is_null())) {
+    if (op == FilterOp::kIsNotNull) {
+      return SearchValidationResult::kAllData;
+    }
+    if (op == FilterOp::kIsNull) {
+      return SearchValidationResult::kNoData;
+    }
+    PERFETTO_DFATAL(
+        "Invalid filter operation. NULL should only be compared with 'IS NULL' "
+        "and 'IS NOT NULL'");
+    return SearchValidationResult::kNoData;
+  }
+
+  // FilterOp checks. Switch so that we get a warning if new FilterOp is not
+  // handled.
+  switch (op) {
+    case FilterOp::kEq:
+    case FilterOp::kNe:
+    case FilterOp::kLt:
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+    case FilterOp::kGe:
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Invalid constraint");
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      return SearchValidationResult::kNoData;
+  }
+
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      break;
+    case SqlValue::kString:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kLt || op == FilterOp::kLe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  // TODO(b/307482437): Remove after adding support for double
+  PERFETTO_CHECK(val.type != SqlValue::kDouble);
+
+  // Bounds of the value.
+  if (PERFETTO_UNLIKELY(val.AsLong() > std::numeric_limits<uint32_t>::max())) {
+    if (op == FilterOp::kLe || op == FilterOp::kLt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+  if (PERFETTO_UNLIKELY(val.AsLong() < std::numeric_limits<uint32_t>::min())) {
+    if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+
+  return SearchValidationResult::kOk;
+}
+
 RangeOrBitVector IdStorage::Search(FilterOp op,
                                    SqlValue sql_val,
-                                   RowMap::Range range) const {
+                                   RowMap::Range search_range) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "IdStorage::Search",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  PERFETTO_DCHECK(search_range.end <= size_);
+
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(search_range);
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
   if (op == FilterOp::kNe) {
-    if (sql_val.AsLong() > std::numeric_limits<uint32_t>::max() ||
-        sql_val.AsLong() < std::numeric_limits<uint32_t>::min()) {
-      return RangeOrBitVector(range);
-    }
-
-    uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
-    BitVector ret(range.start, false);
-    ret.Resize(range.end, true);
+    BitVector ret(search_range.start, false);
+    ret.Resize(search_range.end, true);
     ret.Resize(size_, false);
-
     ret.Clear(val);
     return RangeOrBitVector(std::move(ret));
   }
-  return RangeOrBitVector(BinarySearchIntrinsic(op, sql_val, range));
+  return RangeOrBitVector(BinarySearchIntrinsic(op, val, search_range));
 }
 
 RangeOrBitVector IdStorage::IndexSearch(FilterOp op,
@@ -109,29 +190,17 @@
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
-  // Validate sql_val
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return RangeOrBitVector(Range(indices_size, true));
-    }
-    return RangeOrBitVector(Range());
+
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
   }
 
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return RangeOrBitVector(Range(indices_size, true));
-    }
-    return RangeOrBitVector(Range());
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return RangeOrBitVector(Range(indices_size, true));
-    }
-    return RangeOrBitVector(Range());
-  }
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
   switch (op) {
@@ -154,46 +223,15 @@
       return IndexSearchWithComparator(val, indices, indices_size,
                                        std::greater_equal<uint32_t>());
     case FilterOp::kIsNotNull:
-      return RangeOrBitVector(Range(indices_size, true));
     case FilterOp::kIsNull:
     case FilterOp::kGlob:
     case FilterOp::kRegex:
-      return RangeOrBitVector(Range());
+      PERFETTO_FATAL("Invalid filter operation");
   }
   PERFETTO_FATAL("FilterOp not matched");
 }
 
-Range IdStorage::BinarySearchIntrinsic(FilterOp op,
-                                       SqlValue sql_val,
-                                       Range range) const {
-  PERFETTO_DCHECK(range.end <= size_);
-
-  // Validate sql_value
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return range;
-    }
-    return Range();
-  }
-
-  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
-
+Range IdStorage::BinarySearchIntrinsic(FilterOp op, Id val, Range range) const {
   switch (op) {
     case FilterOp::kEq:
       return Range(val, val + (range.start <= val && val < range.end));
@@ -206,13 +244,11 @@
     case FilterOp::kGt:
       return RowMap::Range(std::max(val + 1, range.start), range.end);
     case FilterOp::kIsNotNull:
-      return range;
     case FilterOp::kNe:
-      PERFETTO_FATAL("Shouldn't be called");
     case FilterOp::kIsNull:
     case FilterOp::kGlob:
     case FilterOp::kRegex:
-      return RowMap::Range();
+      PERFETTO_FATAL("Invalid filter operation");
   }
   PERFETTO_FATAL("FilterOp not matched");
 }
diff --git a/src/trace_processor/db/storage/id_storage.h b/src/trace_processor/db/storage/id_storage.h
index e797028..475c29b 100644
--- a/src/trace_processor/db/storage/id_storage.h
+++ b/src/trace_processor/db/storage/id_storage.h
@@ -16,6 +16,10 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_ID_STORAGE_H_
 #define SRC_TRACE_PROCESSOR_DB_STORAGE_ID_STORAGE_H_
 
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
@@ -34,6 +38,9 @@
  public:
   explicit IdStorage(uint32_t size) : size_(size) {}
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -53,9 +60,11 @@
   uint32_t size() const override { return size_; }
 
  private:
-  BitVector IndexSearch(FilterOp, SqlValue, uint32_t*, uint32_t) const;
+  using Id = uint32_t;
+
+  BitVector IndexSearch(FilterOp, Id, uint32_t*, uint32_t) const;
   RowMap::Range BinarySearchIntrinsic(FilterOp op,
-                                      SqlValue val,
+                                      Id,
                                       RowMap::Range search_range) const;
 
   const uint32_t size_ = 0;
diff --git a/src/trace_processor/db/storage/id_storage_unittest.cc b/src/trace_processor/db/storage/id_storage_unittest.cc
index 57c148d..2262c43 100644
--- a/src/trace_processor/db/storage/id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/id_storage_unittest.cc
@@ -14,17 +14,100 @@
  * limitations under the License.
  */
 #include "src/trace_processor/db/storage/id_storage.h"
+#include <limits>
 
 #include "src/trace_processor/db/storage/types.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace trace_processor {
+
+inline bool operator==(const RowMap::Range& a, const RowMap::Range& b) {
+  return std::tie(a.start, a.end) == std::tie(b.start, b.end);
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
+TEST(IdStorageUnittest, InvalidSearchConstraints) {
+  IdStorage storage(100);
+  Range test_range(10, 20);
+  Range empty_range;
+
+  // NULL checks
+  SqlValue val;
+  val.type = SqlValue::kNull;
+  Range search_result =
+      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+
+  // FilterOp checks
+  search_result =
+      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Type checks
+  search_result =
+      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Value bounds
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
+  search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, test_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
 TEST(IdStorageUnittest, BinarySearchIntrinsicEqSimple) {
   IdStorage storage(100);
   Range range = storage.Search(FilterOp::kEq, SqlValue::Long(15), Range(10, 20))
diff --git a/src/trace_processor/db/storage/null_storage.cc b/src/trace_processor/db/storage/null_storage.cc
index cac62d6..6de2759 100644
--- a/src/trace_processor/db/storage/null_storage.cc
+++ b/src/trace_processor/db/storage/null_storage.cc
@@ -73,6 +73,12 @@
 
 }  // namespace
 
+Storage::SearchValidationResult NullStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return storage_->ValidateSearchConstraints(sql_val, op);
+}
+
 NullStorage::NullStorage(std::unique_ptr<Storage> storage,
                          const BitVector* non_null)
     : storage_(std::move(storage)), non_null_(non_null) {
diff --git a/src/trace_processor/db/storage/null_storage.h b/src/trace_processor/db/storage/null_storage.h
index c1ea44b..3087749 100644
--- a/src/trace_processor/db/storage/null_storage.h
+++ b/src/trace_processor/db/storage/null_storage.h
@@ -34,6 +34,9 @@
  public:
   NullStorage(std::unique_ptr<Storage> storage, const BitVector* non_null);
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
index b7ecfa9..0b792de 100644
--- a/src/trace_processor/db/storage/numeric_storage.cc
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -17,12 +17,17 @@
 
 #include "src/trace_processor/db/storage/numeric_storage.h"
 
+#include <cmath>
 #include <cstddef>
 #include <string>
 
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
 #include "protos/perfetto/trace_processor/serialization.pbzero.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 #include "src/trace_processor/db/storage/utils.h"
 #include "src/trace_processor/tp_metatrace.h"
@@ -32,7 +37,7 @@
 namespace storage {
 namespace {
 
-// All viable numeric values for ColumnTypes.
+using Range = RowMap::Range;
 using NumericValue = std::variant<uint32_t, int32_t, int64_t, double_t>;
 
 // Using the fact that binary operators in std are operators() of classes, we
@@ -46,33 +51,22 @@
                                      std::equal_to<T>,
                                      std::not_equal_to<T>>;
 
-// Based on SqlValue and ColumnType, casts SqlValue to proper type, returns
-// std::nullopt if SqlValue can't be cast and should be considered invalid for
-// comparison.
-inline std::optional<NumericValue> GetNumericTypeVariant(ColumnType type,
-                                                         SqlValue val) {
-  if (val.is_null())
-    return std::nullopt;
-
+// Based on SqlValue and ColumnType, casts SqlValue to proper type. Assumes the
+// |val| and |type| are correct.
+inline NumericValue GetNumericTypeVariant(ColumnType type, SqlValue val) {
   switch (type) {
     case ColumnType::kDouble:
       return val.AsDouble();
     case ColumnType::kInt64:
       return val.AsLong();
     case ColumnType::kInt32:
-      if (val.AsLong() > std::numeric_limits<int32_t>::max() ||
-          val.AsLong() < std::numeric_limits<int32_t>::min())
-        return std::nullopt;
       return static_cast<int32_t>(val.AsLong());
     case ColumnType::kUint32:
-      if (val.AsLong() > std::numeric_limits<uint32_t>::max() ||
-          val.AsLong() < std::numeric_limits<uint32_t>::min())
-        return std::nullopt;
       return static_cast<uint32_t>(val.AsLong());
     case ColumnType::kString:
     case ColumnType::kDummy:
     case ColumnType::kId:
-      return std::nullopt;
+      PERFETTO_FATAL("Invalid type");
   }
   PERFETTO_FATAL("For GCC");
 }
@@ -201,72 +195,198 @@
 
 }  // namespace
 
+NumericStorageBase::SearchValidationResult
+NumericStorageBase::ValidateSearchConstraints(SqlValue val, FilterOp op) const {
+  // NULL checks.
+  if (PERFETTO_UNLIKELY(val.is_null())) {
+    if (op == FilterOp::kIsNotNull) {
+      return SearchValidationResult::kAllData;
+    }
+    if (op == FilterOp::kIsNull) {
+      return SearchValidationResult::kNoData;
+    }
+    PERFETTO_FATAL(
+        "Invalid path. NULL should only be compared with 'IS NULL' and 'IS NOT "
+        "NULL'");
+  }
+
+  // FilterOp checks. Switch so that we get a warning if new FilterOp is not
+  // handled.
+  switch (op) {
+    case FilterOp::kEq:
+    case FilterOp::kNe:
+    case FilterOp::kLt:
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+    case FilterOp::kGe:
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Invalid constraint");
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      return SearchValidationResult::kNoData;
+  }
+
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      break;
+    case SqlValue::kString:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kLt || op == FilterOp::kLe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  // TODO(b/307482437): There is currently no support for comparison with double
+  // and it is prevented on QueryExecutor level.
+  if (type_ != ColumnType::kDouble) {
+    PERFETTO_CHECK(val.type != SqlValue::kDouble);
+  }
+
+  // Bounds of the value.
+  enum ExtremeVal { kTooBig, kTooSmall, kOk };
+  ExtremeVal extreme_validator = kOk;
+
+  switch (type_) {
+    case ColumnType::kDouble:
+      // Any value would make a sensible comparison with a double.
+    case ColumnType::kInt64:
+      // TODO(b/307482437): As long as the type is not double there is nothing
+      // to verify here, as all values are going to be in the int64_t limits.
+      break;
+    case ColumnType::kInt32:
+      if (val.AsLong() > std::numeric_limits<int32_t>::max()) {
+        extreme_validator = kTooBig;
+        break;
+      }
+      if (val.AsLong() < std::numeric_limits<int32_t>::min()) {
+        extreme_validator = kTooSmall;
+        break;
+      }
+      break;
+    case ColumnType::kUint32:
+      if (val.AsLong() > std::numeric_limits<uint32_t>::max()) {
+        extreme_validator = kTooBig;
+        break;
+      }
+      if (val.AsLong() < std::numeric_limits<uint32_t>::min()) {
+        extreme_validator = kTooSmall;
+        break;
+      }
+      break;
+    case ColumnType::kString:
+    case ColumnType::kDummy:
+    case ColumnType::kId:
+      break;
+  }
+
+  switch (extreme_validator) {
+    case kOk:
+      return Storage::SearchValidationResult::kOk;
+    case kTooBig:
+      if (op == FilterOp::kLt || op == FilterOp::kLe || op == FilterOp::kNe) {
+        return SearchValidationResult::kAllData;
+      }
+      return SearchValidationResult::kNoData;
+    case kTooSmall:
+      if (op == FilterOp::kGt || op == FilterOp::kGe || op == FilterOp::kNe) {
+        return SearchValidationResult::kAllData;
+      }
+      return SearchValidationResult::kNoData;
+  }
+
+  PERFETTO_FATAL("For GCC");
+}
+
 RangeOrBitVector NumericStorageBase::Search(FilterOp op,
-                                            SqlValue value,
-                                            RowMap::Range range) const {
+                                            SqlValue sql_val,
+                                            RowMap::Range search_range) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "NumericStorage::Search",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, search_range.end));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  NumericValue val = GetNumericTypeVariant(type_, sql_val);
+
   if (is_sorted_) {
     if (op != FilterOp::kNe) {
-      return RangeOrBitVector(BinarySearchIntrinsic(op, value, range));
+      return RangeOrBitVector(BinarySearchIntrinsic(op, val, search_range));
     }
     // Not equal is a special operation on binary search, as it doesn't define a
     // range, and rather just `not` range returned with `equal` operation.
-    RowMap::Range r = BinarySearchIntrinsic(FilterOp::kEq, value, range);
+    RowMap::Range r = BinarySearchIntrinsic(FilterOp::kEq, val, search_range);
     BitVector bv(r.start, true);
     bv.Resize(r.end, false);
-    bv.Resize(range.end, true);
+    bv.Resize(search_range.end, true);
     return RangeOrBitVector(std::move(bv));
   }
-  return RangeOrBitVector(LinearSearchInternal(op, value, range));
+
+  return RangeOrBitVector(LinearSearchInternal(op, val, search_range));
 }
 
 RangeOrBitVector NumericStorageBase::IndexSearch(FilterOp op,
-                                                 SqlValue value,
+                                                 SqlValue sql_val,
                                                  uint32_t* indices,
-                                                 uint32_t indices_count,
+                                                 uint32_t indices_size,
                                                  bool sorted) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "NumericStorage::IndexSearch",
-                    [indices_count, op](metatrace::Record* r) {
-                      r->AddArg("Count", std::to_string(indices_count));
+                    [indices_size, op](metatrace::Record* r) {
+                      r->AddArg("Count", std::to_string(indices_size));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
+
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+  NumericValue val = GetNumericTypeVariant(type_, sql_val);
   if (sorted) {
     return RangeOrBitVector(
-        BinarySearchExtrinsic(op, value, indices, indices_count));
+        BinarySearchExtrinsic(op, val, indices, indices_size));
   }
-  return RangeOrBitVector(
-      IndexSearchInternal(op, value, indices, indices_count));
+  return RangeOrBitVector(IndexSearchInternal(op, val, indices, indices_size));
 }
 
 BitVector NumericStorageBase::LinearSearchInternal(FilterOp op,
-                                                   SqlValue sql_val,
+                                                   NumericValue val,
                                                    RowMap::Range range) const {
-  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-  if (op == FilterOp::kIsNotNull)
-    return BitVector(range.end, true);
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return BitVector(range.end, false);
-
   BitVector::Builder builder(range.end, range.start);
-  if (const auto* u32 = std::get_if<uint32_t>(&*val)) {
+  if (const auto* u32 = std::get_if<uint32_t>(&val)) {
     auto* start = static_cast<const uint32_t*>(data_) + range.start;
     TypedLinearSearch(*u32, start, op, builder);
-  } else if (const auto* i64 = std::get_if<int64_t>(&*val)) {
+  } else if (const auto* i64 = std::get_if<int64_t>(&val)) {
     auto* start = static_cast<const int64_t*>(data_) + range.start;
     TypedLinearSearch(*i64, start, op, builder);
-  } else if (const auto* i32 = std::get_if<int32_t>(&*val)) {
+  } else if (const auto* i32 = std::get_if<int32_t>(&val)) {
     auto* start = static_cast<const int32_t*>(data_) + range.start;
     TypedLinearSearch(*i32, start, op, builder);
-  } else if (const auto* db = std::get_if<double>(&*val)) {
+  } else if (const auto* db = std::get_if<double>(&val)) {
     auto* start = static_cast<const double*>(data_) + range.start;
     TypedLinearSearch(*db, start, op, builder);
   } else {
@@ -277,16 +397,9 @@
 
 BitVector NumericStorageBase::IndexSearchInternal(
     FilterOp op,
-    SqlValue sql_val,
+    NumericValue val,
     uint32_t* indices,
     uint32_t indices_count) const {
-  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-  if (op == FilterOp::kIsNotNull)
-    return BitVector(indices_count, true);
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return BitVector(indices_count, false);
-
   BitVector::Builder builder(indices_count);
   std::visit(
       [this, indices, op, &builder](auto val) {
@@ -299,37 +412,30 @@
             },
             GetFilterOpVariant<T>(op));
       },
-      *val);
+      val);
   return std::move(builder).Build();
 }
 
 RowMap::Range NumericStorageBase::BinarySearchIntrinsic(
     FilterOp op,
-    SqlValue sql_val,
+    NumericValue val,
     RowMap::Range search_range) const {
-  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-  if (op == FilterOp::kIsNotNull)
-    return search_range;
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return RowMap::Range();
-
   switch (op) {
     case FilterOp::kEq:
-      return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
-                           UpperBoundIntrinsic(data_, *val, search_range));
+      return RowMap::Range(LowerBoundIntrinsic(data_, val, search_range),
+                           UpperBoundIntrinsic(data_, val, search_range));
     case FilterOp::kLe: {
       return RowMap::Range(search_range.start,
-                           UpperBoundIntrinsic(data_, *val, search_range));
+                           UpperBoundIntrinsic(data_, val, search_range));
     }
     case FilterOp::kLt:
       return RowMap::Range(search_range.start,
-                           LowerBoundIntrinsic(data_, *val, search_range));
+                           LowerBoundIntrinsic(data_, val, search_range));
     case FilterOp::kGe:
-      return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
+      return RowMap::Range(LowerBoundIntrinsic(data_, val, search_range),
                            search_range.end);
     case FilterOp::kGt:
-      return RowMap::Range(UpperBoundIntrinsic(data_, *val, search_range),
+      return RowMap::Range(UpperBoundIntrinsic(data_, val, search_range),
                            search_range.end);
     case FilterOp::kNe:
     case FilterOp::kIsNull:
@@ -343,34 +449,26 @@
 
 RowMap::Range NumericStorageBase::BinarySearchExtrinsic(
     FilterOp op,
-    SqlValue sql_val,
+    NumericValue val,
     uint32_t* indices,
     uint32_t indices_count) const {
-  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
-
-  if (op == FilterOp::kIsNotNull)
-    return RowMap::Range(0, size());
-
-  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
-    return RowMap::Range();
-
   switch (op) {
     case FilterOp::kEq:
       return RowMap::Range(
-          LowerBoundExtrinsic(data_, *val, indices, indices_count),
-          UpperBoundExtrinsic(data_, *val, indices, indices_count));
+          LowerBoundExtrinsic(data_, val, indices, indices_count),
+          UpperBoundExtrinsic(data_, val, indices, indices_count));
     case FilterOp::kLe:
       return RowMap::Range(
-          0, UpperBoundExtrinsic(data_, *val, indices, indices_count));
+          0, UpperBoundExtrinsic(data_, val, indices, indices_count));
     case FilterOp::kLt:
       return RowMap::Range(
-          0, LowerBoundExtrinsic(data_, *val, indices, indices_count));
+          0, LowerBoundExtrinsic(data_, val, indices, indices_count));
     case FilterOp::kGe:
       return RowMap::Range(
-          LowerBoundExtrinsic(data_, *val, indices, indices_count), size_);
+          LowerBoundExtrinsic(data_, val, indices, indices_count), size_);
     case FilterOp::kGt:
       return RowMap::Range(
-          UpperBoundExtrinsic(data_, *val, indices, indices_count), size_);
+          UpperBoundExtrinsic(data_, val, indices, indices_count), size_);
     case FilterOp::kNe:
     case FilterOp::kIsNull:
     case FilterOp::kIsNotNull:
@@ -382,7 +480,6 @@
 }
 
 void NumericStorageBase::StableSort(uint32_t* rows, uint32_t rows_size) const {
-  NumericValue val = *GetNumericTypeVariant(type_, SqlValue::Long(0));
   std::visit(
       [this, &rows, rows_size](auto val_data) {
         using T = decltype(val_data);
@@ -394,7 +491,7 @@
                            return first_val < second_val;
                          });
       },
-      val);
+      GetNumericTypeVariant(type_, SqlValue::Long(0)));
 }
 
 void NumericStorageBase::Sort(uint32_t*, uint32_t) const {
diff --git a/src/trace_processor/db/storage/numeric_storage.h b/src/trace_processor/db/storage/numeric_storage.h
index a91e289..741a8e0 100644
--- a/src/trace_processor/db/storage/numeric_storage.h
+++ b/src/trace_processor/db/storage/numeric_storage.h
@@ -18,6 +18,7 @@
 
 #include <variant>
 
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 
@@ -33,6 +34,9 @@
 // Storage for all numeric type data (i.e. doubles, int32, int64, uint32).
 class NumericStorageBase : public Storage {
  public:
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -59,21 +63,24 @@
       : size_(size), data_(data), type_(type), is_sorted_(is_sorted) {}
 
  private:
+  // All viable numeric values for ColumnTypes.
+  using NumericValue = std::variant<uint32_t, int32_t, int64_t, double>;
+
   BitVector LinearSearchInternal(FilterOp op,
-                                 SqlValue val,
+                                 NumericValue val,
                                  RowMap::Range) const;
 
   BitVector IndexSearchInternal(FilterOp op,
-                                SqlValue value,
+                                NumericValue value,
                                 uint32_t* indices,
                                 uint32_t indices_count) const;
 
   RowMap::Range BinarySearchIntrinsic(FilterOp op,
-                                      SqlValue val,
+                                      NumericValue val,
                                       RowMap::Range search_range) const;
 
   RowMap::Range BinarySearchExtrinsic(FilterOp op,
-                                      SqlValue val,
+                                      NumericValue val,
                                       uint32_t* indices,
                                       uint32_t indices_count) const;
 
diff --git a/src/trace_processor/db/storage/numeric_storage_unittest.cc b/src/trace_processor/db/storage/numeric_storage_unittest.cc
index b6ffb59..e82883f 100644
--- a/src/trace_processor/db/storage/numeric_storage_unittest.cc
+++ b/src/trace_processor/db/storage/numeric_storage_unittest.cc
@@ -20,11 +20,160 @@
 
 namespace perfetto {
 namespace trace_processor {
+
+inline bool operator==(const RowMap::Range& a, const RowMap::Range& b) {
+  return std::tie(a.start, a.end) == std::tie(b.start, b.end);
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
+TEST(IdStorageUnittest, InvalidSearchConstraintsGeneralChecks) {
+  std::vector<uint32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32);
+
+  Range test_range(20, 100);
+  Range full_range(0, 100);
+  Range empty_range;
+
+  // NULL checks
+  SqlValue val;
+  val.type = SqlValue::kNull;
+  Range search_result =
+      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  // FilterOp checks
+  search_result =
+      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Type checks
+  search_result =
+      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
+TEST(IdStorageUnittest, InvalidValueBoundsUint32) {
+  std::vector<uint32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage<uint32_t> storage(&data_vec, ColumnType::kUint32);
+
+  Range test_range(20, 100);
+  Range full_range(0, 100);
+  Range empty_range;
+
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
+  Range search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
+TEST(IdStorageUnittest, InvalidValueBoundsInt32) {
+  std::vector<int32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage<int32_t> storage(&data_vec, ColumnType::kInt32);
+
+  Range test_range(20, 100);
+  Range full_range(0, 100);
+  Range empty_range;
+
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 10);
+  Range search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<int32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
 TEST(NumericStorageUnittest, StableSortTrivial) {
   std::vector<uint32_t> data_vec{0, 1, 2, 0, 1, 2, 0, 1, 2};
   std::vector<uint32_t> out = {0, 1, 2, 3, 4, 5, 6, 7, 8};
diff --git a/src/trace_processor/db/storage/selector_storage.cc b/src/trace_processor/db/storage/selector_storage.cc
index 8a4e93a..934b900 100644
--- a/src/trace_processor/db/storage/selector_storage.cc
+++ b/src/trace_processor/db/storage/selector_storage.cc
@@ -31,6 +31,12 @@
                                  const BitVector* selector)
     : inner_(std::move(inner)), selector_(selector) {}
 
+Storage::SearchValidationResult SelectorStorage::ValidateSearchConstraints(
+    SqlValue sql_val,
+    FilterOp op) const {
+  return inner_->ValidateSearchConstraints(sql_val, op);
+}
+
 RangeOrBitVector SelectorStorage::Search(FilterOp op,
                                          SqlValue sql_val,
                                          RowMap::Range in) const {
diff --git a/src/trace_processor/db/storage/selector_storage.h b/src/trace_processor/db/storage/selector_storage.h
index bc8de30..9d368e2 100644
--- a/src/trace_processor/db/storage/selector_storage.h
+++ b/src/trace_processor/db/storage/selector_storage.h
@@ -31,6 +31,9 @@
  public:
   SelectorStorage(std::unique_ptr<Storage> storage, const BitVector* non_null);
 
+  Storage::SearchValidationResult ValidateSearchConstraints(SqlValue, FilterOp)
+      const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
diff --git a/src/trace_processor/db/storage/set_id_storage.cc b/src/trace_processor/db/storage/set_id_storage.cc
index 8244ae0..570afb7 100644
--- a/src/trace_processor/db/storage/set_id_storage.cc
+++ b/src/trace_processor/db/storage/set_id_storage.cc
@@ -58,74 +58,139 @@
 
 }  // namespace
 
+SetIdStorage::SearchValidationResult SetIdStorage::ValidateSearchConstraints(
+    SqlValue val,
+    FilterOp op) const {
+  // NULL checks.
+  if (PERFETTO_UNLIKELY(val.is_null())) {
+    if (op == FilterOp::kIsNotNull) {
+      return SearchValidationResult::kAllData;
+    }
+    if (op == FilterOp::kIsNull) {
+      return SearchValidationResult::kNoData;
+    }
+    PERFETTO_FATAL(
+        "Invalid filter operation. NULL should only be compared with 'IS NULL' "
+        "and 'IS NOT NULL'");
+  }
+
+  // FilterOp checks. Switch so that we get a warning if new FilterOp is not
+  // handled.
+  switch (op) {
+    case FilterOp::kEq:
+    case FilterOp::kNe:
+    case FilterOp::kLt:
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+    case FilterOp::kGe:
+      break;
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+      PERFETTO_FATAL("Invalid constraints.");
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      return SearchValidationResult::kNoData;
+  }
+
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      break;
+    case SqlValue::kString:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kLt || op == FilterOp::kLe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  // TODO(b/307482437): Remove after adding support for double
+  PERFETTO_CHECK(val.type != SqlValue::kDouble);
+
+  // Bounds of the value.
+  if (PERFETTO_UNLIKELY(val.AsLong() > std::numeric_limits<uint32_t>::max())) {
+    if (op == FilterOp::kLe || op == FilterOp::kLt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+  if (PERFETTO_UNLIKELY(val.AsLong() < std::numeric_limits<uint32_t>::min())) {
+    if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
+      return SearchValidationResult::kAllData;
+    }
+    return SearchValidationResult::kNoData;
+  }
+
+  return SearchValidationResult::kOk;
+}
+
 RangeOrBitVector SetIdStorage::Search(FilterOp op,
                                       SqlValue sql_val,
-                                      RowMap::Range range) const {
+                                      RowMap::Range search_range) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "SetIdStorage::Search",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  PERFETTO_DCHECK(range.end <= size());
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, search_range.end));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  PERFETTO_DCHECK(search_range.end <= size());
+  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
   if (op == FilterOp::kNe) {
-    if (sql_val.is_null()) {
-      return RangeOrBitVector(Range());
-    }
     // Not equal is a special operation on binary search, as it doesn't define a
     // range, and rather just `not` range returned with `equal` operation.
     RowMap::Range eq_range =
-        BinarySearchIntrinsic(FilterOp::kEq, sql_val, range);
-    BitVector bv(range.start, false);
+        BinarySearchIntrinsic(FilterOp::kEq, val, search_range);
+    BitVector bv(search_range.start, false);
     bv.Resize(eq_range.start, true);
     bv.Resize(eq_range.end, false);
-    bv.Resize(range.end, true);
+    bv.Resize(search_range.end, true);
     return RangeOrBitVector(std::move(bv));
   }
-  return RangeOrBitVector(BinarySearchIntrinsic(op, sql_val, range));
+  return RangeOrBitVector(BinarySearchIntrinsic(op, val, search_range));
 }
 
 RangeOrBitVector SetIdStorage::IndexSearch(FilterOp op,
                                            SqlValue sql_val,
                                            uint32_t* indices,
-                                           uint32_t indices_count,
+                                           uint32_t indices_size,
                                            bool) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "SetIdStorage::IndexSearch",
-                    [indices_count, op](metatrace::Record* r) {
-                      r->AddArg("Count", std::to_string(indices_count));
+                    [indices_size, op](metatrace::Record* r) {
+                      r->AddArg("Count", std::to_string(indices_size));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  // Validate sql_val
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return RangeOrBitVector(Range(indices_count, true));
-    }
-    return RangeOrBitVector(Range());
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
   }
 
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return RangeOrBitVector(Range(indices_count, true));
-    }
-    return RangeOrBitVector(Range());
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return RangeOrBitVector(Range(indices_count, true));
-    }
-    return RangeOrBitVector(Range());
-  }
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
-  BitVector::Builder builder(indices_count);
+  BitVector::Builder builder(indices_size);
 
   // TODO(mayzner): Instead of utils::IndexSearchWithComparator, use the
   // property of SetId data - that for each index i, data[i] <= i.
@@ -155,7 +220,7 @@
                                        std::greater_equal<uint32_t>(), builder);
       break;
     case FilterOp::kIsNotNull:
-      return RangeOrBitVector(Range(0, indices_count));
+      return RangeOrBitVector(Range(0, indices_size));
     case FilterOp::kIsNull:
       return RangeOrBitVector(Range());
     case FilterOp::kGlob:
@@ -166,34 +231,8 @@
 }
 
 Range SetIdStorage::BinarySearchIntrinsic(FilterOp op,
-                                          SqlValue sql_val,
+                                          SetId val,
                                           Range range) const {
-  // Validate sql_value
-  if (PERFETTO_UNLIKELY(sql_val.is_null())) {
-    if (op == FilterOp::kIsNotNull) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() >
-                        std::numeric_limits<uint32_t>::max())) {
-    if (op == FilterOp::kLe || op == FilterOp::kLt) {
-      return range;
-    }
-    return Range();
-  }
-
-  if (PERFETTO_UNLIKELY(sql_val.AsLong() <
-                        std::numeric_limits<uint32_t>::min())) {
-    if (op == FilterOp::kGe || op == FilterOp::kGt) {
-      return range;
-    }
-    return Range();
-  }
-
-  uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
-
   switch (op) {
     case FilterOp::kEq:
       return Range(LowerBoundIntrinsic(values_->data(), val, range),
diff --git a/src/trace_processor/db/storage/set_id_storage.h b/src/trace_processor/db/storage/set_id_storage.h
index ad9fd1a..6886bef 100644
--- a/src/trace_processor/db/storage/set_id_storage.h
+++ b/src/trace_processor/db/storage/set_id_storage.h
@@ -36,6 +36,9 @@
 
   explicit SetIdStorage(const std::vector<uint32_t>* data) : values_(data) {}
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -57,9 +60,9 @@
   }
 
  private:
-  BitVector IndexSearch(FilterOp, SqlValue, uint32_t*, uint32_t) const;
-  RowMap::Range BinarySearchIntrinsic(FilterOp op,
-                                      SqlValue val,
+  BitVector IndexSearch(FilterOp, SetId, uint32_t*, uint32_t) const;
+  RowMap::Range BinarySearchIntrinsic(FilterOp,
+                                      SetId,
                                       RowMap::Range search_range) const;
 
   // TODO(b/307482437): After the migration vectors should be owned by storage,
diff --git a/src/trace_processor/db/storage/set_id_storage_unittest.cc b/src/trace_processor/db/storage/set_id_storage_unittest.cc
index f487dfa..658b731 100644
--- a/src/trace_processor/db/storage/set_id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/set_id_storage_unittest.cc
@@ -19,11 +19,96 @@
 
 namespace perfetto {
 namespace trace_processor {
+
+inline bool operator==(const RowMap::Range& a, const RowMap::Range& b) {
+  return std::tie(a.start, a.end) == std::tie(b.start, b.end);
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
+TEST(IdStorageUnittest, InvalidSearchConstraints) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+
+  Range test_range(3, 9);
+  Range full_range(0, 9);
+  Range empty_range;
+
+  // NULL checks
+  SqlValue val;
+  val.type = SqlValue::kNull;
+  Range search_result =
+      storage.Search(FilterOp::kIsNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kIsNotNull, val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  // FilterOp checks
+  search_result =
+      storage.Search(FilterOp::kGlob, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kRegex, SqlValue::Long(15), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Type checks
+  search_result =
+      storage.Search(FilterOp::kGe, SqlValue::String("cheese"), test_range)
+          .TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  // Value bounds
+  SqlValue max_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
+  search_result =
+      storage.Search(FilterOp::kGe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kGt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kLt, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, max_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  SqlValue min_val = SqlValue::Long(
+      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
+  search_result =
+      storage.Search(FilterOp::kGe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kGt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+  search_result =
+      storage.Search(FilterOp::kNe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, full_range);
+
+  search_result =
+      storage.Search(FilterOp::kLe, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kLt, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+  search_result =
+      storage.Search(FilterOp::kEq, min_val, test_range).TakeIfRange();
+  ASSERT_EQ(search_result, empty_range);
+}
+
 TEST(SetIdStorageUnittest, SearchEqSimple) {
   std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
 
diff --git a/src/trace_processor/db/storage/storage.h b/src/trace_processor/db/storage/storage.h
index 605329b..2a9c9fb 100644
--- a/src/trace_processor/db/storage/storage.h
+++ b/src/trace_processor/db/storage/storage.h
@@ -16,7 +16,6 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_H_
 #define SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_H_
 
-#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/storage/types.h"
@@ -34,8 +33,24 @@
  public:
   using StorageProto = protos::pbzero::SerializedColumn_Storage;
 
+  enum class SearchValidationResult { kOk = 0, kAllData = 1, kNoData = 2 };
+
   virtual ~Storage();
 
+  // Verifies whether any further filtering is needed and if not, whether the
+  // search would return all values or none of them. This allows for skipping
+  // the |Search| and |IndexSearch| in special cases.
+  //
+  // Notes for callers:
+  // * This function is being called by Search and IndexSearch and there is no
+  //   need to call it before searches.
+  // * The SqlValue and FilterOp have to be valid in Sqlite: it will crash if
+  //   either: value is NULL and operation is different than "IS NULL" and "IS
+  //   NOT NULL" or the operation is "IS NULL" and "IS NOT NULL" and value is
+  //   different than NULL.
+  virtual SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                           FilterOp) const = 0;
+
   // Searches for elements which match |op| and |value| between |range.start|
   // and |range.end|.
   //
@@ -49,9 +64,7 @@
   //    optimize based on this.
   //  * Implementations should ensure that, if they return a BitVector, it is
   //    precisely of size |range.end|.
-  virtual RangeOrBitVector Search(FilterOp op,
-                                  SqlValue value,
-                                  RowMap::Range range) const = 0;
+  virtual RangeOrBitVector Search(FilterOp, SqlValue, RowMap::Range) const = 0;
 
   // Searches for elements which match |op| and |value| at the positions given
   // by |indices| array. The |sorted| flag allows the caller to specify if the
@@ -69,8 +82,8 @@
   // Notes for implementors:
   //  * Implementations should ensure that, if they return a BitVector, it is
   //    precisely of size |indices_count|.
-  virtual RangeOrBitVector IndexSearch(FilterOp op,
-                                       SqlValue value,
+  virtual RangeOrBitVector IndexSearch(FilterOp,
+                                       SqlValue,
                                        uint32_t* indices,
                                        uint32_t indices_count,
                                        bool sorted) const = 0;
diff --git a/src/trace_processor/db/storage/string_storage.cc b/src/trace_processor/db/storage/string_storage.cc
index d1d88b7..d9c9b98 100644
--- a/src/trace_processor/db/storage/string_storage.cc
+++ b/src/trace_processor/db/storage/string_storage.cc
@@ -19,7 +19,6 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/trace_processor/basic_types.h"
 #include "protos/perfetto/trace_processor/serialization.pbzero.h"
 
 #include "perfetto/base/logging.h"
@@ -27,6 +26,7 @@
 #include "src/trace_processor/containers/null_term_string_view.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/storage/storage.h"
 #include "src/trace_processor/db/storage/types.h"
 
 #include "src/trace_processor/db/storage/utils.h"
@@ -163,55 +163,97 @@
 
 }  // namespace
 
+StringStorage::SearchValidationResult StringStorage::ValidateSearchConstraints(
+    SqlValue val,
+    FilterOp op) const {
+  // Type checks.
+  switch (val.type) {
+    case SqlValue::kNull:
+    case SqlValue::kString:
+      break;
+    case SqlValue::kLong:
+    case SqlValue::kDouble:
+      // Any string is always more than any numeric.
+      if (op == FilterOp::kGt || op == FilterOp::kGe) {
+        return Storage::SearchValidationResult::kAllData;
+      }
+      return Storage::SearchValidationResult::kNoData;
+    case SqlValue::kBytes:
+      return Storage::SearchValidationResult::kNoData;
+  }
+
+  return SearchValidationResult::kOk;
+}
+
 RangeOrBitVector StringStorage::Search(FilterOp op,
-                                       SqlValue value,
-                                       RowMap::Range range) const {
-  PERFETTO_TP_TRACE(metatrace::Category::DB, "StringStorage::LinearSearch",
-                    [&range, op](metatrace::Record* r) {
-                      r->AddArg("Start", std::to_string(range.start));
-                      r->AddArg("End", std::to_string(range.end));
+                                       SqlValue sql_val,
+                                       Range search_range) const {
+  PERFETTO_TP_TRACE(metatrace::Category::DB, "StringStorage::Search",
+                    [&search_range, op](metatrace::Record* r) {
+                      r->AddArg("Start", std::to_string(search_range.start));
+                      r->AddArg("End", std::to_string(search_range.end));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, search_range.end));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
   if (is_sorted_) {
     if (op != FilterOp::kNe) {
-      return RangeOrBitVector(BinarySearchIntrinsic(op, value, range));
+      return RangeOrBitVector(BinarySearchIntrinsic(op, sql_val, search_range));
     }
     // Not equal is a special operation on binary search, as it doesn't define
     // a range, and rather just `not` range returned with `equal` operation.
-    RowMap::Range r = BinarySearchIntrinsic(FilterOp::kEq, value, range);
+    Range r = BinarySearchIntrinsic(FilterOp::kEq, sql_val, search_range);
     BitVector bv(r.start, true);
-    bv.Resize(r.end, false);
-    bv.Resize(range.end, true);
+    bv.Resize(r.end);
+    bv.Resize(search_range.end, true);
     return RangeOrBitVector(std::move(bv));
   }
-  return RangeOrBitVector(LinearSearchInternal(op, value, range));
+  return RangeOrBitVector(LinearSearch(op, sql_val, search_range));
 }
 
 RangeOrBitVector StringStorage::IndexSearch(FilterOp op,
-                                            SqlValue value,
+                                            SqlValue sql_val,
                                             uint32_t* indices,
-                                            uint32_t indices_count,
-                                            bool sorted) const {
+                                            uint32_t indices_size,
+                                            bool indices_sorted) const {
   PERFETTO_TP_TRACE(metatrace::Category::DB, "StringStorage::IndexSearch",
-                    [indices_count, op](metatrace::Record* r) {
-                      r->AddArg("Count", std::to_string(indices_count));
+                    [indices_size, op](metatrace::Record* r) {
+                      r->AddArg("Count", std::to_string(indices_size));
                       r->AddArg("Op",
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  if (sorted) {
+  // After this switch we assume the search is valid.
+  switch (ValidateSearchConstraints(sql_val, op)) {
+    case SearchValidationResult::kOk:
+      break;
+    case SearchValidationResult::kAllData:
+      return RangeOrBitVector(Range(0, indices_size));
+    case SearchValidationResult::kNoData:
+      return RangeOrBitVector(Range());
+  }
+
+  if (indices_sorted) {
     return RangeOrBitVector(
-        BinarySearchExtrinsic(op, value, indices, indices_count));
+        BinarySearchExtrinsic(op, sql_val, indices, indices_size));
   }
   return RangeOrBitVector(
-      IndexSearchInternal(op, value, indices, indices_count, sorted));
+      IndexSearchInternal(op, sql_val, indices, indices_size));
 }
 
-BitVector StringStorage::LinearSearchInternal(FilterOp op,
-                                              SqlValue sql_val,
-                                              RowMap::Range range) const {
+BitVector StringStorage::LinearSearch(FilterOp op,
+                                      SqlValue sql_val,
+                                      RowMap::Range range) const {
   if (sql_val.is_null() &&
       (op != FilterOp::kIsNotNull && op != FilterOp::kIsNull)) {
     return BitVector(range.end, false);
@@ -227,16 +269,6 @@
           ? StringPool::Id::Null()
           : string_pool_->InternString(base::StringView(sql_val.AsString()));
   const StringPool::Id* start = values_->data() + range.start;
-  PERFETTO_TP_TRACE(
-      metatrace::Category::DB, "StringStorage::Search",
-      [range, op, &sql_val](metatrace::Record* r) {
-        r->AddArg("Start", std::to_string(range.start));
-        r->AddArg("End", std::to_string(range.end));
-        r->AddArg("Op", std::to_string(static_cast<uint32_t>(op)));
-        r->AddArg("String", sql_val.type == SqlValue::Type::kString
-                                ? sql_val.AsString()
-                                : "NULL");
-      });
 
   BitVector::Builder builder(range.end, range.start);
   switch (op) {
@@ -318,11 +350,11 @@
   return std::move(builder).Build();
 }
 
-RangeOrBitVector StringStorage::IndexSearchInternal(FilterOp op,
-                                                    SqlValue sql_val,
-                                                    uint32_t* indices,
-                                                    uint32_t indices_size,
-                                                    bool) const {
+RangeOrBitVector StringStorage::IndexSearchInternal(
+    FilterOp op,
+    SqlValue sql_val,
+    uint32_t* indices,
+    uint32_t indices_size) const {
   if (sql_val.is_null() &&
       (op != FilterOp::kIsNotNull && op != FilterOp::kIsNull)) {
     return RangeOrBitVector(Range());
diff --git a/src/trace_processor/db/storage/string_storage.h b/src/trace_processor/db/storage/string_storage.h
index 9f14917..1e89f24 100644
--- a/src/trace_processor/db/storage/string_storage.h
+++ b/src/trace_processor/db/storage/string_storage.h
@@ -16,6 +16,7 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_STRING_STORAGE_H_
 #define SRC_TRACE_PROCESSOR_DB_STORAGE_STRING_STORAGE_H_
 
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/storage/storage.h"
@@ -38,6 +39,9 @@
                 bool is_sorted = false)
       : values_(data), string_pool_(string_pool), is_sorted_(is_sorted) {}
 
+  SearchValidationResult ValidateSearchConstraints(SqlValue,
+                                                   FilterOp) const override;
+
   RangeOrBitVector Search(FilterOp op,
                           SqlValue value,
                           RowMap::Range range) const override;
@@ -58,13 +62,12 @@
   }
 
  private:
-  BitVector LinearSearchInternal(FilterOp, SqlValue, RowMap::Range) const;
+  BitVector LinearSearch(FilterOp, SqlValue, RowMap::Range) const;
 
   RangeOrBitVector IndexSearchInternal(FilterOp op,
                                        SqlValue sql_val,
                                        uint32_t* indices,
-                                       uint32_t indices_size,
-                                       bool) const;
+                                       uint32_t indices_size) const;
 
   RowMap::Range BinarySearchExtrinsic(FilterOp,
                                       SqlValue,
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index 557512c..225f951 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<FtraceMessageDescriptor, 487> descriptors{{
+std::array<FtraceMessageDescriptor, 488> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -5351,6 +5351,30 @@
             {"cmd", ProtoSchemaType::kUint32},
         },
     },
+    {
+        "sched_switch_with_ctrs",
+        17,
+        {
+            {},
+            {"old_pid", ProtoSchemaType::kInt32},
+            {"new_pid", ProtoSchemaType::kInt32},
+            {"cctr", ProtoSchemaType::kUint32},
+            {"ctr0", ProtoSchemaType::kUint32},
+            {"ctr1", ProtoSchemaType::kUint32},
+            {"ctr2", ProtoSchemaType::kUint32},
+            {"ctr3", ProtoSchemaType::kUint32},
+            {"lctr0", ProtoSchemaType::kUint32},
+            {"lctr1", ProtoSchemaType::kUint32},
+            {"ctr4", ProtoSchemaType::kUint32},
+            {"ctr5", ProtoSchemaType::kUint32},
+            {"prev_comm", ProtoSchemaType::kString},
+            {"prev_pid", ProtoSchemaType::kInt32},
+            {"cyc", ProtoSchemaType::kUint32},
+            {"inst", ProtoSchemaType::kUint32},
+            {"stallbm", ProtoSchemaType::kUint32},
+            {"l3dm", ProtoSchemaType::kUint32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 7ad51cf..62bd4b9 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -7361,6 +7361,64 @@
        kUnsetFtraceId,
        430,
        kUnsetSize},
+      {"sched_switch_with_ctrs",
+       "perf_trace_counters",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "old_pid", 1, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "new_pid", 2, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cctr", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr0", 4, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr1", 5, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr2", 6, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr3", 7, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "lctr0", 8, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "lctr1", 9, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr4", 10, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ctr5", 11, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "prev_comm", 12, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "prev_pid", 13, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cyc", 14, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "inst", 15, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "stallbm", 16, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "l3dm", 17, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       487,
+       kUnsetSize},
       {"cpu_frequency",
        "power",
        {
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/perf_trace_counters/sched_switch_with_ctrs/format b/src/traced/probes/ftrace/test/data/synthetic/events/perf_trace_counters/sched_switch_with_ctrs/format
new file mode 100644
index 0000000..e357f11
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/perf_trace_counters/sched_switch_with_ctrs/format
@@ -0,0 +1,16 @@
+name: sched_switch_with_ctrs
+ID: 1237
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:char prev_comm[16];	offset:8;	size:16;	signed:0;
+	field:pid_t prev_pid;	offset:24;	size:4;	signed:1;
+	field:u32 cyc;	offset:28;	size:4;	signed:0;
+	field:u32 inst;	offset:32;	size:4;	signed:0;
+	field:u32 stallbm;	offset:36;	size:4;	signed:0;
+	field:u32 l3dm;	offset:40;	size:4;	signed:0;
+
+print fmt: "prev_comm=%s, prev_pid=%d, CYC=%u, INST=%u, STALLBM=%u, L3DM=%u", REC->prev_comm, REC->prev_pid, REC->cyc, REC->inst, REC->stallbm, REC->l3dm
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 0995ff2..2dbe0f2 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -446,8 +446,10 @@
       if (slice.flags & SLICE_FLAGS_INSTANT) {
         this.drawChevron(ctx, slice.x, y, sliceHeight);
       } else if (slice.flags & SLICE_FLAGS_INCOMPLETE) {
-        const w = CROP_INCOMPLETE_SLICE_FLAG.get() ? slice.w : Math.max(slice.w - 2, 2);
-        drawIncompleteSlice(ctx, slice.x, y, w, sliceHeight, !CROP_INCOMPLETE_SLICE_FLAG.get());
+        const w = CROP_INCOMPLETE_SLICE_FLAG.get() ? slice.w :
+                                                     Math.max(slice.w - 2, 2);
+        drawIncompleteSlice(
+            ctx, slice.x, y, w, sliceHeight, !CROP_INCOMPLETE_SLICE_FLAG.get());
       } else {
         const w = Math.max(slice.w, SLICE_MIN_WIDTH_PX);
         ctx.fillRect(slice.x, y, w, sliceHeight);
@@ -815,12 +817,15 @@
     }
 
     for (const slice of this.incomplete) {
+      const visibleTimeScale = globals.frontendLocalState.visibleTimeScale;
       const startPx = CROP_INCOMPLETE_SLICE_FLAG.get() ?
-        globals.frontendLocalState.visibleTimeScale.timeToPx(slice.startNsQ) : slice.x;
+          visibleTimeScale.timeToPx(slice.startNsQ) :
+          slice.x;
       const cropUnfinishedSlicesCondition = CROP_INCOMPLETE_SLICE_FLAG.get() ?
         startPx + INCOMPLETE_SLICE_WIDTH_PX >= x : true;
 
-      if (slice.depth === depth && startPx <= x && cropUnfinishedSlicesCondition) {
+      if (slice.depth === depth && startPx <= x &&
+          cropUnfinishedSlicesCondition) {
         return slice;
       }
     }
diff --git a/ui/src/tracks/chrome_critical_user_interactions/index.ts b/ui/src/tracks/chrome_critical_user_interactions/index.ts
index 73e7428..2e0a381 100644
--- a/ui/src/tracks/chrome_critical_user_interactions/index.ts
+++ b/ui/src/tracks/chrome_critical_user_interactions/index.ts
@@ -23,7 +23,6 @@
   NAMED_ROW,
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
 import {
   Plugin,
   PluginContext,
@@ -80,10 +79,6 @@
     CustomSqlTableSliceTrack<CriticalUserInteractionSliceTrackTypes> {
   static readonly kind = CRITICAL_USER_INTERACTIONS_KIND;
 
-  static create(args: NewTrackArgs): TrackBase {
-    return new CriticalUserInteractionTrack(args);
-  }
-
   getSqlDataSource(): CustomSqlTableDefConfig {
     return {
       columns: ['scoped_id AS id', 'name', 'ts', 'dur', 'type'],
diff --git a/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts b/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
index 95d1924..5b87694 100644
--- a/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track.ts
@@ -17,7 +17,7 @@
   NamedSliceTrack,
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {NewTrackArgs} from '../../frontend/track';
 import {Engine} from '../../trace_processor/engine';
 import {NUM} from '../../trace_processor/query_result';
 
@@ -35,9 +35,6 @@
 export class ChromeTasksScrollJankTrack extends
     NamedSliceTrack<ChromeTasksScrollJankTrackTypes> {
   static readonly kind = 'org.chromium.ScrollJank.BrowserUIThreadLongTasks';
-  static create(args: NewTrackArgs): TrackBase {
-    return new ChromeTasksScrollJankTrack(args);
-  }
 
   constructor(args: NewTrackArgs) {
     super(args);
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
index a5bf0aa..14d296a 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -14,7 +14,7 @@
 
 import {globals} from '../../frontend/globals';
 import {NamedRow, NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {NewTrackArgs} from '../../frontend/track';
 import {PrimaryTrackSortKey, Slice} from '../../public';
 import {
   CustomSqlDetailsPanelConfig,
@@ -38,10 +38,6 @@
     CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
   static readonly kind = 'org.chromium.ScrollJank.scroll_jank_v3_track';
 
-  static create(args: NewTrackArgs): TrackBase {
-    return new ScrollJankV3Track(args);
-  }
-
   constructor(args: NewTrackArgs) {
     super(args);
     ScrollJankPluginState.getInstance().registerTrack({
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
index 7eaba65..a888844 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
@@ -13,13 +13,14 @@
 // limitations under the License.
 
 import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
+import {NewTrackArgs} from '../../frontend/track';
 import {PrimaryTrackSortKey} from '../../public';
 import {
   CustomSqlDetailsPanelConfig,
   CustomSqlTableDefConfig,
   CustomSqlTableSliceTrack,
 } from '../custom_sql_table_slices';
+
 import {
   SCROLL_JANK_GROUP_ID,
   ScrollJankPluginState,
@@ -33,9 +34,6 @@
 export class TopLevelScrollTrack extends
     CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
   public static kind = CHROME_TOPLEVEL_SCROLLS_KIND;
-  static create(args: NewTrackArgs): TrackBase {
-    return new TopLevelScrollTrack(args);
-  }
 
   getSqlDataSource(): CustomSqlTableDefConfig {
     return {
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index c42b480..f07f263 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -274,10 +274,6 @@
 const RECT_HEIGHT = 20;
 
 class CpuFreqTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): CpuFreqTrack {
-    return new CpuFreqTrack(args);
-  }
-
   private mousePos = {x: 0, y: 0};
   private hoveredValue: number|undefined = undefined;
   private hoveredTs: time|undefined = undefined;
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index 143e057..59ad373 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -90,10 +90,6 @@
 }
 
 class CpuProfileTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): CpuProfileTrack {
-    return new CpuProfileTrack(args);
-  }
-
   private centerY = this.getHeight() / 2 + BAR_HEIGHT;
   private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
   private hoveredTs: time|undefined = undefined;
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 28497a2..0e9c470 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -206,10 +206,6 @@
 const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
 
 class CpuSliceTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): CpuSliceTrack {
-    return new CpuSliceTrack(args);
-  }
-
   private mousePos?: {x: number, y: number};
   private utidHoveredInThisTrack = -1;
 
diff --git a/ui/src/tracks/null_track/index.ts b/ui/src/tracks/null_track/index.ts
index 1b2bb24..7bc77b5 100644
--- a/ui/src/tracks/null_track/index.ts
+++ b/ui/src/tracks/null_track/index.ts
@@ -28,10 +28,6 @@
     super(args);
   }
 
-  static create(args: NewTrackArgs): NullTrack {
-    return new NullTrack(args);
-  }
-
   getHeight(): number {
     return 30;
   }
@@ -50,7 +46,7 @@
       uri: NULL_TRACK_URI,
       displayName: 'Null Track',
       kind: NULL_TRACK_KIND,
-      track: ({trackKey}) => NullTrack.create({
+      track: ({trackKey}) => new NullTrack({
         engine: ctx.engine,
         trackKey,
       }),
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index 5da1f71..dd30045 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -87,10 +87,6 @@
 const RECT_HEIGHT = 30.5;
 
 class PerfSamplesProfileTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): PerfSamplesProfileTrack {
-    return new PerfSamplesProfileTrack(args);
-  }
-
   private centerY = this.getHeight() / 2;
   private markerWidth = (this.getHeight() - MARGIN_TOP) / 2;
   private hoveredTs: time|undefined = undefined;
diff --git a/ui/src/tracks/process_summary/process_scheduling_track.ts b/ui/src/tracks/process_summary/process_scheduling_track.ts
index f7ad4c1..a955278 100644
--- a/ui/src/tracks/process_summary/process_scheduling_track.ts
+++ b/ui/src/tracks/process_summary/process_scheduling_track.ts
@@ -189,10 +189,6 @@
 }
 
 export class ProcessSchedulingTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): ProcessSchedulingTrack {
-    return new ProcessSchedulingTrack(args);
-  }
-
   private mousePos?: {x: number, y: number};
   private utidHoveredInThisTrack = -1;
 
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
index a1b32c7..ee0bcc8 100644
--- a/ui/src/tracks/process_summary/process_summary_track.ts
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -142,10 +142,6 @@
 const SUMMARY_HEIGHT = TRACK_HEIGHT - MARGIN_TOP;
 
 export class ProcessSummaryTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): ProcessSummaryTrack {
-    return new ProcessSummaryTrack(args);
-  }
-
   constructor(args: NewTrackArgs) {
     super(args);
   }
diff --git a/ui/src/tracks/screenshots/index.ts b/ui/src/tracks/screenshots/index.ts
index 32df756..4af3c94 100644
--- a/ui/src/tracks/screenshots/index.ts
+++ b/ui/src/tracks/screenshots/index.ts
@@ -16,7 +16,6 @@
 import {
   NamedSliceTrackTypes,
 } from '../../frontend/named_slice_track';
-import {NewTrackArgs, TrackBase} from '../../frontend/track';
 import {
   Plugin,
   PluginContext,
@@ -36,9 +35,6 @@
 
 class ScreenshotsTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
   static readonly kind = 'dev.perfetto.ScreenshotsTrack';
-  static create(args: NewTrackArgs): TrackBase {
-    return new ScreenshotsTrack(args);
-  }
 
   getSqlDataSource(): CustomSqlTableDefConfig {
     return {
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index 5882283..7544ddb 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -179,10 +179,6 @@
 const EXCESS_WIDTH = 10;
 
 class ThreadStateTrack extends TrackAdapter<Config, Data> {
-  static create(args: NewTrackArgs): ThreadStateTrack {
-    return new ThreadStateTrack(args);
-  }
-
   constructor(args: NewTrackArgs) {
     super(args);
   }