| /* |
| * 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/table.h" |
| |
| #include <ctype.h> |
| #include <string.h> |
| |
| #include "perfetto/base/logging.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| |
| namespace { |
| |
| struct TableDescriptor { |
| Table::Factory factory; |
| const TraceStorage* storage = nullptr; |
| std::string name; |
| sqlite3_module module = {}; |
| }; |
| |
| Table* ToTable(sqlite3_vtab* vtab) { |
| return static_cast<Table*>(vtab); |
| } |
| |
| Table::RawCursor* ToCursor(sqlite3_vtab_cursor* cursor) { |
| return static_cast<Table::RawCursor*>(cursor); |
| } |
| |
| std::string TypeToString(Table::ColumnType type) { |
| switch (type) { |
| case Table::ColumnType::kString: |
| return "STRING"; |
| case Table::ColumnType::kUint: |
| return "UNSIGNED INT"; |
| case Table::ColumnType::kLong: |
| return "BIG INT"; |
| case Table::ColumnType::kInt: |
| return "INT"; |
| case Table::ColumnType::kDouble: |
| return "DOUBLE"; |
| case Table::ColumnType::kUnknown: |
| PERFETTO_FATAL("Cannot map unknown column type"); |
| } |
| PERFETTO_FATAL("Not reached"); // For gcc |
| } |
| |
| } // namespace |
| |
| // static |
| bool Table::debug = false; |
| |
| Table::Table() = default; |
| Table::~Table() = default; |
| |
| void Table::RegisterInternal(sqlite3* db, |
| const TraceStorage* storage, |
| const std::string& table_name, |
| bool read_write, |
| bool requires_args, |
| Factory factory) { |
| std::unique_ptr<TableDescriptor> desc(new TableDescriptor()); |
| desc->storage = storage; |
| desc->factory = factory; |
| desc->name = table_name; |
| sqlite3_module* module = &desc->module; |
| memset(module, 0, sizeof(*module)); |
| |
| auto create_fn = [](sqlite3* xdb, void* arg, int argc, |
| const char* const* argv, sqlite3_vtab** tab, char**) { |
| const TableDescriptor* xdesc = static_cast<const TableDescriptor*>(arg); |
| auto table = xdesc->factory(xdb, xdesc->storage); |
| |
| auto opt_schema = table->Init(argc, argv); |
| if (!opt_schema.has_value()) { |
| PERFETTO_ELOG("Failed to create schema (table %s)", xdesc->name.c_str()); |
| return SQLITE_ERROR; |
| } |
| |
| const auto& schema = opt_schema.value(); |
| auto create_stmt = schema.ToCreateTableStmt(); |
| PERFETTO_DLOG("Create table statement: %s", create_stmt.c_str()); |
| |
| int res = sqlite3_declare_vtab(xdb, create_stmt.c_str()); |
| if (res != SQLITE_OK) |
| return res; |
| |
| // Freed in xDisconnect(). |
| table->schema_ = std::move(schema); |
| table->name_ = xdesc->name; |
| *tab = table.release(); |
| |
| return SQLITE_OK; |
| }; |
| module->xCreate = create_fn; |
| module->xConnect = create_fn; |
| |
| auto destroy_fn = [](sqlite3_vtab* t) { |
| delete ToTable(t); |
| return SQLITE_OK; |
| }; |
| module->xDisconnect = destroy_fn; |
| module->xDestroy = destroy_fn; |
| |
| module->xOpen = [](sqlite3_vtab* t, sqlite3_vtab_cursor** c) { |
| return ToTable(t)->OpenInternal(c); |
| }; |
| |
| module->xClose = [](sqlite3_vtab_cursor* c) { |
| delete ToCursor(c); |
| return SQLITE_OK; |
| }; |
| |
| module->xBestIndex = [](sqlite3_vtab* t, sqlite3_index_info* i) { |
| return ToTable(t)->BestIndexInternal(i); |
| }; |
| |
| module->xFilter = [](sqlite3_vtab_cursor* c, int i, const char* s, int a, |
| sqlite3_value** v) { |
| return ToCursor(c)->Filter(i, s, a, v); |
| }; |
| module->xNext = [](sqlite3_vtab_cursor* c) { |
| return ToCursor(c)->cursor()->Next(); |
| }; |
| module->xEof = [](sqlite3_vtab_cursor* c) { |
| return ToCursor(c)->cursor()->Eof(); |
| }; |
| module->xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) { |
| return ToCursor(c)->cursor()->Column(a, b); |
| }; |
| |
| module->xRowid = [](sqlite3_vtab_cursor* c, sqlite3_int64* r) { |
| return ToCursor(c)->cursor()->RowId(r); |
| }; |
| |
| module->xFindFunction = |
| [](sqlite3_vtab* t, int, const char* name, |
| void (**fn)(sqlite3_context*, int, sqlite3_value**), |
| void** args) { return ToTable(t)->FindFunction(name, fn, args); }; |
| |
| if (read_write) { |
| module->xUpdate = [](sqlite3_vtab* t, int a, sqlite3_value** v, |
| sqlite3_int64* r) { |
| return ToTable(t)->Update(a, v, r); |
| }; |
| } |
| |
| int res = sqlite3_create_module_v2( |
| db, table_name.c_str(), module, desc.release(), |
| [](void* arg) { delete static_cast<TableDescriptor*>(arg); }); |
| PERFETTO_CHECK(res == SQLITE_OK); |
| |
| // Register virtual tables into an internal 'perfetto_tables' table. This is |
| // used for iterating through all the tables during a database export. Note |
| // that virtual tables requiring arguments aren't registered because they |
| // can't be automatically instantiated for exporting. |
| if (!requires_args) { |
| char* insert_sql = sqlite3_mprintf( |
| "INSERT INTO perfetto_tables(name) VALUES('%q')", table_name.c_str()); |
| char* error = nullptr; |
| sqlite3_exec(db, insert_sql, 0, 0, &error); |
| sqlite3_free(insert_sql); |
| if (error) { |
| PERFETTO_ELOG("Error registering table: %s", error); |
| sqlite3_free(error); |
| } |
| } |
| } |
| |
| int Table::OpenInternal(sqlite3_vtab_cursor** ppCursor) { |
| // Freed in xClose(). |
| *ppCursor = static_cast<sqlite3_vtab_cursor*>(new RawCursor(this)); |
| return SQLITE_OK; |
| } |
| |
| int Table::BestIndexInternal(sqlite3_index_info* idx) { |
| QueryConstraints query_constraints; |
| |
| for (int i = 0; i < idx->nOrderBy; i++) { |
| int column = idx->aOrderBy[i].iColumn; |
| bool desc = idx->aOrderBy[i].desc; |
| query_constraints.AddOrderBy(column, desc); |
| } |
| |
| for (int i = 0; i < idx->nConstraint; i++) { |
| const auto& cs = idx->aConstraint[i]; |
| if (!cs.usable) |
| continue; |
| query_constraints.AddConstraint(cs.iColumn, cs.op); |
| |
| // argvIndex is 1-based so use the current size of the vector. |
| int argv_index = static_cast<int>(query_constraints.constraints().size()); |
| idx->aConstraintUsage[i].argvIndex = argv_index; |
| } |
| |
| BestIndexInfo info; |
| info.omit.resize(query_constraints.constraints().size()); |
| |
| int ret = BestIndex(query_constraints, &info); |
| |
| if (Table::debug) { |
| PERFETTO_LOG( |
| "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%d", |
| name_.c_str(), query_constraints.ToNewSqlite3String().get(), |
| info.order_by_consumed, info.estimated_cost); |
| } |
| |
| if (ret != SQLITE_OK) |
| return ret; |
| |
| idx->orderByConsumed = info.order_by_consumed; |
| idx->estimatedCost = info.estimated_cost; |
| |
| size_t j = 0; |
| for (int i = 0; i < idx->nConstraint; i++) { |
| const auto& cs = idx->aConstraint[i]; |
| if (cs.usable) |
| idx->aConstraintUsage[i].omit = info.omit[j++]; |
| } |
| |
| if (!info.order_by_consumed) |
| query_constraints.ClearOrderBy(); |
| |
| idx->idxStr = query_constraints.ToNewSqlite3String().release(); |
| idx->needToFreeIdxStr = true; |
| idx->idxNum = ++best_index_num_; |
| |
| return SQLITE_OK; |
| } |
| |
| int Table::FindFunction(const char*, FindFunctionFn, void**) { |
| return 0; |
| } |
| |
| int Table::Update(int, sqlite3_value**, sqlite3_int64*) { |
| return SQLITE_READONLY; |
| } |
| |
| Table::RawCursor::RawCursor(Table* table) : table_(table) {} |
| |
| int Table::RawCursor::Filter(int idxNum, |
| const char* idxStr, |
| int argc, |
| sqlite3_value** argv) { |
| auto* table = ToTable(this->pVtab); |
| bool cache_hit = true; |
| if (idxNum != table->qc_hash_) { |
| table->qc_cache_ = QueryConstraints::FromString(idxStr); |
| table->qc_hash_ = idxNum; |
| cache_hit = false; |
| } |
| if (Table::debug) { |
| PERFETTO_LOG("[%s::Filter] constraints=%s argc=%d cache_hit=%d", |
| table->name_.c_str(), idxStr, argc, cache_hit); |
| } |
| PERFETTO_DCHECK(table->qc_cache_.constraints().size() == |
| static_cast<size_t>(argc)); |
| cursor_ = table_->CreateCursor(table->qc_cache_, argv); |
| return !cursor_ ? SQLITE_ERROR : SQLITE_OK; |
| } |
| |
| Table::Cursor::~Cursor() = default; |
| |
| int Table::Cursor::RowId(sqlite3_int64*) { |
| return SQLITE_ERROR; |
| } |
| |
| Table::Column::Column(size_t index, |
| std::string name, |
| ColumnType type, |
| bool hidden) |
| : index_(index), name_(name), type_(type), hidden_(hidden) {} |
| |
| Table::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()); |
| } |
| } |
| |
| Table::Schema::Schema() = default; |
| Table::Schema::Schema(const Schema&) = default; |
| Table::Schema& Table::Schema::operator=(const Schema&) = default; |
| |
| std::string Table::Schema::ToCreateTableStmt() const { |
| std::string stmt = "CREATE TABLE x("; |
| for (const auto& col : columns_) { |
| stmt += " " + col.name() + " " + TypeToString(col.type()); |
| 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 |