| /* |
| * 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 <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/status.h" |
| #include "perfetto/ext/base/status_or.h" |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/trace_processor/basic_types.h" |
| #include "src/trace_processor/sqlite/scoped_db.h" |
| |
| namespace perfetto::trace_processor::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 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 {reinterpret_cast<const wchar_t*>(sqlite3_value_text16(value)), count}; |
| } |
| |
| base::Status GetColumnsForTable( |
| sqlite3* db, |
| const std::string& raw_table_name, |
| std::vector<std::pair<SqlValue::Type, std::string>>& 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 (base::CaseInsensitiveEqual(raw_type, "BLOB")) { |
| type = SqlValue::Type::kBytes; |
| } 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(type, name); |
| } |
| |
| // 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 CheckArgCount(const char* function_name, |
| size_t argc, |
| size_t expected_argc) { |
| if (argc == expected_argc) { |
| return base::OkStatus(); |
| } |
| return base::ErrStatus("%s: expected %zu arguments, got %zu", function_name, |
| expected_argc, argc); |
| } |
| |
| base::StatusOr<int64_t> ExtractIntArg(const char* function_name, |
| const char* arg_name, |
| sqlite3_value* sql_value) { |
| SqlValue value = SqliteValueToSqlValue(sql_value); |
| std::optional<int64_t> result; |
| |
| base::Status status = ExtractFromSqlValue(value, result); |
| if (!status.ok()) { |
| return base::ErrStatus("%s(%s): %s", function_name, arg_name, |
| status.message().c_str()); |
| } |
| PERFETTO_CHECK(result); |
| return *result; |
| } |
| |
| base::StatusOr<double> ExtractDoubleArg(const char* function_name, |
| const char* arg_name, |
| sqlite3_value* sql_value) { |
| SqlValue value = SqliteValueToSqlValue(sql_value); |
| std::optional<double> result; |
| |
| base::Status status = ExtractFromSqlValue(value, result); |
| if (!status.ok()) { |
| return base::ErrStatus("%s(%s): %s", function_name, arg_name, |
| status.message().c_str()); |
| } |
| PERFETTO_CHECK(result); |
| return *result; |
| } |
| |
| base::StatusOr<std::string> ExtractStringArg(const char* function_name, |
| const char* arg_name, |
| sqlite3_value* sql_value) { |
| SqlValue value = SqliteValueToSqlValue(sql_value); |
| std::optional<const char*> result; |
| |
| base::Status status = ExtractFromSqlValue(value, result); |
| if (!status.ok()) { |
| return base::ErrStatus("%s(%s): %s", function_name, arg_name, |
| status.message().c_str()); |
| } |
| PERFETTO_CHECK(result); |
| return std::string(*result); |
| } |
| |
| 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 perfetto::trace_processor::sqlite::utils |