Merge "trace_processor: make schema more explicit in tables"
diff --git a/src/trace_processor/counters_table.cc b/src/trace_processor/counters_table.cc
index 1cc4026..df5723d 100644
--- a/src/trace_processor/counters_table.cc
+++ b/src/trace_processor/counters_table.cc
@@ -36,17 +36,18 @@
   Table::Register<CountersTable>(db, storage, "counters");
 }
 
-std::string CountersTable::CreateTableStmt(int, const char* const*) {
-  return "CREATE TABLE x("
-         "ts UNSIGNED BIG INT, "
-         "name text, "
-         "value UNSIGNED BIG INT, "
-         "dur UNSIGNED BIG INT, "
-         "value_delta UNSIGNED BIG INT, "
-         "ref UNSIGNED INT, "
-         "ref_type TEXT, "
-         "PRIMARY KEY(name, ts, ref)"
-         ") WITHOUT ROWID;";
+Table::Schema CountersTable::CreateSchema(int, const char* const*) {
+  return Schema(
+      {
+          Table::Column(Column::kTimestamp, "ts", ColumnType::kUlong),
+          Table::Column(Column::kName, "name", ColumnType::kString),
+          Table::Column(Column::kValue, "value", ColumnType::kUlong),
+          Table::Column(Column::kDuration, "dur", ColumnType::kUlong),
+          Table::Column(Column::kValueDelta, "value_delta", ColumnType::kUlong),
+          Table::Column(Column::kRef, "ref", ColumnType::kUint),
+          Table::Column(Column::kRefType, "ref_type", ColumnType::kString),
+      },
+      {Column::kName, Column::kTimestamp, Column::kRef});
 }
 
 std::unique_ptr<Table::Cursor> CountersTable::CreateCursor() {
diff --git a/src/trace_processor/counters_table.h b/src/trace_processor/counters_table.h
index 5ef21ba..83e41d7 100644
--- a/src/trace_processor/counters_table.h
+++ b/src/trace_processor/counters_table.h
@@ -43,7 +43,7 @@
   CountersTable(sqlite3*, const TraceStorage*);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
diff --git a/src/trace_processor/process_table.cc b/src/trace_processor/process_table.cc
index a5b34e4..fab28e7 100644
--- a/src/trace_processor/process_table.cc
+++ b/src/trace_processor/process_table.cc
@@ -36,13 +36,14 @@
   Table::Register<ProcessTable>(db, storage, "process");
 }
 
-std::string ProcessTable::CreateTableStmt(int, const char* const*) {
-  return "CREATE TABLE x("
-         "upid UNSIGNED INT, "
-         "name TEXT, "
-         "pid UNSIGNED INT, "
-         "PRIMARY KEY(upid)"
-         ") WITHOUT ROWID;";
+Table::Schema ProcessTable::CreateSchema(int, const char* const*) {
+  return Schema(
+      {
+          Table::Column(Column::kUpid, "upid", ColumnType::kInt),
+          Table::Column(Column::kName, "name", ColumnType::kString),
+          Table::Column(Column::kPid, "pid", ColumnType::kUint),
+      },
+      {Column::kUpid});
 }
 
 std::unique_ptr<Table::Cursor> ProcessTable::CreateCursor() {
diff --git a/src/trace_processor/process_table.h b/src/trace_processor/process_table.h
index 33c5a30..9ba9ce5 100644
--- a/src/trace_processor/process_table.h
+++ b/src/trace_processor/process_table.h
@@ -37,7 +37,7 @@
   ProcessTable(sqlite3*, const TraceStorage*);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
diff --git a/src/trace_processor/sched_slice_table.cc b/src/trace_processor/sched_slice_table.cc
index ab01a6d..bf0acf4 100644
--- a/src/trace_processor/sched_slice_table.cc
+++ b/src/trace_processor/sched_slice_table.cc
@@ -96,14 +96,15 @@
   Table::Register<SchedSliceTable>(db, storage, "sched");
 }
 
-std::string SchedSliceTable::CreateTableStmt(int, const char* const*) {
-  return "CREATE TABLE sched("
-         "ts UNSIGNED BIG INT, "
-         "cpu UNSIGNED INT, "
-         "dur UNSIGNED BIG INT, "
-         "utid UNSIGNED INT, "
-         "PRIMARY KEY(cpu, ts)"
-         ") WITHOUT ROWID;";
+Table::Schema SchedSliceTable::CreateSchema(int, const char* const*) {
+  return Schema(
+      {
+          Table::Column(Column::kTimestamp, "ts", ColumnType::kUlong),
+          Table::Column(Column::kCpu, "cpu", ColumnType::kUint),
+          Table::Column(Column::kDuration, "dur", ColumnType::kUlong),
+          Table::Column(Column::kUtid, "utid", ColumnType::kUint),
+      },
+      {Column::kCpu, Column::kTimestamp});
 }
 
 std::unique_ptr<Table::Cursor> SchedSliceTable::CreateCursor() {
diff --git a/src/trace_processor/sched_slice_table.h b/src/trace_processor/sched_slice_table.h
index 582dded..255a3bb 100644
--- a/src/trace_processor/sched_slice_table.h
+++ b/src/trace_processor/sched_slice_table.h
@@ -45,7 +45,7 @@
   static void RegisterTable(sqlite3* db, const TraceStorage* storage);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
diff --git a/src/trace_processor/slice_table.cc b/src/trace_processor/slice_table.cc
index e271d93..ee486bd 100644
--- a/src/trace_processor/slice_table.cc
+++ b/src/trace_processor/slice_table.cc
@@ -35,20 +35,20 @@
   Table::Register<SliceTable>(db, storage, "slices");
 }
 
-std::string SliceTable::CreateTableStmt(int, const char* const*) {
-  // TODO(primiano): add support for ts_lower_bound. It requires the guarantee
-  // that slices are pushed in the storage monotonically.
-  return "CREATE TABLE x("
-         "ts UNSIGNED BIG INT, "
-         "dur UNSIGNED BIG INT, "
-         "utid UNSIGNED INT,"
-         "cat STRING,"
-         "name STRING,"
-         "depth INT,"
-         "stack_id UNSIGNED BIG INT,"
-         "parent_stack_id UNSIGNED BIG INT,"
-         "PRIMARY KEY(utid, ts, depth)"
-         ") WITHOUT ROWID;";
+Table::Schema SliceTable::CreateSchema(int, const char* const*) {
+  return Schema(
+      {
+          Table::Column(Column::kTimestamp, "ts", ColumnType::kUlong),
+          Table::Column(Column::kDuration, "dur", ColumnType::kUlong),
+          Table::Column(Column::kUtid, "utid", ColumnType::kUint),
+          Table::Column(Column::kCategory, "cat", ColumnType::kString),
+          Table::Column(Column::kName, "name", ColumnType::kString),
+          Table::Column(Column::kDepth, "depth", ColumnType::kInt),
+          Table::Column(Column::kStackId, "stack_id", ColumnType::kUlong),
+          Table::Column(Column::kParentStackId, "parent_stack_id",
+                        ColumnType::kUlong),
+      },
+      {Column::kUtid, Column::kTimestamp, Column::kDepth});
 }
 
 std::unique_ptr<Table::Cursor> SliceTable::CreateCursor() {
diff --git a/src/trace_processor/slice_table.h b/src/trace_processor/slice_table.h
index 8cbf2a7..d575a10 100644
--- a/src/trace_processor/slice_table.h
+++ b/src/trace_processor/slice_table.h
@@ -52,7 +52,7 @@
   static void RegisterTable(sqlite3* db, const TraceStorage* storage);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
diff --git a/src/trace_processor/span_operator_table.cc b/src/trace_processor/span_operator_table.cc
index 55ee520..cceba72 100644
--- a/src/trace_processor/span_operator_table.cc
+++ b/src/trace_processor/span_operator_table.cc
@@ -33,7 +33,7 @@
 
 constexpr uint64_t kU64Max = std::numeric_limits<uint64_t>::max();
 
-std::vector<SpanOperatorTable::ColumnDefinition> GetColumnsForTable(
+std::vector<Table::Column> GetColumnsForTable(
     sqlite3* db,
     const std::string& raw_table_name) {
   char sql[1024];
@@ -50,7 +50,7 @@
   ScopedStmt stmt(raw_stmt);
   PERFETTO_DCHECK(sqlite3_column_count(*stmt) == 2);
 
-  std::vector<SpanOperatorTable::ColumnDefinition> columns;
+  std::vector<Table::Column> columns;
   while (true) {
     err = sqlite3_step(raw_stmt);
     if (err == SQLITE_DONE)
@@ -62,29 +62,24 @@
 
     const char* name =
         reinterpret_cast<const char*>(sqlite3_column_text(*stmt, 0));
-    const char* type =
+    const char* raw_type =
         reinterpret_cast<const char*>(sqlite3_column_text(*stmt, 1));
-    if (!name || !type || !*name || !*type) {
+    if (!name || !raw_type || !*name || !*raw_type) {
       PERFETTO_ELOG("Schema has invalid column values");
       return {};
     }
 
-    SpanOperatorTable::ColumnDefinition column;
-    column.name = name;
-    column.type_name = type;
-
-    std::transform(column.type_name.begin(), column.type_name.end(),
-                   column.type_name.begin(), ::toupper);
-    if (column.type_name == "UNSIGNED BIG INT") {
-      column.type = SpanOperatorTable::Value::Type::kULong;
-    } else if (column.type_name == "UNSIGNED INT") {
-      column.type = SpanOperatorTable::Value::Type::kUInt;
-    } else if (column.type_name == "TEXT") {
-      column.type = SpanOperatorTable::Value::Type::kText;
+    Table::ColumnType type;
+    if (strcmp(raw_type, "UNSIGNED BIG INT") == 0) {
+      type = Table::ColumnType::kUlong;
+    } else if (strcmp(raw_type, "UNSIGNED INT") == 0) {
+      type = Table::ColumnType::kUint;
+    } else if (strcmp(raw_type, "STRING") == 0) {
+      type = Table::ColumnType::kString;
     } else {
       PERFETTO_FATAL("Unknown column type on table %s", raw_table_name.c_str());
     }
-    columns.emplace_back(column);
+    columns.emplace_back(columns.size(), name, type);
   }
   return columns;
 }
@@ -99,12 +94,12 @@
   Table::Register<SpanOperatorTable>(db, storage, "span");
 }
 
-std::string SpanOperatorTable::CreateTableStmt(int argc,
-                                               const char* const* argv) {
+Table::Schema SpanOperatorTable::CreateSchema(int argc,
+                                              const char* const* argv) {
   // argv[0] - argv[2] are SQLite populated fields which are always present.
   if (argc < 6) {
     PERFETTO_ELOG("SPAN JOIN expected at least 3 args, received %d", argc - 3);
-    return "";
+    return Table::Schema({}, {});
   }
 
   // The order arguments is (t1_name, t2_name, join_col).
@@ -120,8 +115,8 @@
   // are actually valid to be joined i.e. they have the ts and dur columns and
   // have the join column.
 
-  auto filter_fn = [this](const ColumnDefinition& it) {
-    return it.name == "ts" || it.name == "dur" || it.name == join_col_;
+  auto filter_fn = [this](const Table::Column& it) {
+    return it.name() == "ts" || it.name() == "dur" || it.name() == join_col_;
   };
   auto t1_remove_it =
       std::remove_if(t1_defn_.cols.begin(), t1_defn_.cols.end(), filter_fn);
@@ -130,23 +125,19 @@
       std::remove_if(t2_defn_.cols.begin(), t2_defn_.cols.end(), filter_fn);
   t2_defn_.cols.erase(t2_remove_it, t2_defn_.cols.end());
 
-  // Create the statement as the combination of the unique columns of the two
-  // tables.
-  std::string create_stmt;
-  create_stmt +=
-      "CREATE TABLE x("
-      "ts UNSIGNED BIG INT, "
-      "dur UNSIGNED BIG INT, ";
-  create_stmt += join_col_ + " UNSIGNED INT, ";
+  std::vector<Table::Column> columns = {
+      Table::Column(Column::kTimestamp, "ts", ColumnType::kUlong),
+      Table::Column(Column::kDuration, "dur", ColumnType::kUlong),
+      Table::Column(Column::kJoinValue, join_col_, ColumnType::kUlong),
+  };
+  size_t index = kReservedColumns;
   for (const auto& col : t1_defn_.cols) {
-    create_stmt += col.name + " " + col.type_name + ", ";
+    columns.emplace_back(index++, col.name(), col.type());
   }
   for (const auto& col : t2_defn_.cols) {
-    create_stmt += col.name + " " + col.type_name + ", ";
+    columns.emplace_back(index++, col.name(), col.type());
   }
-  create_stmt += "PRIMARY KEY(ts, " + join_col_ + ")) WITHOUT ROWID;";
-  PERFETTO_DLOG("Create statement: %s", create_stmt.c_str());
-  return create_stmt;
+  return Schema(columns, {Column::kTimestamp, kJoinValue});
 }
 
 std::unique_ptr<Table::Cursor> SpanOperatorTable::CreateCursor() {
@@ -187,7 +178,7 @@
   std::string sql;
   sql += "SELECT ts, dur, " + table_->join_col_;
   for (const auto& col : def.cols) {
-    sql += ", " + col.name;
+    sql += ", " + col.name();
   }
   sql += " FROM " + def.name;
   sql += " WHERE 1";
@@ -206,7 +197,7 @@
       auto index_pair = table_->GetTableAndColumnIndex(c);
       bool is_constraint_in_current_table = index_pair.first == is_t1;
       if (is_constraint_in_current_table) {
-        col_name = def.cols[index_pair.second].name;
+        col_name = def.cols[index_pair.second].name();
       }
     }
 
@@ -352,19 +343,21 @@
     size_t off = static_cast<size_t>(i - kReservedColumns);
 
     Value* value = &pull_span->values[off];
-    value->type = table_desc.cols[off].type;
+    value->type = table_desc.cols[off].type();
     switch (value->type) {
-      case Value::Type::kULong:
+      case Table::ColumnType::kUlong:
         value->ulong_value =
             static_cast<uint64_t>(sqlite3_column_int64(stmt, i));
         break;
-      case Value::Type::kUInt:
+      case Table::ColumnType::kUint:
         value->uint_value = static_cast<uint32_t>(sqlite3_column_int(stmt, i));
         break;
-      case Value::Type::kText:
+      case Table::ColumnType::kString:
         value->text_value =
             reinterpret_cast<const char*>(sqlite3_column_text(stmt, i));
         break;
+      case Table::ColumnType::kInt:
+        PERFETTO_CHECK(false);
     }
   }
 
@@ -440,14 +433,14 @@
     sqlite3_context* context,
     SpanOperatorTable::Value value) {
   switch (value.type) {
-    case Value::Type::kUInt:
+    case Table::ColumnType::kUint:
       sqlite3_result_int(context, static_cast<int>(value.uint_value));
       break;
-    case Value::Type::kULong:
+    case Table::ColumnType::kUlong:
       sqlite3_result_int64(context,
                            static_cast<sqlite3_int64>(value.ulong_value));
       break;
-    case Value::Type::kText:
+    case Table::ColumnType::kString: {
       // Note: If you could guarantee that you never sqlite3_step() the cursor
       // before accessing the values here, you could avoid string copies and
       // pass through the const char* obtained in ExtractNext
@@ -456,6 +449,9 @@
       sqlite3_result_text(context, value.text_value.c_str(), -1,
                           kSqliteTransient);
       break;
+    }
+    case Table::ColumnType::kInt:
+      PERFETTO_CHECK(false);
   }
 }
 
diff --git a/src/trace_processor/span_operator_table.h b/src/trace_processor/span_operator_table.h
index 24d21ac..55c8c42 100644
--- a/src/trace_processor/span_operator_table.h
+++ b/src/trace_processor/span_operator_table.h
@@ -74,31 +74,18 @@
 
   // Represents possible values of a SQLite joined table.
   struct Value {
-    enum Type {
-      kText = 0,
-      kULong = 1,
-      kUInt = 2,
-    };
-
-    Type type;
+    Table::ColumnType type;
     std::string text_value;
     uint64_t ulong_value;
     uint32_t uint_value;
   };
 
-  // Stores the definition of a column
-  struct ColumnDefinition {
-    std::string name;
-    std::string type_name;
-    Value::Type type = Value::Type::kText;
-  };
-
   SpanOperatorTable(sqlite3*, const TraceStorage*);
 
   static void RegisterTable(sqlite3* db, const TraceStorage* storage);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) override;
 
@@ -108,7 +95,7 @@
   // Contains the definition of the child tables.
   struct TableDefinition {
     std::string name;
-    std::vector<ColumnDefinition> cols;
+    std::vector<Table::Column> cols;
     std::string join_col_name;
   };
 
diff --git a/src/trace_processor/string_table.cc b/src/trace_processor/string_table.cc
index 9e204d1..adbcabe 100644
--- a/src/trace_processor/string_table.cc
+++ b/src/trace_processor/string_table.cc
@@ -35,12 +35,13 @@
   Table::Register<StringTable>(db, storage, "strings");
 }
 
-std::string StringTable::CreateTableStmt(int, const char* const*) {
-  return "CREATE TABLE x("
-         "id UNSIGNED BIG INT, "
-         "str STRING,"
-         "PRIMARY KEY(id)"
-         ") WITHOUT ROWID;";
+Table::Schema StringTable::CreateSchema(int, const char* const*) {
+  return Schema(
+      {
+          Table::Column(Column::kStringId, "id", ColumnType::kUlong),
+          Table::Column(Column::kString, "str", ColumnType::kString),
+      },
+      {Column::kStringId});
 }
 
 std::unique_ptr<Table::Cursor> StringTable::CreateCursor() {
diff --git a/src/trace_processor/string_table.h b/src/trace_processor/string_table.h
index 1d5efc7..e74d834 100644
--- a/src/trace_processor/string_table.h
+++ b/src/trace_processor/string_table.h
@@ -41,7 +41,7 @@
   static void RegisterTable(sqlite3* db, const TraceStorage* storage);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
diff --git a/src/trace_processor/table.cc b/src/trace_processor/table.cc
index ac0e4b8..d81b467 100644
--- a/src/trace_processor/table.cc
+++ b/src/trace_processor/table.cc
@@ -41,6 +41,20 @@
   return static_cast<Table::Cursor*>(cursor);
 }
 
+std::string TypeToString(Table::ColumnType type) {
+  switch (type) {
+    case Table::ColumnType::kString:
+      return "STRING";
+    case Table::ColumnType::kUint:
+      return "UNSIGNED INT";
+    case Table::ColumnType::kUlong:
+      return "UNSIGNED BIG INT";
+    case Table::ColumnType::kInt:
+      return "INT";
+  }
+  PERFETTO_CHECK(false);
+}
+
 }  // namespace
 
 // static
@@ -66,15 +80,15 @@
     const TableDescriptor* xdesc = static_cast<const TableDescriptor*>(arg);
     auto table = xdesc->factory(xdb, xdesc->storage);
 
-    auto create_stmt = table->CreateTableStmt(argc, argv);
-    if (create_stmt.empty())
-      return SQLITE_ERROR;
+    auto schema = table->CreateSchema(argc, argv);
+    auto create_stmt = schema.ToCreateTableStmt();
 
     int res = sqlite3_declare_vtab(xdb, create_stmt.c_str());
     if (res != SQLITE_OK)
       return res;
 
     // Freed in xDisconnect().
+    table->schema_ = schema;
     table->name_ = xdesc->name;
     *tab = table.release();
 
@@ -230,5 +244,41 @@
   return Filter(table->qc_cache_, argv);
 }
 
+Table::Column::Column(size_t index,
+                      std::string name,
+                      ColumnType type,
+                      bool hidden)
+    : index_(index), name_(name), type_(type), hidden_(hidden) {}
+
+Table::Schema::Schema(std::vector<Column> columns,
+                      std::vector<size_t> primary_keys)
+    : columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) {
+  for (size_t i = 0; i < columns_.size(); i++) {
+    PERFETTO_CHECK(columns_[i].index() == i);
+  }
+  for (auto key : primary_keys_) {
+    PERFETTO_CHECK(key < columns_.size());
+  }
+}
+
+Table::Schema::Schema() = default;
+Table::Schema::Schema(const Schema&) = default;
+Table::Schema& Table::Schema::operator=(const Schema&) = default;
+
+std::string Table::Schema::ToCreateTableStmt() {
+  std::string stmt = "CREATE TABLE x(";
+  for (const auto& col : columns_) {
+    stmt += " " + col.name() + " " + TypeToString(col.type()) + ",";
+  }
+  stmt += " PRIMARY KEY(";
+  for (size_t i = 0; i < primary_keys_.size(); i++) {
+    if (i != 0)
+      stmt += ", ";
+    stmt += columns_[primary_keys_[i]].name();
+  }
+  stmt += ")) WITHOUT ROWID;";
+  return stmt;
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/table.h b/src/trace_processor/table.h
index 26992f9..ba84d37 100644
--- a/src/trace_processor/table.h
+++ b/src/trace_processor/table.h
@@ -39,6 +39,34 @@
   using Factory =
       std::function<std::unique_ptr<Table>(sqlite3*, const TraceStorage*)>;
 
+  // Allowed types for columns in a table.
+  enum ColumnType {
+    kString = 1,
+    kUlong = 2,
+    kUint = 3,
+    kInt = 4,
+  };
+
+  // Describes a column of this table.
+  class Column {
+   public:
+    Column(size_t index,
+           std::string name,
+           ColumnType type,
+           bool hidden = false);
+
+    size_t index() const { return index_; }
+    const std::string& name() const { return name_; }
+    ColumnType type() const { return type_; }
+    bool hidden() const { return hidden_; }
+
+   private:
+    size_t index_ = 0;
+    std::string name_;
+    ColumnType type_ = ColumnType::kString;
+    bool hidden_ = false;
+  };
+
   // When set it logs all BestIndex and Filter actions on the console.
   static bool debug;
 
@@ -76,6 +104,30 @@
     std::vector<bool> omit;
   };
 
+  // The schema of the table. Created by subclasses to allow the table class to
+  // do filtering and inform SQLite about the CREATE table statement.
+  class Schema {
+   public:
+    Schema();
+    Schema(std::vector<Column>, std::vector<size_t> primary_keys);
+
+    // This class is explicitly copiable.
+    Schema(const Schema&) noexcept;
+    Schema& operator=(const Schema& t);
+
+    std::string ToCreateTableStmt();
+
+    const std::vector<Column>& columns() { return columns_; }
+    const std::vector<size_t> primary_keys() { return primary_keys_; }
+
+   private:
+    // The names and types of the columns of the table.
+    std::vector<Column> columns_;
+
+    // The primary keys of the table given by an offset into |columns|.
+    std::vector<size_t> primary_keys_;
+  };
+
   Table();
 
   // Called by derived classes to register themselves with the SQLite db.
@@ -88,7 +140,7 @@
   }
 
   // Methods to be implemented by derived table classes.
-  virtual std::string CreateTableStmt(int argc, const char* const* argv) = 0;
+  virtual Schema CreateSchema(int argc, const char* const* argv) = 0;
   virtual std::unique_ptr<Cursor> CreateCursor() = 0;
   virtual int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) = 0;
 
@@ -121,6 +173,8 @@
   Table& operator=(const Table&) = delete;
 
   std::string name_;
+  Schema schema_;
+
   QueryConstraints qc_cache_;
   int qc_hash_ = 0;
   int best_index_num_ = 0;
diff --git a/src/trace_processor/thread_table.cc b/src/trace_processor/thread_table.cc
index c130c3c..4b6f2be 100644
--- a/src/trace_processor/thread_table.cc
+++ b/src/trace_processor/thread_table.cc
@@ -36,14 +36,15 @@
   Table::Register<ThreadTable>(db, storage, "thread");
 }
 
