blob: c4077a5d2230896de38c6b803fb67f1051ea8d10 [file] [log] [blame]
/*
* Copyright (C) 2022 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/sqlite_utils.h"
#include <bitset>
#include <sstream>
#include "perfetto/base/status.h"
namespace perfetto {
namespace trace_processor {
namespace sqlite_utils {
namespace internal {
namespace {
std::string ToExpectedTypesString(ExpectedTypesSet expected_types) {
PERFETTO_CHECK(expected_types.any());
std::stringstream ss;
if (expected_types.count() > 1) {
ss << "any of ";
}
bool add_separator = false;
for (size_t i = 0; i < expected_types.size(); ++i) {
if (expected_types[i]) {
ss << (add_separator ? ", " : "")
<< SqliteTypeToFriendlyString(static_cast<SqlValue::Type>(i));
add_separator = true;
}
}
return ss.str();
}
} // namespace
base::Status InvalidArgumentTypeError(const char* argument_name,
size_t arg_index,
SqlValue::Type actual_type,
ExpectedTypesSet expected_types) {
return ToInvalidArgumentError(
argument_name, arg_index,
base::ErrStatus("does not have expected type. Expected %s but found %s",
ToExpectedTypesString(expected_types).c_str(),
SqliteTypeToFriendlyString(actual_type)));
}
base::StatusOr<SqlValue> ExtractArgument(size_t argc,
sqlite3_value** argv,
const char* argument_name,
size_t arg_index,
ExpectedTypesSet expected_types) {
if (arg_index >= argc) {
return MissingArgumentError(argument_name);
}
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);
}
} // namespace internal
std::wstring SqliteValueToWString(sqlite3_value* value) {
PERFETTO_CHECK(sqlite3_value_type(value) == SQLITE_TEXT);
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);
}
base::Status GetColumnsForTable(sqlite3* db,
const std::string& raw_table_name,
std::vector<SqliteTable::Column>& columns) {
PERFETTO_DCHECK(columns.empty());
char sql[1024];
const char kRawSql[] = "SELECT name, type from pragma_table_info(\"%s\")";
// Support names which are table valued functions with arguments.
std::string table_name = raw_table_name.substr(0, raw_table_name.find('('));
size_t n = base::SprintfTrunc(sql, sizeof(sql), kRawSql, table_name.c_str());
PERFETTO_DCHECK(n > 0);
sqlite3_stmt* raw_stmt = nullptr;
int err =
sqlite3_prepare_v2(db, sql, static_cast<int>(n), &raw_stmt, nullptr);
if (err != SQLITE_OK) {
return base::ErrStatus("Preparing database failed");
}
ScopedStmt stmt(raw_stmt);
PERFETTO_DCHECK(sqlite3_column_count(*stmt) == 2);
for (;;) {
err = sqlite3_step(raw_stmt);
if (err == SQLITE_DONE)
break;
if (err != SQLITE_ROW) {
return base::ErrStatus("Querying schema of table %s failed",
raw_table_name.c_str());
}
const char* name =
reinterpret_cast<const char*>(sqlite3_column_text(*stmt, 0));
const char* raw_type =
reinterpret_cast<const char*>(sqlite3_column_text(*stmt, 1));
if (!name || !raw_type || !*name) {
return base::ErrStatus("Schema for %s has invalid column values",
raw_table_name.c_str());
}
SqlValue::Type type;
if (base::CaseInsensitiveEqual(raw_type, "STRING") ||
base::CaseInsensitiveEqual(raw_type, "TEXT")) {
type = SqlValue::Type::kString;
} else if (base::CaseInsensitiveEqual(raw_type, "DOUBLE")) {
type = SqlValue::Type::kDouble;
} else if (base::CaseInsensitiveEqual(raw_type, "BIG INT") ||
base::CaseInsensitiveEqual(raw_type, "BIGINT") ||
base::CaseInsensitiveEqual(raw_type, "UNSIGNED INT") ||
base::CaseInsensitiveEqual(raw_type, "INT") ||
base::CaseInsensitiveEqual(raw_type, "BOOLEAN") ||
base::CaseInsensitiveEqual(raw_type, "INTEGER")) {
type = SqlValue::Type::kLong;
} else if (!*raw_type) {
PERFETTO_DLOG("Unknown column type for %s %s", raw_table_name.c_str(),
name);
type = SqlValue::Type::kNull;
} else {
return base::ErrStatus("Unknown column type '%s' on table %s", raw_type,
raw_table_name.c_str());
}
columns.emplace_back(columns.size(), name, type);
}
// Catch mis-spelt table names.
//
// A SELECT on pragma_table_info() returns no rows if the
// table that was queried is not present.
if (columns.empty()) {
return base::ErrStatus("Unknown table or view name '%s'",
raw_table_name.c_str());
}
return base::OkStatus();
}
const char* SqliteTypeToFriendlyString(SqlValue::Type type) {
switch (type) {
case SqlValue::Type::kNull:
return "NULL";
case SqlValue::Type::kLong:
return "BOOL/INT/UINT/LONG";
case SqlValue::Type::kDouble:
return "FLOAT/DOUBLE";
case SqlValue::Type::kString:
return "STRING";
case SqlValue::Type::kBytes:
return "BYTES/PROTO";
}
PERFETTO_FATAL("For GCC");
}
base::Status TypeCheckSqliteValue(sqlite3_value* value,
SqlValue::Type expected_type) {
return TypeCheckSqliteValue(value, expected_type,
SqliteTypeToFriendlyString(expected_type));
}
base::Status TypeCheckSqliteValue(sqlite3_value* value,
SqlValue::Type expected_type,
const char* expected_type_str) {
SqlValue::Type actual_type =
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",
expected_type_str, SqliteTypeToFriendlyString(actual_type));
}
return base::OkStatus();
}
template <typename T>
base::Status ExtractFromSqlValueInt(const SqlValue& value,
std::optional<T>& out) {
if (value.is_null()) {
out = std::nullopt;
return base::OkStatus();
}
if (value.type != SqlValue::kLong) {
return base::ErrStatus(
"value has type %s which does not match the expected type %s",
SqliteTypeToFriendlyString(value.type),
SqliteTypeToFriendlyString(SqlValue::kLong));
}
int64_t res = value.AsLong();
if (res > std::numeric_limits<T>::max() ||
res < std::numeric_limits<T>::min()) {
return base::ErrStatus("value %ld does not fit inside the range [%ld, %ld]",
static_cast<long>(res),
static_cast<long>(std::numeric_limits<T>::min()),
static_cast<long>(std::numeric_limits<T>::max()));
}
out = static_cast<T>(res);
return base::OkStatus();
}
base::Status ExtractFromSqlValue(const SqlValue& value,
std::optional<int64_t>& out) {
return ExtractFromSqlValueInt(value, out);
}
base::Status ExtractFromSqlValue(const SqlValue& value,
std::optional<int32_t>& out) {
return ExtractFromSqlValueInt(value, out);
}
base::Status ExtractFromSqlValue(const SqlValue& value,
std::optional<uint32_t>& out) {
return ExtractFromSqlValueInt(value, out);
}
base::Status ExtractFromSqlValue(const SqlValue& value,
std::optional<double>& out) {
if (value.is_null()) {
out = std::nullopt;
return base::OkStatus();
}
if (value.type != SqlValue::kDouble) {
return base::ErrStatus(
"value has type %s which does not match the expected type %s",
SqliteTypeToFriendlyString(value.type),
SqliteTypeToFriendlyString(SqlValue::kDouble));
}
out = value.AsDouble();
return base::OkStatus();
}
base::Status ExtractFromSqlValue(const SqlValue& value,
std::optional<const char*>& out) {
if (value.is_null()) {
out = std::nullopt;
return base::OkStatus();
}
if (value.type != SqlValue::kString) {
return base::ErrStatus(
"value has type %s which does not match the expected type %s",
SqliteTypeToFriendlyString(value.type),
SqliteTypeToFriendlyString(SqlValue::kString));
}
out = value.AsString();
return base::OkStatus();
}
base::Status MissingArgumentError(const char* argument_name) {
return base::ErrStatus("argument missing: %s", argument_name);
}
base::Status ToInvalidArgumentError(const char* argument_name,
size_t arg_index,
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