Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "sql/recovery.h" | 5 #include "sql/recovery.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 #include <string> | 8 #include <string> |
| 9 #include <utility> | 9 #include <utility> |
| 10 | 10 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 60 // to create the table or index. For certain automatic SQLite | 60 // to create the table or index. For certain automatic SQLite |
| 61 // structures with no sql, the name is used. | 61 // structures with no sql, the name is used. |
| 62 std::string GetSchema(sql::Connection* db) { | 62 std::string GetSchema(sql::Connection* db) { |
| 63 const char kSql[] = | 63 const char kSql[] = |
| 64 "SELECT COALESCE(sql, name) FROM sqlite_master ORDER BY 1"; | 64 "SELECT COALESCE(sql, name) FROM sqlite_master ORDER BY 1"; |
| 65 return ExecuteWithResults(db, kSql, "|", "\n"); | 65 return ExecuteWithResults(db, kSql, "|", "\n"); |
| 66 } | 66 } |
| 67 | 67 |
| 68 using SQLRecoveryTest = sql::SQLTestBase; | 68 using SQLRecoveryTest = sql::SQLTestBase; |
| 69 | 69 |
| 70 // Baseline sql::Recovery test covering the different ways to dispose of the | |
| 71 // scoped pointer received from sql::Recovery::Begin(). | |
| 70 TEST_F(SQLRecoveryTest, RecoverBasic) { | 72 TEST_F(SQLRecoveryTest, RecoverBasic) { |
| 71 const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; | 73 const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; |
| 72 const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')"; | 74 const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')"; |
| 75 const char kAltInsertSql[] = "INSERT INTO x VALUES ('That was a test')"; | |
| 73 ASSERT_TRUE(db().Execute(kCreateSql)); | 76 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 74 ASSERT_TRUE(db().Execute(kInsertSql)); | 77 ASSERT_TRUE(db().Execute(kInsertSql)); |
| 75 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); | 78 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); |
| 76 | 79 |
| 77 // If the Recovery handle goes out of scope without being | 80 // If the Recovery handle goes out of scope without being |
| 78 // Recovered(), the database is razed. | 81 // Recovered(), the database is razed. |
| 79 { | 82 { |
| 80 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 83 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 81 ASSERT_TRUE(recovery.get()); | 84 ASSERT_TRUE(recovery.get()); |
| 82 } | 85 } |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 112 recovery = sql::Recovery::Begin(&db(), db_path()); | 115 recovery = sql::Recovery::Begin(&db(), db_path()); |
| 113 ASSERT_FALSE(recovery.get()); | 116 ASSERT_FALSE(recovery.get()); |
| 114 } | 117 } |
| 115 ASSERT_TRUE(Reopen()); | 118 ASSERT_TRUE(Reopen()); |
| 116 | 119 |
| 117 // Recreate the database. | 120 // Recreate the database. |
| 118 ASSERT_TRUE(db().Execute(kCreateSql)); | 121 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 119 ASSERT_TRUE(db().Execute(kInsertSql)); | 122 ASSERT_TRUE(db().Execute(kInsertSql)); |
| 120 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); | 123 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); |
| 121 | 124 |
| 125 // Unrecovered table to distinguish from recovered database. | |
| 126 ASSERT_TRUE(db().Execute("CREATE TABLE y (c INTEGER)")); | |
| 127 ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db())); | |
| 128 | |
| 122 // Recovered() replaces the original with the "recovered" version. | 129 // Recovered() replaces the original with the "recovered" version. |
| 123 { | 130 { |
| 124 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 131 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 125 ASSERT_TRUE(recovery.get()); | 132 ASSERT_TRUE(recovery.get()); |
| 126 | 133 |
| 127 // Create the new version of the table. | 134 // Create the new version of the table. |
| 128 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | 135 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); |
| 129 | 136 |
| 130 // Insert different data to distinguish from original database. | 137 // Insert different data to distinguish from original database. |
| 131 const char kAltInsertSql[] = "INSERT INTO x VALUES ('That was a test')"; | |
| 132 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql)); | 138 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql)); |
| 133 | 139 |
| 134 // Successfully recovered. | 140 // Successfully recovered. |
| 135 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 141 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 136 } | 142 } |
| 137 EXPECT_FALSE(db().is_open()); | 143 EXPECT_FALSE(db().is_open()); |
| 138 ASSERT_TRUE(Reopen()); | 144 ASSERT_TRUE(Reopen()); |
| 139 EXPECT_TRUE(db().is_open()); | 145 EXPECT_TRUE(db().is_open()); |
| 140 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); | 146 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); |
| 141 | 147 |
| 142 const char* kXSql = "SELECT * FROM x ORDER BY 1"; | 148 const char* kXSql = "SELECT * FROM x ORDER BY 1"; |
| 143 ASSERT_EQ("That was a test", | 149 ASSERT_EQ("That was a test", |
| 144 ExecuteWithResults(&db(), kXSql, "|", "\n")); | 150 ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 151 | |
| 152 // Reset the database contents. | |
| 153 ASSERT_TRUE(db().Execute("DELETE FROM x")); | |
| 154 ASSERT_TRUE(db().Execute(kInsertSql)); | |
| 155 | |
| 156 // Rollback() discards recovery progress and leaves the database as it was. | |
| 157 { | |
| 158 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | |
| 159 ASSERT_TRUE(recovery.get()); | |
| 160 | |
| 161 ASSERT_TRUE(recovery->db()->Execute(kCreateSql)); | |
| 162 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql)); | |
| 163 | |
| 164 sql::Recovery::Rollback(std::move(recovery)); | |
| 165 } | |
| 166 EXPECT_FALSE(db().is_open()); | |
| 167 ASSERT_TRUE(Reopen()); | |
| 168 EXPECT_TRUE(db().is_open()); | |
| 169 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); | |
| 170 | |
| 171 ASSERT_EQ("This is a test", | |
| 172 ExecuteWithResults(&db(), kXSql, "|", "\n")); | |
| 145 } | 173 } |
| 146 | 174 |
| 147 // The recovery virtual table is only supported for Chromium's SQLite. | 175 // The recovery virtual table is only supported for Chromium's SQLite. |
| 148 #if !defined(USE_SYSTEM_SQLITE) | 176 #if !defined(USE_SYSTEM_SQLITE) |
|
Ryan Hamilton
2016/02/04 20:24:19
Can you remove this now since you're not using the
Scott Hess - ex-Googler
2016/02/04 20:47:50
AutoRecoverTable() is itself implemented in terms
| |
| 149 | 177 |
| 150 // Run recovery through its paces on a valid database. | 178 // Test operation of the virtual table used by sql::Recovery. |
| 151 TEST_F(SQLRecoveryTest, VirtualTable) { | 179 TEST_F(SQLRecoveryTest, VirtualTable) { |
| 152 const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; | 180 const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; |
| 153 ASSERT_TRUE(db().Execute(kCreateSql)); | 181 ASSERT_TRUE(db().Execute(kCreateSql)); |
| 154 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')")); | 182 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')")); |
| 155 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test')")); | 183 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test')")); |
| 156 | 184 |
| 157 // Successfully recover the database. | 185 // Successfully recover the database. |
| 158 { | 186 { |
| 159 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 187 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 160 | 188 |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 182 // data should be recovered. | 210 // data should be recovered. |
| 183 ASSERT_TRUE(Reopen()); | 211 ASSERT_TRUE(Reopen()); |
| 184 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); | 212 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db())); |
| 185 | 213 |
| 186 const char* kXSql = "SELECT * FROM x ORDER BY 1"; | 214 const char* kXSql = "SELECT * FROM x ORDER BY 1"; |
| 187 ASSERT_EQ("That was a test\nThis is a test", | 215 ASSERT_EQ("That was a test\nThis is a test", |
| 188 ExecuteWithResults(&db(), kXSql, "|", "\n")); | 216 ExecuteWithResults(&db(), kXSql, "|", "\n")); |
| 189 } | 217 } |
| 190 | 218 |
| 191 void RecoveryCallback(sql::Connection* db, const base::FilePath& db_path, | 219 void RecoveryCallback(sql::Connection* db, const base::FilePath& db_path, |
| 220 const char* create_table, const char* create_index, | |
| 192 int* record_error, int error, sql::Statement* stmt) { | 221 int* record_error, int error, sql::Statement* stmt) { |
| 193 *record_error = error; | 222 *record_error = error; |
| 194 | 223 |
| 195 // Clear the error callback to prevent reentrancy. | 224 // Clear the error callback to prevent reentrancy. |
| 196 db->reset_error_callback(); | 225 db->reset_error_callback(); |
| 197 | 226 |
| 198 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); | 227 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); |
| 199 ASSERT_TRUE(recovery.get()); | 228 ASSERT_TRUE(recovery.get()); |
| 200 | 229 |
| 201 const char kRecoveryCreateSql[] = | 230 ASSERT_TRUE(recovery->db()->Execute(create_table)); |
| 202 "CREATE VIRTUAL TABLE temp.recover_x using recover(" | 231 ASSERT_TRUE(recovery->db()->Execute(create_index)); |
| 203 " corrupt.x," | |
| 204 " id INTEGER STRICT," | |
| 205 " v INTEGER STRICT" | |
| 206 ")"; | |
| 207 const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)"; | |
| 208 const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)"; | |
| 209 | 232 |
| 210 // Replicate data over. | 233 size_t rows = 0; |
| 211 const char kRecoveryCopySql[] = | 234 ASSERT_TRUE(recovery->AutoRecoverTable("x", &rows)); |
| 212 "INSERT OR REPLACE INTO x SELECT id, v FROM recover_x"; | |
| 213 | |
| 214 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql)); | |
| 215 ASSERT_TRUE(recovery->db()->Execute(kCreateTable)); | |
| 216 ASSERT_TRUE(recovery->db()->Execute(kCreateIndex)); | |
| 217 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql)); | |
| 218 | 235 |
| 219 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); | 236 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery))); |
| 220 } | 237 } |
| 221 | 238 |
| 222 // Build a database, corrupt it by making an index reference to | 239 // Build a database, corrupt it by making an index reference to |
| 223 // deleted row, then recover when a query selects that row. | 240 // deleted row, then recover when a query selects that row. |
| 224 TEST_F(SQLRecoveryTest, RecoverCorruptIndex) { | 241 TEST_F(SQLRecoveryTest, RecoverCorruptIndex) { |
| 225 const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)"; | 242 const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)"; |
| 226 const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)"; | 243 const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)"; |
| 227 ASSERT_TRUE(db().Execute(kCreateTable)); | 244 ASSERT_TRUE(db().Execute(kCreateTable)); |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 246 db().Close(); | 263 db().Close(); |
| 247 | 264 |
| 248 // Delete a row from the table, while leaving the index entry which | 265 // Delete a row from the table, while leaving the index entry which |
| 249 // references it. | 266 // references it. |
| 250 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0"; | 267 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0"; |
| 251 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x_id", kDeleteSql)); | 268 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x_id", kDeleteSql)); |
| 252 | 269 |
| 253 ASSERT_TRUE(Reopen()); | 270 ASSERT_TRUE(Reopen()); |
| 254 | 271 |
| 255 int error = SQLITE_OK; | 272 int error = SQLITE_OK; |
| 256 db().set_error_callback(base::Bind(&RecoveryCallback, | 273 db().set_error_callback(base::Bind(&RecoveryCallback, &db(), db_path(), |
| 257 &db(), db_path(), &error)); | 274 kCreateTable, kCreateIndex, &error)); |
| 258 | 275 |
| 259 // This works before the callback is called. | 276 // This works before the callback is called. |
| 260 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master"; | 277 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master"; |
| 261 EXPECT_TRUE(db().IsSQLValid(kTrivialSql)); | 278 EXPECT_TRUE(db().IsSQLValid(kTrivialSql)); |
| 262 | 279 |
| 263 // TODO(shess): Could this be delete? Anything which fails should work. | 280 // TODO(shess): Could this be delete? Anything which fails should work. |
| 264 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; | 281 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0"; |
| 265 ASSERT_FALSE(db().Execute(kSelectSql)); | 282 ASSERT_FALSE(db().Execute(kSelectSql)); |
| 266 EXPECT_EQ(SQLITE_CORRUPT, error); | 283 EXPECT_EQ(SQLITE_CORRUPT, error); |
| 267 | 284 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 302 } | 319 } |
| 303 | 320 |
| 304 ASSERT_TRUE(db().CommitTransaction()); | 321 ASSERT_TRUE(db().CommitTransaction()); |
| 305 } | 322 } |
| 306 db().Close(); | 323 db().Close(); |
| 307 | 324 |
| 308 // Delete a row from the index while leaving a table entry. | 325 // Delete a row from the index while leaving a table entry. |
| 309 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0"; | 326 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0"; |
| 310 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x", kDeleteSql)); | 327 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x", kDeleteSql)); |
| 311 | 328 |
| 312 // TODO(shess): Figure out a query which causes SQLite to notice | 329 ASSERT_TRUE(Reopen()); |
| 313 // this organically. Meanwhile, just handle it manually. | |
| 314 | 330 |
| 315 ASSERT_TRUE(Reopen()); | 331 int error = SQLITE_OK; |
| 332 db().set_error_callback(base::Bind(&RecoveryCallback, &db(), db_path(), | |
| 333 kCreateTable, kCreateIndex, &error)); | |
| 316 | 334 |
| 317 // Index shows one less than originally inserted. | 335 // Index shows one less than originally inserted. |
| 318 const char kCountSql[] = "SELECT COUNT (*) FROM x"; | 336 const char kCountSql[] = "SELECT COUNT (*) FROM x"; |
| 319 EXPECT_EQ("9", ExecuteWithResults(&db(), kCountSql, "|", ",")); | 337 EXPECT_EQ("9", ExecuteWithResults(&db(), kCountSql, "|", ",")); |
| 320 | 338 |
| 321 // A full table scan shows all of the original data. Using column [v] to | 339 // A full table scan shows all of the original data. Using column [v] to |
| 322 // force use of the table rather than the index. | 340 // force use of the table rather than the index. |
| 323 const char kDistinctSql[] = "SELECT DISTINCT COUNT (v) FROM x"; | 341 const char kDistinctSql[] = "SELECT DISTINCT COUNT (v) FROM x"; |
| 324 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); | 342 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); |
| 325 | 343 |
| 326 // Insert id 0 again. Since it is not in the index, the insert | 344 // Insert id 0 again. Since it is not in the index, the insert |
| 327 // succeeds, but results in a duplicate value in the table. | 345 // succeeds, but results in a duplicate value in the table. |
| 328 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (0, 100)"; | 346 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (0, 100)"; |
| 329 ASSERT_TRUE(db().Execute(kInsertSql)); | 347 ASSERT_TRUE(db().Execute(kInsertSql)); |
| 330 | 348 |
| 331 // Duplication is visible. | 349 // Duplication is visible. |
| 332 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); | 350 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); |
| 333 EXPECT_EQ("11", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); | 351 EXPECT_EQ("11", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); |
| 334 | 352 |
| 335 // This works before the callback is called. | 353 // This works before the callback is called. |
| 336 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master"; | 354 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master"; |
| 337 EXPECT_TRUE(db().IsSQLValid(kTrivialSql)); | 355 EXPECT_TRUE(db().IsSQLValid(kTrivialSql)); |
| 338 | 356 |
| 339 // Call the recovery callback manually. | 357 // TODO(shess): Figure out a statement which causes SQLite to notice the |
| 340 int error = SQLITE_OK; | 358 // corruption. SELECT doesn't see errors because missing index values aren't |
| 341 RecoveryCallback(&db(), db_path(), &error, SQLITE_CORRUPT, NULL); | 359 // visible. UPDATE or DELETE against v=0 don't see errors, even though the |
| 342 EXPECT_EQ(SQLITE_CORRUPT, error); | 360 // index item is missing. I suspect SQLite only deletes the key in these |
| 361 // cases, but doesn't verify that one or more keys were deleted. | |
| 362 ASSERT_FALSE(db().Execute("INSERT INTO x (id, v) VALUES (0, 101)")); | |
| 363 EXPECT_EQ(SQLITE_CONSTRAINT_UNIQUE, error); | |
| 343 | 364 |
| 344 // Database handle has been poisoned. | 365 // Database handle has been poisoned. |
| 345 EXPECT_FALSE(db().IsSQLValid(kTrivialSql)); | 366 EXPECT_FALSE(db().IsSQLValid(kTrivialSql)); |
| 346 | 367 |
| 347 ASSERT_TRUE(Reopen()); | 368 ASSERT_TRUE(Reopen()); |
| 348 | 369 |
| 349 // The recovered table has consistency between the index and the table. | 370 // The recovered table has consistency between the index and the table. |
| 350 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); | 371 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ",")); |
| 351 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); | 372 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ",")); |
| 352 | 373 |
| (...skipping 370 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 723 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); | 744 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(&db(), db_path()); |
| 724 ASSERT_TRUE(recovery.get()); | 745 ASSERT_TRUE(recovery.get()); |
| 725 | 746 |
| 726 // In the current implementation, the PRAGMA successfully runs with no result | 747 // In the current implementation, the PRAGMA successfully runs with no result |
| 727 // rows. Running with a single result of |0| is also acceptable. | 748 // rows. Running with a single result of |0| is also acceptable. |
| 728 sql::Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size")); | 749 sql::Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size")); |
| 729 EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0)); | 750 EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0)); |
| 730 } | 751 } |
| 731 | 752 |
| 732 } // namespace | 753 } // namespace |
| OLD | NEW |