OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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 "chrome/browser/sync/syncable/directory_backing_store.h" | 5 #include "chrome/browser/sync/syncable/directory_backing_store.h" |
6 | 6 |
7 #include "build/build_config.h" | 7 #include "build/build_config.h" |
8 | 8 |
9 #if defined(OS_MACOSX) | 9 #if defined(OS_MACOSX) |
10 #include <CoreFoundation/CoreFoundation.h> | 10 #include <CoreFoundation/CoreFoundation.h> |
(...skipping 22 matching lines...) Expand all Loading... |
33 using std::string; | 33 using std::string; |
34 | 34 |
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 = 71; | 43 const int32 kCurrentDBVersion = 72; |
44 | 44 |
45 namespace { | 45 namespace { |
46 | 46 |
47 int ExecQuery(sqlite3* dbhandle, const char* query) { | 47 int ExecQuery(sqlite3* dbhandle, const char* query) { |
48 SQLStatement statement; | 48 SQLStatement statement; |
49 int result = statement.prepare(dbhandle, query); | 49 int result = statement.prepare(dbhandle, query); |
50 if (SQLITE_OK != result) | 50 if (SQLITE_OK != result) |
51 return result; | 51 return result; |
52 do { | 52 do { |
53 result = statement.step(); | 53 result = statement.step(); |
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
208 SetFileAttributes(backing_filepath_.value().c_str(), | 208 SetFileAttributes(backing_filepath_.value().c_str(), |
209 attrs | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); | 209 attrs | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); |
210 #endif | 210 #endif |
211 | 211 |
212 return true; | 212 return true; |
213 } | 213 } |
214 return false; | 214 return false; |
215 } | 215 } |
216 | 216 |
217 DirOpenResult DirectoryBackingStore::Load(MetahandlesIndex* entry_bucket, | 217 DirOpenResult DirectoryBackingStore::Load(MetahandlesIndex* entry_bucket, |
218 ExtendedAttributes* xattrs_bucket, | |
219 Directory::KernelLoadInfo* kernel_load_info) { | 218 Directory::KernelLoadInfo* kernel_load_info) { |
220 if (!BeginLoad()) | 219 if (!BeginLoad()) |
221 return FAILED_OPEN_DATABASE; | 220 return FAILED_OPEN_DATABASE; |
222 | 221 |
223 DirOpenResult result = InitializeTables(); | 222 DirOpenResult result = InitializeTables(); |
224 if (OPENED != result) | 223 if (OPENED != result) |
225 return result; | 224 return result; |
226 | 225 |
227 if (!DropDeletedEntries() || | 226 if (!DropDeletedEntries() || |
228 !LoadEntries(entry_bucket) || | 227 !LoadEntries(entry_bucket) || |
229 !LoadExtendedAttributes(xattrs_bucket) || | |
230 !LoadInfo(kernel_load_info)) { | 228 !LoadInfo(kernel_load_info)) { |
231 return FAILED_DATABASE_CORRUPT; | 229 return FAILED_DATABASE_CORRUPT; |
232 } | 230 } |
233 | 231 |
234 EndLoad(); | 232 EndLoad(); |
235 return OPENED; | 233 return OPENED; |
236 } | 234 } |
237 | 235 |
238 bool DirectoryBackingStore::BeginLoad() { | 236 bool DirectoryBackingStore::BeginLoad() { |
239 DCHECK(load_dbhandle_ == NULL); | 237 DCHECK(load_dbhandle_ == NULL); |
(...skipping 14 matching lines...) Expand all Loading... |
254 | 252 |
255 bool DirectoryBackingStore::SaveChanges( | 253 bool DirectoryBackingStore::SaveChanges( |
256 const Directory::SaveChangesSnapshot& snapshot) { | 254 const Directory::SaveChangesSnapshot& snapshot) { |
257 sqlite3* dbhandle = LazyGetSaveHandle(); | 255 sqlite3* dbhandle = LazyGetSaveHandle(); |
258 | 256 |
259 // SQLTransaction::BeginExclusive causes a disk write to occur. This is not | 257 // SQLTransaction::BeginExclusive causes a disk write to occur. This is not |
260 // something that should happen every 10 seconds when this function runs, so | 258 // something that should happen every 10 seconds when this function runs, so |
261 // just stop here if there's nothing to save. | 259 // just stop here if there's nothing to save. |
262 bool save_info = | 260 bool save_info = |
263 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status); | 261 (Directory::KERNEL_SHARE_INFO_DIRTY == snapshot.kernel_info_status); |
264 if (snapshot.dirty_metas.size() < 1 && snapshot.dirty_xattrs.size() < 1 && | 262 if (snapshot.dirty_metas.size() < 1 && !save_info) |
265 !save_info) | |
266 return true; | 263 return true; |
267 | 264 |
268 SQLTransaction transaction(dbhandle); | 265 SQLTransaction transaction(dbhandle); |
269 if (SQLITE_OK != transaction.BeginExclusive()) | 266 if (SQLITE_OK != transaction.BeginExclusive()) |
270 return false; | 267 return false; |
271 | 268 |
272 for (OriginalEntries::const_iterator i = snapshot.dirty_metas.begin(); | 269 for (OriginalEntries::const_iterator i = snapshot.dirty_metas.begin(); |
273 i != snapshot.dirty_metas.end(); ++i) { | 270 i != snapshot.dirty_metas.end(); ++i) { |
274 DCHECK(i->is_dirty()); | 271 DCHECK(i->is_dirty()); |
275 if (!SaveEntryToDB(*i)) | 272 if (!SaveEntryToDB(*i)) |
276 return false; | 273 return false; |
277 } | 274 } |
278 | 275 |
279 for (ExtendedAttributes::const_iterator i = snapshot.dirty_xattrs.begin(); | |
280 i != snapshot.dirty_xattrs.end(); ++i) { | |
281 DCHECK(i->second.dirty); | |
282 if (i->second.is_deleted) { | |
283 if (!DeleteExtendedAttributeFromDB(i)) | |
284 return false; | |
285 } else { | |
286 if (!SaveExtendedAttributeToDB(i)) | |
287 return false; | |
288 } | |
289 } | |
290 | |
291 if (save_info) { | 276 if (save_info) { |
292 const Directory::PersistedKernelInfo& info = snapshot.kernel_info; | 277 const Directory::PersistedKernelInfo& info = snapshot.kernel_info; |
293 SQLStatement update; | 278 SQLStatement update; |
294 update.prepare(dbhandle, "UPDATE share_info " | 279 update.prepare(dbhandle, "UPDATE share_info " |
295 "SET store_birthday = ?, " | 280 "SET store_birthday = ?, " |
296 "next_id = ?"); | 281 "next_id = ?"); |
297 update.bind_string(0, info.store_birthday); | 282 update.bind_string(0, info.store_birthday); |
298 update.bind_int64(1, info.next_id); | 283 update.bind_int64(1, info.next_id); |
299 | 284 |
300 if (!(SQLITE_DONE == update.step() && | 285 if (!(SQLITE_DONE == update.step() && |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
348 if (MigrateVersion69To70()) | 333 if (MigrateVersion69To70()) |
349 version_on_disk = 70; | 334 version_on_disk = 70; |
350 } | 335 } |
351 | 336 |
352 // Version 71 changed the sync progress information to be per-datatype. | 337 // Version 71 changed the sync progress information to be per-datatype. |
353 if (version_on_disk == 70) { | 338 if (version_on_disk == 70) { |
354 if (MigrateVersion70To71()) | 339 if (MigrateVersion70To71()) |
355 version_on_disk = 71; | 340 version_on_disk = 71; |
356 } | 341 } |
357 | 342 |
| 343 // Version 72 removed extended attributes, a legacy way to do extensible |
| 344 // key/value information, stored in their own table. |
| 345 if (version_on_disk == 71) { |
| 346 if (MigrateVersion71To72()) |
| 347 version_on_disk = 72; |
| 348 } |
| 349 |
358 // If one of the migrations requested it, drop columns that aren't current. | 350 // If one of the migrations requested it, drop columns that aren't current. |
359 // It's only safe to do this after migrating all the way to the current | 351 // It's only safe to do this after migrating all the way to the current |
360 // version. | 352 // version. |
361 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) { | 353 if (version_on_disk == kCurrentDBVersion && needs_column_refresh_) { |
362 if (!RefreshColumns()) | 354 if (!RefreshColumns()) |
363 version_on_disk = 0; | 355 version_on_disk = 0; |
364 } | 356 } |
365 | 357 |
366 // A final, alternative catch-all migration to simply re-sync everything. | 358 // A final, alternative catch-all migration to simply re-sync everything. |
367 if (version_on_disk != kCurrentDBVersion) { | 359 if (version_on_disk != kCurrentDBVersion) { |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
443 base::hash_set<int64> handles; | 435 base::hash_set<int64> handles; |
444 EntryKernel* kernel = NULL; | 436 EntryKernel* kernel = NULL; |
445 int query_result; | 437 int query_result; |
446 while (SQLITE_ROW == (query_result = UnpackEntry(&statement, &kernel))) { | 438 while (SQLITE_ROW == (query_result = UnpackEntry(&statement, &kernel))) { |
447 DCHECK(handles.insert(kernel->ref(META_HANDLE)).second); // Only in debug. | 439 DCHECK(handles.insert(kernel->ref(META_HANDLE)).second); // Only in debug. |
448 entry_bucket->insert(kernel); | 440 entry_bucket->insert(kernel); |
449 } | 441 } |
450 return SQLITE_DONE == query_result; | 442 return SQLITE_DONE == query_result; |
451 } | 443 } |
452 | 444 |
453 bool DirectoryBackingStore::LoadExtendedAttributes( | |
454 ExtendedAttributes* xattrs_bucket) { | |
455 SQLStatement statement; | |
456 statement.prepare( | |
457 load_dbhandle_, | |
458 "SELECT metahandle, key, value FROM extended_attributes"); | |
459 int step_result = statement.step(); | |
460 while (SQLITE_ROW == step_result) { | |
461 int64 metahandle = statement.column_int64(0); | |
462 | |
463 string path_string_key; | |
464 statement.column_string(1, &path_string_key); | |
465 | |
466 ExtendedAttributeValue val; | |
467 statement.column_blob_as_vector(2, &(val.value)); | |
468 val.is_deleted = false; | |
469 | |
470 ExtendedAttributeKey key(metahandle, path_string_key); | |
471 xattrs_bucket->insert(std::make_pair(key, val)); | |
472 step_result = statement.step(); | |
473 } | |
474 | |
475 return SQLITE_DONE == step_result; | |
476 } | |
477 | |
478 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) { | 445 bool DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) { |
479 { | 446 { |
480 SQLStatement query; | 447 SQLStatement query; |
481 query.prepare(load_dbhandle_, | 448 query.prepare(load_dbhandle_, |
482 "SELECT store_birthday, next_id, cache_guid " | 449 "SELECT store_birthday, next_id, cache_guid " |
483 "FROM share_info"); | 450 "FROM share_info"); |
484 if (SQLITE_ROW != query.step()) | 451 if (SQLITE_ROW != query.step()) |
485 return false; | 452 return false; |
486 info->kernel_info.store_birthday = query.column_string(0); | 453 info->kernel_info.store_birthday = query.column_string(0); |
487 info->kernel_info.next_id = query.column_int64(1); | 454 info->kernel_info.next_id = query.column_int64(1); |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
535 values.append(" )"); | 502 values.append(" )"); |
536 query.append(values); | 503 query.append(values); |
537 SQLStatement statement; | 504 SQLStatement statement; |
538 statement.prepare(save_dbhandle_, query.c_str()); | 505 statement.prepare(save_dbhandle_, query.c_str()); |
539 BindFields(entry, &statement); | 506 BindFields(entry, &statement); |
540 return (SQLITE_DONE == statement.step() && | 507 return (SQLITE_DONE == statement.step() && |
541 SQLITE_OK == statement.reset() && | 508 SQLITE_OK == statement.reset() && |
542 1 == statement.changes()); | 509 1 == statement.changes()); |
543 } | 510 } |
544 | 511 |
545 bool DirectoryBackingStore::SaveExtendedAttributeToDB( | |
546 ExtendedAttributes::const_iterator i) { | |
547 DCHECK(save_dbhandle_); | |
548 SQLStatement insert; | |
549 insert.prepare(save_dbhandle_, | |
550 "INSERT INTO extended_attributes " | |
551 "(metahandle, key, value) " | |
552 "values ( ?, ?, ? )"); | |
553 insert.bind_int64(0, i->first.metahandle); | |
554 insert.bind_string(1, i->first.key); | |
555 insert.bind_blob(2, &i->second.value.at(0), i->second.value.size()); | |
556 return (SQLITE_DONE == insert.step() && | |
557 SQLITE_OK == insert.reset() && | |
558 1 == insert.changes()); | |
559 } | |
560 | |
561 bool DirectoryBackingStore::DeleteExtendedAttributeFromDB( | |
562 ExtendedAttributes::const_iterator i) { | |
563 DCHECK(save_dbhandle_); | |
564 SQLStatement delete_attribute; | |
565 delete_attribute.prepare(save_dbhandle_, | |
566 "DELETE FROM extended_attributes " | |
567 "WHERE metahandle = ? AND key = ? "); | |
568 delete_attribute.bind_int64(0, i->first.metahandle); | |
569 delete_attribute.bind_string(1, i->first.key); | |
570 if (!(SQLITE_DONE == delete_attribute.step() && | |
571 SQLITE_OK == delete_attribute.reset() && | |
572 1 == delete_attribute.changes())) { | |
573 LOG(ERROR) << "DeleteExtendedAttributeFromDB(),StepDone() failed " | |
574 << "for metahandle: " << i->first.metahandle << " key: " | |
575 << i->first.key; | |
576 return false; | |
577 } | |
578 // The attribute may have never been saved to the database if it was | |
579 // created and then immediately deleted. So don't check that we | |
580 // deleted exactly 1 row. | |
581 return true; | |
582 } | |
583 | |
584 bool DirectoryBackingStore::DropDeletedEntries() { | 512 bool DirectoryBackingStore::DropDeletedEntries() { |
585 static const char delete_extended_attributes[] = | |
586 "DELETE FROM extended_attributes WHERE metahandle IN " | |
587 "(SELECT metahandle from death_row)"; | |
588 static const char delete_metas[] = "DELETE FROM metas WHERE metahandle IN " | 513 static const char delete_metas[] = "DELETE FROM metas WHERE metahandle IN " |
589 "(SELECT metahandle from death_row)"; | 514 "(SELECT metahandle from death_row)"; |
590 // Put all statements into a transaction for better performance | 515 // Put all statements into a transaction for better performance |
591 SQLTransaction transaction(load_dbhandle_); | 516 SQLTransaction transaction(load_dbhandle_); |
592 transaction.Begin(); | 517 transaction.Begin(); |
593 if (SQLITE_DONE != ExecQuery( | 518 if (SQLITE_DONE != ExecQuery( |
594 load_dbhandle_, | 519 load_dbhandle_, |
595 "CREATE TEMP TABLE death_row (metahandle BIGINT)")) { | 520 "CREATE TEMP TABLE death_row (metahandle BIGINT)")) { |
596 return false; | 521 return false; |
597 } | 522 } |
598 if (SQLITE_DONE != ExecQuery(load_dbhandle_, | 523 if (SQLITE_DONE != ExecQuery(load_dbhandle_, |
599 "INSERT INTO death_row " | 524 "INSERT INTO death_row " |
600 "SELECT metahandle from metas WHERE is_del > 0 " | 525 "SELECT metahandle from metas WHERE is_del > 0 " |
601 " AND is_unsynced < 1" | 526 " AND is_unsynced < 1" |
602 " AND is_unapplied_update < 1")) { | 527 " AND is_unapplied_update < 1")) { |
603 return false; | 528 return false; |
604 } | 529 } |
605 if (SQLITE_DONE != ExecQuery(load_dbhandle_, delete_extended_attributes)) { | |
606 return false; | |
607 } | |
608 if (SQLITE_DONE != ExecQuery(load_dbhandle_, delete_metas)) { | 530 if (SQLITE_DONE != ExecQuery(load_dbhandle_, delete_metas)) { |
609 return false; | 531 return false; |
610 } | 532 } |
611 if (SQLITE_DONE != ExecQuery(load_dbhandle_, "DROP TABLE death_row")) { | 533 if (SQLITE_DONE != ExecQuery(load_dbhandle_, "DROP TABLE death_row")) { |
612 return false; | 534 return false; |
613 } | 535 } |
614 transaction.Commit(); | 536 transaction.Commit(); |
615 return true; | 537 return true; |
616 } | 538 } |
617 | 539 |
618 int DirectoryBackingStore::SafeDropTable(const char* table_name) { | 540 int DirectoryBackingStore::SafeDropTable(const char* table_name) { |
619 string query = "DROP TABLE IF EXISTS "; | 541 string query = "DROP TABLE IF EXISTS "; |
620 query.append(table_name); | 542 query.append(table_name); |
621 SQLStatement statement; | 543 SQLStatement statement; |
622 int result = statement.prepare(load_dbhandle_, query.data(), | 544 int result = statement.prepare(load_dbhandle_, query.data(), |
623 query.size()); | 545 query.size()); |
624 if (SQLITE_OK == result) { | 546 if (SQLITE_OK == result) { |
625 result = statement.step(); | 547 result = statement.step(); |
626 if (SQLITE_DONE == result) | 548 if (SQLITE_DONE == result) |
627 statement.finalize(); | 549 statement.finalize(); |
628 } | 550 } |
629 | 551 |
630 return result; | 552 return result; |
631 } | 553 } |
632 | 554 |
633 int DirectoryBackingStore::CreateExtendedAttributeTable() { | |
634 int result = SafeDropTable("extended_attributes"); | |
635 if (result != SQLITE_DONE) | |
636 return result; | |
637 LOG(INFO) << "CreateExtendedAttributeTable"; | |
638 return ExecQuery(load_dbhandle_, | |
639 "CREATE TABLE extended_attributes(" | |
640 "metahandle bigint, " | |
641 "key varchar(127), " | |
642 "value blob, " | |
643 "PRIMARY KEY(metahandle, key) ON CONFLICT REPLACE)"); | |
644 } | |
645 | |
646 void DirectoryBackingStore::DropAllTables() { | 555 void DirectoryBackingStore::DropAllTables() { |
647 SafeDropTable("metas"); | 556 SafeDropTable("metas"); |
648 SafeDropTable("temp_metas"); | 557 SafeDropTable("temp_metas"); |
649 SafeDropTable("share_info"); | 558 SafeDropTable("share_info"); |
650 SafeDropTable("temp_share_info"); | 559 SafeDropTable("temp_share_info"); |
651 SafeDropTable("share_version"); | 560 SafeDropTable("share_version"); |
652 SafeDropTable("extended_attributes"); | 561 SafeDropTable("extended_attributes"); |
653 SafeDropTable("models"); | 562 SafeDropTable("models"); |
654 needs_column_refresh_ = false; | 563 needs_column_refresh_ = false; |
655 } | 564 } |
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
882 return false; | 791 return false; |
883 SafeDropTable("share_info"); | 792 SafeDropTable("share_info"); |
884 result = ExecQuery(load_dbhandle_, | 793 result = ExecQuery(load_dbhandle_, |
885 "ALTER TABLE temp_share_info RENAME TO share_info"); | 794 "ALTER TABLE temp_share_info RENAME TO share_info"); |
886 if (result != SQLITE_DONE) | 795 if (result != SQLITE_DONE) |
887 return false; | 796 return false; |
888 SetVersion(71); | 797 SetVersion(71); |
889 return true; | 798 return true; |
890 } | 799 } |
891 | 800 |
| 801 bool DirectoryBackingStore::MigrateVersion71To72() { |
| 802 SafeDropTable("extended_attributes"); |
| 803 SetVersion(72); |
| 804 return true; |
| 805 } |
| 806 |
892 int DirectoryBackingStore::CreateTables() { | 807 int DirectoryBackingStore::CreateTables() { |
893 LOG(INFO) << "First run, creating tables"; | 808 LOG(INFO) << "First run, creating tables"; |
894 // Create two little tables share_version and share_info | 809 // Create two little tables share_version and share_info |
895 int result = ExecQuery(load_dbhandle_, | 810 int result = ExecQuery(load_dbhandle_, |
896 "CREATE TABLE share_version (" | 811 "CREATE TABLE share_version (" |
897 "id VARCHAR(128) primary key, data INT)"); | 812 "id VARCHAR(128) primary key, data INT)"); |
898 if (result != SQLITE_DONE) | 813 if (result != SQLITE_DONE) |
899 return result; | 814 return result; |
900 { | 815 { |
901 SQLStatement statement; | 816 SQLStatement statement; |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
943 const int64 now = Now(); | 858 const int64 now = Now(); |
944 SQLStatement statement; | 859 SQLStatement statement; |
945 statement.prepare(load_dbhandle_, | 860 statement.prepare(load_dbhandle_, |
946 "INSERT INTO metas " | 861 "INSERT INTO metas " |
947 "( id, metahandle, is_dir, ctime, mtime) " | 862 "( id, metahandle, is_dir, ctime, mtime) " |
948 "VALUES ( \"r\", 1, 1, ?, ?)"); | 863 "VALUES ( \"r\", 1, 1, ?, ?)"); |
949 statement.bind_int64(0, now); | 864 statement.bind_int64(0, now); |
950 statement.bind_int64(1, now); | 865 statement.bind_int64(1, now); |
951 result = statement.step(); | 866 result = statement.step(); |
952 } | 867 } |
953 if (result != SQLITE_DONE) | |
954 return result; | |
955 result = CreateExtendedAttributeTable(); | |
956 return result; | 868 return result; |
957 } | 869 } |
958 | 870 |
959 sqlite3* DirectoryBackingStore::LazyGetSaveHandle() { | 871 sqlite3* DirectoryBackingStore::LazyGetSaveHandle() { |
960 if (!save_dbhandle_ && !OpenAndConfigureHandleHelper(&save_dbhandle_)) { | 872 if (!save_dbhandle_ && !OpenAndConfigureHandleHelper(&save_dbhandle_)) { |
961 DCHECK(FALSE) << "Unable to open handle for saving"; | 873 DCHECK(FALSE) << "Unable to open handle for saving"; |
962 return NULL; | 874 return NULL; |
963 } | 875 } |
964 return save_dbhandle_; | 876 return save_dbhandle_; |
965 } | 877 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
998 "name TEXT, " | 910 "name TEXT, " |
999 "store_birthday TEXT, " | 911 "store_birthday TEXT, " |
1000 "db_create_version TEXT, " | 912 "db_create_version TEXT, " |
1001 "db_create_time INT, " | 913 "db_create_time INT, " |
1002 "next_id INT default -2, " | 914 "next_id INT default -2, " |
1003 "cache_guid TEXT)"); | 915 "cache_guid TEXT)"); |
1004 return ExecQuery(load_dbhandle_, query.c_str()); | 916 return ExecQuery(load_dbhandle_, query.c_str()); |
1005 } | 917 } |
1006 | 918 |
1007 } // namespace syncable | 919 } // namespace syncable |
OLD | NEW |