1 // Copyright 2012 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 5 #include "sync/syncable/directory_backing_store.h" 6 7 #include "build/build_config.h" 8 9 #include <limits> 10 11 #include "base/base64.h" 12 #include "base/debug/trace_event.h" 13 #include "base/logging.h" 14 #include "base/rand_util.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/time/time.h" 17 #include "sql/connection.h" 18 #include "sql/statement.h" 19 #include "sql/transaction.h" 20 #include "sync/internal_api/public/base/node_ordinal.h" 21 #include "sync/protocol/bookmark_specifics.pb.h" 22 #include "sync/protocol/sync.pb.h" 23 #include "sync/syncable/syncable-inl.h" 24 #include "sync/syncable/syncable_columns.h" 25 #include "sync/syncable/syncable_util.h" 26 #include "sync/util/time.h" 27 28 using std::string; 29 30 namespace syncer { 31 namespace syncable { 32 33 // This just has to be big enough to hold an UPDATE or INSERT statement that 34 // modifies all the columns in the entry table. 35 static const string::size_type kUpdateStatementBufferSize = 2048; 36 37 // Increment this version whenever updating DB tables. 38 const int32 kCurrentDBVersion = 86; 39 40 // Iterate over the fields of |entry| and bind each to |statement| for 41 // updating. Returns the number of args bound. 42 void BindFields(const EntryKernel& entry, 43 sql::Statement* statement) { 44 int index = 0; 45 int i = 0; 46 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) { 47 statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i))); 48 } 49 for ( ; i < TIME_FIELDS_END; ++i) { 50 statement->BindInt64(index++, 51 TimeToProtoTime( 52 entry.ref(static_cast<TimeField>(i)))); 53 } 54 for ( ; i < ID_FIELDS_END; ++i) { 55 statement->BindString(index++, entry.ref(static_cast<IdField>(i)).s_); 56 } 57 for ( ; i < BIT_FIELDS_END; ++i) { 58 statement->BindInt(index++, entry.ref(static_cast<BitField>(i))); 59 } 60 for ( ; i < STRING_FIELDS_END; ++i) { 61 statement->BindString(index++, entry.ref(static_cast<StringField>(i))); 62 } 63 for ( ; i < PROTO_FIELDS_END; ++i) { 64 std::string temp; 65 entry.ref(static_cast<ProtoField>(i)).SerializeToString(&temp); 66 statement->BindBlob(index++, temp.data(), temp.length()); 67 } 68 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) { 69 std::string temp; 70 entry.ref(static_cast<UniquePositionField>(i)).SerializeToString(&temp); 71 statement->BindBlob(index++, temp.data(), temp.length()); 72 } 73 } 74 75 // The caller owns the returned EntryKernel*. Assumes the statement currently 76 // points to a valid row in the metas table. Returns NULL to indicate that 77 // it detected a corruption in the data on unpacking. 78 scoped_ptr<EntryKernel> UnpackEntry(sql::Statement* statement) { 79 scoped_ptr<EntryKernel> kernel(new EntryKernel()); 80 DCHECK_EQ(statement->ColumnCount(), static_cast<int>(FIELD_COUNT)); 81 int i = 0; 82 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) { 83 kernel->put(static_cast<Int64Field>(i), statement->ColumnInt64(i)); 84 } 85 for ( ; i < TIME_FIELDS_END; ++i) { 86 kernel->put(static_cast<TimeField>(i), 87 ProtoTimeToTime(statement->ColumnInt64(i))); 88 } 89 for ( ; i < ID_FIELDS_END; ++i) { 90 kernel->mutable_ref(static_cast<IdField>(i)).s_ = 91 statement->ColumnString(i); 92 } 93 for ( ; i < BIT_FIELDS_END; ++i) { 94 kernel->put(static_cast<BitField>(i), (0 != statement->ColumnInt(i))); 95 } 96 for ( ; i < STRING_FIELDS_END; ++i) { 97 kernel->put(static_cast<StringField>(i), 98 statement->ColumnString(i)); 99 } 100 for ( ; i < PROTO_FIELDS_END; ++i) { 101 kernel->mutable_ref(static_cast<ProtoField>(i)).ParseFromArray( 102 statement->ColumnBlob(i), statement->ColumnByteLength(i)); 103 } 104 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) { 105 std::string temp; 106 statement->ColumnBlobAsString(i, &temp); 107 108 sync_pb::UniquePosition proto; 109 if (!proto.ParseFromString(temp)) { 110 DVLOG(1) << "Unpacked invalid position. Assuming the DB is corrupt"; 111 return scoped_ptr<EntryKernel>(); 112 } 113 114 kernel->mutable_ref(static_cast<UniquePositionField>(i)) = 115 UniquePosition::FromProto(proto); 116 } 117 return kernel.Pass(); 118 } 119 120 namespace { 121 122 string ComposeCreateTableColumnSpecs() { 123 const ColumnSpec* begin = g_metas_columns; 124 const ColumnSpec* end = g_metas_columns + arraysize(g_metas_columns); 125 string query; 126 query.reserve(kUpdateStatementBufferSize); 127 char separator = '('; 128 for (const ColumnSpec* column = begin; column != end; ++column) { 129 query.push_back(separator); 130 separator = ','; 131 query.append(column->name); 132 query.push_back(' '); 133 query.append(column->spec); 134 } 135 query.push_back(')'); 136 return query; 137 } 138 139 void AppendColumnList(std::string* output) { 140 const char* joiner = " "; 141 // Be explicit in SELECT order to match up with UnpackEntry. 142 for (int i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) { 143 output->append(joiner); 144 output->append(ColumnName(i)); 145 joiner = ", "; 146 } 147 } 148 149 } // namespace 150 151 /////////////////////////////////////////////////////////////////////////////// 152 // DirectoryBackingStore implementation. 153 154 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name) 155 : db_(new sql::Connection()), 156 dir_name_(dir_name), 157 needs_column_refresh_(false) { 158 db_->set_histogram_tag("SyncDirectory"); 159 db_->set_page_size(4096); 160 db_->set_cache_size(32); 161 } 162 163 DirectoryBackingStore::DirectoryBackingStore(const string& dir_name, 164 sql::Connection* db) 165 : db_(db), 166 dir_name_(dir_name), 167 needs_column_refresh_(false) { 168 } 169 170 DirectoryBackingStore::~DirectoryBackingStore() { 171 } 172 173 bool DirectoryBackingStore::DeleteEntries(EntryTable from, 174 const MetahandleSet& handles) { 175 if (handles.empty()) 176 return true; 177 178 sql::Statement statement; 179 // Call GetCachedStatement() separately to get different statements for 180 // different tables. 181 switch (from) { 182 case METAS_TABLE: 183 statement.Assign(db_->GetCachedStatement( 184 SQL_FROM_HERE, "DELETE FROM metas WHERE metahandle = ?")); 185 break; 186 case DELETE_JOURNAL_TABLE: 187 statement.Assign(db_->GetCachedStatement( 188 SQL_FROM_HERE, "DELETE FROM deleted_metas WHERE metahandle = ?")); 189 break; 190 } 191 192 for (MetahandleSet::const_iterator i = handles.begin(); i != handles.end(); 193 ++i) { 194 statement.BindInt64(0, *i); 195 if (!statement.Run()) 196 return false; 197 statement.Reset(true); 198 } 199 return true; 200 } 201 202 bool DirectoryBackingStore::SaveChanges( 203 const Directory::SaveChangesSnapshot& snapshot) { 204 DCHECK(CalledOnValidThread()); 205 DCHECK(db_->is_open()); 206 207 // Back out early if there is nothing to write. 208 bool save_info = 209 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status); 210 if (snapshot.dirty_metas.empty() && snapshot.metahandles_to_purge.empty() && 211 snapshot.delete_journals.empty() && 212 snapshot.delete_journals_to_purge.empty() && !save_info) { 213 return true; 214 } 215 216 sql::Transaction transaction(db_.get()); 217 if (!transaction.Begin()) 218 return false; 219 220 PrepareSaveEntryStatement(METAS_TABLE, &save_meta_statment_); 221 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); 222 i != snapshot.dirty_metas.end(); ++i) { 223 DCHECK((*i)->is_dirty()); 224 if (!SaveEntryToDB(&save_meta_statment_, **i)) 225 return false; 226 } 227 228 if (!DeleteEntries(METAS_TABLE, snapshot.metahandles_to_purge)) 229 return false; 230 231 PrepareSaveEntryStatement(DELETE_JOURNAL_TABLE, 232 &save_delete_journal_statment_); 233 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin(); 234 i != snapshot.delete_journals.end(); ++i) { 235 if (!SaveEntryToDB(&save_delete_journal_statment_, **i)) 236 return false; 237 } 238 239 if (!DeleteEntries(DELETE_JOURNAL_TABLE, snapshot.delete_journals_to_purge)) 240 return false; 241 242 if (save_info) { 243 const Directory::PersistedKernelInfo& info = snapshot.kernel_info; 244 sql::Statement s1(db_->GetCachedStatement( 245 SQL_FROM_HERE, 246 "UPDATE share_info " 247 "SET store_birthday = ?, " 248 "next_id = ?, " 249 "bag_of_chips = ?")); 250 s1.BindString(0, info.store_birthday); 251 s1.BindInt64(1, info.next_id); 252 s1.BindBlob(2, info.bag_of_chips.data(), info.bag_of_chips.size()); 253 254 if (!s1.Run()) 255 return false; 256 DCHECK_EQ(db_->GetLastChangeCount(), 1); 257 258 sql::Statement s2(db_->GetCachedStatement( 259 SQL_FROM_HERE, 260 "INSERT OR REPLACE " 261 "INTO models (model_id, progress_marker, transaction_version) " 262 "VALUES (?, ?, ?)")); 263 264 ModelTypeSet protocol_types = ProtocolTypes(); 265 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good(); 266 iter.Inc()) { 267 ModelType type = iter.Get(); 268 // We persist not ModelType but rather a protobuf-derived ID. 269 string model_id = ModelTypeEnumToModelId(type); 270 string progress_marker; 271 info.download_progress[type].SerializeToString(&progress_marker); 272 s2.BindBlob(0, model_id.data(), model_id.length()); 273 s2.BindBlob(1, progress_marker.data(), progress_marker.length()); 274 s2.BindInt64(2, info.transaction_version[type]); 275 if (!s2.Run()) 276 return false; 277 DCHECK_EQ(db_->GetLastChangeCount(), 1); 278 s2.Reset(true); 279 } 280 } 281 282 return transaction.Commit(); 283 } 284 285 bool DirectoryBackingStore::InitializeTables() { 286 sql::Transaction transaction(db_.get()); 287 if (!transaction.Begin()) 288 return false; 289 290 int version_on_disk = GetVersion(); 291 292 // Upgrade from version 67. Version 67 was widely distributed as the original 293 // Bookmark Sync release. Version 68 removed unique naming. 294 if (version_on_disk == 67) { 295 if (MigrateVersion67To68()) 296 version_on_disk = 68; 297 } 298 // Version 69 introduced additional datatypes. 299 if (version_on_disk == 68) { 300 if (MigrateVersion68To69()) 301 version_on_disk = 69; 302 } 303 304 if (version_on_disk == 69) { 305 if (MigrateVersion69To70()) 306 version_on_disk = 70; 307 } 308 309 // Version 71 changed the sync progress information to be per-datatype. 310 if (version_on_disk == 70) { 311 if (MigrateVersion70To71()) 312 version_on_disk = 71; 313 } 314 315 // Version 72 removed extended attributes, a legacy way to do extensible 316 // key/value information, stored in their own table. 317 if (version_on_disk == 71) { 318 if (MigrateVersion71To72()) 319 version_on_disk = 72; 320 } 321 322 // Version 73 added a field for notification state. 323 if (version_on_disk == 72) { 324 if (MigrateVersion72To73()) 325 version_on_disk = 73; 326 } 327 328 // Version 74 added state for the autofill migration. 329 if (version_on_disk == 73) { 330 if (MigrateVersion73To74()) 331 version_on_disk = 74; 332 } 333 334 // Version 75 migrated from int64-based timestamps to per-datatype tokens. 335 if (version_on_disk == 74) { 336 if (MigrateVersion74To75()) 337 version_on_disk = 75; 338 } 339 340 // Version 76 removed all (5) autofill migration related columns. 341 if (version_on_disk == 75) { 342 if (MigrateVersion75To76()) 343 version_on_disk = 76; 344 } 345 346 // Version 77 standardized all time fields to ms since the Unix 347 // epoch. 348 if (version_on_disk == 76) { 349 if (MigrateVersion76To77()) 350 version_on_disk = 77; 351 } 352 353 // Version 78 added the column base_server_specifics to the metas table. 354 if (version_on_disk == 77) { 355 if (MigrateVersion77To78()) 356 version_on_disk = 78; 357 } 358 359 // Version 79 migration is a one-time fix for some users in a bad state. 360 if (version_on_disk == 78) { 361 if (MigrateVersion78To79()) 362 version_on_disk = 79; 363 } 364 365 // Version 80 migration is adding the bag_of_chips column. 366 if (version_on_disk == 79) { 367 if (MigrateVersion79To80()) 368 version_on_disk = 80; 369 } 370 371 // Version 81 replaces the int64 server_position_in_parent_field 372 // with a blob server_ordinal_in_parent field. 373 if (version_on_disk == 80) { 374 if (MigrateVersion80To81()) 375 version_on_disk = 81; 376 } 377 378 // Version 82 migration added transaction_version column per data type. 379 if (version_on_disk == 81) { 380 if (MigrateVersion81To82()) 381 version_on_disk = 82; 382 } 383 384 // Version 83 migration added transaction_version column per sync entry. 385 if (version_on_disk == 82) { 386 if (MigrateVersion82To83()) 387 version_on_disk = 83; 388 } 389 390 // Version 84 migration added deleted_metas table. 391 if (version_on_disk == 83) { 392 if (MigrateVersion83To84()) 393 version_on_disk = 84; 394 } 395 396 // Version 85 migration removes the initial_sync_ended bits. 397 if (version_on_disk == 84) { 398 if (MigrateVersion84To85()) 399 version_on_disk = 85; 400 } 401 402 // Version 86 migration converts bookmarks to the unique positioning system. 403 // It also introduces a new field to store a unique ID for each bookmark. 404 if (version_on_disk == 85) { 405 if (MigrateVersion85To86()) 406 version_on_disk = 86; 407 } 408 409 // If one of the migrations requested it, drop columns that aren't current. 410 // It's only safe to do this after migrating all the way to the current 411 // version. 412 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) { 413 if (!RefreshColumns()) 414 version_on_disk = 0; 415 } 416 417 // A final, alternative catch-all migration to simply re-sync everything. 418 if (version_on_disk != kCurrentDBVersion) { 419 if (version_on_disk > kCurrentDBVersion) 420 return false; 421 422 // Fallback (re-sync everything) migration path. 423 DVLOG(1) << "Old/null sync database, version " << version_on_disk; 424 // Delete the existing database (if any), and create a fresh one. 425 DropAllTables(); 426 if (!CreateTables()) 427 return false; 428 } 429 430 sql::Statement s(db_->GetUniqueStatement( 431 "SELECT db_create_version, db_create_time FROM share_info")); 432 if (!s.Step()) 433 return false; 434 string db_create_version = s.ColumnString(0); 435 int db_create_time = s.ColumnInt(1); 436 DVLOG(1) << "DB created at " << db_create_time << " by version " << 437 db_create_version; 438 439 return transaction.Commit(); 440 } 441 442 // This function drops unused columns by creating a new table that contains only 443 // the currently used columns then copying all rows from the old tables into 444 // this new one. The tables are then rearranged so the new replaces the old. 445 bool DirectoryBackingStore::RefreshColumns() { 446 DCHECK(needs_column_refresh_); 447 448 // Create a new table named temp_metas. 449 SafeDropTable("temp_metas"); 450 if (!CreateMetasTable(true)) 451 return false; 452 453 // Populate temp_metas from metas. 454 // 455 // At this point, the metas table may contain columns belonging to obsolete 456 // schema versions. This statement explicitly lists only the columns that 457 // belong to the current schema version, so the obsolete columns will be 458 // effectively dropped once we rename temp_metas over top of metas. 459 std::string query = "INSERT INTO temp_metas ("; 460 AppendColumnList(&query); 461 query.append(") SELECT "); 462 AppendColumnList(&query); 463 query.append(" FROM metas"); 464 if (!db_->Execute(query.c_str())) 465 return false; 466 467 // Drop metas. 468 SafeDropTable("metas"); 469 470 // Rename temp_metas -> metas. 471 if (!db_->Execute("ALTER TABLE temp_metas RENAME TO metas")) 472 return false; 473 474 // Repeat the process for share_info. 475 SafeDropTable("temp_share_info"); 476 if (!CreateShareInfoTable(true)) 477 return false; 478 479 // TODO(rlarocque, 124140): Remove notification_state. 480 if (!db_->Execute( 481 "INSERT INTO temp_share_info (id, name, store_birthday, " 482 "db_create_version, db_create_time, next_id, cache_guid," 483 "notification_state, bag_of_chips) " 484 "SELECT id, name, store_birthday, db_create_version, " 485 "db_create_time, next_id, cache_guid, notification_state, " 486 "bag_of_chips " 487 "FROM share_info")) 488 return false; 489 490 SafeDropTable("share_info"); 491 if (!db_->Execute("ALTER TABLE temp_share_info RENAME TO share_info")) 492 return false; 493 494 needs_column_refresh_ = false; 495 return true; 496 } 497 498 bool DirectoryBackingStore::LoadEntries( 499 Directory::MetahandlesMap* handles_map) { 500 string select; 501 select.reserve(kUpdateStatementBufferSize); 502 select.append("SELECT "); 503 AppendColumnList(&select); 504 select.append(" FROM metas"); 505 506 sql::Statement s(db_->GetUniqueStatement(select.c_str())); 507 508 while (s.Step()) { 509 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s); 510 // A null kernel is evidence of external data corruption. 511 if (!kernel) 512 return false; 513 514 int64 handle = kernel->ref(META_HANDLE); 515 (*handles_map)[handle] = kernel.release(); 516 } 517 return s.Succeeded(); 518 } 519 520 bool DirectoryBackingStore::LoadDeleteJournals( 521 JournalIndex* delete_journals) { 522 string select; 523 select.reserve(kUpdateStatementBufferSize); 524 select.append("SELECT "); 525 AppendColumnList(&select); 526 select.append(" FROM deleted_metas"); 527 528 sql::Statement s(db_->GetUniqueStatement(select.c_str())); 529 530 while (s.Step()) { 531 scoped_ptr<EntryKernel> kernel = UnpackEntry(&s); 532 // A null kernel is evidence of external data corruption. 533 if (!kernel) 534 return false; 535 delete_journals->insert(kernel.release()); 536 } 537 return s.Succeeded(); 538 } 539 540 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) { 541 { 542 sql::Statement s( 543 db_->GetUniqueStatement( 544 "SELECT store_birthday, next_id, cache_guid, bag_of_chips " 545 "FROM share_info")); 546 if (!s.Step()) 547 return false; 548 549 info->kernel_info.store_birthday = s.ColumnString(0); 550 info->kernel_info.next_id = s.ColumnInt64(1); 551 info->cache_guid = s.ColumnString(2); 552 s.ColumnBlobAsString(3, &(info->kernel_info.bag_of_chips)); 553 554 // Verify there was only one row returned. 555 DCHECK(!s.Step()); 556 DCHECK(s.Succeeded()); 557 } 558 559 { 560 sql::Statement s( 561 db_->GetUniqueStatement( 562 "SELECT model_id, progress_marker, " 563 "transaction_version FROM models")); 564 565 while (s.Step()) { 566 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0), 567 s.ColumnByteLength(0)); 568 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) { 569 info->kernel_info.download_progress[type].ParseFromArray( 570 s.ColumnBlob(1), s.ColumnByteLength(1)); 571 info->kernel_info.transaction_version[type] = s.ColumnInt64(2); 572 } 573 } 574 if (!s.Succeeded()) 575 return false; 576 } 577 { 578 sql::Statement s( 579 db_->GetUniqueStatement( 580 "SELECT MAX(metahandle) FROM metas")); 581 if (!s.Step()) 582 return false; 583 584 info->max_metahandle = s.ColumnInt64(0); 585 586 // Verify only one row was returned. 587 DCHECK(!s.Step()); 588 DCHECK(s.Succeeded()); 589 } 590 return true; 591 } 592 593 /* static */ 594 bool DirectoryBackingStore::SaveEntryToDB(sql::Statement* save_statement, 595 const EntryKernel& entry) { 596 save_statement->Reset(true); 597 BindFields(entry, save_statement); 598 return save_statement->Run(); 599 } 600 601 bool DirectoryBackingStore::DropDeletedEntries() { 602 if (!db_->Execute("DELETE FROM metas " 603 "WHERE is_del > 0 " 604 "AND is_unsynced < 1 " 605 "AND is_unapplied_update < 1")) { 606 return false; 607 } 608 if (!db_->Execute("DELETE FROM metas " 609 "WHERE is_del > 0 " 610 "AND id LIKE 'c%'")) { 611 return false; 612 } 613 return true; 614 } 615 616 bool DirectoryBackingStore::SafeDropTable(const char* table_name) { 617 string query = "DROP TABLE IF EXISTS "; 618 query.append(table_name); 619 return db_->Execute(query.c_str()); 620 } 621 622 void DirectoryBackingStore::DropAllTables() { 623 SafeDropTable("metas"); 624 SafeDropTable("temp_metas"); 625 SafeDropTable("share_info"); 626 SafeDropTable("temp_share_info"); 627 SafeDropTable("share_version"); 628 SafeDropTable("extended_attributes"); 629 SafeDropTable("models"); 630 SafeDropTable("temp_models"); 631 needs_column_refresh_ = false; 632 } 633 634 // static 635 ModelType DirectoryBackingStore::ModelIdToModelTypeEnum( 636 const void* data, int size) { 637 sync_pb::EntitySpecifics specifics; 638 if (!specifics.ParseFromArray(data, size)) 639 return UNSPECIFIED; 640 return GetModelTypeFromSpecifics(specifics); 641 } 642 643 // static 644 string DirectoryBackingStore::ModelTypeEnumToModelId(ModelType model_type) { 645 sync_pb::EntitySpecifics specifics; 646 AddDefaultFieldValue(model_type, &specifics); 647 return specifics.SerializeAsString(); 648 } 649 650 // static 651 std::string DirectoryBackingStore::GenerateCacheGUID() { 652 // Generate a GUID with 128 bits of randomness. 653 const int kGuidBytes = 128 / 8; 654 std::string guid; 655 base::Base64Encode(base::RandBytesAsString(kGuidBytes), &guid); 656 return guid; 657 } 658 659 bool DirectoryBackingStore::MigrateToSpecifics( 660 const char* old_columns, 661 const char* specifics_column, 662 void (*handler_function)(sql::Statement* old_value_query, 663 int old_value_column, 664 sync_pb::EntitySpecifics* mutable_new_value)) { 665 std::string query_sql = base::StringPrintf( 666 "SELECT metahandle, %s, %s FROM metas", specifics_column, old_columns); 667 std::string update_sql = base::StringPrintf( 668 "UPDATE metas SET %s = ? WHERE metahandle = ?", specifics_column); 669 670 sql::Statement query(db_->GetUniqueStatement(query_sql.c_str())); 671 sql::Statement update(db_->GetUniqueStatement(update_sql.c_str())); 672 673 while (query.Step()) { 674 int64 metahandle = query.ColumnInt64(0); 675 std::string new_value_bytes; 676 query.ColumnBlobAsString(1, &new_value_bytes); 677 sync_pb::EntitySpecifics new_value; 678 new_value.ParseFromString(new_value_bytes); 679 handler_function(&query, 2, &new_value); 680 new_value.SerializeToString(&new_value_bytes); 681 682 update.BindBlob(0, new_value_bytes.data(), new_value_bytes.length()); 683 update.BindInt64(1, metahandle); 684 if (!update.Run()) 685 return false; 686 update.Reset(true); 687 } 688 return query.Succeeded(); 689 } 690 691 bool DirectoryBackingStore::SetVersion(int version) { 692 sql::Statement s(db_->GetCachedStatement( 693 SQL_FROM_HERE, "UPDATE share_version SET data = ?")); 694 s.BindInt(0, version); 695 696 return s.Run(); 697 } 698 699 int DirectoryBackingStore::GetVersion() { 700 if (!db_->DoesTableExist("share_version")) 701 return 0; 702 703 sql::Statement statement(db_->GetUniqueStatement( 704 "SELECT data FROM share_version")); 705 if (statement.Step()) { 706 return statement.ColumnInt(0); 707 } else { 708 return 0; 709 } 710 } 711 712 bool DirectoryBackingStore::MigrateVersion67To68() { 713 // This change simply removed three columns: 714 // string NAME 715 // string UNSANITIZED_NAME 716 // string SERVER_NAME 717 // No data migration is necessary, but we should do a column refresh. 718 SetVersion(68); 719 needs_column_refresh_ = true; 720 return true; 721 } 722 723 bool DirectoryBackingStore::MigrateVersion69To70() { 724 // Added "unique_client_tag", renamed "singleton_tag" to unique_server_tag 725 SetVersion(70); 726 if (!db_->Execute( 727 "ALTER TABLE metas ADD COLUMN unique_server_tag varchar")) 728 return false; 729 if (!db_->Execute( 730 "ALTER TABLE metas ADD COLUMN unique_client_tag varchar")) 731 return false; 732 needs_column_refresh_ = true; 733 734 if (!db_->Execute( 735 "UPDATE metas SET unique_server_tag = singleton_tag")) 736 return false; 737 738 return true; 739 } 740 741 namespace { 742 743 // Callback passed to MigrateToSpecifics for the v68->v69 migration. See 744 // MigrateVersion68To69(). 745 void EncodeBookmarkURLAndFavicon(sql::Statement* old_value_query, 746 int old_value_column, 747 sync_pb::EntitySpecifics* mutable_new_value) { 748 // Extract data from the column trio we expect. 749 bool old_is_bookmark_object = old_value_query->ColumnBool(old_value_column); 750 std::string old_url = old_value_query->ColumnString(old_value_column + 1); 751 std::string old_favicon; 752 old_value_query->ColumnBlobAsString(old_value_column + 2, &old_favicon); 753 bool old_is_dir = old_value_query->ColumnBool(old_value_column + 3); 754 755 if (old_is_bookmark_object) { 756 sync_pb::BookmarkSpecifics* bookmark_data = 757 mutable_new_value->mutable_bookmark(); 758 if (!old_is_dir) { 759 bookmark_data->set_url(old_url); 760 bookmark_data->set_favicon(old_favicon); 761 } 762 } 763 } 764 765 } // namespace 766 767 bool DirectoryBackingStore::MigrateVersion68To69() { 768 // In Version 68, there were columns on table 'metas': 769 // string BOOKMARK_URL 770 // string SERVER_BOOKMARK_URL 771 // blob BOOKMARK_FAVICON 772 // blob SERVER_BOOKMARK_FAVICON 773 // In version 69, these columns went away in favor of storing 774 // a serialized EntrySpecifics protobuf in the columns: 775 // protobuf blob SPECIFICS 776 // protobuf blob SERVER_SPECIFICS 777 // For bookmarks, EntrySpecifics is extended as per 778 // bookmark_specifics.proto. This migration converts bookmarks from the 779 // former scheme to the latter scheme. 780 781 // First, add the two new columns to the schema. 782 if (!db_->Execute( 783 "ALTER TABLE metas ADD COLUMN specifics blob")) 784 return false; 785 if (!db_->Execute( 786 "ALTER TABLE metas ADD COLUMN server_specifics blob")) 787 return false; 788 789 // Next, fold data from the old columns into the new protobuf columns. 790 if (!MigrateToSpecifics(("is_bookmark_object, bookmark_url, " 791 "bookmark_favicon, is_dir"), 792 "specifics", 793 &EncodeBookmarkURLAndFavicon)) { 794 return false; 795 } 796 if (!MigrateToSpecifics(("server_is_bookmark_object, " 797 "server_bookmark_url, " 798 "server_bookmark_favicon, " 799 "server_is_dir"), 800 "server_specifics", 801 &EncodeBookmarkURLAndFavicon)) { 802 return false; 803 } 804 805 // Lastly, fix up the "Google Chrome" folder, which is of the TOP_LEVEL_FOLDER 806 // ModelType: it shouldn't have BookmarkSpecifics. 807 if (!db_->Execute( 808 "UPDATE metas SET specifics = NULL, server_specifics = NULL WHERE " 809 "singleton_tag IN ('google_chrome')")) 810 return false; 811 812 SetVersion(69); 813 needs_column_refresh_ = true; // Trigger deletion of old columns. 814 return true; 815 } 816 817 // Version 71, the columns 'initial_sync_ended' and 'last_sync_timestamp' 818 // were removed from the share_info table. They were replaced by 819 // the 'models' table, which has these values on a per-datatype basis. 820 bool DirectoryBackingStore::MigrateVersion70To71() { 821 if (!CreateV71ModelsTable()) 822 return false; 823 824 // Move data from the old share_info columns to the new models table. 825 { 826 sql::Statement fetch(db_->GetUniqueStatement( 827 "SELECT last_sync_timestamp, initial_sync_ended FROM share_info")); 828 if (!fetch.Step()) 829 return false; 830 831 int64 last_sync_timestamp = fetch.ColumnInt64(0); 832 bool initial_sync_ended = fetch.ColumnBool(1); 833 834 // Verify there were no additional rows returned. 835 DCHECK(!fetch.Step()); 836 DCHECK(fetch.Succeeded()); 837 838 sql::Statement update(db_->GetUniqueStatement( 839 "INSERT INTO models (model_id, " 840 "last_download_timestamp, initial_sync_ended) VALUES (?, ?, ?)")); 841 string bookmark_model_id = ModelTypeEnumToModelId(BOOKMARKS); 842 update.BindBlob(0, bookmark_model_id.data(), bookmark_model_id.size()); 843 update.BindInt64(1, last_sync_timestamp); 844 update.BindBool(2, initial_sync_ended); 845 846 if (!update.Run()) 847 return false; 848 } 849 850 // Drop the columns from the old share_info table via a temp table. 851 const bool kCreateAsTempShareInfo = true; 852 853 if (!CreateShareInfoTableVersion71(kCreateAsTempShareInfo)) 854 return false; 855 if (!db_->Execute( 856 "INSERT INTO temp_share_info (id, name, store_birthday, " 857 "db_create_version, db_create_time, next_id, cache_guid) " 858 "SELECT id, name, store_birthday, db_create_version, " 859 "db_create_time, next_id, cache_guid FROM share_info")) 860 return false; 861 SafeDropTable("share_info"); 862 if (!db_->Execute( 863 "ALTER TABLE temp_share_info RENAME TO share_info")) 864 return false; 865 SetVersion(71); 866 return true; 867 } 868 869 bool DirectoryBackingStore::MigrateVersion71To72() { 870 // Version 72 removed a table 'extended_attributes', whose 871 // contents didn't matter. 872 SafeDropTable("extended_attributes"); 873 SetVersion(72); 874 return true; 875 } 876 877 bool DirectoryBackingStore::MigrateVersion72To73() { 878 // Version 73 added one column to the table 'share_info': notification_state 879 if (!db_->Execute( 880 "ALTER TABLE share_info ADD COLUMN notification_state BLOB")) 881 return false; 882 SetVersion(73); 883 return true; 884 } 885 886 bool DirectoryBackingStore::MigrateVersion73To74() { 887 // Version 74 added the following columns to the table 'share_info': 888 // autofill_migration_state 889 // bookmarks_added_during_autofill_migration 890 // autofill_migration_time 891 // autofill_entries_added_during_migration 892 // autofill_profiles_added_during_migration 893 894 if (!db_->Execute( 895 "ALTER TABLE share_info ADD COLUMN " 896 "autofill_migration_state INT default 0")) 897 return false; 898 899 if (!db_->Execute( 900 "ALTER TABLE share_info ADD COLUMN " 901 "bookmarks_added_during_autofill_migration " 902 "INT default 0")) 903 return false; 904 905 if (!db_->Execute( 906 "ALTER TABLE share_info ADD COLUMN autofill_migration_time " 907 "INT default 0")) 908 return false; 909 910 if (!db_->Execute( 911 "ALTER TABLE share_info ADD COLUMN " 912 "autofill_entries_added_during_migration " 913 "INT default 0")) 914 return false; 915 916 if (!db_->Execute( 917 "ALTER TABLE share_info ADD COLUMN " 918 "autofill_profiles_added_during_migration " 919 "INT default 0")) 920 return false; 921 922 SetVersion(74); 923 return true; 924 } 925 926 bool DirectoryBackingStore::MigrateVersion74To75() { 927 // In version 74, there was a table 'models': 928 // blob model_id (entity specifics, primary key) 929 // int last_download_timestamp 930 // boolean initial_sync_ended 931 // In version 75, we deprecated the integer-valued last_download_timestamp, 932 // using insted a protobuf-valued progress_marker field: 933 // blob progress_marker 934 // The progress_marker values are initialized from the value of 935 // last_download_timestamp, thereby preserving the download state. 936 937 // Move aside the old table and create a new empty one at the current schema. 938 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models")) 939 return false; 940 if (!CreateV75ModelsTable()) 941 return false; 942 943 sql::Statement query(db_->GetUniqueStatement( 944 "SELECT model_id, last_download_timestamp, initial_sync_ended " 945 "FROM temp_models")); 946 947 sql::Statement update(db_->GetUniqueStatement( 948 "INSERT INTO models (model_id, " 949 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)")); 950 951 while (query.Step()) { 952 ModelType type = ModelIdToModelTypeEnum(query.ColumnBlob(0), 953 query.ColumnByteLength(0)); 954 if (type != UNSPECIFIED) { 955 // Set the |timestamp_token_for_migration| on a new 956 // DataTypeProgressMarker, using the old value of last_download_timestamp. 957 // The server will turn this into a real token on our behalf the next 958 // time we check for updates. 959 sync_pb::DataTypeProgressMarker progress_marker; 960 progress_marker.set_data_type_id( 961 GetSpecificsFieldNumberFromModelType(type)); 962 progress_marker.set_timestamp_token_for_migration(query.ColumnInt64(1)); 963 std::string progress_blob; 964 progress_marker.SerializeToString(&progress_blob); 965 966 update.BindBlob(0, query.ColumnBlob(0), query.ColumnByteLength(0)); 967 update.BindBlob(1, progress_blob.data(), progress_blob.length()); 968 update.BindBool(2, query.ColumnBool(2)); 969 if (!update.Run()) 970 return false; 971 update.Reset(true); 972 } 973 } 974 if (!query.Succeeded()) 975 return false; 976 977 // Drop the old table. 978 SafeDropTable("temp_models"); 979 980 SetVersion(75); 981 return true; 982 } 983 984 bool DirectoryBackingStore::MigrateVersion75To76() { 985 // This change removed five columns: 986 // autofill_migration_state 987 // bookmarks_added_during_autofill_migration 988 // autofill_migration_time 989 // autofill_entries_added_during_migration 990 // autofill_profiles_added_during_migration 991 // No data migration is necessary, but we should do a column refresh. 992 SetVersion(76); 993 needs_column_refresh_ = true; 994 return true; 995 } 996 997 bool DirectoryBackingStore::MigrateVersion76To77() { 998 // This change changes the format of stored timestamps to ms since 999 // the Unix epoch. 1000 #if defined(OS_WIN) 1001 // On Windows, we used to store timestamps in FILETIME format (100s of 1002 // ns since Jan 1, 1601). Magic numbers taken from 1003 // http://stackoverflow.com/questions/5398557/ 1004 // java-library-for-dealing-with-win32-filetime 1005 // . 1006 #define TO_UNIX_TIME_MS(x) #x " = " #x " / 10000 - 11644473600000" 1007 #else 1008 // On other platforms, we used to store timestamps in time_t format (s 1009 // since the Unix epoch). 1010 #define TO_UNIX_TIME_MS(x) #x " = " #x " * 1000" 1011 #endif 1012 sql::Statement update_timestamps(db_->GetUniqueStatement( 1013 "UPDATE metas SET " 1014 TO_UNIX_TIME_MS(mtime) ", " 1015 TO_UNIX_TIME_MS(server_mtime) ", " 1016 TO_UNIX_TIME_MS(ctime) ", " 1017 TO_UNIX_TIME_MS(server_ctime))); 1018 #undef TO_UNIX_TIME_MS 1019 if (!update_timestamps.Run()) 1020 return false; 1021 SetVersion(77); 1022 return true; 1023 } 1024 1025 bool DirectoryBackingStore::MigrateVersion77To78() { 1026 // Version 78 added one column to table 'metas': base_server_specifics. 1027 if (!db_->Execute( 1028 "ALTER TABLE metas ADD COLUMN base_server_specifics BLOB")) { 1029 return false; 1030 } 1031 SetVersion(78); 1032 return true; 1033 } 1034 1035 bool DirectoryBackingStore::MigrateVersion78To79() { 1036 // Some users are stuck with a DB that causes them to reuse existing IDs. We 1037 // perform this one-time fixup on all users to help the few that are stuck. 1038 // See crbug.com/142987 for details. 1039 if (!db_->Execute( 1040 "UPDATE share_info SET next_id = next_id - 65536")) { 1041 return false; 1042 } 1043 SetVersion(79); 1044 return true; 1045 } 1046 1047 bool DirectoryBackingStore::MigrateVersion79To80() { 1048 if (!db_->Execute( 1049 "ALTER TABLE share_info ADD COLUMN bag_of_chips BLOB")) 1050 return false; 1051 sql::Statement update(db_->GetUniqueStatement( 1052 "UPDATE share_info SET bag_of_chips = ?")); 1053 // An empty message is serialized to an empty string. 1054 update.BindBlob(0, NULL, 0); 1055 if (!update.Run()) 1056 return false; 1057 SetVersion(80); 1058 return true; 1059 } 1060 1061 bool DirectoryBackingStore::MigrateVersion80To81() { 1062 if(!db_->Execute( 1063 "ALTER TABLE metas ADD COLUMN server_ordinal_in_parent BLOB")) 1064 return false; 1065 1066 sql::Statement get_positions(db_->GetUniqueStatement( 1067 "SELECT metahandle, server_position_in_parent FROM metas")); 1068 1069 sql::Statement put_ordinals(db_->GetUniqueStatement( 1070 "UPDATE metas SET server_ordinal_in_parent = ?" 1071 "WHERE metahandle = ?")); 1072 1073 while(get_positions.Step()) { 1074 int64 metahandle = get_positions.ColumnInt64(0); 1075 int64 position = get_positions.ColumnInt64(1); 1076 1077 const std::string& ordinal = Int64ToNodeOrdinal(position).ToInternalValue(); 1078 put_ordinals.BindBlob(0, ordinal.data(), ordinal.length()); 1079 put_ordinals.BindInt64(1, metahandle); 1080 1081 if(!put_ordinals.Run()) 1082 return false; 1083 put_ordinals.Reset(true); 1084 } 1085 1086 SetVersion(81); 1087 needs_column_refresh_ = true; 1088 return true; 1089 } 1090 1091 bool DirectoryBackingStore::MigrateVersion81To82() { 1092 if (!db_->Execute( 1093 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0")) 1094 return false; 1095 sql::Statement update(db_->GetUniqueStatement( 1096 "UPDATE models SET transaction_version = 0")); 1097 if (!update.Run()) 1098 return false; 1099 SetVersion(82); 1100 return true; 1101 } 1102 1103 bool DirectoryBackingStore::MigrateVersion82To83() { 1104 // Version 83 added transaction_version on sync node. 1105 if (!db_->Execute( 1106 "ALTER TABLE metas ADD COLUMN transaction_version BIGINT default 0")) 1107 return false; 1108 sql::Statement update(db_->GetUniqueStatement( 1109 "UPDATE metas SET transaction_version = 0")); 1110 if (!update.Run()) 1111 return false; 1112 SetVersion(83); 1113 return true; 1114 } 1115 1116 bool DirectoryBackingStore::MigrateVersion83To84() { 1117 // Version 84 added deleted_metas table to store deleted metas until we know 1118 // for sure that the deletions are persisted in native models. 1119 string query = "CREATE TABLE deleted_metas "; 1120 query.append(ComposeCreateTableColumnSpecs()); 1121 if (!db_->Execute(query.c_str())) 1122 return false; 1123 SetVersion(84); 1124 return true; 1125 } 1126 1127 bool DirectoryBackingStore::MigrateVersion84To85() { 1128 // Version 85 removes the initial_sync_ended flag. 1129 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models")) 1130 return false; 1131 if (!CreateModelsTable()) 1132 return false; 1133 if (!db_->Execute("INSERT INTO models SELECT " 1134 "model_id, progress_marker, transaction_version " 1135 "FROM temp_models")) { 1136 return false; 1137 } 1138 SafeDropTable("temp_models"); 1139 1140 SetVersion(85); 1141 return true; 1142 } 1143 1144 bool DirectoryBackingStore::MigrateVersion85To86() { 1145 // Version 86 removes both server ordinals and local NEXT_ID, PREV_ID and 1146 // SERVER_{POSITION,ORDINAL}_IN_PARENT and replaces them with UNIQUE_POSITION 1147 // and SERVER_UNIQUE_POSITION. 1148 if (!db_->Execute("ALTER TABLE metas ADD COLUMN " 1149 "server_unique_position BLOB")) { 1150 return false; 1151 } 1152 if (!db_->Execute("ALTER TABLE metas ADD COLUMN " 1153 "unique_position BLOB")) { 1154 return false; 1155 } 1156 if (!db_->Execute("ALTER TABLE metas ADD COLUMN " 1157 "unique_bookmark_tag VARCHAR")) { 1158 return false; 1159 } 1160 1161 // Fetch the cache_guid from the DB, because we don't otherwise have access to 1162 // it from here. 1163 sql::Statement get_cache_guid(db_->GetUniqueStatement( 1164 "SELECT cache_guid FROM share_info")); 1165 if (!get_cache_guid.Step()) { 1166 return false; 1167 } 1168 std::string cache_guid = get_cache_guid.ColumnString(0); 1169 DCHECK(!get_cache_guid.Step()); 1170 DCHECK(get_cache_guid.Succeeded()); 1171 1172 sql::Statement get(db_->GetUniqueStatement( 1173 "SELECT " 1174 " metahandle, " 1175 " id, " 1176 " specifics, " 1177 " is_dir, " 1178 " unique_server_tag, " 1179 " server_ordinal_in_parent " 1180 "FROM metas")); 1181 1182 // Note that we set both the local and server position based on the server 1183 // position. We wll lose any unsynced local position changes. Unfortunately, 1184 // there's nothing we can do to avoid that. The NEXT_ID / PREV_ID values 1185 // can't be translated into a UNIQUE_POSTION in a reliable way. 1186 sql::Statement put(db_->GetCachedStatement( 1187 SQL_FROM_HERE, 1188 "UPDATE metas SET" 1189 " server_unique_position = ?," 1190 " unique_position = ?," 1191 " unique_bookmark_tag = ?" 1192 "WHERE metahandle = ?")); 1193 1194 while (get.Step()) { 1195 int64 metahandle = get.ColumnInt64(0); 1196 1197 std::string id_string; 1198 get.ColumnBlobAsString(1, &id_string); 1199 1200 sync_pb::EntitySpecifics specifics; 1201 specifics.ParseFromArray( 1202 get.ColumnBlob(2), get.ColumnByteLength(2)); 1203 1204 bool is_dir = get.ColumnBool(3); 1205 1206 std::string server_unique_tag = get.ColumnString(4); 1207 1208 std::string ordinal_string; 1209 get.ColumnBlobAsString(5, &ordinal_string); 1210 NodeOrdinal ordinal(ordinal_string); 1211 1212 1213 std::string unique_bookmark_tag; 1214 1215 // We only maintain positions for bookmarks that are not server-defined 1216 // top-level folders. 1217 UniquePosition position; 1218 if (GetModelTypeFromSpecifics(specifics) == BOOKMARKS 1219 && !(is_dir && !server_unique_tag.empty())) { 1220 if (id_string.at(0) == 'c') { 1221 // We found an uncommitted item. This is rare, but fortunate. This 1222 // means we can set the bookmark tag according to the originator client 1223 // item ID and originator cache guid, because (unlike the other case) we 1224 // know that this client is the originator. 1225 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash( 1226 cache_guid, 1227 id_string.substr(1)); 1228 } else { 1229 // If we've already committed the item, then we don't know who the 1230 // originator was. We do not have access to the originator client item 1231 // ID and originator cache guid at this point. 1232 // 1233 // We will base our hash entirely on the server ID instead. This is 1234 // incorrect, but at least all clients that undergo this migration step 1235 // will be incorrect in the same way. 1236 // 1237 // To get everyone back into a synced state, we will update the bookmark 1238 // tag according to the originator_cache_guid and originator_item_id 1239 // when we see updates for this item. That should ensure that commonly 1240 // modified items will end up with the proper tag values eventually. 1241 unique_bookmark_tag = syncable::GenerateSyncableBookmarkHash( 1242 std::string(), // cache_guid left intentionally blank. 1243 id_string.substr(1)); 1244 } 1245 1246 int64 int_position = NodeOrdinalToInt64(ordinal); 1247 position = UniquePosition::FromInt64(int_position, unique_bookmark_tag); 1248 } else { 1249 // Leave bookmark_tag and position at their default (invalid) values. 1250 } 1251 1252 std::string position_blob; 1253 position.SerializeToString(&position_blob); 1254 put.BindBlob(0, position_blob.data(), position_blob.length()); 1255 put.BindBlob(1, position_blob.data(), position_blob.length()); 1256 put.BindBlob(2, unique_bookmark_tag.data(), unique_bookmark_tag.length()); 1257 put.BindInt64(3, metahandle); 1258 1259 if (!put.Run()) 1260 return false; 1261 put.Reset(true); 1262 } 1263 1264 SetVersion(86); 1265 needs_column_refresh_ = true; 1266 return true; 1267 } 1268 1269 bool DirectoryBackingStore::CreateTables() { 1270 DVLOG(1) << "First run, creating tables"; 1271 // Create two little tables share_version and share_info 1272 if (!db_->Execute( 1273 "CREATE TABLE share_version (" 1274 "id VARCHAR(128) primary key, data INT)")) { 1275 return false; 1276 } 1277 1278 { 1279 sql::Statement s(db_->GetUniqueStatement( 1280 "INSERT INTO share_version VALUES(?, ?)")); 1281 s.BindString(0, dir_name_); 1282 s.BindInt(1, kCurrentDBVersion); 1283 1284 if (!s.Run()) 1285 return false; 1286 } 1287 1288 const bool kCreateAsTempShareInfo = false; 1289 if (!CreateShareInfoTable(kCreateAsTempShareInfo)) { 1290 return false; 1291 } 1292 1293 { 1294 sql::Statement s(db_->GetUniqueStatement( 1295 "INSERT INTO share_info VALUES" 1296 "(?, " // id 1297 "?, " // name 1298 "?, " // store_birthday 1299 "?, " // db_create_version 1300 "?, " // db_create_time 1301 "-2, " // next_id 1302 "?, " // cache_guid 1303 // TODO(rlarocque, 124140): Remove notification_state field. 1304 "?, " // notification_state 1305 "?);")); // bag_of_chips 1306 s.BindString(0, dir_name_); // id 1307 s.BindString(1, dir_name_); // name 1308 s.BindString(2, std::string()); // store_birthday 1309 // TODO(akalin): Remove this unused db_create_version field. (Or 1310 // actually use it for something.) http://crbug.com/118356 1311 s.BindString(3, "Unknown"); // db_create_version 1312 s.BindInt(4, static_cast<int32>(time(0))); // db_create_time 1313 s.BindString(5, GenerateCacheGUID()); // cache_guid 1314 // TODO(rlarocque, 124140): Remove this unused notification-state field. 1315 s.BindBlob(6, NULL, 0); // notification_state 1316 s.BindBlob(7, NULL, 0); // bag_of_chips 1317 if (!s.Run()) 1318 return false; 1319 } 1320 1321 if (!CreateModelsTable()) 1322 return false; 1323 1324 // Create the big metas table. 1325 if (!CreateMetasTable(false)) 1326 return false; 1327 1328 { 1329 // Insert the entry for the root into the metas table. 1330 const int64 now = TimeToProtoTime(base::Time::Now()); 1331 sql::Statement s(db_->GetUniqueStatement( 1332 "INSERT INTO metas " 1333 "( id, metahandle, is_dir, ctime, mtime ) " 1334 "VALUES ( \"r\", 1, 1, ?, ? )")); 1335 s.BindInt64(0, now); 1336 s.BindInt64(1, now); 1337 1338 if (!s.Run()) 1339 return false; 1340 } 1341 1342 return true; 1343 } 1344 1345 bool DirectoryBackingStore::CreateMetasTable(bool is_temporary) { 1346 string query = "CREATE TABLE "; 1347 query.append(is_temporary ? "temp_metas" : "metas"); 1348 query.append(ComposeCreateTableColumnSpecs()); 1349 if (!db_->Execute(query.c_str())) 1350 return false; 1351 1352 // Create a deleted_metas table to save copies of deleted metas until the 1353 // deletions are persisted. For simplicity, don't try to migrate existing 1354 // data because it's rarely used. 1355 SafeDropTable("deleted_metas"); 1356 query = "CREATE TABLE deleted_metas "; 1357 query.append(ComposeCreateTableColumnSpecs()); 1358 return db_->Execute(query.c_str()); 1359 } 1360 1361 bool DirectoryBackingStore::CreateV71ModelsTable() { 1362 // This is an old schema for the Models table, used from versions 71 to 74. 1363 return db_->Execute( 1364 "CREATE TABLE models (" 1365 "model_id BLOB primary key, " 1366 "last_download_timestamp INT, " 1367 // Gets set if the syncer ever gets updates from the 1368 // server and the server returns 0. Lets us detect the 1369 // end of the initial sync. 1370 "initial_sync_ended BOOLEAN default 0)"); 1371 } 1372 1373 bool DirectoryBackingStore::CreateV75ModelsTable() { 1374 // This is an old schema for the Models table, used from versions 75 to 80. 1375 return db_->Execute( 1376 "CREATE TABLE models (" 1377 "model_id BLOB primary key, " 1378 "progress_marker BLOB, " 1379 // Gets set if the syncer ever gets updates from the 1380 // server and the server returns 0. Lets us detect the 1381 // end of the initial sync. 1382 "initial_sync_ended BOOLEAN default 0)"); 1383 } 1384 1385 bool DirectoryBackingStore::CreateModelsTable() { 1386 // This is the current schema for the Models table, from version 81 1387 // onward. If you change the schema, you'll probably want to double-check 1388 // the use of this function in the v84-v85 migration. 1389 return db_->Execute( 1390 "CREATE TABLE models (" 1391 "model_id BLOB primary key, " 1392 "progress_marker BLOB, " 1393 // Gets set if the syncer ever gets updates from the 1394 // server and the server returns 0. Lets us detect the 1395 // end of the initial sync. 1396 "transaction_version BIGINT default 0)"); 1397 } 1398 1399 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) { 1400 const char* name = is_temporary ? "temp_share_info" : "share_info"; 1401 string query = "CREATE TABLE "; 1402 query.append(name); 1403 // This is the current schema for the ShareInfo table, from version 76 1404 // onward. 1405 query.append(" (" 1406 "id TEXT primary key, " 1407 "name TEXT, " 1408 "store_birthday TEXT, " 1409 "db_create_version TEXT, " 1410 "db_create_time INT, " 1411 "next_id INT default -2, " 1412 "cache_guid TEXT, " 1413 // TODO(rlarocque, 124140): Remove notification_state field. 1414 "notification_state BLOB, " 1415 "bag_of_chips BLOB" 1416 ")"); 1417 return db_->Execute(query.c_str()); 1418 } 1419 1420 bool DirectoryBackingStore::CreateShareInfoTableVersion71( 1421 bool is_temporary) { 1422 const char* name = is_temporary ? "temp_share_info" : "share_info"; 1423 string query = "CREATE TABLE "; 1424 query.append(name); 1425 // This is the schema for the ShareInfo table used from versions 71 to 72. 1426 query.append(" (" 1427 "id TEXT primary key, " 1428 "name TEXT, " 1429 "store_birthday TEXT, " 1430 "db_create_version TEXT, " 1431 "db_create_time INT, " 1432 "next_id INT default -2, " 1433 "cache_guid TEXT )"); 1434 return db_->Execute(query.c_str()); 1435 } 1436 1437 // This function checks to see if the given list of Metahandles has any nodes 1438 // whose PARENT_ID values refer to ID values that do not actually exist. 1439 // Returns true on success. 1440 bool DirectoryBackingStore::VerifyReferenceIntegrity( 1441 const Directory::MetahandlesMap* handles_map) { 1442 TRACE_EVENT0("sync", "SyncDatabaseIntegrityCheck"); 1443 using namespace syncable; 1444 typedef base::hash_set<std::string> IdsSet; 1445 1446 IdsSet ids_set; 1447 bool is_ok = true; 1448 1449 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin(); 1450 it != handles_map->end(); ++it) { 1451 EntryKernel* entry = it->second; 1452 bool is_duplicate_id = !(ids_set.insert(entry->ref(ID).value()).second); 1453 is_ok = is_ok && !is_duplicate_id; 1454 } 1455 1456 IdsSet::iterator end = ids_set.end(); 1457 for (Directory::MetahandlesMap::const_iterator it = handles_map->begin(); 1458 it != handles_map->end(); ++it) { 1459 EntryKernel* entry = it->second; 1460 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end); 1461 if (!parent_exists) { 1462 return false; 1463 } 1464 } 1465 return is_ok; 1466 } 1467 1468 void DirectoryBackingStore::PrepareSaveEntryStatement( 1469 EntryTable table, sql::Statement* save_statement) { 1470 if (save_statement->is_valid()) 1471 return; 1472 1473 string query; 1474 query.reserve(kUpdateStatementBufferSize); 1475 switch (table) { 1476 case METAS_TABLE: 1477 query.append("INSERT OR REPLACE INTO metas "); 1478 break; 1479 case DELETE_JOURNAL_TABLE: 1480 query.append("INSERT OR REPLACE INTO deleted_metas "); 1481 break; 1482 } 1483 1484 string values; 1485 values.reserve(kUpdateStatementBufferSize); 1486 values.append(" VALUES "); 1487 const char* separator = "( "; 1488 int i = 0; 1489 for (i = BEGIN_FIELDS; i < FIELD_COUNT; ++i) { 1490 query.append(separator); 1491 values.append(separator); 1492 separator = ", "; 1493 query.append(ColumnName(i)); 1494 values.append("?"); 1495 } 1496 query.append(" ) "); 1497 values.append(" )"); 1498 query.append(values); 1499 save_statement->Assign(db_->GetUniqueStatement( 1500 base::StringPrintf(query.c_str(), "metas").c_str())); 1501 } 1502 1503 } // namespace syncable 1504 } // namespace syncer 1505