| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "sync/syncable/directory_backing_store.h" | 5 #include "sync/syncable/directory_backing_store.h" |
| 6 | 6 |
| 7 #include "build/build_config.h" | 7 #include "build/build_config.h" |
| 8 | 8 |
| 9 #include <limits> | 9 #include <limits> |
| 10 | 10 |
| (...skipping 22 matching lines...) Expand all Loading... |
| 33 | 33 |
| 34 namespace syncer { | 34 namespace syncer { |
| 35 namespace syncable { | 35 namespace syncable { |
| 36 | 36 |
| 37 // This just has to be big enough to hold an UPDATE or INSERT statement that | 37 // This just has to be big enough to hold an UPDATE or INSERT statement that |
| 38 // modifies all the columns in the entry table. | 38 // modifies all the columns in the entry table. |
| 39 static const string::size_type kUpdateStatementBufferSize = 2048; | 39 static const string::size_type kUpdateStatementBufferSize = 2048; |
| 40 | 40 |
| 41 // Increment this version whenever updating DB tables. | 41 // Increment this version whenever updating DB tables. |
| 42 extern const int32 kCurrentDBVersion; // Global visibility for our unittest. | 42 extern const int32 kCurrentDBVersion; // Global visibility for our unittest. |
| 43 const int32 kCurrentDBVersion = 84; | 43 const int32 kCurrentDBVersion = 85; |
| 44 | 44 |
| 45 // Iterate over the fields of |entry| and bind each to |statement| for | 45 // Iterate over the fields of |entry| and bind each to |statement| for |
| 46 // updating. Returns the number of args bound. | 46 // updating. Returns the number of args bound. |
| 47 void BindFields(const EntryKernel& entry, | 47 void BindFields(const EntryKernel& entry, |
| 48 sql::Statement* statement) { | 48 sql::Statement* statement) { |
| 49 int index = 0; | 49 int index = 0; |
| 50 int i = 0; | 50 int i = 0; |
| 51 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) { | 51 for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) { |
| 52 statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i))); | 52 statement->BindInt64(index++, entry.ref(static_cast<Int64Field>(i))); |
| 53 } | 53 } |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 190 } | 190 } |
| 191 | 191 |
| 192 bool DirectoryBackingStore::SaveChanges( | 192 bool DirectoryBackingStore::SaveChanges( |
| 193 const Directory::SaveChangesSnapshot& snapshot) { | 193 const Directory::SaveChangesSnapshot& snapshot) { |
| 194 DCHECK(CalledOnValidThread()); | 194 DCHECK(CalledOnValidThread()); |
| 195 DCHECK(db_->is_open()); | 195 DCHECK(db_->is_open()); |
| 196 | 196 |
| 197 // Back out early if there is nothing to write. | 197 // Back out early if there is nothing to write. |
| 198 bool save_info = | 198 bool save_info = |
| 199 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status); | 199 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status); |
| 200 if (snapshot.dirty_metas.size() < 1 && !save_info) | 200 if (snapshot.dirty_metas.empty() |
| 201 && snapshot.metahandles_to_purge.empty() |
| 202 && !save_info) { |
| 201 return true; | 203 return true; |
| 204 } |
| 202 | 205 |
| 203 sql::Transaction transaction(db_.get()); | 206 sql::Transaction transaction(db_.get()); |
| 204 if (!transaction.Begin()) | 207 if (!transaction.Begin()) |
| 205 return false; | 208 return false; |
| 206 | 209 |
| 207 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); | 210 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); |
| 208 i != snapshot.dirty_metas.end(); ++i) { | 211 i != snapshot.dirty_metas.end(); ++i) { |
| 209 DCHECK(i->is_dirty()); | 212 DCHECK(i->is_dirty()); |
| 210 if (!SaveEntryToDB(*i)) | 213 if (!SaveEntryToDB(*i)) |
| 211 return false; | 214 return false; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 229 info.notification_state.size()); | 232 info.notification_state.size()); |
| 230 s1.BindBlob(3, info.bag_of_chips.data(), info.bag_of_chips.size()); | 233 s1.BindBlob(3, info.bag_of_chips.data(), info.bag_of_chips.size()); |
| 231 | 234 |
| 232 if (!s1.Run()) | 235 if (!s1.Run()) |
| 233 return false; | 236 return false; |
| 234 DCHECK_EQ(db_->GetLastChangeCount(), 1); | 237 DCHECK_EQ(db_->GetLastChangeCount(), 1); |
| 235 | 238 |
| 236 sql::Statement s2(db_->GetCachedStatement( | 239 sql::Statement s2(db_->GetCachedStatement( |
| 237 SQL_FROM_HERE, | 240 SQL_FROM_HERE, |
| 238 "INSERT OR REPLACE " | 241 "INSERT OR REPLACE " |
| 239 "INTO models (model_id, progress_marker, initial_sync_ended, " | 242 "INTO models (model_id, progress_marker, transaction_version) " |
| 240 " transaction_version) " | 243 "VALUES (?, ?, ?)")); |
| 241 "VALUES (?, ?, ?, ?)")); | |
| 242 | 244 |
| 243 for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { | 245 for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { |
| 244 // We persist not ModelType but rather a protobuf-derived ID. | 246 // We persist not ModelType but rather a protobuf-derived ID. |
| 245 string model_id = ModelTypeEnumToModelId(ModelTypeFromInt(i)); | 247 string model_id = ModelTypeEnumToModelId(ModelTypeFromInt(i)); |
| 246 string progress_marker; | 248 string progress_marker; |
| 247 info.download_progress[i].SerializeToString(&progress_marker); | 249 info.download_progress[i].SerializeToString(&progress_marker); |
| 248 s2.BindBlob(0, model_id.data(), model_id.length()); | 250 s2.BindBlob(0, model_id.data(), model_id.length()); |
| 249 s2.BindBlob(1, progress_marker.data(), progress_marker.length()); | 251 s2.BindBlob(1, progress_marker.data(), progress_marker.length()); |
| 250 s2.BindBool(2, info.initial_sync_ended.Has(ModelTypeFromInt(i))); | 252 s2.BindInt64(2, info.transaction_version[i]); |
| 251 s2.BindInt64(3, info.transaction_version[i]); | |
| 252 if (!s2.Run()) | 253 if (!s2.Run()) |
| 253 return false; | 254 return false; |
| 254 DCHECK_EQ(db_->GetLastChangeCount(), 1); | 255 DCHECK_EQ(db_->GetLastChangeCount(), 1); |
| 255 s2.Reset(true); | 256 s2.Reset(true); |
| 256 } | 257 } |
| 257 } | 258 } |
| 258 | 259 |
| 259 return transaction.Commit(); | 260 return transaction.Commit(); |
| 260 } | 261 } |
| 261 | 262 |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 if (MigrateVersion82To83()) | 364 if (MigrateVersion82To83()) |
| 364 version_on_disk = 83; | 365 version_on_disk = 83; |
| 365 } | 366 } |
| 366 | 367 |
| 367 // Version 84 migration added deleted_metas table. | 368 // Version 84 migration added deleted_metas table. |
| 368 if (version_on_disk == 83) { | 369 if (version_on_disk == 83) { |
| 369 if (MigrateVersion83To84()) | 370 if (MigrateVersion83To84()) |
| 370 version_on_disk = 84; | 371 version_on_disk = 84; |
| 371 } | 372 } |
| 372 | 373 |
| 374 // Version 85 migration removes the initial_sync_ended bits. |
| 375 if (version_on_disk == 84) { |
| 376 if (MigrateVersion84To85()) |
| 377 version_on_disk = 85; |
| 378 } |
| 379 |
| 373 // If one of the migrations requested it, drop columns that aren't current. | 380 // If one of the migrations requested it, drop columns that aren't current. |
| 374 // It's only safe to do this after migrating all the way to the current | 381 // It's only safe to do this after migrating all the way to the current |
| 375 // version. | 382 // version. |
| 376 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) { | 383 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) { |
| 377 if (!RefreshColumns()) | 384 if (!RefreshColumns()) |
| 378 version_on_disk = 0; | 385 version_on_disk = 0; |
| 379 } | 386 } |
| 380 | 387 |
| 381 // A final, alternative catch-all migration to simply re-sync everything. | 388 // A final, alternative catch-all migration to simply re-sync everything. |
| 382 // | |
| 383 // TODO(rlarocque): It's wrong to recreate the database here unless the higher | |
| 384 // layers were expecting us to do so. See crbug.com/103824. We must leave | |
| 385 // this code as is for now because this is the code that ends up creating the | |
| 386 // database in the first time sync case, where the higher layers are expecting | |
| 387 // us to create a fresh database. The solution to this should be to implement | |
| 388 // crbug.com/105018. | |
| 389 if (version_on_disk != kCurrentDBVersion) { | 389 if (version_on_disk != kCurrentDBVersion) { |
| 390 if (version_on_disk > kCurrentDBVersion) | 390 if (version_on_disk > kCurrentDBVersion) |
| 391 return false; | 391 return false; |
| 392 | 392 |
| 393 // Fallback (re-sync everything) migration path. | 393 // Fallback (re-sync everything) migration path. |
| 394 DVLOG(1) << "Old/null sync database, version " << version_on_disk; | 394 DVLOG(1) << "Old/null sync database, version " << version_on_disk; |
| 395 // Delete the existing database (if any), and create a fresh one. | 395 // Delete the existing database (if any), and create a fresh one. |
| 396 DropAllTables(); | 396 DropAllTables(); |
| 397 if (!CreateTables()) | 397 if (!CreateTables()) |
| 398 return false; | 398 return false; |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 501 s.ColumnBlobAsString(4, &(info->kernel_info.bag_of_chips)); | 501 s.ColumnBlobAsString(4, &(info->kernel_info.bag_of_chips)); |
| 502 | 502 |
| 503 // Verify there was only one row returned. | 503 // Verify there was only one row returned. |
| 504 DCHECK(!s.Step()); | 504 DCHECK(!s.Step()); |
| 505 DCHECK(s.Succeeded()); | 505 DCHECK(s.Succeeded()); |
| 506 } | 506 } |
| 507 | 507 |
| 508 { | 508 { |
| 509 sql::Statement s( | 509 sql::Statement s( |
| 510 db_->GetUniqueStatement( | 510 db_->GetUniqueStatement( |
| 511 "SELECT model_id, progress_marker, initial_sync_ended, " | 511 "SELECT model_id, progress_marker, " |
| 512 "transaction_version FROM models")); | 512 "transaction_version FROM models")); |
| 513 | 513 |
| 514 while (s.Step()) { | 514 while (s.Step()) { |
| 515 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0), | 515 ModelType type = ModelIdToModelTypeEnum(s.ColumnBlob(0), |
| 516 s.ColumnByteLength(0)); | 516 s.ColumnByteLength(0)); |
| 517 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) { | 517 if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER) { |
| 518 info->kernel_info.download_progress[type].ParseFromArray( | 518 info->kernel_info.download_progress[type].ParseFromArray( |
| 519 s.ColumnBlob(1), s.ColumnByteLength(1)); | 519 s.ColumnBlob(1), s.ColumnByteLength(1)); |
| 520 if (s.ColumnBool(2)) | 520 info->kernel_info.transaction_version[type] = s.ColumnInt64(2); |
| 521 info->kernel_info.initial_sync_ended.Put(type); | |
| 522 info->kernel_info.transaction_version[type] = s.ColumnInt64(3); | |
| 523 } | 521 } |
| 524 } | 522 } |
| 525 if (!s.Succeeded()) | 523 if (!s.Succeeded()) |
| 526 return false; | 524 return false; |
| 527 } | 525 } |
| 528 { | 526 { |
| 529 sql::Statement s( | 527 sql::Statement s( |
| 530 db_->GetUniqueStatement( | 528 db_->GetUniqueStatement( |
| 531 "SELECT MAX(metahandle) FROM metas")); | 529 "SELECT MAX(metahandle) FROM metas")); |
| 532 if (!s.Step()) | 530 if (!s.Step()) |
| (...skipping 374 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 907 // boolean initial_sync_ended | 905 // boolean initial_sync_ended |
| 908 // In version 75, we deprecated the integer-valued last_download_timestamp, | 906 // In version 75, we deprecated the integer-valued last_download_timestamp, |
| 909 // using insted a protobuf-valued progress_marker field: | 907 // using insted a protobuf-valued progress_marker field: |
| 910 // blob progress_marker | 908 // blob progress_marker |
| 911 // The progress_marker values are initialized from the value of | 909 // The progress_marker values are initialized from the value of |
| 912 // last_download_timestamp, thereby preserving the download state. | 910 // last_download_timestamp, thereby preserving the download state. |
| 913 | 911 |
| 914 // Move aside the old table and create a new empty one at the current schema. | 912 // Move aside the old table and create a new empty one at the current schema. |
| 915 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models")) | 913 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models")) |
| 916 return false; | 914 return false; |
| 917 if (!CreateModelsTable()) | 915 if (!CreateV75ModelsTable()) |
| 918 return false; | 916 return false; |
| 919 | 917 |
| 920 sql::Statement query(db_->GetUniqueStatement( | 918 sql::Statement query(db_->GetUniqueStatement( |
| 921 "SELECT model_id, last_download_timestamp, initial_sync_ended " | 919 "SELECT model_id, last_download_timestamp, initial_sync_ended " |
| 922 "FROM temp_models")); | 920 "FROM temp_models")); |
| 923 | 921 |
| 924 sql::Statement update(db_->GetUniqueStatement( | 922 sql::Statement update(db_->GetUniqueStatement( |
| 925 "INSERT INTO models (model_id, " | 923 "INSERT INTO models (model_id, " |
| 926 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)")); | 924 "progress_marker, initial_sync_ended) VALUES (?, ?, ?)")); |
| 927 | 925 |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1058 return false; | 1056 return false; |
| 1059 put_ordinals.Reset(true); | 1057 put_ordinals.Reset(true); |
| 1060 } | 1058 } |
| 1061 | 1059 |
| 1062 SetVersion(81); | 1060 SetVersion(81); |
| 1063 needs_column_refresh_ = true; | 1061 needs_column_refresh_ = true; |
| 1064 return true; | 1062 return true; |
| 1065 } | 1063 } |
| 1066 | 1064 |
| 1067 bool DirectoryBackingStore::MigrateVersion81To82() { | 1065 bool DirectoryBackingStore::MigrateVersion81To82() { |
| 1068 // Version 82 added transaction_version to kernel info. But if user is | |
| 1069 // migrating from 74 or before, 74->75 migration would recreate models table | |
| 1070 // that already has transaction_version column. | |
| 1071 if (db_->DoesColumnExist("models", "transaction_version")) { | |
| 1072 SetVersion(82); | |
| 1073 return true; | |
| 1074 } | |
| 1075 | |
| 1076 if (!db_->Execute( | 1066 if (!db_->Execute( |
| 1077 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0")) | 1067 "ALTER TABLE models ADD COLUMN transaction_version BIGINT default 0")) |
| 1078 return false; | 1068 return false; |
| 1079 sql::Statement update(db_->GetUniqueStatement( | 1069 sql::Statement update(db_->GetUniqueStatement( |
| 1080 "UPDATE models SET transaction_version = 0")); | 1070 "UPDATE models SET transaction_version = 0")); |
| 1081 if (!update.Run()) | 1071 if (!update.Run()) |
| 1082 return false; | 1072 return false; |
| 1083 SetVersion(82); | 1073 SetVersion(82); |
| 1084 return true; | 1074 return true; |
| 1085 } | 1075 } |
| (...skipping 11 matching lines...) Expand all Loading... |
| 1097 return true; | 1087 return true; |
| 1098 } | 1088 } |
| 1099 | 1089 |
| 1100 bool DirectoryBackingStore::MigrateVersion83To84() { | 1090 bool DirectoryBackingStore::MigrateVersion83To84() { |
| 1101 // Version 84 added deleted_metas table to store deleted metas until we know | 1091 // Version 84 added deleted_metas table to store deleted metas until we know |
| 1102 // for sure that the deletions are persisted in native models. | 1092 // for sure that the deletions are persisted in native models. |
| 1103 string query = "CREATE TABLE deleted_metas "; | 1093 string query = "CREATE TABLE deleted_metas "; |
| 1104 query.append(ComposeCreateTableColumnSpecs()); | 1094 query.append(ComposeCreateTableColumnSpecs()); |
| 1105 if (!db_->Execute(query.c_str())) | 1095 if (!db_->Execute(query.c_str())) |
| 1106 return false; | 1096 return false; |
| 1097 |
| 1107 SetVersion(84); | 1098 SetVersion(84); |
| 1108 return true; | 1099 return true; |
| 1109 } | 1100 } |
| 1110 | 1101 |
| 1102 bool DirectoryBackingStore::MigrateVersion84To85() { |
| 1103 // Version 84 removes the initial_sync_ended flag. |
| 1104 if (!db_->Execute("ALTER TABLE models RENAME TO temp_models")) |
| 1105 return false; |
| 1106 if (!CreateModelsTable()) |
| 1107 return false; |
| 1108 if (!db_->Execute("INSERT INTO models SELECT " |
| 1109 "model_id, progress_marker, transaction_version " |
| 1110 "FROM temp_models")) { |
| 1111 return false; |
| 1112 } |
| 1113 SafeDropTable("temp_models"); |
| 1114 |
| 1115 SetVersion(85); |
| 1116 return true; |
| 1117 } |
| 1118 |
| 1111 bool DirectoryBackingStore::CreateTables() { | 1119 bool DirectoryBackingStore::CreateTables() { |
| 1112 DVLOG(1) << "First run, creating tables"; | 1120 DVLOG(1) << "First run, creating tables"; |
| 1113 // Create two little tables share_version and share_info | 1121 // Create two little tables share_version and share_info |
| 1114 if (!db_->Execute( | 1122 if (!db_->Execute( |
| 1115 "CREATE TABLE share_version (" | 1123 "CREATE TABLE share_version (" |
| 1116 "id VARCHAR(128) primary key, data INT)")) { | 1124 "id VARCHAR(128) primary key, data INT)")) { |
| 1117 return false; | 1125 return false; |
| 1118 } | 1126 } |
| 1119 | 1127 |
| 1120 { | 1128 { |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1206 return db_->Execute( | 1214 return db_->Execute( |
| 1207 "CREATE TABLE models (" | 1215 "CREATE TABLE models (" |
| 1208 "model_id BLOB primary key, " | 1216 "model_id BLOB primary key, " |
| 1209 "last_download_timestamp INT, " | 1217 "last_download_timestamp INT, " |
| 1210 // Gets set if the syncer ever gets updates from the | 1218 // Gets set if the syncer ever gets updates from the |
| 1211 // server and the server returns 0. Lets us detect the | 1219 // server and the server returns 0. Lets us detect the |
| 1212 // end of the initial sync. | 1220 // end of the initial sync. |
| 1213 "initial_sync_ended BOOLEAN default 0)"); | 1221 "initial_sync_ended BOOLEAN default 0)"); |
| 1214 } | 1222 } |
| 1215 | 1223 |
| 1224 bool DirectoryBackingStore::CreateV75ModelsTable() { |
| 1225 // This is an old schema for the Models table, used from versions 75 to 80. |
| 1226 return db_->Execute( |
| 1227 "CREATE TABLE models (" |
| 1228 "model_id BLOB primary key, " |
| 1229 "progress_marker BLOB, " |
| 1230 // Gets set if the syncer ever gets updates from the |
| 1231 // server and the server returns 0. Lets us detect the |
| 1232 // end of the initial sync. |
| 1233 "initial_sync_ended BOOLEAN default 0)"); |
| 1234 } |
| 1235 |
| 1216 bool DirectoryBackingStore::CreateModelsTable() { | 1236 bool DirectoryBackingStore::CreateModelsTable() { |
| 1217 // This is the current schema for the Models table, from version 81 | 1237 // This is the current schema for the Models table, from version 81 |
| 1218 // onward. If you change the schema, you'll probably want to double-check | 1238 // onward. If you change the schema, you'll probably want to double-check |
| 1219 // the use of this function in the v74-v75 migration. | 1239 // the use of this function in the v84-v85 migration. |
| 1220 return db_->Execute( | 1240 return db_->Execute( |
| 1221 "CREATE TABLE models (" | 1241 "CREATE TABLE models (" |
| 1222 "model_id BLOB primary key, " | 1242 "model_id BLOB primary key, " |
| 1223 "progress_marker BLOB, " | 1243 "progress_marker BLOB, " |
| 1224 // Gets set if the syncer ever gets updates from the | 1244 // Gets set if the syncer ever gets updates from the |
| 1225 // server and the server returns 0. Lets us detect the | 1245 // server and the server returns 0. Lets us detect the |
| 1226 // end of the initial sync. | 1246 // end of the initial sync. |
| 1227 "initial_sync_ended BOOLEAN default 0, " | |
| 1228 "transaction_version BIGINT default 0)"); | 1247 "transaction_version BIGINT default 0)"); |
| 1229 } | 1248 } |
| 1230 | 1249 |
| 1231 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) { | 1250 bool DirectoryBackingStore::CreateShareInfoTable(bool is_temporary) { |
| 1232 const char* name = is_temporary ? "temp_share_info" : "share_info"; | 1251 const char* name = is_temporary ? "temp_share_info" : "share_info"; |
| 1233 string query = "CREATE TABLE "; | 1252 string query = "CREATE TABLE "; |
| 1234 query.append(name); | 1253 query.append(name); |
| 1235 // This is the current schema for the ShareInfo table, from version 76 | 1254 // This is the current schema for the ShareInfo table, from version 76 |
| 1236 // onward. | 1255 // onward. |
| 1237 query.append(" (" | 1256 query.append(" (" |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1291 bool prev_exists = (ids_set.find(entry->ref(PREV_ID).value()) != end); | 1310 bool prev_exists = (ids_set.find(entry->ref(PREV_ID).value()) != end); |
| 1292 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end); | 1311 bool parent_exists = (ids_set.find(entry->ref(PARENT_ID).value()) != end); |
| 1293 bool next_exists = (ids_set.find(entry->ref(NEXT_ID).value()) != end); | 1312 bool next_exists = (ids_set.find(entry->ref(NEXT_ID).value()) != end); |
| 1294 is_ok = is_ok && prev_exists && parent_exists && next_exists; | 1313 is_ok = is_ok && prev_exists && parent_exists && next_exists; |
| 1295 } | 1314 } |
| 1296 return is_ok; | 1315 return is_ok; |
| 1297 } | 1316 } |
| 1298 | 1317 |
| 1299 } // namespace syncable | 1318 } // namespace syncable |
| 1300 } // namespace syncer | 1319 } // namespace syncer |
| OLD | NEW |