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 |