blob: c174814e60969351d4b7cdecbbb61538fb2c5efb [file] [log] [blame]
/*
* Copyright (C) 2023 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.
*/
#include "src/trace_processor/sqlite/perfetto_sql_engine.h"
#include <optional>
#include <string>
#include <variant>
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/sqlite/db_sqlite_table.h"
#include "src/trace_processor/sqlite/perfetto_sql_parser.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
#include "src/trace_processor/tp_metatrace.h"
#include "src/trace_processor/util/status_macros.h"
namespace perfetto {
namespace trace_processor {
namespace {
void IncrementCountForStmt(const SqliteEngine::PreparedStatement& p_stmt,
PerfettoSqlEngine::ExecutionStats* res) {
res->statement_count++;
// If the stmt is already done, it clearly didn't have any output.
if (p_stmt.IsDone())
return;
sqlite3_stmt* stmt = p_stmt.sqlite_stmt();
if (sqlite3_column_count(stmt) == 1) {
sqlite3_value* value = sqlite3_column_value(stmt, 0);
// If the "VOID" pointer associated to the return value is not null,
// that means this is a function which is forced to return a value
// (because all functions in SQLite have to) but doesn't actually
// wait to (i.e. it wants to be treated like CREATE TABLE or similar).
// Because of this, ignore the return value of this function.
// See |WrapSqlFunction| for where this is set.
if (sqlite3_value_pointer(value, "VOID") != nullptr) {
return;
}
// If the statement only has a single column and that column is named
// "suppress_query_output", treat it as a statement without output for
// accounting purposes. This allows an escape hatch for cases where the
// user explicitly wants to ignore functions as having output.
if (strcmp(sqlite3_column_name(stmt, 0), "suppress_query_output") == 0) {
return;
}
}
// Otherwise, the statement has output and so increment the count.
res->statement_count_with_output++;
}
} // namespace
PerfettoSqlEngine::PerfettoSqlEngine() : query_cache_(new QueryCache()) {}
void PerfettoSqlEngine::RegisterTable(const Table& table,
const std::string& table_name) {
DbSqliteTable::Context context{query_cache_.get(),
DbSqliteTable::TableComputation::kStatic,
&table, nullptr};
engine_.RegisterVirtualTableModule<DbSqliteTable>(
table_name, std::move(context), SqliteTable::kEponymousOnly, false);
// Register virtual tables into an internal 'perfetto_tables' table.
// This is used for iterating through all the tables during a database
// export.
char* insert_sql = sqlite3_mprintf(
"INSERT INTO perfetto_tables(name) VALUES('%q')", table_name.c_str());
char* error = nullptr;
sqlite3_exec(engine_.db(), insert_sql, nullptr, nullptr, &error);
sqlite3_free(insert_sql);
if (error) {
PERFETTO_ELOG("Error adding table to perfetto_tables: %s", error);
sqlite3_free(error);
}
}
void PerfettoSqlEngine::RegisterTableFunction(
std::unique_ptr<TableFunction> fn) {
std::string table_name = fn->TableName();
DbSqliteTable::Context context{query_cache_.get(),
DbSqliteTable::TableComputation::kDynamic,
nullptr, std::move(fn)};
engine_.RegisterVirtualTableModule<DbSqliteTable>(
table_name, std::move(context), SqliteTable::kEponymousOnly, false);
}
base::StatusOr<PerfettoSqlEngine::ExecutionStats> PerfettoSqlEngine::Execute(
SqlSource sql) {
auto res = ExecuteUntilLastStatement(std::move(sql));
RETURN_IF_ERROR(res.status());
if (res->stmt.IsDone()) {
return res->stats;
}
while (res->stmt.Step()) {
}
RETURN_IF_ERROR(res->stmt.status());
return res->stats;
}
base::StatusOr<PerfettoSqlEngine::ExecutionResult>
PerfettoSqlEngine::ExecuteUntilLastStatement(SqlSource sql_source) {
// A SQL string can contain several statements. Some of them might be comment
// only, e.g. "SELECT 1; /* comment */; SELECT 2;". Some statements can also
// be PerfettoSQL statements which we need to transpile before execution or
// execute without delegating to SQLite.
//
// The logic here is the following:
// - We parse the statement as a PerfettoSQL statement.
// - If the statement is actually an SQLite statement, we invoke PrepareStmt.
// - We step once to make sure side effects take effect (e.g. for CREATE
// TABLE statements, tables are created).
// - If we encounter a valid statement afterwards, we step internally through
// all rows of the previous one. This ensures that any further side effects
// take hold *before* we step into the next statement.
// - Once no further statements are encountered, we return the prepared
// statement for the last valid statement.
std::optional<SqliteEngine::PreparedStatement> res;
ExecutionStats stats;
PerfettoSqlParser parser(std::move(sql_source));
while (parser.Next()) {
// If none of the above matched, this must just be an SQL statement directly
// executable by SQLite.
auto* sql = std::get_if<PerfettoSqlParser::SqliteSql>(&parser.statement());
PERFETTO_CHECK(sql);
// Try to get SQLite to prepare the statement.
std::optional<SqliteEngine::PreparedStatement> cur_stmt;
{
PERFETTO_TP_TRACE(metatrace::Category::QUERY, "QUERY_PREPARE");
auto stmt_or = engine_.PrepareStatement(std::move(sql->sql));
RETURN_IF_ERROR(stmt_or.status());
cur_stmt = std::move(stmt_or.value());
}
// The only situation where we'd have an ok status but also no prepared
// statement is if the SQL was a pure comment. However, the PerfettoSQL
// parser should filter out such statements so this should never happen.
PERFETTO_DCHECK(cur_stmt->sqlite_stmt());
// Before stepping into |cur_stmt|, we need to finish iterating through
// the previous statement so we don't have two clashing statements (e.g.
// SELECT * FROM v and DROP VIEW v) partially stepped into.
if (res && !res->IsDone()) {
PERFETTO_TP_TRACE(metatrace::Category::QUERY, "STMT_STEP_UNTIL_DONE",
[&res](metatrace::Record* record) {
record->AddArg("SQL", res->expanded_sql());
});
while (res->Step()) {
}
RETURN_IF_ERROR(res->status());
}
// Propogate the current statement to the next iteration.
res = std::move(cur_stmt);
// Step the newly prepared statement once. This is considered to be
// "executing" the statement.
{
PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "STMT_FIRST_STEP",
[&res](metatrace::Record* record) {
record->AddArg("SQL", res->expanded_sql());
});
PERFETTO_DLOG("Executing statement: %s", res->sql());
res->Step();
RETURN_IF_ERROR(res->status());
}
// Increment the neecessary counts for the statement.
IncrementCountForStmt(*res, &stats);
}
RETURN_IF_ERROR(parser.status());
// If we didn't manage to prepare a single statement, that means everything
// in the SQL was treated as a comment.
if (!res)
return base::ErrStatus("No valid SQL to run");
// Update the output statement and column count.
stats.column_count =
static_cast<uint32_t>(sqlite3_column_count(res->sqlite_stmt()));
return ExecutionResult{std::move(*res), stats};
}
} // namespace trace_processor
} // namespace perfetto