| /* | 
 |  * Copyright (C) 2019 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/sqlite/db_sqlite_table.h" | 
 |  | 
 | #include <sqlite3.h> | 
 | #include <algorithm> | 
 | #include <cmath> | 
 | #include <cstddef> | 
 | #include <cstdint> | 
 | #include <functional> | 
 | #include <iterator> | 
 | #include <limits> | 
 | #include <memory> | 
 | #include <optional> | 
 | #include <string> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "perfetto/base/logging.h" | 
 | #include "perfetto/base/status.h" | 
 | #include "perfetto/ext/base/small_vector.h" | 
 | #include "perfetto/ext/base/status_or.h" | 
 | #include "perfetto/ext/base/string_view.h" | 
 | #include "perfetto/trace_processor/basic_types.h" | 
 | #include "src/trace_processor/containers/row_map.h" | 
 | #include "src/trace_processor/db/column/types.h" | 
 | #include "src/trace_processor/db/runtime_table.h" | 
 | #include "src/trace_processor/db/table.h" | 
 | #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h" | 
 | #include "src/trace_processor/sqlite/query_cache.h" | 
 | #include "src/trace_processor/sqlite/query_constraints.h" | 
 | #include "src/trace_processor/sqlite/sqlite_table.h" | 
 | #include "src/trace_processor/sqlite/sqlite_utils.h" | 
 | #include "src/trace_processor/tp_metatrace.h" | 
 | #include "src/trace_processor/util/regex.h" | 
 | #include "src/trace_processor/util/status_macros.h" | 
 |  | 
 | #include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h" | 
 |  | 
 | namespace perfetto::trace_processor { | 
 | namespace { | 
 |  | 
 | static constexpr uint32_t kInvalidArgumentIndex = | 
 |     std::numeric_limits<uint32_t>::max(); | 
 |  | 
 | std::optional<FilterOp> SqliteOpToFilterOp(int sqlite_op) { | 
 |   switch (sqlite_op) { | 
 |     case SQLITE_INDEX_CONSTRAINT_EQ: | 
 |       return FilterOp::kEq; | 
 |     case SQLITE_INDEX_CONSTRAINT_GT: | 
 |       return FilterOp::kGt; | 
 |     case SQLITE_INDEX_CONSTRAINT_LT: | 
 |       return FilterOp::kLt; | 
 |     case SQLITE_INDEX_CONSTRAINT_NE: | 
 |       return FilterOp::kNe; | 
 |     case SQLITE_INDEX_CONSTRAINT_GE: | 
 |       return FilterOp::kGe; | 
 |     case SQLITE_INDEX_CONSTRAINT_LE: | 
 |       return FilterOp::kLe; | 
 |     case SQLITE_INDEX_CONSTRAINT_ISNULL: | 
 |       return FilterOp::kIsNull; | 
 |     case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: | 
 |       return FilterOp::kIsNotNull; | 
 |     case SQLITE_INDEX_CONSTRAINT_GLOB: | 
 |       return FilterOp::kGlob; | 
 |     case SQLITE_INDEX_CONSTRAINT_REGEXP: | 
 |       if constexpr (regex::IsRegexSupported()) { | 
 |         return FilterOp::kRegex; | 
 |       } | 
 |       return std::nullopt; | 
 |     case SQLITE_INDEX_CONSTRAINT_LIKE: | 
 |     // TODO(lalitm): start supporting these constraints. | 
 |     case SQLITE_INDEX_CONSTRAINT_LIMIT: | 
 |     case SQLITE_INDEX_CONSTRAINT_OFFSET: | 
 |     case SQLITE_INDEX_CONSTRAINT_IS: | 
 |     case SQLITE_INDEX_CONSTRAINT_ISNOT: | 
 |       return std::nullopt; | 
 |     default: | 
 |       PERFETTO_FATAL("Currently unsupported constraint"); | 
 |   } | 
 | } | 
 |  | 
 | SqlValue SqliteValueToSqlValue(sqlite3_value* sqlite_val) { | 
 |   auto col_type = sqlite3_value_type(sqlite_val); | 
 |   SqlValue value; | 
 |   switch (col_type) { | 
 |     case SQLITE_INTEGER: | 
 |       value.type = SqlValue::kLong; | 
 |       value.long_value = sqlite3_value_int64(sqlite_val); | 
 |       break; | 
 |     case SQLITE_TEXT: | 
 |       value.type = SqlValue::kString; | 
 |       value.string_value = | 
 |           reinterpret_cast<const char*>(sqlite3_value_text(sqlite_val)); | 
 |       break; | 
 |     case SQLITE_FLOAT: | 
 |       value.type = SqlValue::kDouble; | 
 |       value.double_value = sqlite3_value_double(sqlite_val); | 
 |       break; | 
 |     case SQLITE_BLOB: | 
 |       value.type = SqlValue::kBytes; | 
 |       value.bytes_value = sqlite3_value_blob(sqlite_val); | 
 |       value.bytes_count = static_cast<size_t>(sqlite3_value_bytes(sqlite_val)); | 
 |       break; | 
 |     case SQLITE_NULL: | 
 |       value.type = SqlValue::kNull; | 
 |       break; | 
 |     default: | 
 |       PERFETTO_FATAL("Unexpected sqlite3_value type"); | 
 |   } | 
 |   return value; | 
 | } | 
 |  | 
 | class SafeStringWriter { | 
 |  public: | 
 |   void AppendString(const char* s) { | 
 |     for (const char* c = s; *c; ++c) { | 
 |       buffer_.emplace_back(*c); | 
 |     } | 
 |   } | 
 |  | 
 |   void AppendString(const std::string& s) { | 
 |     for (char c : s) { | 
 |       buffer_.emplace_back(c); | 
 |     } | 
 |   } | 
 |  | 
 |   base::StringView GetStringView() const { | 
 |     return base::StringView(buffer_.data(), buffer_.size()); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::SmallVector<char, 2048> buffer_; | 
 | }; | 
 |  | 
 | base::Status ValidateTableFunctionArguments(const std::string& name, | 
 |                                             const Table::Schema& schema, | 
 |                                             const QueryConstraints& qc) { | 
 |   for (uint32_t i = 0; i < schema.columns.size(); ++i) { | 
 |     if (!schema.columns[i].is_hidden) { | 
 |       continue; | 
 |     } | 
 |     auto pred = [i](const QueryConstraints::Constraint& c) { | 
 |       return i == static_cast<uint32_t>(c.column); | 
 |     }; | 
 |     auto it = | 
 |         std::find_if(qc.constraints().begin(), qc.constraints().end(), pred); | 
 |     if (it == qc.constraints().end()) { | 
 |       return base::ErrStatus( | 
 |           "Failed to find constraint on column '%u' in function %s", i, | 
 |           name.c_str()); | 
 |     } | 
 |  | 
 |     // Arguments should always use equality constraints. | 
 |     if (it->op != SQLITE_INDEX_CONSTRAINT_EQ) { | 
 |       return base::ErrStatus( | 
 |           "Only equality constraints supported on column '%u'", i); | 
 |     } | 
 |  | 
 |     // Disallow multiple constraints on an argument column. | 
 |     auto count = std::count_if(it + 1, qc.constraints().end(), pred); | 
 |     if (count > 0) { | 
 |       return base::ErrStatus( | 
 |           "Found multiple constraints on column '%u' in function %s", i, | 
 |           name.c_str()); | 
 |     } | 
 |   } | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | DbSqliteTable::DbSqliteTable(sqlite3*, Context* context) : context_(context) {} | 
 | DbSqliteTable::~DbSqliteTable() { | 
 |   if (context_->computation == DbSqliteTableContext::Computation::kRuntime) { | 
 |     context_->erase_runtime_table(name()); | 
 |   } | 
 | } | 
 |  | 
 | base::Status DbSqliteTable::Init(int, const char* const*, Schema* schema) { | 
 |   switch (context_->computation) { | 
 |     case TableComputation::kStatic: | 
 |       schema_ = context_->static_schema; | 
 |       break; | 
 |     case TableComputation::kRuntime: | 
 |       runtime_table_ = context_->get_runtime_table(name()); | 
 |       PERFETTO_CHECK(runtime_table_); | 
 |       PERFETTO_CHECK(!runtime_table_->columns().empty()); | 
 |       schema_ = runtime_table_->schema(); | 
 |       break; | 
 |     case TableComputation::kTableFunction: | 
 |       schema_ = context_->static_table_function->CreateSchema(); | 
 |       break; | 
 |   } | 
 |   *schema = ComputeSchema(schema_, name().c_str()); | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | SqliteTable::Schema DbSqliteTable::ComputeSchema(const Table::Schema& schema, | 
 |                                                  const char* table_name) { | 
 |   std::vector<SqliteTable::Column> schema_cols; | 
 |   for (uint32_t i = 0; i < schema.columns.size(); ++i) { | 
 |     const auto& col = schema.columns[i]; | 
 |     schema_cols.emplace_back(i, col.name, col.type, col.is_hidden); | 
 |   } | 
 |  | 
 |   auto it = | 
 |       std::find_if(schema.columns.begin(), schema.columns.end(), | 
 |                    [](const Table::Schema::Column& c) { return c.is_id; }); | 
 |   if (it == schema.columns.end()) { | 
 |     PERFETTO_FATAL( | 
 |         "id column not found in %s. All tables need to contain an id column;", | 
 |         table_name); | 
 |   } | 
 |  | 
 |   std::vector<size_t> primary_keys; | 
 |   primary_keys.emplace_back(std::distance(schema.columns.begin(), it)); | 
 |   return Schema(std::move(schema_cols), std::move(primary_keys)); | 
 | } | 
 |  | 
 | int DbSqliteTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) { | 
 |   switch (context_->computation) { | 
 |     case TableComputation::kStatic: | 
 |       BestIndex(schema_, context_->static_table->row_count(), qc, info); | 
 |       break; | 
 |     case TableComputation::kRuntime: | 
 |       BestIndex(schema_, runtime_table_->row_count(), qc, info); | 
 |       break; | 
 |     case TableComputation::kTableFunction: | 
 |       base::Status status = ValidateTableFunctionArguments(name(), schema_, qc); | 
 |       if (!status.ok()) { | 
 |         // TODO(lalitm): instead of returning SQLITE_CONSTRAINT which shows the | 
 |         // user a very cryptic error message, consider instead SQLITE_OK but | 
 |         // with a very high (~infinite) cost. If SQLite still chose the query | 
 |         // plan after that, we can throw a proper error message in xFilter. | 
 |         return SQLITE_CONSTRAINT; | 
 |       } | 
 |       BestIndex(schema_, context_->static_table_function->EstimateRowCount(), | 
 |                 qc, info); | 
 |       break; | 
 |   } | 
 |   return SQLITE_OK; | 
 | } | 
 |  | 
 | void DbSqliteTable::BestIndex(const Table::Schema& schema, | 
 |                               uint32_t row_count, | 
 |                               const QueryConstraints& qc, | 
 |                               BestIndexInfo* info) { | 
 |   auto cost_and_rows = EstimateCost(schema, row_count, qc); | 
 |   info->estimated_cost = cost_and_rows.cost; | 
 |   info->estimated_rows = cost_and_rows.rows; | 
 |  | 
 |   const auto& cs = qc.constraints(); | 
 |   for (uint32_t i = 0; i < cs.size(); ++i) { | 
 |     // SqliteOpToFilterOp will return std::nullopt for any constraint which we | 
 |     // don't support filtering ourselves. Only omit filtering by SQLite when we | 
 |     // can handle filtering. | 
 |     std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs[i].op); | 
 |     info->sqlite_omit_constraint[i] = opt_op.has_value(); | 
 |   } | 
 |  | 
 |   // We can sort on any column correctly. | 
 |   info->sqlite_omit_order_by = true; | 
 | } | 
 |  | 
 | base::Status DbSqliteTable::ModifyConstraints(QueryConstraints* qc) { | 
 |   ModifyConstraints(schema_, qc); | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | void DbSqliteTable::ModifyConstraints(const Table::Schema& schema, | 
 |                                       QueryConstraints* qc) { | 
 |   using C = QueryConstraints::Constraint; | 
 |  | 
 |   // Reorder constraints to consider the constraints on columns which are | 
 |   // cheaper to filter first. | 
 |   auto* cs = qc->mutable_constraints(); | 
 |   std::sort(cs->begin(), cs->end(), [&schema](const C& a, const C& b) { | 
 |     uint32_t a_idx = static_cast<uint32_t>(a.column); | 
 |     uint32_t b_idx = static_cast<uint32_t>(b.column); | 
 |     const auto& a_col = schema.columns[a_idx]; | 
 |     const auto& b_col = schema.columns[b_idx]; | 
 |  | 
 |     // Id columns are always very cheap to filter on so try and get them | 
 |     // first. | 
 |     if (a_col.is_id || b_col.is_id) | 
 |       return a_col.is_id && !b_col.is_id; | 
 |  | 
 |     // Set id columns are always very cheap to filter on so try and get them | 
 |     // second. | 
 |     if (a_col.is_set_id || b_col.is_set_id) | 
 |       return a_col.is_set_id && !b_col.is_set_id; | 
 |  | 
 |     // Sorted columns are also quite cheap to filter so order them after | 
 |     // any id/set id columns. | 
 |     if (a_col.is_sorted || b_col.is_sorted) | 
 |       return a_col.is_sorted && !b_col.is_sorted; | 
 |  | 
 |     // TODO(lalitm): introduce more orderings here based on empirical data. | 
 |     return false; | 
 |   }); | 
 |  | 
 |   // Remove any order by constraints which also have an equality constraint. | 
 |   auto* ob = qc->mutable_order_by(); | 
 |   { | 
 |     auto p = [&cs](const QueryConstraints::OrderBy& o) { | 
 |       auto inner_p = [&o](const QueryConstraints::Constraint& c) { | 
 |         return c.column == o.iColumn && sqlite_utils::IsOpEq(c.op); | 
 |       }; | 
 |       return std::any_of(cs->begin(), cs->end(), inner_p); | 
 |     }; | 
 |     auto remove_it = std::remove_if(ob->begin(), ob->end(), p); | 
 |     ob->erase(remove_it, ob->end()); | 
 |   } | 
 |  | 
 |   // Go through the order by constraints in reverse order and eliminate | 
 |   // constraints until the first non-sorted column or the first order by in | 
 |   // descending order. | 
 |   { | 
 |     auto p = [&schema](const QueryConstraints::OrderBy& o) { | 
 |       const auto& col = schema.columns[static_cast<uint32_t>(o.iColumn)]; | 
 |       return o.desc || !col.is_sorted; | 
 |     }; | 
 |     auto first_non_sorted_it = std::find_if(ob->rbegin(), ob->rend(), p); | 
 |     auto pop_count = std::distance(ob->rbegin(), first_non_sorted_it); | 
 |     ob->resize(ob->size() - static_cast<uint32_t>(pop_count)); | 
 |   } | 
 | } | 
 |  | 
 | DbSqliteTable::QueryCost DbSqliteTable::EstimateCost( | 
 |     const Table::Schema& schema, | 
 |     uint32_t row_count, | 
 |     const QueryConstraints& qc) { | 
 |   // Currently our cost estimation algorithm is quite simplistic but is good | 
 |   // enough for the simplest cases. | 
 |   // TODO(lalitm): replace hardcoded constants with either more heuristics | 
 |   // based on the exact type of constraint or profiling the queries themselves. | 
 |  | 
 |   // We estimate the fixed cost of set-up and tear-down of a query in terms of | 
 |   // the number of rows scanned. | 
 |   constexpr double kFixedQueryCost = 1000.0; | 
 |  | 
 |   // Setup the variables for estimating the number of rows we will have at the | 
 |   // end of filtering. Note that |current_row_count| should always be at least 1 | 
 |   // unless we are absolutely certain that we will return no rows as otherwise | 
 |   // SQLite can make some bad choices. | 
 |   uint32_t current_row_count = row_count; | 
 |  | 
 |   // If the table is empty, any constraint set only pays the fixed cost. Also we | 
 |   // can return 0 as the row count as we are certain that we will return no | 
 |   // rows. | 
 |   if (current_row_count == 0) | 
 |     return QueryCost{kFixedQueryCost, 0}; | 
 |  | 
 |   // Setup the variables for estimating the cost of filtering. | 
 |   double filter_cost = 0.0; | 
 |   const auto& cs = qc.constraints(); | 
 |   for (const auto& c : cs) { | 
 |     if (current_row_count < 2) | 
 |       break; | 
 |     const auto& col_schema = schema.columns[static_cast<uint32_t>(c.column)]; | 
 |     if (sqlite_utils::IsOpEq(c.op) && col_schema.is_id) { | 
 |       // If we have an id equality constraint, we can very efficiently filter | 
 |       // down to a single row in C++. However, if we're joining with another | 
 |       // table, SQLite will do this once per row which can be extremely | 
 |       // expensive because of all the virtual table (which is implemented using | 
 |       // virtual function calls) machinery. Indicate this by saying that an | 
 |       // entire filter call is ~10x the cost of iterating a single row. | 
 |       filter_cost += 10; | 
 |       current_row_count = 1; | 
 |     } else if (sqlite_utils::IsOpEq(c.op)) { | 
 |       // If there is only a single equality constraint, we have special logic | 
 |       // to sort by that column and then binary search if we see the constraint | 
 |       // set often. Model this by dividing by the log of the number of rows as | 
 |       // a good approximation. Otherwise, we'll need to do a full table scan. | 
 |       // Alternatively, if the column is sorted, we can use the same binary | 
 |       // search logic so we have the same low cost (even better because we don't | 
 |       // have to sort at all). | 
 |       filter_cost += cs.size() == 1 || col_schema.is_sorted | 
 |                          ? log2(current_row_count) | 
 |                          : current_row_count; | 
 |  | 
 |       // As an extremely rough heuristic, assume that an equalty constraint will | 
 |       // cut down the number of rows by approximately double log of the number | 
 |       // of rows. | 
 |       double estimated_rows = current_row_count / (2 * log2(current_row_count)); | 
 |       current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u); | 
 |     } else if (col_schema.is_sorted && | 
 |                (sqlite_utils::IsOpLe(c.op) || sqlite_utils::IsOpLt(c.op) || | 
 |                 sqlite_utils::IsOpGt(c.op) || sqlite_utils::IsOpGe(c.op))) { | 
 |       // On a sorted column, if we see any partition constraints, we can do this | 
 |       // filter very efficiently. Model this using the log of the  number of | 
 |       // rows as a good approximation. | 
 |       filter_cost += log2(current_row_count); | 
 |  | 
 |       // As an extremely rough heuristic, assume that an partition constraint | 
 |       // will cut down the number of rows by approximately double log of the | 
 |       // number of rows. | 
 |       double estimated_rows = current_row_count / (2 * log2(current_row_count)); | 
 |       current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u); | 
 |     } else { | 
 |       // Otherwise, we will need to do a full table scan and we estimate we will | 
 |       // maybe (at best) halve the number of rows. | 
 |       filter_cost += current_row_count; | 
 |       current_row_count = std::max(current_row_count / 2u, 1u); | 
 |     } | 
 |   } | 
 |  | 
 |   // Now, to figure out the cost of sorting, multiply the final row count | 
 |   // by |qc.order_by().size()| * log(row count). This should act as a crude | 
 |   // estimation of the cost. | 
 |   double sort_cost = | 
 |       static_cast<double>(qc.order_by().size() * current_row_count) * | 
 |       log2(current_row_count); | 
 |  | 
 |   // The cost of iterating rows is more expensive than just filtering the rows | 
 |   // so multiply by an appropriate factor. | 
 |   double iteration_cost = current_row_count * 2.0; | 
 |  | 
 |   // To get the final cost, add up all the individual components. | 
 |   double final_cost = | 
 |       kFixedQueryCost + filter_cost + sort_cost + iteration_cost; | 
 |   return QueryCost{final_cost, current_row_count}; | 
 | } | 
 |  | 
 | std::unique_ptr<SqliteTable::BaseCursor> DbSqliteTable::CreateCursor() { | 
 |   return std::make_unique<Cursor>(this, context_->cache); | 
 | } | 
 |  | 
 | DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache) | 
 |     : SqliteTable::BaseCursor(sqlite_table), | 
 |       db_sqlite_table_(sqlite_table), | 
 |       cache_(cache) { | 
 |   switch (db_sqlite_table_->context_->computation) { | 
 |     case TableComputation::kStatic: | 
 |       upstream_table_ = db_sqlite_table_->context_->static_table; | 
 |       argument_index_per_column_.resize(sqlite_table->schema_.columns.size(), | 
 |                                         kInvalidArgumentIndex); | 
 |       break; | 
 |     case TableComputation::kRuntime: | 
 |       upstream_table_ = db_sqlite_table_->runtime_table_; | 
 |       argument_index_per_column_.resize(sqlite_table->schema_.columns.size(), | 
 |                                         kInvalidArgumentIndex); | 
 |       break; | 
 |     case TableComputation::kTableFunction: { | 
 |       uint32_t argument_index = 0; | 
 |       for (const auto& col : sqlite_table->schema_.columns) { | 
 |         argument_index_per_column_.emplace_back( | 
 |             col.is_hidden ? argument_index++ : kInvalidArgumentIndex); | 
 |       } | 
 |       table_function_arguments_.resize(argument_index); | 
 |       break; | 
 |     } | 
 |   } | 
 | } | 
 | DbSqliteTable::Cursor::~Cursor() = default; | 
 |  | 
 | void DbSqliteTable::Cursor::TryCacheCreateSortedTable( | 
 |     const QueryConstraints& qc, | 
 |     FilterHistory history) { | 
 |   // Check if we have a cache. Some subclasses (e.g. the flamegraph table) may | 
 |   // pass nullptr to disable caching. | 
 |   if (!cache_) | 
 |     return; | 
 |  | 
 |   if (history == FilterHistory::kDifferent) { | 
 |     repeated_cache_count_ = 0; | 
 |  | 
 |     // Check if the new constraint set is cached by another cursor. | 
 |     sorted_cache_table_ = | 
 |         cache_->GetIfCached(upstream_table_, qc.constraints()); | 
 |     return; | 
 |   } | 
 |  | 
 |   PERFETTO_DCHECK(history == FilterHistory::kSame); | 
 |  | 
 |   // TODO(lalitm): all of the caching policy below should live in QueryCache and | 
 |   // not here. This is only here temporarily to allow migration of sched without | 
 |   // regressing UI performance and should be removed ASAP. | 
 |  | 
 |   // Only try and create the cached table on exactly the third time we see this | 
 |   // constraint set. | 
 |   constexpr uint32_t kRepeatedThreshold = 3; | 
 |   if (sorted_cache_table_ || repeated_cache_count_++ != kRepeatedThreshold) | 
 |     return; | 
 |  | 
 |   // If we have more than one constraint, we can't cache the table using | 
 |   // this method. | 
 |   if (qc.constraints().size() != 1) | 
 |     return; | 
 |  | 
 |   // If the constraing is not an equality constraint, there's little | 
 |   // benefit to caching | 
 |   const auto& c = qc.constraints().front(); | 
 |   if (!sqlite_utils::IsOpEq(c.op)) | 
 |     return; | 
 |  | 
 |   // If the column is already sorted, we don't need to cache at all. | 
 |   auto col = static_cast<uint32_t>(c.column); | 
 |   if (db_sqlite_table_->schema_.columns[col].is_sorted) | 
 |     return; | 
 |  | 
 |   // Try again to get the result or start caching it. | 
 |   sorted_cache_table_ = | 
 |       cache_->GetOrCache(upstream_table_, qc.constraints(), [this, col]() { | 
 |         return upstream_table_->Sort({Order{col, false}}); | 
 |       }); | 
 | } | 
 |  | 
 | base::Status DbSqliteTable::Cursor::Filter(const QueryConstraints& qc, | 
 |                                            sqlite3_value** argv, | 
 |                                            FilterHistory history) { | 
 |   // Clear out the iterator before filtering to ensure the destructor is run | 
 |   // before the table's destructor. | 
 |   iterator_ = std::nullopt; | 
 |  | 
 |   RETURN_IF_ERROR(PopulateConstraintsAndArguments(qc, argv)); | 
 |   PopulateOrderBys(qc); | 
 |  | 
 |   // Setup the upstream table based on the computation state. | 
 |   switch (db_sqlite_table_->context_->computation) { | 
 |     case TableComputation::kStatic: | 
 |     case TableComputation::kRuntime: | 
 |       // Tries to create a sorted cached table which can be used to speed up | 
 |       // filters below. | 
 |       TryCacheCreateSortedTable(qc, history); | 
 |       break; | 
 |     case TableComputation::kTableFunction: { | 
 |       PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED, | 
 |                         "TABLE_FUNCTION_CALL", [this](metatrace::Record* r) { | 
 |                           r->AddArg("Name", db_sqlite_table_->name()); | 
 |                         }); | 
 |       base::StatusOr<std::unique_ptr<Table>> table = | 
 |           db_sqlite_table_->context_->static_table_function->ComputeTable( | 
 |               table_function_arguments_); | 
 |       if (!table.ok()) { | 
 |         return base::ErrStatus("%s: %s", db_sqlite_table_->name().c_str(), | 
 |                                table.status().c_message()); | 
 |       } | 
 |       dynamic_table_ = std::move(*table); | 
 |       upstream_table_ = dynamic_table_.get(); | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   PERFETTO_TP_TRACE( | 
 |       metatrace::Category::QUERY_DETAILED, "DB_TABLE_FILTER_AND_SORT", | 
 |       [this](metatrace::Record* r) { FilterAndSortMetatrace(r); }); | 
 |  | 
 |   RowMap filter_map = SourceTable()->QueryToRowMap(constraints_, orders_); | 
 |   if (filter_map.IsRange() && filter_map.size() <= 1) { | 
 |     // Currently, our criteria where we have a special fast path is if it's | 
 |     // a single ranged row. We have this fast path for joins on id columns | 
 |     // where we get repeated queries filtering down to a single row. The | 
 |     // other path performs allocations when creating the new table as well | 
 |     // as the iterator on the new table whereas this path only uses a single | 
 |     // number and lives entirely on the stack. | 
 |  | 
 |     // TODO(lalitm): investigate some other criteria where it is beneficial | 
 |     // to have a fast path and expand to them. | 
 |     mode_ = Mode::kSingleRow; | 
 |     single_row_ = filter_map.size() == 1 ? std::make_optional(filter_map.Get(0)) | 
 |                                          : std::nullopt; | 
 |     eof_ = !single_row_.has_value(); | 
 |   } else { | 
 |     mode_ = Mode::kTable; | 
 |     iterator_ = SourceTable()->ApplyAndIterateRows(std::move(filter_map)); | 
 |     eof_ = !*iterator_; | 
 |   } | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | base::Status DbSqliteTable::Cursor::PopulateConstraintsAndArguments( | 
 |     const QueryConstraints& qc, | 
 |     sqlite3_value** argv) { | 
 |   // We reuse this vector to reduce memory allocations on nested subqueries. | 
 |   constraints_.resize(qc.constraints().size()); | 
 |   uint32_t constraints_pos = 0; | 
 |   for (size_t i = 0; i < qc.constraints().size(); ++i) { | 
 |     const auto& cs = qc.constraints()[i]; | 
 |     auto col = static_cast<uint32_t>(cs.column); | 
 |  | 
 |     // If we get a std::nullopt FilterOp, that means we should allow SQLite | 
 |     // to handle the constraint. | 
 |     std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs.op); | 
 |     if (!opt_op) | 
 |       continue; | 
 |  | 
 |     SqlValue value = SqliteValueToSqlValue(argv[i]); | 
 |     if constexpr (regex::IsRegexSupported()) { | 
 |       if (*opt_op == FilterOp::kRegex) { | 
 |         if (value.type != SqlValue::kString) | 
 |           return base::ErrStatus("Value has to be a string"); | 
 |  | 
 |         if (auto regex_status = regex::Regex::Create(value.AsString()); | 
 |             !regex_status.ok()) | 
 |           return regex_status.status(); | 
 |       } | 
 |     } | 
 |  | 
 |     uint32_t argument_index = argument_index_per_column_[col]; | 
 |     if (argument_index == kInvalidArgumentIndex) { | 
 |       constraints_[constraints_pos++] = Constraint{col, *opt_op, value}; | 
 |     } else { | 
 |       table_function_arguments_[argument_index] = value; | 
 |     } | 
 |   } | 
 |   constraints_.resize(constraints_pos); | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | void DbSqliteTable::Cursor::PopulateOrderBys(const QueryConstraints& qc) { | 
 |   // We reuse this vector to reduce memory allocations on nested subqueries. | 
 |   orders_.resize(qc.order_by().size()); | 
 |   for (size_t i = 0; i < qc.order_by().size(); ++i) { | 
 |     const auto& ob = qc.order_by()[i]; | 
 |     auto col = static_cast<uint32_t>(ob.iColumn); | 
 |     orders_[i] = Order{col, static_cast<bool>(ob.desc)}; | 
 |   } | 
 | } | 
 |  | 
 | void DbSqliteTable::Cursor::FilterAndSortMetatrace(metatrace::Record* r) { | 
 |   r->AddArg("Table", db_sqlite_table_->name()); | 
 |   for (const Constraint& c : constraints_) { | 
 |     SafeStringWriter writer; | 
 |     writer.AppendString(db_sqlite_table_->schema_.columns[c.col_idx].name); | 
 |  | 
 |     writer.AppendString(" "); | 
 |     switch (c.op) { | 
 |       case FilterOp::kEq: | 
 |         writer.AppendString("="); | 
 |         break; | 
 |       case FilterOp::kGe: | 
 |         writer.AppendString(">="); | 
 |         break; | 
 |       case FilterOp::kGt: | 
 |         writer.AppendString(">"); | 
 |         break; | 
 |       case FilterOp::kLe: | 
 |         writer.AppendString("<="); | 
 |         break; | 
 |       case FilterOp::kLt: | 
 |         writer.AppendString("<"); | 
 |         break; | 
 |       case FilterOp::kNe: | 
 |         writer.AppendString("!="); | 
 |         break; | 
 |       case FilterOp::kIsNull: | 
 |         writer.AppendString("IS"); | 
 |         break; | 
 |       case FilterOp::kIsNotNull: | 
 |         writer.AppendString("IS NOT"); | 
 |         break; | 
 |       case FilterOp::kGlob: | 
 |         writer.AppendString("GLOB"); | 
 |         break; | 
 |       case FilterOp::kRegex: | 
 |         writer.AppendString("REGEXP"); | 
 |         break; | 
 |     } | 
 |     writer.AppendString(" "); | 
 |  | 
 |     switch (c.value.type) { | 
 |       case SqlValue::kString: | 
 |         writer.AppendString(c.value.AsString()); | 
 |         break; | 
 |       case SqlValue::kBytes: | 
 |         writer.AppendString("<bytes>"); | 
 |         break; | 
 |       case SqlValue::kNull: | 
 |         writer.AppendString("<null>"); | 
 |         break; | 
 |       case SqlValue::kDouble: { | 
 |         writer.AppendString(std::to_string(c.value.AsDouble())); | 
 |         break; | 
 |       } | 
 |       case SqlValue::kLong: { | 
 |         writer.AppendString(std::to_string(c.value.AsLong())); | 
 |         break; | 
 |       } | 
 |     } | 
 |     r->AddArg("Constraint", writer.GetStringView()); | 
 |   } | 
 |  | 
 |   for (const auto& o : orders_) { | 
 |     SafeStringWriter writer; | 
 |     writer.AppendString(db_sqlite_table_->schema_.columns[o.col_idx].name); | 
 |     if (o.desc) | 
 |       writer.AppendString(" desc"); | 
 |     r->AddArg("Order by", writer.GetStringView()); | 
 |   } | 
 | } | 
 |  | 
 | DbSqliteTableContext::DbSqliteTableContext(QueryCache* query_cache, | 
 |                                            const Table* table, | 
 |                                            Table::Schema schema) | 
 |     : cache(query_cache), | 
 |       computation(Computation::kStatic), | 
 |       static_table(table), | 
 |       static_schema(std::move(schema)) {} | 
 |  | 
 | DbSqliteTableContext::DbSqliteTableContext( | 
 |     QueryCache* query_cache, | 
 |     std::function<RuntimeTable*(std::string)> get_table, | 
 |     std::function<void(std::string)> erase_table) | 
 |     : cache(query_cache), | 
 |       computation(Computation::kRuntime), | 
 |       get_runtime_table(std::move(get_table)), | 
 |       erase_runtime_table(std::move(erase_table)) {} | 
 |  | 
 | DbSqliteTableContext::DbSqliteTableContext( | 
 |     QueryCache* query_cache, | 
 |     std::unique_ptr<StaticTableFunction> table) | 
 |     : cache(query_cache), | 
 |       computation(Computation::kTableFunction), | 
 |       static_table_function(std::move(table)) {} | 
 |  | 
 | }  // namespace perfetto::trace_processor |