Merge changes I8ed3c55e,I021610d7 into main

* changes:
  tp: Migrate stdlib to CREATE PERFETTO INDEX
  tp: Fix validation
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index 567e888..d8408d7 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -192,22 +192,12 @@
 
 # Given SQL string check whether there is (not allowlisted) usage of
 # CREATE TABLE {name} AS.
-def check_banned_create_table_as(sql: str, filename: str, stdlib_path: str,
-                                 allowlist: Dict[str, List[str]]) -> List[str]:
+def check_banned_create_table_as(sql: str, filename: str,
+                                 stdlib_path: str) -> List[str]:
   errors = []
   for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items():
     name = matches[0]
-    # Normalize paths before checking presence in the allowlist so it will
-    # work on Windows for the Chrome stdlib presubmit.
-    allowlist_normpath = dict(
-        (os.path.normpath(path), tables) for path, tables in allowlist.items())
-    allowlist_key = os.path.normpath(filename[len(stdlib_path):])
-    if allowlist_key not in allowlist_normpath:
-      errors.append(f"CREATE TABLE '{name}' is deprecated. "
-                    "Use CREATE PERFETTO TABLE instead.\n"
-                    f"Offending file: {filename}\n")
-      continue
-    if name not in allowlist_normpath[allowlist_key]:
+    if name != "trace_bounds":
       errors.append(
           f"Table '{name}' uses CREATE TABLE which is deprecated "
           "and this table is not allowlisted. Use CREATE PERFETTO TABLE.\n"
diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc
index 426c6d2..b31d8cb 100644
--- a/src/trace_processor/db/column/dense_null_overlay.cc
+++ b/src/trace_processor/db/column/dense_null_overlay.cc
@@ -95,6 +95,9 @@
   if (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) {
     return SearchValidationResult::kOk;
   }
+  if (sql_val.is_null()) {
+    return SearchValidationResult::kNoData;
+  }
   return inner_->ValidateSearchConstraints(op, sql_val);
 }
 
diff --git a/src/trace_processor/db/column/id_storage.cc b/src/trace_processor/db/column/id_storage.cc
index 1ebe459..74a8147 100644
--- a/src/trace_processor/db/column/id_storage.cc
+++ b/src/trace_processor/db/column/id_storage.cc
@@ -60,12 +60,6 @@
     if (op == FilterOp::kIsNotNull) {
       return SearchValidationResult::kAllData;
     }
-    if (op == FilterOp::kIsNull) {
-      return SearchValidationResult::kNoData;
-    }
-    PERFETTO_DFATAL(
-        "Invalid filter operation. NULL should only be compared with 'IS NULL' "
-        "and 'IS NOT NULL'");
     return SearchValidationResult::kNoData;
   }
 
diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc
index 372d3b0..4e88682 100644
--- a/src/trace_processor/db/column/null_overlay.cc
+++ b/src/trace_processor/db/column/null_overlay.cc
@@ -148,6 +148,9 @@
   if (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) {
     return SearchValidationResult::kOk;
   }
+  if (sql_val.is_null()) {
+    return SearchValidationResult::kNoData;
+  }
   return inner_->ValidateSearchConstraints(op, sql_val);
 }
 
diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc
index 3612356..768c087 100644
--- a/src/trace_processor/db/column/numeric_storage.cc
+++ b/src/trace_processor/db/column/numeric_storage.cc
@@ -257,12 +257,7 @@
     if (op == FilterOp::kIsNotNull) {
       return SearchValidationResult::kAllData;
     }
-    if (op == FilterOp::kIsNull) {
-      return SearchValidationResult::kNoData;
-    }
-    PERFETTO_FATAL(
-        "Invalid path. NULL should only be compared with 'IS NULL' and 'IS NOT "
-        "NULL'");
+    return SearchValidationResult::kNoData;
   }
 
   // FilterOp checks. Switch so that we get a warning if new FilterOp is not
diff --git a/src/trace_processor/db/column/range_overlay.cc b/src/trace_processor/db/column/range_overlay.cc
index dee7838..9c9e05a 100644
--- a/src/trace_processor/db/column/range_overlay.cc
+++ b/src/trace_processor/db/column/range_overlay.cc
@@ -51,6 +51,10 @@
 SearchValidationResult RangeOverlay::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue sql_val) const {
+  if (sql_val.is_null() &&
+      !(op == FilterOp::kIsNotNull || op == FilterOp::kIsNull)) {
+    return SearchValidationResult::kNoData;
+  }
   return inner_->ValidateSearchConstraints(op, sql_val);
 }
 
