blob: 12b4915279823f30421dfadb81e201cfe08172e6 [file] [log] [blame]
/*
* Copyright (C) 2021 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/create_view_function.h"
#include <numeric>
#include "perfetto/base/status.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/create_function_internal.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
#include "src/trace_processor/util/status_macros.h"
namespace perfetto {
namespace trace_processor {
namespace {
class CreatedViewFunction : public SqliteTable {
public:
class Cursor : public SqliteTable::Cursor {
public:
explicit Cursor(CreatedViewFunction* table);
~Cursor() override;
int Filter(const QueryConstraints& qc,
sqlite3_value**,
FilterHistory) override;
int Next() override;
int Eof() override;
int Column(sqlite3_context* context, int N) override;
private:
sqlite3_stmt* stmt_ = nullptr;
CreatedViewFunction* table_ = nullptr;
bool is_eof_ = false;
};
CreatedViewFunction(sqlite3*, CreateViewFunction::State* state);
~CreatedViewFunction() override;
base::Status Init(int argc, const char* const* argv, Schema*) override;
std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) override;
static void Register(sqlite3* db, CreateViewFunction::State* state) {
SqliteTable::Register<CreatedViewFunction>(
db, state, "internal_view_function_impl", false, true);
}
private:
Schema CreateSchema();
sqlite3* db_ = nullptr;
Prototype prototype_;
std::vector<Prototype::Argument> return_values_;
std::string prototype_str_;
std::string sql_defn_str_;
CreateViewFunction::State* state_;
};
CreatedViewFunction::CreatedViewFunction(sqlite3* db,
CreateViewFunction::State* state)
: db_(db), state_(state) {}
CreatedViewFunction::~CreatedViewFunction() = default;
base::Status CreatedViewFunction::Init(int argc,
const char* const* argv,
Schema* schema) {
// The first three args are SQLite ones which we ignore.
PERFETTO_CHECK(argc == 6);
prototype_str_ = argv[3];
std::string return_prototype_str = argv[4];
sql_defn_str_ = argv[5];
// SQLite gives us strings with quotes included (i.e. 'string'). Strip these
// from the front and back.
prototype_str_ = prototype_str_.substr(1, prototype_str_.size() - 2);
return_prototype_str =
return_prototype_str.substr(1, return_prototype_str.size() - 2);
sql_defn_str_ = sql_defn_str_.substr(1, sql_defn_str_.size() - 2);
// Parse all the arguments into a more friendly form.
base::Status status =
ParsePrototype(base::StringView(prototype_str_), prototype_);
if (!status.ok()) {
return base::ErrStatus("CREATE_VIEW_FUNCTION[prototype=%s]: %s",
prototype_str_.c_str(), status.c_message());
}
// Parse the return type into a enum format.
status = ParseArgs(return_prototype_str, return_values_);
if (!status.ok()) {
return base::ErrStatus(
"CREATE_VIEW_FUNCTION[prototype=%s, return=%s]: unknown return type "
"specified",
prototype_str_.c_str(), return_prototype_str.c_str());
}
// Now we've parsed prototype and return values, create the schema.
*schema = CreateSchema();
return base::OkStatus();
}
SqliteTable::Schema CreatedViewFunction::CreateSchema() {
std::vector<Column> columns;
for (size_t i = 0; i < return_values_.size(); ++i) {
const auto& ret = return_values_[i];
columns.push_back(Column(columns.size(), ret.name, ret.type, false));
}
for (size_t i = 0; i < prototype_.arguments.size(); ++i) {
const auto& arg = prototype_.arguments[i];
columns.push_back(Column(columns.size(), arg.name, arg.type, true));
}
std::vector<size_t> primary_keys(return_values_.size());
std::iota(primary_keys.begin(), primary_keys.end(), 0);
return SqliteTable::Schema(std::move(columns), std::move(primary_keys));
}
std::unique_ptr<SqliteTable::Cursor> CreatedViewFunction::CreateCursor() {
return std::unique_ptr<Cursor>(new Cursor(this));
}
int CreatedViewFunction::BestIndex(const QueryConstraints& qc,
BestIndexInfo* info) {
for (size_t i = 0; i < info->sqlite_omit_constraint.size(); ++i) {
size_t col = static_cast<size_t>(qc.constraints()[i].column);
if (schema().columns()[col].hidden()) {
info->sqlite_omit_constraint[i] = true;
}
}
return SQLITE_OK;
}
CreatedViewFunction::Cursor::Cursor(CreatedViewFunction* table)
: SqliteTable::Cursor(table), table_(table) {}
CreatedViewFunction::Cursor::~Cursor() = default;
int CreatedViewFunction::Cursor::Filter(const QueryConstraints& qc,
sqlite3_value** argv,
FilterHistory) {
auto col_to_arg_idx = [this](int col) {
return static_cast<size_t>(col) - table_->return_values_.size();
};
size_t seen_hidden_constraints = 0;
for (size_t i = 0; i < qc.constraints().size(); ++i) {
const auto& cs = qc.constraints()[i];
// Only consider hidden columns (i.e. input parameters) as we're delegating
// the rest to SQLite.
if (!table_->schema().columns()[static_cast<size_t>(cs.column)].hidden())
continue;
// We only support equality constraints as we're expecting "input arguments"
// to our "function".
if (!sqlite_utils::IsOpEq(cs.op)) {
table_->SetErrorMessage(
sqlite3_mprintf("%s: non-equality constraint passed",
table_->prototype_.function_name.c_str()));
return SQLITE_ERROR;
}
const auto& arg = table_->prototype_.arguments[col_to_arg_idx(cs.column)];
SqlValue::Type expected_type = arg.type;
base::Status status = TypeCheckSqliteValue(argv[i], expected_type);
if (!status.ok()) {
table_->SetErrorMessage(
sqlite3_mprintf("%s: argument %s (index %u) %s",
table_->prototype_.function_name.c_str(),
arg.name.c_str(), i, status.c_message()));
return SQLITE_ERROR;
}
seen_hidden_constraints++;
}
// Verify that we saw one valid constriant for every input argument.
if (seen_hidden_constraints < table_->prototype_.arguments.size()) {
table_->SetErrorMessage(
sqlite3_mprintf("%s: missing value for input argument",
table_->prototype_.function_name.c_str()));
return SQLITE_ERROR;
}
// Prepare the SQL definition as a statement using SQLite.
// TODO(lalitm): see if we can reuse this prepared statement rather than
// creating it very time.
// TODO(lalitm): measure and implement whether it would be a good idea to
// forward constraints here when we build the nested query.
ScopedStmt stmt;
int ret = sqlite3_prepare_v2(table_->db_, table_->sql_defn_str_.data(),
static_cast<int>(table_->sql_defn_str_.size()),
&stmt_, nullptr);
stmt.reset(stmt_);
if (ret != SQLITE_OK) {
table_->SetErrorMessage(sqlite3_mprintf(
"%s: Failed to prepare SQL statement for function. "
"Check the SQL defintion this function for syntax errors. "
"(SQLite error: %s).",
table_->prototype_.function_name.c_str(), sqlite3_errmsg(table_->db_)));
return SQLITE_ERROR;
}
// 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];
const auto& arg = table_->prototype_.arguments[col_to_arg_idx(cs.column)];
auto status = MaybeBindArgument(stmt_, table_->prototype_.function_name,
arg, argv[i]);
if (!status.ok()) {
table_->SetErrorMessage(sqlite3_mprintf("%s", status.c_message()));
return SQLITE_ERROR;
}
}
ret = Next();
if (ret != SQLITE_OK)
return ret;
// Keep track of the scoped statements in the stmts vector so we can clean
// all these up before destroying trace processor.
table_->state_->erase(table_->prototype_.function_name);
table_->state_->emplace(table_->prototype_.function_name, std::move(stmt));
return SQLITE_OK;
}
int CreatedViewFunction::Cursor::Next() {
int ret = sqlite3_step(stmt_);
is_eof_ = ret == SQLITE_DONE;
if (ret != SQLITE_ROW && ret != SQLITE_DONE) {
table_->SetErrorMessage(sqlite3_mprintf(
"%s: SQLite error while stepping statement: %s",
table_->prototype_.function_name.c_str(), sqlite3_errmsg(table_->db_)));
return ret;
}
return SQLITE_OK;
}
int CreatedViewFunction::Cursor::Eof() {
return is_eof_;
}
int CreatedViewFunction::Cursor::Column(sqlite3_context* ctx, int i) {
size_t idx = static_cast<size_t>(i);
if (idx < table_->return_values_.size()) {
sqlite3_result_value(ctx, sqlite3_column_value(stmt_, i));
} else {
sqlite3_result_null(ctx);
}
return SQLITE_OK;
}
} // namespace
base::Status CreateViewFunction::Run(CreateViewFunction::Context* ctx,
size_t argc,
sqlite3_value** argv,
SqlValue&,
Destructors&) {
if (argc != 3) {
return base::ErrStatus(
"CREATE_VIEW_FUNCTION: invalid number of args; expected %u, received "
"%zu",
3u, argc);
}
sqlite3_value* prototype_value = argv[0];
sqlite3_value* return_prototype_value = argv[1];
sqlite3_value* sql_defn_value = argv[2];
// Type check all the arguments.
{
auto type_check = [prototype_value](sqlite3_value* value,
SqlValue::Type type, const char* desc) {
base::Status status = TypeCheckSqliteValue(value, type);
if (!status.ok()) {
return base::ErrStatus("CREATE_VIEW_FUNCTION[prototype=%s]: %s %s",
sqlite3_value_text(prototype_value), desc,
status.c_message());
}
return base::OkStatus();
};
RETURN_IF_ERROR(type_check(prototype_value, SqlValue::Type::kString,
"function prototype (first argument)"));
RETURN_IF_ERROR(type_check(return_prototype_value, SqlValue::Type::kString,
"return prototype (second argument)"));
RETURN_IF_ERROR(type_check(sql_defn_value, SqlValue::Type::kString,
"SQL definition (third argument)"));
}
// Extract the arguments from the value wrappers.
auto extract_string = [](sqlite3_value* value) -> const char* {
return reinterpret_cast<const char*>(sqlite3_value_text(value));
};
const char* prototype_str = extract_string(prototype_value);
const char* return_prototype_str = extract_string(return_prototype_value);
const char* sql_defn_str = extract_string(sql_defn_value);
base::StringView function_name;
RETURN_IF_ERROR(ParseFunctionName(prototype_str, function_name));
base::StackString<1024> sql(
"CREATE VIRTUAL TABLE IF NOT EXISTS %s USING "
"INTERNAL_VIEW_FUNCTION_IMPL('%s', '%s', '%s');",
function_name.ToStdString().c_str(), prototype_str, return_prototype_str,
sql_defn_str);
ScopedSqliteString errmsg;
char* errmsg_raw = nullptr;
int ret = sqlite3_exec(ctx->db, sql.c_str(), nullptr, nullptr, &errmsg_raw);
errmsg.reset(errmsg_raw);
if (ret != SQLITE_OK)
return base::ErrStatus("%s", errmsg.get());
// CREATE_VIEW_FUNCTION doesn't have a return value so just don't sent |out|.
return base::OkStatus();
}
void CreateViewFunction::RegisterTable(sqlite3* db,
CreateViewFunction::State* state) {
CreatedViewFunction::Register(db, state);
}
} // namespace trace_processor
} // namespace perfetto