|  | /* | 
|  | * Copyright (C) 2018 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_table.h" | 
|  |  | 
|  | #include <string.h> | 
|  | #include <algorithm> | 
|  | #include <cinttypes> | 
|  | #include <map> | 
|  |  | 
|  | #include "perfetto/base/logging.h" | 
|  | #include "src/trace_processor/tp_metatrace.h" | 
|  |  | 
|  | namespace perfetto { | 
|  | namespace trace_processor { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | std::string TypeToSqlString(SqlValue::Type type) { | 
|  | switch (type) { | 
|  | case SqlValue::Type::kString: | 
|  | return "TEXT"; | 
|  | case SqlValue::Type::kLong: | 
|  | return "BIGINT"; | 
|  | case SqlValue::Type::kDouble: | 
|  | return "DOUBLE"; | 
|  | case SqlValue::Type::kBytes: | 
|  | return "BLOB"; | 
|  | case SqlValue::Type::kNull: | 
|  | PERFETTO_FATAL("Cannot map unknown column type"); | 
|  | } | 
|  | PERFETTO_FATAL("Not reached");  // For gcc | 
|  | } | 
|  |  | 
|  | std::string OpToDebugString(int op) { | 
|  | switch (op) { | 
|  | case SQLITE_INDEX_CONSTRAINT_EQ: | 
|  | return "="; | 
|  | case SQLITE_INDEX_CONSTRAINT_NE: | 
|  | return "!="; | 
|  | case SQLITE_INDEX_CONSTRAINT_GE: | 
|  | return ">="; | 
|  | case SQLITE_INDEX_CONSTRAINT_GT: | 
|  | return ">"; | 
|  | case SQLITE_INDEX_CONSTRAINT_LE: | 
|  | return "<="; | 
|  | case SQLITE_INDEX_CONSTRAINT_LT: | 
|  | return "<"; | 
|  | case SQLITE_INDEX_CONSTRAINT_LIKE: | 
|  | return "like"; | 
|  | case SQLITE_INDEX_CONSTRAINT_ISNULL: | 
|  | return "is null"; | 
|  | case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: | 
|  | return "is not null"; | 
|  | case SQLITE_INDEX_CONSTRAINT_GLOB: | 
|  | return "glob"; | 
|  | case SQLITE_INDEX_CONSTRAINT_LIMIT: | 
|  | return "limit"; | 
|  | case SQLITE_INDEX_CONSTRAINT_OFFSET: | 
|  | return "offset"; | 
|  | case SqliteTable::CustomFilterOpcode::kSourceGeqOpCode: | 
|  | return "source_geq"; | 
|  | default: | 
|  | PERFETTO_FATAL("Operator to string conversion not impemented for %d", op); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ConstraintsToString(const QueryConstraints& qc, | 
|  | const SqliteTable::Schema& schema, | 
|  | std::string& out) { | 
|  | bool is_first = true; | 
|  | for (const auto& cs : qc.constraints()) { | 
|  | if (!is_first) { | 
|  | out.append(","); | 
|  | } | 
|  | out.append(schema.columns()[static_cast<size_t>(cs.column)].name()); | 
|  | out.append(" "); | 
|  | out.append(OpToDebugString(cs.op)); | 
|  | is_first = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | void OrderByToString(const QueryConstraints& qc, | 
|  | const SqliteTable::Schema& schema, | 
|  | std::string& out) { | 
|  | bool is_first = true; | 
|  | for (const auto& ob : qc.order_by()) { | 
|  | if (!is_first) { | 
|  | out.append(","); | 
|  | } | 
|  | out.append(schema.columns()[static_cast<size_t>(ob.iColumn)].name()); | 
|  | out.append(" "); | 
|  | out.append(std::to_string(ob.desc)); | 
|  | is_first = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::string QcDebugStr(const QueryConstraints& qc, | 
|  | const SqliteTable::Schema& schema) { | 
|  | std::string str_result; | 
|  | str_result.reserve(512); | 
|  |  | 
|  | str_result.append("C"); | 
|  | str_result.append(std::to_string(qc.constraints().size())); | 
|  | str_result.append(","); | 
|  | ConstraintsToString(qc, schema, str_result); | 
|  | str_result.append(";"); | 
|  |  | 
|  | str_result.append("O"); | 
|  | str_result.append(std::to_string(qc.order_by().size())); | 
|  | str_result.append(","); | 
|  | OrderByToString(qc, schema, str_result); | 
|  | str_result.append(";"); | 
|  |  | 
|  | str_result.append("U"); | 
|  | str_result.append(std::to_string(qc.cols_used())); | 
|  |  | 
|  | return str_result; | 
|  | } | 
|  |  | 
|  | void WriteQueryConstraintsToMetatrace(metatrace::Record* r, | 
|  | const QueryConstraints& qc, | 
|  | const SqliteTable::Schema& schema) { | 
|  | r->AddArg("constraint_count", std::to_string(qc.constraints().size())); | 
|  | std::string constraints; | 
|  | ConstraintsToString(qc, schema, constraints); | 
|  | r->AddArg("constraints", constraints); | 
|  | r->AddArg("order_by_count", std::to_string(qc.order_by().size())); | 
|  | std::string order_by; | 
|  | OrderByToString(qc, schema, order_by); | 
|  | r->AddArg("order_by", order_by); | 
|  | r->AddArg("columns_used", std::to_string(qc.cols_used())); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | bool SqliteTable::debug = false; | 
|  |  | 
|  | SqliteTable::SqliteTable() = default; | 
|  | SqliteTable::~SqliteTable() = default; | 
|  |  | 
|  | int SqliteTable::OpenInternal(sqlite3_vtab_cursor** ppCursor) { | 
|  | // Freed in xClose(). | 
|  | *ppCursor = static_cast<sqlite3_vtab_cursor*>(CreateCursor().release()); | 
|  | return SQLITE_OK; | 
|  | } | 
|  |  | 
|  | int SqliteTable::BestIndexInternal(sqlite3_index_info* idx) { | 
|  | QueryConstraints qc(idx->colUsed); | 
|  |  | 
|  | for (int i = 0; i < idx->nConstraint; i++) { | 
|  | const auto& cs = idx->aConstraint[i]; | 
|  | if (!cs.usable) | 
|  | continue; | 
|  | qc.AddConstraint(cs.iColumn, cs.op, i); | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < idx->nOrderBy; i++) { | 
|  | int column = idx->aOrderBy[i].iColumn; | 
|  | bool desc = idx->aOrderBy[i].desc; | 
|  | qc.AddOrderBy(column, desc); | 
|  | } | 
|  |  | 
|  | int ret = ModifyConstraints(&qc); | 
|  | if (ret != SQLITE_OK) | 
|  | return ret; | 
|  |  | 
|  | BestIndexInfo info; | 
|  | info.estimated_cost = idx->estimatedCost; | 
|  | info.estimated_rows = idx->estimatedRows; | 
|  | info.sqlite_omit_constraint.resize(qc.constraints().size()); | 
|  |  | 
|  | ret = BestIndex(qc, &info); | 
|  |  | 
|  | if (ret != SQLITE_OK) | 
|  | return ret; | 
|  |  | 
|  | idx->orderByConsumed = qc.order_by().empty() || info.sqlite_omit_order_by; | 
|  | idx->estimatedCost = info.estimated_cost; | 
|  | idx->estimatedRows = info.estimated_rows; | 
|  |  | 
|  | // First pass: mark all constraints as omitted to ensure that any pruned | 
|  | // constraints are not checked for by SQLite. | 
|  | for (int i = 0; i < idx->nConstraint; ++i) { | 
|  | auto& u = idx->aConstraintUsage[i]; | 
|  | u.omit = true; | 
|  | } | 
|  |  | 
|  | // Second pass: actually set the correct omit and index values for all | 
|  | // retained constraints. | 
|  | for (uint32_t i = 0; i < qc.constraints().size(); ++i) { | 
|  | auto& u = idx->aConstraintUsage[qc.constraints()[i].a_constraint_idx]; | 
|  | u.omit = info.sqlite_omit_constraint[i]; | 
|  | u.argvIndex = static_cast<int>(i) + 1; | 
|  | } | 
|  |  | 
|  | PERFETTO_TP_TRACE( | 
|  | metatrace::Category::QUERY, "SQLITE_TABLE_BEST_INDEX", | 
|  | [&](metatrace::Record* r) { | 
|  | r->AddArg("name", name_); | 
|  | WriteQueryConstraintsToMetatrace(r, qc, schema()); | 
|  | r->AddArg("order_by_consumed", std::to_string(idx->orderByConsumed)); | 
|  | r->AddArg("estimated_cost", std::to_string(idx->estimatedCost)); | 
|  | r->AddArg("estimated_rows", | 
|  | std::to_string(static_cast<int64_t>(idx->estimatedRows))); | 
|  | }); | 
|  |  | 
|  | auto out_qc_str = qc.ToNewSqlite3String(); | 
|  | if (SqliteTable::debug) { | 
|  | PERFETTO_LOG( | 
|  | "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%f " | 
|  | "estimatedRows=%" PRId64, | 
|  | name_.c_str(), QcDebugStr(qc, schema()).c_str(), idx->orderByConsumed, | 
|  | idx->estimatedCost, static_cast<int64_t>(idx->estimatedRows)); | 
|  | } | 
|  |  | 
|  | idx->idxStr = out_qc_str.release(); | 
|  | idx->needToFreeIdxStr = true; | 
|  | idx->idxNum = ++best_index_num_; | 
|  |  | 
|  | return SQLITE_OK; | 
|  | } | 
|  |  | 
|  | int SqliteTable::ModifyConstraints(QueryConstraints*) { | 
|  | return SQLITE_OK; | 
|  | } | 
|  |  | 
|  | int SqliteTable::FindFunction(const char*, FindFunctionFn*, void**) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int SqliteTable::Update(int, sqlite3_value**, sqlite3_int64*) { | 
|  | return SQLITE_READONLY; | 
|  | } | 
|  |  | 
|  | bool SqliteTable::ReadConstraints(int idxNum, const char* idxStr, int argc) { | 
|  | bool cache_hit = true; | 
|  | if (idxNum != qc_hash_) { | 
|  | qc_cache_ = QueryConstraints::FromString(idxStr); | 
|  | qc_hash_ = idxNum; | 
|  | cache_hit = false; | 
|  | } | 
|  |  | 
|  | PERFETTO_TP_TRACE(metatrace::Category::QUERY, "SQLITE_TABLE_READ_CONSTRAINTS", | 
|  | [&](metatrace::Record* r) { | 
|  | r->AddArg("cache_hit", std::to_string(cache_hit)); | 
|  | r->AddArg("name", name_); | 
|  | WriteQueryConstraintsToMetatrace(r, qc_cache_, schema_); | 
|  | r->AddArg("raw_constraints", idxStr); | 
|  | r->AddArg("argc", std::to_string(argc)); | 
|  | }); | 
|  |  | 
|  | // Logging this every ReadConstraints just leads to log spam on joins making | 
|  | // it unusable. Instead, only print this out when we miss the cache (which | 
|  | // happens precisely when the constraint set from SQLite changes.) | 
|  | if (SqliteTable::debug && !cache_hit) { | 
|  | PERFETTO_LOG("[%s::ParseConstraints] constraints=%s argc=%d", name_.c_str(), | 
|  | QcDebugStr(qc_cache_, schema_).c_str(), argc); | 
|  | } | 
|  | return cache_hit; | 
|  | } | 
|  |  | 
|  | SqliteTable::Cursor::Cursor(SqliteTable* table) : table_(table) { | 
|  | // This is required to prevent us from leaving this field uninitialised if | 
|  | // we ever move construct the Cursor. | 
|  | pVtab = table; | 
|  | } | 
|  | SqliteTable::Cursor::~Cursor() = default; | 
|  |  | 
|  | int SqliteTable::Cursor::RowId(sqlite3_int64*) { | 
|  | return SQLITE_ERROR; | 
|  | } | 
|  |  | 
|  | SqliteTable::Column::Column(size_t index, | 
|  | std::string name, | 
|  | SqlValue::Type type, | 
|  | bool hidden) | 
|  | : index_(index), name_(name), type_(type), hidden_(hidden) {} | 
|  |  | 
|  | SqliteTable::Schema::Schema(std::vector<Column> columns, | 
|  | std::vector<size_t> primary_keys) | 
|  | : columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) { | 
|  | for (size_t i = 0; i < columns_.size(); i++) { | 
|  | PERFETTO_CHECK(columns_[i].index() == i); | 
|  | } | 
|  | for (auto key : primary_keys_) { | 
|  | PERFETTO_CHECK(key < columns_.size()); | 
|  | } | 
|  | } | 
|  |  | 
|  | SqliteTable::Schema::Schema() = default; | 
|  | SqliteTable::Schema::Schema(const Schema&) = default; | 
|  | SqliteTable::Schema& SqliteTable::Schema::operator=(const Schema&) = default; | 
|  |  | 
|  | std::string SqliteTable::Schema::ToCreateTableStmt() const { | 
|  | std::string stmt = "CREATE TABLE x("; | 
|  | for (size_t i = 0; i < columns_.size(); ++i) { | 
|  | const Column& col = columns_[i]; | 
|  | stmt += " " + col.name(); | 
|  |  | 
|  | if (col.type() != SqlValue::Type::kNull) { | 
|  | stmt += " " + TypeToSqlString(col.type()); | 
|  | } else if (std::find(primary_keys_.begin(), primary_keys_.end(), i) != | 
|  | primary_keys_.end()) { | 
|  | PERFETTO_FATAL("Unknown type for primary key column %s", | 
|  | col.name().c_str()); | 
|  | } | 
|  | if (col.hidden()) { | 
|  | stmt += " HIDDEN"; | 
|  | } | 
|  | stmt += ","; | 
|  | } | 
|  | stmt += " PRIMARY KEY("; | 
|  | for (size_t i = 0; i < primary_keys_.size(); i++) { | 
|  | if (i != 0) | 
|  | stmt += ", "; | 
|  | stmt += columns_[primary_keys_[i]].name(); | 
|  | } | 
|  | stmt += ")) WITHOUT ROWID;"; | 
|  | return stmt; | 
|  | } | 
|  |  | 
|  | }  // namespace trace_processor | 
|  | }  // namespace perfetto |