Merge "tp: centralise all sqlite_result_* function calls" into main
diff --git a/BUILD b/BUILD
index d0deed7..51b4e5f 100644
--- a/BUILD
+++ b/BUILD
@@ -2658,6 +2658,7 @@
         "src/trace_processor/sqlite/sql_stats_table.h",
         "src/trace_processor/sqlite/sqlite_engine.cc",
         "src/trace_processor/sqlite/sqlite_engine.h",
+        "src/trace_processor/sqlite/sqlite_result.h",
         "src/trace_processor/sqlite/sqlite_table.cc",
         "src/trace_processor/sqlite/sqlite_table.h",
         "src/trace_processor/sqlite/sqlite_tokenizer.cc",
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index e37c9c6..84c09ce 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -156,6 +156,7 @@
       "../../protos/perfetto/common:zero",
       "../../protos/perfetto/trace:zero",
       "../../protos/perfetto/trace/perfetto:zero",
+      "../../protos/perfetto/trace_processor:zero",
       "../base",
       "../protozero",
       "db",
diff --git a/src/trace_processor/iterator_impl.h b/src/trace_processor/iterator_impl.h
index 89f300b..47e2e5f 100644
--- a/src/trace_processor/iterator_impl.h
+++ b/src/trace_processor/iterator_impl.h
@@ -18,22 +18,18 @@
 #define SRC_TRACE_PROCESSOR_ITERATOR_IMPL_H_
 
 #include <sqlite3.h>
+#include <cstddef>
+#include <cstdint>
+#include <string>
 
-#include <memory>
-#include <optional>
-#include <vector>
-
-#include "perfetto/base/build_config.h"
-#include "perfetto/base/export.h"
+#include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
+#include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/iterator.h"
-#include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index 639e252..d351e0a 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -465,8 +465,8 @@
   if (repeated_field_type_ && repeated_field_type_ != type) {
     return base::ErrStatus(
         "Inconsistent type in RepeatedField: was %s but now seen value %s",
-        sqlite_utils::SqliteTypeToFriendlyString(*repeated_field_type_),
-        sqlite_utils::SqliteTypeToFriendlyString(type));
+        sqlite::utils::SqliteTypeToFriendlyString(*repeated_field_type_),
+        sqlite::utils::SqliteTypeToFriendlyString(type));
   }
   repeated_field_type_ = type;
   return base::OkStatus();
@@ -514,13 +514,13 @@
     return base::OkStatus();
   }
 
-  out = sqlite_utils::SqliteValueToSqlValue(argv[0]);
+  out = sqlite::utils::SqliteValueToSqlValue(argv[0]);
   return base::OkStatus();
 }
 
 void RepeatedFieldStep(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
   if (argc != 1) {
-    sqlite3_result_error(ctx, "RepeatedField: only expected one arg", -1);
+    sqlite::result::Error(ctx, "RepeatedField: only expected one arg");
     return;
   }
 
@@ -538,11 +538,11 @@
     *builder_ptr_ptr = new RepeatedFieldBuilder();
   }
 
-  auto value = sqlite_utils::SqliteValueToSqlValue(argv[0]);
+  auto value = sqlite::utils::SqliteValueToSqlValue(argv[0]);
   RepeatedFieldBuilder* builder = *builder_ptr_ptr;
   auto status = builder->AddSqlValue(value);
   if (!status.ok()) {
-    sqlite3_result_error(ctx, status.c_message(), -1);
+    sqlite::result::Error(ctx, status.c_message());
   }
 }
 
@@ -554,7 +554,7 @@
 
   // If Step has never been called, |builder_ptr_ptr| will be null.
   if (builder_ptr_ptr == nullptr) {
-    sqlite3_result_null(ctx);
+    sqlite::result::Null(ctx);
     return;
   }
 
@@ -563,14 +563,15 @@
   std::unique_ptr<RepeatedFieldBuilder> builder(*builder_ptr_ptr);
   std::vector<uint8_t> raw = builder->SerializeToProtoBuilderResult();
   if (raw.empty()) {
-    sqlite3_result_null(ctx);
+    sqlite::result::Null(ctx);
     return;
   }
 
   std::unique_ptr<uint8_t[], base::FreeDeleter> data(
       static_cast<uint8_t*>(malloc(raw.size())));
   memcpy(data.get(), raw.data(), raw.size());
-  sqlite3_result_blob(ctx, data.release(), static_cast<int>(raw.size()), free);
+  return sqlite::result::RawBytes(ctx, data.release(),
+                                  static_cast<int>(raw.size()), free);
 }
 
 // SQLite function implementation used to build a proto directly in SQL. The
@@ -600,7 +601,7 @@
 
     const char* key =
         reinterpret_cast<const char*>(sqlite3_value_text(argv[i]));
-    SqlValue value = sqlite_utils::SqliteValueToSqlValue(argv[i + 1]);
+    SqlValue value = sqlite::utils::SqliteValueToSqlValue(argv[i + 1]);
     RETURN_IF_ERROR(builder.AppendSqlValue(key, value));
   }
 
@@ -610,7 +611,7 @@
   if (raw.empty()) {
     // Passing nullptr to SQLite feels dangerous so just pass an empty string
     // and zero as the size so we don't deref nullptr accidentially somewhere.
-    destructors.bytes_destructor = sqlite_utils::kSqliteStatic;
+    destructors.bytes_destructor = sqlite::utils::kSqliteStatic;
     out = SqlValue::Bytes("", 0);
     return base::OkStatus();
   }
@@ -647,10 +648,10 @@
       return base::ErrStatus("RUN_METRIC: all keys must be strings");
     }
 
-    std::optional<std::string> key_str = sqlite_utils::SqlValueToString(
-        sqlite_utils::SqliteValueToSqlValue(argv[i]));
-    std::optional<std::string> value_str = sqlite_utils::SqlValueToString(
-        sqlite_utils::SqliteValueToSqlValue(argv[i + 1]));
+    std::optional<std::string> key_str = sqlite::utils::SqlValueToString(
+        sqlite::utils::SqliteValueToSqlValue(argv[i]));
+    std::optional<std::string> value_str = sqlite::utils::SqlValueToString(
+        sqlite::utils::SqliteValueToSqlValue(argv[i + 1]));
 
     if (!value_str) {
       return base::ErrStatus(
@@ -682,8 +683,8 @@
         "arguments");
   }
 
-  SqlValue proto = sqlite_utils::SqliteValueToSqlValue(argv[0]);
-  SqlValue message_type = sqlite_utils::SqliteValueToSqlValue(argv[1]);
+  SqlValue proto = sqlite::utils::SqliteValueToSqlValue(argv[0]);
+  SqlValue message_type = sqlite::utils::SqliteValueToSqlValue(argv[1]);
 
   if (proto.type != SqlValue::Type::kBytes) {
     return base::ErrStatus("UNWRAP_METRIC_PROTO: proto is not a blob");
@@ -696,7 +697,7 @@
   const uint8_t* ptr = static_cast<const uint8_t*>(proto.AsBytes());
   size_t size = proto.bytes_count;
   if (size == 0) {
-    destructors.bytes_destructor = sqlite_utils::kSqliteStatic;
+    destructors.bytes_destructor = sqlite::utils::kSqliteStatic;
     out = SqlValue::Bytes("", 0);
     return base::OkStatus();
   }
@@ -766,7 +767,7 @@
                              sql_metric.output_table_name.value().c_str());
     }
 
