Source/WebCore/ChangeLog

 12020-09-28 Sihui Liu <sihui_liu@apple.com>
 2
 3 IndexedDB Index Corruption after upgrade from iOS 13 to iOS 14
 4 https://bugs.webkit.org/show_bug.cgi?id=216962
 5 <rdar://problem/69587004>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 There is an implementation error in r255318, which updated index ID in database IndexInfo and IndexRecords
 10 tables: it made changes to original table while iterating records of it. This means modified records can
 11 be modified again, which leads to that two indices have the same index ID. As index ID is supposed to be unique,
 12 some indices and index records cannot be populated and clients will find some indices are missing.
 13
 14 To fix this, we need to change the implementation of ID update and deal with already corrupted tables. For index
 15 ID update, now we create a temporary table for storing the updated records, and replace the original table with
 16 temporary table at the end of iteration. For the corruption issue, we check if IndexInfo table has duplicate
 17 index IDs. If it has, we try recovering IndexInfo and IndexRecords table by removing records with duplicate
 18 index IDs, assigning new IDs to duplicate indices, and adding records with updated index IDs.
 19
 20 API tests: IndexedDB.IndexUpgradeToV2WithMultipleIndices
 21 IndexedDB.IndexUpgradeToV2WithMultipleIndicesHaveSameID
 22
 23 * Modules/indexeddb/server/SQLiteIDBBackingStore.cpp:
 24 (WebCore::IDBServer::indexInfoTableSchema):
 25 (WebCore::IDBServer::SQLiteIDBBackingStore::createAndPopulateInitialDatabaseInfo):
 26 (WebCore::IDBServer::SQLiteIDBBackingStore::migrateIndexInfoTableForIDUpdate):
 27 (WebCore::IDBServer::SQLiteIDBBackingStore::migrateIndexRecordsTableForIDUpdate):
 28 (WebCore::IDBServer::SQLiteIDBBackingStore::removeExistingIndex):
 29 (WebCore::IDBServer::SQLiteIDBBackingStore::addExistingIndex):
 30 (WebCore::IDBServer::SQLiteIDBBackingStore::handleDuplicateIndexIDs):
 31 (WebCore::IDBServer::SQLiteIDBBackingStore::extractExistingDatabaseInfo):
 32 (WebCore::IDBServer::SQLiteIDBBackingStore::createIndex):
 33 (WebCore::IDBServer::SQLiteIDBBackingStore::updateOneIndexForAddRecord):
 34 * Modules/indexeddb/server/SQLiteIDBBackingStore.h:
 35 * Modules/indexeddb/shared/IDBIndexInfo.h:
 36 (WebCore::IDBIndexInfo::setIdentifier):
 37 * Modules/indexeddb/shared/IDBObjectStoreInfo.cpp:
 38 (WebCore::IDBObjectStoreInfo::addExistingIndex):
 39
1402020-09-27 Zalan Bujtas <zalan@apple.com>
241
342 Unreviewed. Call showLayoutTree only when trees are mismatching.

Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp

@@static String createV2ObjectStoreInfoSchema(ASCIILiteral tableName)
239239 return makeString("CREATE TABLE ", tableName, " (id INTEGER PRIMARY KEY NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, name TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, keyPath BLOB NOT NULL ON CONFLICT FAIL, autoInc INTEGER NOT NULL ON CONFLICT FAIL)");
240240}
241241
 242static String indexInfoTableSchema(const String& tableName)
 243{
 244 return makeString("CREATE TABLE ", tableName, " (id INTEGER NOT NULL ON CONFLICT FAIL, name TEXT NOT NULL ON CONFLICT FAIL, objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, keyPath BLOB NOT NULL ON CONFLICT FAIL, isUnique INTEGER NOT NULL ON CONFLICT FAIL, multiEntry INTEGER NOT NULL ON CONFLICT FAIL)");
 245}
 246
242247SQLiteIDBBackingStore::SQLiteIDBBackingStore(PAL::SessionID sessionID, const IDBDatabaseIdentifier& identifier, const String& databaseRootDirectory)
243248 : m_sessionID(sessionID)
244249 , m_identifier(identifier)