diff --git a/src/trace_processor/db/column/selector_overlay.cc b/src/trace_processor/db/column/selector_overlay.cc
index 9e957cb..7e858cf 100644
--- a/src/trace_processor/db/column/selector_overlay.cc
+++ b/src/trace_processor/db/column/selector_overlay.cc
@@ -52,6 +52,10 @@
 SearchValidationResult SelectorOverlay::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue sql_val) const {
+  if (sql_val.is_null() &&
+      !(op == FilterOp::kIsNotNull || op == FilterOp::kIsNull)) {
+    return SearchValidationResult::kNoData;
+  }
   return inner_->ValidateSearchConstraints(op, sql_val);
 }
 
diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc
index 42ab4ea..c37460e 100644
--- a/src/trace_processor/db/column/set_id_storage.cc
+++ b/src/trace_processor/db/column/set_id_storage.cc
@@ -82,12 +82,7 @@
     if (op == FilterOp::kIsNotNull) {
       return SearchValidationResult::kAllData;
     }
-    if (op == FilterOp::kIsNull) {
-      return SearchValidationResult::kNoData;
-    }
-    PERFETTO_FATAL(
-        "Invalid filter operation. NULL should only be compared with 'IS NULL' "
-        "and 'IS NOT NULL'");
+    return SearchValidationResult::kNoData;
   }
 
   // FilterOp checks. Switch so that we get a warning if new FilterOp is not
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index 1d25dbb..3aa00e9 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -254,6 +254,10 @@
   // Type checks.
   switch (val.type) {
     case SqlValue::kNull:
+      if (op != FilterOp::kIsNotNull && op != FilterOp::kIsNull) {
+        return SearchValidationResult::kNoData;
+      }
+      break;
     case SqlValue::kString:
       break;
     case SqlValue::kLong:
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index 8880ae9..26a416b 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -84,12 +84,14 @@
     }
   }
 
-  // Comparison of NULL with any operation apart from |IS_NULL| and
-  // |IS_NOT_NULL| should return no rows.
-  if (c.value.is_null() && c.op != FilterOp::kIsNull &&
-      c.op != FilterOp::kIsNotNull) {
-    rm->Clear();
-    return;
+  switch (chain.ValidateSearchConstraints(c.op, c.value)) {
+    case SearchValidationResult::kNoData:
+      rm->Clear();
+      return;
+    case SearchValidationResult::kAllData:
+      return;
+    case SearchValidationResult::kOk:
+      break;
   }
 
   uint32_t rm_last = rm->Get(rm_size - 1);
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index a6aa6f2..ceeddf9 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -648,12 +648,9 @@
   q.orders = obs;
   RowMap sorted_rm = t->QueryToRowMap(q);
 
-  PERFETTO_CHECK(sorted_rm.IsIndexVector());
-  std::vector<uint32_t> sorted_indices =
-      std::move(sorted_rm).TakeAsIndexVector();
-
   RETURN_IF_ERROR(t->SetIndex(index.name, std::move(col_idxs),
-                              std::move(sorted_indices), index.replace));
+                              std::move(sorted_rm).TakeAsIndexVector(),
+                              index.replace));
   return base::OkStatus();
 }
 
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
index 13edd57..78eaf41 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
@@ -350,7 +350,7 @@
   token = tokenizer_.NextNonWhitespace();
   if (token.token_type != SqliteTokenType::TK_LP) {
     base::StackString<1024> err(
-        "Expected parenthesis after table name, received %*s.",
+        "Expected parenthesis after table name, received '%*s'.",
         static_cast<int>(token.str.size()), token.str.data());
     return ErrorAtToken(token, err.c_str());
   }
