Merge "[ui] Pass canvas size into Track.render() functions." into main
diff --git a/Android.bp b/Android.bp
index ef5737b..cebcd7b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10983,6 +10983,7 @@
         "src/trace_processor/db/storage/set_id_storage.cc",
         "src/trace_processor/db/storage/storage.cc",
         "src/trace_processor/db/storage/string_storage.cc",
+        "src/trace_processor/db/storage/utils.cc",
     ],
 }
 
diff --git a/BUILD b/BUILD
index 5c8fbe6..f3d4584 100644
--- a/BUILD
+++ b/BUILD
@@ -1321,6 +1321,7 @@
         "src/trace_processor/db/storage/string_storage.cc",
         "src/trace_processor/db/storage/string_storage.h",
         "src/trace_processor/db/storage/types.h",
+        "src/trace_processor/db/storage/utils.cc",
         "src/trace_processor/db/storage/utils.h",
     ],
 )
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index d0ef7ed..cb9a2cf 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -178,9 +178,11 @@
         col.type() == SqlValue::kLong && c.value.type == SqlValue::kDouble;
     bool double_with_int =
         col.type() == SqlValue::kDouble && c.value.type == SqlValue::kLong;
-    use_legacy = use_legacy ||
-                 (c.op != FilterOp::kIsNull && c.op != FilterOp::kIsNotNull &&
-                  (int_with_double || double_with_int));
+    bool double_int_enabled_col_type = col.IsId() || col.IsSetId();
+    use_legacy =
+        use_legacy ||
+        (!double_int_enabled_col_type && c.op != FilterOp::kIsNull &&
+         c.op != FilterOp::kIsNotNull && (int_with_double || double_with_int));
 
     // Extrinsically sorted columns.
     use_legacy = use_legacy ||
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index eb23903..38feabd 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -403,6 +403,24 @@
 }
 BENCHMARK(BM_QEDenseNullFilterIsNull)->ArgsProduct({{DB::V1, DB::V2}});
 
+static void BM_QEIdColumnWithIntAsDouble(benchmark::State& state) {
+  SliceTableForBenchmark table(state);
+  Constraint c{table.table_.track_id().index_in_table(), FilterOp::kEq,
+               SqlValue::Double(100)};
+  BenchmarkSliceTable(state, table, {c});
+}
+
+BENCHMARK(BM_QEIdColumnWithIntAsDouble)->ArgsProduct({{DB::V1, DB::V2}});
+
+static void BM_QEIdColumnWithDouble(benchmark::State& state) {
+  SliceTableForBenchmark table(state);
+  Constraint c{table.table_.track_id().index_in_table(), FilterOp::kEq,
+               SqlValue::Double(100.5)};
+  BenchmarkSliceTable(state, table, {c});
+}
+
+BENCHMARK(BM_QEIdColumnWithDouble)->ArgsProduct({{DB::V1, DB::V2}});
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
index 1f73314..7f2dfcc 100644
--- a/src/trace_processor/db/query_executor_unittest.cc
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -560,6 +560,29 @@
   ASSERT_EQ(res.size(), 0u);
 }
 
