blob: a0c5ce9977a86f3a336484967dde41da956d8e03 [file] [log] [blame]
/*
* 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