summaryrefslogtreecommitdiffstats
path: root/sql
diff options
context:
space:
mode:
Diffstat (limited to 'sql')
-rw-r--r--sql/meta_table.cc90
-rw-r--r--sql/meta_table.h14
-rw-r--r--sql/meta_table_unittest.cc47
-rw-r--r--sql/test/test_helpers.cc14
-rw-r--r--sql/test/test_helpers.h4
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