@@ -359,18 +359,12 @@
 
   do {
     Token col_name_tok = tokenizer_.NextNonWhitespace();
-    if (col_name_tok.token_type != SqliteTokenType::TK_ID) {
-      base::StackString<1024> err("Invalid column name %.*s",
-                                  static_cast<int>(col_name_tok.str.size()),
-                                  col_name_tok.str.data());
-      return ErrorAtToken(col_name_tok, err.c_str());
-    }
     cols.push_back(std::string(col_name_tok.str));
     token = tokenizer_.NextNonWhitespace();
   } while (token.token_type == SqliteTokenType::TK_COMMA);
 
   if (token.token_type != SqliteTokenType::TK_RP) {
-    base::StackString<1024> err("Expected closed parenthesis, received %*s.",
+    base::StackString<1024> err("Expected closed parenthesis, received '%*s'.",
                                 static_cast<int>(token.str.size()),
                                 token.str.data());
     return ErrorAtToken(token, err.c_str());
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
index 70e0c7a..2991dee 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
@@ -157,7 +157,7 @@
   binder_txn_id,
   binder_reply_id;
 
-CREATE TABLE _oom_score AS
+CREATE PERFETTO TABLE _oom_score AS
   SELECT
     process.upid,
     CAST(c.value AS INT) AS value,
@@ -168,7 +168,7 @@
          JOIN process USING (upid)
    WHERE t.name = 'oom_score_adj';
 
-CREATE INDEX _oom_score_idx ON _oom_score(upid, ts);
+CREATE PERFETTO INDEX _oom_score_idx ON _oom_score(upid, ts);
 
 -- Breakdown synchronous binder transactions per txn.
 -- It returns data about the client and server ends of every binder transaction.
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
index 005f867..c7ad3bf 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
@@ -121,29 +121,62 @@
 GROUP BY slice.id;
 
 -- Contains parsed monitor contention slices.
---
--- @column blocking_method Name of the method holding the lock.
--- @column blocked_methhod Name of the method trying to acquire the lock.
--- @column short_blocking_method Blocking_method without arguments and return types.
--- @column short_blocked_method Blocked_method without arguments and return types.
--- @column blocking_src File location of blocking_method in form <filename:linenumber>.
--- @column blocked_src File location of blocked_method in form <filename:linenumber>.
--- @column waiter_count Zero indexed number of threads trying to acquire the lock.
--- @column blocking_utid Utid of thread holding the lock.
--- @column blocking_thread_name Thread name of thread holding the lock.
--- @column upid Upid of process experiencing lock contention.
--- @column process_name Process name of process experiencing lock contention.
--- @column id Slice id of lock contention.
--- @column ts Timestamp of lock contention start.
--- @column dur Wall clock duration of lock contention.
--- @column monotonic_dur Monotonic clock duration of lock contention.
--- @column track_id Thread track id of blocked thread.
--- @column is_blocked_main_thread Whether the blocked thread is the main thread.
--- @column is_blocking_main_thread Whether the blocking thread is the main thread.
--- @column binder_reply_id Slice id of binder reply slice if lock contention was part of a binder txn.
--- @column binder_reply_ts Timestamp of binder reply slice if lock contention was part of a binder txn.
--- @column binder_reply_tid Tid of binder reply slice if lock contention was part of a binder txn.
-CREATE TABLE android_monitor_contention AS
+CREATE PERFETTO TABLE android_monitor_contention(
+-- Name of the method holding the lock.
+blocking_method STRING,
+-- Blocked_method without arguments and return types.
+blocked_method STRING,
+-- Blocking_method without arguments and return types.
+short_blocking_method STRING,
+-- Blocked_method without arguments and return types.
+short_blocked_method STRING,
+-- File location of blocking_method in form <filename:linenumber>.
+blocking_src STRING,
+-- File location of blocked_method in form <filename:linenumber>.
+blocked_src STRING,
+-- Zero indexed number of threads trying to acquire the lock.
+waiter_count INT,
+-- Utid of thread holding the lock.
+blocked_utid INT,
+-- Thread name of thread holding the lock.
+blocked_thread_name STRING,
+-- Utid of thread holding the lock.
+blocking_utid INT,
+-- Thread name of thread holding the lock.
+blocking_thread_name STRING,
+-- Tid of thread holding the lock.
+blocking_tid INT,
+-- Upid of process experiencing lock contention.
+upid INT,
+-- Process name of process experiencing lock contention.
+process_name STRING,
+-- Slice id of lock contention.
+id INT,
+-- Timestamp of lock contention start.
+ts INT,
+-- Wall clock duration of lock contention.
+dur INT,
+-- Monotonic clock duration of lock contention.
+monotonic_dur INT,
+-- Thread track id of blocked thread.
+track_id INT,
+-- Whether the blocked thread is the main thread.
+is_blocked_thread_main INT,
+-- Tid of the blocked thread
+blocked_thread_tid INT,
+-- Whether the blocking thread is the main thread.
+is_blocking_thread_main INT,
+-- Tid of thread holding the lock.
+blocking_thread_tid INT,
+-- Slice id of binder reply slice if lock contention was part of a binder txn.
+binder_reply_id INT,
+-- Timestamp of binder reply slice if lock contention was part of a binder txn.
+binder_reply_ts INT,
+-- Tid of binder reply slice if lock contention was part of a binder txn.
+binder_reply_tid INT,
+-- Pid of process experiencing lock contention.
+pid INT
+) AS
 SELECT
   android_extract_android_monitor_contention_blocking_method(slice.name) AS blocking_method,
   android_extract_android_monitor_contention_blocked_method(slice.name)  AS blocked_method,
@@ -192,10 +225,10 @@
   AND short_blocked_method IS NOT NULL
 GROUP BY slice.id;
 
-CREATE INDEX _android_monitor_contention_blocking_utid_idx
+CREATE PERFETTO INDEX _android_monitor_contention_blocking_utid_idx
   ON android_monitor_contention (blocking_utid, ts);
 
-CREATE INDEX _android_monitor_contention_id_idx
+CREATE PERFETTO INDEX _android_monitor_contention_id_idx
   ON android_monitor_contention (id);
 
 -- Monitor contention slices that are blocked by another monitor contention slice.
@@ -214,7 +247,7 @@
 
 -- Monitor contention slices that are neither blocking nor blocked by another monitor contention
 -- slice. They neither have |parent_id| nor |child_id| fields.
-CREATE TABLE _isolated AS
+CREATE PERFETTO TABLE _isolated AS
 WITH parents_and_children AS (
  SELECT id FROM _children
  UNION ALL
@@ -227,31 +260,66 @@
 SELECT * FROM android_monitor_contention JOIN isolated USING (id);
 
 -- Contains parsed monitor contention slices with the parent-child relationships.
---
--- @column parent_id Id of monitor contention slice blocking this contention.
--- @column blocking_method Name of the method holding the lock.
--- @column blocked_methhod Name of the method trying to acquire the lock.
--- @column short_blocking_method Blocking_method without arguments and return types.
--- @column short_blocked_method Blocked_method without arguments and return types.
--- @column blocking_src File location of blocking_method in form <filename:linenumber>.
--- @column blocked_src File location of blocked_method in form <filename:linenumber>.
--- @column waiter_count Zero indexed number of threads trying to acquire the lock.
--- @column blocking_utid Utid of thread holding the lock.
--- @column blocking_thread_name Thread name of thread holding the lock.
--- @column upid Upid of process experiencing lock contention.
--- @column process_name Process name of process experiencing lock contention.
--- @column id Slice id of lock contention.
--- @column ts Timestamp of lock contention start.
--- @column dur Wall clock duration of lock contention.
--- @column monotonic_dur Monotonic clock duration of lock contention.
--- @column track_id Thread track id of blocked thread.
--- @column is_blocked_main_thread Whether the blocked thread is the main thread.
--- @column is_blocking_main_thread Whether the blocking thread is the main thread.
--- @column binder_reply_id Slice id of binder reply slice if lock contention was part of a binder txn.
--- @column binder_reply_ts Timestamp of binder reply slice if lock contention was part of a binder txn.
--- @column binder_reply_tid Tid of binder reply slice if lock contention was part of a binder txn.
--- @column child_id Id of monitor contention slice blocked by this contention.
-CREATE TABLE android_monitor_contention_chain AS
+CREATE PERFETTO TABLE android_monitor_contention_chain(
+-- Id of monitor contention slice blocking this contention.
+parent_id INT,
+-- Name of the method holding the lock.
+blocking_method STRING,
+-- Blocked_method without arguments and return types.
+blocked_method STRING,
+-- Blocking_method without arguments and return types.
+short_blocking_method STRING,
+-- Blocked_method without arguments and return types.
+short_blocked_method STRING,
+-- File location of blocking_method in form <filename:linenumber>.
+blocking_src STRING,
+-- File location of blocked_method in form <filename:linenumber>.
+blocked_src STRING,
+-- Zero indexed number of threads trying to acquire the lock.
+waiter_count INT,
+-- Utid of thread holding the lock.
+blocked_utid INT,
+-- Thread name of thread holding the lock.
+blocked_thread_name STRING,
+-- Utid of thread holding the lock.
+blocking_utid INT,
+-- Thread name of thread holding the lock.
+blocking_thread_name STRING,
+-- Tid of thread holding the lock.
+blocking_tid INT,
+-- Upid of process experiencing lock contention.
+upid INT,
+-- Process name of process experiencing lock contention.
+process_name STRING,
+-- Slice id of lock contention.
+id INT,
+-- Timestamp of lock contention start.
+ts INT,
+-- Wall clock duration of lock contention.
+dur INT,
+-- Monotonic clock duration of lock contention.
+monotonic_dur INT,
+-- Thread track id of blocked thread.
+track_id INT,
+-- Whether the blocked thread is the main thread.
+is_blocked_thread_main INT,
+-- Tid of the blocked thread
+blocked_thread_tid INT,
+-- Whether the blocking thread is the main thread.
+is_blocking_thread_main INT,
+-- Tid of thread holding the lock.
+blocking_thread_tid INT,
+-- Slice id of binder reply slice if lock contention was part of a binder txn.
+binder_reply_id INT,
+-- Timestamp of binder reply slice if lock contention was part of a binder txn.
+binder_reply_ts INT,
+-- Tid of binder reply slice if lock contention was part of a binder txn.
+binder_reply_tid INT,
+-- Pid of process experiencing lock contention.
+pid INT,
+-- Id of monitor contention slice blocked by this contention.
+child_id INT
+) AS
 SELECT NULL AS parent_id, *, NULL AS child_id FROM _isolated
 UNION ALL
 SELECT c.*, p.child_id FROM _children c
@@ -260,7 +328,7 @@
 SELECT c.parent_id, p.* FROM _parents p
 LEFT JOIN _children c USING(id);
 
-CREATE INDEX _android_monitor_contention_chain_idx
+CREATE PERFETTO INDEX _android_monitor_contention_chain_idx
   ON android_monitor_contention_chain (blocking_method, blocking_utid, ts);
 
 -- First blocked node on a lock, i.e nodes with |waiter_count| = 0. The |dur| here is adjusted
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
index a177cba..35c9335 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
@@ -105,7 +105,7 @@
 --
 -- Note: this might include messages received within a sync mojo call.
 -- TODO(altimin): This should use EXTEND_TABLE when it becomes available.
-CREATE TABLE _chrome_mojo_slices AS
+CREATE PERFETTO TABLE _chrome_mojo_slices AS
 WITH
 -- Select all new-style (post crrev.com/c/3270337) mojo slices and
 -- generate |task_name| for them.
@@ -153,17 +153,16 @@
   FROM slice
   WHERE
     category GLOB "*toplevel*" AND name = "Connector::DispatchMessage"
-)
+),
+merged AS (
 -- Merge all mojo slices.
 SELECT * FROM new_mojo_slices
 UNION ALL
 SELECT * FROM old_associated_mojo_slices
 UNION ALL
-SELECT * FROM old_non_associated_mojo_slices;
-
--- As we lookup by ID on |_chrome_mojo_slices| table, add an index on
--- id to make lookups fast.
-CREATE INDEX _chrome_mojo_slices_idx ON _chrome_mojo_slices(id);
+SELECT * FROM old_non_associated_mojo_slices
+)
+SELECT * FROM merged ORDER BY id;
 
 -- This table contains a list of slices corresponding to the _representative_
 -- Chrome Java view operations.
@@ -178,7 +177,7 @@
 --                                      capture toolbar screenshot.
 -- @column is_hardware_screenshot BOOL  Whether this slice is a part of accelerated
 --                                      capture toolbar screenshot.
-CREATE TABLE _chrome_java_views AS
+CREATE PERFETTO TABLE _chrome_java_views AS
 WITH
 -- .draw, .onLayout and .onMeasure parts of the java view names don't add much, strip them.
 java_slices_with_trimmed_names AS (
@@ -363,7 +362,7 @@
 GROUP BY root.id;
 
 -- A list of tasks executed by Chrome scheduler.
-CREATE TABLE _chrome_scheduler_tasks AS
+CREATE PERFETTO TABLE _chrome_scheduler_tasks AS
 SELECT
   id
 FROM slice
@@ -476,7 +475,7 @@
 -- @column task_name STRING  Name for the given task.
 -- @column task_type STRING  Type of the task (e.g. "scheduler").
 -- @column scheduling_delay INT
-CREATE TABLE _chrome_tasks AS
+CREATE PERFETTO TABLE _chrome_tasks AS
 WITH
 -- Select slices from "toplevel" category which do not have another
 -- "toplevel" slice as ancestor. The possible cases include sync mojo messages
diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql b/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql
index 9456911..066314b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql
@@ -53,7 +53,7 @@
 -- @column upid               Alias for `process.upid`.
 -- @column pid                Alias for `process.pid`.
 -- @column process_name       Alias for `process.name`.
-CREATE TABLE _slice_flattened
+CREATE PERFETTO TABLE _slice_flattened
 AS
 WITH
   root_slices AS (
@@ -86,10 +86,8 @@
 JOIN thread_slice
   USING (id);
 
-CREATE
-  INDEX _slice_flattened_id_idx
+CREATE PERFETTO INDEX _slice_flattened_id_idx
 ON _slice_flattened(slice_id);
 
-CREATE
-  INDEX _slice_flattened_ts_idx
+CREATE PERFETTO INDEX _slice_flattened_ts_idx
 ON _slice_flattened(ts);
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql
index 8e07e17..333a532 100644
--- a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql
@@ -23,7 +23,7 @@
 JOIN _wattson_device as device ON dc.device = device.name
 JOIN _dev_cpu_policy_map as cp ON dc.policy = cp.policy;
 
-CREATE TABLE _filtered_curves_1d AS
+CREATE PERFETTO TABLE _filtered_curves_1d AS
 SELECT policy, freq_khz, -1 as idle, active as curve_value
 FROM _filtered_curves_1d_raw
 UNION
@@ -36,7 +36,7 @@
 SELECT policy, freq_khz, 255, static
 FROM _filtered_curves_1d_raw;
 
-CREATE INDEX freq_1d ON _filtered_curves_1d(policy, freq_khz, idle);
+CREATE PERFETTO INDEX freq_1d ON _filtered_curves_1d(policy, freq_khz, idle);
 
 -- 2D LUT; with dependency on another CPU
 CREATE PERFETTO TABLE _filtered_curves_2d_raw AS
@@ -52,7 +52,7 @@
 JOIN _wattson_device as device ON dc.device = device.name
 JOIN _dev_cpu_policy_map as cp ON dc.other_policy = cp.policy;
 
-CREATE TABLE _filtered_curves_2d AS
+CREATE PERFETTO TABLE _filtered_curves_2d AS
 SELECT freq_khz, other_policy, other_freq_khz, -1 as idle, active as curve_value
 FROM _filtered_curves_2d_raw
 UNION
@@ -65,7 +65,7 @@
 SELECT freq_khz, other_policy, other_freq_khz, 255, static
 FROM _filtered_curves_2d_raw;
 
-CREATE INDEX freq_2d
+CREATE PERFETTO INDEX freq_2d
 ON _filtered_curves_2d(freq_khz, other_policy, other_freq_khz, idle);
 
 -- L3 cache LUT
@@ -80,14 +80,14 @@
 JOIN _wattson_device as device ON dc.device = device.name
 JOIN _dev_cpu_policy_map as cp ON dc.other_policy = cp.policy;
 
-CREATE TABLE _filtered_curves_l3 AS
+CREATE PERFETTO TABLE _filtered_curves_l3 AS
 SELECT
-  freq_khz, other_policy, other_freq_khz, 'hit' as action, l3_hit as curve_value
+  freq_khz, other_policy, other_freq_khz, 'hit' AS action, l3_hit as curve_value
 FROM _filtered_curves_l3_raw
 UNION
 SELECT
-  freq_khz, other_policy, other_freq_khz, 'miss', l3_miss
+  freq_khz, other_policy, other_freq_khz, 'miss' AS action, l3_miss
 FROM _filtered_curves_l3_raw;
 
-CREATE INDEX freq_l3
+CREATE PERFETTO INDEX freq_l3
 ON _filtered_curves_l3(freq_khz, other_policy, other_freq_khz, action);
diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py
index aa394c2..cd82c6d 100755
--- a/tools/check_sql_metrics.py
+++ b/tools/check_sql_metrics.py
@@ -27,12 +27,36 @@
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 sys.path.append(os.path.join(ROOT_DIR))
 
-from python.generators.sql_processing.utils import check_banned_create_table_as
 from python.generators.sql_processing.utils import check_banned_create_view_as
 from python.generators.sql_processing.utils import check_banned_words
 from python.generators.sql_processing.utils import match_pattern
 from python.generators.sql_processing.utils import DROP_TABLE_VIEW_PATTERN
 from python.generators.sql_processing.utils import CREATE_TABLE_VIEW_PATTERN
+from python.generators.sql_processing.utils import CREATE_TABLE_AS_PATTERN
+
+
+def check_if_create_table_allowlisted(
+    sql: str, filename: str, stdlib_path: str,
+    allowlist: Dict[str, List[str]]) -> List[str]:
+  errors = []
+  for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items():
+    name = matches[0]
+    # Normalize paths before checking presence in the allowlist so it will
+    # work on Windows for the Chrome stdlib presubmit.
+    allowlist_normpath = dict(
+        (os.path.normpath(path), tables) for path, tables in allowlist.items())
+    allowlist_key = os.path.normpath(filename[len(stdlib_path):])
+    if allowlist_key not in allowlist_normpath:
+      errors.append(f"CREATE TABLE '{name}' is deprecated. "
+                    "Use CREATE PERFETTO TABLE instead.\n"
+                    f"Offending file: {filename}\n")
+      continue
+    if name not in allowlist_normpath[allowlist_key]:
+      errors.append(
+          f"Table '{name}' uses CREATE TABLE which is deprecated "
+          "and this table is not allowlisted. Use CREATE PERFETTO TABLE.\n"
+          f"Offending file: {filename}\n")
+  return errors
 
 # Allowlist path are relative to the metrics root.
 CREATE_TABLE_ALLOWLIST = {
@@ -99,10 +123,10 @@
       sql, CREATE_TABLE_VIEW_PATTERN)
   drop_table_view_dir = match_drop_view_pattern_to_dict(
       sql, DROP_TABLE_VIEW_PATTERN)
-  errors += check_banned_create_table_as(sql,
-                                         path.split(ROOT_DIR)[1],
-                                         metrics_sources.split(ROOT_DIR)[1],
-                                         CREATE_TABLE_ALLOWLIST)
+  errors += check_if_create_table_allowlisted(
+      sql,
+      path.split(ROOT_DIR)[1],
+      metrics_sources.split(ROOT_DIR)[1], CREATE_TABLE_ALLOWLIST)
   errors += check_banned_create_view_as(sql, path.split(ROOT_DIR)[1])
   for name, [line, type] in create_table_view_dir.items():
     if name not in drop_table_view_dir:
diff --git a/tools/check_sql_modules.py b/tools/check_sql_modules.py
index 17d0115..7765697 100755
--- a/tools/check_sql_modules.py
+++ b/tools/check_sql_modules.py
@@ -32,27 +32,6 @@
 from python.generators.sql_processing.utils import check_banned_words
 from python.generators.sql_processing.utils import check_banned_include_all
 
-# Allowlist path are relative to the stdlib root.
-CREATE_TABLE_ALLOWLIST = {
-    '/prelude/trace_bounds.sql': ['trace_bounds'],
-    '/android/binder.sql': ['_oom_score'],
-    '/android/monitor_contention.sql': [
-        '_isolated', 'android_monitor_contention_chain',
-        'android_monitor_contention'
-    ],
-    '/chrome/tasks.sql': [
-        '_chrome_mojo_slices', '_chrome_java_views', '_chrome_scheduler_tasks',
-        '_chrome_tasks'
-    ],
-    '/sched/thread_executing_span.sql': [
-        '_wakeup_graph', '_thread_executing_span_graph', '_critical_path'
-    ],
-    '/slices/flat_slices.sql': ['_slice_flattened'],
-    '/wattson/curves/utils.sql': [
-        '_filtered_curves_1d', '_filtered_curves_2d', '_filtered_curves_l3'
-    ],
-}
-
 
 def main():
   parser = argparse.ArgumentParser()
@@ -126,7 +105,7 @@
     errors += check_banned_create_table_as(
         sql,
         path.split(ROOT_DIR)[1],
-        args.stdlib_sources.split(ROOT_DIR)[1], CREATE_TABLE_ALLOWLIST)
+        args.stdlib_sources.split(ROOT_DIR)[1])
     errors += check_banned_create_view_as(sql, path.split(ROOT_DIR)[1])
     errors += check_banned_include_all(sql, path.split(ROOT_DIR)[1])