-    SqlValue col = sqlite_utils::SqliteValueToSqlValue(
+    SqlValue col = sqlite::utils::SqliteValueToSqlValue(
         sqlite3_column_value(it->stmt.sqlite_stmt(), 0));
     if (col.type != SqlValue::kBytes) {
       return base::ErrStatus("Output table %s column has invalid type",
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.cc b/src/trace_processor/perfetto_sql/engine/created_function.cc
index 7df16a8..be4f956 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/created_function.cc
@@ -75,7 +75,7 @@
   }
 
   SqlValue result =
-      sqlite_utils::SqliteValueToSqlValue(sqlite3_column_value(stmt, 0));
+      sqlite::utils::SqliteValueToSqlValue(sqlite3_column_value(stmt, 0));
 
   // If we return a bytes type but have a null pointer, SQLite will convert this
   // to an SQL null. However, for proto build functions, we actively want to
@@ -205,7 +205,7 @@
     if (argc != 1) {
       return std::nullopt;
     }
-    SqlValue arg = sqlite_utils::SqliteValueToSqlValue(argv[0]);
+    SqlValue arg = sqlite::utils::SqliteValueToSqlValue(argv[0]);
     if (arg.type != SqlValue::Type::kLong) {
       return std::nullopt;
     }
@@ -611,7 +611,7 @@
   for (size_t i = 0; i < argc; ++i) {
     sqlite3_value* arg = argv[i];
     sql_argument::Type type = state->prototype().arguments[i].type();
-    base::Status status = sqlite_utils::TypeCheckSqliteValue(
+    base::Status status = sqlite::utils::TypeCheckSqliteValue(
         arg, sql_argument::TypeToSqlValueType(type),
         sql_argument::TypeToHumanFriendlyString(type));
     if (!status.ok()) {
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index 69e6b58..e180027 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -41,6 +41,7 @@
 #include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
+#include "src/trace_processor/sqlite/sqlite_result.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/util/sql_argument.h"
 #include "src/trace_processor/util/sql_modules.h"
@@ -267,13 +268,13 @@
   base::Status status =
       Function::Run(ud, static_cast<size_t>(argc), argv, value, destructors);
   if (!status.ok()) {
-    sqlite3_result_error(ctx, status.c_message(), -1);
+    sqlite::result::Error(ctx, status.c_message());
     return;
   }
 
   if (Function::kVoidReturn) {
     if (!value.is_null()) {
-      sqlite3_result_error(ctx, "void SQL function returned value", -1);
+      sqlite::result::Error(ctx, "void SQL function returned value");
       return;
     }
 
@@ -283,15 +284,15 @@
     // if we don't actually read it - just set it to a pointer to an empty
     // string for this reason.
     static char kVoidValue[] = "";
-    sqlite3_result_pointer(ctx, kVoidValue, "VOID", nullptr);
+    sqlite::result::StaticPointer(ctx, kVoidValue, "VOID");
   } else {
-    sqlite_utils::ReportSqlValue(ctx, value, destructors.string_destructor,
-                                 destructors.bytes_destructor);
+    sqlite::utils::ReportSqlValue(ctx, value, destructors.string_destructor,
+                                  destructors.bytes_destructor);
   }
 
   status = Function::VerifyPostConditions(ud);
   if (!status.ok()) {
-    sqlite3_result_error(ctx, status.c_message(), -1);
+    sqlite::result::Error(ctx, status.c_message());
     return;
   }
 }
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
index 247d79b..1eaab01 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
@@ -20,6 +20,7 @@
 #include <utility>
 
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/sqlite/sqlite_result.h"
 #include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto {
@@ -148,13 +149,13 @@
 
     // We only support equality constraints as we're expecting "input arguments"
     // to our "function".
-    if (!sqlite_utils::IsOpEq(cs.op)) {
+    if (!sqlite::utils::IsOpEq(cs.op)) {
       return base::ErrStatus("%s: non-equality constraint passed",
                              state_->prototype.function_name.c_str());
     }
 
     const auto& arg = state_->prototype.arguments[col_to_arg_idx(cs.column)];
-    base::Status status = sqlite_utils::TypeCheckSqliteValue(
+    base::Status status = sqlite::utils::TypeCheckSqliteValue(
         argv[i], sql_argument::TypeToSqlValueType(arg.type()),
         sql_argument::TypeToHumanFriendlyString(arg.type()));
     if (!status.ok()) {
@@ -229,16 +230,16 @@
 base::Status RuntimeTableFunction::Cursor::Column(sqlite3_context* ctx, int i) {
   size_t idx = static_cast<size_t>(i);
   if (state_->IsReturnValueColumn(idx)) {
-    sqlite3_result_value(ctx, sqlite3_column_value(stmt_->sqlite_stmt(), i));
+    sqlite::result::Value(ctx, sqlite3_column_value(stmt_->sqlite_stmt(), i));
   } else if (state_->IsArgumentColumn(idx)) {
     // TODO(lalitm): it may be more appropriate to keep a note of the arguments
     // which we passed in and return them here. Not doing this to because it
     // doesn't seem necessary for any useful thing but something which may need
     // to be changed in the future.
-    sqlite3_result_null(ctx);
+    sqlite::result::Null(ctx);
   } else {
     PERFETTO_DCHECK(state_->IsPrimaryKeyColumn(idx));
-    sqlite3_result_int(ctx, next_call_count_);
+    sqlite::result::Long(ctx, next_call_count_);
   }
   return base::OkStatus();
 }
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc
index 9c16baf..cc0eddc 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc
@@ -39,7 +39,7 @@
       return base::ErrStatus("BASE64: expected one arg but got %zu", argc);
     }
 
-    auto in = sqlite_utils::SqliteValueToSqlValue(argv[0]);
+    auto in = sqlite::utils::SqliteValueToSqlValue(argv[0]);
 
     const char* src = nullptr;
     size_t src_size = 0;
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc
index 18394d7..a7fc1d4 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc
@@ -38,7 +38,7 @@
                                  sqlite3_value** argv,
                                  SqlValue&,
                                  Destructors&) {
-  RETURN_IF_ERROR(sqlite_utils::CheckArgCount("CREATE_FUNCTION", argc, 3u));
+  RETURN_IF_ERROR(sqlite::utils::CheckArgCount("CREATE_FUNCTION", argc, 3u));
 
   sqlite3_value* prototype_value = argv[0];
   sqlite3_value* return_type_value = argv[1];
@@ -48,7 +48,7 @@
   {
     auto type_check = [prototype_value](sqlite3_value* value,
                                         SqlValue::Type type, const char* desc) {
-      base::Status status = sqlite_utils::TypeCheckSqliteValue(value, type);
+      base::Status status = sqlite::utils::TypeCheckSqliteValue(value, type);
       if (!status.ok()) {
         return base::ErrStatus("CREATE_FUNCTION[prototype=%s]: %s %s",
                                sqlite3_value_text(prototype_value), desc,
@@ -85,9 +85,10 @@
                                       sqlite3_value** argv,
                                       SqlValue&,
                                       Destructors&) {
-  RETURN_IF_ERROR(sqlite_utils::CheckArgCount("EXPERIMENTAL_MEMOIZE", argc, 1));
+  RETURN_IF_ERROR(
+      sqlite::utils::CheckArgCount("EXPERIMENTAL_MEMOIZE", argc, 1));
   base::StatusOr<std::string> function_name =
-      sqlite_utils::ExtractStringArg("MEMOIZE", "function_name", argv[0]);
+      sqlite::utils::ExtractStringArg("MEMOIZE", "function_name", argv[0]);
   RETURN_IF_ERROR(function_name.status());
   return engine->EnableSqlFunctionMemoization(*function_name);
 }
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
index 1a19bd4..44782e7 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
@@ -54,7 +54,7 @@
   {
     auto type_check = [prototype_value](sqlite3_value* value,
                                         SqlValue::Type type, const char* desc) {
-      base::Status status = sqlite_utils::TypeCheckSqliteValue(value, type);
+      base::Status status = sqlite::utils::TypeCheckSqliteValue(value, type);
       if (!status.ok()) {
         return base::ErrStatus("CREATE_VIEW_FUNCTION[prototype=%s]: %s %s",
                                sqlite3_value_text(prototype_value), desc,
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
index 30d363e..a660723 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
@@ -48,8 +48,8 @@
 
   // Type check
   {
-    base::Status status =
-        sqlite_utils::TypeCheckSqliteValue(import_val, SqlValue::Type::kString);
+    base::Status status = sqlite::utils::TypeCheckSqliteValue(
+        import_val, SqlValue::Type::kString);
     if (!status.ok()) {
       return base::ErrStatus("IMPORT(%s): %s", sqlite3_value_text(import_val),
                              status.c_message());
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
index cc5cc8a..2f37c86 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
@@ -136,11 +136,11 @@
   RETURN_IF_ERROR(slice_packer.status());
 
   base::StatusOr<SqlValue> ts =
-      sqlite_utils::ExtractArgument(argc, argv, "ts", 0, SqlValue::kLong);
+      sqlite::utils::ExtractArgument(argc, argv, "ts", 0, SqlValue::kLong);
   RETURN_IF_ERROR(ts.status());
 
   base::StatusOr<SqlValue> dur =
-      sqlite_utils::ExtractArgument(argc, argv, "dur", 1, SqlValue::kLong);
+      sqlite::utils::ExtractArgument(argc, argv, "dur", 1, SqlValue::kLong);
   RETURN_IF_ERROR(dur.status());
 
   return slice_packer.value()->AddSlice(ts->AsLong(), dur.value().AsLong());
@@ -151,7 +151,7 @@
 
   base::Status status = Step(ctx, static_cast<size_t>(argc), argv);
   if (!status.ok()) {
-    sqlite_utils::SetSqliteError(ctx, kFunctionName, status);
+    sqlite::utils::SetError(ctx, kFunctionName, status);
     return;
   }
 }
@@ -162,7 +162,7 @@
   if (!slice_packer || !*slice_packer) {
     return;
   }
-  sqlite3_result_int64(ctx,
+  sqlite::result::Long(ctx,
                        static_cast<int64_t>((*slice_packer)->GetLastDepth()));
   delete *slice_packer;
 }
@@ -171,15 +171,15 @@
   base::StatusOr<SlicePacker*> slice_packer =
       GetOrCreateAggregationContext(ctx);
   if (!slice_packer.ok()) {
-    sqlite_utils::SetSqliteError(ctx, kFunctionName, slice_packer.status());
+    sqlite::utils::SetError(ctx, kFunctionName, slice_packer.status());
     return;
   }
-  sqlite3_result_int64(
+  sqlite::result::Long(
       ctx, static_cast<int64_t>(slice_packer.value()->GetLastDepth()));
 }
 
 void InverseWrapper(sqlite3_context* ctx, int, sqlite3_value**) {
-  sqlite_utils::SetSqliteError(ctx, kFunctionName, base::ErrStatus(R"(
+  sqlite::utils::SetError(ctx, kFunctionName, base::ErrStatus(R"(
 The inverse step is not supported: the window clause should be
 "BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW".
 )"));
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc
index c3126e0..d02eaeb 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc
@@ -21,6 +21,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <limits>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -28,6 +29,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/protozero/packed_repeated_fields.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/status.h"
@@ -41,8 +43,7 @@
 // should cache this somewhere maybe even have a helper table that stores all
 // this data.
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 
 using protos::pbzero::Stack;
@@ -69,8 +70,8 @@
   base::Status Step(size_t argc, sqlite3_value** argv) {
     RETURN_IF_ERROR(UpdateSampleValue(argc, argv));
 
-    base::StatusOr<SqlValue> value =
-        sqlite_utils::ExtractArgument(argc, argv, "stack", 0, SqlValue::kBytes);
+    base::StatusOr<SqlValue> value = sqlite::utils::ExtractArgument(
+        argc, argv, "stack", 0, SqlValue::kBytes);
     if (!value.ok()) {
       return value.status();
     }
@@ -78,7 +79,7 @@
     Stack::Decoder stack(static_cast<const uint8_t*>(value->bytes_value),
                          value->bytes_count);
     if (stack.bytes_left() != 0) {
-      return sqlite_utils::ToInvalidArgumentError(
+      return sqlite::utils::ToInvalidArgumentError(
           "stack", 0, base::ErrStatus("failed to deserialize Stack proto"));
     }
     if (!builder_.AddSample(stack, sample_values_)) {
@@ -94,8 +95,8 @@
     std::unique_ptr<uint8_t[], base::FreeDeleter> data(
         static_cast<uint8_t*>(malloc(profile_proto.size())));
     memcpy(data.get(), profile_proto.data(), profile_proto.size());
-    sqlite3_result_blob(ctx, data.release(),
-                        static_cast<int>(profile_proto.size()), free);
+    return sqlite::result::RawBytes(
+        ctx, data.release(), static_cast<int>(profile_proto.size()), free);
   }
 
  private:
@@ -109,13 +110,13 @@
     }
 
     for (size_t i = 1; i < argc; i += 3) {
-      base::StatusOr<SqlValue> type = sqlite_utils::ExtractArgument(
+      base::StatusOr<SqlValue> type = sqlite::utils::ExtractArgument(
           argc, argv, "sample_type", i, SqlValue::kString);
       if (!type.ok()) {
         return type.status();
       }
 
-      base::StatusOr<SqlValue> units = sqlite_utils::ExtractArgument(
+      base::StatusOr<SqlValue> units = sqlite::utils::ExtractArgument(
           argc, argv, "sample_units", i + 1, SqlValue::kString);
       if (!units.ok()) {
         return units.status();
@@ -140,7 +141,7 @@
 
     PERFETTO_CHECK(argc == 1 + (sample_values_.size() * 3));
     for (size_t i = 0; i < sample_values_.size(); ++i) {
-      base::StatusOr<SqlValue> value = sqlite_utils::ExtractArgument(
+      base::StatusOr<SqlValue> value = sqlite::utils::ExtractArgument(
           argc, argv, "sample_value", 3 + i * 3, SqlValue::kLong);
       if (!value.ok()) {
         return value.status();
@@ -155,17 +156,15 @@
   std::vector<int64_t> sample_values_;
 };
 
-static base::Status Step(sqlite3_context* ctx,
-                         size_t argc,
-                         sqlite3_value** argv) {
-  AggregateContext** agg_context_ptr = static_cast<AggregateContext**>(
+base::Status Step(sqlite3_context* ctx, size_t argc, sqlite3_value** argv) {
+  auto** agg_context_ptr = static_cast<AggregateContext**>(
       sqlite3_aggregate_context(ctx, sizeof(AggregateContext*)));
   if (!agg_context_ptr) {
     return base::ErrStatus("Failed to allocate aggregate context");
   }
 
   if (!*agg_context_ptr) {
-    TraceProcessorContext* tp_context =
+    auto* tp_context =
         static_cast<TraceProcessorContext*>(sqlite3_user_data(ctx));
     base::StatusOr<std::unique_ptr<AggregateContext>> agg_context =
         AggregateContext::Create(tp_context, argc, argv);
@@ -179,18 +178,18 @@
   return (*agg_context_ptr)->Step(argc, argv);
 }
 
-static void StepWrapper(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
+void StepWrapper(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
   PERFETTO_CHECK(argc >= 0);
 
   base::Status status = Step(ctx, static_cast<size_t>(argc), argv);
 
   if (!status.ok()) {
-    sqlite_utils::SetSqliteError(ctx, kFunctionName, status);
+    sqlite::utils::SetError(ctx, kFunctionName, status);
   }
 }
 
-static void FinalWrapper(sqlite3_context* ctx) {
-  AggregateContext** agg_context_ptr =
+void FinalWrapper(sqlite3_context* ctx) {
+  auto** agg_context_ptr =
       static_cast<AggregateContext**>(sqlite3_aggregate_context(ctx, 0));
 
   if (!agg_context_ptr) {
@@ -217,5 +216,4 @@
   return base::OkStatus();
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.cc
index 04a9996..9553947 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.cc
@@ -16,8 +16,9 @@
 
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
 
-namespace perfetto {
-namespace trace_processor {
+#include "perfetto/base/status.h"
+
+namespace perfetto::trace_processor {
 
 base::Status SqlFunction::VerifyPostConditions(Context*) {
   return base::OkStatus();
@@ -25,5 +26,4 @@
 
 void SqlFunction::Cleanup(Context*) {}
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/sqlite3_str_split.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/sqlite3_str_split.cc
index 3cd9325..f504b2aa 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/sqlite3_str_split.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/sqlite3_str_split.cc
@@ -32,27 +32,27 @@
                       sqlite3_value** argv) {
   PERFETTO_DCHECK(argc == 3);
   if (sqlite3_value_type(argv[1]) != SQLITE_TEXT) {
-    sqlite3_result_error(context, kDelimiterError, -1);
+    sqlite::result::Error(context, kDelimiterError);
     return;
   }
   const char* delimiter =
       reinterpret_cast<const char*>(sqlite3_value_text(argv[1]));
   const size_t delimiter_len = strlen(delimiter);
   if (delimiter_len == 0) {
-    sqlite3_result_error(context, kDelimiterError, -1);
+    sqlite::result::Error(context, kDelimiterError);
     return;
   }
   if (sqlite3_value_type(argv[2]) != SQLITE_INTEGER) {
-    sqlite3_result_error(context, kSplitFieldIndexError, -1);
+    sqlite::result::Error(context, kSplitFieldIndexError);
     return;
   }
   int fld = sqlite3_value_int(argv[2]);
   if (fld < 0) {
-    sqlite3_result_error(context, kSplitFieldIndexError, -1);
+    sqlite::result::Error(context, kSplitFieldIndexError);
     return;
   }
   if (sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
-    sqlite3_result_null(context);
+    sqlite::result::Null(context);
     return;
   }
   const char* in = reinterpret_cast<const char*>(sqlite3_value_text(argv[0]));
@@ -62,7 +62,8 @@
     if (fld == 0) {
       int size = next != nullptr ? static_cast<int>(next - in)
                                  : static_cast<int>(strlen(in));
-      sqlite3_result_text(context, in, size, sqlite_utils::kSqliteTransient);
+      sqlite::result::RawString(context, in, size,
+                                sqlite::utils::kSqliteTransient);
       return;
     } else if (next == nullptr) {
       break;
@@ -70,7 +71,7 @@
     in = next + delimiter_len;
     --fld;
   } while (fld >= 0);
-  sqlite3_result_null(context);
+  sqlite::result::Null(context);
 }
 }  // namespace
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/stack_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/stack_functions.cc
index 3fa7bd0..1babe3d 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/stack_functions.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/stack_functions.cc
@@ -87,7 +87,7 @@
     // Stack expects the opposite, thus iterates the args in reverse order.
     for (size_t i = argc; i > 0; --i) {
       size_t arg_index = i - 1;
-      SqlValue value = sqlite_utils::SqliteValueToSqlValue(argv[arg_index]);
+      SqlValue value = sqlite::utils::SqliteValueToSqlValue(argv[arg_index]);
       switch (value.type) {
         case SqlValue::kBytes: {
           stack->AppendRawProtoBytes(value.bytes_value, value.bytes_count);
@@ -101,7 +101,7 @@
           break;
         case SqlValue::kLong:
         case SqlValue::kDouble:
-          return sqlite_utils::InvalidArgumentTypeError(
+          return sqlite::utils::InvalidArgumentTypeError(
               "entry", arg_index, value.type, SqlValue::kBytes,
               SqlValue::kString, SqlValue::kNull);
       }
@@ -148,7 +148,7 @@
           kFunctionName, argc);
     }
 
-    base::StatusOr<SqlValue> value = sqlite_utils::ExtractArgument(
+    base::StatusOr<SqlValue> value = sqlite::utils::ExtractArgument(
         argc, argv, "callsite_id", 0, SqlValue::kNull, SqlValue::kLong);
     if (!value.ok()) {
       return value.status();
@@ -162,7 +162,7 @@
              .FindById(tables::StackProfileCallsiteTable::Id(
                  static_cast<uint32_t>(value->AsLong())))
              .has_value()) {
-      return sqlite_utils::ToInvalidArgumentError(
+      return sqlite::utils::ToInvalidArgumentError(
           "callsite_id", 0,
           base::ErrStatus("callsite_id does not exist: %" PRId64,
                           value->AsLong()));
@@ -172,8 +172,8 @@
 
     bool annotate = false;
     if (argc == 2) {
-      value = sqlite_utils::ExtractArgument(argc, argv, "annotate", 1,
-                                            SqlValue::Type::kLong);
+      value = sqlite::utils::ExtractArgument(argc, argv, "annotate", 1,
+                                             SqlValue::Type::kLong);
       if (!value.ok()) {
         return value.status();
       }
@@ -215,7 +215,7 @@
                               sqlite3_value** argv,
                               SqlValue& out,
                               Destructors& destructors) {
-    base::StatusOr<SqlValue> value = sqlite_utils::ExtractArgument(
+    base::StatusOr<SqlValue> value = sqlite::utils::ExtractArgument(
         argc, argv, "frame_id", 0, SqlValue::kNull, SqlValue::kLong);
 
     if (!value.ok()) {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h b/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h
index 57bd8dc..cc502ff 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h
@@ -230,13 +230,13 @@
   }
 
   base::Status status =
-      sqlite_utils::TypeCheckSqliteValue(argv[0], SqlValue::kString);
+      sqlite::utils::TypeCheckSqliteValue(argv[0], SqlValue::kString);
   if (!status.ok()) {
     return base::ErrStatus("WRITE_FILE: argument 1, filename; %s",
                            status.c_message());
   }
 
-  status = sqlite_utils::TypeCheckSqliteValue(argv[1], SqlValue::kBytes);
+  status = sqlite::utils::TypeCheckSqliteValue(argv[1], SqlValue::kBytes);
   if (!status.ok()) {
     return base::ErrStatus("WRITE_FILE: argument 2, content; %s",
                            status.c_message());
@@ -305,7 +305,7 @@
 
   // This function always returns static strings (i.e. scoped to lifetime
   // of the TraceStorage thread pool) so prevent SQLite from making copies.
-  destructors.string_destructor = sqlite_utils::kSqliteStatic;
+  destructors.string_destructor = sqlite::utils::kSqliteStatic;
 
   switch (opt_value->type) {
     case Variadic::kNull:
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h b/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h
index 3b90896..2da34fc 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h
@@ -18,18 +18,14 @@
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_WINDOW_FUNCTIONS_H_
 
 #include <sqlite3.h>
-#include <unordered_map>
-#include "perfetto/ext/base/base64.h"
-#include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/trace_processor/demangle.h"
-#include "protos/perfetto/common/builtin_clock.pbzero.h"
-#include "src/trace_processor/export_json.h"
-#include "src/trace_processor/importers/common/clock_tracker.h"
-#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
-#include "src/trace_processor/util/status_macros.h"
+#include <cstdint>
+#include <type_traits>
 
-namespace perfetto {
-namespace trace_processor {
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/sqlite/sqlite_result.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+namespace perfetto::trace_processor {
 // Keeps track of the latest non null value and its position withing the
 // window. Every time the window shrinks (`xInverse` is called) the window size
 // is reduced by one and the position of the value moves one back, if it gets
@@ -89,10 +85,10 @@
   sqlite3_value* last_non_null_value_;
 };
 
-static_assert(std::is_standard_layout<LastNonNullAggregateContext>::value,
+static_assert(std::is_standard_layout_v<LastNonNullAggregateContext>,
               "Must be able to be initialized by sqlite3_aggregate_context "
               "(similar to calloc, i.e. no constructor called)");
-static_assert(std::is_trivial<LastNonNullAggregateContext>::value,
+static_assert(std::is_trivial_v<LastNonNullAggregateContext>,
               "Must be able to be destroyed by just calling free (i.e. no "
               "destructor called)");
 
@@ -100,15 +96,14 @@
                             int argc,
                             sqlite3_value** argv) {
   if (argc != 1) {
-    sqlite3_result_error(
-        ctx, "Unsupported number of args passed to LAST_NON_NULL", -1);
-    return;
+    return sqlite::result::Error(
+        ctx, "Unsupported number of args passed to LAST_NON_NULL");
   }
 
   auto* ptr = LastNonNullAggregateContext::GetOrCreate(ctx);
   if (!ptr) {
-    sqlite3_result_error(ctx, "LAST_NON_NULL: Failed to allocate context", -1);
-    return;
+    return sqlite::result::Error(ctx,
+                                 "LAST_NON_NULL: Failed to allocate context");
   }
 
   ptr->PushBack(argv[0]);
@@ -123,20 +118,18 @@
 inline void LastNonNullValue(sqlite3_context* ctx) {
   auto* ptr = LastNonNullAggregateContext::GetOrCreate(ctx);
   if (!ptr || !ptr->last_non_null_value()) {
-    sqlite3_result_null(ctx);
-  } else {
-    sqlite3_result_value(ctx, ptr->last_non_null_value());
+    return sqlite::result::Null(ctx);
   }
+  sqlite::result::Value(ctx, ptr->last_non_null_value());
 }
 
 inline void LastNonNullFinal(sqlite3_context* ctx) {
   auto* ptr = LastNonNullAggregateContext::Get(ctx);
   if (!ptr || !ptr->last_non_null_value()) {
-    sqlite3_result_null(ctx);
-  } else {
-    sqlite3_result_value(ctx, ptr->last_non_null_value());
-    ptr->Destroy();
+    return sqlite::result::Null(ctx);
   }
+  sqlite::result::Value(ctx, ptr->last_non_null_value());
+  ptr->Destroy();
 }
 
 inline void RegisterLastNonNullFunction(sqlite3* db) {
@@ -148,7 +141,6 @@
     PERFETTO_ELOG("Error initializing LAST_NON_NULL");
   }
 }
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_WINDOW_FUNCTIONS_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
index a1d9630..6fe221d 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
@@ -274,7 +274,7 @@
                                         void**) {
   if (base::CaseInsensitiveEqual(name, "source_geq")) {
     *fn = [](sqlite3_context* ctx, int, sqlite3_value**) {
-      sqlite3_result_error(ctx, "Should not be called.", -1);
+      return sqlite::result::Error(ctx, "Should not be called.");
     };
     return kSourceGeqOpCode;
   }
@@ -297,7 +297,7 @@
     // affect the span join computation. Similarily, source_geq constraints
     // explicitly request that they are passed as geq constraints to the source
     // tables.
-    if (col_name == kTsColumnName && !sqlite_utils::IsOpLe(cs.op) &&
+    if (col_name == kTsColumnName && !sqlite::utils::IsOpLe(cs.op) &&
         cs.op != kSourceGeqOpCode)
       continue;
 
@@ -332,7 +332,7 @@
   }
 
   std::vector<SqliteTable::Column> cols;
-  RETURN_IF_ERROR(sqlite_utils::GetColumnsForTable(
+  RETURN_IF_ERROR(sqlite::utils::GetColumnsForTable(
       engine_->sqlite_engine()->db(), desc.name, cols));
 
   uint32_t required_columns_found = 0;
@@ -558,14 +558,14 @@
   switch (N) {
     case Column::kTimestamp: {
       auto max_ts = std::max(t1_.ts(), t2_.ts());
-      sqlite3_result_int64(context, static_cast<sqlite3_int64>(max_ts));
+      sqlite::result::Long(context, static_cast<sqlite3_int64>(max_ts));
       break;
     }
     case Column::kDuration: {
       auto max_start = std::max(t1_.ts(), t2_.ts());
       auto min_end = std::min(t1_.raw_ts_end(), t2_.raw_ts_end());
       auto dur = min_end - max_start;
-      sqlite3_result_int64(context, static_cast<sqlite3_int64>(dur));
+      sqlite::result::Long(context, static_cast<sqlite3_int64>(dur));
       break;
     }
     case Column::kPartition: {
@@ -576,7 +576,7 @@
         } else {
           partition = t1_.IsReal() ? t1_.partition() : t2_.partition();
         }
-        sqlite3_result_int64(context, static_cast<sqlite3_int64>(partition));
+        sqlite::result::Long(context, static_cast<sqlite3_int64>(partition));
         break;
       }
       PERFETTO_FALLTHROUGH;
@@ -803,33 +803,29 @@
 
 void SpanJoinOperatorTable::Query::ReportSqliteResult(sqlite3_context* context,
                                                       size_t index) {
-  const auto kSqliteTransient = reinterpret_cast<sqlite3_destructor_type>(-1);
   if (state_ != State::kReal) {
-    sqlite3_result_null(context);
-    return;
+    return sqlite::result::Null(context);
   }
 
   sqlite3_stmt* stmt = stmt_->sqlite_stmt();
   int idx = static_cast<int>(index);
   switch (sqlite3_column_type(stmt, idx)) {
     case SQLITE_INTEGER:
-      sqlite3_result_int64(context, sqlite3_column_int64(stmt, idx));
-      break;
+      return sqlite::result::Long(context, sqlite3_column_int64(stmt, idx));
     case SQLITE_FLOAT:
-      sqlite3_result_double(context, sqlite3_column_double(stmt, idx));
-      break;
+      return sqlite::result::Double(context, sqlite3_column_double(stmt, idx));
     case SQLITE_TEXT: {
       // TODO(lalitm): note for future optimizations: if we knew the addresses
       // of the string intern pool, we could check if the string returned here
       // comes from the pool, and pass it as non-transient.
-      auto ptr = reinterpret_cast<const char*>(sqlite3_column_text(stmt, idx));
-      sqlite3_result_text(context, ptr, -1, kSqliteTransient);
-      break;
+      const auto* ptr =
+          reinterpret_cast<const char*>(sqlite3_column_text(stmt, idx));
+      return sqlite::result::TransientString(context, ptr);
     }
     case SQLITE_BLOB: {
-      sqlite3_result_blob(context, sqlite3_column_blob(stmt, idx),
-                          sqlite3_column_bytes(stmt, idx), kSqliteTransient);
-      break;
+      return sqlite::result::TransientBytes(context,
+                                            sqlite3_column_blob(stmt, idx),
+                                            sqlite3_column_bytes(stmt, idx));
     }
   }
 }
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc
index ba591a9..41c2841 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc
@@ -17,15 +17,12 @@
 #include "src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h"
 
 #include "perfetto/base/status.h"
+#include "src/trace_processor/sqlite/sqlite_result.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-namespace {
-using namespace sqlite_utils;
-}  // namespace
-
 WindowOperatorTable::WindowOperatorTable(sqlite3*, const TraceStorage*) {}
 WindowOperatorTable::~WindowOperatorTable() = default;
 
@@ -114,7 +111,7 @@
   // return the first row.
   bool return_first = qc.constraints().size() == 1 &&
                       qc.constraints()[0].column == Column::kRowId &&
-                      IsOpEq(qc.constraints()[0].op) &&
+                      sqlite::utils::IsOpEq(qc.constraints()[0].op) &&
                       sqlite3_value_int(argv[0]) == 0;
   if (return_first) {
     filter_type_ = FilterType::kReturnFirst;
@@ -128,33 +125,33 @@
                                                  int N) {
   switch (N) {
     case Column::kQuantum: {
-      sqlite3_result_int64(context,
+      sqlite::result::Long(context,
                            static_cast<sqlite_int64>(table_->quantum_));
       break;
     }
     case Column::kWindowStart: {
-      sqlite3_result_int64(context,
+      sqlite::result::Long(context,
                            static_cast<sqlite_int64>(table_->window_start_));
       break;
     }
     case Column::kWindowDur: {
-      sqlite3_result_int(context, static_cast<int>(table_->window_dur_));
+      sqlite::result::Long(context, static_cast<int>(table_->window_dur_));
       break;
     }
     case Column::kTs: {
-      sqlite3_result_int64(context, static_cast<sqlite_int64>(current_ts_));
+      sqlite::result::Long(context, static_cast<sqlite_int64>(current_ts_));
       break;
     }
     case Column::kDuration: {
-      sqlite3_result_int64(context, static_cast<sqlite_int64>(step_size_));
+      sqlite::result::Long(context, static_cast<sqlite_int64>(step_size_));
       break;
     }
     case Column::kQuantumTs: {
-      sqlite3_result_int64(context, static_cast<sqlite_int64>(quantum_ts_));
+      sqlite::result::Long(context, static_cast<sqlite_int64>(quantum_ts_));
       break;
     }
     case Column::kRowId: {
-      sqlite3_result_int64(context, static_cast<sqlite_int64>(row_id_));
+      sqlite::result::Long(context, static_cast<sqlite_int64>(row_id_));
       break;
     }
     default: {
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index e16e591..37682fa 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -28,6 +28,7 @@
     "sql_stats_table.h",
     "sqlite_engine.cc",
     "sqlite_engine.h",
+    "sqlite_result.h",
     "sqlite_table.cc",
     "sqlite_table.h",
     "sqlite_tokenizer.cc",
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 8d18365..b0b6678 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -319,7 +319,7 @@
   {
     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 c.column == o.iColumn && sqlite::utils::IsOpEq(c.op);
       };
       return std::any_of(cs->begin(), cs->end(), inner_p);
     };
@@ -373,7 +373,7 @@
     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 (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
@@ -382,7 +382,7 @@
       // 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)) {
+    } 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
@@ -400,8 +400,8 @@
       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))) {
+               (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.
@@ -506,7 +506,7 @@
   // 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))
+  if (!sqlite::utils::IsOpEq(c.op))
     return;
 
   // If the column is already sorted, we don't need to cache at all.
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 45bc124..82993ca 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -128,8 +128,8 @@
       // long as we don't call Next(). However, that only happens when Next() is
       // called on the Cursor itself, at which point SQLite no longer cares
       // about the bytes pointer.
-      sqlite_utils::ReportSqlValue(ctx, value, sqlite_utils::kSqliteStatic,
-                                   sqlite_utils::kSqliteStatic);
+      sqlite::utils::ReportSqlValue(ctx, value, sqlite::utils::kSqliteStatic,
+                                    sqlite::utils::kSqliteStatic);
     }
 
    private:
diff --git a/src/trace_processor/sqlite/sql_stats_table.cc b/src/trace_processor/sqlite/sql_stats_table.cc
index e6cdf87..fb1d59c 100644
--- a/src/trace_processor/sqlite/sql_stats_table.cc
+++ b/src/trace_processor/sqlite/sql_stats_table.cc
@@ -83,17 +83,16 @@
   const TraceStorage::SqlStats& stats = storage_->sql_stats();
   switch (col) {
     case Column::kQuery:
-      sqlite3_result_text(context, stats.queries()[row_].c_str(), -1,
-                          sqlite_utils::kSqliteStatic);
+      sqlite::result::StaticString(context, stats.queries()[row_].c_str());
       break;
     case Column::kTimeStarted:
-      sqlite3_result_int64(context, stats.times_started()[row_]);
+      sqlite::result::Long(context, stats.times_started()[row_]);
       break;
     case Column::kTimeFirstNext:
-      sqlite3_result_int64(context, stats.times_first_next()[row_]);
+      sqlite::result::Long(context, stats.times_first_next()[row_]);
       break;
     case Column::kTimeEnded:
-      sqlite3_result_int64(context, stats.times_ended()[row_]);
+      sqlite::result::Long(context, stats.times_ended()[row_]);
       break;
   }
   return base::OkStatus();
diff --git a/src/trace_processor/sqlite/sqlite_result.h b/src/trace_processor/sqlite/sqlite_result.h
new file mode 100644
index 0000000..4030043
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_result.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RESULT_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RESULT_H_
+
+#include <sqlite3.h>
+#include <cstdint>
+
+namespace perfetto::trace_processor::sqlite::result {
+
+// This file contains wraps the sqlite3_result_* functions which tell SQLite
+// about the result of executing a function or calling a xColumn on a virtual
+// table.
+
+const auto kSqliteStatic = reinterpret_cast<sqlite3_destructor_type>(0);
+const auto kSqliteTransient = reinterpret_cast<sqlite3_destructor_type>(-1);
+
+inline void Null(sqlite3_context* ctx) {
+  sqlite3_result_null(ctx);
+}
+
+inline void Long(sqlite3_context* ctx, int64_t res) {
+  sqlite3_result_int64(ctx, res);
+}
+
+inline void Double(sqlite3_context* ctx, double res) {
+  sqlite3_result_double(ctx, res);
+}
+
+inline void RawString(sqlite3_context* ctx,
+                      const char* str,
+                      int size,
+                      sqlite3_destructor_type destructor) {
+  sqlite3_result_text(ctx, str, size, destructor);
+}
+inline void RawString(sqlite3_context* ctx,
+                      const char* str,
+                      sqlite3_destructor_type destructor) {
+  RawString(ctx, str, -1, destructor);
+}
+inline void StaticString(sqlite3_context* ctx, const char* str) {
+  RawString(ctx, str, kSqliteStatic);
+}
+inline void TransientString(sqlite3_context* ctx, const char* str) {
+  RawString(ctx, str, kSqliteTransient);
+}
+
+inline void RawBytes(sqlite3_context* ctx,
+                     const void* bytes,
+                     int size,
+                     sqlite3_destructor_type destructor) {
+  sqlite3_result_blob(ctx, bytes, size, destructor);
+}
+inline void StaticBytes(sqlite3_context* ctx, const void* bytes, int size) {
+  RawBytes(ctx, bytes, size, kSqliteStatic);
+}
+inline void TransientBytes(sqlite3_context* ctx, const void* bytes, int size) {
+  RawBytes(ctx, bytes, size, kSqliteTransient);
+}
+
+inline void Error(sqlite3_context* ctx, const char* error) {
+  sqlite3_result_error(ctx, error, -1);
+}
+
+inline void Value(sqlite3_context* ctx, sqlite3_value* value) {
+  sqlite3_result_value(ctx, value);
+}
+
+inline void RawPointer(sqlite3_context* ctx,
+                       void* ptr,
+                       const char* name,
+                       sqlite3_destructor_type destructor) {
+  sqlite3_result_pointer(ctx, ptr, name, destructor);
+}
+inline void StaticPointer(sqlite3_context* ctx, void* ptr, const char* name) {
+  RawPointer(ctx, ptr, name, nullptr);
+}
+
+}  // namespace perfetto::trace_processor::sqlite::result
+
+#endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RESULT_H_
diff --git a/src/trace_processor/sqlite/sqlite_utils.cc b/src/trace_processor/sqlite/sqlite_utils.cc
index 4165f2d..04ddf9c 100644
--- a/src/trace_processor/sqlite/sqlite_utils.cc
+++ b/src/trace_processor/sqlite/sqlite_utils.cc
@@ -15,15 +15,24 @@
  */
 
 #include "src/trace_processor/sqlite/sqlite_utils.h"
-#include <bitset>
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
 #include <sstream>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_table.h"
 
-namespace perfetto {
-namespace trace_processor {
-namespace sqlite_utils {
+namespace perfetto::trace_processor::sqlite::utils {
 namespace internal {
 namespace {
 std::string ToExpectedTypesString(ExpectedTypesSet expected_types) {
@@ -66,14 +75,12 @@
     return MissingArgumentError(argument_name);
   }
 
-  SqlValue value = sqlite_utils::SqliteValueToSqlValue(argv[arg_index]);
-
+  SqlValue value = sqlite::utils::SqliteValueToSqlValue(argv[arg_index]);
   if (!expected_types.test(value.type)) {
     return InvalidArgumentTypeError(argument_name, arg_index, value.type,
                                     expected_types);
   }
-
-  return std::move(value);
+  return value;
 }
 }  // namespace internal
 
@@ -82,8 +89,7 @@
   int len = sqlite3_value_bytes16(value);
   PERFETTO_CHECK(len >= 0);
   size_t count = static_cast<size_t>(len) / sizeof(wchar_t);
-  return std::wstring(
-      reinterpret_cast<const wchar_t*>(sqlite3_value_text16(value)), count);
+  return {reinterpret_cast<const wchar_t*>(sqlite3_value_text16(value)), count};
 }
 
 base::Status GetColumnsForTable(sqlite3* db,
@@ -244,7 +250,7 @@
                                   SqlValue::Type expected_type,
                                   const char* expected_type_str) {
   SqlValue::Type actual_type =
-      sqlite_utils::SqliteTypeToSqlValueType(sqlite3_value_type(value));
+      sqlite::utils::SqliteTypeToSqlValueType(sqlite3_value_type(value));
   if (actual_type != SqlValue::Type::kNull && actual_type != expected_type) {
     return base::ErrStatus(
         "does not have expected type: expected %s, actual %s",
@@ -328,11 +334,9 @@
 
 base::Status ToInvalidArgumentError(const char* argument_name,
                                     size_t arg_index,
-                                    const base::Status error) {
+                                    const base::Status& error) {
   return base::ErrStatus("argument %s at pos %zu: %s", argument_name,
                          arg_index + 1, error.message().c_str());
 }
 
-}  // namespace sqlite_utils
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor::sqlite::utils
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 7ca61b2..7cdb1d1 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -17,27 +17,23 @@
 #ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_UTILS_H_
 #define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_UTILS_H_
 
-#include <math.h>
 #include <sqlite3.h>
 #include <bitset>
 #include <cstddef>
+#include <cstdint>
 #include <cstring>
 #include <optional>
 #include <string>
-#include <utility>
+#include <vector>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/status_or.h"
-#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/string_view.h"
 #include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_result.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 
-namespace perfetto {
-namespace trace_processor {
-namespace sqlite_utils {
+namespace perfetto::trace_processor::sqlite::utils {
 
 const auto kSqliteStatic = reinterpret_cast<sqlite3_destructor_type>(0);
 const auto kSqliteTransient = reinterpret_cast<sqlite3_destructor_type>(-1);
@@ -121,36 +117,36 @@
     sqlite3_destructor_type bytes_destructor = kSqliteTransient) {
   switch (value.type) {
     case SqlValue::Type::kLong:
-      sqlite3_result_int64(ctx, value.long_value);
+      sqlite::result::Long(ctx, value.long_value);
       break;
     case SqlValue::Type::kDouble:
-      sqlite3_result_double(ctx, value.double_value);
+      sqlite::result::Double(ctx, value.double_value);
       break;
     case SqlValue::Type::kString: {
-      sqlite3_result_text(ctx, value.string_value, -1, string_destructor);
+      sqlite::result::RawString(ctx, value.string_value, string_destructor);
       break;
     }
     case SqlValue::Type::kBytes:
-      sqlite3_result_blob(ctx, value.bytes_value,
-                          static_cast<int>(value.bytes_count),
-                          bytes_destructor);
+      sqlite::result::RawBytes(ctx, value.bytes_value,
+                               static_cast<int>(value.bytes_count),
+                               bytes_destructor);
       break;
     case SqlValue::Type::kNull:
-      sqlite3_result_null(ctx);
+      sqlite::result::Null(ctx);
       break;
   }
 }
 
-inline void SetSqliteError(sqlite3_context* ctx, const base::Status& status) {
+inline void SetError(sqlite3_context* ctx, const base::Status& status) {
   PERFETTO_CHECK(!status.ok());
-  sqlite3_result_error(ctx, status.c_message(), -1);
+  sqlite::result::Error(ctx, status.c_message());
 }
 
-inline void SetSqliteError(sqlite3_context* ctx,
-                           const std::string& function_name,
-                           const base::Status& status) {
-  SetSqliteError(ctx, base::ErrStatus("%s: %s", function_name.c_str(),
-                                      status.c_message()));
+inline void SetError(sqlite3_context* ctx,
+                     const std::string& function_name,
+                     const base::Status& status) {
+  SetError(ctx, base::ErrStatus("%s: %s", function_name.c_str(),
+                                status.c_message()));
 }
 
 // Exracts the given type from the SqlValue if |value| can fit
@@ -250,7 +246,7 @@
 
 base::Status ToInvalidArgumentError(const char* argument_name,
                                     size_t arg_index,
-                                    const base::Status error);
+                                    const base::Status& error);
 
 template <typename... args>
 base::StatusOr<SqlValue> ExtractArgument(size_t argc,
@@ -264,8 +260,6 @@
       internal::ToExpectedTypesSet(expected_type, expected_type_args...));
 }
 
-}  // namespace sqlite_utils
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor::sqlite::utils
 
 #endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_UTILS_H_
diff --git a/src/trace_processor/sqlite/sqlite_utils_unittest.cc b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
index be28af1..4347317 100644
--- a/src/trace_processor/sqlite/sqlite_utils_unittest.cc
+++ b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
@@ -16,11 +16,10 @@
 
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 
+#include "src/trace_processor/sqlite/scoped_db.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
-namespace sqlite_utils {
+namespace perfetto::trace_processor::sqlite::utils {
 
 namespace {
 
@@ -54,7 +53,7 @@
 TEST_F(GetColumnsForTableTest, ValidInput) {
   RunStatement("CREATE TABLE foo (name STRING, ts INT, dur INT);");
   std::vector<SqliteTable::Column> columns;
-  auto status = sqlite_utils::GetColumnsForTable(*db_, "foo", columns);
+  auto status = sqlite::utils::GetColumnsForTable(*db_, "foo", columns);
   ASSERT_TRUE(status.ok());
 }
 
@@ -64,13 +63,14 @@
   // crashing.
   RunStatement("CREATE TABLE foo (name NUM, ts INT, dur INT);");
   std::vector<SqliteTable::Column> columns;
-  auto status = sqlite_utils::GetColumnsForTable(*db_, "foo", columns);
+  auto status = sqlite::utils::GetColumnsForTable(*db_, "foo", columns);
   ASSERT_FALSE(status.ok());
 }
 
 TEST_F(GetColumnsForTableTest, UnknownTableName) {
   std::vector<SqliteTable::Column> columns;
-  auto status = sqlite_utils::GetColumnsForTable(*db_, "unknowntable", columns);
+  auto status =
+      sqlite::utils::GetColumnsForTable(*db_, "unknowntable", columns);
   ASSERT_FALSE(status.ok());
 }
 
@@ -178,6 +178,4 @@
 }
 
 }  // namespace
-}  // namespace sqlite_utils
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor::sqlite::utils
diff --git a/src/trace_processor/sqlite/sqlite_vtable_benchmark.cc b/src/trace_processor/sqlite/sqlite_vtable_benchmark.cc
index f231abf..c2ca5d6 100644
--- a/src/trace_processor/sqlite/sqlite_vtable_benchmark.cc
+++ b/src/trace_processor/sqlite/sqlite_vtable_benchmark.cc
@@ -19,13 +19,18 @@
 // in a buffer. This is to have a fair estimate w.r.t. cache-misses and pointer
 // chasing of what an upper-bound can be for a virtual table implementation.
 
-#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
 #include <random>
+#include <string>
+#include <vector>
 
 #include <benchmark/benchmark.h>
 #include <sqlite3.h>
 
 #include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 
 namespace {
@@ -76,7 +81,7 @@
   }
   PERFETTO_NO_INLINE int Next();
   PERFETTO_NO_INLINE int Column(sqlite3_context* ctx, int);
-  PERFETTO_NO_INLINE int Eof();
+  PERFETTO_NO_INLINE int Eof() const;
   void RandomFill();
 
  private:
@@ -113,7 +118,7 @@
   return SQLITE_OK;
 }
 
-int BenchmarkCursor::Eof() {
+int BenchmarkCursor::Eof() const {
   return eof_;
 }
 
@@ -202,9 +207,9 @@
   return db;
 }
 
-static void BM_SqliteStepAndResult(benchmark::State& state) {
-  size_t batch_size = static_cast<size_t>(state.range(0));
-  size_t num_cols = static_cast<size_t>(state.range(1));
+void BM_SqliteStepAndResult(benchmark::State& state) {
+  auto batch_size = static_cast<size_t>(state.range(0));
+  auto num_cols = static_cast<size_t>(state.range(1));
 
   // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in
   // the database close function and so this struct needs to be available then.
@@ -236,8 +241,8 @@
 
 BENCHMARK(BM_SqliteStepAndResult)->Apply(BenchmarkArgs);
 
-static void BM_SqliteCountOne(benchmark::State& state) {
-  size_t batch_size = static_cast<size_t>(state.range(0));
+void BM_SqliteCountOne(benchmark::State& state) {
+  auto batch_size = static_cast<size_t>(state.range(0));
 
   // Make sure the module outlives the ScopedDb. SQLite calls xDisconnect in
   // the database close function and so this struct needs to be available then.
diff --git a/src/trace_processor/sqlite/stats_table.cc b/src/trace_processor/sqlite/stats_table.cc
index 296a2f5..d93057d 100644
--- a/src/trace_processor/sqlite/stats_table.cc
+++ b/src/trace_processor/sqlite/stats_table.cc
@@ -16,18 +16,25 @@
 
 #include "src/trace_processor/sqlite/stats_table.h"
 
-#include "perfetto/base/status.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include <memory>
 
-namespace perfetto {
-namespace trace_processor {
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/sqlite/query_constraints.h"
+#include "src/trace_processor/sqlite/sqlite_result.h"
+#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto::trace_processor {
 
 StatsTable::StatsTable(sqlite3*, const TraceStorage* storage)
     : storage_(storage) {}
 
 StatsTable::~StatsTable() = default;
 
-util::Status StatsTable::Init(int, const char* const*, Schema* schema) {
+base::Status StatsTable::Init(int, const char* const*, Schema* schema) {
   *schema = Schema(
       {
           SqliteTable::Column(Column::kName, "name", SqlValue::Type::kString),
@@ -42,7 +49,7 @@
                               SqlValue::Type::kString),
       },
       {Column::kName});
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 std::unique_ptr<SqliteTable::BaseCursor> StatsTable::CreateCursor() {
@@ -68,50 +75,49 @@
 }
 
 base::Status StatsTable::Cursor::Column(sqlite3_context* ctx, int N) {
-  const auto kSqliteStatic = sqlite_utils::kSqliteStatic;
   switch (N) {
     case Column::kName:
-      sqlite3_result_text(ctx, stats::kNames[key_], -1, kSqliteStatic);
+      sqlite::result::StaticString(ctx, stats::kNames[key_]);
       break;
     case Column::kIndex:
       if (stats::kTypes[key_] == stats::kIndexed) {
-        sqlite3_result_int(ctx, index_->first);
+        sqlite::result::Long(ctx, index_->first);
       } else {
-        sqlite3_result_null(ctx);
+        sqlite::result::Null(ctx);
       }
       break;
     case Column::kSeverity:
       switch (stats::kSeverities[key_]) {
         case stats::kInfo:
-          sqlite3_result_text(ctx, "info", -1, kSqliteStatic);
+          sqlite::result::StaticString(ctx, "info");
           break;
         case stats::kDataLoss:
-          sqlite3_result_text(ctx, "data_loss", -1, kSqliteStatic);
+          sqlite::result::StaticString(ctx, "data_loss");
           break;
         case stats::kError:
-          sqlite3_result_text(ctx, "error", -1, kSqliteStatic);
+          sqlite::result::StaticString(ctx, "error");
           break;
       }
       break;
     case Column::kSource:
       switch (stats::kSources[key_]) {
         case stats::kTrace:
-          sqlite3_result_text(ctx, "trace", -1, kSqliteStatic);
+          sqlite::result::StaticString(ctx, "trace");
           break;
         case stats::kAnalysis:
-          sqlite3_result_text(ctx, "analysis", -1, kSqliteStatic);
+          sqlite::result::StaticString(ctx, "analysis");
           break;
       }
       break;
     case Column::kValue:
       if (stats::kTypes[key_] == stats::kIndexed) {
-        sqlite3_result_int64(ctx, index_->second);
+        sqlite::result::Long(ctx, index_->second);
       } else {
-        sqlite3_result_int64(ctx, storage_->stats()[key_].value);
+        sqlite::result::Long(ctx, storage_->stats()[key_].value);
       }
       break;
     case Column::kDescription:
-      sqlite3_result_text(ctx, stats::kDescriptions[key_], -1, kSqliteStatic);
+      sqlite::result::StaticString(ctx, stats::kDescriptions[key_]);
       break;
     default:
       PERFETTO_FATAL("Unknown column %d", N);
@@ -144,5 +150,4 @@
   return key_ >= stats::kNumKeys;
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index d0d335f..20bee47 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -17,20 +17,32 @@
 #include "src/trace_processor/trace_processor_impl.h"
 
 #include <algorithm>
+#include <chrono>
+#include <cinttypes>
 #include <cstddef>
 #include <cstdint>
+#include <limits>
 #include <memory>
 #include <string>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/small_vector.h"
+#include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/iterator.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "perfetto/trace_processor/trace_processor.h"
+#include "sqlite/sqlite_utils.h"
 #include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
@@ -88,10 +100,14 @@
 #include "src/trace_processor/sqlite/sql_stats_table.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/sqlite/stats_table.h"
+#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/trace_processor_storage_impl.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/types/variadic.h"
 #include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/gzip_utils.h"
 #include "src/trace_processor/util/protozero_to_json.h"
 #include "src/trace_processor/util/protozero_to_text.h"
 #include "src/trace_processor/util/regex.h"
@@ -103,6 +119,7 @@
 #include "protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
 
 namespace perfetto::trace_processor {
 namespace {
@@ -130,8 +147,8 @@
     std::replace(fn_name.begin(), fn_name.end(), '.', '_');
     RegisterFunction<metrics::BuildProto>(
         engine, fn_name.c_str(), -1,
-        std::unique_ptr<metrics::BuildProto::Context>(
-            new metrics::BuildProto::Context{tp, pool, i}));
+        std::make_unique<metrics::BuildProto::Context>(
+            metrics::BuildProto::Context{tp, pool, i}));
   }
 }
 
@@ -169,24 +186,22 @@
 
   // Note that sqlite3_aggregate_context zeros the memory for us so all the
   // variables of the struct should be zero.
-  ValueAtMaxTsContext* fn_ctx = reinterpret_cast<ValueAtMaxTsContext*>(
+  auto* fn_ctx = reinterpret_cast<ValueAtMaxTsContext*>(
       sqlite3_aggregate_context(ctx, sizeof(ValueAtMaxTsContext)));
 
   // For performance reasons, we only do the check for the type of ts and value
   // on the first call of the function.
   if (PERFETTO_UNLIKELY(!fn_ctx->initialized)) {
     if (sqlite3_value_type(ts) != SQLITE_INTEGER) {
-      sqlite3_result_error(ctx, "VALUE_AT_MAX_TS: ts passed was not an integer",
-                           -1);
-      return;
+      return sqlite::result::Error(
+          ctx, "VALUE_AT_MAX_TS: ts passed was not an integer");
     }
 
     fn_ctx->value_type = sqlite3_value_type(value);
     if (fn_ctx->value_type != SQLITE_INTEGER &&
         fn_ctx->value_type != SQLITE_FLOAT) {
-      sqlite3_result_error(
-          ctx, "VALUE_AT_MAX_TS: value passed was not an integer or float", -1);
-      return;
+      return sqlite::result::Error(
+          ctx, "VALUE_AT_MAX_TS: value passed was not an integer or float");
     }
 
     fn_ctx->max_ts = std::numeric_limits<int64_t>::min();
@@ -196,14 +211,12 @@
   // On dcheck builds however, we check every passed ts and value.
 #if PERFETTO_DCHECK_IS_ON()
   if (sqlite3_value_type(ts) != SQLITE_INTEGER) {
-    sqlite3_result_error(ctx, "VALUE_AT_MAX_TS: ts passed was not an integer",
-                         -1);
-    return;
+    return sqlite::result::Error(
+        ctx, "VALUE_AT_MAX_TS: ts passed was not an integer");
   }
   if (sqlite3_value_type(value) != fn_ctx->value_type) {
-    sqlite3_result_error(ctx, "VALUE_AT_MAX_TS: value type is inconsistent",
-                         -1);
-    return;
+    return sqlite::result::Error(ctx,
+                                 "VALUE_AT_MAX_TS: value type is inconsistent");
   }
 #endif
 
@@ -223,13 +236,13 @@
   ValueAtMaxTsContext* fn_ctx =
       reinterpret_cast<ValueAtMaxTsContext*>(sqlite3_aggregate_context(ctx, 0));
   if (!fn_ctx) {
-    sqlite3_result_null(ctx);
+    sqlite::result::Null(ctx);
     return;
   }
   if (fn_ctx->value_type == SQLITE_INTEGER) {
-    sqlite3_result_int64(ctx, fn_ctx->int_value_at_max_ts);
+    sqlite::result::Long(ctx, fn_ctx->int_value_at_max_ts);
   } else {
-    sqlite3_result_double(ctx, fn_ctx->double_value_at_max_ts);
+    sqlite::result::Double(ctx, fn_ctx->double_value_at_max_ts);
   }
 }
 
@@ -318,27 +331,33 @@
 
 TraceProcessorImpl::TraceProcessorImpl(const Config& cfg)
     : TraceProcessorStorageImpl(cfg), config_(cfg) {
-  context_.fuchsia_trace_tokenizer.reset(new FuchsiaTraceTokenizer(&context_));
-  context_.fuchsia_trace_parser.reset(new FuchsiaTraceParser(&context_));
-  context_.ninja_log_parser.reset(new NinjaLogParser(&context_));
-  context_.systrace_trace_parser.reset(new SystraceTraceParser(&context_));
-  context_.perf_data_trace_tokenizer.reset(
-      new perf_importer::PerfDataTokenizer(&context_));
-  context_.perf_data_parser.reset(new perf_importer::PerfDataParser(&context_));
+  context_.fuchsia_trace_tokenizer =
+      std::make_unique<FuchsiaTraceTokenizer>(&context_);
+  context_.fuchsia_trace_parser =
+      std::make_unique<FuchsiaTraceParser>(&context_);
+  context_.ninja_log_parser = std::make_unique<NinjaLogParser>(&context_);
+  context_.systrace_trace_parser =
+      std::make_unique<SystraceTraceParser>(&context_);
+  context_.perf_data_trace_tokenizer =
+      std::make_unique<perf_importer::PerfDataTokenizer>(&context_);
+  context_.perf_data_parser =
+      std::make_unique<perf_importer::PerfDataParser>(&context_);
 
   if (util::IsGzipSupported()) {
-    context_.gzip_trace_parser.reset(new GzipTraceParser(&context_));
-    context_.android_bugreport_parser.reset(
-        new AndroidBugreportParser(&context_));
+    context_.gzip_trace_parser = std::make_unique<GzipTraceParser>(&context_);
+    context_.android_bugreport_parser =
+        std::make_unique<AndroidBugreportParser>(&context_);
   }
 
   if (json::IsJsonSupported()) {
-    context_.json_trace_tokenizer.reset(new JsonTraceTokenizer(&context_));
-    context_.json_trace_parser.reset(new JsonTraceParser(&context_));
+    context_.json_trace_tokenizer =
+        std::make_unique<JsonTraceTokenizer>(&context_);
+    context_.json_trace_parser = std::make_unique<JsonTraceParser>(&context_);
   }
 
   if (context_.config.analyze_trace_proto_content) {
-    context_.content_analyzer.reset(new ProtoContentAnalyzer(&context_));
+    context_.content_analyzer =
+        std::make_unique<ProtoContentAnalyzer>(&context_);
   }
 
   // Add metrics to descriptor pool
@@ -480,7 +499,8 @@
       pool_.FindDescriptorIdx(".perfetto.protos.TraceMetrics");
   if (!desc_idx.has_value())
     return false;
-  auto field_idx = pool_.descriptors()[*desc_idx].FindFieldByName(metric_name);
+  const auto* field_idx =
+      pool_.descriptors()[*desc_idx].FindFieldByName(metric_name);
   return field_idx != nullptr;
 }
 
@@ -661,10 +681,10 @@
                                         1, engine_.get());
   RegisterFunction<Import>(
       engine_.get(), "IMPORT", 1,
-      std::unique_ptr<Import::Context>(new Import::Context{engine_.get()}));
+      std::make_unique<Import::Context>(Import::Context{engine_.get()}));
   RegisterFunction<ToFtrace>(
       engine_.get(), "TO_FTRACE", 1,
-      std::unique_ptr<ToFtrace::Context>(new ToFtrace::Context{
+      std::make_unique<ToFtrace::Context>(ToFtrace::Context{
           context_.storage.get(), SystraceSerializer(&context_)}));
 
   if constexpr (regex::IsRegexSupported()) {
@@ -691,12 +711,12 @@
       PERFETTO_ELOG("%s", status.c_message());
   }
   {
-    base::Status status = RegisterMathFunctions(*engine_.get());
+    base::Status status = RegisterMathFunctions(*engine_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
   {
-    base::Status status = RegisterBase64Functions(*engine_.get());
+    base::Status status = RegisterBase64Functions(*engine_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
@@ -744,8 +764,8 @@
                                                "UNWRAP_METRIC_PROTO", 2);
   RegisterFunction<metrics::RunMetric>(
       engine_.get(), "RUN_METRIC", -1,
-      std::unique_ptr<metrics::RunMetric::Context>(
-          new metrics::RunMetric::Context{engine_.get(), &sql_metrics_}));
+      std::make_unique<metrics::RunMetric::Context>(
+          metrics::RunMetric::Context{engine_.get(), &sql_metrics_}));
 
   // Legacy tables.
   engine_->sqlite_engine()->RegisterVirtualTableModule<SqlStatsTable>(
@@ -855,42 +875,37 @@
   RegisterStaticTable(storage->experimental_missing_chrome_processes_table());
 
   // Tables dynamically generated at query time.
-  engine_->RegisterStaticTableFunction(std::unique_ptr<ExperimentalFlamegraph>(
-      new ExperimentalFlamegraph(&context_)));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<ExperimentalCounterDur>(
-      new ExperimentalCounterDur(storage->counter_table())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<ExperimentalSliceLayout>(
-      new ExperimentalSliceLayout(context_.storage.get()->mutable_string_pool(),
-                                  &storage->slice_table())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<TableInfo>(new TableInfo(
-      context_.storage.get()->mutable_string_pool(), engine_.get())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<Ancestor>(
-      new Ancestor(Ancestor::Type::kSlice, context_.storage.get())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<Ancestor>(new Ancestor(
-      Ancestor::Type::kStackProfileCallsite, context_.storage.get())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<Ancestor>(
-      new Ancestor(Ancestor::Type::kSliceByStack, context_.storage.get())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<Descendant>(
-      new Descendant(Descendant::Type::kSlice, context_.storage.get())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<Descendant>(
-      new Descendant(Descendant::Type::kSliceByStack, context_.storage.get())));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<ConnectedFlow>(
-      new ConnectedFlow(ConnectedFlow::Mode::kDirectlyConnectedFlow,
-                        context_.storage.get())));
   engine_->RegisterStaticTableFunction(
-      std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
-          ConnectedFlow::Mode::kPrecedingFlow, context_.storage.get())));
+      std::make_unique<ExperimentalFlamegraph>(&context_));
   engine_->RegisterStaticTableFunction(
-      std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
-          ConnectedFlow::Mode::kFollowingFlow, context_.storage.get())));
+      std::make_unique<ExperimentalCounterDur>(storage->counter_table()));
   engine_->RegisterStaticTableFunction(
-      std::unique_ptr<ExperimentalSchedUpid>(new ExperimentalSchedUpid(
-          storage->sched_slice_table(), storage->thread_table())));
+      std::make_unique<ExperimentalSliceLayout>(
+          context_.storage->mutable_string_pool(), &storage->slice_table()));
+  engine_->RegisterStaticTableFunction(std::make_unique<TableInfo>(
+      context_.storage->mutable_string_pool(), engine_.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<Ancestor>(
+      Ancestor::Type::kSlice, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<Ancestor>(
+      Ancestor::Type::kStackProfileCallsite, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<Ancestor>(
+      Ancestor::Type::kSliceByStack, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<Descendant>(
+      Descendant::Type::kSlice, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<Descendant>(
+      Descendant::Type::kSliceByStack, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<ConnectedFlow>(
+      ConnectedFlow::Mode::kDirectlyConnectedFlow, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<ConnectedFlow>(
+      ConnectedFlow::Mode::kPrecedingFlow, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<ConnectedFlow>(
+      ConnectedFlow::Mode::kFollowingFlow, context_.storage.get()));
+  engine_->RegisterStaticTableFunction(std::make_unique<ExperimentalSchedUpid>(
+      storage->sched_slice_table(), storage->thread_table()));
   engine_->RegisterStaticTableFunction(
-      std::unique_ptr<ExperimentalAnnotatedStack>(
-          new ExperimentalAnnotatedStack(&context_)));
-  engine_->RegisterStaticTableFunction(std::unique_ptr<ExperimentalFlatSlice>(
-      new ExperimentalFlatSlice(&context_)));
+      std::make_unique<ExperimentalAnnotatedStack>(&context_));
+  engine_->RegisterStaticTableFunction(
+      std::make_unique<ExperimentalFlatSlice>(&context_));
   engine_->RegisterStaticTableFunction(
       std::make_unique<DominatorTree>(context_.storage->mutable_string_pool()));
   engine_->RegisterStaticTableFunction(std::make_unique<IntervalIntersect>(
@@ -982,7 +997,7 @@
   base::FlatHashMap<std::string, uint64_t> interned_strings;
   metatrace::DisableAndReadBuffer([&trace, &interned_strings](
                                       metatrace::Record* record) {
-    auto packet = trace->add_packet();
+    auto* packet = trace->add_packet();
     packet->set_timestamp(record->timestamp_ns);
     auto* evt = packet->set_perfetto_metatrace();
 
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 4c08f23..249ceb9 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -29,7 +29,9 @@
 
 #include "perfetto/base/status.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
 #include "perfetto/trace_processor/trace_processor.h"
+#include "src/trace_processor/iterator_impl.h"
 #include "src/trace_processor/metrics/metrics.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h"