@@std::unique_ptr<IDBDatabaseInfo> SQLiteIDBBackingStore::createAndPopulateInitial
607612 return nullptr;
608613 }
609614
610  if (!m_sqliteDB->executeCommand("CREATE TABLE IndexInfo (id INTEGER NOT NULL ON CONFLICT FAIL, name TEXT NOT NULL ON CONFLICT FAIL, objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, keyPath BLOB NOT NULL ON CONFLICT FAIL, isUnique INTEGER NOT NULL ON CONFLICT FAIL, multiEntry INTEGER NOT NULL ON CONFLICT FAIL);")) {
 615 if (!m_sqliteDB->executeCommand(indexInfoTableSchema("IndexInfo"))) {
611616 LOG_ERROR("Could not create IndexInfo table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
612617 closeSQLiteDB();
613618 return nullptr;

@@Optional<IsSchemaUpgraded> SQLiteIDBBackingStore::ensureValidObjectStoreInfoTabl
723728 return { IsSchemaUpgraded::Yes };
724729}
725730
 731bool SQLiteIDBBackingStore::migrateIndexInfoTableForIDUpdate(const HashMap<std::pair<uint64_t, uint64_t>, uint64_t>& indexIDMap)
 732{
 733 SQLiteDatabase& database = *m_sqliteDB;
 734 SQLiteTransaction transaction(database);
 735 transaction.begin();
 736
 737 if (!database.executeCommand(indexInfoTableSchema("_Temp_IndexInfo"))) {
 738 LOG_ERROR("Error creating _Temp_IndexInfo table in database (%i) - %s", database.lastError(), database.lastErrorMsg());
 739 return false;
 740 }
 741
 742 {
 743 SQLiteStatement statement(database, "SELECT id, name, objectStoreID, keyPath, isUnique, multiEntry FROM IndexInfo;"_s);
 744 if (statement.prepare() != SQLITE_OK) {
 745 LOG_ERROR("Error preparing statement to fetch records from IndexInfo table (%i) - %s", database.lastError(), database.lastErrorMsg());
 746 return false;
 747 }
 748
 749 int result = statement.step();
 750 while (result == SQLITE_ROW) {
 751 uint64_t id = statement.getColumnInt64(0);
 752 String name = statement.getColumnText(1);
 753 uint64_t objectStoreID = statement.getColumnInt64(2);
 754 uint64_t newID = indexIDMap.get({ objectStoreID, id });
 755 Vector<uint8_t> keyPathBuffer;
 756 statement.getColumnBlobAsVector(3, keyPathBuffer);
 757 bool unique = statement.getColumnInt(4);
 758 bool multiEntry = statement.getColumnInt(5);
 759
 760 auto sql = cachedStatement(SQL::CreateTempIndexInfo, "INSERT INTO _Temp_IndexInfo VALUES (?, ?, ?, ?, ?, ?);"_s);
 761 if (!sql
 762 || sql->bindInt64(1, newID) != SQLITE_OK
 763 || sql->bindText(2, name) != SQLITE_OK
 764 || sql->bindInt64(3, objectStoreID) != SQLITE_OK
 765 || sql->bindBlob(4, keyPathBuffer.data(), keyPathBuffer.size()) != SQLITE_OK
 766 || sql->bindInt(5, unique) != SQLITE_OK
 767 || sql->bindInt(6, multiEntry) != SQLITE_OK
 768 || sql->step() != SQLITE_DONE) {
 769 LOG_ERROR("Error adding index '%s' to _Temp_IndexInfo table (%i) - %s", name.utf8().data(), database.lastError(), database.lastErrorMsg());
 770 return false;
 771 }
 772
 773 result = statement.step();
 774 }
 775
 776 if (result != SQLITE_DONE) {
 777 LOG_ERROR("Error fetching indices from IndexInfo table (%i) - %s", database.lastError(), database.lastErrorMsg());
 778 return false;
 779 }
 780 }
 781
 782 if (!database.executeCommand("DROP TABLE IndexInfo")) {
 783 LOG_ERROR("Error dropping existing IndexInfo table (%i) - %s", database.lastError(), database.lastErrorMsg());
 784 return false;
 785 }
 786
 787 if (!database.executeCommand("ALTER TABLE _Temp_IndexInfo RENAME TO IndexInfo")) {
 788 LOG_ERROR("Error renaming _Temp_IndexInfo table (%i) - %s", database.lastError(), database.lastErrorMsg());
 789 return false;
 790 }
 791
 792 transaction.commit();
 793 return true;
 794}
 795
 796bool SQLiteIDBBackingStore::migrateIndexRecordsTableForIDUpdate(const HashMap<std::pair<uint64_t, uint64_t>, uint64_t>& indexIDMap)
 797{
 798 SQLiteDatabase& database = *m_sqliteDB;
 799 SQLiteTransaction transaction(database);
 800 transaction.begin();
 801
 802 if (!database.executeCommand(v3IndexRecordsTableSchema("_Temp_IndexRecords"))) {
 803 LOG_ERROR("Error creating _Temp_IndexRecords table in database (%i) - %s", database.lastError(), database.lastErrorMsg());
 804 return false;
 805 }
 806
 807 {
 808 SQLiteStatement statement(database, "SELECT indexID, objectStoreID, key, value, objectStoreRecordID FROM IndexRecords;"_s);
 809 if (statement.prepare() != SQLITE_OK) {
 810 LOG_ERROR("Error preparing statement to fetch records from the IndexRecords table (%i) - %s", database.lastError(), database.lastErrorMsg());
 811 return false;
 812 }
 813
 814 int result = statement.step();
 815 while (result == SQLITE_ROW) {
 816 uint64_t id = statement.getColumnInt64(0);
 817 uint64_t objectStoreID = statement.getColumnInt64(1);
 818 uint64_t newID = indexIDMap.get({ objectStoreID, id });
 819 Vector<uint8_t> keyBuffer;
 820 statement.getColumnBlobAsVector(2, keyBuffer);
 821 Vector<uint8_t> valueBuffer;
 822 statement.getColumnBlobAsVector(3, valueBuffer);
 823 uint64_t recordID = statement.getColumnInt64(4);
 824
 825 auto sql = cachedStatement(SQL::PutTempIndexRecord, "INSERT INTO _Temp_IndexRecords VALUES (?, ?, CAST(? AS TEXT), CAST(? AS TEXT), ?);"_s);
 826 if (!sql
 827 || sql->bindInt64(1, newID) != SQLITE_OK
 828 || sql->bindInt64(2, objectStoreID) != SQLITE_OK
 829 || sql->bindBlob(3, keyBuffer.data(), keyBuffer.size()) != SQLITE_OK
 830 || sql->bindBlob(4, valueBuffer.data(), valueBuffer.size()) != SQLITE_OK
 831 || sql->bindInt64(5, recordID) != SQLITE_OK
 832 || sql->step() != SQLITE_DONE) {
 833 LOG_ERROR("Error adding index record to _Temp_IndexRecords table (%i) - %s", database.lastError(), database.lastErrorMsg());
 834 return false;
 835 }
 836
 837 result = statement.step();
 838 }
 839
 840 if (result != SQLITE_DONE) {
 841 LOG_ERROR("Error fetching index record from database on disk (%i) - %s", database.lastError(), database.lastErrorMsg());
 842 return false;
 843 }
 844 }
 845
 846 if (!database.executeCommand("DROP TABLE IndexRecords")) {
 847 LOG_ERROR("Error dropping existing IndexRecords table (%i) - %s", database.lastError(), database.lastErrorMsg());
 848 return false;
 849 }
 850
 851 if (!database.executeCommand("ALTER TABLE _Temp_IndexRecords RENAME TO IndexRecords")) {
 852 LOG_ERROR("Error renaming temporary IndexRecords table (%i) - %s", database.lastError(), database.lastErrorMsg());
 853 return false;
 854 }
 855
 856 transaction.commit();
 857 return true;
 858}
 859
 860bool SQLiteIDBBackingStore::removeExistingIndex(uint64_t indexID)
 861{
 862 SQLiteTransaction transaction(*m_sqliteDB);
 863 transaction.begin();
 864
 865 {
 866 auto sql = cachedStatement(SQL::RemoveIndexInfo, "DELETE FROM IndexInfo WHERE id = ?;"_s);
 867 if (!sql
 868 || sql->bindInt64(1, indexID) != SQLITE_OK
 869 || sql->step() != SQLITE_DONE) {
 870 LOG_ERROR("Unable to remove existing index from IndexInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
 871 return false;
 872 }
 873 }
 874
 875 {
 876 auto sql = cachedStatement(SQL::DeleteIndexRecords, "DELETE FROM IndexRecords WHERE indexID = ?;"_s);
 877 if (!sql
 878 || sql->bindInt64(1, indexID) != SQLITE_OK
 879 || sql->step() != SQLITE_DONE) {
 880 LOG_ERROR("Unable to remove existing index from IndexRecords table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
 881 return false;
 882 }
 883 }
 884
 885 transaction.commit();
 886 return true;
 887}
 888
 889bool SQLiteIDBBackingStore::addExistingIndex(IDBObjectStoreInfo& objectStoreInfo, const IDBIndexInfo& info)
 890{
 891 SQLiteTransaction transaction(*m_sqliteDB);
 892 transaction.begin();
 893
 894 RefPtr<SharedBuffer> keyPathBlob = serializeIDBKeyPath(info.keyPath());
 895 if (!keyPathBlob) {
 896 LOG_ERROR("Unable to serialize IDBKeyPath to save in database");
 897 return false;
 898 }
 899
 900 {
 901 auto sql = cachedStatement(SQL::CreateIndexInfo, "INSERT INTO IndexInfo VALUES (?, ?, ?, ?, ?, ?);"_s);
 902 if (!sql
 903 || sql->bindInt64(1, info.identifier()) != SQLITE_OK
 904 || sql->bindText(2, info.name()) != SQLITE_OK
 905 || sql->bindInt64(3, info.objectStoreIdentifier()) != SQLITE_OK
 906 || sql->bindBlob(4, keyPathBlob->data(), keyPathBlob->size()) != SQLITE_OK
 907 || sql->bindInt(5, info.unique()) != SQLITE_OK
 908 || sql->bindInt(6, info.multiEntry()) != SQLITE_OK
 909 || sql->step() != SQLITE_DONE) {
 910 LOG_ERROR("Could not add index '%s' to IndexInfo table (%i) - %s", info.name().utf8().data(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
 911 return false;
 912 }
 913 }
 914
 915 {
 916 SQLiteStatement sql(*m_sqliteDB, "SELECT key, value, recordID FROM Records WHERE objectStoreID = ?;");
 917 if (sql.prepare() != SQLITE_OK
 918 || sql.bindInt64(1, info.objectStoreIdentifier()) != SQLITE_OK) {
 919 LOG_ERROR("Unable to prepare statement or bind values (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
 920 return false;
 921 }
 922
 923 int result = sql.step();
 924 while (result == SQLITE_ROW) {
 925 Vector<uint8_t> keyBuffer;
 926 IDBKeyData keyData;
 927 sql.getColumnBlobAsVector(0, keyBuffer);
 928 if (!deserializeIDBKeyData(keyBuffer.data(), keyBuffer.size(), keyData)) {
 929 LOG_ERROR("Unable to deserialize key data from database while getting all records");
 930 return false;
 931 }
 932
 933 Vector<uint8_t> valueBuffer;
 934 sql.getColumnBlobAsVector(1, valueBuffer);
 935 auto value = ThreadSafeDataBuffer::create(WTFMove(valueBuffer));
 936
 937 uint64_t recordID = sql.getColumnInt64(2);
 938 IDBError error = updateOneIndexForAddRecord(objectStoreInfo, info, keyData, value, recordID);
 939 if (!error.isNull())
 940 return false;
 941
 942 result = sql.step();
 943 }
 944
 945 if (result != SQLITE_DONE) {
 946 LOG_ERROR("Error fetching object records from Records table on disk");
 947 return false;
 948 }
 949 }
 950
 951 transaction.commit();
 952
 953 return true;
 954}
 955
 956bool SQLiteIDBBackingStore::handleDuplicateIndexIDs(const HashMap<uint64_t, Vector<IDBIndexInfo>>& indexInfoMap, IDBDatabaseInfo& databaseInfo)
 957{
 958 for (auto& [indexID, infos] : indexInfoMap) {
 959 if (infos.size() == 1)
 960 continue;
 961
 962 if (!removeExistingIndex(indexID))
 963 return false;
 964
 965 for (auto info : infos) {
 966 auto objectStoreInfo = databaseInfo.infoForExistingObjectStore(info.objectStoreIdentifier());
 967 ASSERT(objectStoreInfo);
 968 objectStoreInfo->deleteIndex(info.identifier());
 969
 970 // A new index with the same name may be added.
 971 if (objectStoreInfo->hasIndex(info.name()))
 972 continue;
 973
 974 info.setIdentifier(databaseInfo.generateNextIndexID());
 975 if (!addExistingIndex(*objectStoreInfo, info))
 976 return false;
 977
 978 objectStoreInfo->addExistingIndex(info);
 979 }
 980 }
 981
 982 return true;
 983}
 984
726985std::unique_ptr<IDBDatabaseInfo> SQLiteIDBBackingStore::extractExistingDatabaseInfo()
727986{
728987 ASSERT(m_sqliteDB);

@@std::unique_ptr<IDBDatabaseInfo> SQLiteIDBBackingStore::extractExistingDatabaseI
7971056
7981057 uint64_t maxIndexID = 0;
7991058 HashMap<std::pair<uint64_t, uint64_t>, uint64_t> indexIDMap;
 1059 HashMap<uint64_t, Vector<IDBIndexInfo>> indexInfoMap;
8001060 {
8011061 SQLiteStatement sql(*m_sqliteDB, "SELECT id, name, objectStoreID, keyPath, isUnique, multiEntry FROM IndexInfo;"_s);
8021062 if (sql.prepare() != SQLITE_OK) {

@@std::unique_ptr<IDBDatabaseInfo> SQLiteIDBBackingStore::extractExistingDatabaseI
8371097 indexID = maxIndexID;
8381098 }
8391099
840  objectStore->addExistingIndex({ indexID, objectStoreID, indexName, WTFMove(indexKeyPath.value()), unique, multiEntry });
 1100 auto indexInfo = IDBIndexInfo { indexID, objectStoreID, indexName, WTFMove(indexKeyPath.value()), unique, multiEntry };
 1101 if (!shouldUpdateIndexID) {
 1102 auto& indexInfos = indexInfoMap.ensure(indexID, []() -> Vector<IDBIndexInfo> {
 1103 return { };
 1104 }).iterator->value;
 1105 indexInfos.append(indexInfo);
 1106 }
 1107
 1108 objectStore->addExistingIndex(indexInfo);
8411109 maxIndexID = maxIndexID < indexID ? indexID : maxIndexID;
8421110
8431111 result = sql.step();

@@std::unique_ptr<IDBDatabaseInfo> SQLiteIDBBackingStore::extractExistingDatabaseI
8481116 return nullptr;
8491117 }
8501118 databaseInfo->setMaxIndexID(maxIndexID);
 1119 }
8511120
852  if (!shouldUpdateIndexID)
853  return databaseInfo;
854 
855  for (auto& entry : indexIDMap) {
856  SQLiteStatement sql(*m_sqliteDB, "UPDATE IndexInfo SET id = ? WHERE id = ? AND objectStoreID = ?;"_s);
857  if (sql.prepare() != SQLITE_OK
858  || sql.bindInt64(1, entry.value) != SQLITE_OK
859  || sql.bindInt64(2, entry.key.second) != SQLITE_OK
860  || sql.bindInt64(3, entry.key.first) != SQLITE_OK
861  || sql.step() != SQLITE_DONE) {
862  LOG_ERROR("Unable to update id of IndexInfo table");
863  return nullptr;
864  }
865 
866  SQLiteStatement recordSql(*m_sqliteDB, "UPDATE IndexRecords SET indexID = ? WHERE indexID = ? AND objectStoreID = ?;"_s);
867  if (recordSql.prepare() != SQLITE_OK
868  || recordSql.bindInt64(1, entry.value) != SQLITE_OK
869  || recordSql.bindInt64(2, entry.key.second) != SQLITE_OK
870  || recordSql.bindInt64(3, entry.key.first) != SQLITE_OK
871  || recordSql.step() != SQLITE_DONE) {
872  LOG_ERROR("Unable to update indexID of IndexRecords table");
873  return nullptr;
874  }
875  }
 1121 if (shouldUpdateIndexID) {
 1122 if (!migrateIndexInfoTableForIDUpdate(indexIDMap) || !migrateIndexRecordsTableForIDUpdate(indexIDMap))
 1123 return nullptr;
 1124 } else {
 1125 if (!handleDuplicateIndexIDs(indexInfoMap, *databaseInfo))
 1126 return nullptr;
8761127 }
8771128
8781129 return databaseInfo;

@@IDBError SQLiteIDBBackingStore::createIndex(const IDBResourceIdentifier& transac
13801631
13811632 ASSERT(cursor->currentRecordRowID());
13821633
1383  IDBError error = updateOneIndexForAddRecord(info, key, valueBuffer, cursor->currentRecordRowID());
 1634 auto* objectStoreInfo = infoForObjectStore(info.objectStoreIdentifier());
 1635 ASSERT(objectStoreInfo);
 1636 IDBError error = updateOneIndexForAddRecord(*objectStoreInfo, info, key, valueBuffer, cursor->currentRecordRowID());
13841637 if (!error.isNull()) {
13851638 auto sql = cachedStatement(SQL::DeleteIndexInfo, "DELETE FROM IndexInfo WHERE id = ? AND objectStoreID = ?;"_s);
13861639 if (!sql

@@IDBError SQLiteIDBBackingStore::deleteRange(const IDBResourceIdentifier& transac
18422095 return error;
18432096}
18442097
1845 IDBError SQLiteIDBBackingStore::updateOneIndexForAddRecord(const IDBIndexInfo& info, const IDBKeyData& key, const ThreadSafeDataBuffer& value, int64_t recordID)
 2098IDBError SQLiteIDBBackingStore::updateOneIndexForAddRecord(IDBObjectStoreInfo& objectStoreInfo, const IDBIndexInfo& info, const IDBKeyData& key, const ThreadSafeDataBuffer& value, int64_t recordID)
18462099{
18472100 JSLockHolder locker(m_serializationContext->vm());
18482101

@@IDBError SQLiteIDBBackingStore::updateOneIndexForAddRecord(const IDBIndexInfo& i
18512104 return IDBError { };
18522105
18532106 IndexKey indexKey;
1854  auto* objectStoreInfo = infoForObjectStore(info.objectStoreIdentifier());
1855  ASSERT(objectStoreInfo);
1856  generateIndexKeyForValue(m_serializationContext->execState(), info, jsValue, indexKey, objectStoreInfo->keyPath(), key);
 2107 generateIndexKeyForValue(m_serializationContext->execState(), info, jsValue, indexKey, objectStoreInfo.keyPath(), key);
18572108
18582109 if (indexKey.isNull())
18592110 return IDBError { };

Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.h

@@private:
127127 IDBError uncheckedSetKeyGeneratorValue(int64_t objectStoreID, uint64_t value);
128128
129129 IDBError updateAllIndexesForAddRecord(const IDBObjectStoreInfo&, const IDBKeyData&, const IndexIDToIndexKeyMap&, int64_t recordID);
130  IDBError updateOneIndexForAddRecord(const IDBIndexInfo&, const IDBKeyData&, const ThreadSafeDataBuffer& value, int64_t recordID);
 130 IDBError updateOneIndexForAddRecord(IDBObjectStoreInfo&, const IDBIndexInfo&, const IDBKeyData&, const ThreadSafeDataBuffer& value, int64_t recordID);
131131 IDBError uncheckedPutIndexKey(const IDBIndexInfo&, const IDBKeyData& keyValue, const IndexKey&, int64_t recordID);
132132 IDBError uncheckedPutIndexRecord(int64_t objectStoreID, int64_t indexID, const IDBKeyData& keyValue, const IDBKeyData& indexKey, int64_t recordID);
133133 IDBError uncheckedHasIndexRecord(const IDBIndexInfo&, const IDBKeyData&, bool& hasRecord);

@@private:
141141 void closeSQLiteDB();
142142 void close() final;
143143
 144 bool migrateIndexInfoTableForIDUpdate(const HashMap<std::pair<uint64_t, uint64_t>, uint64_t>& indexIDMap);
 145 bool migrateIndexRecordsTableForIDUpdate(const HashMap<std::pair<uint64_t, uint64_t>, uint64_t>& indexIDMap);
 146
 147 bool removeExistingIndex(uint64_t indexID);
 148 bool addExistingIndex(IDBObjectStoreInfo&, const IDBIndexInfo&);
 149 bool handleDuplicateIndexIDs(const HashMap<uint64_t, Vector<IDBIndexInfo>>&, IDBDatabaseInfo&);
 150
144151 enum class SQL : size_t {
145152 CreateObjectStoreInfo,
146153 CreateObjectStoreKeyGenerator,

@@private:
154161 ClearObjectStoreRecords,
155162 ClearObjectStoreIndexRecords,
156163 CreateIndexInfo,
 164 CreateTempIndexInfo,
157165 DeleteIndexInfo,
 166 RemoveIndexInfo,
158167 HasIndexRecord,
159168 PutIndexRecord,
 169 PutTempIndexRecord,
160170 GetIndexRecordForOneKey,
161171 DeleteIndexRecords,
162172 RenameIndex,

Source/WebCore/Modules/indexeddb/shared/IDBIndexInfo.h

@@public:
5959 // FIXME: Remove the need for this.
6060 static const int64_t InvalidId = -1;
6161
 62 void setIdentifier(uint64_t identifier) { m_identifier = identifier; }
6263private:
6364 uint64_t m_identifier { 0 };
6465 uint64_t m_objectStoreIdentifier { 0 };

Source/WebCore/Modules/indexeddb/shared/IDBObjectStoreInfo.cpp

@@IDBIndexInfo IDBObjectStoreInfo::createNewIndex(uint64_t indexID, const String&
5252
5353void IDBObjectStoreInfo::addExistingIndex(const IDBIndexInfo& info)
5454{
55  ASSERT(!m_indexMap.contains(info.identifier()));
 55 if (m_indexMap.contains(info.identifier()))
 56 LOG_ERROR("Adding an index '%s' with existing Index ID", info.name().utf8().data());
5657
5758 m_indexMap.set(info.identifier(), info);
5859}

Tools/ChangeLog

 12020-09-28 Sihui Liu <sihui_liu@apple.com>
 2
 3 IndexedDB Index Corruption after upgrade from iOS 13 to iOS 14
 4 https://bugs.webkit.org/show_bug.cgi?id=216962
 5 <rdar://problem/69587004>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
 10 * TestWebKitAPI/Tests/WebKitCocoa/IDBIndexUpgradeToV2.mm:
 11 (runMultipleIndicesTestWithDatabase):
 12 (TEST):
 13 * TestWebKitAPI/Tests/WebKitCocoa/IDBIndexUpgradeToV2WithMultipleIndices.html: Added.
 14 * TestWebKitAPI/Tests/WebKitCocoa/IndexUpgradeWithMultipleIndices.sqlite3: Added.
 15 * TestWebKitAPI/Tests/WebKitCocoa/IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3: Added.
 16
1172020-09-27 Darin Adler <darin@apple.com>
218
319 Remove run-webkit-tests code that strips trailing spaces when comparing expected.txt files

Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj

810810 9399BA0423711140008392BF /* IndexedDBInPageCache.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9399BA01237110AE008392BF /* IndexedDBInPageCache.mm */; };
811811 9399BA052371114F008392BF /* IndexedDBInPageCache.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9399BA02237110BF008392BF /* IndexedDBInPageCache.html */; };
812812 9399BA062371114F008392BF /* IndexedDBNotInPageCache.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9399BA03237110BF008392BF /* IndexedDBNotInPageCache.html */; };
 813 93A2749D251EF90700A1B6D4 /* IDBIndexUpgradeToV2WithMultipleIndices.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 93A2749A251EF52A00A1B6D4 /* IDBIndexUpgradeToV2WithMultipleIndices.html */; };
 814 93A274A2252163EE00A1B6D4 /* IndexUpgradeWithMultipleIndices.sqlite3 in Copy Resources */ = {isa = PBXBuildFile; fileRef = 93A274A1252163D600A1B6D4 /* IndexUpgradeWithMultipleIndices.sqlite3 */; };
 815 93A274A5252241DE00A1B6D4 /* IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3 in Copy Resources */ = {isa = PBXBuildFile; fileRef = 93A274A4252241D000A1B6D4 /* IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3 */; };
813816 93AF4ECE1506F064007FD57E /* NewFirstVisuallyNonEmptyLayoutForImages_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93AF4ECD1506F064007FD57E /* NewFirstVisuallyNonEmptyLayoutForImages_Bundle.cpp */; };
814817 93AF4ED11506F130007FD57E /* lots-of-images.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 93AF4ECF1506F123007FD57E /* lots-of-images.html */; };
815818 93BCBC8323CC6F2A00CA2221 /* IDBObjectStoreInfoUpgradeToV2.mm in Sources */ = {isa = PBXBuildFile; fileRef = 93BCBC8023CC6EE800CA2221 /* IDBObjectStoreInfoUpgradeToV2.mm */; };

14211424 510477731D298DDD009747EB /* IDBDeleteRecovery.sqlite3-shm in Copy Resources */,
14221425 510477741D298DDD009747EB /* IDBDeleteRecovery.sqlite3-wal in Copy Resources */,
14231426 5110FCF11E01CD64006F8D0B /* IDBIndexUpgradeToV2.html in Copy Resources */,
 1427 93A2749D251EF90700A1B6D4 /* IDBIndexUpgradeToV2WithMultipleIndices.html in Copy Resources */,
14241428 937B569E23CD23DB002AE640 /* IDBObjectStoreInfoUpgrade.sqlite3 in Copy Resources */,
14251429 93BCBC8423CC6F4400CA2221 /* IDBObjectStoreInfoUpgradeToV2.html in Copy Resources */,
14261430 1CC80CEA2474F249004DC489 /* idempotent-mode-autosizing-only-honors-percentages.html in Copy Resources */,

14581462 CA97B3952193667A0045DF6F /* IndexedDBUserDelete.html in Copy Resources */,
14591463 5110FCF91E01CD8A006F8D0B /* IndexUpgrade.blob in Copy Resources */,
14601464 5110FCF61E01CD83006F8D0B /* IndexUpgrade.sqlite3 in Copy Resources */,
 1465 93A274A2252163EE00A1B6D4 /* IndexUpgradeWithMultipleIndices.sqlite3 in Copy Resources */,
 1466 93A274A5252241DE00A1B6D4 /* IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3 in Copy Resources */,
14611467 2EFF06CD1D8A429A0004BB30 /* input-field-in-scrollable-document.html in Copy Resources */,
14621468 CE3524FA1B1443890028A7C5 /* input-focus-blur.html in Copy Resources */,
14631469 CE6D0EE32426B932002AD901 /* insert-text.html in Copy Resources */,

23942400 939BA91614103412001A01BD /* DeviceScaleFactorOnBack.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DeviceScaleFactorOnBack.mm; sourceTree = "<group>"; };
23952401 939BFE3918E5548900883275 /* StringTruncator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = StringTruncator.mm; sourceTree = "<group>"; };
23962402 93A258981F92FF15003E510C /* TextCodec.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TextCodec.cpp; sourceTree = "<group>"; };
 2403 93A2749A251EF52A00A1B6D4 /* IDBIndexUpgradeToV2WithMultipleIndices.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = IDBIndexUpgradeToV2WithMultipleIndices.html; sourceTree = "<group>"; };
 2404 93A274A1252163D600A1B6D4 /* IndexUpgradeWithMultipleIndices.sqlite3 */ = {isa = PBXFileReference; lastKnownFileType = file; path = IndexUpgradeWithMultipleIndices.sqlite3; sourceTree = "<group>"; };
 2405 93A274A4252241D000A1B6D4 /* IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3 */ = {isa = PBXFileReference; lastKnownFileType = file; path = IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3; sourceTree = "<group>"; };
23972406 93A427A8180D9B0700CD24D7 /* RefPtr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RefPtr.cpp; sourceTree = "<group>"; };
23982407 93A427AA180DA26400CD24D7 /* Ref.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Ref.cpp; sourceTree = "<group>"; };
23992408 93A427AC180DA60F00CD24D7 /* MoveOnly.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoveOnly.h; sourceTree = "<group>"; };

37373746 510477701D298D85009747EB /* IDBDeleteRecovery.sqlite3-shm */,
37383747 510477711D298D85009747EB /* IDBDeleteRecovery.sqlite3-wal */,
37393748 5110FCF01E01CD53006F8D0B /* IDBIndexUpgradeToV2.html */,
 3749 93A2749A251EF52A00A1B6D4 /* IDBIndexUpgradeToV2WithMultipleIndices.html */,
37403750 93BCBC8123CC6EF500CA2221 /* IDBObjectStoreInfoUpgrade.sqlite3 */,
37413751 93BCBC8223CC6EF500CA2221 /* IDBObjectStoreInfoUpgradeToV2.html */,
37423752 F41AB9991EF4692C0083FA08 /* image-and-contenteditable.html */,

37723782 CA97B393219366470045DF6F /* IndexedDBUserDelete.html */,
37733783 5110FCF21E01CD77006F8D0B /* IndexUpgrade.blob */,
37743784 5110FCF31E01CD77006F8D0B /* IndexUpgrade.sqlite3 */,
 3785 93A274A1252163D600A1B6D4 /* IndexUpgradeWithMultipleIndices.sqlite3 */,
 3786 93A274A4252241D000A1B6D4 /* IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3 */,
37753787 2EFF06CC1D8A42910004BB30 /* input-field-in-scrollable-document.html */,
37763788 937A6C8824357BF300C3A6B0 /* KillWebProcessWithOpenConnection-1.html */,
37773789 937A6C8924357BF300C3A6B0 /* KillWebProcessWithOpenConnection-2.html */,

Tools/TestWebKitAPI/Tests/WebKitCocoa/IDBIndexUpgradeToV2.mm

@@TEST(IndexedDB, IndexUpgradeToV2)
8383
8484 EXPECT_WK_STREQ(@"Object expected to be a blob: [object Blob]", [lastScriptMessage body]);
8585}
 86
 87static void runMultipleIndicesTestWithDatabase(NSString* databaseName)
 88{
 89 RetainPtr<IDBIndexUpgradeToV2MessageHandler> handler = adoptNS([[IDBIndexUpgradeToV2MessageHandler alloc] init]);
 90 RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
 91 [[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
 92
 93 [configuration.get().processPool _terminateNetworkProcess];
 94
 95 NSURL *url = [[NSBundle mainBundle] URLForResource:databaseName withExtension:@"sqlite3" subdirectory:@"TestWebKitAPI.resources"];
 96
 97 NSString *hash = WebCore::SQLiteFileSystem::computeHashForFileName("index-upgrade-test");
 98 NSString *originDirectory = @"~/Library/WebKit/com.apple.WebKit.TestWebKitAPI/WebsiteData/IndexedDB/v1/file__0/";
 99 NSString *databaseDirectory = [[originDirectory stringByAppendingString:hash] stringByExpandingTildeInPath];
 100 NSURL *targetURL = [NSURL fileURLWithPath:databaseDirectory];
 101 [[NSFileManager defaultManager] removeItemAtURL:targetURL error:nil];
 102 [[NSFileManager defaultManager] createDirectoryAtURL:targetURL withIntermediateDirectories:YES attributes:nil error:nil];
 103
 104 [[NSFileManager defaultManager] copyItemAtURL:url toURL:[targetURL URLByAppendingPathComponent:@"IndexedDB.sqlite3"] error:nil];
 105
 106 RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
 107
 108 NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"IDBIndexUpgradeToV2WithMultipleIndices" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
 109 [webView loadRequest:request];
 110
 111 TestWebKitAPI::Util::run(&receivedScriptMessage);
 112
 113 EXPECT_WK_STREQ(@"Get object: {\"name\":\"apple\",\"color\":\"red\"}", [lastScriptMessage body]);
 114}
 115
 116TEST(IndexedDB, IndexUpgradeToV2WithMultipleIndices)
 117{
 118 runMultipleIndicesTestWithDatabase(@"IndexUpgradeWithMultipleIndices");
 119}
 120
 121TEST(IndexedDB, IndexUpgradeToV2WithMultipleIndicesHaveSameID)
 122{
 123 runMultipleIndicesTestWithDatabase(@"IndexUpgradeWithMultipleIndicesHaveSameID");
 124}

Tools/TestWebKitAPI/Tests/WebKitCocoa/IDBIndexUpgradeToV2WithMultipleIndices.html

 1<script>
 2
 3var openRequest = indexedDB.open('index-upgrade-test');
 4openRequest.onupgradeneeded = function(event) {
 5 window.webkit.messageHandlers.testHandler.postMessage('Unexpected upgradeneeded');
 6 return;
 7
 8 // The code below was used to generate database file IndexUpgradeWithMultipleIndices.sqlite3.
 9 // It should not be reached during upgrade test because database file already exists.
 10 let os1 = db.createObjectStore('colors');
 11 os1.createIndex('name', 'name');
 12 let os2 = db.createObjectStore('fruits');
 13 os2.createIndex('color', 'color');
 14 os2.createIndex('name', 'name');
 15 os2.put({ name:'apple', color:'red' }, 1);
 16}
 17
 18openRequest.onsuccess = function(event) {
 19 let db = event.target.result;
 20 try {
 21 let transaction = db.transaction(['fruits']);
 22 let colorIndex = transaction.objectStore('fruits').index('color');
 23 let getRequest = colorIndex.get('red');
 24 getRequest.onsuccess = (event) => {
 25 window.webkit.messageHandlers.testHandler.postMessage("Get object: " + JSON.stringify(event.target.result));
 26 }
 27 getRequest.onerror = (event) => {
 28 window.webkit.messageHandlers.testHandler.postMessage('Unexpected get error');
 29 }
 30 } catch(e) {
 31 window.webkit.messageHandlers.testHandler.postMessage("Unexpected error - " + e);
 32 }
 33}
 34
 35openRequest.onerror = function(event) {
 36 window.webkit.messageHandlers.testHandler.postMessage("Unexpected database error- " + e);
 37}
 38
 39</script>
 40

Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexUpgradeWithMultipleIndices.sqlite3

Binary file, nothing to see here

Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexUpgradeWithMultipleIndicesHaveSameID.sqlite3

Binary file, nothing to see here