+TEST(QueryExecutor, MismatchedTypeIdWithDouble) {
+  IdStorage storage(5);
+
+  // Filter.
+  Constraint c{0, FilterOp::kGe, SqlValue::Double(1.5)};
+  QueryExecutor exec({&storage}, 5);
+  RowMap res = exec.Filter({c});
+
+  ASSERT_EQ(res.size(), 3u);
+}
+
+TEST(QueryExecutor, MismatchedTypeSetIdWithDouble) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+
+  // Filter.
+  Constraint c{0, FilterOp::kGe, SqlValue::Double(1.5)};
+  QueryExecutor exec({&storage}, storage.size());
+  RowMap res = exec.Filter({c});
+
+  ASSERT_EQ(res.size(), 9u);
+}
+
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 TEST(QueryExecutor, StringBinarySearchRegex) {
   StringPool pool;
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
index 72d8d67..d13b530 100644
--- a/src/trace_processor/db/storage/BUILD.gn
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -37,6 +37,7 @@
     "string_storage.cc",
     "string_storage.h",
     "types.h",
+    "utils.cc",
     "utils.h",
   ]
   deps = [
diff --git a/src/trace_processor/db/storage/id_storage.cc b/src/trace_processor/db/storage/id_storage.cc
index 68cfb2f..0b9adee 100644
--- a/src/trace_processor/db/storage/id_storage.cc
+++ b/src/trace_processor/db/storage/id_storage.cc
@@ -15,7 +15,6 @@
  */
 
 #include "src/trace_processor/db/storage/id_storage.h"
-
 #include <optional>
 
 #include "perfetto/base/logging.h"
@@ -124,17 +123,18 @@
       return 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())) {
+  double_t num_val = val.type == SqlValue::kLong
+                         ? static_cast<double_t>(val.AsLong())
+                         : val.AsDouble();
+
+  if (PERFETTO_UNLIKELY(num_val > 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 (PERFETTO_UNLIKELY(num_val < std::numeric_limits<uint32_t>::min())) {
     if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
@@ -157,7 +157,21 @@
 
   PERFETTO_DCHECK(search_range.end <= size_);
 
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, search_range.end));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
+  }
+
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
+
   if (op == FilterOp::kNe) {
     BitVector ret(search_range.start, false);
     ret.Resize(search_range.end, true);
@@ -180,6 +194,19 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, indices_size));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
+  }
+
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
   switch (op) {
diff --git a/src/trace_processor/db/storage/id_storage_unittest.cc b/src/trace_processor/db/storage/id_storage_unittest.cc
index 91cc37b..ab04d86 100644
--- a/src/trace_processor/db/storage/id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/id_storage_unittest.cc
@@ -28,9 +28,28 @@
   return std::tie(a.start, a.end) == std::tie(b.start, b.end);
 }
 
+inline bool operator==(const BitVector& a, const BitVector& b) {
+  return a.size() == b.size() && a.CountSetBits() == b.CountSetBits();
+}
+
 namespace storage {
 namespace {
 
+using testing::ElementsAre;
+using testing::IsEmpty;
+using Range = RowMap::Range;
+
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector& r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
 using Range = RowMap::Range;
 
 TEST(IdStorageUnittest, InvalidSearchConstraints) {
@@ -55,6 +74,11 @@
                                               FilterOp::kGe),
             SearchValidationResult::kNoData);
 
+  // With double
+  ASSERT_EQ(
+      storage.ValidateSearchConstraints(SqlValue::Double(-1), FilterOp::kGe),
+      SearchValidationResult::kAllData);
+
   // Value bounds
   SqlValue max_val = SqlValue::Long(
       static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 10);
@@ -72,8 +96,7 @@
   ASSERT_EQ(storage.ValidateSearchConstraints(max_val, FilterOp::kNe),
             SearchValidationResult::kAllData);
 
-  SqlValue min_val = SqlValue::Long(
-      static_cast<int64_t>(std::numeric_limits<uint32_t>::min()) - 1);
+  SqlValue min_val = SqlValue::Long(-1);
   ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGe),
             SearchValidationResult::kAllData);
   ASSERT_EQ(storage.ValidateSearchConstraints(min_val, FilterOp::kGt),
@@ -259,6 +282,61 @@
   ASSERT_TRUE(bv.IsSet(5));
 }
 
