blob: 4c987725e63e556db6baf48d75e228da6f682335 [file] [log] [blame]
// Copyright 2016 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 "components/password_manager/core/browser/sql_table_builder.h"
#include <algorithm>
#include <set>
#include <utility>
#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "sql/database.h"
#include "sql/transaction.h"
namespace password_manager {
namespace {
// Appends |name| to |list_of_names|, separating items with ", ".
void Append(const std::string& name, std::string* list_of_names) {
if (list_of_names->empty())
*list_of_names = name;
else
*list_of_names += ", " + name;
}
} // namespace
// static
unsigned constexpr SQLTableBuilder::kInvalidVersion;
struct SQLTableBuilder::Column {
std::string name;
std::string type;
// Whether this column is part of the table's PRIMARY KEY constraint.
bool part_of_primary_key;
// Whether this column is part of the table's UNIQUE constraint.
bool part_of_unique_key;
// The first version this column is part of.
unsigned min_version;
// The last version this column is part of. The value of kInvalidVersion
// means that it is part of all versions since |min_version|.
unsigned max_version;
// Renaming of a column is stored as a sequence of one removed and one added
// column in |columns_|. To distinguish it from an unrelated removal and
// addition, the following bit is set to true for the added columns which
// are part of renaming. Those columns will get the data of their
// predecessors. If the bit is false, the column will be filled with the
// default value on creation.
bool gets_previous_data;
};
struct SQLTableBuilder::Index {
// The name of this index.
std::string name;
// The names of columns this index is built from.
std::vector<std::string> columns;
// The first version this index is part of.
unsigned min_version;
// The last version this index is part of. The value of kInvalidVersion
// means that it is part of all versions since |min_version|.
unsigned max_version;
};
SQLTableBuilder::SQLTableBuilder(const std::string& table_name)
: table_name_(table_name) {}
SQLTableBuilder::~SQLTableBuilder() = default;
void SQLTableBuilder::AddColumn(std::string name, std::string type) {
DCHECK(FindLastColumnByName(name) == columns_.rend());
columns_.push_back({std::move(name), std::move(type), false, false,
sealed_version_ + 1, kInvalidVersion, false});
}
void SQLTableBuilder::AddColumnToPrimaryKey(std::string name,
std::string type) {
AddColumn(std::move(name), std::move(type));
columns_.back().part_of_primary_key = true;
}
void SQLTableBuilder::AddColumnToUniqueKey(std::string name, std::string type) {
AddColumn(std::move(name), std::move(type));
columns_.back().part_of_unique_key = true;
}
void SQLTableBuilder::RenameColumn(const std::string& old_name,
const std::string& new_name) {
auto old_column = FindLastColumnByName(old_name);
DCHECK(old_column != columns_.rend());
if (old_name == new_name) // The easy case.
return;
DCHECK(FindLastColumnByName(new_name) == columns_.rend());
// Check there is no index in the current version that references |old_name|.
DCHECK(std::none_of(indices_.begin(), indices_.end(),
[&old_name](const Index& index) {
return index.max_version == kInvalidVersion &&
base::ContainsValue(index.columns, old_name);
}));
if (sealed_version_ != kInvalidVersion &&
old_column->min_version <= sealed_version_) {
// This column exists in the last sealed version. Therefore it cannot be
// just replaced, it needs to be kept for generating the migration code.
Column new_column = {new_name,
old_column->type,
old_column->part_of_primary_key,
old_column->part_of_unique_key,
sealed_version_ + 1,
kInvalidVersion,
true};
old_column->max_version = sealed_version_;
auto past_old =
old_column.base(); // Points one element after |old_column|.
columns_.insert(past_old, std::move(new_column));
} else {
// This column was just introduced in the currently unsealed version. To
// rename it, it is enough just to modify the entry in columns_.
old_column->name = new_name;
}
}
// Removes column |name|. |name| must have been added in the past.
void SQLTableBuilder::DropColumn(const std::string& name) {
auto column = FindLastColumnByName(name);
DCHECK(column != columns_.rend());
// Check there is no index in the current version that references |old_name|.
DCHECK(std::none_of(indices_.begin(), indices_.end(),
[&name](const Index& index) {
return index.max_version == kInvalidVersion &&
base::ContainsValue(index.columns, name);
}));
if (sealed_version_ != kInvalidVersion &&
column->min_version <= sealed_version_) {
// This column exists in the last sealed version. Therefore it cannot be
// just deleted, it needs to be kept for generating the migration code.
column->max_version = sealed_version_;
} else {
// This column was just introduced in the currently unsealed version. It
// can be just erased from |columns_|.
columns_.erase(
--(column.base())); // base() points one element after |column|.
}
}
void SQLTableBuilder::AddIndex(std::string name,
std::vector<std::string> columns) {
DCHECK(!columns.empty());
// Check if all entries of |columns| are unique.
DCHECK_EQ(std::set<std::string>(columns.begin(), columns.end()).size(),
columns.size());
// |name| must not have been added before.
DCHECK(FindLastIndexByName(name) == indices_.rend());
// Check that all referenced columns are present in the last version by making
// sure that the inner predicate applies to all columns names in |columns|.
DCHECK(std::all_of(
columns.begin(), columns.end(), [this](const std::string& column_name) {
// Check if there is any column with the required name which is also
// present in the latest version. Note that we don't require the last
// version to be sealed.
return std::any_of(columns_.begin(), columns_.end(),
[&column_name](const Column& column) {
return column.name == column_name &&
column.max_version == kInvalidVersion;
});
}));
indices_.push_back({std::move(name), std::move(columns), sealed_version_ + 1,
kInvalidVersion});
}
void SQLTableBuilder::RenameIndex(const std::string& old_name,
const std::string& new_name) {
auto old_index = FindLastIndexByName(old_name);
// Check that there is an index with the old name.
DCHECK(old_index != indices_.rend());
if (old_name == new_name) // The easy case.
return;
// Check that there is no index with the new name.
DCHECK(FindLastIndexByName(new_name) == indices_.rend());
// Check that there is at least one sealed version.
DCHECK_NE(sealed_version_, kInvalidVersion);
// Check that the old index has been added before the last version was sealed.
DCHECK_LE(old_index->min_version, sealed_version_);
// Check that the old index has not been renamed or deleted yet.
DCHECK_EQ(old_index->max_version, kInvalidVersion);
// This index exists in the last sealed version. Therefore it cannot be
// just replaced, it needs to be kept for generating the migration code.
old_index->max_version = sealed_version_;
// Add the new index.
indices_.push_back(
{new_name, old_index->columns, sealed_version_ + 1, kInvalidVersion});
}
void SQLTableBuilder::DropIndex(const std::string& name) {
auto index = FindLastIndexByName(name);
// Check that an index with the name exists.
DCHECK(index != indices_.rend());
// Check that this index exists in the last sealed version and hasn't been
// renamed or deleted yet.
// Check that there is at least one sealed version.
DCHECK_NE(sealed_version_, kInvalidVersion);
// Check that the index has been added before the last version was sealed.
DCHECK_LE(index->min_version, sealed_version_);
// Check that the index has not been renamed or deleted yet.
DCHECK_EQ(index->max_version, kInvalidVersion);
// This index exists in the last sealed version. Therefore it cannot be
// just deleted, it needs to be kept for generating the migration code.
index->max_version = sealed_version_;
}
std::string SQLTableBuilder::ComputeConstraints(unsigned version) const {
std::string primary_key;
std::string unique_key;
for (const Column& column : columns_) {
// Ignore dropped columns.
if (column.max_version < version)
continue;
// Ignore columns columns from future versions.
if (column.min_version > version)
continue;
if (column.part_of_primary_key)
Append(column.name, &primary_key);
if (column.part_of_unique_key)
Append(column.name, &unique_key);
}
std::string constraints;
if (!primary_key.empty())
Append("PRIMARY KEY (" + primary_key + ")", &constraints);
if (!unique_key.empty())
Append("UNIQUE (" + unique_key + ")", &constraints);
return constraints;
}
unsigned SQLTableBuilder::SealVersion() {
return ++sealed_version_;
}
bool SQLTableBuilder::MigrateFrom(unsigned old_version,
sql::Database* db) const {
for (; old_version < sealed_version_; ++old_version) {
if (!MigrateToNextFrom(old_version, db))
return false;
}
return true;
}
bool SQLTableBuilder::CreateTable(sql::Database* db) const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
if (db->DoesTableExist(table_name_.c_str()))
return true;
std::string constraints = ComputeConstraints(sealed_version_);
DCHECK(!constraints.empty());
std::string names; // Names and types of the current columns.
for (const Column& column : columns_) {
if (IsColumnInLastVersion(column))
Append(column.name + " " + column.type, &names);
}
std::vector<std::string>
create_index_sqls; // CREATE INDEX statements for the current indices.
for (const Index& index : indices_) {
if (IsIndexInLastVersion(index)) {
create_index_sqls.push_back(base::StringPrintf(
"CREATE INDEX %s ON %s (%s)", index.name.c_str(), table_name_.c_str(),
base::JoinString(index.columns, ", ").c_str()));
}
}
sql::Transaction transaction(db);
return transaction.Begin() &&
db->Execute(base::StringPrintf("CREATE TABLE %s (%s, %s)",
table_name_.c_str(), names.c_str(),
constraints.c_str())
.c_str()) &&
std::all_of(create_index_sqls.begin(), create_index_sqls.end(),
[&db](const std::string& sql) {
return db->Execute(sql.c_str());
}) &&
transaction.Commit();
}
std::string SQLTableBuilder::ListAllColumnNames() const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
std::string result;
for (const Column& column : columns_) {
if (IsColumnInLastVersion(column))
Append(column.name, &result);
}
return result;
}
std::string SQLTableBuilder::ListAllNonuniqueKeyNames() const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
std::string result;
for (const Column& column : columns_) {
if (IsColumnInLastVersion(column) &&
!(column.part_of_primary_key || column.part_of_unique_key))
Append(column.name + "=?", &result);
}
return result;
}
std::string SQLTableBuilder::ListAllUniqueKeyNames() const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
std::string result;
for (const Column& column : columns_) {
if (IsColumnInLastVersion(column) && column.part_of_unique_key) {
if (!result.empty())
result += " AND ";
result += column.name + "=?";
}
}
return result;
}
std::vector<base::StringPiece> SQLTableBuilder::AllPrimaryKeyNames() const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
std::vector<base::StringPiece> result;
result.reserve(columns_.size());
for (const Column& column : columns_) {
if (IsColumnInLastVersion(column) && column.part_of_primary_key) {
result.emplace_back(column.name);
}
}
return result;
}
std::vector<base::StringPiece> SQLTableBuilder::AllIndexNames() const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
std::vector<base::StringPiece> result;
result.reserve(indices_.size());
for (const Index& index : indices_) {
if (IsIndexInLastVersion(index)) {
result.emplace_back(index.name);
}
}
return result;
}
size_t SQLTableBuilder::NumberOfColumns() const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
return base::checked_cast<size_t>(std::count_if(
columns_.begin(), columns_.end(),
[this](const Column& column) { return IsColumnInLastVersion(column); }));
}
size_t SQLTableBuilder::NumberOfIndices() const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
return base::checked_cast<size_t>(std::count_if(
indices_.begin(), indices_.end(),
[this](const Index& index) { return IsIndexInLastVersion(index); }));
}
bool SQLTableBuilder::MigrateToNextFrom(unsigned old_version,
sql::Database* db) const {
DCHECK_LT(old_version, sealed_version_);
DCHECK_GE(old_version, 0u);
DCHECK(IsVersionLastAndSealed(sealed_version_));
// Names of columns from old version, values of which are copied. This
// contains only the names without their types.
std::string old_names_of_existing_columns_without_types;
// Names of columns in new version, except for added ones.
std::string new_names_of_existing_columns;
// Names of columns in new version, except for added ones. This contains only
// the names without their types.
std::string new_names_of_existing_columns_without_types;
std::vector<std::string>
names_of_new_columns_list; // Names of added columns.
// A temporary table will be needed if some columns are dropped or renamed,
// because that is not supported by a single SQLite command.
bool needs_temp_table = false;
for (auto column = columns_.begin(); column != columns_.end(); ++column) {
if (column->max_version == old_version) {
// This column was deleted after |old_version|. It can have two reasons:
needs_temp_table = true;
auto next_column = std::next(column);
if (next_column != columns_.end() && next_column->gets_previous_data) {
// (1) The column is being renamed.
DCHECK_EQ(column->type, next_column->type);
DCHECK_NE(column->name, next_column->name);
Append(column->name, &old_names_of_existing_columns_without_types);
Append(next_column->name + " " + next_column->type,
&new_names_of_existing_columns);
Append(next_column->name, &new_names_of_existing_columns_without_types);
++column; // Avoid processing next_column in the next loop.
} else {
// (2) The column is being dropped.
}
} else if (column->min_version == old_version + 1) {
// This column was added after old_version.
if (column->part_of_primary_key || column->part_of_unique_key)
needs_temp_table = true;
names_of_new_columns_list.push_back(column->name + " " + column->type);
} else if (column->min_version <= old_version &&
(column->max_version == kInvalidVersion ||
column->max_version > old_version)) {
// This column stays.
Append(column->name, &old_names_of_existing_columns_without_types);
Append(column->name + " " + column->type, &new_names_of_existing_columns);
Append(column->name, &new_names_of_existing_columns_without_types);
}
}
if (needs_temp_table) {
// Following the instructions from
// https://www.sqlite.org/lang_altertable.html#otheralter, this code works
// around the fact that SQLite does not allow dropping or renaming
// columns. Instead, a new table is constructed, with the new column
// names, and data from all but dropped columns from the current table are
// copied into it. After that, the new table is renamed to the current
// one.
std::string constraints = ComputeConstraints(old_version + 1);
DCHECK(!constraints.empty());
// Foreign key constraints are not enabled for the login database, so no
// PRAGMA foreign_keys=off needed.
const std::string temp_table_name = "temp_" + table_name_;
std::string names_of_all_columns = new_names_of_existing_columns;
for (const std::string& new_column : names_of_new_columns_list) {
Append(new_column, &names_of_all_columns);
}
sql::Transaction transaction(db);
if (!(transaction.Begin() &&
db->Execute(base::StringPrintf(
"CREATE TABLE %s (%s, %s)", temp_table_name.c_str(),
names_of_all_columns.c_str(), constraints.c_str())
.c_str()) &&
db->Execute(base::StringPrintf(
"INSERT OR REPLACE INTO %s (%s) SELECT %s FROM %s",
temp_table_name.c_str(),
new_names_of_existing_columns_without_types.c_str(),
old_names_of_existing_columns_without_types.c_str(),
table_name_.c_str())
.c_str()) &&
db->Execute(base::StringPrintf("DROP TABLE %s", table_name_.c_str())
.c_str()) &&
db->Execute(base::StringPrintf("ALTER TABLE %s RENAME TO %s",
temp_table_name.c_str(),
table_name_.c_str())
.c_str()) &&
transaction.Commit())) {
return false;
}
} else if (!names_of_new_columns_list.empty()) {
// If no new table has been created, we need to add the new columns here if
// any.
sql::Transaction transaction(db);
if (!(transaction.Begin() &&
std::all_of(names_of_new_columns_list.begin(),
names_of_new_columns_list.end(),
[this, &db](const std::string& name) {
return db->Execute(
base::StringPrintf("ALTER TABLE %s ADD COLUMN %s",
table_name_.c_str(),
name.c_str())
.c_str());
}) &&
transaction.Commit())) {
return false;
}
}
return MigrateIndicesToNextFrom(old_version, db);
}
bool SQLTableBuilder::MigrateIndicesToNextFrom(unsigned old_version,
sql::Database* db) const {
DCHECK_LT(old_version, sealed_version_);
DCHECK(IsVersionLastAndSealed(sealed_version_));
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
for (const auto& index : indices_) {
std::string sql;
if (index.max_version <= old_version) {
// Index is not supposed to exist in the new version.
sql = base::StringPrintf("DROP INDEX IF EXISTS %s", index.name.c_str());
} else if (index.min_version <= old_version + 1) {
// Index is supposed to exist in the new version.
sql = base::StringPrintf("CREATE INDEX IF NOT EXISTS %s ON %s (%s)",
index.name.c_str(), table_name_.c_str(),
base::JoinString(index.columns, ", ").c_str());
} else {
continue;
}
if (!db->Execute(sql.c_str()))
return false;
}
return transaction.Commit();
}
std::vector<SQLTableBuilder::Column>::reverse_iterator
SQLTableBuilder::FindLastColumnByName(const std::string& name) {
return std::find_if(
columns_.rbegin(), columns_.rend(),
[&name](const Column& column) { return name == column.name; });
}
std::vector<SQLTableBuilder::Index>::reverse_iterator
SQLTableBuilder::FindLastIndexByName(const std::string& name) {
return std::find_if(
indices_.rbegin(), indices_.rend(),
[&name](const Index& index) { return name == index.name; });
}
bool SQLTableBuilder::IsVersionLastAndSealed(unsigned version) const {
// Is |version| the last sealed one?
if (sealed_version_ != version)
return false;
// Is the current version the last sealed one? In other words, is there
// neither a column or index added past the sealed version (min_version >
// sealed) nor deleted one version after the sealed (max_version == sealed)?
return std::none_of(columns_.begin(), columns_.end(),
[this](const Column& column) {
return column.min_version > sealed_version_ ||
column.max_version == sealed_version_;
}) &&
std::none_of(indices_.begin(), indices_.end(),
[this](const Index& index) {
return index.min_version > sealed_version_ ||
index.max_version == sealed_version_;
});
}
bool SQLTableBuilder::IsColumnInLastVersion(const Column& column) const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
return (column.min_version <= sealed_version_ &&
(column.max_version == kInvalidVersion ||
column.max_version >= sealed_version_));
}
bool SQLTableBuilder::IsIndexInLastVersion(const Index& index) const {
DCHECK(IsVersionLastAndSealed(sealed_version_));
return (index.min_version <= sealed_version_ &&
(index.max_version == kInvalidVersion ||
index.max_version >= sealed_version_));
}
} // namespace password_manager