IDB: Some Mozilla cursor mutation tests fail
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 8 Feb 2014 00:30:50 +0000 (00:30 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 8 Feb 2014 00:30:50 +0000 (00:30 +0000)
<rdar://problem/16011680> and https://bugs.webkit.org/show_bug.cgi?id=128374

Reviewed by Sam Weinig.

Source/WebCore:

Tested by:
storage/indexeddb/mozilla/cursor-mutation-objectstore-only.html
storage/indexeddb/mozilla/cursor-mutation.html

The previous comment about LevelDB was wrong.
Apparently calling onSuccess() with a null SharedBuffer means the cursor reached the end.
Update to distinguish between an error and reaching the end:
* Modules/indexeddb/IDBCursorBackendOperations.cpp:
(WebCore::CursorAdvanceOperation::perform):
(WebCore::CursorIterationOperation::perform):

Add a new IDBKey type - Maximum - To be used in comparisons between keys.
This will allow the DatabaseProcess to operate on the range of all keys:
* Modules/indexeddb/IDBKey.cpp:
(WebCore::IDBKey::compare):
* Modules/indexeddb/IDBKey.h:

* Modules/indexeddb/IDBKeyData.cpp:
(WebCore::IDBKeyData::IDBKeyData):
(WebCore::IDBKeyData::maybeCreateIDBKey):
(WebCore::IDBKeyData::isolatedCopy):
(WebCore::IDBKeyData::encode):
(WebCore::IDBKeyData::decode):
(WebCore::IDBKeyData::compare):
(WebCore::IDBKeyData::loggingString):
* Modules/indexeddb/IDBKeyData.h:
(WebCore::IDBKeyData::minimum):
(WebCore::IDBKeyData::maximum):

* bindings/js/IDBBindingUtilities.cpp:
(WebCore::idbKeyToJSValue):

Remove an unused callback:
* Modules/indexeddb/IDBCallbacks.h:
* Modules/indexeddb/IDBRequest.h:

Source/WebKit2:

After a SQLite statement is prepared, stepping it will not pick up any subsequent changes
to the object store.

By keeping track of the current record we’re looking at in the object store and being told
that the object store changed its contents, we can reset the statement to pick up where it
left off but with the new object store contents.

* DatabaseProcess/IndexedDB/UniqueIDBDatabase.cpp:
(WebKit::UniqueIDBDatabase::putRecordInBackingStore): Tell the backing store to notify
  its cursors that the object store changed its records.
(WebKit::UniqueIDBDatabase::deleteRangeInBackingStore): Ditto.

* DatabaseProcess/IndexedDB/UniqueIDBDatabaseBackingStore.h:
* DatabaseProcess/IndexedDB/sqlite/UniqueIDBDatabaseBackingStoreSQLite.cpp:
(WebKit::UniqueIDBDatabaseBackingStoreSQLite::notifyCursorsOfChanges): Tell the transaction
  to notify its cursors that the object store changed its records.
* DatabaseProcess/IndexedDB/sqlite/UniqueIDBDatabaseBackingStoreSQLite.h:

* DatabaseProcess/IndexedDB/sqlite/SQLiteIDBTransaction.cpp:
(WebKit::SQLiteIDBTransaction::notifyCursorsOfChanges): Tell the relevant cursors that their
  object store changed its records.
* DatabaseProcess/IndexedDB/sqlite/SQLiteIDBTransaction.h:

* DatabaseProcess/IndexedDB/sqlite/SQLiteIDBCursor.cpp:
(WebKit::SQLiteIDBCursor::SQLiteIDBCursor):
(WebKit::getIndexStatement): All statements now have a lower and upper range. By default we bind
  IDBKey::Minimum to the lower range and IDBKey::Maximum to the upper range.
(WebKit::getObjectStoreStatement): Ditto.
(WebKit::SQLiteIDBCursor::establishStatement):
(WebKit::SQLiteIDBCursor::createSQLiteStatement):
(WebKit::SQLiteIDBCursor::objectStoreRecordsChanged): Set the flag indicating the statement
  needs to be reset and rebound.
(WebKit::SQLiteIDBCursor::resetAndRebindStatement):
(WebKit::SQLiteIDBCursor::bindArguments): Factored out to be shared between statement prepare()
  and statement reset().
(WebKit::SQLiteIDBCursor::advance):
(WebKit::SQLiteIDBCursor::advanceOnce): If the statement needs to be reset, do so before advancing it.
(WebKit::SQLiteIDBCursor::internalAdvanceOnce): In a few cases, tell advanceOnce that it needs to
  try again because it is on a missing record or is repeating a record.
* DatabaseProcess/IndexedDB/sqlite/SQLiteIDBCursor.h:
(WebKit::SQLiteIDBCursor::objectStoreID):

Make sure Maximum and Minimum keys are never sent across IPC, as that doesn’t make sense:
* Shared/WebCoreArgumentCoders.cpp:
(IPC::ArgumentCoder<IDBKeyData>::encode):
(IPC::ArgumentCoder<IDBKeyData>::decode):

LayoutTests:

* platform/mac-wk2/TestExpectations: Add two tests that now pass.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@163662 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
LayoutTests/ChangeLog
LayoutTests/platform/mac-wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/IDBCallbacks.h
Source/WebCore/Modules/indexeddb/IDBCursorBackendOperations.cpp
Source/WebCore/Modules/indexeddb/IDBKey.cpp
Source/WebCore/Modules/indexeddb/IDBKey.h
Source/WebCore/Modules/indexeddb/IDBKeyData.cpp
Source/WebCore/Modules/indexeddb/IDBKeyData.h
Source/WebCore/Modules/indexeddb/IDBRequest.h
Source/WebCore/bindings/js/IDBBindingUtilities.cpp
Source/WebKit2/ChangeLog
Source/WebKit2/DatabaseProcess/IndexedDB/UniqueIDBDatabase.cpp
Source/WebKit2/DatabaseProcess/IndexedDB/UniqueIDBDatabaseBackingStore.h
Source/WebKit2/DatabaseProcess/IndexedDB/sqlite/SQLiteIDBCursor.cpp
Source/WebKit2/DatabaseProcess/IndexedDB/sqlite/SQLiteIDBCursor.h
Source/WebKit2/DatabaseProcess/IndexedDB/sqlite/SQLiteIDBTransaction.cpp
Source/WebKit2/DatabaseProcess/IndexedDB/sqlite/SQLiteIDBTransaction.h
Source/WebKit2/DatabaseProcess/IndexedDB/sqlite/UniqueIDBDatabaseBackingStoreSQLite.cpp
Source/WebKit2/DatabaseProcess/IndexedDB/sqlite/UniqueIDBDatabaseBackingStoreSQLite.h
Source/WebKit2/Shared/WebCoreArgumentCoders.cpp

index 880185b..6340fbf 100644 (file)
@@ -1,3 +1,12 @@
+2014-02-07  Brady Eidson  <beidson@apple.com>
+
+        IDB: Some Mozilla cursor mutation tests fail
+        <rdar://problem/16011680> and https://bugs.webkit.org/show_bug.cgi?id=128374
+
+        Reviewed by Sam Weinig.
+
+        * platform/mac-wk2/TestExpectations: Add two tests that now pass.
+
 2014-02-07  Ryosuke Niwa  <rniwa@webkit.org>
 
         Revert r154384 and r154674 as they broke selection rect computations for text with ligatures.
index 605e95e..467577b 100644 (file)
@@ -475,6 +475,8 @@ storage/indexeddb/mozilla/add-twice-failure.html [ Pass ]
 storage/indexeddb/mozilla/autoincrement-indexes.html [ Pass ]
 storage/indexeddb/mozilla/clear.html [ Pass ]
 storage/indexeddb/mozilla/create-index-with-integer-keys.html [ Pass ]
+storage/indexeddb/mozilla/cursor-mutation-objectstore-only.html [ Pass ]
+storage/indexeddb/mozilla/cursor-mutation.html [ Pass ]
 
 
 ### END OF (5) Features that are not supported in WebKit1, so skipped in mac/TestExpectations then re-enabled here
index e4abcba..4ef0860 100644 (file)
@@ -1,3 +1,46 @@
+2014-02-07  Brady Eidson  <beidson@apple.com>
+
+        IDB: Some Mozilla cursor mutation tests fail
+        <rdar://problem/16011680> and https://bugs.webkit.org/show_bug.cgi?id=128374
+
+        Reviewed by Sam Weinig.
+
+        Tested by:
+        storage/indexeddb/mozilla/cursor-mutation-objectstore-only.html
+        storage/indexeddb/mozilla/cursor-mutation.html
+
+        The previous comment about LevelDB was wrong.
+        Apparently calling onSuccess() with a null SharedBuffer means the cursor reached the end.
+        Update to distinguish between an error and reaching the end:
+        * Modules/indexeddb/IDBCursorBackendOperations.cpp:
+        (WebCore::CursorAdvanceOperation::perform):
+        (WebCore::CursorIterationOperation::perform):
+        Add a new IDBKey type - Maximum - To be used in comparisons between keys.
+        This will allow the DatabaseProcess to operate on the range of all keys:
+        * Modules/indexeddb/IDBKey.cpp:
+        (WebCore::IDBKey::compare):
+        * Modules/indexeddb/IDBKey.h:
+
+        * Modules/indexeddb/IDBKeyData.cpp:
+        (WebCore::IDBKeyData::IDBKeyData):
+        (WebCore::IDBKeyData::maybeCreateIDBKey):
+        (WebCore::IDBKeyData::isolatedCopy):
+        (WebCore::IDBKeyData::encode):
+        (WebCore::IDBKeyData::decode):
+        (WebCore::IDBKeyData::compare):
+        (WebCore::IDBKeyData::loggingString):
+        * Modules/indexeddb/IDBKeyData.h:
+        (WebCore::IDBKeyData::minimum):
+        (WebCore::IDBKeyData::maximum):
+
+        * bindings/js/IDBBindingUtilities.cpp:
+        (WebCore::idbKeyToJSValue):
+
+        Remove an unused callback:
+        * Modules/indexeddb/IDBCallbacks.h:
+        * Modules/indexeddb/IDBRequest.h:
+
 2014-02-07  Roger Fong  <roger_fong@apple.com>
 
         CGContextGetUserSpaceToDeviceSpaceTransform returns the wrong value on Windows.
index 6957662..18b4738 100644 (file)
@@ -68,8 +68,6 @@ public:
     // From IDBCursor.advance()/continue()
     virtual void onSuccess(PassRefPtr<IDBKey>, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer>) = 0;
 
-    // From IDBCursor.advance()/continue()
-    virtual void onSuccessWithPrefetch(const Vector<RefPtr<IDBKey>>& keys, const Vector<RefPtr<IDBKey>>& primaryKeys, const Vector<RefPtr<SharedBuffer>>& values) = 0;
     // From IDBFactory.open()/deleteDatabase()
     virtual void onBlocked(uint64_t /* existingVersion */) { ASSERT_NOT_REACHED(); }
     // From IDBFactory.open()
index fb16fc6..bd6cc2b 100644 (file)
@@ -43,8 +43,10 @@ void CursorAdvanceOperation::perform(std::function<void()> completionCallback)
     auto callback = [this, operation, completionCallback](PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> valueBuffer, PassRefPtr<IDBDatabaseError> error) {
         if (error) {
             m_cursor->clear();
-            // FIXME: The LevelDB backend calls onSuccess even on failure.
-            // This will probably have to change soon (for sanity) and will probably break LevelDB
+            m_callbacks->onError(error);
+        } else if (!key) {
+            // If there's no error but also no key, then the cursor reached the end.
+            m_cursor->clear();
             m_callbacks->onSuccess(static_cast<SharedBuffer*>(0));
         } else {
             m_cursor->updateCursorData(key.get(), primaryKey.get(), valueBuffer.get());
@@ -66,8 +68,10 @@ void CursorIterationOperation::perform(std::function<void()> completionCallback)
     auto callback = [this, operation, completionCallback](PassRefPtr<IDBKey> key, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer> valueBuffer, PassRefPtr<IDBDatabaseError> error) {
         if (error) {
             m_cursor->clear();
-            // FIXME: The LevelDB backend calls onSuccess even on failure.
-            // This will probably have to change soon (for sanity) and will probably break LevelDB
+            m_callbacks->onError(error);
+        } else if (!key) {
+            // If there's no error but also no key, then the cursor reached the end.
+            m_cursor->clear();
             m_callbacks->onSuccess(static_cast<SharedBuffer*>(0));
         } else {
             m_cursor->updateCursorData(key.get(), primaryKey.get(), valueBuffer.get());
index 3d2f92c..b563176 100644 (file)
@@ -76,6 +76,7 @@ int IDBKey::compare(const IDBKey* other) const
                 (m_number > other-> m_number) ? 1 : 0;
     case InvalidType:
     case MinType:
+    case MaxType:
         ASSERT_NOT_REACHED();
         return 0;
     }
index f42f258..5b80f9a 100644 (file)
@@ -98,6 +98,7 @@ public:
 
     // In order of the least to the highest precedent in terms of sort order.
     enum Type {
+        MaxType = -1,
         InvalidType = 0,
         ArrayType,
         StringType,
index 1971144..2a9fdc9 100644 (file)
@@ -61,8 +61,8 @@ IDBKeyData::IDBKeyData(const IDBKey* key)
     case IDBKey::NumberType:
         numberValue = key->number();
         break;
+    case IDBKey::MaxType:
     case IDBKey::MinType:
-        ASSERT_NOT_REACHED();
         break;
     }
 }
@@ -90,6 +90,7 @@ PassRefPtr<IDBKey> IDBKeyData::maybeCreateIDBKey() const
         return IDBKey::createDate(numberValue);
     case IDBKey::NumberType:
         return IDBKey::createNumber(numberValue);
+    case IDBKey::MaxType:
     case IDBKey::MinType:
         ASSERT_NOT_REACHED();
         return nullptr;
@@ -119,8 +120,8 @@ IDBKeyData IDBKeyData::isolatedCopy() const
     case IDBKey::NumberType:
         result.numberValue = numberValue;
         return result;
+    case IDBKey::MaxType:
     case IDBKey::MinType:
-        ASSERT_NOT_REACHED();
         return result;
     }
 
@@ -151,8 +152,8 @@ void IDBKeyData::encode(KeyedEncoder& encoder) const
     case IDBKey::NumberType:
         encoder.encodeDouble("number", numberValue);
         return;
+    case IDBKey::MaxType:
     case IDBKey::MinType:
-        ASSERT_NOT_REACHED();
         return;
     }
 
@@ -168,7 +169,8 @@ bool IDBKeyData::decode(KeyedDecoder& decoder, IDBKeyData& result)
         return true;
 
     auto enumFunction = [](int64_t value) {
-        return value == IDBKey::InvalidType
+        return value == IDBKey::MaxType
+            || value == IDBKey::InvalidType
             || value == IDBKey::ArrayType
             || value == IDBKey::StringType
             || value == IDBKey::DateType
@@ -181,10 +183,11 @@ bool IDBKeyData::decode(KeyedDecoder& decoder, IDBKeyData& result)
     if (result.type == IDBKey::InvalidType)
         return true;
 
-    if (result.type == IDBKey::MinType) {
-        ASSERT_NOT_REACHED();
+    if (result.type == IDBKey::MaxType)
+        return true;
+
+    if (result.type == IDBKey::MinType)
         return true;
-    }
 
     if (result.type == IDBKey::StringType)
         return decoder.decodeString("string", result.stringValue);
@@ -237,8 +240,8 @@ int IDBKeyData::compare(const IDBKeyData& other) const
         if (numberValue == other.numberValue)
             return 0;
         return numberValue > other.numberValue ? 1 : -1;
+    case IDBKey::MaxType:
     case IDBKey::MinType:
-        ASSERT_NOT_REACHED();
         return 0;
     }
 
@@ -272,6 +275,8 @@ String IDBKeyData::loggingString() const
         return String::format("Date type - %f", numberValue);
     case IDBKey::NumberType:
         return String::format("<number> - %f", numberValue);
+    case IDBKey::MaxType:
+        return "<maximum>";
     case IDBKey::MinType:
         return "<minimum>";
     default:
index c5961d7..7014b15 100644 (file)
@@ -45,6 +45,22 @@ struct IDBKeyData {
 
     IDBKeyData(const IDBKey*);
 
+    static IDBKeyData minimum()
+    {
+        IDBKeyData result;
+        result.type = IDBKey::MinType;
+        result.isNull = false;
+        return result;
+    }
+
+    static IDBKeyData maximum()
+    {
+        IDBKeyData result;
+        result.type = IDBKey::MaxType;
+        result.isNull = false;
+        return result;
+    }
+
     PassRefPtr<IDBKey> maybeCreateIDBKey() const;
 
     IDBKeyData isolatedCopy() const;
index 4427f19..3339e56 100644 (file)
@@ -93,7 +93,6 @@ public:
     virtual void onSuccess(int64_t);
     virtual void onSuccess();
     virtual void onSuccess(PassRefPtr<IDBKey>, PassRefPtr<IDBKey> primaryKey, PassRefPtr<SharedBuffer>);
-    virtual void onSuccessWithPrefetch(const Vector<RefPtr<IDBKey>>&, const Vector<RefPtr<IDBKey>>&, const Vector<RefPtr<SharedBuffer>>&) { ASSERT_NOT_REACHED(); } // Not implemented. Callback should not reach the renderer side.
 
     // ActiveDOMObject
     virtual bool hasPendingActivity() const override;
index 8793029..3d76be4 100644 (file)
@@ -101,6 +101,7 @@ static JSValue idbKeyToJSValue(ExecState* exec, JSDOMGlobalObject* globalObject,
     case IDBKey::NumberType:
         return jsNumber(key->number());
     case IDBKey::MinType:
+    case IDBKey::MaxType:
     case IDBKey::InvalidType:
         ASSERT_NOT_REACHED();
         return jsUndefined();
index 0251e8d..943ba4e 100644 (file)
@@ -1,3 +1,57 @@
+2014-02-07  Brady Eidson  <beidson@apple.com>
+
+        IDB: Some Mozilla cursor mutation tests fail
+        <rdar://problem/16011680> and https://bugs.webkit.org/show_bug.cgi?id=128374
+
+        Reviewed by Sam Weinig.
+
+        After a SQLite statement is prepared, stepping it will not pick up any subsequent changes
+        to the object store.
+
+        By keeping track of the current record we’re looking at in the object store and being told
+        that the object store changed its contents, we can reset the statement to pick up where it
+        left off but with the new object store contents.
+
+        * DatabaseProcess/IndexedDB/UniqueIDBDatabase.cpp:
+        (WebKit::UniqueIDBDatabase::putRecordInBackingStore): Tell the backing store to notify
+          its cursors that the object store changed its records.
+        (WebKit::UniqueIDBDatabase::deleteRangeInBackingStore): Ditto.
+
+        * DatabaseProcess/IndexedDB/UniqueIDBDatabaseBackingStore.h:
+        * DatabaseProcess/IndexedDB/sqlite/UniqueIDBDatabaseBackingStoreSQLite.cpp:
+        (WebKit::UniqueIDBDatabaseBackingStoreSQLite::notifyCursorsOfChanges): Tell the transaction
+          to notify its cursors that the object store changed its records.
+        * DatabaseProcess/IndexedDB/sqlite/UniqueIDBDatabaseBackingStoreSQLite.h:
+
+        * DatabaseProcess/IndexedDB/sqlite/SQLiteIDBTransaction.cpp:
+        (WebKit::SQLiteIDBTransaction::notifyCursorsOfChanges): Tell the relevant cursors that their
+          object store changed its records.
+        * DatabaseProcess/IndexedDB/sqlite/SQLiteIDBTransaction.h:
+
+        * DatabaseProcess/IndexedDB/sqlite/SQLiteIDBCursor.cpp:
+        (WebKit::SQLiteIDBCursor::SQLiteIDBCursor):
+        (WebKit::getIndexStatement): All statements now have a lower and upper range. By default we bind
+          IDBKey::Minimum to the lower range and IDBKey::Maximum to the upper range.
+        (WebKit::getObjectStoreStatement): Ditto.
+        (WebKit::SQLiteIDBCursor::establishStatement):
+        (WebKit::SQLiteIDBCursor::createSQLiteStatement):
+        (WebKit::SQLiteIDBCursor::objectStoreRecordsChanged): Set the flag indicating the statement
+          needs to be reset and rebound.
+        (WebKit::SQLiteIDBCursor::resetAndRebindStatement):
+        (WebKit::SQLiteIDBCursor::bindArguments): Factored out to be shared between statement prepare()
+          and statement reset().
+        (WebKit::SQLiteIDBCursor::advance):
+        (WebKit::SQLiteIDBCursor::advanceOnce): If the statement needs to be reset, do so before advancing it.
+        (WebKit::SQLiteIDBCursor::internalAdvanceOnce): In a few cases, tell advanceOnce that it needs to
+          try again because it is on a missing record or is repeating a record.
+        * DatabaseProcess/IndexedDB/sqlite/SQLiteIDBCursor.h:
+        (WebKit::SQLiteIDBCursor::objectStoreID):
+
+        Make sure Maximum and Minimum keys are never sent across IPC, as that doesn’t make sense:
+        * Shared/WebCoreArgumentCoders.cpp:
+        (IPC::ArgumentCoder<IDBKeyData>::encode):
+        (IPC::ArgumentCoder<IDBKeyData>::decode):
+
 2014-02-07  Anders Carlsson  <andersca@apple.com>
 
         Make it possible for each web page to have different preferences
index 68a6850..edd62ad 100644 (file)
@@ -866,6 +866,8 @@ void UniqueIDBDatabase::putRecordInBackingStore(uint64_t requestID, const IDBIde
         }
     }
 
+    m_backingStore->notifyCursorsOfChanges(transaction, objectStoreMetadata.id);
+
     if (putMode != IDBDatabaseBackend::CursorUpdate && objectStoreMetadata.autoIncrement && key.type == IDBKey::NumberType) {
         if (!m_backingStore->updateKeyGeneratorNumber(transaction, objectStoreMetadata.id, keyNumber, keyWasGenerated)) {
             postMainThreadTask(createAsyncTask(*this, &UniqueIDBDatabase::didPutRecordInBackingStore, requestID, IDBKeyData(), IDBDatabaseException::UnknownError, ASCIILiteral("Internal backing store error updating key generator")));
@@ -1049,6 +1051,8 @@ void UniqueIDBDatabase::deleteRangeInBackingStore(uint64_t requestID, const IDBI
         postMainThreadTask(createAsyncTask(*this, &UniqueIDBDatabase::didDeleteRangeInBackingStore, requestID, IDBDatabaseException::UnknownError, ASCIILiteral("Failed to get count from backing store")));
     }
 
+    m_backingStore->notifyCursorsOfChanges(transactionIdentifier, objectStoreID);
+
     postMainThreadTask(createAsyncTask(*this, &UniqueIDBDatabase::didDeleteRangeInBackingStore, requestID, 0, String(StringImpl::empty())));
 }
 
index c61104c..40b8a8d 100644 (file)
@@ -82,6 +82,7 @@ public:
     virtual bool openCursor(const IDBIdentifier& transactionIdentifier, int64_t objectStoreID, int64_t indexID, WebCore::IndexedDB::CursorDirection, WebCore::IndexedDB::CursorType, WebCore::IDBDatabaseBackend::TaskType, const WebCore::IDBKeyRangeData&, int64_t& cursorID, WebCore::IDBKeyData&, WebCore::IDBKeyData&, Vector<uint8_t>&) = 0;
     virtual bool advanceCursor(const IDBIdentifier& cursorIdentifier, uint64_t count, WebCore::IDBKeyData&, WebCore::IDBKeyData&, Vector<uint8_t>&) = 0;
     virtual bool iterateCursor(const IDBIdentifier& cursorIdentifier, const WebCore::IDBKeyData&, WebCore::IDBKeyData&, WebCore::IDBKeyData&, Vector<uint8_t>&) = 0;
+    virtual void notifyCursorsOfChanges(const IDBIdentifier& transactionIdentifier, int64_t objectStoreID) = 0;
 };
 
 } // namespace WebKit
index 6dcb837..c7a8a2e 100644 (file)
@@ -58,6 +58,9 @@ SQLiteIDBCursor::SQLiteIDBCursor(SQLiteIDBTransaction* transaction, const IDBIde
     , m_indexID(indexID)
     , m_cursorDirection(cursorDirection)
     , m_keyRange(keyRange)
+    , m_currentRecordID(-1)
+    , m_statementNeedsReset(false)
+    , m_boundID(0)
     , m_completed(false)
     , m_errored(false)
 {
@@ -69,46 +72,32 @@ static const String& getIndexStatement(bool hasLowerKey, bool isLowerOpen, bool
     DEFINE_STATIC_LOCAL(Vector<String>, indexStatements, ());
 
     if (indexStatements.isEmpty()) {
-        indexStatements.reserveCapacity(18);
-
-        // No lower key statements (6)
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key <= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key < CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
-
-        // Closed lower key statements (6)
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
-
-        // Open lower key statements (6)
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
+        indexStatements.reserveCapacity(8);
+
+        // Lower missing/open, upper missing/open.
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
+
+        // Lower missing/open, upper closed.
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
+
+        // Lower closed, upper missing/open.
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
+
+        // Lower closed, upper closed.
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
+        indexStatements.append(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
     }
 
     size_t i = 0;
 
-    if (hasLowerKey) {
-        i += 6;
-        if (isLowerOpen)
-            i += 6;
-    }
+    if (hasLowerKey && !isLowerOpen)
+        i += 4;
 
-    if (hasUpperKey) {
+    if (hasUpperKey && !isUpperOpen)
         i += 2;
-        if (isUpperOpen)
-            i += 2;
-    }
 
     if (descending)
         i += 1;
@@ -118,102 +107,133 @@ static const String& getIndexStatement(bool hasLowerKey, bool isLowerOpen, bool
 
 static const String& getObjectStoreStatement(bool hasLowerKey, bool isLowerOpen, bool hasUpperKey, bool isUpperOpen, bool descending)
 {
-    DEFINE_STATIC_LOCAL(Vector<String>, indexStatements, ());
+    DEFINE_STATIC_LOCAL(Vector<String>, statements, ());
 
-    if (indexStatements.isEmpty()) {
-        indexStatements.reserveCapacity(18);
-
-        // No lower key statements (6)
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key <= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key < CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
-
-        // Closed lower key statements (6)
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
-
-        // Open lower key statements (6)
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
-        indexStatements.append(ASCIILiteral("SELECT key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
+    if (statements.isEmpty()) {
+        statements.reserveCapacity(8);
+
+        // Lower missing/open, upper missing/open.
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
+
+        // Lower missing/open, upper closed.
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
+
+        // Lower closed, upper missing/open.
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
+
+        // Lower closed, upper closed.
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
+        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
     }
 
     size_t i = 0;
 
-    if (hasLowerKey) {
-        i += 6;
-        if (isLowerOpen)
-            i += 6;
-    }
+    if (hasLowerKey && !isLowerOpen)
+        i += 4;
 
-    if (hasUpperKey) {
+    if (hasUpperKey && !isUpperOpen)
         i += 2;
-        if (isUpperOpen)
-            i += 2;
-    }
 
     if (descending)
         i += 1;
 
-    return indexStatements[i];
+    return statements[i];
 }
 
 bool SQLiteIDBCursor::establishStatement()
 {
+    ASSERT(!m_statement);
     String sql;
-    int64_t id;
 
     if (m_indexID != IDBIndexMetadata::InvalidId) {
         sql = getIndexStatement(!m_keyRange.lowerKey.isNull, m_keyRange.lowerOpen, !m_keyRange.upperKey.isNull, m_keyRange.upperOpen, m_cursorDirection == IndexedDB::CursorDirection::Prev || m_cursorDirection == IndexedDB::CursorDirection::PrevNoDuplicate);
-        id = m_indexID;
+        m_boundID = m_indexID;
     } else {
         sql = getObjectStoreStatement(!m_keyRange.lowerKey.isNull, m_keyRange.lowerOpen, !m_keyRange.upperKey.isNull, m_keyRange.upperOpen, m_cursorDirection == IndexedDB::CursorDirection::Prev || m_cursorDirection == IndexedDB::CursorDirection::PrevNoDuplicate);
-        id = m_objectStoreID;
+        m_boundID = m_objectStoreID;
     }
 
-    return createSQLiteStatement(sql, id);
+    m_currentLowerKey = m_keyRange.lowerKey.isNull ? IDBKeyData::minimum() : m_keyRange.lowerKey;
+    m_currentUpperKey = m_keyRange.upperKey.isNull ? IDBKeyData::maximum() : m_keyRange.upperKey;
+
+    return createSQLiteStatement(sql);
 }
 
-bool SQLiteIDBCursor::createSQLiteStatement(const String& sql, int64_t idToBind)
+bool SQLiteIDBCursor::createSQLiteStatement(const String& sql)
 {
-    ASSERT(m_transaction->sqliteTransaction());
-    SQLiteDatabase& database = m_transaction->sqliteTransaction()->database();
-
     LOG(IDB, "Creating cursor with SQL query: \"%s\"", sql.utf8().data());
 
-    m_statement = std::make_unique<SQLiteStatement>(database, sql);
+    ASSERT(!m_currentLowerKey.isNull);
+    ASSERT(!m_currentUpperKey.isNull);
+    ASSERT(m_transaction->sqliteTransaction());
+
+    m_statement = std::make_unique<SQLiteStatement>(m_transaction->sqliteTransaction()->database(), sql);
 
-    if (m_statement->prepare() != SQLResultOk
-        || m_statement->bindInt64(1, idToBind) != SQLResultOk) {
-        LOG_ERROR("Could not create cursor statement");
+    if (m_statement->prepare() != SQLResultOk) {
+        LOG_ERROR("Could not create cursor statement (prepare/id) - '%s'", m_transaction->sqliteTransaction()->database().lastErrorMsg());
         return false;
     }
 
-    int nextBindArgument = 2;
+    return bindArguments();
+}
 
-    if (!m_keyRange.lowerKey.isNull) {
-        RefPtr<SharedBuffer> buffer = serializeIDBKeyData(m_keyRange.lowerKey);
-        if (m_statement->bindBlob(nextBindArgument++, buffer->data(), buffer->size()) != SQLResultOk) {
-            LOG_ERROR("Could not create cursor statement");
-            return false;
-        }
+void SQLiteIDBCursor::objectStoreRecordsChanged()
+{
+    // If ObjectStore or Index contents changed, we need to reset the statement and bind new parameters to it.
+    // This is to pick up any changes that might exist.
+
+    m_statementNeedsReset = true;
+}
+
+void SQLiteIDBCursor::resetAndRebindStatement()
+{
+    ASSERT(!m_currentLowerKey.isNull);
+    ASSERT(!m_currentUpperKey.isNull);
+    ASSERT(m_transaction->sqliteTransaction());
+    ASSERT(m_statement);
+    ASSERT(m_statementNeedsReset);
+
+    m_statementNeedsReset = false;
+
+    // If this cursor never fetched any records, we don't need to reset the statement.
+    if (m_currentKey.isNull)
+        return;
+
+    // Otherwise update the lower key or upper key used for the cursor range.
+    // This is so the cursor can pick up where we left off.
+    if (m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::NextNoDuplicate)
+        m_currentLowerKey = m_currentKey;
+    else
+        m_currentUpperKey = m_currentKey;
+
+    if (m_statement->reset() != SQLResultOk) {
+        LOG_ERROR("Could not reset cursor statement to respond to object store changes");
+        return;
     }
-    if (!m_keyRange.upperKey.isNull) {
-        RefPtr<SharedBuffer> buffer = serializeIDBKeyData(m_keyRange.upperKey);
-        if (m_statement->bindBlob(nextBindArgument, buffer->data(), buffer->size()) != SQLResultOk) {
-            LOG_ERROR("Could not create cursor statement");
-            return false;
-        }
+
+    bindArguments();
+}
+
+bool SQLiteIDBCursor::bindArguments()
+{
+    if (m_statement->bindInt64(1, m_boundID) != SQLResultOk) {
+        LOG_ERROR("Could not bind id argument (bound ID)");
+        return false;
+    }
+
+    RefPtr<SharedBuffer> buffer = serializeIDBKeyData(m_currentLowerKey);
+    if (m_statement->bindBlob(2, buffer->data(), buffer->size()) != SQLResultOk) {
+        LOG_ERROR("Could not create cursor statement (lower key)");
+        return false;
+    }
+
+    buffer = serializeIDBKeyData(m_currentUpperKey);
+    if (m_statement->bindBlob(3, buffer->data(), buffer->size()) != SQLResultOk) {
+        LOG_ERROR("Could not create cursor statement (upper key)");
+        return false;
     }
 
     return true;
@@ -227,11 +247,10 @@ bool SQLiteIDBCursor::advance(uint64_t count)
         if (!isUnique) {
             if (!advanceOnce())
                 return false;
-            continue;
+        } else {
+            if (!advanceUnique())
+                return false;
         }
-
-        if (!advanceUnique())
-            return false;
     }
 
     return true;
@@ -253,15 +272,27 @@ bool SQLiteIDBCursor::advanceUnique()
     return false;
 }
 
-
 bool SQLiteIDBCursor::advanceOnce()
 {
+    if (m_statementNeedsReset)
+        resetAndRebindStatement();
+
+    AdvanceResult result;
+    do {
+        result = internalAdvanceOnce();
+    } while (result == AdvanceResult::ShouldAdvanceAgain);
+
+    return result == AdvanceResult::Success;
+}
+
+SQLiteIDBCursor::AdvanceResult SQLiteIDBCursor::internalAdvanceOnce()
+{
     ASSERT(m_transaction->sqliteTransaction());
     ASSERT(m_statement);
 
     if (m_completed) {
         LOG_ERROR("Attempt to advance a completed cursor");
-        return false;
+        return AdvanceResult::Failure;
     }
 
     int result = m_statement->step();
@@ -273,27 +304,37 @@ bool SQLiteIDBCursor::advanceOnce()
         m_currentPrimaryKey = IDBKeyData();
         m_currentValueBuffer.clear();
 
-        return true;
+        return AdvanceResult::Success;
     }
 
     if (result != SQLResultRow) {
         LOG_ERROR("Error advancing cursor - (%i) %s", result, m_transaction->sqliteTransaction()->database().lastErrorMsg());
         m_completed = true;
         m_errored = true;
-        return false;
+        return AdvanceResult::Failure;
     }
 
+    int64_t recordID = m_statement->getColumnInt64(0);
+
+    // If the recordID of the record just fetched is the same as the current record ID
+    // then this statement must have been re-prepared in response to an object store change.
+    // We don't want to re-use the current record so we'll move on to the next one.
+    if (recordID == m_currentRecordID)
+        return AdvanceResult::ShouldAdvanceAgain;
+
+    m_currentRecordID = recordID;
+
     Vector<uint8_t> keyData;
-    m_statement->getColumnBlobAsVector(0, keyData);
+    m_statement->getColumnBlobAsVector(1, keyData);
 
     if (!deserializeIDBKeyData(keyData.data(), keyData.size(), m_currentKey)) {
         LOG_ERROR("Unable to deserialize key data from database while advancing cursor");
         m_completed = true;
         m_errored = true;
-        return false;
+        return AdvanceResult::Failure;
     }
 
-    m_statement->getColumnBlobAsVector(1, keyData);
+    m_statement->getColumnBlobAsVector(2, keyData);
     m_currentValueBuffer = keyData;
 
     if (m_indexID != IDBIndexMetadata::InvalidId) {
@@ -301,25 +342,38 @@ bool SQLiteIDBCursor::advanceOnce()
             LOG_ERROR("Unable to deserialize value data from database while advancing index cursor");
             m_completed = true;
             m_errored = true;
-            return false;
+            return AdvanceResult::Failure;
         }
 
         SQLiteStatement objectStoreStatement(*m_statement->database(), "SELECT value FROM Records WHERE key = CAST(? AS TEXT) and objectStoreID = ?;");
 
         if (objectStoreStatement.prepare() != SQLResultOk
             || objectStoreStatement.bindBlob(1, m_currentValueBuffer.data(), m_currentValueBuffer.size()) != SQLResultOk
-            || objectStoreStatement.bindInt64(2, m_objectStoreID) != SQLResultOk
-            || objectStoreStatement.step() != SQLResultRow) {
-            LOG_ERROR("Could not create index cursor statement into object store records");
+            || objectStoreStatement.bindInt64(2, m_objectStoreID) != SQLResultOk) {
+            LOG_ERROR("Could not create index cursor statement into object store records (%i) '%s'", m_statement->database()->lastError(), m_statement->database()->lastErrorMsg());
             m_completed = true;
             m_errored = true;
-            return false;
+            return AdvanceResult::Failure;
         }
 
-        objectStoreStatement.getColumnBlobAsVector(0, m_currentValueBuffer);
+        int result = objectStoreStatement.step();
+
+        if (result == SQLResultRow)
+            objectStoreStatement.getColumnBlobAsVector(0, m_currentValueBuffer);
+        else if (result == SQLResultDone) {
+            // This indicates that the record we're trying to retrieve has been removed from the object store.
+            // Skip over it.
+            return AdvanceResult::ShouldAdvanceAgain;
+        } else {
+            LOG_ERROR("Could not step index cursor statement into object store records (%i) '%s'", m_statement->database()->lastError(), m_statement->database()->lastErrorMsg());
+            m_completed = true;
+            m_errored = true;
+            return AdvanceResult::Failure;
+
+        }
     }
 
-    return true;
+    return AdvanceResult::Success;
 }
 
 bool SQLiteIDBCursor::iterate(const WebCore::IDBKeyData& targetKey)
index 81b437f..c86b5d3 100644 (file)
@@ -56,6 +56,8 @@ public:
     const IDBIdentifier& identifier() const { return m_cursorIdentifier; }
     SQLiteIDBTransaction* transaction() const { return m_transaction; }
 
+    int64_t objectStoreID() const { return m_objectStoreID; }
+
     const WebCore::IDBKeyData& currentKey() const { return m_currentKey; }
     const WebCore::IDBKeyData& currentPrimaryKey() const { return m_currentPrimaryKey; }
     const Vector<uint8_t>& currentValueBuffer() const { return m_currentValueBuffer; }
@@ -65,12 +67,24 @@ public:
 
     bool didError() const { return m_errored; }
 
+    void objectStoreRecordsChanged();
+
 private:
     SQLiteIDBCursor(SQLiteIDBTransaction*, const IDBIdentifier& cursorIdentifier, int64_t objectStoreID, int64_t indexID, WebCore::IndexedDB::CursorDirection, WebCore::IndexedDB::CursorType, WebCore::IDBDatabaseBackend::TaskType, const WebCore::IDBKeyRangeData&);
 
     bool establishStatement();
-    bool createSQLiteStatement(const String& sql, int64_t idToBind);
+    bool createSQLiteStatement(const String& sql);
+    bool bindArguments();
+
+    void resetAndRebindStatement();
 
+    enum class AdvanceResult {
+        Success,
+        Failure,
+        ShouldAdvanceAgain
+    };
+
+    AdvanceResult internalAdvanceOnce();
     bool advanceOnce();
     bool advanceUnique();
 
@@ -81,11 +95,17 @@ private:
     WebCore::IndexedDB::CursorDirection m_cursorDirection;
     WebCore::IDBKeyRangeData m_keyRange;
 
+    WebCore::IDBKeyData m_currentLowerKey;
+    WebCore::IDBKeyData m_currentUpperKey;
+
+    int64_t m_currentRecordID;
     WebCore::IDBKeyData m_currentKey;
     WebCore::IDBKeyData m_currentPrimaryKey;
     Vector<uint8_t> m_currentValueBuffer;
 
     std::unique_ptr<WebCore::SQLiteStatement> m_statement;
+    bool m_statementNeedsReset;
+    int64_t m_boundID;
 
     bool m_completed;
     bool m_errored;
index 786dcd6..9292fc1 100644 (file)
@@ -123,6 +123,14 @@ void SQLiteIDBTransaction::closeCursor(SQLiteIDBCursor& cursor)
     m_cursors.remove(cursor.identifier());
 }
 
+void SQLiteIDBTransaction::notifyCursorsOfChanges(int64_t objectStoreID)
+{
+    for (auto& i : m_cursors) {
+        if (i.value->objectStoreID() == objectStoreID)
+            i.value->objectStoreRecordsChanged();
+    }
+}
+
 void SQLiteIDBTransaction::clearCursors()
 {
     for (auto& cursor : m_cursors.values())
index 6538443..3a91e04 100644 (file)
@@ -73,6 +73,7 @@ public:
     SQLiteIDBCursor* openCursor(int64_t objectStoreID, int64_t indexID, WebCore::IndexedDB::CursorDirection, WebCore::IndexedDB::CursorType, WebCore::IDBDatabaseBackend::TaskType, const WebCore::IDBKeyRangeData&);
 
     void closeCursor(SQLiteIDBCursor&);
+    void notifyCursorsOfChanges(int64_t objectStoreID);
 
     WebCore::IndexedDB::TransactionMode mode() const { return m_mode; }
     bool inProgress() const;
index 20e6da2..1d164e8 100644 (file)
@@ -1267,6 +1267,21 @@ bool UniqueIDBDatabaseBackingStoreSQLite::iterateCursor(const IDBIdentifier& cur
     return true;
 }
 
+void UniqueIDBDatabaseBackingStoreSQLite::notifyCursorsOfChanges(const IDBIdentifier& transactionIdentifier, int64_t objectStoreID)
+{
+    ASSERT(!isMainThread());
+    ASSERT(m_sqliteDB);
+    ASSERT(m_sqliteDB->isOpen());
+
+    SQLiteIDBTransaction* transaction = m_transactions.get(transactionIdentifier);
+    if (!transaction || !transaction->inProgress()) {
+        LOG_ERROR("Attempt to notify cursors of changes in database without an established, in-progress transaction");
+        return;
+    }
+
+    transaction->notifyCursorsOfChanges(objectStoreID);
+}
+
 int UniqueIDBDatabaseBackingStoreSQLite::idbKeyCollate(int aLength, const void* aBuffer, int bLength, const void* bBuffer)
 {
     IDBKeyData a, b;
index aecc106..94416da 100644 (file)
@@ -91,6 +91,7 @@ public:
     virtual bool openCursor(const IDBIdentifier& transactionIdentifier, int64_t objectStoreID, int64_t indexID, WebCore::IndexedDB::CursorDirection, WebCore::IndexedDB::CursorType, WebCore::IDBDatabaseBackend::TaskType, const WebCore::IDBKeyRangeData&, int64_t& cursorID, WebCore::IDBKeyData&, WebCore::IDBKeyData&, Vector<uint8_t>&) override;
     virtual bool advanceCursor(const IDBIdentifier& cursorIdentifier, uint64_t count, WebCore::IDBKeyData&, WebCore::IDBKeyData&, Vector<uint8_t>&) override;
     virtual bool iterateCursor(const IDBIdentifier& cursorIdentifier, const WebCore::IDBKeyData&, WebCore::IDBKeyData&, WebCore::IDBKeyData&, Vector<uint8_t>&) override;
+    virtual void notifyCursorsOfChanges(const IDBIdentifier& transactionIdentifier, int64_t objectStoreID) override;
 
     void unregisterCursor(SQLiteIDBCursor*);
 
index fd53b0c..5592cf0 100644 (file)
@@ -1724,7 +1724,10 @@ void ArgumentCoder<IDBKeyData>::encode(ArgumentEncoder& encoder, const IDBKeyDat
     case IDBKey::NumberType:
         encoder << keyData.numberValue;
         break;
+    case IDBKey::MaxType:
     case IDBKey::MinType:
+        // MaxType and MinType are only used for comparison to other keys.
+        // They should never be sent across the wire.
         ASSERT_NOT_REACHED();
         break;
     }
@@ -1757,7 +1760,10 @@ bool ArgumentCoder<IDBKeyData>::decode(ArgumentDecoder& decoder, IDBKeyData& key
         if (!decoder.decode(keyData.numberValue))
             return false;
         break;
+    case IDBKey::MaxType:
     case IDBKey::MinType:
+        // MaxType and MinType are only used for comparison to other keys.
+        // They should never be sent across the wire.
         ASSERT_NOT_REACHED();
         return false;
     }