/* * Copyright (C) 2019 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/prelude/functions/create_function.h" #include "perfetto/base/status.h" #include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/prelude/functions/create_function_internal.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" #include "src/trace_processor/tp_metatrace.h" #include "src/trace_processor/util/status_macros.h" namespace perfetto { namespace trace_processor { namespace { struct CreatedFunction : public SqlFunction { struct Context { SqliteEngine* engine; Prototype prototype; sql_argument::Type return_type; std::string sql; ScopedStmt stmt; }; static base::Status Run(Context* ctx, size_t argc, sqlite3_value** argv, SqlValue& out, Destructors&); static base::Status VerifyPostConditions(Context*); static void Cleanup(Context*); }; base::Status CreatedFunction::Run(CreatedFunction::Context* ctx, size_t argc, sqlite3_value** argv, SqlValue& out, Destructors&) { if (argc != ctx->prototype.arguments.size()) { return base::ErrStatus( "%s: invalid number of args; expected %zu, received %zu", ctx->prototype.function_name.c_str(), ctx->prototype.arguments.size(), argc); } // Type check all the arguments. for (size_t i = 0; i < argc; ++i) { sqlite3_value* arg = argv[i]; sql_argument::Type type = ctx->prototype.arguments[i].type(); base::Status status = sqlite_utils::TypeCheckSqliteValue( arg, sql_argument::TypeToSqlValueType(type), sql_argument::TypeToHumanFriendlyString(type)); if (!status.ok()) { return base::ErrStatus("%s[arg=%s]: argument %zu %s", ctx->prototype.function_name.c_str(), sqlite3_value_text(arg), i, status.c_message()); } } PERFETTO_TP_TRACE( metatrace::Category::FUNCTION, "CREATE_FUNCTION", [ctx, argv](metatrace::Record* r) { r->AddArg("Function", ctx->prototype.function_name.c_str()); for (uint32_t i = 0; i < ctx->prototype.arguments.size(); ++i) { std::string key = "Arg " + std::to_string(i); const char* value = reinterpret_cast(sqlite3_value_text(argv[i])); r->AddArg(base::StringView(key), value ? base::StringView(value) : base::StringView("NULL")); } }); // Bind all the arguments to the appropriate places in the function. for (size_t i = 0; i < argc; ++i) { RETURN_IF_ERROR(MaybeBindArgument(ctx->stmt.get(), ctx->prototype.function_name, ctx->prototype.arguments[i], argv[i])); } int ret = sqlite3_step(ctx->stmt.get()); RETURN_IF_ERROR( SqliteRetToStatus(ctx->engine->db(), ctx->prototype.function_name, ret)); if (ret == SQLITE_DONE) { // No return value means we just return don't set |out|. return base::OkStatus(); } PERFETTO_DCHECK(ret == SQLITE_ROW); size_t col_count = static_cast(sqlite3_column_count(ctx->stmt.get())); if (col_count != 1) { return base::ErrStatus( "%s: SQL definition should only return one column: returned %zu " "columns", ctx->prototype.function_name.c_str(), col_count); } out = sqlite_utils::SqliteValueToSqlValue( sqlite3_column_value(ctx->stmt.get(), 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 // distinguish between nulls and 0 byte strings. Therefore, change the value // to an empty string. if (out.type == SqlValue::kBytes && out.bytes_value == nullptr) { PERFETTO_DCHECK(out.bytes_count == 0); out.bytes_value = ""; } return base::OkStatus(); } base::Status CreatedFunction::VerifyPostConditions(Context* ctx) { int ret = sqlite3_step(ctx->stmt.get()); RETURN_IF_ERROR( SqliteRetToStatus(ctx->engine->db(), ctx->prototype.function_name, ret)); if (ret == SQLITE_ROW) { auto expanded_sql = sqlite_utils::ExpandedSqlForStmt(ctx->stmt.get()); return base::ErrStatus( "%s: multiple values were returned when executing function body. " "Executed SQL was %s", ctx->prototype.function_name.c_str(), expanded_sql.get()); } PERFETTO_DCHECK(ret == SQLITE_DONE); return base::OkStatus(); } void CreatedFunction::Cleanup(CreatedFunction::Context* ctx) { sqlite3_reset(ctx->stmt.get()); sqlite3_clear_bindings(ctx->stmt.get()); } } // namespace base::Status CreateFunction::Run(SqliteEngine* engine, size_t argc, sqlite3_value** argv, SqlValue&, Destructors&) { if (argc != 3) { return base::ErrStatus( "CREATE_FUNCTION: invalid number of args; expected %u, received %zu", 3u, argc); } sqlite3_value* prototype_value = argv[0]; sqlite3_value* return_type_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 = sqlite_utils::TypeCheckSqliteValue(value, type); if (!status.ok()) { return base::ErrStatus("CREATE_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_type_value, SqlValue::Type::kString, "return type (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) -> base::StringView { return reinterpret_cast(sqlite3_value_text(value)); }; base::StringView prototype_str = extract_string(prototype_value); base::StringView return_type_str = extract_string(return_type_value); std::string sql_defn_str = extract_string(sql_defn_value).ToStdString(); // Parse all the arguments into a more friendly form. Prototype prototype; base::Status status = ParsePrototype(prototype_str, prototype); if (!status.ok()) { return base::ErrStatus("CREATE_FUNCTION[prototype=%s]: %s", prototype_str.ToStdString().c_str(), status.c_message()); } // Parse the return type into a enum format. auto opt_return_type = sql_argument::ParseType(return_type_str); if (!opt_return_type) { return base::ErrStatus( "CREATE_FUNCTION[prototype=%s, return=%s]: unknown return type " "specified", prototype_str.ToStdString().c_str(), return_type_str.ToStdString().c_str()); } int created_argc = static_cast(prototype.arguments.size()); auto* fn_ctx = engine->GetFunctionContext(prototype.function_name, created_argc); if (fn_ctx) { // If the function already exists, just verify that the prototype, return // type and SQL matches exactly with what we already had registered. By // doing this, we can avoid the problem plaguing C++ macros where macro // ordering determines which one gets run. auto* created_ctx = static_cast(fn_ctx); if (created_ctx->prototype != prototype) { return base::ErrStatus( "CREATE_FUNCTION[prototype=%s]: function prototype changed", prototype_str.ToStdString().c_str()); } if (created_ctx->return_type != *opt_return_type) { return base::ErrStatus( "CREATE_FUNCTION[prototype=%s]: return type changed from %s to %s", prototype_str.ToStdString().c_str(), sql_argument::TypeToHumanFriendlyString(created_ctx->return_type), return_type_str.ToStdString().c_str()); } if (created_ctx->sql != sql_defn_str) { return base::ErrStatus( "CREATE_FUNCTION[prototype=%s]: function SQL changed from %s to %s", prototype_str.ToStdString().c_str(), created_ctx->sql.c_str(), sql_defn_str.c_str()); } return base::OkStatus(); } // Prepare the SQL definition as a statement using SQLite. ScopedStmt stmt; sqlite3_stmt* stmt_raw = nullptr; int ret = sqlite3_prepare_v2(engine->db(), sql_defn_str.data(), static_cast(sql_defn_str.size()), &stmt_raw, nullptr); if (ret != SQLITE_OK) { return base::ErrStatus( "CREATE_FUNCTION[prototype=%s]: SQLite error when preparing " "statement %s", prototype_str.ToStdString().c_str(), sqlite_utils::FormatErrorMessage( stmt_raw, base::StringView(sql_defn_str), engine->db(), ret) .c_message()); } stmt.reset(stmt_raw); std::string function_name = prototype.function_name; std::unique_ptr created_fn_ctx( new CreatedFunction::Context{engine, std::move(prototype), *opt_return_type, std::move(sql_defn_str), std::move(stmt)}); RETURN_IF_ERROR(engine->RegisterSqlFunction( function_name.c_str(), created_argc, std::move(created_fn_ctx))); // CREATE_FUNCTION doesn't have a return value so just don't sent |out|. return base::OkStatus(); } } // namespace trace_processor } // namespace perfetto