Merge "tp: migrate RuntimeTableFunction away from SqliteTable" into main
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 266245c..4d514ab 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -180,9 +180,10 @@
PERFETTO_FATAL("Failed to initialize perfetto_tables: %s", errmsg_raw);
}
- engine_->RegisterVirtualTableModule<RuntimeTableFunction>(
- "runtime_table_function", this,
- SqliteTableLegacy::TableType::kExplicitCreate, false);
+ auto ctx = std::make_unique<RuntimeTableFunctionModule::Context>();
+ runtime_table_fn_context_ = ctx.get();
+ engine_->RegisterVirtualTableModule<RuntimeTableFunctionModule>(
+ "runtime_table_function", std::move(ctx));
auto context = std::make_unique<DbSqliteTable::Context>(
[this](const std::string& name) {
auto* table = runtime_tables_.Find(name);
@@ -202,7 +203,6 @@
// Destroying the sqlite engine should also destroy all the created table
// functions.
engine_.reset();
- PERFETTO_CHECK(runtime_table_fn_states_.size() == 0);
PERFETTO_CHECK(runtime_tables_.size() == 0);
}
@@ -620,8 +620,9 @@
cf.sql);
}
- std::unique_ptr<RuntimeTableFunction::State> state(
- new RuntimeTableFunction::State{cf.sql, cf.prototype, {}, std::nullopt});
+ auto state = std::make_unique<RuntimeTableFunctionModule::State>(
+ RuntimeTableFunctionModule::State{
+ this, cf.sql, cf.prototype, {}, std::nullopt});
// Parse the return type into a enum format.
{
@@ -699,36 +700,44 @@
state->return_values[i].name().c_str());
}
}
- state->reusable_stmt = std::move(stmt);
+ state->temporary_create_stmt = std::move(stmt);
// TODO(lalitm): this suffers the same non-atomic DROP/CREATE problem as
// CREATE PERFETTO TABLE implementation above: see the comment there for
// more info on this.
- std::string fn_name = state->prototype.function_name;
- std::string lower_name = base::ToLower(state->prototype.function_name);
- if (runtime_table_fn_states_.Find(lower_name)) {
- if (!cf.replace) {
- return base::ErrStatus("Table function named %s already exists",
- state->prototype.function_name.c_str());
- }
- // This will cause |OnTableFunctionDestroyed| below to be executed.
- base::StackString<1024> drop("DROP TABLE %s",
+ if (cf.replace) {
+ base::StackString<1024> drop("DROP TABLE IF EXISTS %s",
state->prototype.function_name.c_str());
auto res = Execute(
SqlSource::FromTraceProcessorImplementation(drop.ToStdString()));
RETURN_IF_ERROR(res.status());
}
- auto it_and_inserted =
- runtime_table_fn_states_.Insert(lower_name, std::move(state));
- PERFETTO_CHECK(it_and_inserted.second);
-
base::StackString<1024> create(
- "CREATE VIRTUAL TABLE %s USING runtime_table_function", fn_name.c_str());
- return Execute(cf.sql.RewriteAllIgnoreExisting(
- SqlSource::FromTraceProcessorImplementation(
- create.ToStdString())))
- .status();
+ "CREATE VIRTUAL TABLE %s USING runtime_table_function",
+ state->prototype.function_name.c_str());
+
+ // Make sure we didn't accidentally leak a state from a previous function
+ // creation.
+ PERFETTO_CHECK(!runtime_table_fn_context_->temporary_create_state);
+
+ // Move the state into the context so that it will be picked up in xCreate
+ // of RuntimeTableFunctionModule.
+ runtime_table_fn_context_->temporary_create_state = std::move(state);
+ auto status = Execute(cf.sql.RewriteAllIgnoreExisting(
+ SqlSource::FromTraceProcessorImplementation(
+ create.ToStdString())))
+ .status();
+
+ // If an error happened, it's possible that the state was not picked up.
+ // Therefore, always reset the state just in case. OTOH if the creation
+ // succeeded, the state should always have been captured.
+ if (status.ok()) {
+ PERFETTO_CHECK(!runtime_table_fn_context_->temporary_create_state);
+ } else {
+ runtime_table_fn_context_->temporary_create_state.reset();
+ }
+ return status;
}
base::Status PerfettoSqlEngine::ExecuteCreateMacro(
@@ -779,18 +788,6 @@
return base::OkStatus();
}
-RuntimeTableFunction::State* PerfettoSqlEngine::GetRuntimeTableFunctionState(
- const std::string& name) const {
- auto* it = runtime_table_fn_states_.Find(base::ToLower(name));
- PERFETTO_CHECK(it);
- return it->get();
-}
-
-void PerfettoSqlEngine::OnRuntimeTableFunctionDestroyed(
- const std::string& name) {
- PERFETTO_CHECK(runtime_table_fn_states_.Erase(base::ToLower(name)));
-}
-
base::StatusOr<std::vector<std::string>>
PerfettoSqlEngine::GetColumnNamesFromSelectStatement(
const SqliteEngine::PreparedStatement& stmt,
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 ef5358d..cb7930b 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -172,13 +172,6 @@
// Registers a trace processor C++ table function with SQLite.
void RegisterStaticTableFunction(std::unique_ptr<StaticTableFunction> fn);
- // Returns the state for the given table function.
- RuntimeTableFunction::State* GetRuntimeTableFunctionState(
- const std::string&) const;
-
- // Should be called when a table function is destroyed.
- void OnRuntimeTableFunctionDestroyed(const std::string&);
-
SqliteEngine* sqlite_engine() { return engine_.get(); }
// Makes new SQL module available to import.
@@ -276,8 +269,7 @@
uint64_t static_window_function_count_ = 0;
uint64_t runtime_function_count_ = 0;
- base::FlatHashMap<std::string, std::unique_ptr<RuntimeTableFunction::State>>
- runtime_table_fn_states_;
+ RuntimeTableFunctionModule::Context* runtime_table_fn_context_ = nullptr;
base::FlatHashMap<std::string, const Table*> static_tables_;
base::FlatHashMap<std::string, std::unique_ptr<RuntimeTable>> runtime_tables_;
base::FlatHashMap<std::string, sql_modules::RegisteredModule> modules_;
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 3d0af93..12faa70 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
@@ -16,15 +16,28 @@
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
+#include <sqlite3.h>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
#include <optional>
+#include <string>
#include <utility>
+#include <vector>
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/public/compiler.h"
+#include "src/trace_processor/perfetto_sql/engine/function_util.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
-#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/util/sql_argument.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
namespace {
@@ -33,217 +46,226 @@
sqlite3_clear_bindings(stmt);
}
-} // namespace
-
-RuntimeTableFunction::RuntimeTableFunction(sqlite3*, PerfettoSqlEngine* engine)
- : engine_(engine) {}
-
-RuntimeTableFunction::~RuntimeTableFunction() {
- engine_->OnRuntimeTableFunctionDestroyed(name());
-}
-
-base::Status RuntimeTableFunction::Init(int,
- const char* const*,
- Schema* schema) {
- state_ = engine_->GetRuntimeTableFunctionState(name());
-
- // Now we've parsed prototype and return values, create the schema.
- *schema = CreateSchema();
- return base::OkStatus();
-}
-
-SqliteTableLegacy::Schema RuntimeTableFunction::CreateSchema() {
- std::vector<Column> columns;
- for (size_t i = 0; i < state_->return_values.size(); ++i) {
- const auto& ret = state_->return_values[i];
- columns.push_back(Column(columns.size(), ret.name().ToStdString(),
+auto CreateTableStrFromState(RuntimeTableFunctionModule::State* state) {
+ std::vector<std::string> columns;
+ columns.reserve(state->return_values.size());
+ for (const auto& ret : state->return_values) {
+ columns.emplace_back(ret.name().ToStdString() + " " +
+ sqlite::utils::SqlValueTypeToString(
sql_argument::TypeToSqlValueType(ret.type())));
}
- for (size_t i = 0; i < state_->prototype.arguments.size(); ++i) {
- const auto& arg = state_->prototype.arguments[i];
-
+ for (const auto& arg : state->prototype.arguments) {
// Add the "in_" prefix to every argument param to avoid clashes between the
// output and input parameters.
- columns.push_back(Column(columns.size(), "in_" + arg.name().ToStdString(),
- sql_argument::TypeToSqlValueType(arg.type()),
- true));
+ columns.emplace_back("in_" + arg.name().ToStdString() + " " +
+ sqlite::utils::SqlValueTypeToString(
+ sql_argument::TypeToSqlValueType(arg.type())) +
+ " HIDDEN");
}
+ columns.emplace_back("_primary_key BIGINT HIDDEN");
- std::vector<size_t> primary_keys;
-
- // Add the "primary key" column. SQLite requires that we provide a column
- // which is non-null and unique. Unfortunately, we have no restrictions on
- // the subqueries so we cannot rely on this constraint being held there.
- // Therefore, we create a "primary key" column which exists purely for SQLite
- // primary key purposes and is equal to the row number.
- columns.push_back(
- Column(columns.size(), "_primary_key", SqlValue::kLong, true));
- primary_keys.emplace_back(columns.size() - 1);
-
- return SqliteTableLegacy::Schema(std::move(columns), std::move(primary_keys));
+ std::string cols = base::Join(columns, ",");
+ return base::StackString<1024>(
+ R"(CREATE TABLE x(%s, PRIMARY KEY(_primary_key)) WITHOUT ROWID)",
+ cols.c_str());
}
-std::unique_ptr<SqliteTableLegacy::BaseCursor>
-RuntimeTableFunction::CreateCursor() {
- return std::unique_ptr<Cursor>(new Cursor(this, state_));
+} // namespace
+
+int RuntimeTableFunctionModule::Create(sqlite3* db,
+ void* ctx,
+ int,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ auto* context = GetContext(ctx);
+ auto state = std::move(context->temporary_create_state);
+
+ auto create_table_str = CreateTableStrFromState(state.get());
+ if (int ret = sqlite3_declare_vtab(db, create_table_str.c_str());
+ ret != SQLITE_OK) {
+ return ret;
+ }
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->reusable_stmt = std::move(state->temporary_create_stmt);
+ state->temporary_create_stmt = std::nullopt;
+ res->state = context->manager.OnCreate(argv, std::move(state));
+ *vtab = res.release();
+ return SQLITE_OK;
}
-int RuntimeTableFunction::BestIndex(const QueryConstraints& qc,
- BestIndexInfo* info) {
- // Only accept constraint sets where every input parameter has a value.
- size_t seen_argument_constraints = 0;
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
- seen_argument_constraints +=
- state_->IsArgumentColumn(static_cast<size_t>(cs.column));
+int RuntimeTableFunctionModule::Destroy(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDestroy(tab->state);
+ return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::Connect(sqlite3* db,
+ void* ctx,
+ int,
+ const char* const*,
+ sqlite3_vtab** vtab,
+ char** argv) {
+ auto* context = GetContext(ctx);
+
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->state = context->manager.OnConnect(argv);
+
+ auto create_table_str = CreateTableStrFromState(
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ res->state));
+ if (int ret = sqlite3_declare_vtab(db, create_table_str.c_str());
+ ret != SQLITE_OK) {
+ // If the registration happens to fail, make sure to disconnect the state
+ // again.
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDisconnect(
+ res->state);
+ return ret;
}
- if (seen_argument_constraints < state_->prototype.arguments.size())
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::Disconnect(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDisconnect(
+ tab->state);
+ return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::BestIndex(sqlite3_vtab* tab,
+ sqlite3_index_info* info) {
+ auto* t = GetVtab(tab);
+ auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ t->state);
+
+ // Don't deal with any constraints on the output parameters for simplicty.
+ // TODO(lalitm): reconsider this decision to allow more efficient queries:
+ // we would need to wrap the query in a SELECT * FROM (...) WHERE constraint
+ // like we do for SPAN JOIN.
+ base::Status status = sqlite::utils::ValidateFunctionArguments(
+ info, s->prototype.arguments.size(),
+ [s](size_t c) { return s->IsArgumentColumn(c); });
+ if (!status.ok()) {
return SQLITE_CONSTRAINT;
-
- for (size_t i = 0; i < info->sqlite_omit_constraint.size(); ++i) {
- size_t col = static_cast<size_t>(qc.constraints()[i].column);
- if (state_->IsArgumentColumn(col)) {
- info->sqlite_omit_constraint[i] = true;
- }
}
return SQLITE_OK;
}
-RuntimeTableFunction::Cursor::Cursor(RuntimeTableFunction* table, State* state)
- : SqliteTableLegacy::BaseCursor(table), table_(table), state_(state) {
- if (state->reusable_stmt) {
- stmt_ = std::move(state->reusable_stmt);
- state->reusable_stmt = std::nullopt;
- return_stmt_to_state_ = true;
+int RuntimeTableFunctionModule::Open(sqlite3_vtab* tab,
+ sqlite3_vtab_cursor** cursor) {
+ auto* t = GetVtab(tab);
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ if (t->reusable_stmt) {
+ c->stmt = std::move(t->reusable_stmt);
+ t->reusable_stmt = std::nullopt;
}
+ *cursor = c.release();
+ return SQLITE_OK;
}
-RuntimeTableFunction::Cursor::~Cursor() {
- if (return_stmt_to_state_) {
- ResetStatement(stmt_->sqlite_stmt());
- state_->reusable_stmt = std::move(stmt_);
+int RuntimeTableFunctionModule::Close(sqlite3_vtab_cursor* cursor) {
+ std::unique_ptr<Cursor> c(GetCursor(cursor));
+ auto* t = GetVtab(c->pVtab);
+ if (!t->reusable_stmt && c->stmt) {
+ ResetStatement(c->stmt->sqlite_stmt());
+ t->reusable_stmt = std::move(c->stmt);
}
+ return SQLITE_OK;
}
-base::Status RuntimeTableFunction::Cursor::Filter(const QueryConstraints& qc,
- sqlite3_value** argv,
- FilterHistory) {
+int RuntimeTableFunctionModule::Filter(sqlite3_vtab_cursor* cur,
+ int,
+ const char*,
+ int argc,
+ sqlite3_value** argv) {
+ auto* c = GetCursor(cur);
+ auto* t = GetVtab(cur->pVtab);
+ auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ t->state);
+
+ PERFETTO_CHECK(static_cast<size_t>(argc) == s->prototype.arguments.size());
PERFETTO_TP_TRACE(metatrace::Category::FUNCTION_CALL, "TABLE_FUNCTION_CALL",
- [this](metatrace::Record* r) {
- r->AddArg("Function",
- state_->prototype.function_name.c_str());
+ [s](metatrace::Record* r) {
+ r->AddArg("Function", s->prototype.function_name.c_str());
});
- auto col_to_arg_idx = [this](int col) {
- return static_cast<uint32_t>(col) -
- static_cast<uint32_t>(state_->return_values.size());
- };
-
- size_t seen_argument_constraints = 0;
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
-
- // Only consider argument columns (i.e. input parameters) as we're
- // delegating the rest to SQLite.
- if (!state_->IsArgumentColumn(static_cast<size_t>(cs.column)))
- continue;
-
- // We only support equality constraints as we're expecting "input arguments"
- // to our "function".
- 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(
- argv[i], sql_argument::TypeToSqlValueType(arg.type()),
- sql_argument::TypeToHumanFriendlyString(arg.type()));
- if (!status.ok()) {
- return base::ErrStatus("%s: argument %s (index %zu) %s",
- state_->prototype.function_name.c_str(),
- arg.name().c_str(), i, status.c_message());
- }
-
- seen_argument_constraints++;
- }
-
- // Verify that we saw one valid constraint for every input argument.
- if (seen_argument_constraints < state_->prototype.arguments.size()) {
- return base::ErrStatus(
- "%s: missing value for input argument. Saw %zu arguments but expected "
- "%zu",
- state_->prototype.function_name.c_str(), seen_argument_constraints,
- state_->prototype.arguments.size());
- }
-
// Prepare the SQL definition as a statement using SQLite.
// TODO(lalitm): measure and implement whether it would be a good idea to
// forward constraints here when we build the nested query.
- if (stmt_) {
+ if (c->stmt) {
// Filter can be called multiple times for the same cursor, so if we
// already have a statement, reset and reuse it. Otherwise, create a
// new one.
- ResetStatement(stmt_->sqlite_stmt());
+ ResetStatement(c->stmt->sqlite_stmt());
} else {
- auto stmt = table_->engine_->sqlite_engine()->PrepareStatement(
- state_->sql_defn_str);
- RETURN_IF_ERROR(stmt.status());
- stmt_ = std::move(stmt);
+ auto stmt = s->engine->sqlite_engine()->PrepareStatement(s->sql_defn_str);
+ c->stmt = std::move(stmt);
+ if (const auto& status = c->stmt->status(); !status.ok()) {
+ return sqlite::utils::SetError(t, status.c_message());
+ }
}
// Bind all the arguments to the appropriate places in the function.
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
-
- // Don't deal with any constraints on the output parameters for simplicty.
- // TODO(lalitm): reconsider this decision to allow more efficient queries:
- // we would need to wrap the query in a SELECT * FROM (...) WHERE constraint
- // like we do for SPAN JOIN.
- if (!state_->IsArgumentColumn(static_cast<size_t>(cs.column)))
- continue;
-
- uint32_t index = col_to_arg_idx(cs.column);
- PERFETTO_DCHECK(index < state_->prototype.arguments.size());
-
- const auto& arg = state_->prototype.arguments[index];
- auto status = MaybeBindArgument(
- stmt_->sqlite_stmt(), state_->prototype.function_name, arg, argv[i]);
- RETURN_IF_ERROR(status);
+ for (uint32_t i = 0; i < static_cast<uint32_t>(argc); ++i) {
+ const auto& arg = s->prototype.arguments[i];
+ base::Status status = MaybeBindArgument(
+ c->stmt->sqlite_stmt(), s->prototype.function_name, arg, argv[i]);
+ if (!status.ok()) {
+ return sqlite::utils::SetError(t, status.c_message());
+ }
}
// Reset the next call count - this is necessary because the same cursor
// can be used for multiple filter operations.
- next_call_count_ = 0;
- return Next();
+ c->next_call_count = 0;
+ return Next(cur);
}
-base::Status RuntimeTableFunction::Cursor::Next() {
- is_eof_ = !stmt_->Step();
- next_call_count_++;
- return stmt_->status();
+int RuntimeTableFunctionModule::Next(sqlite3_vtab_cursor* cur) {
+ auto* c = GetCursor(cur);
+ c->is_eof = !c->stmt->Step();
+ c->next_call_count++;
+ if (const auto& status = c->stmt->status(); !status.ok()) {
+ return sqlite::utils::SetError(cur->pVtab, status.c_message());
+ }
+ return SQLITE_OK;
}
-bool RuntimeTableFunction::Cursor::Eof() {
- return is_eof_;
+int RuntimeTableFunctionModule::Eof(sqlite3_vtab_cursor* cur) {
+ return GetCursor(cur)->is_eof;
}
-base::Status RuntimeTableFunction::Cursor::Column(sqlite3_context* ctx, int i) {
- size_t idx = static_cast<size_t>(i);
- if (state_->IsReturnValueColumn(idx)) {
- sqlite::result::Value(ctx, sqlite3_column_value(stmt_->sqlite_stmt(), i));
- } else if (state_->IsArgumentColumn(idx)) {
+int RuntimeTableFunctionModule::Column(sqlite3_vtab_cursor* cur,
+ sqlite3_context* ctx,
+ int N) {
+ auto* c = GetCursor(cur);
+ auto* t = GetVtab(cur->pVtab);
+ auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+ t->state);
+
+ auto idx = static_cast<size_t>(N);
+ if (PERFETTO_LIKELY(s->IsReturnValueColumn(idx))) {
+ sqlite::result::Value(ctx, sqlite3_column_value(c->stmt->sqlite_stmt(), N));
+ return SQLITE_OK;
+ }
+
+ if (PERFETTO_LIKELY(s->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.
sqlite::result::Null(ctx);
- } else {
- PERFETTO_DCHECK(state_->IsPrimaryKeyColumn(idx));
- sqlite::result::Long(ctx, next_call_count_);
+ return SQLITE_OK;
}
- return base::OkStatus();
+
+ PERFETTO_DCHECK(s->IsPrimaryKeyColumn(idx));
+ sqlite::result::Long(ctx, c->next_call_count);
+ return SQLITE_OK;
}
-} // namespace trace_processor
-} // namespace perfetto
+int RuntimeTableFunctionModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
index 80edf72..a027e02 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
@@ -17,31 +17,37 @@
#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
+#include <cstddef>
+#include <cstdint>
+#include <memory>
#include <optional>
+#include <string>
+#include <vector>
+#include "perfetto/base/logging.h"
#include "src/trace_processor/perfetto_sql/engine/function_util.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
+#include "src/trace_processor/util/sql_argument.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class PerfettoSqlEngine;
// The implementation of the SqliteTableLegacy interface for table functions
// defined at runtime using SQL.
-class RuntimeTableFunction final
- : public TypedSqliteTable<RuntimeTableFunction, PerfettoSqlEngine*> {
- public:
- // The state of this function. This is separated from |RuntimeTableFunction|
- // because |RuntimeTableFunction| is owned by Sqlite while |State| is owned by
- // PerfettoSqlEngine.
+struct RuntimeTableFunctionModule
+ : public sqlite::Module<RuntimeTableFunctionModule> {
struct State {
+ PerfettoSqlEngine* engine;
SqlSource sql_defn_str;
FunctionPrototype prototype;
std::vector<sql_argument::ArgumentDefinition> return_values;
- std::optional<SqliteEngine::PreparedStatement> reusable_stmt;
+ std::optional<SqliteEngine::PreparedStatement> temporary_create_stmt;
bool IsReturnValueColumn(size_t i) const {
PERFETTO_DCHECK(i < TotalColumnCount());
@@ -65,44 +71,55 @@
kPrimaryKeyColumns;
}
};
- class Cursor final : public SqliteTableLegacy::BaseCursor {
- public:
- explicit Cursor(RuntimeTableFunction* table, State* state);
- ~Cursor() final;
-
- base::Status Filter(const QueryConstraints& qc,
- sqlite3_value**,
- FilterHistory);
- base::Status Next();
- bool Eof();
- base::Status Column(sqlite3_context* context, int N);
-
- private:
- RuntimeTableFunction* table_ = nullptr;
- State* state_ = nullptr;
-
- std::optional<SqliteEngine::PreparedStatement> stmt_;
- bool return_stmt_to_state_ = false;
-
- bool is_eof_ = false;
- int next_call_count_ = 0;
+ struct Context {
+ std::unique_ptr<State> temporary_create_state;
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule> manager;
+ };
+ struct Vtab : sqlite::Module<RuntimeTableFunctionModule>::Vtab {
+ sqlite::ModuleStateManager<RuntimeTableFunctionModule>::PerVtabState* state;
+ std::optional<SqliteEngine::PreparedStatement> reusable_stmt;
+ };
+ struct Cursor : sqlite::Module<RuntimeTableFunctionModule>::Cursor {
+ std::optional<SqliteEngine::PreparedStatement> stmt;
+ bool is_eof = false;
+ int next_call_count = 0;
};
- RuntimeTableFunction(sqlite3*, PerfettoSqlEngine*);
- ~RuntimeTableFunction() final;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
- base::Status Init(int argc, const char* const* argv, Schema*) final;
- std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
- int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) final;
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Destroy(sqlite3_vtab*);
- private:
- Schema CreateSchema();
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
- PerfettoSqlEngine* engine_ = nullptr;
- State* state_ = nullptr;
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index ba1eed6..bb95e2e 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -18,10 +18,13 @@
#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_UTILS_H_
#include <sqlite3.h>
+#include <algorithm>
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <functional>
+#include <initializer_list>
#include <optional>
#include <string>
#include <utility>
@@ -156,6 +159,54 @@
status.c_message()));
}
+// For a given |sqlite3_index_info| struct received in a BestIndex call, returns
+// whether all |arg_count| arguments (with |is_arg_column| indicating whether a
+// given column is a function argument) have exactly one equaltiy constraint
+// associated with them.
+//
+// If so, the associated constraint is omitted and the argvIndex is mapped to
+// the corresponding argument's index.
+inline base::Status ValidateFunctionArguments(
+ sqlite3_index_info* info,
+ size_t arg_count,
+ const std::function<bool(size_t)>& is_arg_column) {
+ std::vector<bool> present;
+ size_t present_count = 0;
+ for (int i = 0; i < info->nConstraint; ++i) {
+ const auto& in = info->aConstraint[i];
+ if (!in.usable) {
+ continue;
+ }
+ auto cs_col = static_cast<size_t>(in.iColumn);
+ if (!is_arg_column(cs_col)) {
+ continue;
+ }
+ if (!IsOpEq(in.op)) {
+ return base::ErrStatus(
+ "Unexpected non equality constraints for column %zu", cs_col);
+ }
+ if (cs_col >= present.size()) {
+ present.resize(cs_col + 1);
+ }
+ if (present[cs_col]) {
+ return base::ErrStatus("Unexpected multiple constraints for column %zu",
+ cs_col);
+ }
+ present[cs_col] = true;
+ present_count++;
+
+ auto& out = info->aConstraintUsage[i];
+ out.argvIndex = static_cast<int>(present_count);
+ out.omit = true;
+ }
+ if (present_count != arg_count) {
+ return base::ErrStatus(
+ "Unexpected missing argument: expected %zu, actual %zu", arg_count,
+ present_count);
+ }
+ return base::OkStatus();
+}
+
// Converts the given SqlValue type to the type string SQLite understands.
inline std::string SqlValueTypeToString(SqlValue::Type type) {
switch (type) {