+TEST(IdStorageUnittest, SearchWithIdAsDoubleSimple) {
+  IdStorage storage(100);
+  SqlValue double_val = SqlValue::Double(15.0);
+  SqlValue long_val = SqlValue::Long(15);
+  Range range(10, 20);
+
+  auto res_double = storage.Search(FilterOp::kEq, double_val, range);
+  auto res_long = storage.Search(FilterOp::kEq, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kNe, double_val, range);
+  res_long = storage.Search(FilterOp::kNe, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kLe, double_val, range);
+  res_long = storage.Search(FilterOp::kLe, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kLt, double_val, range);
+  res_long = storage.Search(FilterOp::kLt, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kGe, double_val, range);
+  res_long = storage.Search(FilterOp::kGe, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+
+  res_double = storage.Search(FilterOp::kGt, double_val, range);
+  res_long = storage.Search(FilterOp::kGt, long_val, range);
+  ASSERT_EQ(ToIndexVector(res_double), ToIndexVector(res_long));
+}
+
+TEST(IdStorageUnittest, SearchWithIdAsDouble) {
+  IdStorage storage(100);
+  Range range(10, 20);
+  SqlValue val = SqlValue::Double(15.5);
+
+  auto res = storage.Search(FilterOp::kEq, val, range);
+  ASSERT_THAT(ToIndexVector(res), IsEmpty());
+
+  res = storage.Search(FilterOp::kNe, val, range);
+  ASSERT_EQ(ToIndexVector(res).size(), 20u);
+
+  res = storage.Search(FilterOp::kLe, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(10, 11, 12, 13, 14, 15));
+
+  res = storage.Search(FilterOp::kLt, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(10, 11, 12, 13, 14, 15));
+
+  res = storage.Search(FilterOp::kGe, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(16, 17, 18, 19));
+
+  res = storage.Search(FilterOp::kGt, val, range);
+  ASSERT_THAT(ToIndexVector(res), ElementsAre(16, 17, 18, 19));
+}
+
 TEST(IdStorageUnittest, Sort) {
   std::vector<uint32_t> order{4, 3, 6, 1, 5};
   IdStorage storage(10);
diff --git a/src/trace_processor/db/storage/set_id_storage.cc b/src/trace_processor/db/storage/set_id_storage.cc
index 5efc45f..dd0af25 100644
--- a/src/trace_processor/db/storage/set_id_storage.cc
+++ b/src/trace_processor/db/storage/set_id_storage.cc
@@ -108,17 +108,18 @@
       return 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())) {
+  double_t num_val = val.type == SqlValue::kLong
+                         ? static_cast<double_t>(val.AsLong())
+                         : val.AsDouble();
+
+  if (PERFETTO_UNLIKELY(num_val > 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 (PERFETTO_UNLIKELY(num_val < std::numeric_limits<uint32_t>::min())) {
     if (op == FilterOp::kGe || op == FilterOp::kGt || op == FilterOp::kNe) {
       return SearchValidationResult::kAllData;
     }
@@ -131,6 +132,8 @@
 RangeOrBitVector SetIdStorage::Search(FilterOp op,
                                       SqlValue sql_val,
                                       RowMap::Range search_range) const {
+  PERFETTO_DCHECK(search_range.end <= size());
+
   PERFETTO_TP_TRACE(metatrace::Category::DB, "SetIdStorage::Search",
                     [&search_range, op](metatrace::Record* r) {
                       r->AddArg("Start", std::to_string(search_range.start));
@@ -139,7 +142,18 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
-  PERFETTO_DCHECK(search_range.end <= size());
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, search_range.end));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
+  }
 
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
@@ -169,6 +183,19 @@
                                 std::to_string(static_cast<uint32_t>(op)));
                     });
 
+  // It's a valid filter operation if |sql_val| is a double, although it
+  // requires special logic.
+  if (sql_val.type == SqlValue::kDouble) {
+    switch (utils::CompareIntColumnWithDouble(&sql_val, op)) {
+      case SearchValidationResult::kOk:
+        break;
+      case SearchValidationResult::kAllData:
+        return RangeOrBitVector(Range(0, indices_size));
+      case SearchValidationResult::kNoData:
+        return RangeOrBitVector(Range());
+    }
+  }
+
   uint32_t val = static_cast<uint32_t>(sql_val.AsLong());
 
   BitVector::Builder builder(indices_size);
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 562db61..b7cbfc7 100644
--- a/src/trace_processor/db/storage/set_id_storage_unittest.cc
+++ b/src/trace_processor/db/storage/set_id_storage_unittest.cc
@@ -24,11 +24,26 @@
   return std::tie(a.start, a.end) == std::tie(b.start, b.end);
 }
 
