blob: 3b68ea42e863cbacbde0ce6ba407e2357e2221fe [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sql/recover_module/module.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "sql/recover_module/cursor.h"
#include "sql/recover_module/parsing.h"
#include "sql/recover_module/table.h"
#include "third_party/sqlite/sqlite3.h"
// https://sqlite.org/vtab.html documents SQLite's virtual table module API.
namespace sql {
namespace recover {
namespace {
// SQLite module argument constants.
static constexpr int kModuleNameArgument = 0;
static constexpr int kVirtualTableDbNameArgument = 1;
static constexpr int kVirtualTableNameArgument = 2;
static constexpr int kBackingTableSpecArgument = 3;
static constexpr int kFirstColumnArgument = 4;
// Returns an empty vector on parse errors.
std::vector<RecoveredColumnSpec> ParseColumnSpecs(int argc,
const char* const* argv) {
std::vector<RecoveredColumnSpec> result;
DCHECK_GE(argc, kFirstColumnArgument);
result.reserve(argc - kFirstColumnArgument + 1);
for (int i = kFirstColumnArgument; i < argc; ++i) {
result.emplace_back(ParseColumnSpec(argv[i]));
if (!result.back().IsValid()) {
result.clear();
break;
}
}
return result;
}
int ModuleCreate(sqlite3* sqlite_db,
void* /* client_data */,
int argc,
const char* const* argv,
sqlite3_vtab** result_sqlite_table,
char** /* error_string */) {
DCHECK(sqlite_db != nullptr);
if (argc <= kFirstColumnArgument) {
// The recovery module needs at least one column specification.
return SQLITE_MISUSE;
}
DCHECK(argv != nullptr);
DCHECK(result_sqlite_table != nullptr);
// This module is always expected to be registered as "recover".
DCHECK_EQ("recover", base::StringPiece(argv[kModuleNameArgument]));
base::StringPiece db_name(argv[kVirtualTableDbNameArgument]);
if (db_name != "temp") {
// Refuse to create tables outside the "temp" database.
//
// This check is overly strict. The virtual table can be safely used on any
// temporary database (ATTACH '' AS other_temp). However, there is no easy
// way to determine if an attachment point corresponds to a temporary
// database, and "temp" is sufficient for Chrome's purposes.
return SQLITE_MISUSE;
}
base::StringPiece table_name(argv[kVirtualTableNameArgument]);
if (!table_name.starts_with("recover_")) {
// In the future, we may deploy UMA metrics that use the virtual table name
// to attribute recovery events to Chrome features. In preparation for that
// future, require all recovery table names to start with "recover_".
return SQLITE_MISUSE;
}
TargetTableSpec backing_table_spec =
ParseTableSpec(argv[kBackingTableSpecArgument]);
if (!backing_table_spec.IsValid()) {
// The parser concluded that the string specifying the backing table is
// invalid. This is definitely an error in the SQL using the virtual table.
return SQLITE_MISUSE;
}
std::vector<RecoveredColumnSpec> column_specs = ParseColumnSpecs(argc, argv);
if (column_specs.empty()) {
// The column specifications were invalid.
return SQLITE_MISUSE;
}
int sqlite_status;
std::unique_ptr<VirtualTable> table;
std::tie(sqlite_status, table) = VirtualTable::Create(
sqlite_db, std::move(backing_table_spec), std::move(column_specs));
if (sqlite_status != SQLITE_OK)
return sqlite_status;
{
std::string create_table_sql = table->ToCreateTableSql();
sqlite3_declare_vtab(sqlite_db, create_table_sql.c_str());
}
*result_sqlite_table = table->SqliteTable();
table.release(); // SQLite manages the lifetime of the table.
return SQLITE_OK;
}
int ModuleConnect(sqlite3* sqlite_db,
void* client_data,
int argc,
const char* const* argv,
sqlite3_vtab** result_sqlite_table,
char** error_string) {
// TODO(pwnall): Figure out if it's acceptable to have "recover" be an
// eponymous table. If so, use ModuleCreate instead of
// ModuleConnect in the entry point table.
return ModuleCreate(sqlite_db, client_data, argc, argv, result_sqlite_table,
error_string);
}
int ModuleBestIndex(sqlite3_vtab* sqlite_table,
sqlite3_index_info* index_info) {
DCHECK(sqlite_table != nullptr);
DCHECK(index_info != nullptr);
// The sqlite3_index_info structure is also documented at
// https://www.sqlite.org/draft/c3ref/index_info.html
for (int i = 0; i < index_info->nConstraint; ++i) {
if (index_info->aConstraint[i].usable == static_cast<char>(false))
continue;
// True asks SQLite to evaluate the constraint and pass the result to any
// follow-up xFilter() calls, via argc/argv.
index_info->aConstraintUsage[i].argvIndex = 0;
// True indicates that the virtual table will check the constraint.
index_info->aConstraintUsage[i].omit = false;
}
index_info->orderByConsumed = static_cast<int>(false);
// SQLite saves the sqlite_idx_info fields set here and passes the values to
// xFilter().
index_info->idxStr = nullptr;
index_info->idxNum = 0;
index_info->needToFreeIdxStr = static_cast<int>(false);
return SQLITE_OK;
}
int ModuleDisconnect(sqlite3_vtab* sqlite_table) {
DCHECK(sqlite_table != nullptr);
// SQLite takes ownership of the VirtualTable (which is passed around as a
// sqlite_table) in ModuleCreate() / ModuleConnect(). SQLite then calls
// ModuleDestroy() / ModuleDisconnect() to relinquish ownership of the
// VirtualTable. At this point, the table will not be used again, and can be
// destroyed.
VirtualTable* const table = VirtualTable::FromSqliteTable(sqlite_table);
delete table;
return SQLITE_OK;
}
int ModuleDestroy(sqlite3_vtab* sqlite_table) {
return ModuleDisconnect(sqlite_table);
}
int ModuleOpen(sqlite3_vtab* sqlite_table,
sqlite3_vtab_cursor** result_sqlite_cursor) {
DCHECK(sqlite_table != nullptr);
DCHECK(result_sqlite_cursor != nullptr);
VirtualTable* const table = VirtualTable::FromSqliteTable(sqlite_table);
VirtualCursor* const cursor = table->CreateCursor();
*result_sqlite_cursor = cursor->SqliteCursor();
return SQLITE_OK;
}
int ModuleClose(sqlite3_vtab_cursor* sqlite_cursor) {
DCHECK(sqlite_cursor != nullptr);
// SQLite takes ownership of the VirtualCursor (which is passed around as a
// sqlite_cursor) in ModuleOpen(). SQLite then calls ModuleClose() to
// relinquish ownership of the VirtualCursor. At this point, the cursor will
// not be used again, and can be destroyed.
VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
delete cursor;
return SQLITE_OK;
}
int ModuleFilter(sqlite3_vtab_cursor* sqlite_cursor,
int /* best_index_num */,
const char* /* best_index_str */,
int /* argc */,
sqlite3_value** /* argv */) {
DCHECK(sqlite_cursor != nullptr);
VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
return cursor->First();
}
int ModuleNext(sqlite3_vtab_cursor* sqlite_cursor) {
DCHECK(sqlite_cursor != nullptr);
VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
return cursor->Next();
}
int ModuleEof(sqlite3_vtab_cursor* sqlite_cursor) {
DCHECK(sqlite_cursor != nullptr);
VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
return cursor->IsValid() ? 0 : 1;
}
int ModuleColumn(sqlite3_vtab_cursor* sqlite_cursor,
sqlite3_context* result_context,
int column_index) {
DCHECK(sqlite_cursor != nullptr);
DCHECK(result_context != nullptr);
VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
DCHECK(cursor->IsValid()) << "SQLite called xRowid() without a valid cursor";
return cursor->ReadColumn(column_index, result_context);
}
int ModuleRowid(sqlite3_vtab_cursor* sqlite_cursor,
sqlite3_int64* result_rowid) {
DCHECK(sqlite_cursor != nullptr);
DCHECK(result_rowid != nullptr);
VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
DCHECK(cursor->IsValid()) << "SQLite called xRowid() without a valid cursor";
*result_rowid = cursor->RowId();
return SQLITE_OK;
}
// SQLite module API version supported by this implementation.
constexpr int kSqliteModuleApiVersion = 1;
// Entry points to the SQLite module.
constexpr sqlite3_module kSqliteModule = {
kSqliteModuleApiVersion,
&ModuleCreate,
&ModuleConnect,
&ModuleBestIndex,
&ModuleDisconnect,
&ModuleDestroy,
&ModuleOpen,
&ModuleClose,
&ModuleFilter,
&ModuleNext,
&ModuleEof,
&ModuleColumn,
&ModuleRowid,
/* xUpdate= */ nullptr,
/* xBegin= */ nullptr,
/* xSync= */ nullptr,
/* xCommit= */ nullptr,
/* xRollback= */ nullptr,
/* xFindFunction= */ nullptr,
/* xRename= */ nullptr,
};
} // namespace
int RegisterRecoverExtension(sqlite3* db) {
return sqlite3_create_module_v2(db, "recover", &kSqliteModule,
/* pClientData= */ nullptr,
/* xDestroy= */ nullptr);
}
} // namespace recover
} // namespace sql