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 "base/files/file_path.h" | 7 #include "base/files/file_path.h" |
8 #include "base/format_macros.h" | |
8 #include "base/logging.h" | 9 #include "base/logging.h" |
9 #include "base/metrics/sparse_histogram.h" | 10 #include "base/metrics/sparse_histogram.h" |
11 #include "base/strings/string_util.h" | |
12 #include "base/strings/stringprintf.h" | |
10 #include "sql/connection.h" | 13 #include "sql/connection.h" |
14 #include "sql/statement.h" | |
11 #include "third_party/sqlite/sqlite3.h" | 15 #include "third_party/sqlite/sqlite3.h" |
12 | 16 |
13 namespace sql { | 17 namespace sql { |
14 | 18 |
15 // static | 19 // static |
16 bool Recovery::FullRecoverySupported() { | 20 bool Recovery::FullRecoverySupported() { |
17 // TODO(shess): See comment in Init(). | 21 // TODO(shess): See comment in Init(). |
18 #if defined(USE_SYSTEM_SQLITE) | 22 #if defined(USE_SYSTEM_SQLITE) |
19 return false; | 23 return false; |
20 #else | 24 #else |
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
226 | 230 |
227 recover_db_.Close(); | 231 recover_db_.Close(); |
228 if (raze == RAZE_AND_POISON) { | 232 if (raze == RAZE_AND_POISON) { |
229 db_->RazeAndClose(); | 233 db_->RazeAndClose(); |
230 } else if (raze == POISON) { | 234 } else if (raze == POISON) { |
231 db_->Poison(); | 235 db_->Poison(); |
232 } | 236 } |
233 db_ = NULL; | 237 db_ = NULL; |
234 } | 238 } |
235 | 239 |
240 bool Recovery::AutoRecoverTable(const char* table_name, size_t extend_columns) { | |
241 // Query the info for the recovered table in database [main]. | |
242 std::string query( | |
243 base::StringPrintf("PRAGMA main.table_info(%s)", table_name)); | |
244 Statement s(db()->GetUniqueStatement(query.c_str())); | |
245 | |
246 // The columns of the recover virtual table. | |
247 std::vector<std::string> create_column_decls; | |
248 | |
249 // The columns to select from the recover virtual table when copying | |
250 // to the recovered table. | |
251 std::vector<std::string> insert_columns; | |
252 | |
253 // If PRIMARY KEY is a single INTEGER column, then it is an alias | |
254 // for ROWID. The primary key can be compound, so this can only be | |
255 // determined after processing all column data and tracking what is | |
256 // seen. |pk_column_count| counts the columns in the primary key. | |
257 // |rowid_decl| stores the ROWID version of the last INTEGER column | |
258 // seen, which is at |rowid_ofs| in |create_column_decls|. | |
259 size_t pk_column_count = 0; | |
260 size_t rowid_ofs; // Only valid if rowid_decl is set. | |
261 std::string rowid_decl; // ROWID version of column |rowid_ofs|. | |
262 | |
263 while (s.Step()) { | |
264 const std::string column_name(s.ColumnString(1)); | |
265 const std::string column_type(s.ColumnString(2)); | |
266 const bool not_null = s.ColumnBool(3); | |
267 const int default_type = s.ColumnType(4); | |
268 const bool default_is_null = (default_type == COLUMN_TYPE_NULL); | |
269 const int pk_column = s.ColumnInt(5); | |
270 | |
271 if (pk_column > 0) { | |
272 // TODO(shess): http://www.sqlite.org/pragma.html#pragma_table_info | |
273 // documents column 5 as the index of the column in the primary key | |
274 // (zero for not in primary key). I find that it is always 1 for | |
275 // columns in the primary key. Since this code is very dependent on | |
276 // that pragma, review if the implementation changes. | |
277 DCHECK_EQ(pk_column, 1); | |
278 ++pk_column_count; | |
279 } | |
280 | |
281 // Construct column declaration as "name type [optional constraint]". | |
282 std::string column_decl = column_name; | |
283 | |
284 // SQLite's affinity detection is documented at: | |
285 // http://www.sqlite.org/datatype3.html#affname | |
286 // The gist of it is that CHAR, TEXT, and INT use substring matches. | |
287 if (column_type.find("INT") != std::string::npos) { | |
288 if (pk_column == 1) { | |
289 rowid_ofs = create_column_decls.size(); | |
290 rowid_decl = column_name + " ROWID"; | |
291 } | |
292 column_decl += " INTEGER"; | |
293 } else if (column_type.find("CHAR") != std::string::npos || | |
294 column_type.find("TEXT") != std::string::npos) { | |
295 column_decl += " TEXT"; | |
296 } else if (column_type == "BLOB") { | |
297 column_decl += " BLOB"; | |
298 } else { | |
299 // TODO(shess): AFAICT, there remain: | |
300 // - contains("CLOB") -> TEXT | |
301 // - contains("REAL") -> REAL | |
302 // - contains("FLOA") -> REAL | |
303 // - contains("DOUB") -> REAL | |
304 // - other -> "NUMERIC" | |
305 // Just code those in as they come up. | |
306 NOTREACHED() << " Unsupported type " << column_type; | |
307 return false; | |
308 } | |
309 | |
310 // If column has constraint "NOT NULL", then inserting NULL into | |
311 // that column will fail. If the column has a non-NULL DEFAULT | |
312 // specified, the INSERT will handle it (see below). If the | |
313 // DEFAULT is also NULL, the row must be filtered out. | |
314 // TODO(shess): The above scenario applies to INSERT OR REPLACE, | |
315 // whereas INSERT OR IGNORE drops such rows. | |
316 // http://www.sqlite.org/lang_conflict.html | |
317 if (not_null && default_is_null) | |
318 column_decl += " NOT NULL"; | |
319 | |
320 create_column_decls.push_back(column_decl); | |
321 | |
322 // Per the NOTE in the header file, convert NULL values to the | |
323 // DEFAULT. All columns could be IFNULL(column_name,default), but | |
324 // the NULL case would require special handling either way. | |
325 if (default_is_null) { | |
326 insert_columns.push_back(column_name); | |
327 } else { | |
328 // The default value appears to be pre-quoted, as if it is | |
329 // literally from the sqlite_master CREATE statement. | |
330 std::string default_value = s.ColumnString(4); | |
331 insert_columns.push_back(base::StringPrintf( | |
332 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); | |
333 } | |
334 } | |
335 | |
336 // Receiving no column information implies that the table doesn't exist. | |
337 if (create_column_decls.empty()) | |
338 return false; | |
339 | |
340 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. | |
341 if (pk_column_count == 1 && !rowid_decl.empty()) | |
342 create_column_decls[rowid_ofs] = rowid_decl; | |
343 | |
344 // Additional columns accept anything. | |
345 // TODO(shess): ignoreN isn't well namespaced. But it will fail to | |
346 // execute in case of conflicts. | |
347 for (size_t i = 0; i < extend_columns; ++i) { | |
348 create_column_decls.push_back( | |
349 base::StringPrintf(", ignore%" PRIuS " ANY", i)); | |
pkotwicz
2013/11/04 02:38:31
Is the comma at the beginning of the statement des
Scott Hess - ex-Googler
2013/11/04 21:41:44
Hmm, leftover from a previous version which append
Scott Hess - ex-Googler
2013/11/12 20:25:24
Weird - virtual table doesn't even see the empty c
| |
350 } | |
351 | |
352 std::string recover_create(base::StringPrintf( | |
353 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", | |
354 table_name, | |
355 table_name, | |
356 JoinString(create_column_decls, ',').c_str())); | |
357 | |
358 std::string recover_insert(base::StringPrintf( | |
359 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", | |
360 table_name, | |
361 JoinString(insert_columns, ',').c_str(), | |
362 table_name)); | |
363 | |
364 std::string recover_drop(base::StringPrintf( | |
365 "DROP TABLE temp.recover_%s", table_name)); | |
366 | |
367 if (!db()->Execute(recover_create.c_str())) | |
368 return false; | |
369 | |
370 if (!db()->Execute(recover_insert.c_str())) { | |
371 ignore_result(db()->Execute(recover_drop.c_str())); | |
372 return false; | |
373 } | |
374 | |
375 // TODO(shess): Is leaving the recover table around a breaker? | |
376 return db()->Execute(recover_drop.c_str()); | |
377 } | |
378 | |
379 bool Recovery::SetupMeta() { | |
380 const char kCreateSql[] = | |
381 "CREATE VIRTUAL TABLE temp.recover_meta USING recover" | |
382 "(" | |
383 "corrupt.meta," | |
384 "key TEXT NOT NULL," | |
385 "value ANY" // Whatever is stored. | |
386 ")"; | |
387 return db()->Execute(kCreateSql); | |
388 } | |
389 | |
390 bool Recovery::GetMetaVersionNumber(int* version) { | |
391 DCHECK(version); | |
392 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); | |
393 // Unfortunately, DoesTableExist() queries sqlite_master, not | |
394 // sqlite_temp_master. | |
395 | |
396 const char kVersionSql[] = | |
397 "SELECT value FROM temp.recover_meta WHERE key = 'version'"; | |
398 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); | |
399 if (!recovery_version.Step()) | |
400 return false; | |
401 | |
402 *version = recovery_version.ColumnInt(0); | |
403 return true; | |
404 } | |
405 | |
236 } // namespace sql | 406 } // namespace sql |
OLD | NEW |