tp: harmonize function argument and constraint conversion

By doing this, we both avoid conversions for function arguments we don't
need and also prepare moving away from SqlValue for functions.

Change-Id: Ia0c135d80d48a8ecbbbda4cbf35ba8dff8e40571
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 51e95d6..8d18365 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -23,6 +23,7 @@
 #include <cstdint>
 #include <functional>
 #include <iterator>
+#include <limits>
 #include <memory>
 #include <optional>
 #include <string>
@@ -51,9 +52,11 @@
 #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:
@@ -144,6 +147,41 @@
   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) {}
@@ -203,7 +241,7 @@
       BestIndex(schema_, runtime_table_->row_count(), qc, info);
       break;
     case TableComputation::kTableFunction:
-      base::Status status = ValidateTableFunctionArguments(schema_, qc);
+      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
@@ -399,34 +437,6 @@
   return QueryCost{final_cost, current_row_count};
 }
 
-base::Status DbSqliteTable::ValidateTableFunctionArguments(
-    const Table::Schema& schema,
-    const QueryConstraints& qc) {
-  for (uint32_t i = 0; i < schema.columns.size(); ++i) {
-    const auto& col = schema.columns[i];
-    if (!col.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'", i);
-    }
-    if (it->op != SQLITE_INDEX_CONSTRAINT_EQ) {
-      return base::ErrStatus(
-          "Only equality constraints supported on column '%u'", i);
-    }
-    auto count = std::count_if(it + 1, qc.constraints().end(), pred);
-    if (count > 0) {
-      return base::ErrStatus("Found multiple constraints on column '%u'", i);
-    }
-  }
-  return base::OkStatus();
-}
-
 std::unique_ptr<SqliteTable::BaseCursor> DbSqliteTable::CreateCursor() {
   return std::make_unique<Cursor>(this, context_->cache);
 }
@@ -438,11 +448,21 @@
   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;
     }
   }
@@ -508,41 +528,8 @@
   // before the table's destructor.
   iterator_ = std::nullopt;
 
-  // 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();
-      }
-    }
-    constraints_[constraints_pos++] = Constraint{col, *opt_op, value};
-  }
-  constraints_.resize(constraints_pos);
-
-  // 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)};
-  }
+  RETURN_IF_ERROR(PopulateConstraintsAndArguments(qc, argv));
+  PopulateOrderBys(qc);
 
   // Setup the upstream table based on the computation state.
   switch (db_sqlite_table_->context_->computation) {
@@ -557,8 +544,6 @@
                         "TABLE_FUNCTION_CALL", [this](metatrace::Record* r) {
                           r->AddArg("Name", db_sqlite_table_->name());
                         });
-      RETURN_IF_ERROR(ExtractTableFunctionArguments(
-          db_sqlite_table_->schema_, constraints_, table_function_arguments_));
       base::StatusOr<std::unique_ptr<Table>> table =
           db_sqlite_table_->context_->static_table_function->ComputeTable(
               table_function_arguments_);
@@ -574,79 +559,7 @@
 
   PERFETTO_TP_TRACE(
       metatrace::Category::QUERY_DETAILED, "DB_TABLE_FILTER_AND_SORT",
-      [this](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());
-        }
-      });
+      [this](metatrace::Record* r) { FilterAndSortMetatrace(r); });
 
   RowMap filter_map = SourceTable()->QueryToRowMap(constraints_, orders_);
   if (filter_map.IsRange() && filter_map.size() <= 1) {
@@ -671,46 +584,127 @@
   return base::OkStatus();
 }
 
-base::Status DbSqliteTable::Cursor::ExtractTableFunctionArguments(
-    const Table::Schema& schema,
-    std::vector<Constraint>& constraints,
-    std::vector<SqlValue>& function_arguments) {
-  // Ensure that the vector is empty as we will add to it below.
-  function_arguments.clear();
+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);
 
-  // It's important that we iterate in schema order as this will match the
-  // function argument order.
-  for (uint32_t i = 0; i < schema.columns.size(); ++i) {
-    const auto& col = schema.columns[i];
-    if (!col.is_hidden) {
+    // 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();
+      }
     }
-    // ValidateTableFunctionArguments should ensure we only have one constraint
-    // but double check this is the case.
-    PERFETTO_DCHECK(std::count_if(constraints.begin(), constraints.end(),
-                                  [i](const Constraint& c) {
-                                    return i == c.col_idx;
-                                  }) == 1);
 
-    // ValidateTableFunctionArguments should ensure that we do not get here
-    // without a valid equality constraint.
-    auto it = std::find_if(constraints.begin(), constraints.end(),
-                           [i](const Constraint& c) { return i == c.col_idx; });
-    PERFETTO_CHECK(it != constraints.end());
-    PERFETTO_CHECK(it->op == FilterOp::kEq);
-
-    // Add the argument to the arguments.
-    function_arguments.push_back(it->value);
+    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;
+    }
   }
-  // Remove all the argument constraints from the main vector.
-  constraints.erase(std::remove_if(constraints.begin(), constraints.end(),
-                                   [&schema](const Constraint& c) {
-                                     return schema.columns[c.col_idx].is_hidden;
-                                   }),
-                    constraints.end());
+  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)
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 5f4a3d7..45bc124 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <functional>
+#include <limits>
 #include <memory>
 #include <optional>
 #include <string>
@@ -37,6 +38,7 @@
 #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"
 
 namespace perfetto {
 namespace trace_processor {
@@ -92,8 +94,11 @@
     Cursor(DbSqliteTable*, QueryCache*);
     ~Cursor() final;
 
-    Cursor(Cursor&&) noexcept = default;
-    Cursor& operator=(Cursor&&) = default;
+    Cursor(const Cursor&) = delete;
+    Cursor& operator=(const Cursor&) = delete;
+
+    Cursor(Cursor&&) noexcept = delete;
+    Cursor& operator=(Cursor&&) = delete;
 
     // Implementation of SqliteTable::Cursor.
     base::Status Filter(const QueryConstraints& qc,
@@ -143,16 +148,16 @@
       return sorted_cache_table_ ? &*sorted_cache_table_ : upstream_table_;
     }
 
-    static base::Status ExtractTableFunctionArguments(
-        const Table::Schema&,
-        std::vector<Constraint>& constraints,
-        std::vector<SqlValue>& function_arguments);
+    base::Status PopulateConstraintsAndArguments(const QueryConstraints& qc,
+                                                 sqlite3_value** argv);
 
-    Cursor(const Cursor&) = delete;
-    Cursor& operator=(const Cursor&) = delete;
+    void PopulateOrderBys(const QueryConstraints& qc);
+
+    void FilterAndSortMetatrace(metatrace::Record* record);
 
     DbSqliteTable* db_sqlite_table_ = nullptr;
     QueryCache* cache_ = nullptr;
+    std::vector<uint32_t> argument_index_per_column_;
 
     const Table* upstream_table_ = nullptr;
 
@@ -213,9 +218,6 @@
                                 const QueryConstraints& qc);
 
  private:
-  static base::Status ValidateTableFunctionArguments(const Table::Schema&,
-                                                     const QueryConstraints&);
-
   Context* context_ = nullptr;
 
   // Only valid after Init has completed.