blob: a117690634b6b380ab7eb56b184a5fcdf0e5743b [file] [log] [blame]
[email protected]8d409412013-07-19 18:25:301// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
dchenge48600452015-12-28 02:24:505#include "sql/recovery.h"
avi0b519202015-12-21 07:25:196
dchenge48600452015-12-28 02:24:507#include <stddef.h>
mostynbd82cd9952016-04-11 20:05:348
9#include <memory>
[email protected]ae4f1622013-12-08 06:49:1210#include <string>
dchenge48600452015-12-28 02:24:5011#include <utility>
[email protected]ae4f1622013-12-08 06:49:1212
[email protected]dd325f052013-08-06 02:37:4013#include "base/bind.h"
[email protected]ae4f1622013-12-08 06:49:1214#include "base/files/file_path.h"
thestig22dfc4012014-09-05 08:29:4415#include "base/files/file_util.h"
[email protected]8d409412013-07-19 18:25:3016#include "base/files/scoped_temp_dir.h"
[email protected]cfb821612014-07-10 00:48:0617#include "base/path_service.h"
[email protected]a8848a72013-11-18 04:18:4718#include "base/strings/string_number_conversions.h"
[email protected]8d409412013-07-19 18:25:3019#include "sql/connection.h"
20#include "sql/meta_table.h"
[email protected]8d409412013-07-19 18:25:3021#include "sql/statement.h"
[email protected]cfb821612014-07-10 00:48:0622#include "sql/test/paths.h"
shess976814402016-06-21 06:56:2523#include "sql/test/scoped_error_expecter.h"
erg102ceb412015-06-20 01:38:1324#include "sql/test/sql_test_base.h"
[email protected]ae4f1622013-12-08 06:49:1225#include "sql/test/test_helpers.h"
[email protected]8d409412013-07-19 18:25:3026#include "testing/gtest/include/gtest/gtest.h"
27#include "third_party/sqlite/sqlite3.h"
28
29namespace {
30
31// Execute |sql|, and stringify the results with |column_sep| between
32// columns and |row_sep| between rows.
33// TODO(shess): Promote this to a central testing helper.
34std::string ExecuteWithResults(sql::Connection* db,
35 const char* sql,
36 const char* column_sep,
37 const char* row_sep) {
38 sql::Statement s(db->GetUniqueStatement(sql));
39 std::string ret;
40 while (s.Step()) {
41 if (!ret.empty())
42 ret += row_sep;
43 for (int i = 0; i < s.ColumnCount(); ++i) {
44 if (i > 0)
45 ret += column_sep;
[email protected]a8848a72013-11-18 04:18:4746 if (s.ColumnType(i) == sql::COLUMN_TYPE_NULL) {
47 ret += "<null>";
48 } else if (s.ColumnType(i) == sql::COLUMN_TYPE_BLOB) {
49 ret += "<x'";
50 ret += base::HexEncode(s.ColumnBlob(i), s.ColumnByteLength(i));
51 ret += "'>";
52 } else {
53 ret += s.ColumnString(i);
54 }
[email protected]8d409412013-07-19 18:25:3055 }
56 }
57 return ret;
58}
59
60// Dump consistent human-readable representation of the database
61// schema. For tables or indices, this will contain the sql command
62// to create the table or index. For certain automatic SQLite
63// structures with no sql, the name is used.
64std::string GetSchema(sql::Connection* db) {
65 const char kSql[] =
66 "SELECT COALESCE(sql, name) FROM sqlite_master ORDER BY 1";
67 return ExecuteWithResults(db, kSql, "|", "\n");
68}
69
erg102ceb412015-06-20 01:38:1370using SQLRecoveryTest = sql::SQLTestBase;
[email protected]8d409412013-07-19 18:25:3071
shess6ac39542016-02-04 20:56:2272// Baseline sql::Recovery test covering the different ways to dispose of the
73// scoped pointer received from sql::Recovery::Begin().
[email protected]8d409412013-07-19 18:25:3074TEST_F(SQLRecoveryTest, RecoverBasic) {
75 const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
76 const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')";
shess6ac39542016-02-04 20:56:2277 const char kAltInsertSql[] = "INSERT INTO x VALUES ('That was a test')";
[email protected]8d409412013-07-19 18:25:3078 ASSERT_TRUE(db().Execute(kCreateSql));
79 ASSERT_TRUE(db().Execute(kInsertSql));
80 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
81
82 // If the Recovery handle goes out of scope without being
83 // Recovered(), the database is razed.
84 {
mostynbd82cd9952016-04-11 20:05:3485 std::unique_ptr<sql::Recovery> recovery =
86 sql::Recovery::Begin(&db(), db_path());
[email protected]8d409412013-07-19 18:25:3087 ASSERT_TRUE(recovery.get());
88 }
89 EXPECT_FALSE(db().is_open());
90 ASSERT_TRUE(Reopen());
91 EXPECT_TRUE(db().is_open());
92 ASSERT_EQ("", GetSchema(&db()));
93
94 // Recreate the database.
95 ASSERT_TRUE(db().Execute(kCreateSql));
96 ASSERT_TRUE(db().Execute(kInsertSql));
97 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
98
99 // Unrecoverable() also razes.
100 {
mostynbd82cd9952016-04-11 20:05:34101 std::unique_ptr<sql::Recovery> recovery =
102 sql::Recovery::Begin(&db(), db_path());
[email protected]8d409412013-07-19 18:25:30103 ASSERT_TRUE(recovery.get());
dchenge48600452015-12-28 02:24:50104 sql::Recovery::Unrecoverable(std::move(recovery));
[email protected]8d409412013-07-19 18:25:30105
106 // TODO(shess): Test that calls to recover.db() start failing.
107 }
108 EXPECT_FALSE(db().is_open());
109 ASSERT_TRUE(Reopen());
110 EXPECT_TRUE(db().is_open());
111 ASSERT_EQ("", GetSchema(&db()));
112
shess874ea1bd2016-02-02 05:15:06113 // Attempting to recover a previously-recovered handle fails early.
114 {
mostynbd82cd9952016-04-11 20:05:34115 std::unique_ptr<sql::Recovery> recovery =
116 sql::Recovery::Begin(&db(), db_path());
shess874ea1bd2016-02-02 05:15:06117 ASSERT_TRUE(recovery.get());
118 recovery.reset();
119
120 recovery = sql::Recovery::Begin(&db(), db_path());
121 ASSERT_FALSE(recovery.get());
122 }
123 ASSERT_TRUE(Reopen());
124
[email protected]8d409412013-07-19 18:25:30125 // Recreate the database.
126 ASSERT_TRUE(db().Execute(kCreateSql));
127 ASSERT_TRUE(db().Execute(kInsertSql));
128 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
129
shess6ac39542016-02-04 20:56:22130 // Unrecovered table to distinguish from recovered database.
131 ASSERT_TRUE(db().Execute("CREATE TABLE y (c INTEGER)"));
132 ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db()));
133
[email protected]8d409412013-07-19 18:25:30134 // Recovered() replaces the original with the "recovered" version.
135 {
mostynbd82cd9952016-04-11 20:05:34136 std::unique_ptr<sql::Recovery> recovery =
137 sql::Recovery::Begin(&db(), db_path());
[email protected]8d409412013-07-19 18:25:30138 ASSERT_TRUE(recovery.get());
139
140 // Create the new version of the table.
141 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
142
143 // Insert different data to distinguish from original database.
[email protected]8d409412013-07-19 18:25:30144 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
145
146 // Successfully recovered.
dchenge48600452015-12-28 02:24:50147 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]8d409412013-07-19 18:25:30148 }
149 EXPECT_FALSE(db().is_open());
150 ASSERT_TRUE(Reopen());
151 EXPECT_TRUE(db().is_open());
152 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
153
154 const char* kXSql = "SELECT * FROM x ORDER BY 1";
[email protected]dd325f052013-08-06 02:37:40155 ASSERT_EQ("That was a test",
156 ExecuteWithResults(&db(), kXSql, "|", "\n"));
shess6ac39542016-02-04 20:56:22157
158 // Reset the database contents.
159 ASSERT_TRUE(db().Execute("DELETE FROM x"));
160 ASSERT_TRUE(db().Execute(kInsertSql));
161
162 // Rollback() discards recovery progress and leaves the database as it was.
163 {
mostynbd82cd9952016-04-11 20:05:34164 std::unique_ptr<sql::Recovery> recovery =
165 sql::Recovery::Begin(&db(), db_path());
shess6ac39542016-02-04 20:56:22166 ASSERT_TRUE(recovery.get());
167
168 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
169 ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
170
171 sql::Recovery::Rollback(std::move(recovery));
172 }
173 EXPECT_FALSE(db().is_open());
174 ASSERT_TRUE(Reopen());
175 EXPECT_TRUE(db().is_open());
176 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
177
178 ASSERT_EQ("This is a test",
179 ExecuteWithResults(&db(), kXSql, "|", "\n"));
[email protected]8d409412013-07-19 18:25:30180}
181
shess6ac39542016-02-04 20:56:22182// Test operation of the virtual table used by sql::Recovery.
[email protected]dd325f052013-08-06 02:37:40183TEST_F(SQLRecoveryTest, VirtualTable) {
184 const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
185 ASSERT_TRUE(db().Execute(kCreateSql));
186 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test')"));
187 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test')"));
188
189 // Successfully recover the database.
190 {
mostynbd82cd9952016-04-11 20:05:34191 std::unique_ptr<sql::Recovery> recovery =
192 sql::Recovery::Begin(&db(), db_path());
[email protected]dd325f052013-08-06 02:37:40193
194 // Tables to recover original DB, now at [corrupt].
195 const char kRecoveryCreateSql[] =
196 "CREATE VIRTUAL TABLE temp.recover_x using recover("
197 " corrupt.x,"
198 " t TEXT STRICT"
199 ")";
200 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql));
201
202 // Re-create the original schema.
203 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
204
205 // Copy the data from the recovery tables to the new database.
206 const char kRecoveryCopySql[] =
207 "INSERT INTO x SELECT t FROM recover_x";
208 ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql));
209
210 // Successfully recovered.
dchenge48600452015-12-28 02:24:50211 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]dd325f052013-08-06 02:37:40212 }
213
214 // Since the database was not corrupt, the entire schema and all
215 // data should be recovered.
216 ASSERT_TRUE(Reopen());
217 ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db()));
218
219 const char* kXSql = "SELECT * FROM x ORDER BY 1";
220 ASSERT_EQ("That was a test\nThis is a test",
221 ExecuteWithResults(&db(), kXSql, "|", "\n"));
222}
223
224void RecoveryCallback(sql::Connection* db, const base::FilePath& db_path,
shess6ac39542016-02-04 20:56:22225 const char* create_table, const char* create_index,
[email protected]dd325f052013-08-06 02:37:40226 int* record_error, int error, sql::Statement* stmt) {
227 *record_error = error;
228
229 // Clear the error callback to prevent reentrancy.
230 db->reset_error_callback();
231
mostynbd82cd9952016-04-11 20:05:34232 std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
[email protected]dd325f052013-08-06 02:37:40233 ASSERT_TRUE(recovery.get());
234
shess6ac39542016-02-04 20:56:22235 ASSERT_TRUE(recovery->db()->Execute(create_table));
236 ASSERT_TRUE(recovery->db()->Execute(create_index));
[email protected]dd325f052013-08-06 02:37:40237
shess6ac39542016-02-04 20:56:22238 size_t rows = 0;
239 ASSERT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]dd325f052013-08-06 02:37:40240
dchenge48600452015-12-28 02:24:50241 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]dd325f052013-08-06 02:37:40242}
243
244// Build a database, corrupt it by making an index reference to
245// deleted row, then recover when a query selects that row.
246TEST_F(SQLRecoveryTest, RecoverCorruptIndex) {
247 const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)";
248 const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)";
249 ASSERT_TRUE(db().Execute(kCreateTable));
250 ASSERT_TRUE(db().Execute(kCreateIndex));
251
252 // Insert a bit of data.
253 {
254 ASSERT_TRUE(db().BeginTransaction());
255
256 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)";
257 sql::Statement s(db().GetUniqueStatement(kInsertSql));
258 for (int i = 0; i < 10; ++i) {
259 s.Reset(true);
260 s.BindInt(0, i);
261 s.BindInt(1, i);
262 EXPECT_FALSE(s.Step());
263 EXPECT_TRUE(s.Succeeded());
264 }
265
266 ASSERT_TRUE(db().CommitTransaction());
267 }
[email protected]dd325f052013-08-06 02:37:40268 db().Close();
269
[email protected]ae4f1622013-12-08 06:49:12270 // Delete a row from the table, while leaving the index entry which
271 // references it.
272 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0";
273 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x_id", kDeleteSql));
[email protected]dd325f052013-08-06 02:37:40274
275 ASSERT_TRUE(Reopen());
276
277 int error = SQLITE_OK;
shess6ac39542016-02-04 20:56:22278 db().set_error_callback(base::Bind(&RecoveryCallback, &db(), db_path(),
279 kCreateTable, kCreateIndex, &error));
[email protected]dd325f052013-08-06 02:37:40280
281 // This works before the callback is called.
282 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master";
283 EXPECT_TRUE(db().IsSQLValid(kTrivialSql));
284
285 // TODO(shess): Could this be delete? Anything which fails should work.
286 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0";
287 ASSERT_FALSE(db().Execute(kSelectSql));
288 EXPECT_EQ(SQLITE_CORRUPT, error);
289
290 // Database handle has been poisoned.
291 EXPECT_FALSE(db().IsSQLValid(kTrivialSql));
292
293 ASSERT_TRUE(Reopen());
294
295 // The recovered table should reflect the deletion.
296 const char kSelectAllSql[] = "SELECT v FROM x ORDER BY id";
297 EXPECT_EQ("1,2,3,4,5,6,7,8,9",
298 ExecuteWithResults(&db(), kSelectAllSql, "|", ","));
299
300 // The failing statement should now succeed, with no results.
301 EXPECT_EQ("", ExecuteWithResults(&db(), kSelectSql, "|", ","));
302}
303
304// Build a database, corrupt it by making a table contain a row not
305// referenced by the index, then recover the database.
306TEST_F(SQLRecoveryTest, RecoverCorruptTable) {
307 const char kCreateTable[] = "CREATE TABLE x (id INTEGER, v INTEGER)";
308 const char kCreateIndex[] = "CREATE UNIQUE INDEX x_id ON x (id)";
309 ASSERT_TRUE(db().Execute(kCreateTable));
310 ASSERT_TRUE(db().Execute(kCreateIndex));
311
312 // Insert a bit of data.
313 {
314 ASSERT_TRUE(db().BeginTransaction());
315
316 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (?, ?)";
317 sql::Statement s(db().GetUniqueStatement(kInsertSql));
318 for (int i = 0; i < 10; ++i) {
319 s.Reset(true);
320 s.BindInt(0, i);
321 s.BindInt(1, i);
322 EXPECT_FALSE(s.Step());
323 EXPECT_TRUE(s.Succeeded());
324 }
325
326 ASSERT_TRUE(db().CommitTransaction());
327 }
[email protected]dd325f052013-08-06 02:37:40328 db().Close();
329
[email protected]ae4f1622013-12-08 06:49:12330 // Delete a row from the index while leaving a table entry.
331 const char kDeleteSql[] = "DELETE FROM x WHERE id = 0";
332 ASSERT_TRUE(sql::test::CorruptTableOrIndex(db_path(), "x", kDeleteSql));
[email protected]dd325f052013-08-06 02:37:40333
[email protected]dd325f052013-08-06 02:37:40334 ASSERT_TRUE(Reopen());
335
shess6ac39542016-02-04 20:56:22336 int error = SQLITE_OK;
337 db().set_error_callback(base::Bind(&RecoveryCallback, &db(), db_path(),
338 kCreateTable, kCreateIndex, &error));
339
[email protected]dd325f052013-08-06 02:37:40340 // Index shows one less than originally inserted.
341 const char kCountSql[] = "SELECT COUNT (*) FROM x";
342 EXPECT_EQ("9", ExecuteWithResults(&db(), kCountSql, "|", ","));
343
Scott Hessdcf120482015-02-10 21:33:29344 // A full table scan shows all of the original data. Using column [v] to
345 // force use of the table rather than the index.
346 const char kDistinctSql[] = "SELECT DISTINCT COUNT (v) FROM x";
[email protected]dd325f052013-08-06 02:37:40347 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ","));
348
349 // Insert id 0 again. Since it is not in the index, the insert
350 // succeeds, but results in a duplicate value in the table.
351 const char kInsertSql[] = "INSERT INTO x (id, v) VALUES (0, 100)";
352 ASSERT_TRUE(db().Execute(kInsertSql));
353
354 // Duplication is visible.
355 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ","));
356 EXPECT_EQ("11", ExecuteWithResults(&db(), kDistinctSql, "|", ","));
357
358 // This works before the callback is called.
359 const char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_master";
360 EXPECT_TRUE(db().IsSQLValid(kTrivialSql));
361
shess6ac39542016-02-04 20:56:22362 // TODO(shess): Figure out a statement which causes SQLite to notice the
363 // corruption. SELECT doesn't see errors because missing index values aren't
364 // visible. UPDATE or DELETE against v=0 don't see errors, even though the
365 // index item is missing. I suspect SQLite only deletes the key in these
366 // cases, but doesn't verify that one or more keys were deleted.
367 ASSERT_FALSE(db().Execute("INSERT INTO x (id, v) VALUES (0, 101)"));
368 EXPECT_EQ(SQLITE_CONSTRAINT_UNIQUE, error);
[email protected]dd325f052013-08-06 02:37:40369
370 // Database handle has been poisoned.
371 EXPECT_FALSE(db().IsSQLValid(kTrivialSql));
372
373 ASSERT_TRUE(Reopen());
374
375 // The recovered table has consistency between the index and the table.
376 EXPECT_EQ("10", ExecuteWithResults(&db(), kCountSql, "|", ","));
377 EXPECT_EQ("10", ExecuteWithResults(&db(), kDistinctSql, "|", ","));
378
shess806f4992016-02-04 21:12:09379 // Only one of the values is retained.
[email protected]dd325f052013-08-06 02:37:40380 const char kSelectSql[] = "SELECT v FROM x WHERE id = 0";
shess806f4992016-02-04 21:12:09381 const std::string results = ExecuteWithResults(&db(), kSelectSql, "|", ",");
382 EXPECT_TRUE(results=="100" || results=="0") << "Actual results: " << results;
[email protected]dd325f052013-08-06 02:37:40383}
[email protected]a8848a72013-11-18 04:18:47384
385TEST_F(SQLRecoveryTest, Meta) {
386 const int kVersion = 3;
387 const int kCompatibleVersion = 2;
388
389 {
390 sql::MetaTable meta;
391 EXPECT_TRUE(meta.Init(&db(), kVersion, kCompatibleVersion));
392 EXPECT_EQ(kVersion, meta.GetVersionNumber());
393 }
394
395 // Test expected case where everything works.
396 {
mostynbd82cd9952016-04-11 20:05:34397 std::unique_ptr<sql::Recovery> recovery =
398 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47399 EXPECT_TRUE(recovery->SetupMeta());
400 int version = 0;
401 EXPECT_TRUE(recovery->GetMetaVersionNumber(&version));
402 EXPECT_EQ(kVersion, version);
403
dchenge48600452015-12-28 02:24:50404 sql::Recovery::Rollback(std::move(recovery));
[email protected]a8848a72013-11-18 04:18:47405 }
406 ASSERT_TRUE(Reopen()); // Handle was poisoned.
407
408 // Test version row missing.
409 EXPECT_TRUE(db().Execute("DELETE FROM meta WHERE key = 'version'"));
410 {
mostynbd82cd9952016-04-11 20:05:34411 std::unique_ptr<sql::Recovery> recovery =
412 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47413 EXPECT_TRUE(recovery->SetupMeta());
414 int version = 0;
415 EXPECT_FALSE(recovery->GetMetaVersionNumber(&version));
416 EXPECT_EQ(0, version);
417
dchenge48600452015-12-28 02:24:50418 sql::Recovery::Rollback(std::move(recovery));
[email protected]a8848a72013-11-18 04:18:47419 }
420 ASSERT_TRUE(Reopen()); // Handle was poisoned.
421
422 // Test meta table missing.
423 EXPECT_TRUE(db().Execute("DROP TABLE meta"));
424 {
shess976814402016-06-21 06:56:25425 sql::test::ScopedErrorExpecter expecter;
426 expecter.ExpectError(SQLITE_CORRUPT); // From virtual table.
mostynbd82cd9952016-04-11 20:05:34427 std::unique_ptr<sql::Recovery> recovery =
428 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47429 EXPECT_FALSE(recovery->SetupMeta());
shess976814402016-06-21 06:56:25430 ASSERT_TRUE(expecter.SawExpectedErrors());
[email protected]a8848a72013-11-18 04:18:47431 }
432}
433
434// Baseline AutoRecoverTable() test.
435TEST_F(SQLRecoveryTest, AutoRecoverTable) {
436 // BIGINT and VARCHAR to test type affinity.
437 const char kCreateSql[] = "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)";
438 ASSERT_TRUE(db().Execute(kCreateSql));
439 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (11, 'This is', 'a test')"));
440 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, 'That was', 'a test')"));
441
442 // Save aside a copy of the original schema and data.
443 const std::string orig_schema(GetSchema(&db()));
444 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
445 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
446
447 // Create a lame-duck table which will not be propagated by recovery to
448 // detect that the recovery code actually ran.
449 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
450 ASSERT_NE(orig_schema, GetSchema(&db()));
451
452 {
mostynbd82cd9952016-04-11 20:05:34453 std::unique_ptr<sql::Recovery> recovery =
454 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47455 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
456
457 // Save a copy of the temp db's schema before recovering the table.
458 const char kTempSchemaSql[] = "SELECT name, sql FROM sqlite_temp_master";
459 const std::string temp_schema(
460 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
461
462 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44463 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47464 EXPECT_EQ(2u, rows);
465
466 // Test that any additional temp tables were cleaned up.
467 EXPECT_EQ(temp_schema,
468 ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
469
dchenge48600452015-12-28 02:24:50470 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47471 }
472
473 // Since the database was not corrupt, the entire schema and all
474 // data should be recovered.
475 ASSERT_TRUE(Reopen());
476 ASSERT_EQ(orig_schema, GetSchema(&db()));
477 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
478
479 // Recovery fails if the target table doesn't exist.
480 {
mostynbd82cd9952016-04-11 20:05:34481 std::unique_ptr<sql::Recovery> recovery =
482 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47483 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
484
485 // TODO(shess): Should this failure implicitly lead to Raze()?
486 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44487 EXPECT_FALSE(recovery->AutoRecoverTable("y", &rows));
[email protected]a8848a72013-11-18 04:18:47488
dchenge48600452015-12-28 02:24:50489 sql::Recovery::Unrecoverable(std::move(recovery));
[email protected]a8848a72013-11-18 04:18:47490 }
491}
492
493// Test that default values correctly replace nulls. The recovery
494// virtual table reads directly from the database, so DEFAULT is not
495// interpretted at that level.
496TEST_F(SQLRecoveryTest, AutoRecoverTableWithDefault) {
497 ASSERT_TRUE(db().Execute("CREATE TABLE x (id INTEGER)"));
498 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5)"));
499 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15)"));
500
501 // ALTER effectively leaves the new columns NULL in the first two
502 // rows. The row with 17 will get the default injected at insert
503 // time, while the row with 42 will get the actual value provided.
504 // Embedded "'" to make sure default-handling continues to be quoted
505 // correctly.
506 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'"));
507 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'"));
508 ASSERT_TRUE(db().Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93"));
509 ASSERT_TRUE(db().Execute("INSERT INTO x (id) VALUES (17)"));
510 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)"));
511
512 // Save aside a copy of the original schema and data.
513 const std::string orig_schema(GetSchema(&db()));
514 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
515 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
516
517 // Create a lame-duck table which will not be propagated by recovery to
518 // detect that the recovery code actually ran.
519 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
520 ASSERT_NE(orig_schema, GetSchema(&db()));
521
522 // Mechanically adjust the stored schema and data to allow detecting
523 // where the default value is coming from. The target table is just
524 // like the original with the default for [t] changed, to signal
525 // defaults coming from the recovery system. The two %5 rows should
526 // get the target-table default for [t], while the others should get
527 // the source-table default.
528 std::string final_schema(orig_schema);
529 std::string final_data(orig_data);
530 size_t pos;
531 while ((pos = final_schema.find("'a''a'")) != std::string::npos) {
532 final_schema.replace(pos, 6, "'c''c'");
533 }
534 while ((pos = final_data.find("5|a'a")) != std::string::npos) {
535 final_data.replace(pos, 5, "5|c'c");
536 }
537
538 {
mostynbd82cd9952016-04-11 20:05:34539 std::unique_ptr<sql::Recovery> recovery =
540 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47541 // Different default to detect which table provides the default.
542 ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str()));
543
544 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44545 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47546 EXPECT_EQ(4u, rows);
547
dchenge48600452015-12-28 02:24:50548 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47549 }
550
551 // Since the database was not corrupt, the entire schema and all
552 // data should be recovered.
553 ASSERT_TRUE(Reopen());
554 ASSERT_EQ(final_schema, GetSchema(&db()));
555 ASSERT_EQ(final_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
556}
557
558// Test that rows with NULL in a NOT NULL column are filtered
559// correctly. In the wild, this would probably happen due to
560// corruption, but here it is simulated by recovering a table which
561// allowed nulls into a table which does not.
562TEST_F(SQLRecoveryTest, AutoRecoverTableNullFilter) {
563 const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)";
564 const char kFinalSchema[] = "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)";
565
566 ASSERT_TRUE(db().Execute(kOrigSchema));
567 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (5, null)"));
568 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (15, 'this is a test')"));
569
570 // Create a lame-duck table which will not be propagated by recovery to
571 // detect that the recovery code actually ran.
572 ASSERT_EQ(kOrigSchema, GetSchema(&db()));
573 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
574 ASSERT_NE(kOrigSchema, GetSchema(&db()));
575
576 {
mostynbd82cd9952016-04-11 20:05:34577 std::unique_ptr<sql::Recovery> recovery =
578 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47579 ASSERT_TRUE(recovery->db()->Execute(kFinalSchema));
580
581 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44582 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47583 EXPECT_EQ(1u, rows);
584
dchenge48600452015-12-28 02:24:50585 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47586 }
587
588 // The schema should be the same, but only one row of data should
589 // have been recovered.
590 ASSERT_TRUE(Reopen());
591 ASSERT_EQ(kFinalSchema, GetSchema(&db()));
592 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
593 ASSERT_EQ("15|this is a test", ExecuteWithResults(&db(), kXSql, "|", "\n"));
594}
595
596// Test AutoRecoverTable with a ROWID alias.
597TEST_F(SQLRecoveryTest, AutoRecoverTableWithRowid) {
598 // The rowid alias is almost always the first column, intentionally
599 // put it later.
600 const char kCreateSql[] =
601 "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)";
602 ASSERT_TRUE(db().Execute(kCreateSql));
603 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('This is a test', null)"));
604 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES ('That was a test', null)"));
605
606 // Save aside a copy of the original schema and data.
607 const std::string orig_schema(GetSchema(&db()));
608 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
609 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
610
611 // Create a lame-duck table which will not be propagated by recovery to
612 // detect that the recovery code actually ran.
613 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
614 ASSERT_NE(orig_schema, GetSchema(&db()));
615
616 {
mostynbd82cd9952016-04-11 20:05:34617 std::unique_ptr<sql::Recovery> recovery =
618 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47619 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
620
621 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44622 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47623 EXPECT_EQ(2u, rows);
624
dchenge48600452015-12-28 02:24:50625 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47626 }
627
628 // Since the database was not corrupt, the entire schema and all
629 // data should be recovered.
630 ASSERT_TRUE(Reopen());
631 ASSERT_EQ(orig_schema, GetSchema(&db()));
632 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
633}
634
635// Test that a compound primary key doesn't fire the ROWID code.
636TEST_F(SQLRecoveryTest, AutoRecoverTableWithCompoundKey) {
637 const char kCreateSql[] =
638 "CREATE TABLE x ("
639 "id INTEGER NOT NULL,"
640 "id2 TEXT NOT NULL,"
641 "t TEXT,"
642 "PRIMARY KEY (id, id2)"
643 ")";
644 ASSERT_TRUE(db().Execute(kCreateSql));
645
646 // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will
647 // be the ROWID values.
648 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')"));
649 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')"));
650 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'a', 'Another test')"));
651
652 // Save aside a copy of the original schema and data.
653 const std::string orig_schema(GetSchema(&db()));
654 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
655 const std::string orig_data(ExecuteWithResults(&db(), kXSql, "|", "\n"));
656
657 // Create a lame-duck table which will not be propagated by recovery to
658 // detect that the recovery code actually ran.
659 ASSERT_TRUE(db().Execute("CREATE TABLE y (c TEXT)"));
660 ASSERT_NE(orig_schema, GetSchema(&db()));
661
662 {
mostynbd82cd9952016-04-11 20:05:34663 std::unique_ptr<sql::Recovery> recovery =
664 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47665 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
666
667 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44668 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47669 EXPECT_EQ(3u, rows);
670
dchenge48600452015-12-28 02:24:50671 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47672 }
673
674 // Since the database was not corrupt, the entire schema and all
675 // data should be recovered.
676 ASSERT_TRUE(Reopen());
677 ASSERT_EQ(orig_schema, GetSchema(&db()));
678 ASSERT_EQ(orig_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
679}
680
shess6f68bd32016-02-04 19:29:44681// Test recovering from a table with fewer columns than the target.
682TEST_F(SQLRecoveryTest, AutoRecoverTableMissingColumns) {
[email protected]a8848a72013-11-18 04:18:47683 const char kCreateSql[] = "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
shess6f68bd32016-02-04 19:29:44684 const char kAlterSql[] = "ALTER TABLE x ADD COLUMN t1 TEXT DEFAULT 't'";
[email protected]a8848a72013-11-18 04:18:47685 ASSERT_TRUE(db().Execute(kCreateSql));
686 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (1, 'This is')"));
687 ASSERT_TRUE(db().Execute("INSERT INTO x VALUES (2, 'That was')"));
688
shess6f68bd32016-02-04 19:29:44689 // Generate the expected info by faking a table to match what recovery will
690 // create.
[email protected]a8848a72013-11-18 04:18:47691 const std::string orig_schema(GetSchema(&db()));
692 const char kXSql[] = "SELECT * FROM x ORDER BY 1";
shess6f68bd32016-02-04 19:29:44693 std::string expected_schema;
694 std::string expected_data;
695 {
696 ASSERT_TRUE(db().BeginTransaction());
697 ASSERT_TRUE(db().Execute(kAlterSql));
[email protected]a8848a72013-11-18 04:18:47698
shess6f68bd32016-02-04 19:29:44699 expected_schema = GetSchema(&db());
700 expected_data = ExecuteWithResults(&db(), kXSql, "|", "\n");
[email protected]a8848a72013-11-18 04:18:47701
shess6f68bd32016-02-04 19:29:44702 db().RollbackTransaction();
703 }
704
705 // Following tests are pointless if the rollback didn't work.
706 ASSERT_EQ(orig_schema, GetSchema(&db()));
707
708 // Recover the previous version of the table into the altered version.
[email protected]a8848a72013-11-18 04:18:47709 {
mostynbd82cd9952016-04-11 20:05:34710 std::unique_ptr<sql::Recovery> recovery =
711 sql::Recovery::Begin(&db(), db_path());
[email protected]a8848a72013-11-18 04:18:47712 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
shess6f68bd32016-02-04 19:29:44713 ASSERT_TRUE(recovery->db()->Execute(kAlterSql));
[email protected]a8848a72013-11-18 04:18:47714 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44715 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]a8848a72013-11-18 04:18:47716 EXPECT_EQ(2u, rows);
dchenge48600452015-12-28 02:24:50717 ASSERT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]a8848a72013-11-18 04:18:47718 }
719
720 // Since the database was not corrupt, the entire schema and all
721 // data should be recovered.
722 ASSERT_TRUE(Reopen());
shess6f68bd32016-02-04 19:29:44723 ASSERT_EQ(expected_schema, GetSchema(&db()));
724 ASSERT_EQ(expected_data, ExecuteWithResults(&db(), kXSql, "|", "\n"));
[email protected]a8848a72013-11-18 04:18:47725}
[email protected]cfb821612014-07-10 00:48:06726
727// Recover a golden file where an interior page has been manually modified so
728// that the number of cells is greater than will fit on a single page. This
729// case happened in <http://crbug.com/387868>.
730TEST_F(SQLRecoveryTest, Bug387868) {
731 base::FilePath golden_path;
732 ASSERT_TRUE(PathService::Get(sql::test::DIR_TEST_DATA, &golden_path));
733 golden_path = golden_path.AppendASCII("recovery_387868");
734 db().Close();
735 ASSERT_TRUE(base::CopyFile(golden_path, db_path()));
736 ASSERT_TRUE(Reopen());
737
738 {
mostynbd82cd9952016-04-11 20:05:34739 std::unique_ptr<sql::Recovery> recovery =
740 sql::Recovery::Begin(&db(), db_path());
[email protected]cfb821612014-07-10 00:48:06741 ASSERT_TRUE(recovery.get());
742
743 // Create the new version of the table.
744 const char kCreateSql[] =
745 "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
746 ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
747
748 size_t rows = 0;
shess6f68bd32016-02-04 19:29:44749 EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
[email protected]cfb821612014-07-10 00:48:06750 EXPECT_EQ(43u, rows);
751
752 // Successfully recovered.
dchenge48600452015-12-28 02:24:50753 EXPECT_TRUE(sql::Recovery::Recovered(std::move(recovery)));
[email protected]cfb821612014-07-10 00:48:06754 }
755}
[email protected]dd325f052013-08-06 02:37:40756
shess2f3a814b2015-11-05 18:11:10757// Memory-mapped I/O interacts poorly with I/O errors. Make sure the recovery
758// database doesn't accidentally enable it.
759TEST_F(SQLRecoveryTest, NoMmap) {
mostynbd82cd9952016-04-11 20:05:34760 std::unique_ptr<sql::Recovery> recovery =
761 sql::Recovery::Begin(&db(), db_path());
shess2f3a814b2015-11-05 18:11:10762 ASSERT_TRUE(recovery.get());
763
764 // In the current implementation, the PRAGMA successfully runs with no result
765 // rows. Running with a single result of |0| is also acceptable.
766 sql::Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size"));
767 EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0));
768}
769
[email protected]8d409412013-07-19 18:25:30770} // namespace