-std::string ThreadTable::CreateTableStmt(int, const char* const*) {
-  return "CREATE TABLE x("
-         "utid UNSIGNED INT, "
-         "upid UNSIGNED INT, "
-         "name TEXT, "
-         "tid UNSIGNED INT, "
-         "PRIMARY KEY(utid)"
-         ") WITHOUT ROWID;";
+Table::Schema ThreadTable::CreateSchema(int, const char* const*) {
+  return Schema(
+      {
+          Table::Column(Column::kUtid, "utid", ColumnType::kInt),
+          Table::Column(Column::kUpid, "upid", ColumnType::kInt),
+          Table::Column(Column::kName, "name", ColumnType::kString),
+          Table::Column(Column::kTid, "tid", ColumnType::kInt),
+      },
+      {Column::kUtid});
 }
 
 std::unique_ptr<Table::Cursor> ThreadTable::CreateCursor() {
diff --git a/src/trace_processor/thread_table.h b/src/trace_processor/thread_table.h
index 9b19b1b..ff6ef74 100644
--- a/src/trace_processor/thread_table.h
+++ b/src/trace_processor/thread_table.h
@@ -37,7 +37,7 @@
   ThreadTable(sqlite3*, const TraceStorage*);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
 
diff --git a/src/trace_processor/window_operator_table.cc b/src/trace_processor/window_operator_table.cc
index 3648081..0c4dbc4 100644
--- a/src/trace_processor/window_operator_table.cc
+++ b/src/trace_processor/window_operator_table.cc
@@ -32,20 +32,25 @@
   Table::Register<WindowOperatorTable>(db, storage, "window", true);
 }
 
-std::string WindowOperatorTable::CreateTableStmt(int, const char* const*) {
-  return "CREATE TABLE x("
-         // These are the operator columns:
-         "rowid HIDDEN UNSIGNED BIG INT, "
-         "quantum HIDDEN UNSIGNED BIG INT, "
-         "window_start HIDDEN UNSIGNED BIG INT, "
-         "window_dur HIDDEN UNSIGNED BIG INT, "
-         // These are the ouput columns:
-         "ts UNSIGNED BIG INT, "
-         "dur UNSIGNED BIG INT, "
-         "cpu UNSIGNED INT, "
-         "quantum_ts UNSIGNED BIG INT, "
-         "PRIMARY KEY(rowid)"
-         ") WITHOUT ROWID;";
+Table::Schema WindowOperatorTable::CreateSchema(int, const char* const*) {
+  const bool kHidden = true;
+  return Schema(
+      {
+          // These are the operator columns:
+          Table::Column(Column::kRowId, "rowid", ColumnType::kUlong, kHidden),
+          Table::Column(Column::kQuantum, "quantum", ColumnType::kUlong,
+                        kHidden),
+          Table::Column(Column::kWindowStart, "window_start",
+                        ColumnType::kUlong, kHidden),
+          Table::Column(Column::kWindowDur, "window_dur", ColumnType::kUlong,
+                        kHidden),
+          // These are the ouput columns:
+          Table::Column(Column::kTs, "ts", ColumnType::kUlong),
+          Table::Column(Column::kDuration, "dur", ColumnType::kUlong),
+          Table::Column(Column::kCpu, "cpu", ColumnType::kUint),
+          Table::Column(Column::kQuantumTs, "quantum_ts", ColumnType::kUlong),
+      },
+      {Column::kRowId});
 }
 
 std::unique_ptr<Table::Cursor> WindowOperatorTable::CreateCursor() {
diff --git a/src/trace_processor/window_operator_table.h b/src/trace_processor/window_operator_table.h
index 8b2ac93..37b8f30 100644
--- a/src/trace_processor/window_operator_table.h
+++ b/src/trace_processor/window_operator_table.h
@@ -45,7 +45,7 @@
   WindowOperatorTable(sqlite3*, const TraceStorage*);
 
   // Table implementation.
-  std::string CreateTableStmt(int argc, const char* const* argv) override;
+  Table::Schema CreateSchema(int argc, const char* const* argv) override;
   std::unique_ptr<Table::Cursor> CreateCursor() override;
   int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
   int Update(int, sqlite3_value**, sqlite3_int64*) override;