+inline bool operator==(const BitVector& a, const BitVector& b) {
+  return a.size() == b.size() && a.CountSetBits() == b.CountSetBits();
+}
+
 namespace storage {
 namespace {
 
 using Range = RowMap::Range;
 
+std::vector<uint32_t> ToIndexVector(RangeOrBitVector r_or_bv) {
+  RowMap rm;
+  if (r_or_bv.IsBitVector()) {
+    rm = RowMap(std::move(r_or_bv).TakeIfBitVector());
+  } else {
+    Range range = std::move(r_or_bv).TakeIfRange();
+    rm = RowMap(range.start, range.end);
+  }
+  return rm.GetAllIndices();
+}
+
 TEST(SetIdStorageUnittest, InvalidSearchConstraints) {
   std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
   SetIdStorage storage(&storage_data);
@@ -281,6 +296,52 @@
   ASSERT_EQ(bv.CountSetBits(), 2u);
 }
 
+TEST(SetIdStorageUnittest, SearchWithIdAsDoubleSimple) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+  SqlValue double_val = SqlValue::Double(7.0);
+  SqlValue long_val = SqlValue::Long(7);
+  Range range(1, 9);
+
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kEq, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kEq, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kNe, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kNe, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kLe, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kLe, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kLt, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kLt, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kGe, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kGe, long_val, range)));
+  ASSERT_EQ(ToIndexVector(storage.Search(FilterOp::kGt, double_val, range)),
+            ToIndexVector(storage.Search(FilterOp::kGt, long_val, range)));
+}
+
+TEST(SetIdStorageUnittest, SearchWithIdAsDouble) {
+  std::vector<uint32_t> storage_data{0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9};
+  SetIdStorage storage(&storage_data);
+  SqlValue val = SqlValue::Double(7.5);
+  Range range(5, 10);
+
+  Range res = storage.Search(FilterOp::kEq, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range());
+
+  res = storage.Search(FilterOp::kNe, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(0, 10));
+
+  res = storage.Search(FilterOp::kLe, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(5, 9));
+
+  res = storage.Search(FilterOp::kLt, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(5, 9));
+
+  res = storage.Search(FilterOp::kGe, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(9, 10));
+
+  res = storage.Search(FilterOp::kGt, val, range).TakeIfRange();
+  ASSERT_EQ(res, Range(9, 10));
+}
+
 }  // namespace
 }  // namespace storage
 }  // namespace trace_processor
diff --git a/src/trace_processor/db/storage/utils.cc b/src/trace_processor/db/storage/utils.cc
new file mode 100644
index 0000000..ba12e40
--- /dev/null
+++ b/src/trace_processor/db/storage/utils.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/db/storage/utils.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace utils {
+
+SearchValidationResult CompareIntColumnWithDouble(SqlValue* sql_val,
+                                                  FilterOp op) {
+  double double_val = sql_val->AsDouble();
+  if (std::equal_to<double>()(
+          double_val, static_cast<double>(static_cast<uint32_t>(double_val)))) {
+    // If double is the same as uint32_t, we should just "cast" the |sql_val|
+    // to be treated as long.
+    *sql_val = SqlValue::Long(static_cast<int64_t>(double_val));
+    return SearchValidationResult::kOk;
+  }
+  // Logic for when the value is a real double.
+  switch (op) {
+    case FilterOp::kEq:
+      return SearchValidationResult::kNoData;
+    case FilterOp::kNe:
+      return SearchValidationResult::kAllData;
+
+    case FilterOp::kLe:
+    case FilterOp::kGt:
+      *sql_val = SqlValue::Long(static_cast<int64_t>(std::floor(double_val)));
+      return SearchValidationResult::kOk;
+
+    case FilterOp::kLt:
+    case FilterOp::kGe:
+      *sql_val = SqlValue::Long(static_cast<int64_t>(std::ceil(double_val)));
+      return SearchValidationResult::kOk;
+
+    case FilterOp::kIsNotNull:
+    case FilterOp::kIsNull:
+    case FilterOp::kGlob:
+    case FilterOp::kRegex:
+      PERFETTO_FATAL("Invalid filter operation");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+}  // namespace utils
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto