Changeset 256414 in webkit
- Timestamp:
- Feb 11, 2020 5:05:47 PM (4 years ago)
- Location:
- trunk
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r256398 r256414 1 2020-02-11 Sihui Liu <sihui_liu@apple.com> 2 3 IndexedDB: iteration of cursors skip records if deleted 4 https://bugs.webkit.org/show_bug.cgi?id=207437 5 6 Reviewed by Brady Eidson. 7 8 * storage/indexeddb/cursor-update-while-iterating-expected.txt: 9 * storage/indexeddb/resources/cursor-update-while-iterating.js: 10 (populateObjectStore): 11 (onOpenSuccess.request.onsuccess): 12 (onOpenSuccess): 13 (prepareDatabase): Deleted. 14 1 15 2020-02-11 Jason Lawrence <lawrence.j@apple.com> 2 16 -
trunk/LayoutTests/storage/indexeddb/cursor-update-while-iterating-expected.txt
r237590 r256414 12 12 db = event.target.result 13 13 Deleted all object stores. 14 objectStore = db.createObjectStore('objectStore' , {autoIncrement: true})14 objectStore = db.createObjectStore('objectStore') 15 15 objectStore.createIndex('key', 'key', {unique: false}) 16 16 … … 41 41 Cursor continues 42 42 43 PASS Successfully iterated whole array with cursor updates. 43 PASS JSON.stringify(cursor.value) is "{\"key\":\"key3\",\"value\":\"value5\"}" 44 Delete last record 45 Cursor continues 46 47 PASS totalRecordCount is 6 44 48 PASS successfullyParsed is true 45 49 -
trunk/LayoutTests/storage/indexeddb/resources/cursor-update-while-iterating.js
r237590 r256414 13 13 { key: "key1", value: "value3" }, 14 14 { key: "key2", value: "value2" }, 15 { key: "key2", value: "value4" } 15 { key: "key2", value: "value4" }, 16 { key: "key3", value: "value5" }, 17 { key: "key3", value: "value6" }, 16 18 ]; 17 19 18 20 function populateObjectStore() { 19 for (let object of objectArray) 20 objectStore.add(object).onerror = unexpectedErrorCallback; 21 objectArray.forEach((object, i)=>{ 22 objectStore.add(object, i).onerror = unexpectedErrorCallback; 23 }); 21 24 } 22 25 … … 27 30 deleteAllObjectStores(db); 28 31 29 objectStore = evalAndLog("objectStore = db.createObjectStore('objectStore' , {autoIncrement: true})");32 objectStore = evalAndLog("objectStore = db.createObjectStore('objectStore')"); 30 33 evalAndLog("objectStore.createIndex('key', 'key', {unique: false})"); 31 34 … … 44 47 request = evalAndLog("index.openCursor()"); 45 48 46 var n= 0;49 totalRecordCount = 0; 47 50 request.onsuccess = function(event) { 48 51 cursor = event.target.result; 49 52 if (cursor) { 50 shouldBeEqualToString("JSON.stringify(cursor.value)", JSON.stringify(objectArray[ n++]));53 shouldBeEqualToString("JSON.stringify(cursor.value)", JSON.stringify(objectArray[totalRecordCount++])); 51 54 52 55 if (cursor.key == "key1") { … … 54 57 const {value} = cursor; 55 58 cursor.update(value); 56 } else { 59 } 60 if (cursor.key == "key2") { 57 61 debug("Delete cursor"); 58 62 cursor.delete(); 63 } 64 if (cursor.key == "key3") { 65 debug("Delete last record"); 66 objectStore.delete(6); 59 67 } 60 68 … … 62 70 cursor.continue(); 63 71 } else { 64 if (n != objectArray.length) 65 testFailed("Cursor didn't go through whole array."); 66 else 67 testPassed("Successfully iterated whole array with cursor updates."); 72 shouldBeEqualToNumber("totalRecordCount", objectArray.length - 1); 68 73 } 69 74 } -
trunk/Source/WebCore/ChangeLog
r256400 r256414 1 2020-02-11 Sihui Liu <sihui_liu@apple.com> 2 3 IndexedDB: iteration of cursors skip records if deleted 4 https://bugs.webkit.org/show_bug.cgi?id=207437 5 6 Reviewed by Brady Eidson. 7 8 When changes are made to records, cursors will dump cached records and set new key range for iteration. 9 The new range did not include key of current cursor record, which is wrong because two index records can have 10 the same key but different values. 11 r237590 tried fixing this issue by caching all the following records which have the same key as current record. 12 That is not quite right as there could be changes on the cached records, and the cached records were not 13 updated. 14 15 To correctly fix the issue, set the new key range to include key of current cursor record and exclude values 16 visited before. To not regress preformance, we complete this by making an extra statment and changing 17 IndexRecordsIndex index of table IndexRecords to cover value column. 18 19 Added test case in: storage/indexeddb/cursor-update-while-iterating.html 20 Index upgrade covered by existing API test: IndexedDB.IDBObjectStoreInfoUpgradeToV2 21 22 * Modules/indexeddb/server/SQLiteIDBBackingStore.cpp: 23 (WebCore::IDBServer::SQLiteIDBBackingStore::ensureValidIndexRecordsIndex): 24 * Modules/indexeddb/server/SQLiteIDBCursor.cpp: 25 (WebCore::IDBServer::buildPreIndexStatement): 26 (WebCore::IDBServer::SQLiteIDBCursor::objectStoreRecordsChanged): 27 (WebCore::IDBServer::SQLiteIDBCursor::resetAndRebindPreIndexStatementIfNecessary): 28 (WebCore::IDBServer::SQLiteIDBCursor::fetch): 29 (WebCore::IDBServer::SQLiteIDBCursor::fetchNextRecord): 30 (WebCore::IDBServer::SQLiteIDBCursor::internalFetchNextRecord): 31 * Modules/indexeddb/server/SQLiteIDBCursor.h: 32 (WebCore::IDBServer::SQLiteIDBCursor::isDirectionNext const): 33 1 34 2020-02-11 Ryan Haddad <ryanhaddad@apple.com> 2 35 -
trunk/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp
r255318 r256414 65 65 constexpr auto v2ObjectStoreInfoSchema = "CREATE TABLE ObjectStoreInfo (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)"_s; 66 66 constexpr auto v1IndexRecordsRecordIndexSchema = "CREATE INDEX IndexRecordsRecordIndex ON IndexRecords (objectStoreID, objectStoreRecordID)"_s; 67 constexpr auto v2IndexRecordsIndexSchema = "CREATE INDEX IndexRecordsIndex ON IndexRecords (key, value)"_s; 67 68 68 69 // Current version of the metadata schema being used in the metadata database. … … 515 516 // If there is no IndexRecordsIndex index at all, create it and then bail. 516 517 if (sqliteResult == SQLITE_DONE) { 517 if (!m_sqliteDB->executeCommand(v 1IndexRecordsIndexSchema())) {518 if (!m_sqliteDB->executeCommand(v2IndexRecordsIndexSchema)) { 518 519 LOG_ERROR("Could not create IndexRecordsIndex index in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg()); 519 520 return false; … … 534 535 535 536 // If the schema in the backing store is the current schema, we're done. 536 if (currentSchema == v 1IndexRecordsIndexSchema())537 if (currentSchema == v2IndexRecordsIndexSchema) 537 538 return true; 538 539 539 // There is currently no outdated schema for the IndexRecordsIndex, so any other existing schema means this database is invalid. 540 return false; 540 RELEASE_ASSERT(currentSchema == v1IndexRecordsIndexSchema()); 541 542 SQLiteTransaction transaction(*m_sqliteDB); 543 transaction.begin(); 544 545 if (!m_sqliteDB->executeCommand("DROP INDEX IndexRecordsIndex")) { 546 LOG_ERROR("Could not drop index IndexRecordsIndex in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg()); 547 return false; 548 } 549 550 if (!m_sqliteDB->executeCommand(v2IndexRecordsIndexSchema)) { 551 LOG_ERROR("Could not create IndexRecordsIndex index in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg()); 552 return false; 553 } 554 555 transaction.commit(); 556 557 return true; 541 558 } 542 559 -
trunk/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp
r255318 r256414 117 117 } 118 118 119 static String buildPreIndexStatement(bool isDirectionNext) 120 { 121 StringBuilder builder; 122 123 builder.appendLiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key = CAST(? AS TEXT) AND value "); 124 if (isDirectionNext) 125 builder.append('>'); 126 else 127 builder.append('<'); 128 129 builder.appendLiteral(" CAST(? AS TEXT) ORDER BY value"); 130 if (!isDirectionNext) 131 builder.appendLiteral(" DESC"); 132 133 builder.append(';'); 134 135 return builder.toString(); 136 } 137 119 138 static String buildIndexStatement(const IDBKeyRangeData& keyRange, IndexedDB::CursorDirection cursorDirection) 120 139 { … … 219 238 220 239 m_currentKeyForUniqueness = m_fetchedRecords.first().record.key; 221 222 if (m_cursorDirection != IndexedDB::CursorDirection::Nextunique && m_cursorDirection != IndexedDB::CursorDirection::Prevunique) { 223 if (!m_fetchedRecords.last().isTerminalRecord()) 224 fetch(ShouldFetchForSameKey::Yes); 225 226 while (m_fetchedRecords.last().record.key != m_fetchedRecords.first().record.key) { 227 ASSERT(m_fetchedRecordsSize >= m_fetchedRecords.last().record.size()); 228 m_fetchedRecordsSize -= m_fetchedRecords.last().record.size(); 229 m_fetchedRecords.removeLast(); 230 } 231 } else { 232 m_fetchedRecords.clear(); 233 m_fetchedRecordsSize = 0; 234 } 240 if (m_indexID != IDBIndexInfo::InvalidId) 241 m_currentIndexRecordValue = m_fetchedRecords.first().record.primaryKey; 235 242 236 243 // If ObjectStore or Index contents changed, we need to reset the statement and bind new parameters to it. 237 244 // This is to pick up any changes that might exist. 238 // We also need to throw away any fetched records as they may no longer be valid.239 240 245 m_statementNeedsReset = true; 241 246 242 if ( m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::Nextunique) {247 if (isDirectionNext()) { 243 248 m_currentLowerKey = m_currentKeyForUniqueness; 244 249 if (!m_keyRange.lowerOpen) { … … 255 260 } 256 261 } 262 263 // We also need to throw away any fetched records as they may no longer be valid. 264 m_fetchedRecords.clear(); 265 m_fetchedRecordsSize = 0; 257 266 } 258 267 … … 299 308 if (m_statement->bindBlob(currentBindArgument++, buffer->data(), buffer->size()) != SQLITE_OK) { 300 309 LOG_ERROR("Could not create cursor statement (upper key)"); 310 return false; 311 } 312 313 return true; 314 } 315 316 bool SQLiteIDBCursor::resetAndRebindPreIndexStatementIfNecessary() 317 { 318 if (m_indexID == IDBIndexInfo::InvalidId) 319 return true; 320 321 if (m_currentIndexRecordValue.isNull()) 322 return true; 323 324 auto& database = m_transaction->sqliteTransaction()->database(); 325 if (!m_preIndexStatement) { 326 m_preIndexStatement = makeUnique<SQLiteStatement>(database, buildPreIndexStatement(isDirectionNext())); 327 328 if (m_preIndexStatement->prepare() != SQLITE_OK) { 329 LOG_ERROR("Could not prepare pre statement - '%s'", database.lastErrorMsg()); 330 return false; 331 } 332 } 333 334 if (m_preIndexStatement->reset() != SQLITE_OK) { 335 LOG_ERROR("Could not reset pre statement - '%s'", database.lastErrorMsg()); 336 return false; 337 } 338 339 auto key = isDirectionNext() ? m_currentLowerKey : m_currentUpperKey; 340 int currentBindArgument = 1; 341 342 if (m_preIndexStatement->bindInt64(currentBindArgument++, m_boundID) != SQLITE_OK) { 343 LOG_ERROR("Could not bind id argument to pre statement (bound ID)"); 344 return false; 345 } 346 347 RefPtr<SharedBuffer> buffer = serializeIDBKeyData(key); 348 if (m_preIndexStatement->bindBlob(currentBindArgument++, buffer->data(), buffer->size()) != SQLITE_OK) { 349 LOG_ERROR("Could not bind id argument to pre statement (key)"); 350 return false; 351 } 352 353 buffer = serializeIDBKeyData(m_currentIndexRecordValue); 354 if (m_preIndexStatement->bindBlob(currentBindArgument++, buffer->data(), buffer->size()) != SQLITE_OK) { 355 LOG_ERROR("Could not bind id argument to pre statement (value)"); 301 356 return false; 302 357 } … … 375 430 } 376 431 377 bool SQLiteIDBCursor::fetch( ShouldFetchForSameKey shouldFetchForSameKey)432 bool SQLiteIDBCursor::fetch() 378 433 { 379 434 ASSERT(m_fetchedRecords.isEmpty() || !m_fetchedRecords.last().isTerminalRecord()); … … 381 436 m_fetchedRecords.append({ }); 382 437 383 bool isUnique = m_cursorDirection == IndexedDB::CursorDirection::Nextunique || m_cursorDirection == IndexedDB::CursorDirection::Prevunique || shouldFetchForSameKey == ShouldFetchForSameKey::Yes;438 bool isUnique = m_cursorDirection == IndexedDB::CursorDirection::Nextunique || m_cursorDirection == IndexedDB::CursorDirection::Prevunique; 384 439 if (!isUnique) { 385 440 bool fetchSucceeded = fetchNextRecord(m_fetchedRecords.last()); … … 398 453 return false; 399 454 400 if (shouldFetchForSameKey == ShouldFetchForSameKey::Yes) 401 m_fetchedRecords.append({ }); 455 m_fetchedRecordsSize -= m_fetchedRecords.last().record.size(); 402 456 } 403 457 … … 407 461 bool SQLiteIDBCursor::fetchNextRecord(SQLiteCursorRecord& record) 408 462 { 409 if (m_statementNeedsReset) 463 if (m_statementNeedsReset) { 464 resetAndRebindPreIndexStatementIfNecessary(); 410 465 resetAndRebindStatement(); 466 } 411 467 412 468 FetchResult result; … … 435 491 record.record.value = nullptr; 436 492 437 int result = m_statement->step(); 438 if (result == SQLITE_DONE) { 439 // When a cursor reaches its end, that is indicated by having undefined keys/values 440 record = { }; 441 record.completed = true; 442 443 return FetchResult::Success; 444 } 445 446 if (result != SQLITE_ROW) { 447 LOG_ERROR("Error advancing cursor - (%i) %s", result, m_transaction->sqliteTransaction()->database().lastErrorMsg()); 448 markAsErrored(record); 449 return FetchResult::Failure; 450 } 451 452 record.rowID = m_statement->getColumnInt64(0); 493 auto& database = m_transaction->sqliteTransaction()->database(); 494 SQLiteStatement* statement = nullptr; 495 496 int result; 497 if (m_preIndexStatement) { 498 ASSERT(m_indexID != IDBIndexInfo::InvalidId); 499 500 result = m_preIndexStatement->step(); 501 if (result == SQLITE_ROW) 502 statement = m_preIndexStatement.get(); 503 else if (result != SQLITE_DONE) 504 LOG_ERROR("Error advancing with pre statement - (%i) %s", result, database.lastErrorMsg()); 505 } 506 507 if (!statement) { 508 result = m_statement->step(); 509 if (result == SQLITE_DONE) { 510 record = { }; 511 record.completed = true; 512 return FetchResult::Success; 513 } 514 if (result != SQLITE_ROW) { 515 LOG_ERROR("Error advancing cursor - (%i) %s", result, database.lastErrorMsg()); 516 markAsErrored(record); 517 return FetchResult::Failure; 518 } 519 statement = m_statement.get(); 520 } 521 522 record.rowID = statement->getColumnInt64(0); 453 523 ASSERT(record.rowID); 454 524 455 525 Vector<uint8_t> keyData; 456 m_statement->getColumnBlobAsVector(1, keyData);526 statement->getColumnBlobAsVector(1, keyData); 457 527 458 528 if (!deserializeIDBKeyData(keyData.data(), keyData.size(), record.record.key)) { … … 462 532 } 463 533 464 m_statement->getColumnBlobAsVector(2, keyData);534 statement->getColumnBlobAsVector(2, keyData); 465 535 466 536 // The primaryKey of an ObjectStore cursor is the same as its key. … … 486 556 487 557 if (!m_cachedObjectStoreStatement || m_cachedObjectStoreStatement->reset() != SQLITE_OK) { 488 m_cachedObjectStoreStatement = makeUnique<SQLiteStatement>( m_statement->database(), "SELECT value FROM Records WHERE key = CAST(? AS TEXT) and objectStoreID = ?;");558 m_cachedObjectStoreStatement = makeUnique<SQLiteStatement>(database, "SELECT value FROM Records WHERE key = CAST(? AS TEXT) and objectStoreID = ?;"); 489 559 if (m_cachedObjectStoreStatement->prepare() != SQLITE_OK) 490 560 m_cachedObjectStoreStatement = nullptr; … … 494 564 || m_cachedObjectStoreStatement->bindBlob(1, keyData.data(), keyData.size()) != SQLITE_OK 495 565 || m_cachedObjectStoreStatement->bindInt64(2, m_objectStoreID) != SQLITE_OK) { 496 LOG_ERROR("Could not create index cursor statement into object store records (%i) '%s'", m_statement->database().lastError(), m_statement->database().lastErrorMsg());566 LOG_ERROR("Could not create index cursor statement into object store records (%i) '%s'", database.lastError(), database.lastErrorMsg()); 497 567 markAsErrored(record); 498 568 return FetchResult::Failure; … … 509 579 return FetchResult::ShouldFetchAgain; 510 580 } else { 511 LOG_ERROR("Could not step index cursor statement into object store records (%i) '%s'", m_statement->database().lastError(), m_statement->database().lastErrorMsg());581 LOG_ERROR("Could not step index cursor statement into object store records (%i) '%s'", database.lastError(), database.lastErrorMsg()); 512 582 markAsErrored(record); 513 583 return FetchResult::Failure; -
trunk/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.h
r254264 r256414 45 45 namespace IDBServer { 46 46 47 enum class ShouldFetchForSameKey : bool { No, Yes };48 49 47 class SQLiteIDBTransaction; 50 48 … … 87 85 bool bindArguments(); 88 86 87 bool resetAndRebindPreIndexStatementIfNecessary(); 89 88 void resetAndRebindStatement(); 90 89 … … 95 94 }; 96 95 97 bool fetch( ShouldFetchForSameKey = ShouldFetchForSameKey::No);96 bool fetch(); 98 97 99 98 struct SQLiteCursorRecord { … … 109 108 void markAsErrored(SQLiteCursorRecord&); 110 109 110 bool isDirectionNext() const { return m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::Nextunique; } 111 111 112 SQLiteIDBTransaction* m_transaction; 112 113 IDBResourceIdentifier m_cursorIdentifier; … … 119 120 IDBKeyData m_currentLowerKey; 120 121 IDBKeyData m_currentUpperKey; 122 IDBKeyData m_currentIndexRecordValue; 121 123 122 124 Deque<SQLiteCursorRecord> m_fetchedRecords; … … 124 126 IDBKeyData m_currentKeyForUniqueness; 125 127 128 std::unique_ptr<SQLiteStatement> m_preIndexStatement; 126 129 std::unique_ptr<SQLiteStatement> m_statement; 127 130 std::unique_ptr<SQLiteStatement> m_cachedObjectStoreStatement;
Note: See TracChangeset
for help on using the changeset viewer.