diff options
Diffstat (limited to 'sql')
-rw-r--r-- | sql/meta_table.cc | 90 | ||||
-rw-r--r-- | sql/meta_table.h | 14 | ||||
-rw-r--r-- | sql/meta_table_unittest.cc | 47 | ||||
-rw-r--r-- | sql/test/test_helpers.cc | 14 | ||||
-rw-r--r-- | sql/test/test_helpers.h | 4 |
5 files changed, 166 insertions, 3 deletions
diff --git a/sql/meta_table.cc b/sql/meta_table.cc index c7e803c..b1b9562 100644 --- a/sql/meta_table.cc +++ b/sql/meta_table.cc @@ -5,16 +5,52 @@ #include "sql/meta_table.h" #include "base/logging.h" +#include "base/metrics/histogram.h" #include "base/strings/string_util.h" #include "sql/connection.h" #include "sql/statement.h" #include "sql/transaction.h" -namespace sql { +namespace { // Key used in our meta table for version numbers. -static const char kVersionKey[] = "version"; -static const char kCompatibleVersionKey[] = "last_compatible_version"; +const char kVersionKey[] = "version"; +const char kCompatibleVersionKey[] = "last_compatible_version"; + +// Used to track success/failure of deprecation checks. +enum DeprecationEventType { + // Database has info, but no meta table. This is probably bad. + DEPRECATION_DATABASE_NOT_EMPTY = 0, + + // No meta, unable to query sqlite_master. This is probably bad. + DEPRECATION_DATABASE_UNKNOWN, + + // Failure querying meta table, corruption or similar problem likely. + DEPRECATION_FAILED_VERSION, + + // Version key not found in meta table. Some sort of update error likely. + DEPRECATION_NO_VERSION, + + // Version was out-dated, database successfully razed. Should only + // happen once per long-idle user, low volume expected. + DEPRECATION_RAZED, + + // Version was out-dated, database raze failed. This user's + // database will be stuck. + DEPRECATION_RAZE_FAILED, + + // Always keep this at the end. + DEPRECATION_EVENT_MAX, +}; + +void RecordDeprecationEvent(DeprecationEventType deprecation_event) { + UMA_HISTOGRAM_ENUMERATION("Sqlite.DeprecationVersionResult", + deprecation_event, DEPRECATION_EVENT_MAX); +} + +} // namespace + +namespace sql { MetaTable::MetaTable() : db_(NULL) { } @@ -28,6 +64,54 @@ bool MetaTable::DoesTableExist(sql::Connection* db) { return db->DoesTableExist("meta"); } +// static +void MetaTable::RazeIfDeprecated(Connection* db, int deprecated_version) { + DCHECK_GT(deprecated_version, 0); + DCHECK_EQ(0, db->transaction_nesting()); + + if (!DoesTableExist(db)) { + sql::Statement s(db->GetUniqueStatement( + "SELECT COUNT(*) FROM sqlite_master")); + if (s.Step()) { + if (s.ColumnInt(0) != 0) { + RecordDeprecationEvent(DEPRECATION_DATABASE_NOT_EMPTY); + } + // NOTE(shess): Empty database at first run is expected, so + // don't histogram that case. + } else { + RecordDeprecationEvent(DEPRECATION_DATABASE_UNKNOWN); + } + return; + } + + // TODO(shess): Share sql with PrepareGetStatement(). + sql::Statement s(db->GetUniqueStatement( + "SELECT value FROM meta WHERE key=?")); + s.BindCString(0, kVersionKey); + if (!s.Step()) { + if (!s.Succeeded()) { + RecordDeprecationEvent(DEPRECATION_FAILED_VERSION); + } else { + RecordDeprecationEvent(DEPRECATION_NO_VERSION); + } + return; + } + + int version = s.ColumnInt(0); + s.Clear(); // Clear potential automatic transaction for Raze(). + if (version <= deprecated_version) { + if (db->Raze()) { + RecordDeprecationEvent(DEPRECATION_RAZED); + } else { + RecordDeprecationEvent(DEPRECATION_RAZE_FAILED); + } + return; + } + + // NOTE(shess): Successfully getting a version which is not + // deprecated is expected, so don't histogram that case. +} + bool MetaTable::Init(Connection* db, int version, int compatible_version) { DCHECK(!db_ && db); db_ = db; diff --git a/sql/meta_table.h b/sql/meta_table.h index 0f4ee72..918a261 100644 --- a/sql/meta_table.h +++ b/sql/meta_table.h @@ -23,6 +23,20 @@ class SQL_EXPORT MetaTable { // Returns true if the 'meta' table exists. static bool DoesTableExist(Connection* db); + // If the current version of the database is less than or equal to + // |deprecated_version|, raze the database. Must be called outside + // of a transaction. + // TODO(shess): At this time the database is razed IFF meta exists + // and contains a version row with value <= deprecated_version. It + // may make sense to also raze if meta exists but has no version + // row, or if meta doesn't exist. In those cases if the database is + // not already empty, it probably resulted from a broken + // initialization. + // TODO(shess): Folding this into Init() would allow enforcing + // |deprecated_version|<|version|. But Init() is often called in a + // transaction. + static void RazeIfDeprecated(Connection* db, int deprecated_version); + // Initializes the MetaTableHelper, creating the meta table if necessary. For // new tables, it will initialize the version number to |version| and the // compatible version number to |compatible_version|. Versions must be diff --git a/sql/meta_table_unittest.cc b/sql/meta_table_unittest.cc index 3fbc499..2ffb4bd 100644 --- a/sql/meta_table_unittest.cc +++ b/sql/meta_table_unittest.cc @@ -41,6 +41,53 @@ TEST_F(SQLMetaTableTest, DoesTableExist) { EXPECT_TRUE(sql::MetaTable::DoesTableExist(&db())); } +TEST_F(SQLMetaTableTest, RazeIfDeprecated) { + const int kDeprecatedVersion = 1; + const int kVersion = 2; + + // Setup a current database. + { + sql::MetaTable meta_table; + EXPECT_TRUE(meta_table.Init(&db(), kVersion, kVersion)); + EXPECT_TRUE(db().Execute("CREATE TABLE t(c)")); + EXPECT_TRUE(db().DoesTableExist("t")); + } + + // Table should should still exist if the database version is new enough. + sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion); + EXPECT_TRUE(db().DoesTableExist("t")); + + // TODO(shess): It may make sense to Raze() if meta isn't present or + // version isn't present. See meta_table.h TODO on RazeIfDeprecated(). + + // Table should still exist if the version is not available. + EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'")); + { + sql::MetaTable meta_table; + EXPECT_TRUE(meta_table.Init(&db(), kVersion, kVersion)); + EXPECT_EQ(0, meta_table.GetVersionNumber()); + } + sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion); + EXPECT_TRUE(db().DoesTableExist("t")); + + // Table should still exist if meta table is missing. + EXPECT_TRUE(db().Execute("DROP TABLE meta")); + sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion); + EXPECT_TRUE(db().DoesTableExist("t")); + + // Setup meta with deprecated version. + { + sql::MetaTable meta_table; + EXPECT_TRUE(meta_table.Init(&db(), kDeprecatedVersion, kDeprecatedVersion)); + } + + // Deprecation check should remove the table. + EXPECT_TRUE(db().DoesTableExist("t")); + sql::MetaTable::RazeIfDeprecated(&db(), kDeprecatedVersion); + EXPECT_FALSE(sql::MetaTable::DoesTableExist(&db())); + EXPECT_FALSE(db().DoesTableExist("t")); +} + TEST_F(SQLMetaTableTest, VersionNumber) { // Compatibility versions one less than the main versions to make // sure the values aren't being crossed with each other. diff --git a/sql/test/test_helpers.cc b/sql/test/test_helpers.cc index 607f146..20471dc 100644 --- a/sql/test/test_helpers.cc +++ b/sql/test/test_helpers.cc @@ -55,6 +55,20 @@ size_t CountTableColumns(sql::Connection* db, const char* table) { return rows; } +bool CountTableRows(sql::Connection* db, const char* table, size_t* count) { + // TODO(shess): Table should probably be quoted with [] or "". See + // http://www.sqlite.org/lang_keywords.html . Meanwhile, odd names + // will throw an error. + std::string sql = "SELECT COUNT(*) FROM "; + sql += table; + sql::Statement s(db->GetUniqueStatement(sql.c_str())); + if (!s.Step()) + return false; + + *count = s.ColumnInt64(0); + return true; +} + bool CreateDatabaseFromSQL(const base::FilePath& db_path, const base::FilePath& sql_path) { if (base::PathExists(db_path) || !base::PathExists(sql_path)) diff --git a/sql/test/test_helpers.h b/sql/test/test_helpers.h index 2e01ecd..330f59a 100644 --- a/sql/test/test_helpers.h +++ b/sql/test/test_helpers.h @@ -32,6 +32,10 @@ size_t CountSQLIndices(sql::Connection* db) WARN_UNUSED_RESULT; size_t CountTableColumns(sql::Connection* db, const char* table) WARN_UNUSED_RESULT; +// Sets |*count| to the number of rows in |table|. Returns false in +// case of error, such as the table not existing. +bool CountTableRows(sql::Connection* db, const char* table, size_t* count); + // Creates a SQLite database at |db_path| from the sqlite .dump output // at |sql_path|. Returns false if |db_path| already exists, or if // sql_path does not exist or cannot be read, or if there is an error |