summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/indexeddb/server
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/server')
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBBackingStore.h106
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.cpp192
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.h92
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBConnectionToClientDelegate.h82
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBSerialization.cpp420
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBSerialization.h45
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBServer.cpp682
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IDBServer.h148
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IndexValueEntry.cpp232
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IndexValueEntry.h102
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IndexValueStore.cpp419
-rw-r--r--Source/WebCore/Modules/indexeddb/server/IndexValueStore.h118
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.cpp296
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.h107
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryCursor.cpp66
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryCursor.h58
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.cpp599
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.h101
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryIndex.cpp277
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryIndex.h112
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp228
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.h61
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.cpp522
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.h137
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.cpp356
-rw-r--r--Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.h74
-rw-r--r--Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp2601
-rw-r--r--Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.h202
-rw-r--r--Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp580
-rw-r--r--Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.h132
-rw-r--r--Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.cpp233
-rw-r--r--Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.h97
-rw-r--r--Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.cpp91
-rw-r--r--Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.h78
-rw-r--r--Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp1908
-rw-r--r--Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h281
-rw-r--r--Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.cpp236
-rw-r--r--Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.h102
-rw-r--r--Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.cpp375
-rw-r--r--Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.h105
40 files changed, 12653 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBBackingStore.h b/Source/WebCore/Modules/indexeddb/server/IDBBackingStore.h
new file mode 100644
index 000000000..9dde74c83
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBBackingStore.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBDatabaseInfo.h"
+#include "IDBError.h"
+
+namespace WebCore {
+
+class IDBCursorInfo;
+class IDBGetAllResult;
+class IDBGetResult;
+class IDBIndexInfo;
+class IDBKeyData;
+class IDBObjectStoreInfo;
+class IDBResourceIdentifier;
+class IDBTransactionInfo;
+class IDBValue;
+class ThreadSafeDataBuffer;
+
+enum class IDBGetRecordDataType;
+
+struct IDBGetAllRecordsData;
+struct IDBIterateCursorData;
+struct IDBKeyRangeData;
+
+namespace IndexedDB {
+enum class IndexRecordType;
+}
+
+namespace IDBServer {
+
+class IDBBackingStoreTemporaryFileHandler {
+public:
+ virtual ~IDBBackingStoreTemporaryFileHandler() { }
+ virtual void prepareForAccessToTemporaryFile(const String& path) = 0;
+ virtual void accessToTemporaryFileComplete(const String& path) = 0;
+};
+
+class IDBBackingStore {
+public:
+ virtual ~IDBBackingStore() { }
+
+ virtual IDBError getOrEstablishDatabaseInfo(IDBDatabaseInfo&) = 0;
+
+ virtual IDBError beginTransaction(const IDBTransactionInfo&) = 0;
+ virtual IDBError abortTransaction(const IDBResourceIdentifier& transactionIdentifier) = 0;
+ virtual IDBError commitTransaction(const IDBResourceIdentifier& transactionIdentifier) = 0;
+
+ virtual IDBError createObjectStore(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&) = 0;
+ virtual IDBError deleteObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) = 0;
+ virtual IDBError renameObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName) = 0;
+ virtual IDBError clearObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) = 0;
+ virtual IDBError createIndex(const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo&) = 0;
+ virtual IDBError deleteIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier) = 0;
+ virtual IDBError renameIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName) = 0;
+ virtual IDBError keyExistsInObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData&, bool& keyExists) = 0;
+ virtual IDBError deleteRange(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&) = 0;
+ virtual IDBError addRecord(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&, const IDBKeyData&, const IDBValue&) = 0;
+ virtual IDBError getRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&, IDBGetRecordDataType, IDBGetResult& outValue) = 0;
+ virtual IDBError getAllRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData&, IDBGetAllResult& outValue) = 0;
+ virtual IDBError getIndexRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, IndexedDB::IndexRecordType, const IDBKeyRangeData&, IDBGetResult& outValue) = 0;
+ virtual IDBError getCount(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData&, uint64_t& outCount) = 0;
+ virtual IDBError generateKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t& keyNumber) = 0;
+ virtual IDBError revertGeneratedKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t keyNumber) = 0;
+ virtual IDBError maybeUpdateKeyGeneratorNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, double newKeyNumber) = 0;
+ virtual IDBError openCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo&, IDBGetResult& outResult) = 0;
+ virtual IDBError iterateCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData&, IDBGetResult& outResult) = 0;
+ virtual bool prefetchCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier) = 0;
+
+ virtual IDBObjectStoreInfo* infoForObjectStore(uint64_t objectStoreIdentifier) = 0;
+ virtual void deleteBackingStore() = 0;
+
+ virtual bool supportsSimultaneousTransactions() = 0;
+ virtual bool isEphemeral() = 0;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.cpp b/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.cpp
new file mode 100644
index 000000000..55bea5424
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IDBConnectionToClient.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "UniqueIDBDatabaseConnection.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+Ref<IDBConnectionToClient> IDBConnectionToClient::create(IDBConnectionToClientDelegate& delegate)
+{
+ return adoptRef(*new IDBConnectionToClient(delegate));
+}
+
+IDBConnectionToClient::IDBConnectionToClient(IDBConnectionToClientDelegate& delegate)
+ : m_delegate(delegate)
+{
+}
+
+uint64_t IDBConnectionToClient::identifier() const
+{
+ return m_delegate->identifier();
+}
+
+void IDBConnectionToClient::didDeleteDatabase(const IDBResultData& result)
+{
+ m_delegate->didDeleteDatabase(result);
+}
+
+void IDBConnectionToClient::didOpenDatabase(const IDBResultData& result)
+{
+ m_delegate->didOpenDatabase(result);
+}
+
+void IDBConnectionToClient::didAbortTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
+{
+ m_delegate->didAbortTransaction(transactionIdentifier, error);
+}
+
+void IDBConnectionToClient::didCreateObjectStore(const IDBResultData& result)
+{
+ m_delegate->didCreateObjectStore(result);
+}
+
+void IDBConnectionToClient::didDeleteObjectStore(const IDBResultData& result)
+{
+ m_delegate->didDeleteObjectStore(result);
+}
+
+void IDBConnectionToClient::didRenameObjectStore(const IDBResultData& result)
+{
+ m_delegate->didRenameObjectStore(result);
+}
+
+void IDBConnectionToClient::didClearObjectStore(const IDBResultData& result)
+{
+ m_delegate->didClearObjectStore(result);
+}
+
+void IDBConnectionToClient::didCreateIndex(const IDBResultData& result)
+{
+ m_delegate->didCreateIndex(result);
+}
+
+void IDBConnectionToClient::didDeleteIndex(const IDBResultData& result)
+{
+ m_delegate->didDeleteIndex(result);
+}
+
+void IDBConnectionToClient::didRenameIndex(const IDBResultData& result)
+{
+ m_delegate->didRenameIndex(result);
+}
+
+void IDBConnectionToClient::didPutOrAdd(const IDBResultData& result)
+{
+ m_delegate->didPutOrAdd(result);
+}
+
+void IDBConnectionToClient::didGetRecord(const IDBResultData& result)
+{
+ m_delegate->didGetRecord(result);
+}
+
+void IDBConnectionToClient::didGetAllRecords(const IDBResultData& result)
+{
+ m_delegate->didGetAllRecords(result);
+}
+
+void IDBConnectionToClient::didGetCount(const IDBResultData& result)
+{
+ m_delegate->didGetCount(result);
+}
+
+void IDBConnectionToClient::didDeleteRecord(const IDBResultData& result)
+{
+ m_delegate->didDeleteRecord(result);
+}
+
+void IDBConnectionToClient::didOpenCursor(const IDBResultData& result)
+{
+ m_delegate->didOpenCursor(result);
+}
+
+void IDBConnectionToClient::didIterateCursor(const IDBResultData& result)
+{
+ m_delegate->didIterateCursor(result);
+}
+
+void IDBConnectionToClient::didCommitTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
+{
+ m_delegate->didCommitTransaction(transactionIdentifier, error);
+}
+
+void IDBConnectionToClient::fireVersionChangeEvent(UniqueIDBDatabaseConnection& connection, const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion)
+{
+ m_delegate->fireVersionChangeEvent(connection, requestIdentifier, requestedVersion);
+}
+
+void IDBConnectionToClient::didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
+{
+ m_delegate->didStartTransaction(transactionIdentifier, error);
+}
+
+void IDBConnectionToClient::didCloseFromServer(UniqueIDBDatabaseConnection& connection, const IDBError& error)
+{
+ m_delegate->didCloseFromServer(connection, error);
+}
+
+void IDBConnectionToClient::notifyOpenDBRequestBlocked(const IDBResourceIdentifier& requestIdentifier, uint64_t oldVersion, uint64_t newVersion)
+{
+ m_delegate->notifyOpenDBRequestBlocked(requestIdentifier, oldVersion, newVersion);
+}
+
+void IDBConnectionToClient::didGetAllDatabaseNames(uint64_t callbackID, const Vector<String>& databaseNames)
+{
+ m_delegate->didGetAllDatabaseNames(callbackID, databaseNames);
+}
+
+void IDBConnectionToClient::registerDatabaseConnection(UniqueIDBDatabaseConnection& connection)
+{
+ ASSERT(!m_databaseConnections.contains(&connection));
+ m_databaseConnections.add(&connection);
+}
+
+void IDBConnectionToClient::unregisterDatabaseConnection(UniqueIDBDatabaseConnection& connection)
+{
+ m_databaseConnections.remove(&connection);
+}
+
+void IDBConnectionToClient::connectionToClientClosed()
+{
+ auto databaseConnections = m_databaseConnections;
+
+ for (auto connection : databaseConnections) {
+ if (m_databaseConnections.contains(connection))
+ connection->connectionClosedFromClient();
+ }
+
+ m_databaseConnections.clear();
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.h b/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.h
new file mode 100644
index 000000000..642813dd0
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBConnectionToClientDelegate.h"
+#include <wtf/HashSet.h>
+#include <wtf/Ref.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class IDBError;
+class IDBResourceIdentifier;
+class IDBResultData;
+
+namespace IDBServer {
+
+class UniqueIDBDatabaseConnection;
+
+class IDBConnectionToClient : public RefCounted<IDBConnectionToClient> {
+public:
+ WEBCORE_EXPORT static Ref<IDBConnectionToClient> create(IDBConnectionToClientDelegate&);
+
+ uint64_t identifier() const;
+
+ void didDeleteDatabase(const IDBResultData&);
+ void didOpenDatabase(const IDBResultData&);
+ void didAbortTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&);
+ void didCommitTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&);
+ void didCreateObjectStore(const IDBResultData&);
+ void didDeleteObjectStore(const IDBResultData&);
+ void didRenameObjectStore(const IDBResultData&);
+ void didClearObjectStore(const IDBResultData&);
+ void didCreateIndex(const IDBResultData&);
+ void didDeleteIndex(const IDBResultData&);
+ void didRenameIndex(const IDBResultData&);
+ void didPutOrAdd(const IDBResultData&);
+ void didGetRecord(const IDBResultData&);
+ void didGetAllRecords(const IDBResultData&);
+ void didGetCount(const IDBResultData&);
+ void didDeleteRecord(const IDBResultData&);
+ void didOpenCursor(const IDBResultData&);
+ void didIterateCursor(const IDBResultData&);
+
+ void fireVersionChangeEvent(UniqueIDBDatabaseConnection&, const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion);
+ void didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&);
+ void didCloseFromServer(UniqueIDBDatabaseConnection&, const IDBError&);
+
+ void notifyOpenDBRequestBlocked(const IDBResourceIdentifier& requestIdentifier, uint64_t oldVersion, uint64_t newVersion);
+
+ void didGetAllDatabaseNames(uint64_t callbackID, const Vector<String>& databaseNames);
+
+ void registerDatabaseConnection(UniqueIDBDatabaseConnection&);
+ void unregisterDatabaseConnection(UniqueIDBDatabaseConnection&);
+ void connectionToClientClosed();
+
+private:
+ IDBConnectionToClient(IDBConnectionToClientDelegate&);
+
+ Ref<IDBConnectionToClientDelegate> m_delegate;
+ HashSet<UniqueIDBDatabaseConnection*> m_databaseConnections;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClientDelegate.h b/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClientDelegate.h
new file mode 100644
index 000000000..cc9465366
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBConnectionToClientDelegate.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class IDBError;
+class IDBResourceIdentifier;
+class IDBResultData;
+
+namespace IDBServer {
+
+class UniqueIDBDatabaseConnection;
+
+class IDBConnectionToClientDelegate {
+public:
+ virtual ~IDBConnectionToClientDelegate() { }
+
+ virtual uint64_t identifier() const = 0;
+
+ virtual void didDeleteDatabase(const IDBResultData&) = 0;
+ virtual void didOpenDatabase(const IDBResultData&) = 0;
+ virtual void didAbortTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&) = 0;
+ virtual void didCommitTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&) = 0;
+ virtual void didCreateObjectStore(const IDBResultData&) = 0;
+ virtual void didDeleteObjectStore(const IDBResultData&) = 0;
+ virtual void didRenameObjectStore(const IDBResultData&) = 0;
+ virtual void didClearObjectStore(const IDBResultData&) = 0;
+ virtual void didCreateIndex(const IDBResultData&) = 0;
+ virtual void didDeleteIndex(const IDBResultData&) = 0;
+ virtual void didRenameIndex(const IDBResultData&) = 0;
+ virtual void didPutOrAdd(const IDBResultData&) = 0;
+ virtual void didGetRecord(const IDBResultData&) = 0;
+ virtual void didGetAllRecords(const IDBResultData&) = 0;
+ virtual void didGetCount(const IDBResultData&) = 0;
+ virtual void didDeleteRecord(const IDBResultData&) = 0;
+ virtual void didOpenCursor(const IDBResultData&) = 0;
+ virtual void didIterateCursor(const IDBResultData&) = 0;
+
+ virtual void fireVersionChangeEvent(UniqueIDBDatabaseConnection&, const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion) = 0;
+ virtual void didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&) = 0;
+ virtual void didCloseFromServer(UniqueIDBDatabaseConnection&, const IDBError&) = 0;
+ virtual void notifyOpenDBRequestBlocked(const IDBResourceIdentifier& requestIdentifier, uint64_t oldVersion, uint64_t newVersion) = 0;
+
+ virtual void didGetAllDatabaseNames(uint64_t callbackID, const Vector<String>& databaseNames) = 0;
+
+ virtual void ref() = 0;
+ virtual void deref() = 0;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBSerialization.cpp b/Source/WebCore/Modules/indexeddb/server/IDBSerialization.cpp
new file mode 100644
index 000000000..4806d5cc5
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBSerialization.cpp
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2014, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "config.h"
+#include "IDBSerialization.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBKeyData.h"
+#include "IDBKeyPath.h"
+#include "KeyedCoding.h"
+
+#if USE(GLIB)
+#include <glib.h>
+#include <wtf/glib/GRefPtr.h>
+#endif
+
+namespace WebCore {
+
+enum class KeyPathType { Null, String, Array };
+
+RefPtr<SharedBuffer> serializeIDBKeyPath(const std::optional<IDBKeyPath>& keyPath)
+{
+ auto encoder = KeyedEncoder::encoder();
+
+ if (keyPath) {
+ auto visitor = WTF::makeVisitor([&](const String& string) {
+ encoder->encodeEnum("type", KeyPathType::String);
+ encoder->encodeString("string", string);
+ }, [&](const Vector<String>& vector) {
+ encoder->encodeEnum("type", KeyPathType::Array);
+ encoder->encodeObjects("array", vector.begin(), vector.end(), [](WebCore::KeyedEncoder& encoder, const String& string) {
+ encoder.encodeString("string", string);
+ });
+ });
+ WTF::visit(visitor, keyPath.value());
+ } else
+ encoder->encodeEnum("type", KeyPathType::Null);
+
+ return encoder->finishEncoding();
+}
+
+bool deserializeIDBKeyPath(const uint8_t* data, size_t size, std::optional<IDBKeyPath>& result)
+{
+ if (!data || !size)
+ return false;
+
+ auto decoder = KeyedDecoder::decoder(data, size);
+
+ KeyPathType type;
+ bool succeeded = decoder->decodeEnum("type", type, [](KeyPathType value) {
+ return value == KeyPathType::Null || value == KeyPathType::String || value == KeyPathType::Array;
+ });
+ if (!succeeded)
+ return false;
+
+ switch (type) {
+ case KeyPathType::Null:
+ break;
+ case KeyPathType::String: {
+ String string;
+ if (!decoder->decodeString("string", string))
+ return false;
+ result = IDBKeyPath(WTFMove(string));
+ break;
+ }
+ case KeyPathType::Array: {
+ Vector<String> vector;
+ succeeded = decoder->decodeObjects("array", vector, [](KeyedDecoder& decoder, String& result) {
+ return decoder.decodeString("string", result);
+ });
+ if (!succeeded)
+ return false;
+ result = IDBKeyPath(WTFMove(vector));
+ break;
+ }
+ }
+ return true;
+}
+
+static bool isLegacySerializedIDBKeyData(const uint8_t* data, size_t size)
+{
+#if USE(CF)
+ UNUSED_PARAM(size);
+
+ // This is the magic character that begins serialized PropertyLists, and tells us whether
+ // the key we're looking at is an old-style key.
+ static const uint8_t legacySerializedKeyVersion = 'b';
+ if (data[0] == legacySerializedKeyVersion)
+ return true;
+#elif USE(GLIB)
+ // KeyedEncoderGLib uses a GVariant dictionary, so check if the given data is a valid GVariant dictionary.
+ GRefPtr<GBytes> bytes = adoptGRef(g_bytes_new(data, size));
+ GRefPtr<GVariant> variant = g_variant_new_from_bytes(G_VARIANT_TYPE("a{sv}"), bytes.get(), FALSE);
+ return g_variant_is_normal_form(variant.get());
+#endif
+ return false;
+}
+
+
+/*
+The IDBKeyData serialization format is as follows:
+[1 byte version header][Key Buffer]
+
+The Key Buffer serialization format is as follows:
+[1 byte key type][Type specific data]
+
+Type specific serialization formats are as follows for each of the types:
+Min:
+[0 bytes]
+
+Number:
+[8 bytes representing a double encoded in little endian]
+
+Date:
+[8 bytes representing a double encoded in little endian]
+
+String:
+[4 bytes representing string "length" in little endian]["length" number of 2-byte pairs representing ECMAScript 16-bit code units]
+
+Binary:
+[8 bytes representing the "size" of the binary blob]["size" bytes]
+
+Array:
+[8 bytes representing the "length" of the key array]["length" individual Key Buffer entries]
+
+Max:
+[0 bytes]
+*/
+
+static const uint8_t SIDBKeyVersion = 0x00;
+enum class SIDBKeyType : uint8_t {
+ Min = 0x00,
+ Number = 0x20,
+ Date = 0x40,
+ String = 0x60,
+ Binary = 0x80,
+ Array = 0xA0,
+ Max = 0xFF,
+};
+
+static SIDBKeyType serializedTypeForKeyType(IndexedDB::KeyType type)
+{
+ switch (type) {
+ case IndexedDB::KeyType::Min:
+ return SIDBKeyType::Min;
+ case IndexedDB::KeyType::Number:
+ return SIDBKeyType::Number;
+ case IndexedDB::KeyType::Date:
+ return SIDBKeyType::Date;
+ case IndexedDB::KeyType::String:
+ return SIDBKeyType::String;
+ case IndexedDB::KeyType::Binary:
+ return SIDBKeyType::Binary;
+ case IndexedDB::KeyType::Array:
+ return SIDBKeyType::Array;
+ case IndexedDB::KeyType::Max:
+ return SIDBKeyType::Max;
+ case IndexedDB::KeyType::Invalid:
+ RELEASE_ASSERT_NOT_REACHED();
+ };
+
+ RELEASE_ASSERT_NOT_REACHED();
+}
+
+#if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN) || CPU(NEEDS_ALIGNED_ACCESS)
+template <typename T> static void writeLittleEndian(Vector<char>& buffer, T value)
+{
+ for (unsigned i = 0; i < sizeof(T); i++) {
+ buffer.append(value & 0xFF);
+ value >>= 8;
+ }
+}
+
+template <typename T> static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value)
+{
+ if (ptr > end - sizeof(value))
+ return false;
+
+ value = 0;
+ for (size_t i = 0; i < sizeof(T); i++)
+ value += ((T)*ptr++) << (i * 8);
+ return true;
+}
+#else
+template <typename T> static void writeLittleEndian(Vector<char>& buffer, T value)
+{
+ buffer.append(reinterpret_cast<uint8_t*>(&value), sizeof(value));
+}
+
+template <typename T> static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value)
+{
+ if (ptr > end - sizeof(value))
+ return false;
+
+ value = *reinterpret_cast<const T*>(ptr);
+ ptr += sizeof(T);
+
+ return true;
+}
+#endif
+
+static void writeDouble(Vector<char>& data, double d)
+{
+ writeLittleEndian(data, *reinterpret_cast<uint64_t*>(&d));
+}
+
+static bool readDouble(const uint8_t*& data, const uint8_t* end, double& d)
+{
+ return readLittleEndian(data, end, *reinterpret_cast<uint64_t*>(&d));
+}
+
+static void encodeKey(Vector<char>& data, const IDBKeyData& key)
+{
+ SIDBKeyType type = serializedTypeForKeyType(key.type());
+ data.append(static_cast<char>(type));
+
+ switch (type) {
+ case SIDBKeyType::Number:
+ writeDouble(data, key.number());
+ break;
+ case SIDBKeyType::Date:
+ writeDouble(data, key.date());
+ break;
+ case SIDBKeyType::String: {
+ auto string = key.string();
+ uint32_t length = string.length();
+ writeLittleEndian(data, length);
+
+ for (size_t i = 0; i < length; ++i)
+ writeLittleEndian(data, string[i]);
+
+ break;
+ }
+ case SIDBKeyType::Binary: {
+ auto& buffer = key.binary();
+ uint64_t size = buffer.size();
+ writeLittleEndian(data, size);
+
+ auto* bufferData = buffer.data();
+ ASSERT(bufferData || !size);
+ if (bufferData)
+ data.append(bufferData->data(), bufferData->size());
+
+ break;
+ }
+ case SIDBKeyType::Array: {
+ auto& array = key.array();
+ uint64_t size = array.size();
+ writeLittleEndian(data, size);
+ for (auto& key : array)
+ encodeKey(data, key);
+
+ break;
+ }
+ case SIDBKeyType::Min:
+ case SIDBKeyType::Max:
+ break;
+ }
+}
+
+RefPtr<SharedBuffer> serializeIDBKeyData(const IDBKeyData& key)
+{
+ Vector<char> data;
+ data.append(SIDBKeyVersion);
+
+ encodeKey(data, key);
+ return SharedBuffer::adoptVector(data);
+}
+
+static bool decodeKey(const uint8_t*& data, const uint8_t* end, IDBKeyData& result)
+{
+ if (!data || data >= end)
+ return false;
+
+ SIDBKeyType type = static_cast<SIDBKeyType>(data++[0]);
+ switch (type) {
+ case SIDBKeyType::Min:
+ result = IDBKeyData::minimum();
+ return true;
+ case SIDBKeyType::Max:
+ result = IDBKeyData::maximum();
+ return true;
+ case SIDBKeyType::Number: {
+ double d;
+ if (!readDouble(data, end, d))
+ return false;
+
+ result.setNumberValue(d);
+ return true;
+ }
+ case SIDBKeyType::Date: {
+ double d;
+ if (!readDouble(data, end, d))
+ return false;
+
+ result.setDateValue(d);
+ return true;
+ }
+ case SIDBKeyType::String: {
+ uint32_t length;
+ if (!readLittleEndian(data, end, length))
+ return false;
+
+ if (static_cast<uint64_t>(end - data) < length * 2)
+ return false;
+
+ Vector<UChar> buffer;
+ buffer.reserveInitialCapacity(length);
+ for (size_t i = 0; i < length; i++) {
+ uint16_t ch;
+ if (!readLittleEndian(data, end, ch))
+ return false;
+ buffer.uncheckedAppend(ch);
+ }
+
+ result.setStringValue(String::adopt(WTFMove(buffer)));
+
+ return true;
+ }
+ case SIDBKeyType::Binary: {
+ uint64_t size64;
+ if (!readLittleEndian(data, end, size64))
+ return false;
+
+ if (static_cast<uint64_t>(end - data) < size64)
+ return false;
+
+ if (size64 > std::numeric_limits<size_t>::max())
+ return false;
+
+ size_t size = static_cast<size_t>(size64);
+ Vector<uint8_t> dataVector;
+
+ dataVector.append(data, size);
+ data += size;
+
+ result.setBinaryValue(ThreadSafeDataBuffer::adoptVector(dataVector));
+ return true;
+ }
+ case SIDBKeyType::Array: {
+ uint64_t size64;
+ if (!readLittleEndian(data, end, size64))
+ return false;
+
+ if (size64 > std::numeric_limits<size_t>::max())
+ return false;
+
+ size_t size = static_cast<size_t>(size64);
+ Vector<IDBKeyData> array;
+ array.reserveInitialCapacity(size);
+
+ for (size_t i = 0; i < size; ++i) {
+ IDBKeyData keyData;
+ if (!decodeKey(data, end, keyData))
+ return false;
+
+ ASSERT(keyData.isValid());
+ array.uncheckedAppend(WTFMove(keyData));
+ }
+
+ result.setArrayValue(array);
+
+ return true;
+ }
+ default:
+ LOG_ERROR("decodeKey encountered unexpected type: %i", (int)type);
+ return false;
+ }
+}
+
+bool deserializeIDBKeyData(const uint8_t* data, size_t size, IDBKeyData& result)
+{
+ if (!data || !size)
+ return false;
+
+ if (isLegacySerializedIDBKeyData(data, size)) {
+ auto decoder = KeyedDecoder::decoder(data, size);
+ return IDBKeyData::decode(*decoder, result);
+ }
+
+ // Verify this is a SerializedIDBKey version we understand.
+ const uint8_t* current = data;
+ const uint8_t* end = data + size;
+ if (current++[0] != SIDBKeyVersion)
+ return false;
+
+ if (decodeKey(current, end, result)) {
+ // Even if we successfully decoded a key, the deserialize is only successful
+ // if we actually consumed all input data.
+ return current == end;
+ }
+
+ return false;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBSerialization.h b/Source/WebCore/Modules/indexeddb/server/IDBSerialization.h
new file mode 100644
index 000000000..094fbc6ec
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBSerialization.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBKeyPath.h"
+#include "SharedBuffer.h"
+
+namespace WebCore {
+
+class IDBKeyData;
+
+RefPtr<SharedBuffer> serializeIDBKeyPath(const std::optional<IDBKeyPath>&);
+bool deserializeIDBKeyPath(const uint8_t* buffer, size_t bufferSize, std::optional<IDBKeyPath>&);
+
+RefPtr<SharedBuffer> serializeIDBKeyData(const IDBKeyData&);
+bool deserializeIDBKeyData(const uint8_t* buffer, size_t bufferSize, IDBKeyData&);
+
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBServer.cpp b/Source/WebCore/Modules/indexeddb/server/IDBServer.cpp
new file mode 100644
index 000000000..87476438c
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBServer.cpp
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IDBServer.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBRequestData.h"
+#include "IDBResultData.h"
+#include "Logging.h"
+#include "MemoryIDBBackingStore.h"
+#include "SQLiteFileSystem.h"
+#include "SQLiteIDBBackingStore.h"
+#include "SecurityOrigin.h"
+#include <wtf/CrossThreadCopier.h>
+#include <wtf/Locker.h>
+#include <wtf/MainThread.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+Ref<IDBServer> IDBServer::create(IDBBackingStoreTemporaryFileHandler& fileHandler)
+{
+ return adoptRef(*new IDBServer(fileHandler));
+}
+
+Ref<IDBServer> IDBServer::create(const String& databaseDirectoryPath, IDBBackingStoreTemporaryFileHandler& fileHandler)
+{
+ return adoptRef(*new IDBServer(databaseDirectoryPath, fileHandler));
+}
+
+IDBServer::IDBServer(IDBBackingStoreTemporaryFileHandler& fileHandler)
+ : m_backingStoreTemporaryFileHandler(fileHandler)
+{
+ Locker<Lock> locker(m_databaseThreadCreationLock);
+ m_threadID = createThread(IDBServer::databaseThreadEntry, this, "IndexedDatabase Server");
+}
+
+IDBServer::IDBServer(const String& databaseDirectoryPath, IDBBackingStoreTemporaryFileHandler& fileHandler)
+ : m_databaseDirectoryPath(databaseDirectoryPath)
+ , m_backingStoreTemporaryFileHandler(fileHandler)
+{
+ LOG(IndexedDB, "IDBServer created at path %s", databaseDirectoryPath.utf8().data());
+
+ Locker<Lock> locker(m_databaseThreadCreationLock);
+ m_threadID = createThread(IDBServer::databaseThreadEntry, this, "IndexedDatabase Server");
+}
+
+void IDBServer::registerConnection(IDBConnectionToClient& connection)
+{
+ ASSERT(!m_connectionMap.contains(connection.identifier()));
+ m_connectionMap.set(connection.identifier(), &connection);
+}
+
+void IDBServer::unregisterConnection(IDBConnectionToClient& connection)
+{
+ ASSERT(m_connectionMap.contains(connection.identifier()));
+ ASSERT(m_connectionMap.get(connection.identifier()) == &connection);
+
+ connection.connectionToClientClosed();
+
+ m_connectionMap.remove(connection.identifier());
+}
+
+void IDBServer::registerTransaction(UniqueIDBDatabaseTransaction& transaction)
+{
+ ASSERT(!m_transactions.contains(transaction.info().identifier()));
+ m_transactions.set(transaction.info().identifier(), &transaction);
+}
+
+void IDBServer::unregisterTransaction(UniqueIDBDatabaseTransaction& transaction)
+{
+ ASSERT(m_transactions.contains(transaction.info().identifier()));
+ ASSERT(m_transactions.get(transaction.info().identifier()) == &transaction);
+
+ m_transactions.remove(transaction.info().identifier());
+}
+
+void IDBServer::registerDatabaseConnection(UniqueIDBDatabaseConnection& connection)
+{
+ ASSERT(!m_databaseConnections.contains(connection.identifier()));
+ m_databaseConnections.set(connection.identifier(), &connection);
+}
+
+void IDBServer::unregisterDatabaseConnection(UniqueIDBDatabaseConnection& connection)
+{
+ ASSERT(m_databaseConnections.contains(connection.identifier()));
+ m_databaseConnections.remove(connection.identifier());
+}
+
+UniqueIDBDatabase& IDBServer::getOrCreateUniqueIDBDatabase(const IDBDatabaseIdentifier& identifier)
+{
+ ASSERT(isMainThread());
+
+ auto uniqueIDBDatabase = m_uniqueIDBDatabaseMap.add(identifier, nullptr);
+ if (uniqueIDBDatabase.isNewEntry)
+ uniqueIDBDatabase.iterator->value = UniqueIDBDatabase::create(*this, identifier);
+
+ return *uniqueIDBDatabase.iterator->value;
+}
+
+std::unique_ptr<IDBBackingStore> IDBServer::createBackingStore(const IDBDatabaseIdentifier& identifier)
+{
+ ASSERT(!isMainThread());
+
+ if (m_databaseDirectoryPath.isEmpty())
+ return MemoryIDBBackingStore::create(identifier);
+
+ return std::make_unique<SQLiteIDBBackingStore>(identifier, m_databaseDirectoryPath, m_backingStoreTemporaryFileHandler);
+}
+
+void IDBServer::openDatabase(const IDBRequestData& requestData)
+{
+ LOG(IndexedDB, "IDBServer::openDatabase");
+
+ auto& uniqueIDBDatabase = getOrCreateUniqueIDBDatabase(requestData.databaseIdentifier());
+
+ auto connection = m_connectionMap.get(requestData.requestIdentifier().connectionIdentifier());
+ if (!connection) {
+ // If the connection back to the client is gone, there's no way to open the database as
+ // well as no way to message back failure.
+ return;
+ }
+
+ uniqueIDBDatabase.openDatabaseConnection(*connection, requestData);
+}
+
+void IDBServer::deleteDatabase(const IDBRequestData& requestData)
+{
+ LOG(IndexedDB, "IDBServer::deleteDatabase - %s", requestData.databaseIdentifier().debugString().utf8().data());
+ ASSERT(isMainThread());
+
+ auto connection = m_connectionMap.get(requestData.requestIdentifier().connectionIdentifier());
+ if (!connection) {
+ // If the connection back to the client is gone, there's no way to delete the database as
+ // well as no way to message back failure.
+ return;
+ }
+
+ auto* database = m_uniqueIDBDatabaseMap.get(requestData.databaseIdentifier());
+ if (!database)
+ database = &getOrCreateUniqueIDBDatabase(requestData.databaseIdentifier());
+
+ database->handleDelete(*connection, requestData);
+}
+
+void IDBServer::closeUniqueIDBDatabase(UniqueIDBDatabase& database)
+{
+ LOG(IndexedDB, "IDBServer::closeUniqueIDBDatabase");
+ ASSERT(isMainThread());
+
+ m_uniqueIDBDatabaseMap.remove(database.identifier());
+}
+
+void IDBServer::abortTransaction(const IDBResourceIdentifier& transactionIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::abortTransaction");
+
+ auto transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction) {
+ // If there is no transaction there is nothing to abort.
+ // We also have no access to a connection over which to message failure-to-abort.
+ return;
+ }
+
+ transaction->abort();
+}
+
+void IDBServer::createObjectStore(const IDBRequestData& requestData, const IDBObjectStoreInfo& info)
+{
+ LOG(IndexedDB, "IDBServer::createObjectStore");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ ASSERT(transaction->isVersionChange());
+ transaction->createObjectStore(requestData, info);
+}
+
+void IDBServer::deleteObjectStore(const IDBRequestData& requestData, const String& objectStoreName)
+{
+ LOG(IndexedDB, "IDBServer::deleteObjectStore");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ ASSERT(transaction->isVersionChange());
+ transaction->deleteObjectStore(requestData, objectStoreName);
+}
+
+void IDBServer::renameObjectStore(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "IDBServer::renameObjectStore");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ ASSERT(transaction->isVersionChange());
+ transaction->renameObjectStore(requestData, objectStoreIdentifier, newName);
+}
+
+void IDBServer::clearObjectStore(const IDBRequestData& requestData, uint64_t objectStoreIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::clearObjectStore");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->clearObjectStore(requestData, objectStoreIdentifier);
+}
+
+void IDBServer::createIndex(const IDBRequestData& requestData, const IDBIndexInfo& info)
+{
+ LOG(IndexedDB, "IDBServer::createIndex");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ ASSERT(transaction->isVersionChange());
+ transaction->createIndex(requestData, info);
+}
+
+void IDBServer::deleteIndex(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, const String& indexName)
+{
+ LOG(IndexedDB, "IDBServer::deleteIndex");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ ASSERT(transaction->isVersionChange());
+ transaction->deleteIndex(requestData, objectStoreIdentifier, indexName);
+}
+
+void IDBServer::renameIndex(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "IDBServer::renameIndex");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ ASSERT(transaction->isVersionChange());
+ transaction->renameIndex(requestData, objectStoreIdentifier, indexIdentifier, newName);
+}
+
+void IDBServer::putOrAdd(const IDBRequestData& requestData, const IDBKeyData& keyData, const IDBValue& value, IndexedDB::ObjectStoreOverwriteMode overwriteMode)
+{
+ LOG(IndexedDB, "IDBServer::putOrAdd");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->putOrAdd(requestData, keyData, value, overwriteMode);
+}
+
+void IDBServer::getRecord(const IDBRequestData& requestData, const IDBGetRecordData& getRecordData)
+{
+ LOG(IndexedDB, "IDBServer::getRecord");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->getRecord(requestData, getRecordData);
+}
+
+void IDBServer::getAllRecords(const IDBRequestData& requestData, const IDBGetAllRecordsData& getAllRecordsData)
+{
+ LOG(IndexedDB, "IDBServer::getAllRecords");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->getAllRecords(requestData, getAllRecordsData);
+}
+
+void IDBServer::getCount(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData)
+{
+ LOG(IndexedDB, "IDBServer::getCount");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->getCount(requestData, keyRangeData);
+}
+
+void IDBServer::deleteRecord(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData)
+{
+ LOG(IndexedDB, "IDBServer::deleteRecord");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->deleteRecord(requestData, keyRangeData);
+}
+
+void IDBServer::openCursor(const IDBRequestData& requestData, const IDBCursorInfo& info)
+{
+ LOG(IndexedDB, "IDBServer::openCursor");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->openCursor(requestData, info);
+}
+
+void IDBServer::iterateCursor(const IDBRequestData& requestData, const IDBIterateCursorData& data)
+{
+ LOG(IndexedDB, "IDBServer::iterateCursor");
+
+ auto transaction = m_transactions.get(requestData.transactionIdentifier());
+ if (!transaction)
+ return;
+
+ transaction->iterateCursor(requestData, data);
+}
+
+void IDBServer::establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo& info)
+{
+ LOG(IndexedDB, "IDBServer::establishTransaction");
+
+ auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
+ if (!databaseConnection)
+ return;
+
+ databaseConnection->establishTransaction(info);
+}
+
+void IDBServer::commitTransaction(const IDBResourceIdentifier& transactionIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::commitTransaction");
+
+ auto transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction) {
+ // If there is no transaction there is nothing to commit.
+ // We also have no access to a connection over which to message failure-to-commit.
+ return;
+ }
+
+ transaction->commit();
+}
+
+void IDBServer::didFinishHandlingVersionChangeTransaction(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& transactionIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::didFinishHandlingVersionChangeTransaction - %s", transactionIdentifier.loggingString().utf8().data());
+
+ auto* connection = m_databaseConnections.get(databaseConnectionIdentifier);
+ if (!connection)
+ return;
+
+ connection->didFinishHandlingVersionChange(transactionIdentifier);
+}
+
+void IDBServer::databaseConnectionPendingClose(uint64_t databaseConnectionIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::databaseConnectionPendingClose - %" PRIu64, databaseConnectionIdentifier);
+
+ auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
+ if (!databaseConnection)
+ return;
+
+ databaseConnection->connectionPendingCloseFromClient();
+}
+
+void IDBServer::databaseConnectionClosed(uint64_t databaseConnectionIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::databaseConnectionClosed - %" PRIu64, databaseConnectionIdentifier);
+
+ auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
+ if (!databaseConnection)
+ return;
+
+ databaseConnection->connectionClosedFromClient();
+}
+
+void IDBServer::abortOpenAndUpgradeNeeded(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& transactionIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::abortOpenAndUpgradeNeeded");
+
+ auto transaction = m_transactions.get(transactionIdentifier);
+ if (transaction)
+ transaction->abortWithoutCallback();
+
+ auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
+ if (!databaseConnection)
+ return;
+
+ databaseConnection->connectionClosedFromClient();
+}
+
+void IDBServer::didFireVersionChangeEvent(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& requestIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::didFireVersionChangeEvent");
+
+ if (auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier))
+ databaseConnection->didFireVersionChangeEvent(requestIdentifier);
+}
+
+void IDBServer::openDBRequestCancelled(const IDBRequestData& requestData)
+{
+ LOG(IndexedDB, "IDBServer::openDBRequestCancelled");
+ ASSERT(isMainThread());
+
+ auto* uniqueIDBDatabase = m_uniqueIDBDatabaseMap.get(requestData.databaseIdentifier());
+ if (!uniqueIDBDatabase)
+ return;
+
+ uniqueIDBDatabase->openDBRequestCancelled(requestData.requestIdentifier());
+}
+
+void IDBServer::confirmDidCloseFromServer(uint64_t databaseConnectionIdentifier)
+{
+ LOG(IndexedDB, "IDBServer::confirmDidCloseFromServer");
+
+ if (auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier))
+ databaseConnection->confirmDidCloseFromServer();
+}
+
+void IDBServer::getAllDatabaseNames(uint64_t serverConnectionIdentifier, const SecurityOriginData& mainFrameOrigin, const SecurityOriginData& openingOrigin, uint64_t callbackID)
+{
+ postDatabaseTask(createCrossThreadTask(*this, &IDBServer::performGetAllDatabaseNames, serverConnectionIdentifier, mainFrameOrigin, openingOrigin, callbackID));
+}
+
+void IDBServer::performGetAllDatabaseNames(uint64_t serverConnectionIdentifier, const SecurityOriginData& mainFrameOrigin, const SecurityOriginData& openingOrigin, uint64_t callbackID)
+{
+ String directory = IDBDatabaseIdentifier::databaseDirectoryRelativeToRoot(mainFrameOrigin, openingOrigin, m_databaseDirectoryPath);
+
+ Vector<String> entries = listDirectory(directory, ASCIILiteral("*"));
+ Vector<String> databases;
+ databases.reserveInitialCapacity(entries.size());
+ for (auto& entry : entries) {
+ String encodedName = lastComponentOfPathIgnoringTrailingSlash(entry);
+ databases.uncheckedAppend(SQLiteIDBBackingStore::databaseNameFromEncodedFilename(encodedName));
+ }
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didGetAllDatabaseNames, serverConnectionIdentifier, callbackID, databases));
+}
+
+void IDBServer::didGetAllDatabaseNames(uint64_t serverConnectionIdentifier, uint64_t callbackID, const Vector<String>& databaseNames)
+{
+ auto connection = m_connectionMap.get(serverConnectionIdentifier);
+ if (!connection)
+ return;
+
+ connection->didGetAllDatabaseNames(callbackID, databaseNames);
+}
+
+void IDBServer::postDatabaseTask(CrossThreadTask&& task)
+{
+ m_databaseQueue.append(WTFMove(task));
+}
+
+void IDBServer::postDatabaseTaskReply(CrossThreadTask&& task)
+{
+ ASSERT(!isMainThread());
+ m_databaseReplyQueue.append(WTFMove(task));
+
+
+ Locker<Lock> locker(m_mainThreadReplyLock);
+ if (m_mainThreadReplyScheduled)
+ return;
+
+ m_mainThreadReplyScheduled = true;
+ callOnMainThread([this] {
+ handleTaskRepliesOnMainThread();
+ });
+}
+
+void IDBServer::databaseThreadEntry(void* threadData)
+{
+ ASSERT(threadData);
+ IDBServer* server = reinterpret_cast<IDBServer*>(threadData);
+ server->databaseRunLoop();
+}
+
+void IDBServer::databaseRunLoop()
+{
+ ASSERT(!isMainThread());
+ {
+ Locker<Lock> locker(m_databaseThreadCreationLock);
+ }
+
+ while (!m_databaseQueue.isKilled())
+ m_databaseQueue.waitForMessage().performTask();
+}
+
+void IDBServer::handleTaskRepliesOnMainThread()
+{
+ {
+ Locker<Lock> locker(m_mainThreadReplyLock);
+ m_mainThreadReplyScheduled = false;
+ }
+
+ while (auto task = m_databaseReplyQueue.tryGetMessage())
+ task->performTask();
+}
+
+static uint64_t generateDeleteCallbackID()
+{
+ ASSERT(isMainThread());
+ static uint64_t currentID = 0;
+ return ++currentID;
+}
+
+void IDBServer::closeAndDeleteDatabasesModifiedSince(std::chrono::system_clock::time_point modificationTime, std::function<void ()> completionHandler)
+{
+ uint64_t callbackID = generateDeleteCallbackID();
+ auto addResult = m_deleteDatabaseCompletionHandlers.add(callbackID, WTFMove(completionHandler));
+ ASSERT_UNUSED(addResult, addResult.isNewEntry);
+
+ // If the modification time is in the future, don't both doing anything.
+ if (modificationTime > std::chrono::system_clock::now()) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didPerformCloseAndDeleteDatabases, callbackID));
+ return;
+ }
+
+ HashSet<RefPtr<UniqueIDBDatabase>> openDatabases;
+ for (auto* connection : m_databaseConnections.values())
+ openDatabases.add(&connection->database());
+
+ for (auto& database : openDatabases)
+ database->immediateCloseForUserDelete();
+
+ postDatabaseTask(createCrossThreadTask(*this, &IDBServer::performCloseAndDeleteDatabasesModifiedSince, modificationTime, callbackID));
+}
+
+void IDBServer::closeAndDeleteDatabasesForOrigins(const Vector<SecurityOriginData>& origins, std::function<void ()> completionHandler)
+{
+ uint64_t callbackID = generateDeleteCallbackID();
+ auto addResult = m_deleteDatabaseCompletionHandlers.add(callbackID, WTFMove(completionHandler));
+ ASSERT_UNUSED(addResult, addResult.isNewEntry);
+
+ HashSet<RefPtr<UniqueIDBDatabase>> openDatabases;
+ for (auto* connection : m_databaseConnections.values()) {
+ const auto& identifier = connection->database().identifier();
+ for (auto& origin : origins) {
+ if (identifier.isRelatedToOrigin(origin)) {
+ openDatabases.add(&connection->database());
+ break;
+ }
+ }
+ }
+
+ for (auto& database : openDatabases)
+ database->immediateCloseForUserDelete();
+
+ postDatabaseTask(createCrossThreadTask(*this, &IDBServer::performCloseAndDeleteDatabasesForOrigins, origins, callbackID));
+}
+
+static void removeAllDatabasesForOriginPath(const String& originPath, std::chrono::system_clock::time_point modifiedSince)
+{
+ Vector<String> databasePaths = listDirectory(originPath, "*");
+
+ for (auto& databasePath : databasePaths) {
+ String databaseFile = pathByAppendingComponent(databasePath, "IndexedDB.sqlite3");
+
+ if (modifiedSince > std::chrono::system_clock::time_point::min() && fileExists(databaseFile)) {
+ time_t modificationTime;
+ if (!getFileModificationTime(databaseFile, modificationTime))
+ continue;
+
+ if (std::chrono::system_clock::from_time_t(modificationTime) < modifiedSince)
+ continue;
+ }
+
+ // Deleting this database means we need to delete all files that represent it.
+ // This includes:
+ // - The directory itself, which is named after the database.
+ // - IndexedDB.sqlite3 and related SQLite files.
+ // - Blob files that we stored in the directory.
+ //
+ // To be conservative, we should *not* try to delete files that are unexpected;
+ // We should only delete files we think we put there.
+ //
+ // IndexedDB blob files are named "N.blob" where N is a decimal integer,
+ // so those are the only blob files we should be trying to delete.
+ for (auto& blobPath : listDirectory(databasePath, "[0-9]*.blob")) {
+ // Globbing can't give us only filenames starting with 1-or-more digits.
+ // The above globbing gives us files that start with a digit and ends with ".blob", but there might be non-digits in between.
+ // We need to validate that each filename contains only digits before deleting it, as any other files are not ones we put there.
+ String filename = pathGetFileName(blobPath);
+ auto filenameLength = filename.length();
+
+ ASSERT(filenameLength >= 6);
+ ASSERT(filename.endsWith(".blob"));
+
+ if (filename.length() < 6)
+ continue;
+ if (!filename.endsWith(".blob"))
+ continue;
+
+ bool validFilename = true;
+ for (unsigned i = 0; i < filenameLength - 5; ++i) {
+ if (!isASCIIDigit(filename[i])) {
+ validFilename = false;
+ break;
+ }
+ }
+
+ if (validFilename)
+ deleteFile(blobPath);
+ }
+
+ // Now delete IndexedDB.sqlite3 and related SQLite files.
+ SQLiteFileSystem::deleteDatabaseFile(databaseFile);
+
+ // And finally, if we can, delete the empty directory.
+ deleteEmptyDirectory(databasePath);
+ }
+
+ // If no databases remain for this origin, we can delete the origin directory as well.
+ deleteEmptyDirectory(originPath);
+}
+
+void IDBServer::performCloseAndDeleteDatabasesModifiedSince(std::chrono::system_clock::time_point modifiedSince, uint64_t callbackID)
+{
+ if (!m_databaseDirectoryPath.isEmpty()) {
+ Vector<String> originPaths = listDirectory(m_databaseDirectoryPath, "*");
+ for (auto& originPath : originPaths)
+ removeAllDatabasesForOriginPath(originPath, modifiedSince);
+ }
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didPerformCloseAndDeleteDatabases, callbackID));
+}
+
+void IDBServer::performCloseAndDeleteDatabasesForOrigins(const Vector<SecurityOriginData>& origins, uint64_t callbackID)
+{
+ if (!m_databaseDirectoryPath.isEmpty()) {
+ for (const auto& origin : origins) {
+ String originPath = pathByAppendingComponent(m_databaseDirectoryPath, origin.databaseIdentifier());
+ removeAllDatabasesForOriginPath(originPath, std::chrono::system_clock::time_point::min());
+ }
+ }
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &IDBServer::didPerformCloseAndDeleteDatabases, callbackID));
+}
+
+void IDBServer::didPerformCloseAndDeleteDatabases(uint64_t callbackID)
+{
+ auto callback = m_deleteDatabaseCompletionHandlers.take(callbackID);
+ ASSERT(callback);
+ callback();
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IDBServer.h b/Source/WebCore/Modules/indexeddb/server/IDBServer.h
new file mode 100644
index 000000000..256cd507e
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IDBServer.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2015, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBConnectionToClient.h"
+#include "IDBDatabaseIdentifier.h"
+#include "UniqueIDBDatabase.h"
+#include "UniqueIDBDatabaseConnection.h"
+#include <wtf/CrossThreadQueue.h>
+#include <wtf/CrossThreadTask.h>
+#include <wtf/HashMap.h>
+#include <wtf/Lock.h>
+#include <wtf/Ref.h>
+#include <wtf/RefCounted.h>
+#include <wtf/RefPtr.h>
+
+namespace WebCore {
+
+class IDBCursorInfo;
+class IDBRequestData;
+class IDBValue;
+
+struct IDBGetRecordData;
+
+namespace IDBServer {
+
+class IDBBackingStoreTemporaryFileHandler;
+
+class IDBServer : public RefCounted<IDBServer> {
+public:
+ static Ref<IDBServer> create(IDBBackingStoreTemporaryFileHandler&);
+ WEBCORE_EXPORT static Ref<IDBServer> create(const String& databaseDirectoryPath, IDBBackingStoreTemporaryFileHandler&);
+
+ WEBCORE_EXPORT void registerConnection(IDBConnectionToClient&);
+ WEBCORE_EXPORT void unregisterConnection(IDBConnectionToClient&);
+
+ // Operations requested by the client.
+ WEBCORE_EXPORT void openDatabase(const IDBRequestData&);
+ WEBCORE_EXPORT void deleteDatabase(const IDBRequestData&);
+ WEBCORE_EXPORT void abortTransaction(const IDBResourceIdentifier&);
+ WEBCORE_EXPORT void commitTransaction(const IDBResourceIdentifier&);
+ WEBCORE_EXPORT void didFinishHandlingVersionChangeTransaction(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier&);
+ WEBCORE_EXPORT void createObjectStore(const IDBRequestData&, const IDBObjectStoreInfo&);
+ WEBCORE_EXPORT void renameObjectStore(const IDBRequestData&, uint64_t objectStoreIdentifier, const String& newName);
+ WEBCORE_EXPORT void deleteObjectStore(const IDBRequestData&, const String& objectStoreName);
+ WEBCORE_EXPORT void clearObjectStore(const IDBRequestData&, uint64_t objectStoreIdentifier);
+ WEBCORE_EXPORT void createIndex(const IDBRequestData&, const IDBIndexInfo&);
+ WEBCORE_EXPORT void deleteIndex(const IDBRequestData&, uint64_t objectStoreIdentifier, const String& indexName);
+ WEBCORE_EXPORT void renameIndex(const IDBRequestData&, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName);
+ WEBCORE_EXPORT void putOrAdd(const IDBRequestData&, const IDBKeyData&, const IDBValue&, IndexedDB::ObjectStoreOverwriteMode);
+ WEBCORE_EXPORT void getRecord(const IDBRequestData&, const IDBGetRecordData&);
+ WEBCORE_EXPORT void getAllRecords(const IDBRequestData&, const IDBGetAllRecordsData&);
+ WEBCORE_EXPORT void getCount(const IDBRequestData&, const IDBKeyRangeData&);
+ WEBCORE_EXPORT void deleteRecord(const IDBRequestData&, const IDBKeyRangeData&);
+ WEBCORE_EXPORT void openCursor(const IDBRequestData&, const IDBCursorInfo&);
+ WEBCORE_EXPORT void iterateCursor(const IDBRequestData&, const IDBIterateCursorData&);
+
+ WEBCORE_EXPORT void establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo&);
+ WEBCORE_EXPORT void databaseConnectionPendingClose(uint64_t databaseConnectionIdentifier);
+ WEBCORE_EXPORT void databaseConnectionClosed(uint64_t databaseConnectionIdentifier);
+ WEBCORE_EXPORT void abortOpenAndUpgradeNeeded(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& transactionIdentifier);
+ WEBCORE_EXPORT void didFireVersionChangeEvent(uint64_t databaseConnectionIdentifier, const IDBResourceIdentifier& requestIdentifier);
+ WEBCORE_EXPORT void openDBRequestCancelled(const IDBRequestData&);
+ WEBCORE_EXPORT void confirmDidCloseFromServer(uint64_t databaseConnectionIdentifier);
+
+ WEBCORE_EXPORT void getAllDatabaseNames(uint64_t serverConnectionIdentifier, const SecurityOriginData& mainFrameOrigin, const SecurityOriginData& openingOrigin, uint64_t callbackID);
+
+ void postDatabaseTask(CrossThreadTask&&);
+ void postDatabaseTaskReply(CrossThreadTask&&);
+
+ void registerDatabaseConnection(UniqueIDBDatabaseConnection&);
+ void unregisterDatabaseConnection(UniqueIDBDatabaseConnection&);
+ void registerTransaction(UniqueIDBDatabaseTransaction&);
+ void unregisterTransaction(UniqueIDBDatabaseTransaction&);
+
+ void closeUniqueIDBDatabase(UniqueIDBDatabase&);
+
+ std::unique_ptr<IDBBackingStore> createBackingStore(const IDBDatabaseIdentifier&);
+
+ WEBCORE_EXPORT void closeAndDeleteDatabasesModifiedSince(std::chrono::system_clock::time_point, std::function<void ()> completionHandler);
+ WEBCORE_EXPORT void closeAndDeleteDatabasesForOrigins(const Vector<SecurityOriginData>&, std::function<void ()> completionHandler);
+
+private:
+ IDBServer(IDBBackingStoreTemporaryFileHandler&);
+ IDBServer(const String& databaseDirectoryPath, IDBBackingStoreTemporaryFileHandler&);
+
+ UniqueIDBDatabase& getOrCreateUniqueIDBDatabase(const IDBDatabaseIdentifier&);
+
+ void performGetAllDatabaseNames(uint64_t serverConnectionIdentifier, const SecurityOriginData& mainFrameOrigin, const SecurityOriginData& openingOrigin, uint64_t callbackID);
+ void didGetAllDatabaseNames(uint64_t serverConnectionIdentifier, uint64_t callbackID, const Vector<String>& databaseNames);
+
+ void performCloseAndDeleteDatabasesModifiedSince(std::chrono::system_clock::time_point, uint64_t callbackID);
+ void performCloseAndDeleteDatabasesForOrigins(const Vector<SecurityOriginData>&, uint64_t callbackID);
+ void didPerformCloseAndDeleteDatabases(uint64_t callbackID);
+
+ static void databaseThreadEntry(void*);
+ void databaseRunLoop();
+ void handleTaskRepliesOnMainThread();
+
+ HashMap<uint64_t, RefPtr<IDBConnectionToClient>> m_connectionMap;
+ HashMap<IDBDatabaseIdentifier, RefPtr<UniqueIDBDatabase>> m_uniqueIDBDatabaseMap;
+
+ ThreadIdentifier m_threadID { 0 };
+ Lock m_databaseThreadCreationLock;
+ Lock m_mainThreadReplyLock;
+ bool m_mainThreadReplyScheduled { false };
+
+ CrossThreadQueue<CrossThreadTask> m_databaseQueue;
+ CrossThreadQueue<CrossThreadTask> m_databaseReplyQueue;
+
+ HashMap<uint64_t, UniqueIDBDatabaseConnection*> m_databaseConnections;
+ HashMap<IDBResourceIdentifier, UniqueIDBDatabaseTransaction*> m_transactions;
+
+ HashMap<uint64_t, std::function<void ()>> m_deleteDatabaseCompletionHandlers;
+
+ String m_databaseDirectoryPath;
+ IDBBackingStoreTemporaryFileHandler& m_backingStoreTemporaryFileHandler;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IndexValueEntry.cpp b/Source/WebCore/Modules/indexeddb/server/IndexValueEntry.cpp
new file mode 100644
index 000000000..38c860677
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IndexValueEntry.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IndexValueEntry.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+IndexValueEntry::IndexValueEntry(bool unique)
+ : m_unique(unique)
+{
+ if (m_unique)
+ m_key = nullptr;
+ else
+ m_orderedKeys = new std::set<IDBKeyData>;
+}
+
+IndexValueEntry::~IndexValueEntry()
+{
+ if (m_unique)
+ delete m_key;
+ else
+ delete m_orderedKeys;
+}
+
+void IndexValueEntry::addKey(const IDBKeyData& key)
+{
+ if (m_unique) {
+ delete m_key;
+ m_key = new IDBKeyData(key);
+ return;
+ }
+
+ m_orderedKeys->insert(key);
+}
+
+bool IndexValueEntry::removeKey(const IDBKeyData& key)
+{
+ if (m_unique) {
+ if (m_key && *m_key == key) {
+ delete m_key;
+ m_key = nullptr;
+ return true;
+ }
+
+ return false;
+ }
+
+ return m_orderedKeys->erase(key);
+}
+
+const IDBKeyData* IndexValueEntry::getLowest() const
+{
+ if (m_unique)
+ return m_key;
+
+ if (m_orderedKeys->empty())
+ return nullptr;
+
+ return &(*m_orderedKeys->begin());
+}
+
+uint64_t IndexValueEntry::getCount() const
+{
+ if (m_unique)
+ return m_key ? 1 : 0;
+
+ return m_orderedKeys->size();
+}
+
+IndexValueEntry::Iterator::Iterator(IndexValueEntry& entry)
+ : m_entry(&entry)
+{
+ ASSERT(m_entry->m_key);
+}
+
+IndexValueEntry::Iterator::Iterator(IndexValueEntry& entry, std::set<IDBKeyData>::iterator iterator)
+ : m_entry(&entry)
+ , m_forwardIterator(iterator)
+{
+}
+
+IndexValueEntry::Iterator::Iterator(IndexValueEntry& entry, std::set<IDBKeyData>::reverse_iterator iterator)
+ : m_entry(&entry)
+ , m_forward(false)
+ , m_reverseIterator(iterator)
+{
+}
+
+const IDBKeyData& IndexValueEntry::Iterator::key() const
+{
+ ASSERT(isValid());
+ if (m_entry->unique()) {
+ ASSERT(m_entry->m_key);
+ return *m_entry->m_key;
+ }
+
+ return m_forward ? *m_forwardIterator : *m_reverseIterator;
+}
+
+bool IndexValueEntry::Iterator::isValid() const
+{
+#if !LOG_DISABLED
+ if (m_entry) {
+ if (m_entry->m_unique)
+ ASSERT(m_entry->m_key);
+ else
+ ASSERT(m_entry->m_orderedKeys);
+ }
+#endif
+
+ return m_entry;
+}
+
+void IndexValueEntry::Iterator::invalidate()
+{
+ m_entry = nullptr;
+}
+
+IndexValueEntry::Iterator& IndexValueEntry::Iterator::operator++()
+{
+ if (!isValid())
+ return *this;
+
+ if (m_entry->m_unique) {
+ invalidate();
+ return *this;
+ }
+
+ if (m_forward) {
+ ++m_forwardIterator;
+ if (m_forwardIterator == m_entry->m_orderedKeys->end())
+ invalidate();
+ } else {
+ ++m_reverseIterator;
+ if (m_reverseIterator == m_entry->m_orderedKeys->rend())
+ invalidate();
+ }
+
+ return *this;
+}
+
+IndexValueEntry::Iterator IndexValueEntry::begin()
+{
+ if (m_unique) {
+ ASSERT(m_key);
+ return { *this };
+ }
+
+ ASSERT(m_orderedKeys);
+ return { *this, m_orderedKeys->begin() };
+}
+
+IndexValueEntry::Iterator IndexValueEntry::reverseBegin(CursorDuplicity duplicity)
+{
+ if (m_unique) {
+ ASSERT(m_key);
+ return { *this };
+ }
+
+ ASSERT(m_orderedKeys);
+
+ if (duplicity == CursorDuplicity::Duplicates)
+ return { *this, m_orderedKeys->rbegin() };
+
+ auto iterator = m_orderedKeys->rend();
+ --iterator;
+ return { *this, iterator };
+}
+
+IndexValueEntry::Iterator IndexValueEntry::find(const IDBKeyData& key)
+{
+ if (m_unique) {
+ ASSERT(m_key);
+ return *m_key == key ? IndexValueEntry::Iterator(*this) : IndexValueEntry::Iterator();
+ }
+
+ ASSERT(m_orderedKeys);
+ auto iterator = m_orderedKeys->lower_bound(key);
+ if (iterator == m_orderedKeys->end())
+ return { };
+
+ return { *this, iterator };
+}
+
+IndexValueEntry::Iterator IndexValueEntry::reverseFind(const IDBKeyData& key, CursorDuplicity)
+{
+ if (m_unique) {
+ ASSERT(m_key);
+ return *m_key == key ? IndexValueEntry::Iterator(*this) : IndexValueEntry::Iterator();
+ }
+
+ ASSERT(m_orderedKeys);
+ auto iterator = std::set<IDBKeyData>::reverse_iterator(m_orderedKeys->upper_bound(key));
+ if (iterator == m_orderedKeys->rend())
+ return { };
+
+ return { *this, iterator };
+}
+
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IndexValueEntry.h b/Source/WebCore/Modules/indexeddb/server/IndexValueEntry.h
new file mode 100644
index 000000000..df0543fcf
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IndexValueEntry.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBKeyData.h"
+#include <set>
+
+namespace WebCore {
+
+class ThreadSafeDataBuffer;
+
+enum class CursorDuplicity;
+
+namespace IDBServer {
+
+class IndexValueEntry {
+public:
+ IndexValueEntry(bool unique);
+ ~IndexValueEntry();
+
+ void addKey(const IDBKeyData&);
+
+ // Returns true if a key was actually removed.
+ bool removeKey(const IDBKeyData&);
+
+ const IDBKeyData* getLowest() const;
+
+ uint64_t getCount() const;
+
+ class Iterator {
+ public:
+ Iterator()
+ {
+ }
+
+ Iterator(IndexValueEntry&);
+ Iterator(IndexValueEntry&, std::set<IDBKeyData>::iterator);
+ Iterator(IndexValueEntry&, std::set<IDBKeyData>::reverse_iterator);
+
+ bool isValid() const;
+ void invalidate();
+
+ const IDBKeyData& key() const;
+ const ThreadSafeDataBuffer& value() const;
+
+ Iterator& operator++();
+
+ private:
+ IndexValueEntry* m_entry { nullptr };
+ bool m_forward { true };
+ std::set<IDBKeyData>::iterator m_forwardIterator;
+ std::set<IDBKeyData>::reverse_iterator m_reverseIterator;
+ };
+
+ Iterator begin();
+ Iterator reverseBegin(CursorDuplicity);
+
+ // Finds the key, or the next higher record after the key.
+ Iterator find(const IDBKeyData&);
+ // Finds the key, or the next lowest record before the key.
+ Iterator reverseFind(const IDBKeyData&, CursorDuplicity);
+
+ bool unique() const { return m_unique; }
+
+private:
+ union {
+ std::set<IDBKeyData>* m_orderedKeys;
+ IDBKeyData* m_key;
+ };
+
+ bool m_unique;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IndexValueStore.cpp b/Source/WebCore/Modules/indexeddb/server/IndexValueStore.cpp
new file mode 100644
index 000000000..339888462
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IndexValueStore.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "IndexValueStore.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBError.h"
+#include "IDBKeyRangeData.h"
+#include "Logging.h"
+#include "MemoryIndex.h"
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+IndexValueStore::IndexValueStore(bool unique)
+ : m_unique(unique)
+{
+}
+
+const IDBKeyData* IndexValueStore::lowestValueForKey(const IDBKeyData& key) const
+{
+ const auto& entry = m_records.get(key);
+ if (!entry)
+ return nullptr;
+
+ return entry->getLowest();
+}
+
+Vector<IDBKeyData> IndexValueStore::allValuesForKey(const IDBKeyData& key, uint32_t limit) const
+{
+ const auto& entry = m_records.get(key);
+ if (!entry)
+ return { };
+
+ Vector<IDBKeyData> results;
+ for (auto iterator = entry->begin(); results.size() < limit && iterator.isValid(); ++iterator)
+ results.append(iterator.key());
+
+ return results;
+}
+
+uint64_t IndexValueStore::countForKey(const IDBKeyData& key) const
+{
+ const auto& entry = m_records.get(key);
+ if (!entry)
+ return 0;
+
+ return entry->getCount();
+}
+
+bool IndexValueStore::contains(const IDBKeyData& key) const
+{
+ const auto& entry = m_records.get(key);
+ if (!entry)
+ return false;
+
+ ASSERT(entry->getCount());
+
+ return true;
+}
+
+IDBError IndexValueStore::addRecord(const IDBKeyData& indexKey, const IDBKeyData& valueKey)
+{
+ auto result = m_records.add(indexKey, nullptr);
+
+ if (!result.isNewEntry && m_unique)
+ return IDBError(IDBDatabaseException::ConstraintError);
+
+ if (result.isNewEntry)
+ result.iterator->value = std::make_unique<IndexValueEntry>(m_unique);
+
+ result.iterator->value->addKey(valueKey);
+ m_orderedKeys.insert(indexKey);
+
+ return { };
+}
+
+void IndexValueStore::removeRecord(const IDBKeyData& indexKey, const IDBKeyData& valueKey)
+{
+ auto iterator = m_records.find(indexKey);
+ if (!iterator->value)
+ return;
+
+ if (iterator->value->removeKey(valueKey))
+ m_records.remove(iterator);
+}
+
+void IndexValueStore::removeEntriesWithValueKey(MemoryIndex& index, const IDBKeyData& valueKey)
+{
+ Vector<IDBKeyData> entryKeysToRemove;
+ entryKeysToRemove.reserveInitialCapacity(m_records.size());
+
+ for (auto& entry : m_records) {
+ if (entry.value->removeKey(valueKey))
+ index.notifyCursorsOfValueChange(entry.key, valueKey);
+ if (!entry.value->getCount())
+ entryKeysToRemove.uncheckedAppend(entry.key);
+ }
+
+ for (auto& entry : entryKeysToRemove) {
+ m_orderedKeys.erase(entry);
+ m_records.remove(entry);
+ }
+}
+
+IDBKeyData IndexValueStore::lowestKeyWithRecordInRange(const IDBKeyRangeData& range) const
+{
+ LOG(IndexedDB, "IndexValueStore::lowestKeyWithRecordInRange - %s", range.loggingString().utf8().data());
+
+ if (range.isExactlyOneKey())
+ return m_records.contains(range.lowerKey) ? range.lowerKey : IDBKeyData();
+
+ auto iterator = lowestIteratorInRange(range);
+ if (iterator == m_orderedKeys.end())
+ return { };
+
+ return *iterator;
+}
+
+std::set<IDBKeyData>::iterator IndexValueStore::lowestIteratorInRange(const IDBKeyRangeData& range) const
+{
+ auto lowestInRange = m_orderedKeys.lower_bound(range.lowerKey);
+
+ if (lowestInRange == m_orderedKeys.end())
+ return lowestInRange;
+
+ if (range.lowerOpen && *lowestInRange == range.lowerKey) {
+ ++lowestInRange;
+
+ if (lowestInRange == m_orderedKeys.end())
+ return lowestInRange;
+ }
+
+ if (!range.upperKey.isNull()) {
+ if (lowestInRange->compare(range.upperKey) > 0)
+ return m_orderedKeys.end();
+ if (range.upperOpen && *lowestInRange == range.upperKey)
+ return m_orderedKeys.end();
+ }
+
+ return lowestInRange;
+}
+
+std::set<IDBKeyData>::reverse_iterator IndexValueStore::highestReverseIteratorInRange(const IDBKeyRangeData& range) const
+{
+ auto highestInRange = std::set<IDBKeyData>::reverse_iterator(m_orderedKeys.upper_bound(range.upperKey));
+
+ if (highestInRange == m_orderedKeys.rend())
+ return highestInRange;
+
+ if (range.upperOpen && *highestInRange == range.upperKey) {
+ ++highestInRange;
+
+ if (highestInRange == m_orderedKeys.rend())
+ return highestInRange;
+ }
+
+ if (!range.lowerKey.isNull()) {
+ if (highestInRange->compare(range.lowerKey) < 0)
+ return m_orderedKeys.rend();
+ if (range.lowerOpen && *highestInRange == range.lowerKey)
+ return m_orderedKeys.rend();
+ }
+
+ return highestInRange;
+}
+
+IndexValueStore::Iterator IndexValueStore::find(const IDBKeyData& key, bool open)
+{
+ IDBKeyRangeData range;
+ if (!key.isNull())
+ range.lowerKey = key;
+ else
+ range.lowerKey = IDBKeyData::minimum();
+ range.lowerOpen = open;
+
+ auto iterator = lowestIteratorInRange(range);
+ if (iterator == m_orderedKeys.end())
+ return { };
+
+ auto record = m_records.get(*iterator);
+ ASSERT(record);
+
+ auto primaryIterator = record->begin();
+ ASSERT(primaryIterator.isValid());
+ return { *this, iterator, primaryIterator };
+}
+
+IndexValueStore::Iterator IndexValueStore::find(const IDBKeyData& key, const IDBKeyData& primaryKey)
+{
+ ASSERT(!key.isNull());
+ ASSERT(!primaryKey.isNull());
+
+ IDBKeyRangeData range;
+ range.lowerKey = key;
+ range.lowerOpen = false;
+
+ auto iterator = lowestIteratorInRange(range);
+ if (iterator == m_orderedKeys.end())
+ return { };
+
+ auto record = m_records.get(*iterator);
+ ASSERT(record);
+
+ // If the main record iterator is not equal to the key we were looking for,
+ // we know the primary key record should be the first.
+ if (*iterator != key) {
+ auto primaryIterator = record->begin();
+ ASSERT(primaryIterator.isValid());
+
+ return { *this, iterator, primaryIterator };
+ }
+
+ auto primaryIterator = record->find(primaryKey);
+ if (primaryIterator.isValid())
+ return { *this, iterator, primaryIterator };
+
+ // If we didn't find a primary key iterator in this entry,
+ // we need to move on to start of the next record.
+ iterator++;
+ if (iterator == m_orderedKeys.end())
+ return { };
+
+ record = m_records.get(*iterator);
+ ASSERT(record);
+
+ primaryIterator = record->begin();
+ ASSERT(primaryIterator.isValid());
+
+ return { *this, iterator, primaryIterator };
+}
+
+IndexValueStore::Iterator IndexValueStore::reverseFind(const IDBKeyData& key, CursorDuplicity duplicity, bool open)
+{
+ IDBKeyRangeData range;
+ if (!key.isNull())
+ range.upperKey = key;
+ else
+ range.upperKey = IDBKeyData::maximum();
+ range.upperOpen = open;
+
+ auto iterator = highestReverseIteratorInRange(range);
+ if (iterator == m_orderedKeys.rend())
+ return { };
+
+ auto record = m_records.get(*iterator);
+ ASSERT(record);
+
+ auto primaryIterator = record->reverseBegin(duplicity);
+ ASSERT(primaryIterator.isValid());
+ return { *this, duplicity, iterator, primaryIterator };
+}
+
+IndexValueStore::Iterator IndexValueStore::reverseFind(const IDBKeyData& key, const IDBKeyData& primaryKey, CursorDuplicity duplicity)
+{
+ ASSERT(!key.isNull());
+ ASSERT(!primaryKey.isNull());
+
+ IDBKeyRangeData range;
+ range.upperKey = key;
+ range.upperOpen = false;
+
+ auto iterator = highestReverseIteratorInRange(range);
+ if (iterator == m_orderedKeys.rend())
+ return { };
+
+ auto record = m_records.get(*iterator);
+ ASSERT(record);
+
+ auto primaryIterator = record->reverseFind(primaryKey, duplicity);
+ if (primaryIterator.isValid())
+ return { *this, duplicity, iterator, primaryIterator };
+
+ // If we didn't find a primary key iterator in this entry,
+ // we need to move on to start of the next record.
+ iterator++;
+ if (iterator == m_orderedKeys.rend())
+ return { };
+
+ record = m_records.get(*iterator);
+ ASSERT(record);
+
+ primaryIterator = record->reverseBegin(duplicity);
+ ASSERT(primaryIterator.isValid());
+
+ return { *this, duplicity, iterator, primaryIterator };
+}
+
+
+IndexValueStore::Iterator::Iterator(IndexValueStore& store, std::set<IDBKeyData>::iterator iterator, IndexValueEntry::Iterator primaryIterator)
+ : m_store(&store)
+ , m_forwardIterator(iterator)
+ , m_primaryKeyIterator(primaryIterator)
+{
+}
+
+IndexValueStore::Iterator::Iterator(IndexValueStore& store, CursorDuplicity duplicity, std::set<IDBKeyData>::reverse_iterator iterator, IndexValueEntry::Iterator primaryIterator)
+ : m_store(&store)
+ , m_forward(false)
+ , m_duplicity(duplicity)
+ , m_reverseIterator(iterator)
+ , m_primaryKeyIterator(primaryIterator)
+{
+}
+
+IndexValueStore::Iterator& IndexValueStore::Iterator::nextIndexEntry()
+{
+ if (!m_store)
+ return *this;
+
+ if (m_forward) {
+ ++m_forwardIterator;
+ if (m_forwardIterator == m_store->m_orderedKeys.end()) {
+ invalidate();
+ return *this;
+ }
+
+ auto* entry = m_store->m_records.get(*m_forwardIterator);
+ ASSERT(entry);
+
+ m_primaryKeyIterator = entry->begin();
+ ASSERT(m_primaryKeyIterator.isValid());
+ } else {
+ ++m_reverseIterator;
+ if (m_reverseIterator == m_store->m_orderedKeys.rend()) {
+ invalidate();
+ return *this;
+ }
+
+ auto* entry = m_store->m_records.get(*m_reverseIterator);
+ ASSERT(entry);
+
+ m_primaryKeyIterator = entry->reverseBegin(m_duplicity);
+ ASSERT(m_primaryKeyIterator.isValid());
+ }
+
+ return *this;
+}
+
+IndexValueStore::Iterator& IndexValueStore::Iterator::operator++()
+{
+ if (!isValid())
+ return *this;
+
+ ++m_primaryKeyIterator;
+ if (m_primaryKeyIterator.isValid())
+ return *this;
+
+ // Ran out of primary key records, so move the main index iterator.
+ return nextIndexEntry();
+}
+
+void IndexValueStore::Iterator::invalidate()
+{
+ m_store = nullptr;
+ m_primaryKeyIterator.invalidate();
+}
+
+bool IndexValueStore::Iterator::isValid()
+{
+ return m_store && m_primaryKeyIterator.isValid();
+}
+
+const IDBKeyData& IndexValueStore::Iterator::key()
+{
+ ASSERT(isValid());
+ return m_forward ? *m_forwardIterator : *m_reverseIterator;
+}
+
+const IDBKeyData& IndexValueStore::Iterator::primaryKey()
+{
+ ASSERT(isValid());
+ return m_primaryKeyIterator.key();
+}
+
+#if !LOG_DISABLED
+String IndexValueStore::loggingString() const
+{
+ StringBuilder builder;
+ for (auto& key : m_orderedKeys) {
+ builder.appendLiteral("Key: ");
+ builder.append(key.loggingString());
+ builder.appendLiteral(" Entry has ");
+ builder.appendNumber(m_records.get(key)->getCount());
+ builder.appendLiteral(" entries");
+ }
+ return builder.toString();
+}
+#endif
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/IndexValueStore.h b/Source/WebCore/Modules/indexeddb/server/IndexValueStore.h
new file mode 100644
index 000000000..3af7c1fc0
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/IndexValueStore.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+#include "IDBKeyData.h"
+#include "IndexValueEntry.h"
+#include <set>
+#include <wtf/HashMap.h>
+
+namespace WebCore {
+
+class IDBError;
+
+struct IDBKeyRangeData;
+
+namespace IDBServer {
+
+class MemoryIndex;
+
+typedef HashMap<IDBKeyData, std::unique_ptr<IndexValueEntry>, IDBKeyDataHash, IDBKeyDataHashTraits> IndexKeyValueMap;
+
+class IndexValueStore {
+public:
+ IndexValueStore(bool unique);
+
+ const IDBKeyData* lowestValueForKey(const IDBKeyData&) const;
+ Vector<IDBKeyData> allValuesForKey(const IDBKeyData&, uint32_t limit) const;
+ uint64_t countForKey(const IDBKeyData&) const;
+ IDBKeyData lowestKeyWithRecordInRange(const IDBKeyRangeData&) const;
+ bool contains(const IDBKeyData&) const;
+
+ IDBError addRecord(const IDBKeyData& indexKey, const IDBKeyData& valueKey);
+ void removeRecord(const IDBKeyData& indexKey, const IDBKeyData& valueKey);
+
+ void removeEntriesWithValueKey(MemoryIndex&, const IDBKeyData& valueKey);
+
+ class Iterator {
+ friend class IndexValueStore;
+ public:
+ Iterator()
+ {
+ }
+
+ Iterator(IndexValueStore&, std::set<IDBKeyData>::iterator, IndexValueEntry::Iterator);
+ Iterator(IndexValueStore&, CursorDuplicity, std::set<IDBKeyData>::reverse_iterator, IndexValueEntry::Iterator);
+
+ void invalidate();
+ bool isValid();
+
+ const IDBKeyData& key();
+ const IDBKeyData& primaryKey();
+ const ThreadSafeDataBuffer& value();
+
+ Iterator& operator++();
+ Iterator& nextIndexEntry();
+
+ private:
+ IndexValueStore* m_store { nullptr };
+ bool m_forward { true };
+ CursorDuplicity m_duplicity { CursorDuplicity::Duplicates };
+ std::set<IDBKeyData>::iterator m_forwardIterator;
+ std::set<IDBKeyData>::reverse_iterator m_reverseIterator;
+
+ IndexValueEntry::Iterator m_primaryKeyIterator;
+ };
+
+ // Returns an iterator pointing to the first primaryKey record in the requested key, or the next key if it doesn't exist.
+ Iterator find(const IDBKeyData&, bool open = false);
+ Iterator reverseFind(const IDBKeyData&, CursorDuplicity, bool open = false);
+
+ // Returns an iterator pointing to the key/primaryKey record, or the next one after it if it doesn't exist.
+ Iterator find(const IDBKeyData&, const IDBKeyData& primaryKey);
+ Iterator reverseFind(const IDBKeyData&, const IDBKeyData& primaryKey, CursorDuplicity);
+
+#if !LOG_DISABLED
+ String loggingString() const;
+#endif
+
+private:
+ std::set<IDBKeyData>::iterator lowestIteratorInRange(const IDBKeyRangeData&) const;
+ std::set<IDBKeyData>::reverse_iterator highestReverseIteratorInRange(const IDBKeyRangeData&) const;
+
+ IndexKeyValueMap m_records;
+ std::set<IDBKeyData> m_orderedKeys;
+
+ bool m_unique;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.cpp
new file mode 100644
index 000000000..0e7bb6bda
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryBackingStoreTransaction.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBKeyRangeData.h"
+#include "IDBValue.h"
+#include "IndexedDB.h"
+#include "Logging.h"
+#include "MemoryIDBBackingStore.h"
+#include "MemoryObjectStore.h"
+#include <wtf/SetForScope.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+std::unique_ptr<MemoryBackingStoreTransaction> MemoryBackingStoreTransaction::create(MemoryIDBBackingStore& backingStore, const IDBTransactionInfo& info)
+{
+ return std::make_unique<MemoryBackingStoreTransaction>(backingStore, info);
+}
+
+MemoryBackingStoreTransaction::MemoryBackingStoreTransaction(MemoryIDBBackingStore& backingStore, const IDBTransactionInfo& info)
+ : m_backingStore(backingStore)
+ , m_info(info)
+{
+ if (m_info.mode() == IDBTransactionMode::Versionchange) {
+ IDBDatabaseInfo info;
+ auto error = m_backingStore.getOrEstablishDatabaseInfo(info);
+ if (error.isNull())
+ m_originalDatabaseInfo = std::make_unique<IDBDatabaseInfo>(info);
+ }
+}
+
+MemoryBackingStoreTransaction::~MemoryBackingStoreTransaction()
+{
+ ASSERT(!m_inProgress);
+}
+
+void MemoryBackingStoreTransaction::addNewObjectStore(MemoryObjectStore& objectStore)
+{
+ LOG(IndexedDB, "MemoryBackingStoreTransaction::addNewObjectStore()");
+
+ ASSERT(isVersionChange());
+ m_versionChangeAddedObjectStores.add(&objectStore);
+
+ addExistingObjectStore(objectStore);
+}
+
+void MemoryBackingStoreTransaction::addNewIndex(MemoryIndex& index)
+{
+ LOG(IndexedDB, "MemoryBackingStoreTransaction::addNewIndex()");
+
+ ASSERT(isVersionChange());
+ m_versionChangeAddedIndexes.add(&index);
+
+ addExistingIndex(index);
+}
+
+void MemoryBackingStoreTransaction::addExistingIndex(MemoryIndex& index)
+{
+ LOG(IndexedDB, "MemoryBackingStoreTransaction::addExistingIndex");
+
+ ASSERT(isWriting());
+
+ ASSERT(!m_indexes.contains(&index));
+ m_indexes.add(&index);
+}
+
+void MemoryBackingStoreTransaction::indexDeleted(Ref<MemoryIndex>&& index)
+{
+ m_indexes.remove(&index.get());
+
+ // If this MemoryIndex belongs to an object store that will not get restored if this transaction aborts,
+ // then we can forget about it altogether.
+ auto& objectStore = index->objectStore();
+ if (auto deletedObjectStore = m_deletedObjectStores.get(objectStore.info().name())) {
+ if (deletedObjectStore != &objectStore)
+ return;
+ }
+
+ auto addResult = m_deletedIndexes.add(index->info().name(), nullptr);
+ if (addResult.isNewEntry)
+ addResult.iterator->value = WTFMove(index);
+}
+
+void MemoryBackingStoreTransaction::addExistingObjectStore(MemoryObjectStore& objectStore)
+{
+ LOG(IndexedDB, "MemoryBackingStoreTransaction::addExistingObjectStore");
+
+ ASSERT(isWriting());
+
+ ASSERT(!m_objectStores.contains(&objectStore));
+ m_objectStores.add(&objectStore);
+
+ objectStore.writeTransactionStarted(*this);
+
+ m_originalKeyGenerators.add(&objectStore, objectStore.currentKeyGeneratorValue());
+}
+
+void MemoryBackingStoreTransaction::objectStoreDeleted(Ref<MemoryObjectStore>&& objectStore)
+{
+ ASSERT(m_objectStores.contains(&objectStore.get()));
+ m_objectStores.remove(&objectStore.get());
+
+ objectStore->deleteAllIndexes(*this);
+
+ auto addResult = m_deletedObjectStores.add(objectStore->info().name(), nullptr);
+ if (addResult.isNewEntry)
+ addResult.iterator->value = WTFMove(objectStore);
+}
+
+void MemoryBackingStoreTransaction::objectStoreCleared(MemoryObjectStore& objectStore, std::unique_ptr<KeyValueMap>&& keyValueMap, std::unique_ptr<std::set<IDBKeyData>>&& orderedKeys)
+{
+ ASSERT(m_objectStores.contains(&objectStore));
+
+ auto addResult = m_clearedKeyValueMaps.add(&objectStore, nullptr);
+
+ // If this object store has already been cleared during this transaction, we shouldn't remember this clearing.
+ if (!addResult.isNewEntry)
+ return;
+
+ addResult.iterator->value = WTFMove(keyValueMap);
+
+ ASSERT(!m_clearedOrderedKeys.contains(&objectStore));
+ m_clearedOrderedKeys.add(&objectStore, WTFMove(orderedKeys));
+}
+
+void MemoryBackingStoreTransaction::objectStoreRenamed(MemoryObjectStore& objectStore, const String& oldName)
+{
+ ASSERT(m_objectStores.contains(&objectStore));
+ ASSERT(m_info.mode() == IDBTransactionMode::Versionchange);
+
+ // We only care about the first rename in a given transaction, because if the transaction is aborted we want
+ // to go back to the first 'oldName'
+ m_originalObjectStoreNames.add(&objectStore, oldName);
+}
+
+void MemoryBackingStoreTransaction::indexRenamed(MemoryIndex& index, const String& oldName)
+{
+ ASSERT(m_objectStores.contains(&index.objectStore()));
+ ASSERT(m_info.mode() == IDBTransactionMode::Versionchange);
+
+ // We only care about the first rename in a given transaction, because if the transaction is aborted we want
+ // to go back to the first 'oldName'
+ m_originalIndexNames.add(&index, oldName);
+}
+
+void MemoryBackingStoreTransaction::indexCleared(MemoryIndex& index, std::unique_ptr<IndexValueStore>&& valueStore)
+{
+ auto addResult = m_clearedIndexValueStores.add(&index, nullptr);
+
+ // If this index has already been cleared during this transaction, we shouldn't remember this clearing.
+ if (!addResult.isNewEntry)
+ return;
+
+ addResult.iterator->value = WTFMove(valueStore);
+}
+
+void MemoryBackingStoreTransaction::recordValueChanged(MemoryObjectStore& objectStore, const IDBKeyData& key, ThreadSafeDataBuffer* value)
+{
+ ASSERT(m_objectStores.contains(&objectStore));
+
+ if (m_isAborting)
+ return;
+
+ // If this object store had been cleared during the transaction, no point in recording this
+ // individual key/value change as its entire key/value map will be restored upon abort.
+ if (m_clearedKeyValueMaps.contains(&objectStore))
+ return;
+
+ auto originalAddResult = m_originalValues.add(&objectStore, nullptr);
+ if (originalAddResult.isNewEntry)
+ originalAddResult.iterator->value = std::make_unique<KeyValueMap>();
+
+ auto* map = originalAddResult.iterator->value.get();
+
+ auto addResult = map->add(key, ThreadSafeDataBuffer());
+ if (!addResult.isNewEntry)
+ return;
+
+ if (value)
+ addResult.iterator->value = *value;
+}
+
+void MemoryBackingStoreTransaction::abort()
+{
+ LOG(IndexedDB, "MemoryBackingStoreTransaction::abort()");
+
+ SetForScope<bool> change(m_isAborting, true);
+
+ for (auto iterator : m_originalIndexNames)
+ iterator.key->rename(iterator.value);
+ m_originalIndexNames.clear();
+
+ for (auto iterator : m_originalObjectStoreNames)
+ iterator.key->rename(iterator.value);
+ m_originalObjectStoreNames.clear();
+
+ for (auto objectStore : m_versionChangeAddedObjectStores)
+ m_backingStore.removeObjectStoreForVersionChangeAbort(*objectStore);
+ m_versionChangeAddedObjectStores.clear();
+
+ for (auto& objectStore : m_deletedObjectStores.values()) {
+ m_backingStore.restoreObjectStoreForVersionChangeAbort(*objectStore);
+ ASSERT(!m_objectStores.contains(objectStore.get()));
+ m_objectStores.add(objectStore);
+ }
+ m_deletedObjectStores.clear();
+
+ if (m_originalDatabaseInfo) {
+ ASSERT(m_info.mode() == IDBTransactionMode::Versionchange);
+ m_backingStore.setDatabaseInfo(*m_originalDatabaseInfo);
+ }
+
+ // Restore cleared index value stores before we re-insert values into object stores
+ // because inserting those values will regenerate the appropriate index values.
+ for (auto& iterator : m_clearedIndexValueStores)
+ iterator.key->replaceIndexValueStore(WTFMove(iterator.value));
+ m_clearedIndexValueStores.clear();
+
+ for (auto& objectStore : m_objectStores) {
+ ASSERT(m_originalKeyGenerators.contains(objectStore.get()));
+ objectStore->setKeyGeneratorValue(m_originalKeyGenerators.get(objectStore.get()));
+
+ auto clearedKeyValueMap = m_clearedKeyValueMaps.take(objectStore.get());
+ if (clearedKeyValueMap) {
+ ASSERT(m_clearedOrderedKeys.contains(objectStore.get()));
+ objectStore->replaceKeyValueStore(WTFMove(clearedKeyValueMap), m_clearedOrderedKeys.take(objectStore.get()));
+ }
+
+ auto keyValueMap = m_originalValues.take(objectStore.get());
+ if (!keyValueMap)
+ continue;
+
+ for (auto entry : *keyValueMap) {
+ objectStore->deleteRecord(entry.key);
+ objectStore->addRecord(*this, entry.key, { entry.value });
+ }
+ }
+
+ for (auto& index : m_deletedIndexes.values())
+ index->objectStore().maybeRestoreDeletedIndex(*index);
+ m_deletedIndexes.clear();
+
+ finish();
+}
+
+void MemoryBackingStoreTransaction::commit()
+{
+ LOG(IndexedDB, "MemoryBackingStoreTransaction::commit()");
+
+ finish();
+}
+
+void MemoryBackingStoreTransaction::finish()
+{
+ m_inProgress = false;
+
+ if (!isWriting())
+ return;
+
+ for (auto& objectStore : m_objectStores)
+ objectStore->writeTransactionFinished(*this);
+ for (auto& objectStore : m_deletedObjectStores.values())
+ objectStore->writeTransactionFinished(*this);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.h b/Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.h
new file mode 100644
index 000000000..5ac9302cc
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryBackingStoreTransaction.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBDatabaseInfo.h"
+#include "IDBKeyData.h"
+#include "IDBTransactionInfo.h"
+#include "IndexValueStore.h"
+#include "ThreadSafeDataBuffer.h"
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+class MemoryIDBBackingStore;
+class MemoryIndex;
+class MemoryObjectStore;
+
+typedef HashMap<IDBKeyData, ThreadSafeDataBuffer, IDBKeyDataHash, IDBKeyDataHashTraits> KeyValueMap;
+
+class MemoryBackingStoreTransaction {
+public:
+ static std::unique_ptr<MemoryBackingStoreTransaction> create(MemoryIDBBackingStore&, const IDBTransactionInfo&);
+
+ MemoryBackingStoreTransaction(MemoryIDBBackingStore&, const IDBTransactionInfo&);
+ ~MemoryBackingStoreTransaction();
+
+ bool isVersionChange() const { return m_info.mode() == IDBTransactionMode::Versionchange; }
+ bool isWriting() const { return m_info.mode() != IDBTransactionMode::Readonly; }
+ bool isAborting() const { return m_isAborting; }
+
+ const IDBDatabaseInfo& originalDatabaseInfo() const;
+
+ void addNewObjectStore(MemoryObjectStore&);
+ void addExistingObjectStore(MemoryObjectStore&);
+
+ void recordValueChanged(MemoryObjectStore&, const IDBKeyData&, ThreadSafeDataBuffer*);
+ void objectStoreDeleted(Ref<MemoryObjectStore>&&);
+ void objectStoreCleared(MemoryObjectStore&, std::unique_ptr<KeyValueMap>&&, std::unique_ptr<std::set<IDBKeyData>>&&);
+ void objectStoreRenamed(MemoryObjectStore&, const String& oldName);
+ void indexRenamed(MemoryIndex&, const String& oldName);
+ void indexCleared(MemoryIndex&, std::unique_ptr<IndexValueStore>&&);
+
+ void addNewIndex(MemoryIndex&);
+ void addExistingIndex(MemoryIndex&);
+ void indexDeleted(Ref<MemoryIndex>&&);
+
+ void abort();
+ void commit();
+
+private:
+ void finish();
+
+ MemoryIDBBackingStore& m_backingStore;
+ IDBTransactionInfo m_info;
+
+ std::unique_ptr<IDBDatabaseInfo> m_originalDatabaseInfo;
+
+ bool m_inProgress { true };
+ bool m_isAborting { false };
+
+ HashSet<RefPtr<MemoryObjectStore>> m_objectStores;
+ HashSet<RefPtr<MemoryObjectStore>> m_versionChangeAddedObjectStores;
+ HashSet<RefPtr<MemoryIndex>> m_indexes;
+ HashSet<RefPtr<MemoryIndex>> m_versionChangeAddedIndexes;
+
+ HashMap<MemoryObjectStore*, uint64_t> m_originalKeyGenerators;
+ HashMap<String, RefPtr<MemoryObjectStore>> m_deletedObjectStores;
+ HashMap<String, RefPtr<MemoryIndex>> m_deletedIndexes;
+ HashMap<MemoryObjectStore*, std::unique_ptr<KeyValueMap>> m_originalValues;
+ HashMap<MemoryObjectStore*, std::unique_ptr<KeyValueMap>> m_clearedKeyValueMaps;
+ HashMap<MemoryObjectStore*, std::unique_ptr<std::set<IDBKeyData>>> m_clearedOrderedKeys;
+ HashMap<MemoryObjectStore*, String> m_originalObjectStoreNames;
+ HashMap<MemoryIndex*, String> m_originalIndexNames;
+ HashMap<MemoryIndex*, std::unique_ptr<IndexValueStore>> m_clearedIndexValueStores;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryCursor.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryCursor.cpp
new file mode 100644
index 000000000..67059f313
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryCursor.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryCursor.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBResourceIdentifier.h"
+#include "MemoryIDBBackingStore.h"
+#include <wtf/HashMap.h>
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+static HashMap<IDBResourceIdentifier, MemoryCursor*>& cursorMap()
+{
+ static NeverDestroyed<HashMap<IDBResourceIdentifier, MemoryCursor*>> map;
+ return map;
+}
+
+MemoryCursor::MemoryCursor(const IDBCursorInfo& info)
+ : m_info(info)
+{
+ ASSERT(!cursorMap().contains(m_info.identifier()));
+ cursorMap().set(m_info.identifier(), this);
+}
+
+MemoryCursor::~MemoryCursor()
+{
+ ASSERT(cursorMap().contains(m_info.identifier()));
+ cursorMap().remove(m_info.identifier());
+}
+
+MemoryCursor* MemoryCursor::cursorForIdentifier(const IDBResourceIdentifier& identifier)
+{
+ return cursorMap().get(identifier);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryCursor.h b/Source/WebCore/Modules/indexeddb/server/MemoryCursor.h
new file mode 100644
index 000000000..64ac3e82d
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryCursor.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+
+namespace WebCore {
+
+class IDBGetResult;
+class IDBKeyData;
+class IDBResourceIdentifier;
+
+namespace IDBServer {
+
+class MemoryCursor {
+public:
+ virtual ~MemoryCursor();
+
+ virtual void currentData(IDBGetResult&) = 0;
+ virtual void iterate(const IDBKeyData&, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult&) = 0;
+
+ static MemoryCursor* cursorForIdentifier(const IDBResourceIdentifier&);
+
+protected:
+ MemoryCursor(const IDBCursorInfo&);
+
+ IDBCursorInfo m_info;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.cpp
new file mode 100644
index 000000000..e6330891f
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.cpp
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryIDBBackingStore.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+#include "IDBGetAllRecordsData.h"
+#include "IDBGetRecordData.h"
+#include "IDBGetResult.h"
+#include "IDBIndexInfo.h"
+#include "IDBIterateCursorData.h"
+#include "IDBKeyRangeData.h"
+#include "Logging.h"
+#include "MemoryIndexCursor.h"
+#include "MemoryObjectStore.h"
+#include "MemoryObjectStoreCursor.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+// The IndexedDB spec states the value you can get from the key generator is 2^53
+static uint64_t maxGeneratedKeyValue = 0x20000000000000;
+
+std::unique_ptr<MemoryIDBBackingStore> MemoryIDBBackingStore::create(const IDBDatabaseIdentifier& identifier)
+{
+ return std::make_unique<MemoryIDBBackingStore>(identifier);
+}
+
+MemoryIDBBackingStore::MemoryIDBBackingStore(const IDBDatabaseIdentifier& identifier)
+ : m_identifier(identifier)
+{
+}
+
+MemoryIDBBackingStore::~MemoryIDBBackingStore()
+{
+}
+
+IDBError MemoryIDBBackingStore::getOrEstablishDatabaseInfo(IDBDatabaseInfo& info)
+{
+ if (!m_databaseInfo)
+ m_databaseInfo = std::make_unique<IDBDatabaseInfo>(m_identifier.databaseName(), 0);
+
+ info = *m_databaseInfo;
+ return { };
+}
+
+void MemoryIDBBackingStore::setDatabaseInfo(const IDBDatabaseInfo& info)
+{
+ // It is not valid to directly set database info on a backing store that hasn't already set its own database info.
+ ASSERT(m_databaseInfo);
+
+ m_databaseInfo = std::make_unique<IDBDatabaseInfo>(info);
+}
+
+IDBError MemoryIDBBackingStore::beginTransaction(const IDBTransactionInfo& info)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::beginTransaction");
+
+ if (m_transactions.contains(info.identifier()))
+ return { IDBDatabaseException::InvalidStateError, "Backing store asked to create transaction it already has a record of" };
+
+ auto transaction = MemoryBackingStoreTransaction::create(*this, info);
+
+ // VersionChange transactions are scoped to "every object store".
+ if (transaction->isVersionChange()) {
+ for (auto& objectStore : m_objectStoresByIdentifier.values())
+ transaction->addExistingObjectStore(*objectStore);
+ } else if (transaction->isWriting()) {
+ for (auto& iterator : m_objectStoresByName) {
+ if (info.objectStores().contains(iterator.key))
+ transaction->addExistingObjectStore(*iterator.value);
+ }
+ }
+
+ m_transactions.set(info.identifier(), WTFMove(transaction));
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::abortTransaction(const IDBResourceIdentifier& transactionIdentifier)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::abortTransaction - %s", transactionIdentifier.loggingString().utf8().data());
+
+ auto transaction = m_transactions.take(transactionIdentifier);
+ if (!transaction)
+ return { IDBDatabaseException::InvalidStateError, "Backing store asked to abort transaction it didn't have record of" };
+
+ transaction->abort();
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::commitTransaction(const IDBResourceIdentifier& transactionIdentifier)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::commitTransaction - %s", transactionIdentifier.loggingString().utf8().data());
+
+ auto transaction = m_transactions.take(transactionIdentifier);
+ if (!transaction)
+ return { IDBDatabaseException::InvalidStateError, "Backing store asked to commit transaction it didn't have record of" };
+
+ transaction->commit();
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::createObjectStore(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo& info)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::createObjectStore - adding OS %s with ID %" PRIu64, info.name().utf8().data(), info.identifier());
+
+ ASSERT(m_databaseInfo);
+ if (m_databaseInfo->hasObjectStore(info.name()))
+ return { IDBDatabaseException::ConstraintError };
+
+ ASSERT(!m_objectStoresByIdentifier.contains(info.identifier()));
+ auto objectStore = MemoryObjectStore::create(info);
+
+ m_databaseInfo->addExistingObjectStore(info);
+
+ auto rawTransaction = m_transactions.get(transactionIdentifier);
+ ASSERT(rawTransaction);
+ ASSERT(rawTransaction->isVersionChange());
+
+ rawTransaction->addNewObjectStore(objectStore.get());
+ registerObjectStore(WTFMove(objectStore));
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::deleteObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::deleteObjectStore");
+
+ ASSERT(m_databaseInfo);
+ if (!m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier))
+ return { IDBDatabaseException::ConstraintError };
+
+ auto transaction = m_transactions.get(transactionIdentifier);
+ ASSERT(transaction);
+ ASSERT(transaction->isVersionChange());
+
+ auto objectStore = takeObjectStoreByIdentifier(objectStoreIdentifier);
+ ASSERT(objectStore);
+ if (!objectStore)
+ return { IDBDatabaseException::ConstraintError };
+
+ m_databaseInfo->deleteObjectStore(objectStore->info().name());
+ transaction->objectStoreDeleted(*objectStore);
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::renameObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::renameObjectStore");
+
+ ASSERT(m_databaseInfo);
+ if (!m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier))
+ return { IDBDatabaseException::ConstraintError };
+
+ auto transaction = m_transactions.get(transactionIdentifier);
+ ASSERT(transaction);
+ ASSERT(transaction->isVersionChange());
+
+ auto objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ ASSERT(objectStore);
+ if (!objectStore)
+ return { IDBDatabaseException::ConstraintError };
+
+ String oldName = objectStore->info().name();
+ objectStore->rename(newName);
+ transaction->objectStoreRenamed(*objectStore, oldName);
+
+ m_databaseInfo->renameObjectStore(objectStoreIdentifier, newName);
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::clearObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::clearObjectStore");
+ ASSERT(objectStoreIdentifier);
+
+ ASSERT_UNUSED(transactionIdentifier, m_transactions.contains(transactionIdentifier));
+
+#if !LOG_DISABLED
+ auto transaction = m_transactions.get(transactionIdentifier);
+ ASSERT(transaction->isWriting());
+#endif
+
+ auto objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ if (!objectStore)
+ return { IDBDatabaseException::ConstraintError };
+
+ objectStore->clear();
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::createIndex(const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo& info)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::createIndex");
+
+ auto rawTransaction = m_transactions.get(transactionIdentifier);
+ ASSERT(rawTransaction);
+ ASSERT(rawTransaction->isVersionChange());
+
+ auto* objectStore = m_objectStoresByIdentifier.get(info.objectStoreIdentifier());
+ if (!objectStore)
+ return { IDBDatabaseException::ConstraintError };
+
+ return objectStore->createIndex(*rawTransaction, info);
+}
+
+IDBError MemoryIDBBackingStore::deleteIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::deleteIndex");
+
+ auto rawTransaction = m_transactions.get(transactionIdentifier);
+ ASSERT(rawTransaction);
+ ASSERT(rawTransaction->isVersionChange());
+
+ auto* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ if (!objectStore)
+ return { IDBDatabaseException::ConstraintError };
+
+ return objectStore->deleteIndex(*rawTransaction, indexIdentifier);
+}
+
+IDBError MemoryIDBBackingStore::renameIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::renameIndex");
+
+ ASSERT(m_databaseInfo);
+ auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ if (!objectStoreInfo)
+ return { IDBDatabaseException::ConstraintError };
+
+ auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexIdentifier);
+ if (!indexInfo)
+ return { IDBDatabaseException::ConstraintError };
+
+ auto transaction = m_transactions.get(transactionIdentifier);
+ ASSERT(transaction);
+ ASSERT(transaction->isVersionChange());
+
+ auto objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ ASSERT(objectStore);
+ if (!objectStore)
+ return { IDBDatabaseException::ConstraintError };
+
+ auto* index = objectStore->indexForIdentifier(indexIdentifier);
+ ASSERT(index);
+ if (!index)
+ return { IDBDatabaseException::ConstraintError };
+
+ String oldName = index->info().name();
+ objectStore->renameIndex(*index, newName);
+ transaction->indexRenamed(*index, oldName);
+
+ indexInfo->rename(newName);
+
+ return { };
+}
+
+void MemoryIDBBackingStore::removeObjectStoreForVersionChangeAbort(MemoryObjectStore& objectStore)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::removeObjectStoreForVersionChangeAbort");
+
+ if (!m_objectStoresByIdentifier.contains(objectStore.info().identifier()))
+ return;
+
+ ASSERT(m_objectStoresByIdentifier.get(objectStore.info().identifier()) == &objectStore);
+
+ unregisterObjectStore(objectStore);
+}
+
+void MemoryIDBBackingStore::restoreObjectStoreForVersionChangeAbort(Ref<MemoryObjectStore>&& objectStore)
+{
+ registerObjectStore(WTFMove(objectStore));
+}
+
+IDBError MemoryIDBBackingStore::keyExistsInObjectStore(const IDBResourceIdentifier&, uint64_t objectStoreIdentifier, const IDBKeyData& keyData, bool& keyExists)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::keyExistsInObjectStore");
+
+ ASSERT(objectStoreIdentifier);
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ RELEASE_ASSERT(objectStore);
+
+ keyExists = objectStore->containsRecord(keyData);
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::deleteRange(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData& range)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::deleteRange");
+
+ ASSERT(objectStoreIdentifier);
+
+ if (!m_transactions.contains(transactionIdentifier))
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found to delete from") };
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found") };
+
+ objectStore->deleteRange(range);
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::addRecord(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo& objectStoreInfo, const IDBKeyData& keyData, const IDBValue& value)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::addRecord");
+
+ ASSERT(objectStoreInfo.identifier());
+
+ auto transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found to put record") };
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreInfo.identifier());
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found to put record") };
+
+ return objectStore->addRecord(*transaction, keyData, value);
+}
+
+IDBError MemoryIDBBackingStore::getRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData& range, IDBGetRecordDataType type, IDBGetResult& outValue)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::getRecord");
+
+ ASSERT(objectStoreIdentifier);
+
+ if (!m_transactions.contains(transactionIdentifier))
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found to get record") };
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found") };
+
+ switch (type) {
+ case IDBGetRecordDataType::KeyAndValue:
+ outValue = objectStore->valueForKeyRange(range);
+ break;
+ case IDBGetRecordDataType::KeyOnly:
+ outValue = objectStore->lowestKeyWithRecordInRange(range);
+ break;
+ }
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::getAllRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData& getAllRecordsData, IDBGetAllResult& result)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::getAllRecords");
+
+ ASSERT(getAllRecordsData.objectStoreIdentifier);
+
+ if (!m_transactions.contains(transactionIdentifier))
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found to get all records") };
+
+ auto* objectStore = m_objectStoresByIdentifier.get(getAllRecordsData.objectStoreIdentifier);
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found") };
+
+ if (getAllRecordsData.indexIdentifier) {
+ auto* index = objectStore->indexForIdentifier(getAllRecordsData.indexIdentifier);
+ if (!index)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store index found") };
+
+ index->getAllRecords(getAllRecordsData.keyRangeData, getAllRecordsData.count, getAllRecordsData.getAllType, result);
+ } else
+ objectStore->getAllRecords(getAllRecordsData.keyRangeData, getAllRecordsData.count, getAllRecordsData.getAllType, result);
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::getIndexRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, IndexedDB::IndexRecordType recordType, const IDBKeyRangeData& range, IDBGetResult& outValue)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::getIndexRecord");
+
+ ASSERT(objectStoreIdentifier);
+
+ if (!m_transactions.contains(transactionIdentifier))
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found to get record") };
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found") };
+
+ outValue = objectStore->indexValueForKeyRange(indexIdentifier, recordType, range);
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::getCount(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData& range, uint64_t& outCount)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::getCount");
+
+ ASSERT(objectStoreIdentifier);
+
+ if (!m_transactions.contains(transactionIdentifier))
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found to get count") };
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found") };
+
+ outCount = objectStore->countForKeyRange(indexIdentifier, range);
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::generateKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t& keyNumber)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::generateKeyNumber");
+ ASSERT(objectStoreIdentifier);
+ ASSERT_UNUSED(transactionIdentifier, m_transactions.contains(transactionIdentifier));
+ ASSERT_UNUSED(transactionIdentifier, m_transactions.get(transactionIdentifier)->isWriting());
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ RELEASE_ASSERT(objectStore);
+
+ keyNumber = objectStore->currentKeyGeneratorValue();
+ if (keyNumber > maxGeneratedKeyValue)
+ return { IDBDatabaseException::ConstraintError, "Cannot generate new key value over 2^53 for object store operation" };
+
+ objectStore->setKeyGeneratorValue(keyNumber + 1);
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::revertGeneratedKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t keyNumber)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::revertGeneratedKeyNumber");
+ ASSERT(objectStoreIdentifier);
+ ASSERT_UNUSED(transactionIdentifier, m_transactions.contains(transactionIdentifier));
+ ASSERT_UNUSED(transactionIdentifier, m_transactions.get(transactionIdentifier)->isWriting());
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ RELEASE_ASSERT(objectStore);
+
+ objectStore->setKeyGeneratorValue(keyNumber);
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::maybeUpdateKeyGeneratorNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, double newKeyNumber)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::maybeUpdateKeyGeneratorNumber");
+ ASSERT(objectStoreIdentifier);
+ ASSERT_UNUSED(transactionIdentifier, m_transactions.contains(transactionIdentifier));
+ ASSERT_UNUSED(transactionIdentifier, m_transactions.get(transactionIdentifier)->isWriting());
+
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
+ RELEASE_ASSERT(objectStore);
+
+ if (newKeyNumber < objectStore->currentKeyGeneratorValue())
+ return { };
+
+ uint64_t newKeyInteger(newKeyNumber);
+ if (newKeyInteger <= uint64_t(newKeyNumber))
+ ++newKeyInteger;
+
+ ASSERT(newKeyInteger > uint64_t(newKeyNumber));
+
+ objectStore->setKeyGeneratorValue(newKeyInteger);
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::openCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo& info, IDBGetResult& outData)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::openCursor");
+
+ ASSERT(!MemoryCursor::cursorForIdentifier(info.identifier()));
+
+ if (!m_transactions.contains(transactionIdentifier))
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found in which to open a cursor") };
+
+ switch (info.cursorSource()) {
+ case IndexedDB::CursorSource::ObjectStore: {
+ auto* objectStore = m_objectStoresByIdentifier.get(info.sourceIdentifier());
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found") };
+
+ MemoryCursor* cursor = objectStore->maybeOpenCursor(info);
+ if (!cursor)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not create object store cursor in backing store") };
+
+ cursor->currentData(outData);
+ break;
+ }
+ case IndexedDB::CursorSource::Index:
+ auto* objectStore = m_objectStoresByIdentifier.get(info.objectStoreIdentifier());
+ if (!objectStore)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store object store found") };
+
+ auto* index = objectStore->indexForIdentifier(info.sourceIdentifier());
+ if (!index)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store index found") };
+
+ MemoryCursor* cursor = index->maybeOpenCursor(info);
+ if (!cursor)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not create index cursor in backing store") };
+
+ cursor->currentData(outData);
+ break;
+ }
+
+ return { };
+}
+
+IDBError MemoryIDBBackingStore::iterateCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData& data, IDBGetResult& outData)
+{
+ LOG(IndexedDB, "MemoryIDBBackingStore::iterateCursor");
+
+ if (!m_transactions.contains(transactionIdentifier))
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store transaction found in which to iterate cursor") };
+
+ auto* cursor = MemoryCursor::cursorForIdentifier(cursorIdentifier);
+ if (!cursor)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No backing store cursor found in which to iterate cursor") };
+
+ cursor->iterate(data.keyData, data.primaryKeyData, data.count, outData);
+
+ return { };
+}
+
+void MemoryIDBBackingStore::registerObjectStore(Ref<MemoryObjectStore>&& objectStore)
+{
+ ASSERT(!m_objectStoresByIdentifier.contains(objectStore->info().identifier()));
+ ASSERT(!m_objectStoresByName.contains(objectStore->info().name()));
+
+ m_objectStoresByName.set(objectStore->info().name(), &objectStore.get());
+ m_objectStoresByIdentifier.set(objectStore->info().identifier(), WTFMove(objectStore));
+}
+
+void MemoryIDBBackingStore::unregisterObjectStore(MemoryObjectStore& objectStore)
+{
+ ASSERT(m_objectStoresByIdentifier.contains(objectStore.info().identifier()));
+ ASSERT(m_objectStoresByName.contains(objectStore.info().name()));
+
+ m_objectStoresByName.remove(objectStore.info().name());
+ m_objectStoresByIdentifier.remove(objectStore.info().identifier());
+}
+
+RefPtr<MemoryObjectStore> MemoryIDBBackingStore::takeObjectStoreByIdentifier(uint64_t identifier)
+{
+ auto objectStoreByIdentifier = m_objectStoresByIdentifier.take(identifier);
+ if (!objectStoreByIdentifier)
+ return nullptr;
+
+ auto objectStore = m_objectStoresByName.take(objectStoreByIdentifier->info().name());
+ ASSERT_UNUSED(objectStore, objectStore);
+
+ return objectStoreByIdentifier;
+}
+
+IDBObjectStoreInfo* MemoryIDBBackingStore::infoForObjectStore(uint64_t objectStoreIdentifier)
+{
+ ASSERT(m_databaseInfo);
+ return m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+}
+
+void MemoryIDBBackingStore::deleteBackingStore()
+{
+ // The in-memory IDB backing store doesn't need to do any cleanup when it is deleted.
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.h b/Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.h
new file mode 100644
index 000000000..e93a17df3
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBBackingStore.h"
+#include "IDBDatabaseIdentifier.h"
+#include "IDBResourceIdentifier.h"
+#include "MemoryBackingStoreTransaction.h"
+#include <wtf/HashMap.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+class MemoryObjectStore;
+
+class MemoryIDBBackingStore : public IDBBackingStore {
+public:
+ static std::unique_ptr<MemoryIDBBackingStore> create(const IDBDatabaseIdentifier&);
+
+ MemoryIDBBackingStore(const IDBDatabaseIdentifier&);
+ ~MemoryIDBBackingStore() final;
+
+ IDBError getOrEstablishDatabaseInfo(IDBDatabaseInfo&) final;
+ void setDatabaseInfo(const IDBDatabaseInfo&);
+
+ IDBError beginTransaction(const IDBTransactionInfo&) final;
+ IDBError abortTransaction(const IDBResourceIdentifier& transactionIdentifier) final;
+ IDBError commitTransaction(const IDBResourceIdentifier& transactionIdentifier) final;
+ IDBError createObjectStore(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&) final;
+ IDBError deleteObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) final;
+ IDBError renameObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName) final;
+ IDBError clearObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) final;
+ IDBError createIndex(const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo&) final;
+ IDBError deleteIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier) final;
+ IDBError renameIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName) final;
+ IDBError keyExistsInObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData&, bool& keyExists) final;
+ IDBError deleteRange(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&) final;
+ IDBError addRecord(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&, const IDBKeyData&, const IDBValue&) final;
+ IDBError getRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&, IDBGetRecordDataType, IDBGetResult& outValue) final;
+ IDBError getAllRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData&, IDBGetAllResult& outValue) final;
+ IDBError getIndexRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, IndexedDB::IndexRecordType, const IDBKeyRangeData&, IDBGetResult& outValue) final;
+ IDBError getCount(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData&, uint64_t& outCount) final;
+ IDBError generateKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t& keyNumber) final;
+ IDBError revertGeneratedKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t keyNumber) final;
+ IDBError maybeUpdateKeyGeneratorNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, double newKeyNumber) final;
+ IDBError openCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo&, IDBGetResult& outResult) final;
+ IDBError iterateCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData&, IDBGetResult& outResult) final;
+ bool prefetchCursor(const IDBResourceIdentifier&, const IDBResourceIdentifier&) final { return false; }
+
+ IDBObjectStoreInfo* infoForObjectStore(uint64_t objectStoreIdentifier) final;
+ void deleteBackingStore() final;
+
+ bool supportsSimultaneousTransactions() final { return true; }
+ bool isEphemeral() final { return true; }
+
+ void removeObjectStoreForVersionChangeAbort(MemoryObjectStore&);
+ void restoreObjectStoreForVersionChangeAbort(Ref<MemoryObjectStore>&&);
+
+private:
+ RefPtr<MemoryObjectStore> takeObjectStoreByIdentifier(uint64_t identifier);
+
+ IDBDatabaseIdentifier m_identifier;
+ std::unique_ptr<IDBDatabaseInfo> m_databaseInfo;
+
+ HashMap<IDBResourceIdentifier, std::unique_ptr<MemoryBackingStoreTransaction>> m_transactions;
+
+ void registerObjectStore(Ref<MemoryObjectStore>&&);
+ void unregisterObjectStore(MemoryObjectStore&);
+ HashMap<uint64_t, RefPtr<MemoryObjectStore>> m_objectStoresByIdentifier;
+ HashMap<String, MemoryObjectStore*> m_objectStoresByName;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryIndex.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryIndex.cpp
new file mode 100644
index 000000000..e92d26801
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryIndex.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryIndex.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBError.h"
+#include "IDBGetAllResult.h"
+#include "IDBGetResult.h"
+#include "IDBKeyRangeData.h"
+#include "IndexKey.h"
+#include "Logging.h"
+#include "MemoryBackingStoreTransaction.h"
+#include "MemoryIndexCursor.h"
+#include "MemoryObjectStore.h"
+#include "ThreadSafeDataBuffer.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+Ref<MemoryIndex> MemoryIndex::create(const IDBIndexInfo& info, MemoryObjectStore& objectStore)
+{
+ return adoptRef(*new MemoryIndex(info, objectStore));
+}
+
+MemoryIndex::MemoryIndex(const IDBIndexInfo& info, MemoryObjectStore& objectStore)
+ : m_info(info)
+ , m_objectStore(objectStore)
+{
+}
+
+MemoryIndex::~MemoryIndex()
+{
+}
+
+void MemoryIndex::cursorDidBecomeClean(MemoryIndexCursor& cursor)
+{
+ m_cleanCursors.add(&cursor);
+}
+
+void MemoryIndex::cursorDidBecomeDirty(MemoryIndexCursor& cursor)
+{
+ m_cleanCursors.remove(&cursor);
+}
+
+void MemoryIndex::objectStoreCleared()
+{
+ auto transaction = m_objectStore.writeTransaction();
+ ASSERT(transaction);
+
+ transaction->indexCleared(*this, WTFMove(m_records));
+
+ notifyCursorsOfAllRecordsChanged();
+}
+
+void MemoryIndex::notifyCursorsOfValueChange(const IDBKeyData& indexKey, const IDBKeyData& primaryKey)
+{
+ Vector<MemoryIndexCursor*> cursors;
+ copyToVector(m_cleanCursors, cursors);
+ for (auto* cursor : cursors)
+ cursor->indexValueChanged(indexKey, primaryKey);
+}
+
+void MemoryIndex::notifyCursorsOfAllRecordsChanged()
+{
+ Vector<MemoryIndexCursor*> cursors;
+ copyToVector(m_cleanCursors, cursors);
+ for (auto* cursor : cursors)
+ cursor->indexRecordsAllChanged();
+
+ ASSERT(m_cleanCursors.isEmpty());
+}
+
+void MemoryIndex::clearIndexValueStore()
+{
+ ASSERT(m_objectStore.writeTransaction());
+ ASSERT(m_objectStore.writeTransaction()->isAborting());
+
+ m_records = nullptr;
+}
+
+void MemoryIndex::replaceIndexValueStore(std::unique_ptr<IndexValueStore>&& valueStore)
+{
+ ASSERT(m_objectStore.writeTransaction());
+ ASSERT(m_objectStore.writeTransaction()->isAborting());
+
+ m_records = WTFMove(valueStore);
+}
+
+IDBGetResult MemoryIndex::getResultForKeyRange(IndexedDB::IndexRecordType type, const IDBKeyRangeData& range) const
+{
+ LOG(IndexedDB, "MemoryIndex::getResultForKeyRange - %s", range.loggingString().utf8().data());
+
+ if (!m_records)
+ return { };
+
+ IDBKeyData keyToLookFor;
+ if (range.isExactlyOneKey())
+ keyToLookFor = range.lowerKey;
+ else
+ keyToLookFor = m_records->lowestKeyWithRecordInRange(range);
+
+ if (keyToLookFor.isNull())
+ return { };
+
+ const IDBKeyData* keyValue = m_records->lowestValueForKey(keyToLookFor);
+
+ if (!keyValue)
+ return { };
+
+ return type == IndexedDB::IndexRecordType::Key ? IDBGetResult(*keyValue) : IDBGetResult(m_objectStore.valueForKeyRange(*keyValue));
+}
+
+uint64_t MemoryIndex::countForKeyRange(const IDBKeyRangeData& inRange)
+{
+ LOG(IndexedDB, "MemoryIndex::countForKeyRange");
+
+ if (!m_records)
+ return 0;
+
+ uint64_t count = 0;
+ IDBKeyRangeData range = inRange;
+ while (true) {
+ auto key = m_records->lowestKeyWithRecordInRange(range);
+ if (key.isNull())
+ break;
+
+ count += m_records->countForKey(key);
+
+ range.lowerKey = key;
+ range.lowerOpen = true;
+ }
+
+ return count;
+}
+
+void MemoryIndex::getAllRecords(const IDBKeyRangeData& keyRangeData, std::optional<uint32_t> count, IndexedDB::GetAllType type, IDBGetAllResult& result) const
+{
+ LOG(IndexedDB, "MemoryIndex::getAllRecords");
+
+ result = { type };
+
+ if (!m_records)
+ return;
+
+ uint32_t targetCount;
+ if (count && count.value())
+ targetCount = count.value();
+ else
+ targetCount = std::numeric_limits<uint32_t>::max();
+
+ IDBKeyRangeData range = keyRangeData;
+ uint32_t currentCount = 0;
+ while (currentCount < targetCount) {
+ auto key = m_records->lowestKeyWithRecordInRange(range);
+ if (key.isNull())
+ return;
+
+ range.lowerKey = key;
+ range.lowerOpen = true;
+
+ auto allValues = m_records->allValuesForKey(key, targetCount - currentCount);
+ for (auto& keyValue : allValues) {
+ if (type == IndexedDB::GetAllType::Keys) {
+ IDBKeyData keyCopy { keyValue };
+ result.addKey(WTFMove(keyCopy));
+ } else
+ result.addValue(m_objectStore.valueForKeyRange(keyValue));
+ }
+
+ currentCount += allValues.size();
+ }
+}
+
+
+IDBError MemoryIndex::putIndexKey(const IDBKeyData& valueKey, const IndexKey& indexKey)
+{
+ LOG(IndexedDB, "MemoryIndex::provisionalPutIndexKey");
+
+ if (!m_records) {
+ m_records = std::make_unique<IndexValueStore>(m_info.unique());
+ notifyCursorsOfAllRecordsChanged();
+ }
+
+ if (!m_info.multiEntry()) {
+ IDBKeyData key = indexKey.asOneKey();
+ IDBError result = m_records->addRecord(key, valueKey);
+ notifyCursorsOfValueChange(key, valueKey);
+ return result;
+ }
+
+ Vector<IDBKeyData> keys = indexKey.multiEntry();
+
+ if (m_info.unique()) {
+ for (auto& key : keys) {
+ if (m_records->contains(key))
+ return IDBError(IDBDatabaseException::ConstraintError);
+ }
+ }
+
+ for (auto& key : keys) {
+ auto error = m_records->addRecord(key, valueKey);
+ ASSERT_UNUSED(error, error.isNull());
+ notifyCursorsOfValueChange(key, valueKey);
+ }
+
+ return { };
+}
+
+void MemoryIndex::removeRecord(const IDBKeyData& valueKey, const IndexKey& indexKey)
+{
+ LOG(IndexedDB, "MemoryIndex::removeRecord");
+
+ ASSERT(m_records);
+
+ if (!m_info.multiEntry()) {
+ IDBKeyData key = indexKey.asOneKey();
+ m_records->removeRecord(key, valueKey);
+ notifyCursorsOfValueChange(key, valueKey);
+ return;
+ }
+
+ Vector<IDBKeyData> keys = indexKey.multiEntry();
+ for (auto& key : keys) {
+ m_records->removeRecord(key, valueKey);
+ notifyCursorsOfValueChange(key, valueKey);
+ }
+}
+
+void MemoryIndex::removeEntriesWithValueKey(const IDBKeyData& valueKey)
+{
+ LOG(IndexedDB, "MemoryIndex::removeEntriesWithValueKey");
+
+ if (!m_records)
+ return;
+
+ m_records->removeEntriesWithValueKey(*this, valueKey);
+}
+
+MemoryIndexCursor* MemoryIndex::maybeOpenCursor(const IDBCursorInfo& info)
+{
+ auto result = m_cursors.add(info.identifier(), nullptr);
+ if (!result.isNewEntry)
+ return nullptr;
+
+ result.iterator->value = std::make_unique<MemoryIndexCursor>(*this, info);
+ return result.iterator->value.get();
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryIndex.h b/Source/WebCore/Modules/indexeddb/server/MemoryIndex.h
new file mode 100644
index 000000000..5bf9cba89
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryIndex.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBIndexInfo.h"
+#include "IDBResourceIdentifier.h"
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+
+namespace WebCore {
+
+class IDBCursorInfo;
+class IDBError;
+class IDBGetAllResult;
+class IDBGetResult;
+class IDBKeyData;
+class IndexKey;
+class ThreadSafeDataBuffer;
+
+struct IDBKeyRangeData;
+
+namespace IndexedDB {
+enum class GetAllType;
+enum class IndexRecordType;
+}
+
+namespace IDBServer {
+
+class IndexValueStore;
+class MemoryBackingStoreTransaction;
+class MemoryIndexCursor;
+class MemoryObjectStore;
+
+class MemoryIndex : public RefCounted<MemoryIndex> {
+public:
+ static Ref<MemoryIndex> create(const IDBIndexInfo&, MemoryObjectStore&);
+
+ ~MemoryIndex();
+
+ const IDBIndexInfo& info() const { return m_info; }
+
+ void rename(const String& newName) { m_info.rename(newName); }
+
+ IDBGetResult getResultForKeyRange(IndexedDB::IndexRecordType, const IDBKeyRangeData&) const;
+ uint64_t countForKeyRange(const IDBKeyRangeData&);
+ void getAllRecords(const IDBKeyRangeData&, std::optional<uint32_t> count, IndexedDB::GetAllType, IDBGetAllResult&) const;
+
+ IDBError putIndexKey(const IDBKeyData&, const IndexKey&);
+
+ void removeEntriesWithValueKey(const IDBKeyData&);
+ void removeRecord(const IDBKeyData&, const IndexKey&);
+
+ void objectStoreCleared();
+ void clearIndexValueStore();
+ void replaceIndexValueStore(std::unique_ptr<IndexValueStore>&&);
+
+ MemoryIndexCursor* maybeOpenCursor(const IDBCursorInfo&);
+
+ IndexValueStore* valueStore() { return m_records.get(); }
+
+ MemoryObjectStore& objectStore() { return m_objectStore; }
+
+ void cursorDidBecomeClean(MemoryIndexCursor&);
+ void cursorDidBecomeDirty(MemoryIndexCursor&);
+
+ void notifyCursorsOfValueChange(const IDBKeyData& indexKey, const IDBKeyData& primaryKey);
+
+private:
+ MemoryIndex(const IDBIndexInfo&, MemoryObjectStore&);
+
+ uint64_t recordCountForKey(const IDBKeyData&) const;
+
+ void notifyCursorsOfAllRecordsChanged();
+
+ IDBIndexInfo m_info;
+ MemoryObjectStore& m_objectStore;
+
+ std::unique_ptr<IndexValueStore> m_records;
+
+ HashMap<IDBResourceIdentifier, std::unique_ptr<MemoryIndexCursor>> m_cursors;
+ HashSet<MemoryIndexCursor*> m_cleanCursors;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp
new file mode 100644
index 000000000..72b6eab56
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryIndexCursor.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+#include "IDBGetResult.h"
+#include "IndexValueStore.h"
+#include "Logging.h"
+#include "MemoryCursor.h"
+#include "MemoryIndex.h"
+#include "MemoryObjectStore.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+MemoryIndexCursor::MemoryIndexCursor(MemoryIndex& index, const IDBCursorInfo& info)
+ : MemoryCursor(info)
+ , m_index(index)
+{
+ LOG(IndexedDB, "MemoryIndexCursor::MemoryIndexCursor %s", info.range().loggingString().utf8().data());
+
+ auto* valueStore = m_index.valueStore();
+ if (!valueStore)
+ return;
+
+ if (m_info.isDirectionForward())
+ m_currentIterator = valueStore->find(m_info.range().lowerKey, m_info.range().lowerOpen);
+ else
+ m_currentIterator = valueStore->reverseFind(m_info.range().upperKey, m_info.duplicity(), m_info.range().upperOpen);
+
+ if (m_currentIterator.isValid() && m_info.range().containsKey(m_currentIterator.key())) {
+ m_currentKey = m_currentIterator.key();
+ m_currentPrimaryKey = m_currentIterator.primaryKey();
+ m_index.cursorDidBecomeClean(*this);
+ } else
+ m_currentIterator.invalidate();
+}
+
+MemoryIndexCursor::~MemoryIndexCursor()
+{
+}
+
+void MemoryIndexCursor::currentData(IDBGetResult& getResult)
+{
+ if (!m_currentIterator.isValid()) {
+ getResult = { };
+ return;
+ }
+
+ if (m_info.cursorType() == IndexedDB::CursorType::KeyOnly)
+ getResult = { m_currentKey, m_currentPrimaryKey };
+ else {
+ IDBValue value = { m_index.objectStore().valueForKey(m_currentPrimaryKey), { }, { } };
+ getResult = { m_currentKey, m_currentPrimaryKey, WTFMove(value) };
+ }
+}
+
+void MemoryIndexCursor::iterate(const IDBKeyData& key, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult& getResult)
+{
+ LOG(IndexedDB, "MemoryIndexCursor::iterate to key %s, %u count", key.loggingString().utf8().data(), count);
+
+#ifndef NDEBUG
+ if (primaryKey.isValid())
+ ASSERT(key.isValid());
+#endif
+
+ if (key.isValid()) {
+ // Cannot iterate by both a count and to a key
+ ASSERT(!count);
+
+ auto* valueStore = m_index.valueStore();
+ if (!valueStore) {
+ m_currentKey = { };
+ m_currentPrimaryKey = { };
+ getResult = { };
+ return;
+ }
+
+ if (primaryKey.isValid()) {
+ if (m_info.isDirectionForward())
+ m_currentIterator = valueStore->find(key, primaryKey);
+ else
+ m_currentIterator = valueStore->reverseFind(key, primaryKey, m_info.duplicity());
+ } else {
+ if (m_info.isDirectionForward())
+ m_currentIterator = valueStore->find(key);
+ else
+ m_currentIterator = valueStore->reverseFind(key, m_info.duplicity());
+ }
+
+ if (m_currentIterator.isValid() && !m_info.range().containsKey(m_currentIterator.key()))
+ m_currentIterator.invalidate();
+
+ if (!m_currentIterator.isValid()) {
+ m_currentKey = { };
+ m_currentPrimaryKey = { };
+ getResult = { };
+ return;
+ }
+
+ m_index.cursorDidBecomeClean(*this);
+
+ m_currentKey = m_currentIterator.key();
+ m_currentPrimaryKey = m_currentIterator.primaryKey();
+ currentData(getResult);
+
+ return;
+ }
+
+ // If there was not a valid key argument and no positive count argument
+ // that means the default iteration count of "1"
+ if (!count)
+ count = 1;
+
+ if (!m_currentIterator.isValid()) {
+ auto* valueStore = m_index.valueStore();
+ if (!valueStore) {
+ m_currentKey = { };
+ m_currentPrimaryKey = { };
+ getResult = { };
+ return;
+ }
+
+ switch (m_info.cursorDirection()) {
+ case IndexedDB::CursorDirection::Next:
+ m_currentIterator = valueStore->find(m_currentKey, m_currentPrimaryKey);
+ break;
+ case IndexedDB::CursorDirection::Nextunique:
+ m_currentIterator = valueStore->find(m_currentKey, true);
+ break;
+ case IndexedDB::CursorDirection::Prev:
+ m_currentIterator = valueStore->reverseFind(m_currentKey, m_currentPrimaryKey, m_info.duplicity());
+ break;
+ case IndexedDB::CursorDirection::Prevunique:
+ m_currentIterator = valueStore->reverseFind(m_currentKey, m_info.duplicity(), true);
+ break;
+ }
+
+ if (!m_currentIterator.isValid()) {
+ m_currentKey = { };
+ m_currentPrimaryKey = { };
+ getResult = { };
+ return;
+ }
+
+ m_index.cursorDidBecomeClean(*this);
+
+ // If we restored the current iterator and it does *not* match the current key/primaryKey,
+ // then it is the next record in line and we should consider that an iteration.
+ if (m_currentKey != m_currentIterator.key() || m_currentPrimaryKey != m_currentIterator.primaryKey())
+ --count;
+ }
+
+ ASSERT(m_currentIterator.isValid());
+
+ while (count) {
+ if (m_info.duplicity() == CursorDuplicity::NoDuplicates)
+ m_currentIterator.nextIndexEntry();
+ else
+ ++m_currentIterator;
+
+ if (!m_currentIterator.isValid())
+ break;
+
+ --count;
+ }
+
+ if (m_currentIterator.isValid() && !m_info.range().containsKey(m_currentIterator.key()))
+ m_currentIterator.invalidate();
+
+ // Not having a valid iterator after finishing any iteration means we've reached the end of the cursor.
+ if (!m_currentIterator.isValid()) {
+ m_currentKey = { };
+ m_currentPrimaryKey = { };
+ getResult = { };
+ return;
+ }
+
+ m_currentKey = m_currentIterator.key();
+ m_currentPrimaryKey = m_currentIterator.primaryKey();
+ currentData(getResult);
+}
+
+void MemoryIndexCursor::indexRecordsAllChanged()
+{
+ m_currentIterator.invalidate();
+ m_index.cursorDidBecomeDirty(*this);
+}
+
+void MemoryIndexCursor::indexValueChanged(const IDBKeyData& key, const IDBKeyData& primaryKey)
+{
+ if (m_currentKey != key || m_currentPrimaryKey != primaryKey)
+ return;
+
+ m_currentIterator.invalidate();
+ m_index.cursorDidBecomeDirty(*this);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.h b/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.h
new file mode 100644
index 000000000..d55d20709
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+#include "IndexValueStore.h"
+#include "MemoryCursor.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+class MemoryIndex;
+
+class MemoryIndexCursor : public MemoryCursor {
+public:
+ MemoryIndexCursor(MemoryIndex&, const IDBCursorInfo&);
+ virtual ~MemoryIndexCursor();
+
+ void indexRecordsAllChanged();
+ void indexValueChanged(const IDBKeyData& indexKey, const IDBKeyData& primaryKey);
+
+private:
+ void currentData(IDBGetResult&) final;
+ void iterate(const IDBKeyData&, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult&) final;
+
+ MemoryIndex& m_index;
+
+ IndexValueStore::Iterator m_currentIterator;
+ IDBKeyData m_currentKey;
+ IDBKeyData m_currentPrimaryKey;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.cpp
new file mode 100644
index 000000000..34b4e129a
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.cpp
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryObjectStore.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBBindingUtilities.h"
+#include "IDBDatabaseException.h"
+#include "IDBError.h"
+#include "IDBGetAllResult.h"
+#include "IDBKeyRangeData.h"
+#include "IDBValue.h"
+#include "IndexKey.h"
+#include "Logging.h"
+#include "MemoryBackingStoreTransaction.h"
+#include "UniqueIDBDatabase.h"
+#include <runtime/JSCJSValue.h>
+#include <runtime/JSCJSValueInlines.h>
+#include <runtime/JSLock.h>
+#include <wtf/NeverDestroyed.h>
+
+using namespace JSC;
+
+namespace WebCore {
+namespace IDBServer {
+
+Ref<MemoryObjectStore> MemoryObjectStore::create(const IDBObjectStoreInfo& info)
+{
+ return adoptRef(*new MemoryObjectStore(info));
+}
+
+MemoryObjectStore::MemoryObjectStore(const IDBObjectStoreInfo& info)
+ : m_info(info)
+{
+}
+
+MemoryObjectStore::~MemoryObjectStore()
+{
+ m_writeTransaction = nullptr;
+}
+
+MemoryIndex* MemoryObjectStore::indexForIdentifier(uint64_t identifier)
+{
+ ASSERT(identifier);
+ return m_indexesByIdentifier.get(identifier);
+}
+
+void MemoryObjectStore::writeTransactionStarted(MemoryBackingStoreTransaction& transaction)
+{
+ LOG(IndexedDB, "MemoryObjectStore::writeTransactionStarted");
+
+ ASSERT(!m_writeTransaction);
+ m_writeTransaction = &transaction;
+}
+
+void MemoryObjectStore::writeTransactionFinished(MemoryBackingStoreTransaction& transaction)
+{
+ LOG(IndexedDB, "MemoryObjectStore::writeTransactionFinished");
+
+ ASSERT_UNUSED(transaction, m_writeTransaction == &transaction);
+ m_writeTransaction = nullptr;
+}
+
+IDBError MemoryObjectStore::createIndex(MemoryBackingStoreTransaction& transaction, const IDBIndexInfo& info)
+{
+ LOG(IndexedDB, "MemoryObjectStore::createIndex");
+
+ if (!m_writeTransaction || !m_writeTransaction->isVersionChange() || m_writeTransaction != &transaction)
+ return IDBError(IDBDatabaseException::ConstraintError);
+
+ ASSERT(!m_indexesByIdentifier.contains(info.identifier()));
+ auto index = MemoryIndex::create(info, *this);
+
+ // If there was an error populating the new index, then the current records in the object store violate its contraints
+ auto error = populateIndexWithExistingRecords(index.get());
+ if (!error.isNull())
+ return error;
+
+ m_info.addExistingIndex(info);
+ transaction.addNewIndex(index.get());
+ registerIndex(WTFMove(index));
+
+ return { };
+}
+
+void MemoryObjectStore::maybeRestoreDeletedIndex(Ref<MemoryIndex>&& index)
+{
+ LOG(IndexedDB, "MemoryObjectStore::maybeRestoreDeletedIndex");
+
+ if (m_info.hasIndex(index->info().name()))
+ return;
+
+ m_info.addExistingIndex(index->info());
+
+ ASSERT(!m_indexesByIdentifier.contains(index->info().identifier()));
+ index->clearIndexValueStore();
+ auto error = populateIndexWithExistingRecords(index.get());
+
+ // Since this index was installed in the object store before this transaction started,
+ // assuming things were in a valid state then, we should definitely be able to successfully
+ // repopulate the index with the object store's pre-transaction records.
+ ASSERT_UNUSED(error, error.isNull());
+
+ registerIndex(WTFMove(index));
+}
+
+RefPtr<MemoryIndex> MemoryObjectStore::takeIndexByIdentifier(uint64_t indexIdentifier)
+{
+ auto indexByIdentifier = m_indexesByIdentifier.take(indexIdentifier);
+ if (!indexByIdentifier)
+ return nullptr;
+
+ auto index = m_indexesByName.take(indexByIdentifier->info().name());
+ ASSERT(index);
+
+ return index;
+}
+
+IDBError MemoryObjectStore::deleteIndex(MemoryBackingStoreTransaction& transaction, uint64_t indexIdentifier)
+{
+ LOG(IndexedDB, "MemoryObjectStore::deleteIndex");
+
+ if (!m_writeTransaction || !m_writeTransaction->isVersionChange() || m_writeTransaction != &transaction)
+ return IDBError(IDBDatabaseException::ConstraintError);
+
+ auto index = takeIndexByIdentifier(indexIdentifier);
+ ASSERT(index);
+ if (!index)
+ return IDBError(IDBDatabaseException::ConstraintError);
+
+ m_info.deleteIndex(indexIdentifier);
+ transaction.indexDeleted(*index);
+
+ return { };
+}
+
+void MemoryObjectStore::deleteAllIndexes(MemoryBackingStoreTransaction& transaction)
+{
+ Vector<uint64_t> indexIdentifiers;
+ indexIdentifiers.reserveInitialCapacity(m_indexesByName.size());
+
+ for (auto& index : m_indexesByName.values())
+ indexIdentifiers.uncheckedAppend(index->info().identifier());
+
+ for (auto identifier : indexIdentifiers)
+ deleteIndex(transaction, identifier);
+}
+
+bool MemoryObjectStore::containsRecord(const IDBKeyData& key)
+{
+ if (!m_keyValueStore)
+ return false;
+
+ return m_keyValueStore->contains(key);
+}
+
+void MemoryObjectStore::clear()
+{
+ LOG(IndexedDB, "MemoryObjectStore::clear");
+ ASSERT(m_writeTransaction);
+
+ m_writeTransaction->objectStoreCleared(*this, WTFMove(m_keyValueStore), WTFMove(m_orderedKeys));
+ for (auto& index : m_indexesByIdentifier.values())
+ index->objectStoreCleared();
+
+ for (auto& cursor : m_cursors.values())
+ cursor->objectStoreCleared();
+}
+
+void MemoryObjectStore::replaceKeyValueStore(std::unique_ptr<KeyValueMap>&& store, std::unique_ptr<std::set<IDBKeyData>>&& orderedKeys)
+{
+ ASSERT(m_writeTransaction);
+ ASSERT(m_writeTransaction->isAborting());
+
+ m_keyValueStore = WTFMove(store);
+ m_orderedKeys = WTFMove(orderedKeys);
+}
+
+void MemoryObjectStore::deleteRecord(const IDBKeyData& key)
+{
+ LOG(IndexedDB, "MemoryObjectStore::deleteRecord");
+
+ ASSERT(m_writeTransaction);
+
+ if (!m_keyValueStore) {
+ m_writeTransaction->recordValueChanged(*this, key, nullptr);
+ return;
+ }
+
+ ASSERT(m_orderedKeys);
+
+ auto iterator = m_keyValueStore->find(key);
+ if (iterator == m_keyValueStore->end()) {
+ m_writeTransaction->recordValueChanged(*this, key, nullptr);
+ return;
+ }
+
+ m_writeTransaction->recordValueChanged(*this, key, &iterator->value);
+ m_keyValueStore->remove(iterator);
+ m_orderedKeys->erase(key);
+
+ updateIndexesForDeleteRecord(key);
+ updateCursorsForDeleteRecord(key);
+}
+
+void MemoryObjectStore::deleteRange(const IDBKeyRangeData& inputRange)
+{
+ LOG(IndexedDB, "MemoryObjectStore::deleteRange");
+
+ ASSERT(m_writeTransaction);
+
+ if (inputRange.isExactlyOneKey()) {
+ deleteRecord(inputRange.lowerKey);
+ return;
+ }
+
+ IDBKeyRangeData range = inputRange;
+ while (true) {
+ auto key = lowestKeyWithRecordInRange(range);
+ if (key.isNull())
+ break;
+
+ deleteRecord(key);
+
+ range.lowerKey = key;
+ range.lowerOpen = true;
+ }
+}
+
+IDBError MemoryObjectStore::addRecord(MemoryBackingStoreTransaction& transaction, const IDBKeyData& keyData, const IDBValue& value)
+{
+ LOG(IndexedDB, "MemoryObjectStore::addRecord");
+
+ ASSERT(m_writeTransaction);
+ ASSERT_UNUSED(transaction, m_writeTransaction == &transaction);
+ ASSERT(!m_keyValueStore || !m_keyValueStore->contains(keyData));
+ ASSERT(!m_orderedKeys || m_orderedKeys->find(keyData) == m_orderedKeys->end());
+
+ if (!m_keyValueStore) {
+ ASSERT(!m_orderedKeys);
+ m_keyValueStore = std::make_unique<KeyValueMap>();
+ m_orderedKeys = std::make_unique<std::set<IDBKeyData>>();
+ }
+
+ auto mapResult = m_keyValueStore->set(keyData, value.data());
+ ASSERT(mapResult.isNewEntry);
+ auto listResult = m_orderedKeys->insert(keyData);
+ ASSERT(listResult.second);
+
+ // If there was an error indexing this addition, then revert it.
+ auto error = updateIndexesForPutRecord(keyData, value.data());
+ if (!error.isNull()) {
+ m_keyValueStore->remove(mapResult.iterator);
+ m_orderedKeys->erase(listResult.first);
+ } else
+ updateCursorsForPutRecord(listResult.first);
+
+ return error;
+}
+
+void MemoryObjectStore::updateCursorsForPutRecord(std::set<IDBKeyData>::iterator iterator)
+{
+ for (auto& cursor : m_cursors.values())
+ cursor->keyAdded(iterator);
+}
+
+void MemoryObjectStore::updateCursorsForDeleteRecord(const IDBKeyData& key)
+{
+ for (auto& cursor : m_cursors.values())
+ cursor->keyDeleted(key);
+}
+
+void MemoryObjectStore::updateIndexesForDeleteRecord(const IDBKeyData& value)
+{
+ for (auto& index : m_indexesByName.values())
+ index->removeEntriesWithValueKey(value);
+}
+
+IDBError MemoryObjectStore::updateIndexesForPutRecord(const IDBKeyData& key, const ThreadSafeDataBuffer& value)
+{
+ JSLockHolder locker(UniqueIDBDatabase::databaseThreadVM());
+
+ auto jsValue = deserializeIDBValueToJSValue(UniqueIDBDatabase::databaseThreadExecState(), value);
+ if (jsValue.isUndefinedOrNull())
+ return { };
+
+ IDBError error;
+ Vector<std::pair<MemoryIndex*, IndexKey>> changedIndexRecords;
+
+ for (auto& index : m_indexesByName.values()) {
+ IndexKey indexKey;
+ generateIndexKeyForValue(UniqueIDBDatabase::databaseThreadExecState(), index->info(), jsValue, indexKey);
+
+ if (indexKey.isNull())
+ continue;
+
+ error = index->putIndexKey(key, indexKey);
+ if (!error.isNull())
+ break;
+
+ changedIndexRecords.append(std::make_pair(index.get(), indexKey));
+ }
+
+ // If any of the index puts failed, revert all of the ones that went through.
+ if (!error.isNull()) {
+ for (auto& record : changedIndexRecords)
+ record.first->removeRecord(key, record.second);
+ }
+
+ return error;
+}
+
+IDBError MemoryObjectStore::populateIndexWithExistingRecords(MemoryIndex& index)
+{
+ if (!m_keyValueStore)
+ return { };
+
+ JSLockHolder locker(UniqueIDBDatabase::databaseThreadVM());
+
+ for (auto iterator : *m_keyValueStore) {
+ auto jsValue = deserializeIDBValueToJSValue(UniqueIDBDatabase::databaseThreadExecState(), iterator.value);
+ if (jsValue.isUndefinedOrNull())
+ return { };
+
+ IndexKey indexKey;
+ generateIndexKeyForValue(UniqueIDBDatabase::databaseThreadExecState(), index.info(), jsValue, indexKey);
+
+ if (indexKey.isNull())
+ continue;
+
+ IDBError error = index.putIndexKey(iterator.key, indexKey);
+ if (!error.isNull())
+ return error;
+ }
+
+ return { };
+}
+
+uint64_t MemoryObjectStore::countForKeyRange(uint64_t indexIdentifier, const IDBKeyRangeData& inRange) const
+{
+ LOG(IndexedDB, "MemoryObjectStore::countForKeyRange");
+
+ if (indexIdentifier) {
+ auto* index = m_indexesByIdentifier.get(indexIdentifier);
+ ASSERT(index);
+ return index->countForKeyRange(inRange);
+ }
+
+ if (!m_keyValueStore)
+ return 0;
+
+ uint64_t count = 0;
+ IDBKeyRangeData range = inRange;
+ while (true) {
+ auto key = lowestKeyWithRecordInRange(range);
+ if (key.isNull())
+ break;
+
+ ++count;
+ range.lowerKey = key;
+ range.lowerOpen = true;
+ }
+
+ return count;
+}
+
+ThreadSafeDataBuffer MemoryObjectStore::valueForKey(const IDBKeyData& key) const
+{
+ if (!m_keyValueStore)
+ return { };
+
+ return m_keyValueStore->get(key);
+}
+
+ThreadSafeDataBuffer MemoryObjectStore::valueForKeyRange(const IDBKeyRangeData& keyRangeData) const
+{
+ LOG(IndexedDB, "MemoryObjectStore::valueForKey");
+
+ IDBKeyData key = lowestKeyWithRecordInRange(keyRangeData);
+ if (key.isNull())
+ return ThreadSafeDataBuffer();
+
+ ASSERT(m_keyValueStore);
+ return m_keyValueStore->get(key);
+}
+
+void MemoryObjectStore::getAllRecords(const IDBKeyRangeData& keyRangeData, std::optional<uint32_t> count, IndexedDB::GetAllType type, IDBGetAllResult& result) const
+{
+ result = { type };
+
+ uint32_t targetCount;
+ if (count && count.value())
+ targetCount = count.value();
+ else
+ targetCount = std::numeric_limits<uint32_t>::max();
+
+ IDBKeyRangeData range = keyRangeData;
+ uint32_t currentCount = 0;
+ while (currentCount < targetCount) {
+ IDBKeyData key = lowestKeyWithRecordInRange(range);
+ if (key.isNull())
+ return;
+
+ range.lowerKey = key;
+ range.lowerOpen = true;
+
+ if (type == IndexedDB::GetAllType::Keys)
+ result.addKey(WTFMove(key));
+ else
+ result.addValue(valueForKey(key));
+
+ ++currentCount;
+ }
+}
+
+IDBGetResult MemoryObjectStore::indexValueForKeyRange(uint64_t indexIdentifier, IndexedDB::IndexRecordType recordType, const IDBKeyRangeData& range) const
+{
+ LOG(IndexedDB, "MemoryObjectStore::indexValueForKeyRange");
+
+ auto* index = m_indexesByIdentifier.get(indexIdentifier);
+ ASSERT(index);
+ return index->getResultForKeyRange(recordType, range);
+}
+
+IDBKeyData MemoryObjectStore::lowestKeyWithRecordInRange(const IDBKeyRangeData& keyRangeData) const
+{
+ if (!m_keyValueStore)
+ return { };
+
+ if (keyRangeData.isExactlyOneKey() && m_keyValueStore->contains(keyRangeData.lowerKey))
+ return keyRangeData.lowerKey;
+
+ ASSERT(m_orderedKeys);
+
+ auto lowestInRange = m_orderedKeys->lower_bound(keyRangeData.lowerKey);
+
+ if (lowestInRange == m_orderedKeys->end())
+ return { };
+
+ if (keyRangeData.lowerOpen && *lowestInRange == keyRangeData.lowerKey)
+ ++lowestInRange;
+
+ if (lowestInRange == m_orderedKeys->end())
+ return { };
+
+ if (!keyRangeData.upperKey.isNull()) {
+ if (lowestInRange->compare(keyRangeData.upperKey) > 0)
+ return { };
+ if (keyRangeData.upperOpen && *lowestInRange == keyRangeData.upperKey)
+ return { };
+ }
+
+ return *lowestInRange;
+}
+
+void MemoryObjectStore::registerIndex(Ref<MemoryIndex>&& index)
+{
+ ASSERT(!m_indexesByIdentifier.contains(index->info().identifier()));
+ ASSERT(!m_indexesByName.contains(index->info().name()));
+
+ m_indexesByName.set(index->info().name(), &index.get());
+ m_indexesByIdentifier.set(index->info().identifier(), WTFMove(index));
+}
+
+void MemoryObjectStore::unregisterIndex(MemoryIndex& index)
+{
+ ASSERT(m_indexesByIdentifier.contains(index.info().identifier()));
+ ASSERT(m_indexesByName.contains(index.info().name()));
+
+ m_indexesByName.remove(index.info().name());
+ m_indexesByIdentifier.remove(index.info().identifier());
+}
+
+MemoryObjectStoreCursor* MemoryObjectStore::maybeOpenCursor(const IDBCursorInfo& info)
+{
+ auto result = m_cursors.add(info.identifier(), nullptr);
+ if (!result.isNewEntry)
+ return nullptr;
+
+ result.iterator->value = std::make_unique<MemoryObjectStoreCursor>(*this, info);
+ return result.iterator->value.get();
+}
+
+void MemoryObjectStore::renameIndex(MemoryIndex& index, const String& newName)
+{
+ ASSERT(m_indexesByName.get(index.info().name()) == &index);
+ ASSERT(!m_indexesByName.contains(newName));
+ ASSERT(m_info.infoForExistingIndex(index.info().name()));
+
+ m_info.infoForExistingIndex(index.info().name())->rename(newName);
+ m_indexesByName.set(newName, m_indexesByName.take(index.info().name()));
+ index.rename(newName);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.h b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.h
new file mode 100644
index 000000000..883959e32
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStore.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBKeyData.h"
+#include "IDBObjectStoreInfo.h"
+#include "MemoryIndex.h"
+#include "MemoryObjectStoreCursor.h"
+#include "ThreadSafeDataBuffer.h"
+#include <set>
+#include <wtf/HashMap.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class IDBCursorInfo;
+class IDBError;
+class IDBGetAllResult;
+class IDBKeyData;
+class IDBValue;
+
+struct IDBKeyRangeData;
+
+namespace IndexedDB {
+enum class GetAllType;
+enum class IndexRecordType;
+}
+
+namespace IDBServer {
+
+class MemoryBackingStoreTransaction;
+
+typedef HashMap<IDBKeyData, ThreadSafeDataBuffer, IDBKeyDataHash, IDBKeyDataHashTraits> KeyValueMap;
+
+class MemoryObjectStore : public RefCounted<MemoryObjectStore> {
+public:
+ static Ref<MemoryObjectStore> create(const IDBObjectStoreInfo&);
+
+ ~MemoryObjectStore();
+
+ void writeTransactionStarted(MemoryBackingStoreTransaction&);
+ void writeTransactionFinished(MemoryBackingStoreTransaction&);
+ MemoryBackingStoreTransaction* writeTransaction() { return m_writeTransaction; }
+
+ IDBError createIndex(MemoryBackingStoreTransaction&, const IDBIndexInfo&);
+ IDBError deleteIndex(MemoryBackingStoreTransaction&, uint64_t indexIdentifier);
+ void deleteAllIndexes(MemoryBackingStoreTransaction&);
+ void registerIndex(Ref<MemoryIndex>&&);
+
+ bool containsRecord(const IDBKeyData&);
+ void deleteRecord(const IDBKeyData&);
+ void deleteRange(const IDBKeyRangeData&);
+ IDBError addRecord(MemoryBackingStoreTransaction&, const IDBKeyData&, const IDBValue&);
+
+ uint64_t currentKeyGeneratorValue() const { return m_keyGeneratorValue; }
+ void setKeyGeneratorValue(uint64_t value) { m_keyGeneratorValue = value; }
+
+ void clear();
+ void replaceKeyValueStore(std::unique_ptr<KeyValueMap>&&, std::unique_ptr<std::set<IDBKeyData>>&&);
+
+ ThreadSafeDataBuffer valueForKey(const IDBKeyData&) const;
+ ThreadSafeDataBuffer valueForKeyRange(const IDBKeyRangeData&) const;
+ IDBKeyData lowestKeyWithRecordInRange(const IDBKeyRangeData&) const;
+ IDBGetResult indexValueForKeyRange(uint64_t indexIdentifier, IndexedDB::IndexRecordType, const IDBKeyRangeData&) const;
+ uint64_t countForKeyRange(uint64_t indexIdentifier, const IDBKeyRangeData&) const;
+
+ void getAllRecords(const IDBKeyRangeData&, std::optional<uint32_t> count, IndexedDB::GetAllType, IDBGetAllResult&) const;
+
+ const IDBObjectStoreInfo& info() const { return m_info; }
+
+ MemoryObjectStoreCursor* maybeOpenCursor(const IDBCursorInfo&);
+
+ std::set<IDBKeyData>* orderedKeys() { return m_orderedKeys.get(); }
+
+ MemoryIndex* indexForIdentifier(uint64_t);
+
+ void maybeRestoreDeletedIndex(Ref<MemoryIndex>&&);
+
+ void rename(const String& newName) { m_info.rename(newName); }
+ void renameIndex(MemoryIndex&, const String& newName);
+
+private:
+ MemoryObjectStore(const IDBObjectStoreInfo&);
+
+ std::set<IDBKeyData>::iterator lowestIteratorInRange(const IDBKeyRangeData&, bool reverse) const;
+
+ IDBError populateIndexWithExistingRecords(MemoryIndex&);
+ IDBError updateIndexesForPutRecord(const IDBKeyData&, const ThreadSafeDataBuffer& value);
+ void updateIndexesForDeleteRecord(const IDBKeyData& value);
+ void updateCursorsForPutRecord(std::set<IDBKeyData>::iterator);
+ void updateCursorsForDeleteRecord(const IDBKeyData&);
+
+ RefPtr<MemoryIndex> takeIndexByIdentifier(uint64_t indexIdentifier);
+
+ IDBObjectStoreInfo m_info;
+
+ MemoryBackingStoreTransaction* m_writeTransaction { nullptr };
+ uint64_t m_keyGeneratorValue { 1 };
+
+ std::unique_ptr<KeyValueMap> m_keyValueStore;
+ std::unique_ptr<std::set<IDBKeyData>> m_orderedKeys;
+
+ void unregisterIndex(MemoryIndex&);
+ HashMap<uint64_t, RefPtr<MemoryIndex>> m_indexesByIdentifier;
+ HashMap<String, RefPtr<MemoryIndex>> m_indexesByName;
+ HashMap<IDBResourceIdentifier, std::unique_ptr<MemoryObjectStoreCursor>> m_cursors;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.cpp
new file mode 100644
index 000000000..d95efe6a9
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.cpp
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryObjectStoreCursor.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBGetResult.h"
+#include "Logging.h"
+#include "MemoryObjectStore.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+MemoryObjectStoreCursor::MemoryObjectStoreCursor(MemoryObjectStore& objectStore, const IDBCursorInfo& info)
+ : MemoryCursor(info)
+ , m_objectStore(objectStore)
+ , m_remainingRange(info.range())
+{
+ LOG(IndexedDB, "MemoryObjectStoreCursor::MemoryObjectStoreCursor %s", info.range().loggingString().utf8().data());
+
+ auto* orderedKeys = objectStore.orderedKeys();
+ if (!orderedKeys)
+ return;
+
+ setFirstInRemainingRange(*orderedKeys);
+}
+
+void MemoryObjectStoreCursor::objectStoreCleared()
+{
+ m_iterator = std::nullopt;
+}
+
+void MemoryObjectStoreCursor::keyDeleted(const IDBKeyData& key)
+{
+ if (m_currentPositionKey != key)
+ return;
+
+ m_iterator = std::nullopt;
+}
+
+void MemoryObjectStoreCursor::keyAdded(std::set<IDBKeyData>::iterator iterator)
+{
+ if (m_iterator)
+ return;
+
+ if (*iterator == m_currentPositionKey)
+ m_iterator = iterator;
+}
+
+void MemoryObjectStoreCursor::setFirstInRemainingRange(std::set<IDBKeyData>& set)
+{
+ m_iterator = std::nullopt;
+
+ if (m_info.isDirectionForward()) {
+ setForwardIteratorFromRemainingRange(set);
+ if (m_iterator) {
+ m_remainingRange.lowerKey = **m_iterator;
+ m_remainingRange.lowerOpen = true;
+ }
+ } else {
+ setReverseIteratorFromRemainingRange(set);
+ if (m_iterator) {
+ m_remainingRange.upperKey = **m_iterator;
+ m_remainingRange.upperOpen = true;
+ }
+ }
+
+ ASSERT(!m_iterator || *m_iterator != set.end());
+}
+
+void MemoryObjectStoreCursor::setForwardIteratorFromRemainingRange(std::set<IDBKeyData>& set)
+{
+ if (!set.size()) {
+ m_iterator = std::nullopt;
+ return;
+ }
+
+ if (m_remainingRange.isExactlyOneKey()) {
+ m_iterator = set.find(m_remainingRange.lowerKey);
+ if (*m_iterator == set.end())
+ m_iterator = std::nullopt;
+
+ return;
+ }
+
+ m_iterator = std::nullopt;
+
+ auto lowest = set.lower_bound(m_remainingRange.lowerKey);
+ if (lowest == set.end())
+ return;
+
+ if (m_remainingRange.lowerOpen && *lowest == m_remainingRange.lowerKey) {
+ ++lowest;
+ if (lowest == set.end())
+ return;
+ }
+
+ if (!m_remainingRange.upperKey.isNull()) {
+ if (lowest->compare(m_remainingRange.upperKey) > 0)
+ return;
+
+ if (m_remainingRange.upperOpen && *lowest == m_remainingRange.upperKey)
+ return;
+ }
+
+ m_iterator = lowest;
+}
+
+void MemoryObjectStoreCursor::setReverseIteratorFromRemainingRange(std::set<IDBKeyData>& set)
+{
+ if (!set.size()) {
+ m_iterator = std::nullopt;
+ return;
+ }
+
+ if (m_remainingRange.isExactlyOneKey()) {
+ m_iterator = set.find(m_remainingRange.lowerKey);
+ if (*m_iterator == set.end())
+ m_iterator = std::nullopt;
+
+ return;
+ }
+
+ if (!m_remainingRange.upperKey.isValid()) {
+ m_iterator = --set.end();
+ if (!m_remainingRange.containsKey(**m_iterator))
+ m_iterator = std::nullopt;
+
+ return;
+ }
+
+ m_iterator = std::nullopt;
+
+ // This is one record past the actual key we're looking for.
+ auto highest = set.upper_bound(m_remainingRange.upperKey);
+
+ if (highest == set.begin())
+ return;
+
+ // This is one record before that, which *is* the key we're looking for.
+ --highest;
+
+ if (m_remainingRange.upperOpen && *highest == m_remainingRange.upperKey) {
+ if (highest == set.begin())
+ return;
+ --highest;
+ }
+
+ if (!m_remainingRange.lowerKey.isNull()) {
+ if (highest->compare(m_remainingRange.lowerKey) < 0)
+ return;
+
+ if (m_remainingRange.lowerOpen && *highest == m_remainingRange.lowerKey)
+ return;
+ }
+
+ m_iterator = highest;
+}
+
+void MemoryObjectStoreCursor::currentData(IDBGetResult& data)
+{
+ if (!m_iterator) {
+ m_currentPositionKey = { };
+ data = { };
+ return;
+ }
+
+ m_currentPositionKey = **m_iterator;
+ if (m_info.cursorType() == IndexedDB::CursorType::KeyOnly)
+ data = { m_currentPositionKey, m_currentPositionKey };
+ else {
+ IDBValue value = { m_objectStore.valueForKeyRange(m_currentPositionKey), { }, { } };
+ data = { m_currentPositionKey, m_currentPositionKey, WTFMove(value) };
+ }
+}
+
+void MemoryObjectStoreCursor::incrementForwardIterator(std::set<IDBKeyData>& set, const IDBKeyData& key, uint32_t count)
+{
+ // We might need to re-grab the current iterator.
+ // e.g. If the record it was pointed to had been deleted.
+ bool didResetIterator = false;
+ if (!m_iterator) {
+ if (!m_currentPositionKey.isValid())
+ return;
+
+ m_remainingRange.lowerKey = m_currentPositionKey;
+ m_remainingRange.lowerOpen = false;
+ setFirstInRemainingRange(set);
+
+ didResetIterator = true;
+ }
+
+ if (!m_iterator)
+ return;
+
+ ASSERT(*m_iterator != set.end());
+
+ if (key.isValid()) {
+ // If iterating to a key, the count passed in must be zero, as there is no way to iterate by both a count and to a key.
+ ASSERT(!count);
+
+ if (!m_info.range().containsKey(key))
+ return;
+
+ if ((*m_iterator)->compare(key) < 0) {
+ m_remainingRange.lowerKey = key;
+ m_remainingRange.lowerOpen = false;
+ setFirstInRemainingRange(set);
+ }
+
+ return;
+ }
+
+ if (!count)
+ count = 1;
+
+ // If the forwardIterator was reset because it's previous record had been deleted,
+ // we might have already advanced past the current position, eating one one of the iteration counts.
+ if (didResetIterator && (*m_iterator)->compare(m_currentPositionKey) > 0)
+ --count;
+
+ while (count) {
+ --count;
+ ++*m_iterator;
+
+ if (*m_iterator == set.end() || !m_info.range().containsKey(**m_iterator)) {
+ m_iterator = std::nullopt;
+ return;
+ }
+ }
+}
+
+void MemoryObjectStoreCursor::incrementReverseIterator(std::set<IDBKeyData>& set, const IDBKeyData& key, uint32_t count)
+{
+ // We might need to re-grab the current iterator.
+ // e.g. If the record it was pointed to had been deleted.
+ bool didResetIterator = false;
+ if (!m_iterator) {
+ if (!m_currentPositionKey.isValid())
+ return;
+
+ m_remainingRange.upperKey = m_currentPositionKey;
+ m_remainingRange.upperOpen = false;
+ setFirstInRemainingRange(set);
+
+ didResetIterator = true;
+ }
+
+ if (*m_iterator == set.end())
+ return;
+
+ if (key.isValid()) {
+ // If iterating to a key, the count passed in must be zero, as there is no way to iterate by both a count and to a key.
+ ASSERT(!count);
+
+ if (!m_info.range().containsKey(key))
+ return;
+
+ if ((*m_iterator)->compare(key) > 0) {
+ m_remainingRange.upperKey = key;
+ m_remainingRange.upperOpen = false;
+
+ setFirstInRemainingRange(set);
+ }
+
+ return;
+ }
+
+ if (!count)
+ count = 1;
+
+ // If the reverseIterator was reset because it's previous record had been deleted,
+ // we might have already advanced past the current position, eating one one of the iteration counts.
+ if (didResetIterator && (*m_iterator)->compare(m_currentPositionKey) < 0)
+ --count;
+
+ while (count) {
+ if (*m_iterator == set.begin()) {
+ m_iterator = std::nullopt;
+ return;
+ }
+
+ --count;
+ --*m_iterator;
+
+ if (!m_info.range().containsKey(**m_iterator)) {
+ m_iterator = std::nullopt;
+ return;
+ }
+ }
+}
+
+void MemoryObjectStoreCursor::iterate(const IDBKeyData& key, const IDBKeyData& primaryKeyData, uint32_t count, IDBGetResult& outData)
+{
+ LOG(IndexedDB, "MemoryObjectStoreCursor::iterate to key %s", key.loggingString().utf8().data());
+
+ ASSERT_UNUSED(primaryKeyData, primaryKeyData.isNull());
+
+ if (!m_objectStore.orderedKeys()) {
+ m_currentPositionKey = { };
+ outData = { };
+ return;
+ }
+
+ if (key.isValid() && !m_info.range().containsKey(key)) {
+ m_currentPositionKey = { };
+ outData = { };
+ return;
+ }
+
+ auto* set = m_objectStore.orderedKeys();
+ if (set) {
+ if (m_info.isDirectionForward())
+ incrementForwardIterator(*set, key, count);
+ else
+ incrementReverseIterator(*set, key, count);
+ }
+
+ m_currentPositionKey = { };
+
+ if (!m_iterator) {
+ outData = { };
+ return;
+ }
+
+ currentData(outData);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.h b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.h
new file mode 100644
index 000000000..633f1e66c
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/MemoryObjectStoreCursor.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+#include "IDBKeyData.h"
+#include "MemoryCursor.h"
+#include <set>
+#include <wtf/Optional.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+class MemoryObjectStore;
+
+class MemoryObjectStoreCursor : public MemoryCursor {
+public:
+ MemoryObjectStoreCursor(MemoryObjectStore&, const IDBCursorInfo&);
+
+ void objectStoreCleared();
+ void keyDeleted(const IDBKeyData&);
+ void keyAdded(std::set<IDBKeyData>::iterator);
+
+private:
+ void currentData(IDBGetResult&) final;
+ void iterate(const IDBKeyData&, const IDBKeyData& primaryKey, uint32_t count, IDBGetResult&) final;
+
+ void setFirstInRemainingRange(std::set<IDBKeyData>&);
+ void setForwardIteratorFromRemainingRange(std::set<IDBKeyData>&);
+ void setReverseIteratorFromRemainingRange(std::set<IDBKeyData>&);
+
+ void incrementForwardIterator(std::set<IDBKeyData>&, const IDBKeyData&, uint32_t count);
+ void incrementReverseIterator(std::set<IDBKeyData>&, const IDBKeyData&, uint32_t count);
+
+ bool hasValidPosition() const;
+
+ MemoryObjectStore& m_objectStore;
+
+ IDBKeyRangeData m_remainingRange;
+
+ std::optional<std::set<IDBKeyData>::iterator> m_iterator;
+
+ IDBKeyData m_currentPositionKey;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp
new file mode 100644
index 000000000..fa3755e60
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.cpp
@@ -0,0 +1,2601 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SQLiteIDBBackingStore.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "FileSystem.h"
+#include "IDBBindingUtilities.h"
+#include "IDBDatabaseException.h"
+#include "IDBGetAllRecordsData.h"
+#include "IDBGetAllResult.h"
+#include "IDBGetRecordData.h"
+#include "IDBGetResult.h"
+#include "IDBIterateCursorData.h"
+#include "IDBKeyData.h"
+#include "IDBObjectStoreInfo.h"
+#include "IDBSerialization.h"
+#include "IDBTransactionInfo.h"
+#include "IDBValue.h"
+#include "IndexKey.h"
+#include "Logging.h"
+#include "SQLiteDatabase.h"
+#include "SQLiteFileSystem.h"
+#include "SQLiteIDBCursor.h"
+#include "SQLiteStatement.h"
+#include "SQLiteTransaction.h"
+#include "ThreadSafeDataBuffer.h"
+#include <heap/HeapInlines.h>
+#include <heap/StrongInlines.h>
+#include <runtime/AuxiliaryBarrierInlines.h>
+#include <runtime/JSCJSValueInlines.h>
+#include <runtime/JSGlobalObject.h>
+#include <runtime/StructureInlines.h>
+#include <wtf/NeverDestroyed.h>
+
+using namespace JSC;
+
+namespace WebCore {
+namespace IDBServer {
+
+// Current version of the metadata schema being used in the metadata database.
+static const int currentMetadataVersion = 1;
+
+static int idbKeyCollate(int aLength, const void* aBuffer, int bLength, const void* bBuffer)
+{
+ IDBKeyData a, b;
+ if (!deserializeIDBKeyData(static_cast<const uint8_t*>(aBuffer), aLength, a)) {
+ LOG_ERROR("Unable to deserialize key A in collation function.");
+
+ // There's no way to indicate an error to SQLite - we have to return a sorting decision.
+ // We arbitrarily choose "A > B"
+ return 1;
+ }
+ if (!deserializeIDBKeyData(static_cast<const uint8_t*>(bBuffer), bLength, b)) {
+ LOG_ERROR("Unable to deserialize key B in collation function.");
+
+ // There's no way to indicate an error to SQLite - we have to return a sorting decision.
+ // We arbitrarily choose "A > B"
+ return 1;
+ }
+
+ return a.compare(b);
+}
+
+static const String v1RecordsTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, key TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, value NOT NULL ON CONFLICT FAIL)");
+}
+
+static const String& v1RecordsTableSchema()
+{
+ static NeverDestroyed<WTF::String> v1RecordsTableSchemaString(v1RecordsTableSchema("Records"));
+ return v1RecordsTableSchemaString;
+}
+
+static const String& v1RecordsTableSchemaAlternate()
+{
+ static NeverDestroyed<WTF::String> v1RecordsTableSchemaString(v1RecordsTableSchema("\"Records\""));
+ return v1RecordsTableSchemaString;
+}
+
+static const String v2RecordsTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, key TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL, value NOT NULL ON CONFLICT FAIL)");
+}
+
+static const String& v2RecordsTableSchema()
+{
+ static NeverDestroyed<WTF::String> v2RecordsTableSchemaString(v2RecordsTableSchema("Records"));
+ return v2RecordsTableSchemaString;
+}
+
+static const String& v2RecordsTableSchemaAlternate()
+{
+ static NeverDestroyed<WTF::String> v2RecordsTableSchemaString(v2RecordsTableSchema("\"Records\""));
+ return v2RecordsTableSchemaString;
+}
+
+static const String v3RecordsTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, key TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL, value NOT NULL ON CONFLICT FAIL, recordID INTEGER PRIMARY KEY)");
+}
+
+static const String& v3RecordsTableSchema()
+{
+ static NeverDestroyed<WTF::String> v3RecordsTableSchemaString(v3RecordsTableSchema("Records"));
+ return v3RecordsTableSchemaString;
+}
+
+static const String& v3RecordsTableSchemaAlternate()
+{
+ static NeverDestroyed<WTF::String> v3RecordsTableSchemaString(v3RecordsTableSchema("\"Records\""));
+ return v3RecordsTableSchemaString;
+}
+
+static const String v1IndexRecordsTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (indexID INTEGER NOT NULL ON CONFLICT FAIL, objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, key TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL, value NOT NULL ON CONFLICT FAIL)");
+}
+
+static const String& v1IndexRecordsTableSchema()
+{
+ static NeverDestroyed<WTF::String> v1IndexRecordsTableSchemaString(v1IndexRecordsTableSchema("IndexRecords"));
+ return v1IndexRecordsTableSchemaString;
+}
+
+static const String& v1IndexRecordsTableSchemaAlternate()
+{
+ static NeverDestroyed<WTF::String> v1IndexRecordsTableSchemaString(v1IndexRecordsTableSchema("\"IndexRecords\""));
+ return v1IndexRecordsTableSchemaString;
+}
+
+static const String v2IndexRecordsTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (indexID INTEGER NOT NULL ON CONFLICT FAIL, objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, key TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL, value TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL)");
+}
+
+static const String& v2IndexRecordsTableSchema()
+{
+ static NeverDestroyed<WTF::String> v2IndexRecordsTableSchemaString(v2IndexRecordsTableSchema("IndexRecords"));
+ return v2IndexRecordsTableSchemaString;
+}
+
+static const String& v2IndexRecordsTableSchemaAlternate()
+{
+ static NeverDestroyed<WTF::String> v2IndexRecordsTableSchemaString(v2IndexRecordsTableSchema("\"IndexRecords\""));
+ return v2IndexRecordsTableSchemaString;
+}
+
+static const String v3IndexRecordsTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (indexID INTEGER NOT NULL ON CONFLICT FAIL, objectStoreID INTEGER NOT NULL ON CONFLICT FAIL, key TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL, value TEXT COLLATE IDBKEY NOT NULL ON CONFLICT FAIL, objectStoreRecordID INTEGER NOT NULL ON CONFLICT FAIL)");
+}
+
+static const String v3IndexRecordsTableSchema()
+{
+ static NeverDestroyed<WTF::String> indexRecordsTableSchemaString = v3IndexRecordsTableSchema("IndexRecords");
+ return indexRecordsTableSchemaString;
+}
+
+static const String v3IndexRecordsTableSchemaAlternate()
+{
+ static NeverDestroyed<WTF::String> indexRecordsTableSchemaString = v3IndexRecordsTableSchema("\"IndexRecords\"");
+ return indexRecordsTableSchemaString;
+}
+
+static const String& v1IndexRecordsIndexSchema()
+{
+ static NeverDestroyed<WTF::String> indexRecordsIndexSchemaString("CREATE INDEX IndexRecordsIndex ON IndexRecords (key)");
+ return indexRecordsIndexSchemaString;
+}
+
+static const String blobRecordsTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (objectStoreRow INTEGER NOT NULL ON CONFLICT FAIL, blobURL TEXT NOT NULL ON CONFLICT FAIL)");
+}
+
+static const String& blobRecordsTableSchema()
+{
+ static NeverDestroyed<String> blobRecordsTableSchemaString(blobRecordsTableSchema("BlobRecords"));
+ return blobRecordsTableSchemaString;
+}
+
+static const String& blobRecordsTableSchemaAlternate()
+{
+ static NeverDestroyed<String> blobRecordsTableSchemaString(blobRecordsTableSchema("\"BlobRecords\""));
+ return blobRecordsTableSchemaString;
+}
+
+static const String blobFilesTableSchema(const String& tableName)
+{
+ return makeString("CREATE TABLE ", tableName, " (blobURL TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, fileName TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL)");
+}
+
+static const String& blobFilesTableSchema()
+{
+ static NeverDestroyed<String> blobFilesTableSchemaString(blobFilesTableSchema("BlobFiles"));
+ return blobFilesTableSchemaString;
+}
+
+static const String& blobFilesTableSchemaAlternate()
+{
+ static NeverDestroyed<String> blobFilesTableSchemaString(blobFilesTableSchema("\"BlobFiles\""));
+ return blobFilesTableSchemaString;
+}
+
+SQLiteIDBBackingStore::SQLiteIDBBackingStore(const IDBDatabaseIdentifier& identifier, const String& databaseRootDirectory, IDBBackingStoreTemporaryFileHandler& fileHandler)
+ : m_identifier(identifier)
+ , m_temporaryFileHandler(fileHandler)
+{
+ m_absoluteDatabaseDirectory = identifier.databaseDirectoryRelativeToRoot(databaseRootDirectory);
+}
+
+SQLiteIDBBackingStore::~SQLiteIDBBackingStore()
+{
+ if (m_sqliteDB)
+ closeSQLiteDB();
+
+ if (m_vm) {
+ JSLockHolder locker(m_vm.get());
+ m_globalObject.clear();
+ m_vm = nullptr;
+ }
+}
+
+
+void SQLiteIDBBackingStore::initializeVM()
+{
+ if (!m_vm) {
+ ASSERT(!m_globalObject);
+ m_vm = VM::create();
+
+ JSLockHolder locker(m_vm.get());
+ m_globalObject.set(*m_vm, JSGlobalObject::create(*m_vm, JSGlobalObject::createStructure(*m_vm, jsNull())));
+ }
+}
+
+VM& SQLiteIDBBackingStore::vm()
+{
+ initializeVM();
+ return *m_vm;
+}
+
+JSGlobalObject& SQLiteIDBBackingStore::globalObject()
+{
+ initializeVM();
+ return **m_globalObject;
+}
+
+static bool createOrMigrateRecordsTableIfNecessary(SQLiteDatabase& database)
+{
+ String currentSchema;
+ {
+ // Fetch the schema for an existing records table.
+ SQLiteStatement statement(database, "SELECT type, sql FROM sqlite_master WHERE tbl_name='Records'");
+ if (statement.prepare() != SQLITE_OK) {
+ LOG_ERROR("Unable to prepare statement to fetch schema for the Records table.");
+ return false;
+ }
+
+ int sqliteResult = statement.step();
+
+ // If there is no Records table at all, create it and then bail.
+ if (sqliteResult == SQLITE_DONE) {
+ if (!database.executeCommand(v3RecordsTableSchema())) {
+ LOG_ERROR("Could not create Records table in database (%i) - %s", database.lastError(), database.lastErrorMsg());
+ return false;
+ }
+
+ return true;
+ }
+
+ if (sqliteResult != SQLITE_ROW) {
+ LOG_ERROR("Error executing statement to fetch schema for the Records table.");
+ return false;
+ }
+
+ currentSchema = statement.getColumnText(1);
+ }
+
+ ASSERT(!currentSchema.isEmpty());
+
+ // If the schema in the backing store is the current schema, we're done.
+ if (currentSchema == v3RecordsTableSchema() || currentSchema == v3RecordsTableSchemaAlternate())
+ return true;
+
+ // If the record table is not the current schema then it must be one of the previous schemas.
+ // If it is not then the database is in an unrecoverable state and this should be considered a fatal error.
+ if (currentSchema != v1RecordsTableSchema() && currentSchema != v1RecordsTableSchemaAlternate()
+ && currentSchema != v2RecordsTableSchema() && currentSchema != v2RecordsTableSchemaAlternate())
+ RELEASE_ASSERT_NOT_REACHED();
+
+ SQLiteTransaction transaction(database);
+ transaction.begin();
+
+ // Create a temporary table with the correct schema and migrate all existing content over.
+ if (!database.executeCommand(v3RecordsTableSchema("_Temp_Records"))) {
+ LOG_ERROR("Could not create temporary records table in database (%i) - %s", database.lastError(), database.lastErrorMsg());
+ return false;
+ }
+
+ if (!database.executeCommand("INSERT INTO _Temp_Records (objectStoreID, key, value) SELECT objectStoreID, CAST(key AS TEXT), value FROM Records")) {
+ LOG_ERROR("Could not migrate existing Records content (%i) - %s", database.lastError(), database.lastErrorMsg());
+ return false;
+ }
+
+ if (!database.executeCommand("DROP TABLE Records")) {
+ LOG_ERROR("Could not drop existing Records table (%i) - %s", database.lastError(), database.lastErrorMsg());
+ return false;
+ }
+
+ if (!database.executeCommand("ALTER TABLE _Temp_Records RENAME TO Records")) {
+ LOG_ERROR("Could not rename temporary Records table (%i) - %s", database.lastError(), database.lastErrorMsg());
+ return false;
+ }
+
+ transaction.commit();
+
+ return true;
+}
+
+bool SQLiteIDBBackingStore::ensureValidBlobTables()
+{
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ String currentSchema;
+ {
+ // Fetch the schema for an existing blob record table.
+ SQLiteStatement statement(*m_sqliteDB, "SELECT type, sql FROM sqlite_master WHERE tbl_name='BlobRecords'");
+ if (statement.prepare() != SQLITE_OK) {
+ LOG_ERROR("Unable to prepare statement to fetch schema for the BlobRecords table.");
+ return false;
+ }
+
+ int sqliteResult = statement.step();
+
+ // If there is no BlobRecords table at all, create it..
+ if (sqliteResult == SQLITE_DONE) {
+ if (!m_sqliteDB->executeCommand(blobRecordsTableSchema())) {
+ LOG_ERROR("Could not create BlobRecords table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ currentSchema = blobRecordsTableSchema();
+ } else if (sqliteResult != SQLITE_ROW) {
+ LOG_ERROR("Error executing statement to fetch schema for the BlobRecords table.");
+ return false;
+ } else
+ currentSchema = statement.getColumnText(1);
+ }
+
+ if (currentSchema != blobRecordsTableSchema() && currentSchema != blobRecordsTableSchemaAlternate()) {
+ LOG_ERROR("Invalid BlobRecords table schema found");
+ return false;
+ }
+
+ {
+ // Fetch the schema for an existing blob file table.
+ SQLiteStatement statement(*m_sqliteDB, "SELECT type, sql FROM sqlite_master WHERE tbl_name='BlobFiles'");
+ if (statement.prepare() != SQLITE_OK) {
+ LOG_ERROR("Unable to prepare statement to fetch schema for the BlobFiles table.");
+ return false;
+ }
+
+ int sqliteResult = statement.step();
+
+ // If there is no BlobFiles table at all, create it and then bail.
+ if (sqliteResult == SQLITE_DONE) {
+ if (!m_sqliteDB->executeCommand(blobFilesTableSchema())) {
+ LOG_ERROR("Could not create BlobFiles table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ return true;
+ }
+
+ if (sqliteResult != SQLITE_ROW) {
+ LOG_ERROR("Error executing statement to fetch schema for the BlobFiles table.");
+ return false;
+ }
+
+ currentSchema = statement.getColumnText(1);
+ }
+
+ if (currentSchema != blobFilesTableSchema() && currentSchema != blobFilesTableSchemaAlternate()) {
+ LOG_ERROR("Invalid BlobFiles table schema found");
+ return false;
+ }
+
+ return true;
+}
+
+bool SQLiteIDBBackingStore::ensureValidRecordsTable()
+{
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ if (!createOrMigrateRecordsTableIfNecessary(*m_sqliteDB))
+ return false;
+
+ // Whether the updated records table already existed or if it was just created and the data migrated over,
+ // make sure the uniqueness index exists.
+ if (!m_sqliteDB->executeCommand("CREATE UNIQUE INDEX IF NOT EXISTS RecordsIndex ON Records (objectStoreID, key);")) {
+ LOG_ERROR("Could not create RecordsIndex on Records table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ return true;
+}
+
+bool SQLiteIDBBackingStore::ensureValidIndexRecordsTable()
+{
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ String currentSchema;
+ {
+ // Fetch the schema for an existing index record table.
+ SQLiteStatement statement(*m_sqliteDB, "SELECT type, sql FROM sqlite_master WHERE tbl_name='IndexRecords'");
+ if (statement.prepare() != SQLITE_OK) {
+ LOG_ERROR("Unable to prepare statement to fetch schema for the IndexRecords table.");
+ return false;
+ }
+
+ int sqliteResult = statement.step();
+
+ // If there is no IndexRecords table at all, create it and then bail.
+ if (sqliteResult == SQLITE_DONE) {
+ if (!m_sqliteDB->executeCommand(v3IndexRecordsTableSchema())) {
+ LOG_ERROR("Could not create IndexRecords table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ return true;
+ }
+
+ if (sqliteResult != SQLITE_ROW) {
+ LOG_ERROR("Error executing statement to fetch schema for the IndexRecords table.");
+ return false;
+ }
+
+ currentSchema = statement.getColumnText(1);
+ }
+
+ ASSERT(!currentSchema.isEmpty());
+
+ // If the schema in the backing store is the current schema, we're done.
+ if (currentSchema == v3IndexRecordsTableSchema() || currentSchema == v3IndexRecordsTableSchemaAlternate())
+ return true;
+
+ // If the record table is not the current schema then it must be one of the previous schemas.
+ // If it is not then the database is in an unrecoverable state and this should be considered a fatal error.
+ if (currentSchema != v1IndexRecordsTableSchema() && currentSchema != v1IndexRecordsTableSchemaAlternate()
+ && currentSchema != v2IndexRecordsTableSchema() && currentSchema != v2IndexRecordsTableSchemaAlternate())
+ RELEASE_ASSERT_NOT_REACHED();
+
+ SQLiteTransaction transaction(*m_sqliteDB);
+ transaction.begin();
+
+ // Create a temporary table with the correct schema and migrate all existing content over.
+ if (!m_sqliteDB->executeCommand(v3IndexRecordsTableSchema("_Temp_IndexRecords"))) {
+ LOG_ERROR("Could not create temporary index records table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ if (!m_sqliteDB->executeCommand("INSERT INTO _Temp_IndexRecords SELECT IndexRecords.indexID, IndexRecords.objectStoreID, IndexRecords.key, IndexRecords.value, Records.rowid FROM IndexRecords INNER JOIN Records ON Records.key = IndexRecords.value AND Records.objectStoreID = IndexRecords.objectStoreID")) {
+ LOG_ERROR("Could not migrate existing IndexRecords content (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ if (!m_sqliteDB->executeCommand("DROP TABLE IndexRecords")) {
+ LOG_ERROR("Could not drop existing IndexRecords table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ if (!m_sqliteDB->executeCommand("ALTER TABLE _Temp_IndexRecords RENAME TO IndexRecords")) {
+ LOG_ERROR("Could not rename temporary IndexRecords table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ transaction.commit();
+
+ return true;
+}
+
+bool SQLiteIDBBackingStore::ensureValidIndexRecordsIndex()
+{
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ String currentSchema;
+ {
+ // Fetch the schema for an existing index record index.
+ SQLiteStatement statement(*m_sqliteDB, "SELECT sql FROM sqlite_master WHERE name='IndexRecordsIndex'");
+ if (statement.prepare() != SQLITE_OK) {
+ LOG_ERROR("Unable to prepare statement to fetch schema for the IndexRecordsIndex index.");
+ return false;
+ }
+
+ int sqliteResult = statement.step();
+
+ // If there is no IndexRecordsIndex index at all, create it and then bail.
+ if (sqliteResult == SQLITE_DONE) {
+ if (!m_sqliteDB->executeCommand(v1IndexRecordsIndexSchema())) {
+ LOG_ERROR("Could not create IndexRecordsIndex index in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return false;
+ }
+
+ return true;
+ }
+
+ if (sqliteResult != SQLITE_ROW) {
+ LOG_ERROR("Error executing statement to fetch schema for the IndexRecordsIndex index.");
+ return false;
+ }
+
+ currentSchema = statement.getColumnText(0);
+ }
+
+ ASSERT(!currentSchema.isEmpty());
+
+ // If the schema in the backing store is the current schema, we're done.
+ if (currentSchema == v1IndexRecordsIndexSchema())
+ return true;
+
+ // There is currently no outdated schema for the IndexRecordsIndex, so any other existing schema means this database is invalid.
+ return false;
+}
+
+std::unique_ptr<IDBDatabaseInfo> SQLiteIDBBackingStore::createAndPopulateInitialDatabaseInfo()
+{
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ if (!m_sqliteDB->executeCommand("CREATE TABLE IDBDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, value TEXT NOT NULL ON CONFLICT FAIL);")) {
+ LOG_ERROR("Could not create IDBDatabaseInfo table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+
+ if (!m_sqliteDB->executeCommand("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, maxIndexID INTEGER NOT NULL ON CONFLICT FAIL);")) {
+ LOG_ERROR("Could not create ObjectStoreInfo table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+
+ 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);")) {
+ LOG_ERROR("Could not create IndexInfo table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+
+ if (!m_sqliteDB->executeCommand("CREATE TABLE KeyGenerators (objectStoreID INTEGER NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, currentKey INTEGER NOT NULL ON CONFLICT FAIL);")) {
+ LOG_ERROR("Could not create KeyGenerators table in database (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+
+ {
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("INSERT INTO IDBDatabaseInfo VALUES ('MetadataVersion', ?);"));
+ if (sql.prepare() != SQLITE_OK
+ || sql.bindInt(1, currentMetadataVersion) != SQLITE_OK
+ || sql.step() != SQLITE_DONE) {
+ LOG_ERROR("Could not insert database metadata version into IDBDatabaseInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+ }
+ {
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("INSERT INTO IDBDatabaseInfo VALUES ('DatabaseName', ?);"));
+ if (sql.prepare() != SQLITE_OK
+ || sql.bindText(1, m_identifier.databaseName()) != SQLITE_OK
+ || sql.step() != SQLITE_DONE) {
+ LOG_ERROR("Could not insert database name into IDBDatabaseInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+ }
+ {
+ // Database versions are defined to be a uin64_t in the spec but sqlite3 doesn't support native binding of unsigned integers.
+ // Therefore we'll store the version as a String.
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("INSERT INTO IDBDatabaseInfo VALUES ('DatabaseVersion', ?);"));
+ if (sql.prepare() != SQLITE_OK
+ || sql.bindText(1, String::number(0)) != SQLITE_OK
+ || sql.step() != SQLITE_DONE) {
+ LOG_ERROR("Could not insert default version into IDBDatabaseInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+ }
+
+ if (!m_sqliteDB->executeCommand(ASCIILiteral("INSERT INTO IDBDatabaseInfo VALUES ('MaxObjectStoreID', 1);"))) {
+ LOG_ERROR("Could not insert default version into IDBDatabaseInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ closeSQLiteDB();
+ return nullptr;
+ }
+
+ // This initial database info matches the default values we just put into the metadata database.
+ return std::make_unique<IDBDatabaseInfo>(m_identifier.databaseName(), 0);
+}
+
+std::unique_ptr<IDBDatabaseInfo> SQLiteIDBBackingStore::extractExistingDatabaseInfo()
+{
+ ASSERT(m_sqliteDB);
+
+ if (!m_sqliteDB->tableExists(ASCIILiteral("IDBDatabaseInfo")))
+ return nullptr;
+
+ String databaseName;
+ {
+ SQLiteStatement sql(*m_sqliteDB, "SELECT value FROM IDBDatabaseInfo WHERE key = 'DatabaseName';");
+ if (sql.isColumnNull(0))
+ return nullptr;
+ databaseName = sql.getColumnText(0);
+ if (databaseName != m_identifier.databaseName()) {
+ LOG_ERROR("Database name in the info database ('%s') does not match the expected name ('%s')", databaseName.utf8().data(), m_identifier.databaseName().utf8().data());
+ return nullptr;
+ }
+ }
+ uint64_t databaseVersion;
+ {
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("SELECT value FROM IDBDatabaseInfo WHERE key = 'DatabaseVersion';"));
+ if (sql.isColumnNull(0))
+ return nullptr;
+ String stringVersion = sql.getColumnText(0);
+ bool ok;
+ databaseVersion = stringVersion.toUInt64Strict(&ok);
+ if (!ok) {
+ LOG_ERROR("Database version on disk ('%s') does not cleanly convert to an unsigned 64-bit integer version", stringVersion.utf8().data());
+ return nullptr;
+ }
+ }
+
+ auto databaseInfo = std::make_unique<IDBDatabaseInfo>(databaseName, databaseVersion);
+
+ {
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("SELECT id, name, keyPath, autoInc, maxIndexID FROM ObjectStoreInfo;"));
+ if (sql.prepare() != SQLITE_OK)
+ return nullptr;
+
+ int result = sql.step();
+ while (result == SQLITE_ROW) {
+ uint64_t objectStoreID = sql.getColumnInt64(0);
+ String objectStoreName = sql.getColumnText(1);
+
+ Vector<char> keyPathBuffer;
+ sql.getColumnBlobAsVector(2, keyPathBuffer);
+
+ std::optional<IDBKeyPath> objectStoreKeyPath;
+ if (!deserializeIDBKeyPath(reinterpret_cast<const uint8_t*>(keyPathBuffer.data()), keyPathBuffer.size(), objectStoreKeyPath)) {
+ LOG_ERROR("Unable to extract key path from database");
+ return nullptr;
+ }
+
+ bool autoIncrement = sql.getColumnInt(3);
+
+ databaseInfo->addExistingObjectStore({ objectStoreID, objectStoreName, WTFMove(objectStoreKeyPath), autoIncrement });
+
+ result = sql.step();
+ }
+
+ if (result != SQLITE_DONE) {
+ LOG_ERROR("Error fetching object store info from database on disk");
+ return nullptr;
+ }
+ }
+
+ {
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("SELECT id, name, objectStoreID, keyPath, isUnique, multiEntry FROM IndexInfo;"));
+ if (sql.prepare() != SQLITE_OK)
+ return nullptr;
+
+ int result = sql.step();
+ while (result == SQLITE_ROW) {
+ uint64_t indexID = sql.getColumnInt64(0);
+ String indexName = sql.getColumnText(1);
+ uint64_t objectStoreID = sql.getColumnInt64(2);
+
+ Vector<char> keyPathBuffer;
+ sql.getColumnBlobAsVector(3, keyPathBuffer);
+
+ std::optional<IDBKeyPath> indexKeyPath;
+ if (!deserializeIDBKeyPath(reinterpret_cast<const uint8_t*>(keyPathBuffer.data()), keyPathBuffer.size(), indexKeyPath)) {
+ LOG_ERROR("Unable to extract key path from database");
+ return nullptr;
+ }
+ if (!indexKeyPath) {
+ LOG_ERROR("Unable to extract key path from database");
+ return nullptr;
+ }
+
+ bool unique = sql.getColumnInt(4);
+ bool multiEntry = sql.getColumnInt(5);
+
+ auto objectStore = databaseInfo->infoForExistingObjectStore(objectStoreID);
+ if (!objectStore) {
+ LOG_ERROR("Found index referring to a non-existant object store");
+ return nullptr;
+ }
+
+ objectStore->addExistingIndex({ indexID, objectStoreID, indexName, WTFMove(indexKeyPath.value()), unique, multiEntry });
+
+ result = sql.step();
+ }
+
+ if (result != SQLITE_DONE) {
+ LOG_ERROR("Error fetching index info from database on disk");
+ return nullptr;
+ }
+ }
+
+ return databaseInfo;
+}
+
+String SQLiteIDBBackingStore::databaseNameFromEncodedFilename(const String& encodedName)
+{
+ if (equal(encodedName, ASCIILiteral("%00")))
+ return { };
+
+ String partiallyDecoded = encodedName;
+ partiallyDecoded.replace(ASCIILiteral("%2E"), ASCIILiteral("."));
+
+ return decodeFromFilename(partiallyDecoded);
+}
+
+String SQLiteIDBBackingStore::filenameForDatabaseName() const
+{
+ ASSERT(!m_identifier.databaseName().isNull());
+
+ if (m_identifier.databaseName().isEmpty())
+ return "%00";
+
+ String filename = encodeForFileName(m_identifier.databaseName());
+ filename.replace('.', "%2E");
+
+ return filename;
+}
+
+String SQLiteIDBBackingStore::fullDatabaseDirectory() const
+{
+ ASSERT(!m_identifier.databaseName().isNull());
+
+ return pathByAppendingComponent(m_absoluteDatabaseDirectory, filenameForDatabaseName());
+}
+
+String SQLiteIDBBackingStore::fullDatabasePath() const
+{
+ ASSERT(!m_identifier.databaseName().isNull());
+
+ return pathByAppendingComponent(fullDatabaseDirectory(), "IndexedDB.sqlite3");
+}
+
+IDBError SQLiteIDBBackingStore::getOrEstablishDatabaseInfo(IDBDatabaseInfo& info)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::getOrEstablishDatabaseInfo - database %s", m_identifier.databaseName().utf8().data());
+
+ if (m_databaseInfo) {
+ info = *m_databaseInfo;
+ return { };
+ }
+
+ makeAllDirectories(fullDatabaseDirectory());
+ String dbFilename = fullDatabasePath();
+
+ m_sqliteDB = std::make_unique<SQLiteDatabase>();
+ if (!m_sqliteDB->open(dbFilename)) {
+ LOG_ERROR("Failed to open SQLite database at path '%s'", dbFilename.utf8().data());
+ closeSQLiteDB();
+ }
+
+ if (!m_sqliteDB)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to open database file on disk") };
+
+ m_sqliteDB->setCollationFunction("IDBKEY", [this](int aLength, const void* a, int bLength, const void* b) {
+ return idbKeyCollate(aLength, a, bLength, b);
+ });
+
+ if (!ensureValidRecordsTable()) {
+ LOG_ERROR("Error creating or migrating Records table in database");
+ closeSQLiteDB();
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error creating or migrating Records table in database") };
+ }
+
+ if (!ensureValidIndexRecordsTable()) {
+ LOG_ERROR("Error creating or migrating Index Records table in database");
+ closeSQLiteDB();
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error creating or migrating Index Records table in database") };
+ }
+
+ if (!ensureValidIndexRecordsIndex()) {
+ LOG_ERROR("Error creating or migrating Index Records index in database");
+ closeSQLiteDB();
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error creating or migrating Index Records index in database") };
+ }
+
+ if (!ensureValidBlobTables()) {
+ LOG_ERROR("Error creating or confirming Blob Records tables in database");
+ closeSQLiteDB();
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error creating or confirming Blob Records tables in database") };
+ }
+
+ auto databaseInfo = extractExistingDatabaseInfo();
+ if (!databaseInfo)
+ databaseInfo = createAndPopulateInitialDatabaseInfo();
+
+ if (!databaseInfo) {
+ LOG_ERROR("Unable to establish IDB database at path '%s'", dbFilename.utf8().data());
+ closeSQLiteDB();
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to establish IDB database file") };
+ }
+
+ m_databaseInfo = WTFMove(databaseInfo);
+ info = *m_databaseInfo;
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::beginTransaction(const IDBTransactionInfo& info)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::beginTransaction - %s", info.identifier().loggingString().utf8().data());
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+ ASSERT(m_databaseInfo);
+
+ auto addResult = m_transactions.add(info.identifier(), nullptr);
+ if (!addResult.isNewEntry) {
+ LOG_ERROR("Attempt to establish transaction identifier that already exists");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to establish transaction identifier that already exists") };
+ }
+
+ addResult.iterator->value = std::make_unique<SQLiteIDBTransaction>(*this, info);
+
+ auto error = addResult.iterator->value->begin(*m_sqliteDB);
+ if (error.isNull() && info.mode() == IDBTransactionMode::Versionchange) {
+ m_originalDatabaseInfoBeforeVersionChange = std::make_unique<IDBDatabaseInfo>(*m_databaseInfo);
+
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("UPDATE IDBDatabaseInfo SET value = ? where key = 'DatabaseVersion';"));
+ if (sql.prepare() != SQLITE_OK
+ || sql.bindText(1, String::number(info.newVersion())) != SQLITE_OK
+ || sql.step() != SQLITE_DONE)
+ error = { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to store new database version in database") };
+ }
+
+ return error;
+}
+
+IDBError SQLiteIDBBackingStore::abortTransaction(const IDBResourceIdentifier& identifier)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::abortTransaction - %s", identifier.loggingString().utf8().data());
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto transaction = m_transactions.take(identifier);
+ if (!transaction) {
+ LOG_ERROR("Attempt to commit a transaction that hasn't been established");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to abort a transaction that hasn't been established") };
+ }
+
+ if (transaction->mode() == IDBTransactionMode::Versionchange && m_originalDatabaseInfoBeforeVersionChange)
+ m_databaseInfo = WTFMove(m_originalDatabaseInfoBeforeVersionChange);
+
+ return transaction->abort();
+}
+
+IDBError SQLiteIDBBackingStore::commitTransaction(const IDBResourceIdentifier& identifier)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::commitTransaction - %s", identifier.loggingString().utf8().data());
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto transaction = m_transactions.take(identifier);
+ if (!transaction) {
+ LOG_ERROR("Attempt to commit a transaction that hasn't been established");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to commit a transaction that hasn't been established") };
+ }
+
+ auto error = transaction->commit();
+ if (!error.isNull()) {
+ if (transaction->mode() == IDBTransactionMode::Versionchange) {
+ ASSERT(m_originalDatabaseInfoBeforeVersionChange);
+ m_databaseInfo = WTFMove(m_originalDatabaseInfoBeforeVersionChange);
+ }
+ } else
+ m_originalDatabaseInfoBeforeVersionChange = nullptr;
+
+ return error;
+}
+
+IDBError SQLiteIDBBackingStore::createObjectStore(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo& info)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::createObjectStore - adding OS %s with ID %" PRIu64, info.name().utf8().data(), info.identifier());
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to create an object store without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to create an object store without an in-progress transaction") };
+ }
+ if (transaction->mode() != IDBTransactionMode::Versionchange) {
+ LOG_ERROR("Attempt to create an object store in a non-version-change transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to create an object store in a non-version-change transaction") };
+ }
+
+ RefPtr<SharedBuffer> keyPathBlob = serializeIDBKeyPath(info.keyPath());
+ if (!keyPathBlob) {
+ LOG_ERROR("Unable to serialize IDBKeyPath to save in database for new object store");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize IDBKeyPath to save in database for new object store") };
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::CreateObjectStoreInfo, ASCIILiteral("INSERT INTO ObjectStoreInfo VALUES (?, ?, ?, ?, ?);"));
+ if (!sql
+ || sql->bindInt64(1, info.identifier()) != SQLITE_OK
+ || sql->bindText(2, info.name()) != SQLITE_OK
+ || sql->bindBlob(3, keyPathBlob->data(), keyPathBlob->size()) != SQLITE_OK
+ || sql->bindInt(4, info.autoIncrement()) != SQLITE_OK
+ || sql->bindInt64(5, info.maxIndexID()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not add object store '%s' to ObjectStoreInfo table (%i) - %s", info.name().utf8().data(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not create object store") };
+ }
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::CreateObjectStoreKeyGenerator, ASCIILiteral("INSERT INTO KeyGenerators VALUES (?, 0);"));
+ if (!sql
+ || sql->bindInt64(1, info.identifier()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not seed initial key generator value for ObjectStoreInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not seed initial key generator value for object store") };
+ }
+ }
+
+ m_databaseInfo->addExistingObjectStore(info);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::deleteObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::deleteObjectStore - object store %" PRIu64, objectStoreIdentifier);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to delete an object store without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete an object store without an in-progress transaction") };
+ }
+ if (transaction->mode() != IDBTransactionMode::Versionchange) {
+ LOG_ERROR("Attempt to delete an object store in a non-version-change transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete an object store in a non-version-change transaction") };
+ }
+
+ // Delete the ObjectStore record
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreInfo, ASCIILiteral("DELETE FROM ObjectStoreInfo WHERE id = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete object store id %" PRIi64 " from ObjectStoreInfo table (%i) - %s", objectStoreIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not delete object store") };
+ }
+ }
+
+ // Delete the ObjectStore's key generator record if there is one.
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreKeyGenerator, ASCIILiteral("DELETE FROM KeyGenerators WHERE objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete object store from KeyGenerators table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not delete key generator for deleted object store") };
+ }
+ }
+
+ // Delete all associated records
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreRecords, ASCIILiteral("DELETE FROM Records WHERE objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete records for object store %" PRIi64 " (%i) - %s", objectStoreIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not delete records for deleted object store") };
+ }
+ }
+
+ // Delete all associated Indexes
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreIndexInfo, ASCIILiteral("DELETE FROM IndexInfo WHERE objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete index from IndexInfo table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not delete IDBIndex for deleted object store") };
+ }
+ }
+
+ // Delete all associated Index records
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreIndexRecords, ASCIILiteral("DELETE FROM IndexRecords WHERE objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete index records(%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not delete IDBIndex records for deleted object store") };
+ }
+ }
+
+ // Delete all unused Blob URL records.
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreBlobRecords, ASCIILiteral("DELETE FROM BlobRecords WHERE objectStoreRow NOT IN (SELECT recordID FROM Records)"));
+ if (!sql
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete Blob URL records(%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not delete stored blob records for deleted object store") };
+ }
+ }
+
+ // Delete all unused Blob File records.
+ auto error = deleteUnusedBlobFileRecords(*transaction);
+ if (!error.isNull())
+ return error;
+
+ m_databaseInfo->deleteObjectStore(objectStoreIdentifier);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::renameObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::renameObjectStore - object store %" PRIu64, objectStoreIdentifier);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to rename an object store without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename an object store without an in-progress transaction") };
+ }
+ if (transaction->mode() != IDBTransactionMode::Versionchange) {
+ LOG_ERROR("Attempt to rename an object store in a non-version-change transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename an object store in a non-version-change transaction") };
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::RenameObjectStore, ASCIILiteral("UPDATE ObjectStoreInfo SET name = ? WHERE id = ?;"));
+ if (!sql
+ || sql->bindText(1, newName) != SQLITE_OK
+ || sql->bindInt64(2, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not update name for object store id %" PRIi64 " in ObjectStoreInfo table (%i) - %s", objectStoreIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not rename object store") };
+ }
+ }
+
+ m_databaseInfo->renameObjectStore(objectStoreIdentifier, newName);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::clearObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::clearObjectStore - object store %" PRIu64, objectStoreID);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to clear an object store without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to clear an object store without an in-progress transaction") };
+ }
+ if (transaction->mode() == IDBTransactionMode::Readonly) {
+ LOG_ERROR("Attempt to clear an object store in a read-only transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to clear an object store in a read-only transaction") };
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::ClearObjectStoreRecords, ASCIILiteral("DELETE FROM Records WHERE objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not clear records from object store id %" PRIi64 " (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to clear object store") };
+ }
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::ClearObjectStoreIndexRecords, ASCIILiteral("DELETE FROM IndexRecords WHERE objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete records from index record store id %" PRIi64 " (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to delete index records while clearing object store") };
+ }
+ }
+
+ transaction->notifyCursorsOfChanges(objectStoreID);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::createIndex(const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo& info)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::createIndex - ObjectStore %" PRIu64 ", Index %" PRIu64, info.objectStoreIdentifier(), info.identifier());
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to create an index without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to create an index without an in-progress transaction") };
+ }
+ if (transaction->mode() != IDBTransactionMode::Versionchange) {
+ LOG_ERROR("Attempt to create an index in a non-version-change transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to create an index in a non-version-change transaction") };
+ }
+
+ RefPtr<SharedBuffer> keyPathBlob = serializeIDBKeyPath(info.keyPath());
+ if (!keyPathBlob) {
+ LOG_ERROR("Unable to serialize IDBKeyPath to save in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize IDBKeyPath to create index in database") };
+ }
+
+ auto* sql = cachedStatement(SQL::CreateIndexInfo, ASCIILiteral("INSERT INTO IndexInfo VALUES (?, ?, ?, ?, ?, ?);"));
+ if (!sql
+ || sql->bindInt64(1, info.identifier()) != SQLITE_OK
+ || sql->bindText(2, info.name()) != SQLITE_OK
+ || sql->bindInt64(3, info.objectStoreIdentifier()) != SQLITE_OK
+ || sql->bindBlob(4, keyPathBlob->data(), keyPathBlob->size()) != SQLITE_OK
+ || sql->bindInt(5, info.unique()) != SQLITE_OK
+ || sql->bindInt(6, info.multiEntry()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not add index '%s' to IndexInfo table (%i) - %s", info.name().utf8().data(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to create index in database") };
+ }
+
+ // Write index records for any records that already exist in this object store.
+
+ auto cursor = transaction->maybeOpenBackingStoreCursor(info.objectStoreIdentifier(), 0, IDBKeyRangeData::allKeys());
+
+ if (!cursor) {
+ LOG_ERROR("Cannot open cursor to populate indexes in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to populate indexes in database") };
+ }
+
+ while (!cursor->currentKey().isNull()) {
+ auto& key = cursor->currentKey();
+ auto* value = cursor->currentValue();
+ ThreadSafeDataBuffer valueBuffer = value ? value->data() : ThreadSafeDataBuffer();
+
+ ASSERT(cursor->currentRecordRowID());
+
+ IDBError error = updateOneIndexForAddRecord(info, key, valueBuffer, cursor->currentRecordRowID());
+ if (!error.isNull()) {
+ auto* sql = cachedStatement(SQL::DeleteIndexInfo, ASCIILiteral("DELETE FROM IndexInfo WHERE id = ? AND objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, info.identifier()) != SQLITE_OK
+ || sql->bindInt64(2, info.objectStoreIdentifier()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Index creation failed due to uniqueness constraint failure, but there was an error deleting the Index record from the database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Index creation failed due to uniqueness constraint failure, but there was an error deleting the Index record from the database") };
+ }
+
+ return error;
+ }
+
+ if (!cursor->advance(1)) {
+ LOG_ERROR("Error advancing cursor while indexing existing records for new index.");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error advancing cursor while indexing existing records for new index") };
+ }
+ }
+
+ auto* objectStore = m_databaseInfo->infoForExistingObjectStore(info.objectStoreIdentifier());
+ ASSERT(objectStore);
+ objectStore->addExistingIndex(info);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::uncheckedHasIndexRecord(const IDBIndexInfo& info, const IDBKeyData& indexKey, bool& hasRecord)
+{
+ hasRecord = false;
+
+ RefPtr<SharedBuffer> indexKeyBuffer = serializeIDBKeyData(indexKey);
+ if (!indexKeyBuffer) {
+ LOG_ERROR("Unable to serialize index key to be stored in the database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize IDBKey to check for index record in database") };
+ }
+
+ auto* sql = cachedStatement(SQL::HasIndexRecord, ASCIILiteral("SELECT rowid FROM IndexRecords WHERE indexID = ? AND objectStoreID = ? AND key = CAST(? AS TEXT);"));
+ if (!sql
+ || sql->bindInt64(1, info.identifier()) != SQLITE_OK
+ || sql->bindInt64(2, info.objectStoreIdentifier()) != SQLITE_OK
+ || sql->bindBlob(3, indexKeyBuffer->data(), indexKeyBuffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Error checking for index record in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error checking for index record in database") };
+ }
+
+ int sqlResult = sql->step();
+ if (sqlResult == SQLITE_OK || sqlResult == SQLITE_DONE)
+ return { };
+
+ if (sqlResult != SQLITE_ROW) {
+ // There was an error fetching the record from the database.
+ LOG_ERROR("Could not check if key exists in index (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error checking for existence of IDBKey in index") };
+ }
+
+ hasRecord = true;
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::uncheckedPutIndexKey(const IDBIndexInfo& info, const IDBKeyData& key, const IndexKey& indexKey, int64_t recordID)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::uncheckedPutIndexKey - (%" PRIu64 ") %s, %s", info.identifier(), key.loggingString().utf8().data(), indexKey.asOneKey().loggingString().utf8().data());
+
+ Vector<IDBKeyData> indexKeys;
+ if (info.multiEntry())
+ indexKeys = indexKey.multiEntry();
+ else
+ indexKeys.append(indexKey.asOneKey());
+
+ if (info.unique()) {
+ bool hasRecord;
+ IDBError error;
+ for (auto& indexKey : indexKeys) {
+ if (!indexKey.isValid())
+ continue;
+ error = uncheckedHasIndexRecord(info, indexKey, hasRecord);
+ if (!error.isNull())
+ return error;
+ if (hasRecord)
+ return IDBError(IDBDatabaseException::ConstraintError);
+ }
+ }
+
+ for (auto& indexKey : indexKeys) {
+ if (!indexKey.isValid())
+ continue;
+ auto error = uncheckedPutIndexRecord(info.objectStoreIdentifier(), info.identifier(), key, indexKey, recordID);
+ if (!error.isNull()) {
+ LOG_ERROR("Unable to put index record for newly created index");
+ return error;
+ }
+ }
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::uncheckedPutIndexRecord(int64_t objectStoreID, int64_t indexID, const WebCore::IDBKeyData& keyValue, const WebCore::IDBKeyData& indexKey, int64_t recordID)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::uncheckedPutIndexRecord - %s, %s", keyValue.loggingString().utf8().data(), indexKey.loggingString().utf8().data());
+
+ RefPtr<SharedBuffer> indexKeyBuffer = serializeIDBKeyData(indexKey);
+ if (!indexKeyBuffer) {
+ LOG_ERROR("Unable to serialize index key to be stored in the database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize index key to be stored in the database") };
+ }
+
+ RefPtr<SharedBuffer> valueBuffer = serializeIDBKeyData(keyValue);
+ if (!valueBuffer) {
+ LOG_ERROR("Unable to serialize the value to be stored in the database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize value to be stored in the database") };
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::PutIndexRecord, ASCIILiteral("INSERT INTO IndexRecords VALUES (?, ?, CAST(? AS TEXT), CAST(? AS TEXT), ?);"));
+ if (!sql
+ || sql->bindInt64(1, indexID) != SQLITE_OK
+ || sql->bindInt64(2, objectStoreID) != SQLITE_OK
+ || sql->bindBlob(3, indexKeyBuffer->data(), indexKeyBuffer->size()) != SQLITE_OK
+ || sql->bindBlob(4, valueBuffer->data(), valueBuffer->size()) != SQLITE_OK
+ || sql->bindInt64(5, recordID) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not put index record for index %" PRIi64 " in object store %" PRIi64 " in Records table (%i) - %s", indexID, objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error putting index record into database") };
+ }
+ }
+
+ return { };
+}
+
+
+IDBError SQLiteIDBBackingStore::deleteIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::deleteIndex - object store %" PRIu64, objectStoreIdentifier);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to delete index without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete index without an in-progress transaction") };
+ }
+
+ if (transaction->mode() != IDBTransactionMode::Versionchange) {
+ LOG_ERROR("Attempt to delete index during a non-version-change transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete index during a non-version-change transaction") };
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::DeleteIndexInfo, ASCIILiteral("DELETE FROM IndexInfo WHERE id = ? AND objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, indexIdentifier) != SQLITE_OK
+ || sql->bindInt64(2, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete index id %" PRIi64 " from IndexInfo table (%i) - %s", objectStoreIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error deleting index from database") };
+ }
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::DeleteIndexRecords, ASCIILiteral("DELETE FROM IndexRecords WHERE indexID = ? AND objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, indexIdentifier) != SQLITE_OK
+ || sql->bindInt64(2, objectStoreIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete index records for index id %" PRIi64 " from IndexRecords table (%i) - %s", indexIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error deleting index records from database") };
+ }
+ }
+
+ auto* objectStore = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ ASSERT(objectStore);
+ objectStore->deleteIndex(indexIdentifier);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::renameIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::renameIndex - object store %" PRIu64 ", index %" PRIu64, objectStoreIdentifier, indexIdentifier);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ if (!objectStoreInfo)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not rename index") };
+
+ auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexIdentifier);
+ if (!indexInfo)
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not rename index") };
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to rename an index without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename an index without an in-progress transaction") };
+ }
+
+ if (transaction->mode() != IDBTransactionMode::Versionchange) {
+ LOG_ERROR("Attempt to rename an index in a non-version-change transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename an index in a non-version-change transaction") };
+ }
+
+ {
+ auto* sql = cachedStatement(SQL::RenameIndex, ASCIILiteral("UPDATE IndexInfo SET name = ? WHERE objectStoreID = ? AND id = ?;"));
+ if (!sql
+ || sql->bindText(1, newName) != SQLITE_OK
+ || sql->bindInt64(2, objectStoreIdentifier) != SQLITE_OK
+ || sql->bindInt64(3, indexIdentifier) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not update name for index id (%" PRIi64 ", %" PRIi64 ") in IndexInfo table (%i) - %s", objectStoreIdentifier, indexIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not rename index") };
+ }
+ }
+
+ indexInfo->rename(newName);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::keyExistsInObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID, const IDBKeyData& keyData, bool& keyExists)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::keyExistsInObjectStore - key %s, object store %" PRIu64, keyData.loggingString().utf8().data(), objectStoreID);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ keyExists = false;
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to see if key exists in objectstore without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to see if key exists in objectstore without an in-progress transaction") };
+ }
+
+ RefPtr<SharedBuffer> keyBuffer = serializeIDBKeyData(keyData);
+ if (!keyBuffer) {
+ LOG_ERROR("Unable to serialize IDBKey to check for existence in object store");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize IDBKey to check for existence in object store") };
+ }
+ auto* sql = cachedStatement(SQL::KeyExistsInObjectStore, ASCIILiteral("SELECT key FROM Records WHERE objectStoreID = ? AND key = CAST(? AS TEXT) LIMIT 1;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->bindBlob(2, keyBuffer->data(), keyBuffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Could not get record from object store %" PRIi64 " from Records table (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to check for existence of IDBKey in object store") };
+ }
+
+ int sqlResult = sql->step();
+ if (sqlResult == SQLITE_OK || sqlResult == SQLITE_DONE)
+ return { };
+
+ if (sqlResult != SQLITE_ROW) {
+ // There was an error fetching the record from the database.
+ LOG_ERROR("Could not check if key exists in object store (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error checking for existence of IDBKey in object store") };
+ }
+
+ keyExists = true;
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::deleteUnusedBlobFileRecords(SQLiteIDBTransaction& transaction)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::deleteUnusedBlobFileRecords");
+
+ // Gather the set of blob URLs and filenames that are no longer in use.
+ HashSet<String> removedBlobFilenames;
+ {
+ auto* sql = cachedStatement(SQL::GetUnusedBlobFilenames, ASCIILiteral("SELECT fileName FROM BlobFiles WHERE blobURL NOT IN (SELECT blobURL FROM BlobRecords)"));
+
+ if (!sql) {
+ LOG_ERROR("Error deleting stored blobs (%i) (Could not gather unused blobURLs) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error deleting stored blobs") };
+ }
+
+ int result = sql->step();
+ while (result == SQLITE_ROW) {
+ removedBlobFilenames.add(sql->getColumnText(0));
+ result = sql->step();
+ }
+
+ if (result != SQLITE_DONE) {
+ LOG_ERROR("Error deleting stored blobs (%i) (Could not gather unused blobURLs) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error deleting stored blobs") };
+ }
+ }
+
+ // Remove the blob records that are no longer in use.
+ if (!removedBlobFilenames.isEmpty()) {
+ auto* sql = cachedStatement(SQL::DeleteUnusedBlobs, ASCIILiteral("DELETE FROM BlobFiles WHERE blobURL NOT IN (SELECT blobURL FROM BlobRecords)"));
+
+ if (!sql
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Error deleting stored blobs (%i) (Could not delete blobFile records) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error deleting stored blobs") };
+ }
+ }
+
+ for (auto& file : removedBlobFilenames)
+ transaction.addRemovedBlobFile(file);
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::deleteRecord(SQLiteIDBTransaction& transaction, int64_t objectStoreID, const IDBKeyData& keyData)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::deleteRecord - key %s, object store %" PRIu64, keyData.loggingString().utf8().data(), objectStoreID);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+ ASSERT(transaction.inProgress());
+ ASSERT(transaction.mode() != IDBTransactionMode::Readonly);
+ UNUSED_PARAM(transaction);
+
+ RefPtr<SharedBuffer> keyBuffer = serializeIDBKeyData(keyData);
+ if (!keyBuffer) {
+ LOG_ERROR("Unable to serialize IDBKeyData to be removed from the database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize IDBKeyData to be removed from the database") };
+ }
+
+ // Get the record ID
+ int64_t recordID;
+ {
+ auto* sql = cachedStatement(SQL::GetObjectStoreRecordID, ASCIILiteral("SELECT recordID FROM Records WHERE objectStoreID = ? AND key = CAST(? AS TEXT);"));
+
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->bindBlob(2, keyBuffer->data(), keyBuffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Could not delete record from object store %" PRIi64 " (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to delete record from object store") };
+ }
+
+ int result = sql->step();
+
+ // If there's no record ID, there's no record to delete.
+ if (result == SQLITE_DONE)
+ return { };
+
+ if (result != SQLITE_ROW) {
+ LOG_ERROR("Could not delete record from object store %" PRIi64 " (%i) (unable to fetch record ID) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to delete record from object store") };
+ }
+
+ recordID = sql->getColumnInt64(0);
+ }
+
+ if (recordID < 1) {
+ LOG_ERROR("Could not delete record from object store %" PRIi64 " (%i) (record ID is invalid) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to delete record from object store") };
+ }
+
+ // Delete the blob records for this object store record.
+ {
+ auto* sql = cachedStatement(SQL::DeleteBlobRecord, ASCIILiteral("DELETE FROM BlobRecords WHERE objectStoreRow = ?;"));
+
+ if (!sql
+ || sql->bindInt64(1, recordID) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete record from object store %" PRIi64 " (%i) (Could not delete BlobRecords records) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to delete record from object store") };
+ }
+ }
+
+ auto error = deleteUnusedBlobFileRecords(transaction);
+ if (!error.isNull())
+ return error;
+
+ // Delete record from object store
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreRecord, ASCIILiteral("DELETE FROM Records WHERE objectStoreID = ? AND key = CAST(? AS TEXT);"));
+
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->bindBlob(2, keyBuffer->data(), keyBuffer->size()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete record from object store %" PRIi64 " (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to delete record from object store") };
+ }
+ }
+
+ // Delete record from indexes store
+ {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreIndexRecord, ASCIILiteral("DELETE FROM IndexRecords WHERE objectStoreID = ? AND value = CAST(? AS TEXT);"));
+
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->bindBlob(2, keyBuffer->data(), keyBuffer->size()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not delete record from indexes for object store %" PRIi64 " (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to delete index entries for object store record") };
+ }
+ }
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::deleteRange(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID, const IDBKeyRangeData& keyRange)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::deleteRange - range %s, object store %" PRIu64, keyRange.loggingString().utf8().data(), objectStoreID);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to delete range from database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete range from database without an in-progress transaction") };
+ }
+ if (transaction->mode() == IDBTransactionMode::Readonly) {
+ LOG_ERROR("Attempt to delete records from an object store in a read-only transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete records from an object store in a read-only transaction") };
+ }
+
+ // If the range to delete is exactly one key we can delete it right now.
+ if (keyRange.isExactlyOneKey()) {
+ auto error = deleteRecord(*transaction, objectStoreID, keyRange.lowerKey);
+ if (!error.isNull()) {
+ LOG_ERROR("Failed to delete record for key '%s'", keyRange.lowerKey.loggingString().utf8().data());
+ return error;
+ }
+
+ transaction->notifyCursorsOfChanges(objectStoreID);
+
+ return { };
+ }
+
+ auto cursor = transaction->maybeOpenBackingStoreCursor(objectStoreID, 0, keyRange);
+ if (!cursor) {
+ LOG_ERROR("Cannot open cursor to delete range of records from the database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Cannot open cursor to delete range of records from the database") };
+ }
+
+ Vector<IDBKeyData> keys;
+ while (!cursor->didComplete() && !cursor->didError()) {
+ keys.append(cursor->currentKey());
+ cursor->advance(1);
+ }
+
+ if (cursor->didError()) {
+ LOG_ERROR("Cursor failed while accumulating range of records from the database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Cursor failed while accumulating range of records from the database") };
+ }
+
+ IDBError error;
+ for (auto& key : keys) {
+ error = deleteRecord(*transaction, objectStoreID, key);
+ if (!error.isNull()) {
+ LOG_ERROR("deleteRange: Error deleting keys in range");
+ break;
+ }
+ }
+
+ transaction->notifyCursorsOfChanges(objectStoreID);
+
+ return error;
+}
+
+IDBError SQLiteIDBBackingStore::updateOneIndexForAddRecord(const IDBIndexInfo& info, const IDBKeyData& key, const ThreadSafeDataBuffer& value, int64_t recordID)
+{
+ JSLockHolder locker(vm());
+
+ auto jsValue = deserializeIDBValueToJSValue(*globalObject().globalExec(), value);
+ if (jsValue.isUndefinedOrNull())
+ return { };
+
+ IndexKey indexKey;
+ generateIndexKeyForValue(*m_globalObject->globalExec(), info, jsValue, indexKey);
+
+ if (indexKey.isNull())
+ return { };
+
+ return uncheckedPutIndexKey(info, key, indexKey, recordID);
+}
+
+IDBError SQLiteIDBBackingStore::updateAllIndexesForAddRecord(const IDBObjectStoreInfo& info, const IDBKeyData& key, const ThreadSafeDataBuffer& value, int64_t recordID)
+{
+ JSLockHolder locker(vm());
+
+ auto jsValue = deserializeIDBValueToJSValue(*globalObject().globalExec(), value);
+ if (jsValue.isUndefinedOrNull())
+ return { };
+
+ IDBError error;
+ bool anyRecordsSucceeded = false;
+ for (auto& index : info.indexMap().values()) {
+ IndexKey indexKey;
+ generateIndexKeyForValue(*m_globalObject->globalExec(), index, jsValue, indexKey);
+
+ if (indexKey.isNull())
+ continue;
+
+ error = uncheckedPutIndexKey(index, key, indexKey, recordID);
+ if (!error.isNull())
+ break;
+
+ anyRecordsSucceeded = true;
+ }
+
+ if (!error.isNull() && anyRecordsSucceeded) {
+ RefPtr<SharedBuffer> keyBuffer = serializeIDBKeyData(key);
+
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreIndexRecord, ASCIILiteral("DELETE FROM IndexRecords WHERE objectStoreID = ? AND value = CAST(? AS TEXT);"));
+
+ if (!sql
+ || sql->bindInt64(1, info.identifier()) != SQLITE_OK
+ || sql->bindBlob(2, keyBuffer->data(), keyBuffer->size()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Adding one Index record failed, but failed to remove all others that previously succeeded");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Adding one Index record failed, but failed to remove all others that previously succeeded") };
+ }
+ }
+
+ return error;
+}
+
+IDBError SQLiteIDBBackingStore::addRecord(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo& objectStoreInfo, const IDBKeyData& keyData, const IDBValue& value)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::addRecord - key %s, object store %" PRIu64, keyData.loggingString().utf8().data(), objectStoreInfo.identifier());
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+ ASSERT(value.data().data());
+ ASSERT(value.blobURLs().size() == value.blobFilePaths().size());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to store a record in an object store without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to store a record in an object store without an in-progress transaction") };
+ }
+ if (transaction->mode() == IDBTransactionMode::Readonly) {
+ LOG_ERROR("Attempt to store a record in an object store in a read-only transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to store a record in an object store in a read-only transaction") };
+ }
+
+ RefPtr<SharedBuffer> keyBuffer = serializeIDBKeyData(keyData);
+ if (!keyBuffer) {
+ LOG_ERROR("Unable to serialize IDBKey to be stored in an object store");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize IDBKey to be stored in an object store") };
+ }
+
+ int64_t recordID = 0;
+ {
+ auto* sql = cachedStatement(SQL::AddObjectStoreRecord, ASCIILiteral("INSERT INTO Records VALUES (?, CAST(? AS TEXT), ?, NULL);"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreInfo.identifier()) != SQLITE_OK
+ || sql->bindBlob(2, keyBuffer->data(), keyBuffer->size()) != SQLITE_OK
+ || sql->bindBlob(3, value.data().data()->data(), value.data().data()->size()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not put record for object store %" PRIi64 " in Records table (%i) - %s", objectStoreInfo.identifier(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to store record in object store") };
+ }
+
+ recordID = m_sqliteDB->lastInsertRowID();
+ }
+
+ auto error = updateAllIndexesForAddRecord(objectStoreInfo, keyData, value.data(), recordID);
+
+ if (!error.isNull()) {
+ auto* sql = cachedStatement(SQL::DeleteObjectStoreRecord, ASCIILiteral("DELETE FROM Records WHERE objectStoreID = ? AND key = CAST(? AS TEXT);"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreInfo.identifier()) != SQLITE_OK
+ || sql->bindBlob(2, keyBuffer->data(), keyBuffer->size()) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Indexing new object store record failed, but unable to remove the object store record itself");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Indexing new object store record failed, but unable to remove the object store record itself") };
+ }
+
+ return error;
+ }
+
+ const Vector<String>& blobURLs = value.blobURLs();
+ const Vector<String>& blobFiles = value.blobFilePaths();
+ for (size_t i = 0; i < blobURLs.size(); ++i) {
+ auto& url = blobURLs[i];
+ {
+ auto* sql = cachedStatement(SQL::AddBlobRecord, ASCIILiteral("INSERT INTO BlobRecords VALUES (?, ?);"));
+ if (!sql
+ || sql->bindInt64(1, recordID) != SQLITE_OK
+ || sql->bindText(2, url) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Unable to record Blob record in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to record Blob record in database") };
+ }
+ }
+ int64_t potentialFileNameInteger = m_sqliteDB->lastInsertRowID();
+
+ // If we already have a file for this blobURL, nothing left to do.
+ {
+ auto* sql = cachedStatement(SQL::BlobFilenameForBlobURL, ASCIILiteral("SELECT fileName FROM BlobFiles WHERE blobURL = ?;"));
+ if (!sql
+ || sql->bindText(1, url) != SQLITE_OK) {
+ LOG_ERROR("Unable to examine Blob filenames in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to examine Blob filenames in database") };
+ }
+
+ int result = sql->step();
+ if (result != SQLITE_ROW && result != SQLITE_DONE) {
+ LOG_ERROR("Unable to examine Blob filenames in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to examine Blob filenames in database") };
+ }
+
+ if (result == SQLITE_ROW)
+ continue;
+ }
+
+ // We don't already have a file for this blobURL, so commit our file as a unique filename
+ String storedFilename = String::format("%" PRId64 ".blob", potentialFileNameInteger);
+ {
+ auto* sql = cachedStatement(SQL::AddBlobFilename, ASCIILiteral("INSERT INTO BlobFiles VALUES (?, ?);"));
+ if (!sql
+ || sql->bindText(1, url) != SQLITE_OK
+ || sql->bindText(2, storedFilename) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Unable to record Blob file record in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to record Blob file record in database") };
+ }
+ }
+
+ transaction->addBlobFile(blobFiles[i], storedFilename);
+ }
+
+ transaction->notifyCursorsOfChanges(objectStoreInfo.identifier());
+
+ return error;
+}
+
+IDBError SQLiteIDBBackingStore::getBlobRecordsForObjectStoreRecord(int64_t objectStoreRecord, Vector<String>& blobURLs, Vector<String>& blobFilePaths)
+{
+ ASSERT(objectStoreRecord);
+
+ HashSet<String> blobURLSet;
+ {
+ auto* sql = cachedStatement(SQL::GetBlobURL, ASCIILiteral("SELECT blobURL FROM BlobRecords WHERE objectStoreRow = ?"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreRecord) != SQLITE_OK) {
+ LOG_ERROR("Could not prepare statement to fetch blob URLs for object store record (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to look up blobURL records in object store by key range") };
+ }
+
+ int sqlResult = sql->step();
+ if (sqlResult == SQLITE_OK || sqlResult == SQLITE_DONE) {
+ // There are no blobURLs in the database for this object store record.
+ return { };
+ }
+
+ while (sqlResult == SQLITE_ROW) {
+ blobURLSet.add(sql->getColumnText(0));
+ sqlResult = sql->step();
+ }
+
+ if (sqlResult != SQLITE_DONE) {
+ LOG_ERROR("Could not fetch blob URLs for object store record (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to look up blobURL records in object store by key range") };
+ }
+ }
+
+ ASSERT(!blobURLSet.isEmpty());
+ String databaseDirectory = fullDatabaseDirectory();
+ for (auto& blobURL : blobURLSet) {
+ auto* sql = cachedStatement(SQL::BlobFilenameForBlobURL, ASCIILiteral("SELECT fileName FROM BlobFiles WHERE blobURL = ?;"));
+ if (!sql
+ || sql->bindText(1, blobURL) != SQLITE_OK) {
+ LOG_ERROR("Could not prepare statement to fetch blob filename for object store record (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to look up blobURL records in object store by key range") };
+ }
+
+ if (sql->step() != SQLITE_ROW) {
+ LOG_ERROR("Entry for blob filename for blob url %s does not exist (%i) - %s", blobURL.utf8().data(), m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to look up blobURL records in object store by key range") };
+ }
+
+ blobURLs.append(blobURL);
+
+ String fileName = sql->getColumnText(0);
+ blobFilePaths.append(pathByAppendingComponent(databaseDirectory, fileName));
+ }
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::getRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID, const IDBKeyRangeData& keyRange, IDBGetRecordDataType type, IDBGetResult& resultValue)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::getRecord - key range %s, object store %" PRIu64, keyRange.loggingString().utf8().data(), objectStoreID);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to get a record from database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to get a record from database without an in-progress transaction") };
+ }
+
+ auto key = keyRange.lowerKey;
+ if (key.isNull())
+ key = IDBKeyData::minimum();
+ RefPtr<SharedBuffer> lowerBuffer = serializeIDBKeyData(key);
+ if (!lowerBuffer) {
+ LOG_ERROR("Unable to serialize lower IDBKey in lookup range");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize lower IDBKey in lookup range") };
+ }
+
+ key = keyRange.upperKey;
+ if (key.isNull())
+ key = IDBKeyData::maximum();
+ RefPtr<SharedBuffer> upperBuffer = serializeIDBKeyData(key);
+ if (!upperBuffer) {
+ LOG_ERROR("Unable to serialize upper IDBKey in lookup range");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize upper IDBKey in lookup range") };
+ }
+
+ int64_t recordID = 0;
+ ThreadSafeDataBuffer resultBuffer;
+ {
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperOpen("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperClosed("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperOpen("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperClosed("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperOpenKeyOnly("SELECT key FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperClosedKeyOnly("SELECT key FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperOpenKeyOnly("SELECT key FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperClosedKeyOnly("SELECT key FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+
+ SQLiteStatement* sql = nullptr;
+
+ switch (type) {
+ case IDBGetRecordDataType::KeyAndValue:
+ if (keyRange.lowerOpen) {
+ if (keyRange.upperOpen)
+ sql = cachedStatement(SQL::GetValueRecordsLowerOpenUpperOpen, lowerOpenUpperOpen.get());
+ else
+ sql = cachedStatement(SQL::GetValueRecordsLowerOpenUpperClosed, lowerOpenUpperClosed.get());
+ } else {
+ if (keyRange.upperOpen)
+ sql = cachedStatement(SQL::GetValueRecordsLowerClosedUpperOpen, lowerClosedUpperOpen.get());
+ else
+ sql = cachedStatement(SQL::GetValueRecordsLowerClosedUpperClosed, lowerClosedUpperClosed.get());
+ }
+ break;
+ case IDBGetRecordDataType::KeyOnly:
+ if (keyRange.lowerOpen) {
+ if (keyRange.upperOpen)
+ sql = cachedStatement(SQL::GetKeyRecordsLowerOpenUpperOpen, lowerOpenUpperOpenKeyOnly.get());
+ else
+ sql = cachedStatement(SQL::GetKeyRecordsLowerOpenUpperClosed, lowerOpenUpperClosedKeyOnly.get());
+ } else {
+ if (keyRange.upperOpen)
+ sql = cachedStatement(SQL::GetKeyRecordsLowerClosedUpperOpen, lowerClosedUpperOpenKeyOnly.get());
+ else
+ sql = cachedStatement(SQL::GetKeyRecordsLowerClosedUpperClosed, lowerClosedUpperClosedKeyOnly.get());
+ }
+ }
+
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->bindBlob(2, lowerBuffer->data(), lowerBuffer->size()) != SQLITE_OK
+ || sql->bindBlob(3, upperBuffer->data(), upperBuffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Could not get key range record from object store %" PRIi64 " from Records table (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to look up record in object store by key range") };
+ }
+
+ int sqlResult = sql->step();
+
+ if (sqlResult == SQLITE_OK || sqlResult == SQLITE_DONE) {
+ // There was no record for the key in the database.
+ return { };
+ }
+ if (sqlResult != SQLITE_ROW) {
+ // There was an error fetching the record from the database.
+ LOG_ERROR("Could not get record from object store %" PRIi64 " from Records table (%i) - %s", objectStoreID, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error looking up record in object store by key range") };
+ }
+
+ Vector<uint8_t> buffer;
+ sql->getColumnBlobAsVector(0, buffer);
+ resultBuffer = ThreadSafeDataBuffer::adoptVector(buffer);
+
+ if (type == IDBGetRecordDataType::KeyAndValue)
+ recordID = sql->getColumnInt64(1);
+ }
+
+ if (type == IDBGetRecordDataType::KeyOnly) {
+ auto* vector = resultBuffer.data();
+ if (!vector) {
+ LOG_ERROR("Unable to deserialize key data from database for IDBObjectStore.getKey()");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error extracting key data from database executing IDBObjectStore.getKey()") };
+ }
+
+ IDBKeyData keyData;
+ if (!deserializeIDBKeyData(vector->data(), vector->size(), keyData)) {
+ LOG_ERROR("Unable to deserialize key data from database for IDBObjectStore.getKey()");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error extracting key data from database executing IDBObjectStore.getKey()") };
+ }
+
+ resultValue = { keyData };
+ return { };
+ }
+
+ ASSERT(recordID);
+ Vector<String> blobURLs, blobFilePaths;
+ auto error = getBlobRecordsForObjectStoreRecord(recordID, blobURLs, blobFilePaths);
+ ASSERT(blobURLs.size() == blobFilePaths.size());
+
+ if (!error.isNull())
+ return error;
+
+ resultValue = { { resultBuffer, WTFMove(blobURLs), WTFMove(blobFilePaths) } };
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::getAllRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData& getAllRecordsData, IDBGetAllResult& result)
+{
+ return getAllRecordsData.indexIdentifier ? getAllIndexRecords(transactionIdentifier, getAllRecordsData, result) : getAllObjectStoreRecords(transactionIdentifier, getAllRecordsData, result);
+}
+
+SQLiteStatement* SQLiteIDBBackingStore::cachedStatementForGetAllObjectStoreRecords(const IDBGetAllRecordsData& getAllRecordsData)
+{
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperOpenKey("SELECT key FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperClosedKey("SELECT key FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperOpenKey("SELECT key FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperClosedKey("SELECT key FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperOpenValue("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerOpenUpperClosedValue("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperOpenValue("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;");
+ static NeverDestroyed<ASCIILiteral> lowerClosedUpperClosedValue("SELECT value, ROWID FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;");
+
+ if (getAllRecordsData.getAllType == IndexedDB::GetAllType::Keys) {
+ if (getAllRecordsData.keyRangeData.lowerOpen) {
+ if (getAllRecordsData.keyRangeData.upperOpen)
+ return cachedStatement(SQL::GetAllKeyRecordsLowerOpenUpperOpen, lowerOpenUpperOpenKey.get());
+ return cachedStatement(SQL::GetAllKeyRecordsLowerOpenUpperClosed, lowerOpenUpperClosedKey.get());
+ }
+
+ if (getAllRecordsData.keyRangeData.upperOpen)
+ return cachedStatement(SQL::GetAllKeyRecordsLowerClosedUpperOpen, lowerClosedUpperOpenKey.get());
+ return cachedStatement(SQL::GetAllKeyRecordsLowerClosedUpperClosed, lowerClosedUpperClosedKey.get());
+ }
+
+ if (getAllRecordsData.keyRangeData.lowerOpen) {
+ if (getAllRecordsData.keyRangeData.upperOpen)
+ return cachedStatement(SQL::GetValueRecordsLowerOpenUpperOpen, lowerOpenUpperOpenValue.get());
+ return cachedStatement(SQL::GetValueRecordsLowerOpenUpperClosed, lowerOpenUpperClosedValue.get());
+ }
+
+ if (getAllRecordsData.keyRangeData.upperOpen)
+ return cachedStatement(SQL::GetValueRecordsLowerClosedUpperOpen, lowerClosedUpperOpenValue.get());
+ return cachedStatement(SQL::GetValueRecordsLowerClosedUpperClosed, lowerClosedUpperClosedValue.get());
+}
+
+IDBError SQLiteIDBBackingStore::getAllObjectStoreRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData& getAllRecordsData, IDBGetAllResult& result)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::getAllObjectStoreRecords");
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to get records from database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to get records from database without an in-progress transaction") };
+ }
+
+ auto key = getAllRecordsData.keyRangeData.lowerKey;
+ if (key.isNull())
+ key = IDBKeyData::minimum();
+ auto lowerBuffer = serializeIDBKeyData(key);
+ if (!lowerBuffer) {
+ LOG_ERROR("Unable to serialize lower IDBKey in lookup range");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize lower IDBKey in lookup range") };
+ }
+
+ key = getAllRecordsData.keyRangeData.upperKey;
+ if (key.isNull())
+ key = IDBKeyData::maximum();
+ auto upperBuffer = serializeIDBKeyData(key);
+ if (!upperBuffer) {
+ LOG_ERROR("Unable to serialize upper IDBKey in lookup range");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize upper IDBKey in lookup range") };
+ }
+
+ auto* sql = cachedStatementForGetAllObjectStoreRecords(getAllRecordsData);
+ if (!sql
+ || sql->bindInt64(1, getAllRecordsData.objectStoreIdentifier) != SQLITE_OK
+ || sql->bindBlob(2, lowerBuffer->data(), lowerBuffer->size()) != SQLITE_OK
+ || sql->bindBlob(3, upperBuffer->data(), upperBuffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Could not get key range record from object store %" PRIi64 " from Records table (%i) - %s", getAllRecordsData.objectStoreIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Failed to look up record in object store by key range") };
+ }
+
+ result = { getAllRecordsData.getAllType };
+
+ uint32_t targetResults;
+ if (getAllRecordsData.count && getAllRecordsData.count.value())
+ targetResults = getAllRecordsData.count.value();
+ else
+ targetResults = std::numeric_limits<uint32_t>::max();
+
+ int sqlResult = sql->step();
+ uint32_t returnedResults = 0;
+
+ while (sqlResult == SQLITE_ROW && returnedResults < targetResults) {
+ if (getAllRecordsData.getAllType == IndexedDB::GetAllType::Values) {
+ Vector<uint8_t> buffer;
+ sql->getColumnBlobAsVector(0, buffer);
+ ThreadSafeDataBuffer resultBuffer = ThreadSafeDataBuffer::adoptVector(buffer);
+
+ auto recordID = sql->getColumnInt64(1);
+
+ ASSERT(recordID);
+ Vector<String> blobURLs, blobFilePaths;
+ auto error = getBlobRecordsForObjectStoreRecord(recordID, blobURLs, blobFilePaths);
+ ASSERT(blobURLs.size() == blobFilePaths.size());
+
+ if (!error.isNull())
+ return error;
+
+ result.addValue({ resultBuffer, WTFMove(blobURLs), WTFMove(blobFilePaths) });
+ } else {
+ Vector<uint8_t> keyData;
+ IDBKeyData key;
+ sql->getColumnBlobAsVector(0, keyData);
+
+ if (!deserializeIDBKeyData(keyData.data(), keyData.size(), key)) {
+ LOG_ERROR("Unable to deserialize key data from database while getting all key records");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to deserialize key data while getting all key records") };
+ }
+
+ result.addKey(WTFMove(key));
+ }
+
+ ++returnedResults;
+ sqlResult = sql->step();
+ }
+
+ if (sqlResult == SQLITE_OK || sqlResult == SQLITE_DONE || sqlResult == SQLITE_ROW) {
+ // Finished getting results
+ return { };
+ }
+
+ // There was an error fetching records from the database.
+ LOG_ERROR("Could not get record from object store %" PRIi64 " from Records table (%i) - %s", getAllRecordsData.objectStoreIdentifier, m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error looking up record in object store by key range") };
+}
+
+IDBError SQLiteIDBBackingStore::getAllIndexRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData& getAllRecordsData, IDBGetAllResult& result)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::getAllIndexRecords - %s", getAllRecordsData.keyRangeData.loggingString().utf8().data());
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to get all index records from database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to get all index records from database without an in-progress transaction") };
+ }
+
+ auto cursor = transaction->maybeOpenBackingStoreCursor(getAllRecordsData.objectStoreIdentifier, getAllRecordsData.indexIdentifier, getAllRecordsData.keyRangeData);
+ if (!cursor) {
+ LOG_ERROR("Cannot open cursor to perform index gets in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Cannot open cursor to perform index gets in database") };
+ }
+
+ if (cursor->didError()) {
+ LOG_ERROR("Cursor failed while looking up index records in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Cursor failed while looking up index records in database") };
+ }
+
+ result = { getAllRecordsData.getAllType };
+ uint32_t currentCount = 0;
+ uint32_t targetCount = getAllRecordsData.count ? getAllRecordsData.count.value() : 0;
+ if (!targetCount)
+ targetCount = std::numeric_limits<uint32_t>::max();
+ while (!cursor->didComplete() && !cursor->didError() && currentCount < targetCount) {
+ if (getAllRecordsData.getAllType == IndexedDB::GetAllType::Keys) {
+ IDBKeyData keyCopy = cursor->currentPrimaryKey();
+ result.addKey(WTFMove(keyCopy));
+ } else
+ result.addValue(cursor->currentValue() ? *cursor->currentValue() : IDBValue());
+
+ ++currentCount;
+ cursor->advance(1);
+ }
+
+ if (cursor->didError()) {
+ LOG_ERROR("Cursor failed while looking up index records in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Cursor failed while looking up index records in database") };
+ }
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::getIndexRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID, uint64_t indexID, IndexedDB::IndexRecordType type, const IDBKeyRangeData& range, IDBGetResult& getResult)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::getIndexRecord - %s", range.loggingString().utf8().data());
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to get an index record from database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to get an index record from database without an in-progress transaction") };
+ }
+
+ if (range.isExactlyOneKey())
+ return uncheckedGetIndexRecordForOneKey(indexID, objectStoreID, type, range.lowerKey, getResult);
+
+ auto cursor = transaction->maybeOpenBackingStoreCursor(objectStoreID, indexID, range);
+ if (!cursor) {
+ LOG_ERROR("Cannot open cursor to perform index get in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Cannot open cursor to perform index get in database") };
+ }
+
+ if (cursor->didError()) {
+ LOG_ERROR("Cursor failed while looking up index record in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Cursor failed while looking up index record in database") };
+ }
+
+ if (cursor->didComplete())
+ getResult = { };
+ else {
+ if (type == IndexedDB::IndexRecordType::Key)
+ getResult = { cursor->currentPrimaryKey() };
+ else
+ getResult = { cursor->currentValue() ? *cursor->currentValue() : IDBValue(), cursor->currentPrimaryKey() };
+ }
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::uncheckedGetIndexRecordForOneKey(int64_t indexID, int64_t objectStoreID, IndexedDB::IndexRecordType type, const IDBKeyData& key, IDBGetResult& getResult)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::uncheckedGetIndexRecordForOneKey");
+
+ ASSERT(key.isValid() && key.type() != KeyType::Max && key.type() != KeyType::Min);
+
+ RefPtr<SharedBuffer> buffer = serializeIDBKeyData(key);
+ if (!buffer) {
+ LOG_ERROR("Unable to serialize IDBKey to look up one index record");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to serialize IDBKey to look up one index record") };
+ }
+
+ auto* sql = cachedStatement(SQL::GetIndexRecordForOneKey, ASCIILiteral("SELECT IndexRecords.value, Records.value, Records.recordID FROM Records INNER JOIN IndexRecords ON Records.recordID = IndexRecords.objectStoreRecordID WHERE IndexRecords.indexID = ? AND IndexRecords.objectStoreID = ? AND IndexRecords.key = CAST(? AS TEXT) ORDER BY IndexRecords.key, IndexRecords.value"));
+
+ if (!sql
+ || sql->bindInt64(1, indexID) != SQLITE_OK
+ || sql->bindInt64(2, objectStoreID) != SQLITE_OK
+ || sql->bindBlob(3, buffer->data(), buffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Unable to lookup index record in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to lookup index record in database") };
+ }
+
+ int result = sql->step();
+ if (result != SQLITE_ROW && result != SQLITE_DONE) {
+ LOG_ERROR("Unable to lookup index record in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to lookup index record in database") };
+ }
+
+ if (result == SQLITE_DONE)
+ return { };
+
+ IDBKeyData objectStoreKey;
+ Vector<uint8_t> keyVector;
+ sql->getColumnBlobAsVector(0, keyVector);
+
+ if (!deserializeIDBKeyData(keyVector.data(), keyVector.size(), objectStoreKey)) {
+ LOG_ERROR("Unable to deserialize key looking up index record in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to deserialize key looking up index record in database") };
+ }
+
+ if (type == IndexedDB::IndexRecordType::Key) {
+ getResult = { objectStoreKey };
+ return { };
+ }
+
+ sql->getColumnBlobAsVector(1, keyVector);
+
+ int64_t recordID = sql->getColumnInt64(2);
+ Vector<String> blobURLs, blobFilePaths;
+ auto error = getBlobRecordsForObjectStoreRecord(recordID, blobURLs, blobFilePaths);
+ ASSERT(blobURLs.size() == blobFilePaths.size());
+
+ if (!error.isNull())
+ return error;
+
+ getResult = { { ThreadSafeDataBuffer::adoptVector(keyVector), WTFMove(blobURLs), WTFMove(blobFilePaths) }, objectStoreKey };
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::getCount(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData& range, uint64_t& outCount)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::getCount - object store %" PRIu64, objectStoreIdentifier);
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ outCount = 0;
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to get count from database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to get count from database without an in-progress transaction") };
+ }
+
+ auto cursor = transaction->maybeOpenBackingStoreCursor(objectStoreIdentifier, indexIdentifier, range);
+ if (!cursor) {
+ LOG_ERROR("Cannot open cursor to populate indexes in database");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to populate indexes in database") };
+ }
+
+ while (cursor->advance(1))
+ ++outCount;
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::uncheckedGetKeyGeneratorValue(int64_t objectStoreID, uint64_t& outValue)
+{
+ auto* sql = cachedStatement(SQL::GetKeyGeneratorValue, ASCIILiteral("SELECT currentKey FROM KeyGenerators WHERE objectStoreID = ?;"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK) {
+ LOG_ERROR("Could not retrieve currentKey from KeyGenerators table (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error getting current key generator value from database") };
+ }
+ int result = sql->step();
+ if (result != SQLITE_ROW) {
+ LOG_ERROR("Could not retreive key generator value for object store, but it should be there.");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Error finding current key generator value in database") };
+ }
+
+ int64_t value = sql->getColumnInt64(0);
+ if (value < 0)
+ return { IDBDatabaseException::ConstraintError, "Current key generator value from database is invalid" };
+
+ outValue = value;
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::uncheckedSetKeyGeneratorValue(int64_t objectStoreID, uint64_t value)
+{
+ auto* sql = cachedStatement(SQL::SetKeyGeneratorValue, ASCIILiteral("INSERT INTO KeyGenerators VALUES (?, ?);"));
+ if (!sql
+ || sql->bindInt64(1, objectStoreID) != SQLITE_OK
+ || sql->bindInt64(2, value) != SQLITE_OK
+ || sql->step() != SQLITE_DONE) {
+ LOG_ERROR("Could not update key generator value (%i) - %s", m_sqliteDB->lastError(), m_sqliteDB->lastErrorMsg());
+ return { IDBDatabaseException::ConstraintError, "Error storing new key generator value in database" };
+ }
+
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::generateKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID, uint64_t& generatedKey)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::generateKeyNumber");
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ // The IndexedDatabase spec defines the max key generator value as 2^53;
+ static uint64_t maxGeneratorValue = 0x20000000000000;
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to generate key in database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to generate key in database without an in-progress transaction") };
+ }
+ if (transaction->mode() == IDBTransactionMode::Readonly) {
+ LOG_ERROR("Attempt to generate key in a read-only transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to generate key in a read-only transaction") };
+ }
+
+ uint64_t currentValue;
+ auto error = uncheckedGetKeyGeneratorValue(objectStoreID, currentValue);
+ if (!error.isNull())
+ return error;
+
+ if (currentValue + 1 > maxGeneratorValue)
+ return { IDBDatabaseException::ConstraintError, "Cannot generate new key value over 2^53 for object store operation" };
+
+ generatedKey = currentValue + 1;
+ return uncheckedSetKeyGeneratorValue(objectStoreID, generatedKey);
+}
+
+IDBError SQLiteIDBBackingStore::revertGeneratedKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID, uint64_t newKeyNumber)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::revertGeneratedKeyNumber - object store %" PRIu64 ", reverted number %" PRIu64, objectStoreID, newKeyNumber);
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to revert key generator value in database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to revert key generator value in database without an in-progress transaction") };
+ }
+ if (transaction->mode() == IDBTransactionMode::Readonly) {
+ LOG_ERROR("Attempt to revert key generator value in a read-only transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to revert key generator value in a read-only transaction") };
+ }
+
+ ASSERT(newKeyNumber);
+ return uncheckedSetKeyGeneratorValue(objectStoreID, newKeyNumber - 1);
+}
+
+IDBError SQLiteIDBBackingStore::maybeUpdateKeyGeneratorNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreID, double newKeyNumber)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::maybeUpdateKeyGeneratorNumber");
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to update key generator value in database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to update key generator value in database without an in-progress transaction") };
+ }
+ if (transaction->mode() == IDBTransactionMode::Readonly) {
+ LOG_ERROR("Attempt to update key generator value in a read-only transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to update key generator value in a read-only transaction") };
+ }
+
+ uint64_t currentValue;
+ auto error = uncheckedGetKeyGeneratorValue(objectStoreID, currentValue);
+ if (!error.isNull())
+ return error;
+
+ if (newKeyNumber <= currentValue)
+ return { };
+
+ uint64_t newKeyInteger(newKeyNumber);
+ if (newKeyInteger <= uint64_t(newKeyNumber))
+ ++newKeyInteger;
+
+ ASSERT(newKeyInteger > uint64_t(newKeyNumber));
+
+ return uncheckedSetKeyGeneratorValue(objectStoreID, newKeyInteger - 1);
+}
+
+IDBError SQLiteIDBBackingStore::openCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo& info, IDBGetResult& result)
+{
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* transaction = m_transactions.get(transactionIdentifier);
+ if (!transaction || !transaction->inProgress()) {
+ LOG_ERROR("Attempt to open a cursor in database without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to open a cursor in database without an in-progress transaction") };
+ }
+
+ auto* cursor = transaction->maybeOpenCursor(info);
+ if (!cursor) {
+ LOG_ERROR("Unable to open cursor");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to open cursor") };
+ }
+
+ m_cursors.set(cursor->identifier(), cursor);
+
+ cursor->currentData(result);
+ return { };
+}
+
+IDBError SQLiteIDBBackingStore::iterateCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData& data, IDBGetResult& result)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::iterateCursor");
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* cursor = m_cursors.get(cursorIdentifier);
+ if (!cursor) {
+ LOG_ERROR("Attempt to iterate a cursor that doesn't exist");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to iterate a cursor that doesn't exist") };
+ }
+
+ ASSERT_UNUSED(transactionIdentifier, cursor->transaction()->transactionIdentifier() == transactionIdentifier);
+
+ if (!cursor->transaction() || !cursor->transaction()->inProgress()) {
+ LOG_ERROR("Attempt to iterate a cursor without an in-progress transaction");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to iterate a cursor without an in-progress transaction") };
+ }
+
+ auto key = data.keyData;
+ auto primaryKey = data.primaryKeyData;
+ auto count = data.count;
+
+ if (key.isValid()) {
+ if (!cursor->iterate(key, primaryKey)) {
+ LOG_ERROR("Attempt to iterate cursor failed");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to iterate cursor failed") };
+ }
+ } else {
+ ASSERT(!primaryKey.isValid());
+ if (!count)
+ count = 1;
+ if (!cursor->advance(count)) {
+ LOG_ERROR("Attempt to advance cursor failed");
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to advance cursor failed") };
+ }
+ }
+
+ cursor->currentData(result);
+ return { };
+}
+
+bool SQLiteIDBBackingStore::prefetchCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier)
+{
+ LOG(IndexedDB, "SQLiteIDBBackingStore::prefetchCursor");
+
+ ASSERT(m_sqliteDB);
+ ASSERT(m_sqliteDB->isOpen());
+
+ auto* cursor = m_cursors.get(cursorIdentifier);
+ if (!cursor || !cursor->transaction() || !cursor->transaction()->inProgress())
+ return false;
+
+ ASSERT_UNUSED(transactionIdentifier, cursor->transaction()->transactionIdentifier() == transactionIdentifier);
+
+ return cursor->prefetch();
+}
+
+IDBObjectStoreInfo* SQLiteIDBBackingStore::infoForObjectStore(uint64_t objectStoreIdentifier)
+{
+ ASSERT(m_databaseInfo);
+ return m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+}
+
+void SQLiteIDBBackingStore::deleteBackingStore()
+{
+ String dbFilename = fullDatabasePath();
+
+ LOG(IndexedDB, "SQLiteIDBBackingStore::deleteBackingStore deleting file '%s' on disk", dbFilename.utf8().data());
+
+ Vector<String> blobFiles;
+ {
+ bool errored = true;
+
+ if (m_sqliteDB) {
+ SQLiteStatement sql(*m_sqliteDB, ASCIILiteral("SELECT fileName FROM BlobFiles;"));
+ if (sql.prepare() == SQLITE_OK) {
+ int result = sql.step();
+ while (result == SQLITE_ROW) {
+ blobFiles.append(sql.getColumnText(0));
+ result = sql.step();
+ }
+
+ if (result == SQLITE_DONE)
+ errored = false;
+ }
+ }
+
+ if (errored)
+ LOG_ERROR("Error getting all blob filenames to be deleted");
+ }
+
+ String databaseDirectory = fullDatabaseDirectory();
+ for (auto& file : blobFiles) {
+ String fullPath = pathByAppendingComponent(databaseDirectory, file);
+ if (!deleteFile(fullPath))
+ LOG_ERROR("Error deleting blob file %s", fullPath.utf8().data());
+ }
+
+ if (m_sqliteDB)
+ closeSQLiteDB();
+
+ SQLiteFileSystem::deleteDatabaseFile(dbFilename);
+ SQLiteFileSystem::deleteEmptyDatabaseDirectory(fullDatabaseDirectory());
+ SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_absoluteDatabaseDirectory);
+}
+
+void SQLiteIDBBackingStore::unregisterCursor(SQLiteIDBCursor& cursor)
+{
+ ASSERT(m_cursors.contains(cursor.identifier()));
+ m_cursors.remove(cursor.identifier());
+}
+
+SQLiteStatement* SQLiteIDBBackingStore::cachedStatement(SQLiteIDBBackingStore::SQL sql, const char* statement)
+{
+ if (sql >= SQL::Count) {
+ LOG_ERROR("Invalid SQL statement ID passed to cachedStatement()");
+ return nullptr;
+ }
+
+ if (m_cachedStatements[static_cast<size_t>(sql)]) {
+ if (m_cachedStatements[static_cast<size_t>(sql)]->reset() == SQLITE_OK)
+ return m_cachedStatements[static_cast<size_t>(sql)].get();
+ m_cachedStatements[static_cast<size_t>(sql)] = nullptr;
+ }
+
+ if (m_sqliteDB) {
+ m_cachedStatements[static_cast<size_t>(sql)] = std::make_unique<SQLiteStatement>(*m_sqliteDB, statement);
+ if (m_cachedStatements[static_cast<size_t>(sql)]->prepare() != SQLITE_OK)
+ m_cachedStatements[static_cast<size_t>(sql)] = nullptr;
+ }
+
+ return m_cachedStatements[static_cast<size_t>(sql)].get();
+}
+
+void SQLiteIDBBackingStore::closeSQLiteDB()
+{
+ for (size_t i = 0; i < static_cast<int>(SQL::Count); ++i)
+ m_cachedStatements[i] = nullptr;
+
+ if (m_sqliteDB)
+ m_sqliteDB->close();
+
+ m_sqliteDB = nullptr;
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.h b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.h
new file mode 100644
index 000000000..257efe8c1
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBBackingStore.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBBackingStore.h"
+#include "IDBDatabaseIdentifier.h"
+#include "IDBDatabaseInfo.h"
+#include "IDBResourceIdentifier.h"
+#include "SQLiteIDBTransaction.h"
+#include <JavaScriptCore/Strong.h>
+#include <wtf/HashMap.h>
+
+namespace WebCore {
+
+class IndexKey;
+class SQLiteDatabase;
+class SQLiteStatement;
+
+namespace IDBServer {
+
+class SQLiteIDBCursor;
+
+class SQLiteIDBBackingStore : public IDBBackingStore {
+public:
+ SQLiteIDBBackingStore(const IDBDatabaseIdentifier&, const String& databaseRootDirectory, IDBBackingStoreTemporaryFileHandler&);
+
+ ~SQLiteIDBBackingStore() final;
+
+ IDBError getOrEstablishDatabaseInfo(IDBDatabaseInfo&) final;
+
+ IDBError beginTransaction(const IDBTransactionInfo&) final;
+ IDBError abortTransaction(const IDBResourceIdentifier& transactionIdentifier) final;
+ IDBError commitTransaction(const IDBResourceIdentifier& transactionIdentifier) final;
+ IDBError createObjectStore(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&) final;
+ IDBError deleteObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) final;
+ IDBError renameObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName) final;
+ IDBError clearObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier) final;
+ IDBError createIndex(const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo&) final;
+ IDBError deleteIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier) final;
+ IDBError renameIndex(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName) final;
+ IDBError keyExistsInObjectStore(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData&, bool& keyExists) final;
+ IDBError deleteRange(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&) final;
+ IDBError addRecord(const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&, const IDBKeyData&, const IDBValue&) final;
+ IDBError getRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&, IDBGetRecordDataType, IDBGetResult& outValue) final;
+ IDBError getAllRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData&, IDBGetAllResult& outValue) final;
+ IDBError getIndexRecord(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, IndexedDB::IndexRecordType, const IDBKeyRangeData&, IDBGetResult& outValue) final;
+ IDBError getCount(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData&, uint64_t& outCount) final;
+ IDBError generateKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t& keyNumber) final;
+ IDBError revertGeneratedKeyNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t keyNumber) final;
+ IDBError maybeUpdateKeyGeneratorNumber(const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, double newKeyNumber) final;
+ IDBError openCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo&, IDBGetResult& outResult) final;
+ IDBError iterateCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData&, IDBGetResult& outResult) final;
+ bool prefetchCursor(const IDBResourceIdentifier&, const IDBResourceIdentifier&) final;
+
+ IDBObjectStoreInfo* infoForObjectStore(uint64_t objectStoreIdentifier) final;
+ void deleteBackingStore() final;
+
+ bool supportsSimultaneousTransactions() final { return false; }
+ bool isEphemeral() final { return false; }
+
+ void unregisterCursor(SQLiteIDBCursor&);
+
+ String fullDatabaseDirectory() const;
+
+ IDBBackingStoreTemporaryFileHandler& temporaryFileHandler() const { return m_temporaryFileHandler; }
+
+ IDBError getBlobRecordsForObjectStoreRecord(int64_t objectStoreRecord, Vector<String>& blobURLs, Vector<String>& blobFilePaths);
+
+ static String databaseNameFromEncodedFilename(const String&);
+
+private:
+ String filenameForDatabaseName() const;
+ String fullDatabasePath() const;
+
+ bool ensureValidRecordsTable();
+ bool ensureValidIndexRecordsTable();
+ bool ensureValidIndexRecordsIndex();
+ bool ensureValidBlobTables();
+ std::unique_ptr<IDBDatabaseInfo> createAndPopulateInitialDatabaseInfo();
+ std::unique_ptr<IDBDatabaseInfo> extractExistingDatabaseInfo();
+
+ IDBError deleteRecord(SQLiteIDBTransaction&, int64_t objectStoreID, const IDBKeyData&);
+ IDBError uncheckedGetKeyGeneratorValue(int64_t objectStoreID, uint64_t& outValue);
+ IDBError uncheckedSetKeyGeneratorValue(int64_t objectStoreID, uint64_t value);
+
+ IDBError updateAllIndexesForAddRecord(const IDBObjectStoreInfo&, const IDBKeyData&, const ThreadSafeDataBuffer& value, int64_t recordID);
+ IDBError updateOneIndexForAddRecord(const IDBIndexInfo&, const IDBKeyData&, const ThreadSafeDataBuffer& value, int64_t recordID);
+ IDBError uncheckedPutIndexKey(const IDBIndexInfo&, const IDBKeyData& keyValue, const IndexKey&, int64_t recordID);
+ IDBError uncheckedPutIndexRecord(int64_t objectStoreID, int64_t indexID, const IDBKeyData& keyValue, const IDBKeyData& indexKey, int64_t recordID);
+ IDBError uncheckedHasIndexRecord(const IDBIndexInfo&, const IDBKeyData&, bool& hasRecord);
+ IDBError uncheckedGetIndexRecordForOneKey(int64_t indexeID, int64_t objectStoreID, IndexedDB::IndexRecordType, const IDBKeyData&, IDBGetResult&);
+
+ IDBError deleteUnusedBlobFileRecords(SQLiteIDBTransaction&);
+
+ IDBError getAllObjectStoreRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData&, IDBGetAllResult& outValue);
+ IDBError getAllIndexRecords(const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData&, IDBGetAllResult& outValue);
+
+ void closeSQLiteDB();
+
+ enum class SQL : size_t {
+ CreateObjectStoreInfo,
+ CreateObjectStoreKeyGenerator,
+ DeleteObjectStoreInfo,
+ DeleteObjectStoreKeyGenerator,
+ DeleteObjectStoreRecords,
+ DeleteObjectStoreIndexInfo,
+ DeleteObjectStoreIndexRecords,
+ DeleteObjectStoreBlobRecords,
+ RenameObjectStore,
+ ClearObjectStoreRecords,
+ ClearObjectStoreIndexRecords,
+ CreateIndexInfo,
+ DeleteIndexInfo,
+ HasIndexRecord,
+ PutIndexRecord,
+ GetIndexRecordForOneKey,
+ DeleteIndexRecords,
+ RenameIndex,
+ KeyExistsInObjectStore,
+ GetUnusedBlobFilenames,
+ DeleteUnusedBlobs,
+ GetObjectStoreRecordID,
+ DeleteBlobRecord,
+ DeleteObjectStoreRecord,
+ DeleteObjectStoreIndexRecord,
+ AddObjectStoreRecord,
+ AddBlobRecord,
+ BlobFilenameForBlobURL,
+ AddBlobFilename,
+ GetBlobURL,
+ GetKeyGeneratorValue,
+ SetKeyGeneratorValue,
+ GetAllKeyRecordsLowerOpenUpperOpen,
+ GetAllKeyRecordsLowerOpenUpperClosed,
+ GetAllKeyRecordsLowerClosedUpperOpen,
+ GetAllKeyRecordsLowerClosedUpperClosed,
+ GetValueRecordsLowerOpenUpperOpen,
+ GetValueRecordsLowerOpenUpperClosed,
+ GetValueRecordsLowerClosedUpperOpen,
+ GetValueRecordsLowerClosedUpperClosed,
+ GetKeyRecordsLowerOpenUpperOpen,
+ GetKeyRecordsLowerOpenUpperClosed,
+ GetKeyRecordsLowerClosedUpperOpen,
+ GetKeyRecordsLowerClosedUpperClosed,
+ Count
+ };
+
+ SQLiteStatement* cachedStatement(SQL, const char*);
+ SQLiteStatement* cachedStatementForGetAllObjectStoreRecords(const IDBGetAllRecordsData&);
+
+ std::unique_ptr<SQLiteStatement> m_cachedStatements[static_cast<int>(SQL::Count)];
+
+ JSC::VM& vm();
+ JSC::JSGlobalObject& globalObject();
+ void initializeVM();
+
+ IDBDatabaseIdentifier m_identifier;
+ std::unique_ptr<IDBDatabaseInfo> m_databaseInfo;
+ std::unique_ptr<IDBDatabaseInfo> m_originalDatabaseInfoBeforeVersionChange;
+
+ std::unique_ptr<SQLiteDatabase> m_sqliteDB;
+
+ HashMap<IDBResourceIdentifier, std::unique_ptr<SQLiteIDBTransaction>> m_transactions;
+ HashMap<IDBResourceIdentifier, SQLiteIDBCursor*> m_cursors;
+
+ String m_absoluteDatabaseDirectory;
+
+ RefPtr<JSC::VM> m_vm;
+ JSC::Strong<JSC::JSGlobalObject> m_globalObject;
+
+ IDBBackingStoreTemporaryFileHandler& m_temporaryFileHandler;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp
new file mode 100644
index 000000000..0d3e82928
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2014, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SQLiteIDBCursor.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+#include "IDBGetResult.h"
+#include "IDBSerialization.h"
+#include "Logging.h"
+#include "SQLiteIDBBackingStore.h"
+#include "SQLiteIDBTransaction.h"
+#include "SQLiteStatement.h"
+#include "SQLiteTransaction.h"
+#include <sqlite3.h>
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+namespace IDBServer {
+
+static const size_t prefetchLimit = 8;
+
+std::unique_ptr<SQLiteIDBCursor> SQLiteIDBCursor::maybeCreate(SQLiteIDBTransaction& transaction, const IDBCursorInfo& info)
+{
+ auto cursor = std::make_unique<SQLiteIDBCursor>(transaction, info);
+
+ if (!cursor->establishStatement())
+ return nullptr;
+
+ if (!cursor->advance(1))
+ return nullptr;
+
+ return cursor;
+}
+
+std::unique_ptr<SQLiteIDBCursor> SQLiteIDBCursor::maybeCreateBackingStoreCursor(SQLiteIDBTransaction& transaction, const uint64_t objectStoreID, const uint64_t indexID, const IDBKeyRangeData& range)
+{
+ auto cursor = std::make_unique<SQLiteIDBCursor>(transaction, objectStoreID, indexID, range);
+
+ if (!cursor->establishStatement())
+ return nullptr;
+
+ if (!cursor->advance(1))
+ return nullptr;
+
+ return cursor;
+}
+
+SQLiteIDBCursor::SQLiteIDBCursor(SQLiteIDBTransaction& transaction, const IDBCursorInfo& info)
+ : m_transaction(&transaction)
+ , m_cursorIdentifier(info.identifier())
+ , m_objectStoreID(info.objectStoreIdentifier())
+ , m_indexID(info.cursorSource() == IndexedDB::CursorSource::Index ? info.sourceIdentifier() : IDBIndexInfo::InvalidId)
+ , m_cursorDirection(info.cursorDirection())
+ , m_cursorType(info.cursorType())
+ , m_keyRange(info.range())
+{
+ ASSERT(m_objectStoreID);
+}
+
+SQLiteIDBCursor::SQLiteIDBCursor(SQLiteIDBTransaction& transaction, const uint64_t objectStoreID, const uint64_t indexID, const IDBKeyRangeData& range)
+ : m_transaction(&transaction)
+ , m_cursorIdentifier(transaction.transactionIdentifier())
+ , m_objectStoreID(objectStoreID)
+ , m_indexID(indexID ? indexID : IDBIndexInfo::InvalidId)
+ , m_cursorDirection(IndexedDB::CursorDirection::Next)
+ , m_cursorType(IndexedDB::CursorType::KeyAndValue)
+ , m_keyRange(range)
+ , m_backingStoreCursor(true)
+{
+ ASSERT(m_objectStoreID);
+}
+
+SQLiteIDBCursor::~SQLiteIDBCursor()
+{
+ if (m_backingStoreCursor)
+ m_transaction->closeCursor(*this);
+}
+
+void SQLiteIDBCursor::currentData(IDBGetResult& result)
+{
+ ASSERT(!m_fetchedRecords.isEmpty());
+
+ auto& currentRecord = m_fetchedRecords.first();
+ if (currentRecord.completed) {
+ ASSERT(!currentRecord.errored);
+ result = { };
+ return;
+ }
+
+ result = { currentRecord.record.key, currentRecord.record.primaryKey, currentRecord.record.value ? *currentRecord.record.value : IDBValue() };
+}
+
+static String buildIndexStatement(const IDBKeyRangeData& keyRange, IndexedDB::CursorDirection cursorDirection)
+{
+ StringBuilder builder;
+
+ builder.appendLiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND objectStoreID = ? AND key ");
+ if (!keyRange.lowerKey.isNull() && !keyRange.lowerOpen)
+ builder.appendLiteral(">=");
+ else
+ builder.append('>');
+
+ builder.appendLiteral(" CAST(? AS TEXT) AND key ");
+ if (!keyRange.upperKey.isNull() && !keyRange.upperOpen)
+ builder.appendLiteral("<=");
+ else
+ builder.append('<');
+
+ builder.appendLiteral(" CAST(? AS TEXT) ORDER BY key");
+ if (cursorDirection == IndexedDB::CursorDirection::Prev || cursorDirection == IndexedDB::CursorDirection::Prevunique)
+ builder.appendLiteral(" DESC");
+
+ builder.appendLiteral(", value");
+ if (cursorDirection == IndexedDB::CursorDirection::Prev)
+ builder.appendLiteral(" DESC");
+
+ builder.append(';');
+
+ return builder.toString();
+}
+
+static String buildObjectStoreStatement(const IDBKeyRangeData& keyRange, IndexedDB::CursorDirection cursorDirection)
+{
+ StringBuilder builder;
+
+ builder.appendLiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key ");
+
+ if (!keyRange.lowerKey.isNull() && !keyRange.lowerOpen)
+ builder.appendLiteral(">=");
+ else
+ builder.append('>');
+
+ builder.appendLiteral(" CAST(? AS TEXT) AND key ");
+
+ if (!keyRange.upperKey.isNull() && !keyRange.upperOpen)
+ builder.appendLiteral("<=");
+ else
+ builder.append('<');
+
+ builder.appendLiteral(" CAST(? AS TEXT) ORDER BY key");
+
+ if (cursorDirection == IndexedDB::CursorDirection::Prev || cursorDirection == IndexedDB::CursorDirection::Prevunique)
+ builder.appendLiteral(" DESC");
+
+ builder.append(';');
+
+ return builder.toString();
+}
+
+bool SQLiteIDBCursor::establishStatement()
+{
+ ASSERT(!m_statement);
+ String sql;
+
+ if (m_indexID != IDBIndexInfo::InvalidId) {
+ sql = buildIndexStatement(m_keyRange, m_cursorDirection);
+ m_boundID = m_indexID;
+ } else {
+ sql = buildObjectStoreStatement(m_keyRange, m_cursorDirection);
+ m_boundID = m_objectStoreID;
+ }
+
+ 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)
+{
+ LOG(IndexedDB, "Creating cursor with SQL query: \"%s\"", sql.utf8().data());
+
+ 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() != SQLITE_OK) {
+ LOG_ERROR("Could not create cursor statement (prepare/id) - '%s'", m_transaction->sqliteTransaction()->database().lastErrorMsg());
+ return false;
+ }
+
+ return bindArguments();
+}
+
+void SQLiteIDBCursor::objectStoreRecordsChanged()
+{
+ if (m_statementNeedsReset)
+ return;
+
+ // 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.
+ // We also need to throw away any fetched records as they may no longer be valid.
+
+ m_statementNeedsReset = true;
+ ASSERT(!m_fetchedRecords.isEmpty());
+
+ if (m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::Nextunique) {
+ m_currentLowerKey = m_fetchedRecords.first().record.key;
+ if (!m_keyRange.lowerOpen) {
+ m_keyRange.lowerOpen = true;
+ m_keyRange.lowerKey = m_currentLowerKey;
+ m_statement = nullptr;
+ }
+ } else {
+ m_currentUpperKey = m_fetchedRecords.first().record.key;
+ if (!m_keyRange.upperOpen) {
+ m_keyRange.upperOpen = true;
+ m_keyRange.upperKey = m_currentUpperKey;
+ m_statement = nullptr;
+ }
+ }
+
+ m_currentKeyForUniqueness = m_fetchedRecords.first().record.key;
+
+ m_fetchedRecords.clear();
+}
+
+void SQLiteIDBCursor::resetAndRebindStatement()
+{
+ ASSERT(!m_currentLowerKey.isNull());
+ ASSERT(!m_currentUpperKey.isNull());
+ ASSERT(m_transaction->sqliteTransaction());
+ ASSERT(m_statementNeedsReset);
+
+ m_statementNeedsReset = false;
+
+ if (!m_statement && !establishStatement()) {
+ LOG_ERROR("Unable to establish new statement for cursor iteration");
+ return;
+ }
+
+ if (m_statement->reset() != SQLITE_OK) {
+ LOG_ERROR("Could not reset cursor statement to respond to object store changes");
+ return;
+ }
+
+ bindArguments();
+}
+
+bool SQLiteIDBCursor::bindArguments()
+{
+ LOG(IndexedDB, "Cursor is binding lower key '%s' and upper key '%s'", m_currentLowerKey.loggingString().utf8().data(), m_currentUpperKey.loggingString().utf8().data());
+
+ int currentBindArgument = 1;
+
+ if (m_statement->bindInt64(currentBindArgument++, m_boundID) != SQLITE_OK) {
+ LOG_ERROR("Could not bind id argument (bound ID)");
+ return false;
+ }
+
+ if (m_indexID != IDBIndexInfo::InvalidId && m_statement->bindInt64(currentBindArgument++, m_objectStoreID) != SQLITE_OK) {
+ LOG_ERROR("Could not bind object store id argument for an index cursor");
+ return false;
+ }
+
+ RefPtr<SharedBuffer> buffer = serializeIDBKeyData(m_currentLowerKey);
+ if (m_statement->bindBlob(currentBindArgument++, buffer->data(), buffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Could not create cursor statement (lower key)");
+ return false;
+ }
+
+ buffer = serializeIDBKeyData(m_currentUpperKey);
+ if (m_statement->bindBlob(currentBindArgument++, buffer->data(), buffer->size()) != SQLITE_OK) {
+ LOG_ERROR("Could not create cursor statement (upper key)");
+ return false;
+ }
+
+ return true;
+}
+
+bool SQLiteIDBCursor::prefetch()
+{
+ LOG(IndexedDB, "SQLiteIDBCursor::prefetch() - Cursor already has %zu fetched records", m_fetchedRecords.size());
+
+ if (m_fetchedRecords.isEmpty() || m_fetchedRecords.size() >= prefetchLimit || m_fetchedRecords.last().isTerminalRecord())
+ return false;
+
+ m_currentKeyForUniqueness = m_fetchedRecords.last().record.key;
+ fetch();
+
+ return m_fetchedRecords.size() < prefetchLimit;
+}
+
+bool SQLiteIDBCursor::advance(uint64_t count)
+{
+ LOG(IndexedDB, "SQLiteIDBCursor::advance() - Count %" PRIu64 ", %zu fetched records", count, m_fetchedRecords.size());
+ ASSERT(count);
+
+ if (!m_fetchedRecords.isEmpty() && m_fetchedRecords.first().isTerminalRecord()) {
+ LOG_ERROR("Attempt to advance a completed cursor");
+ return false;
+ }
+
+ if (!m_fetchedRecords.isEmpty())
+ m_currentKeyForUniqueness = m_fetchedRecords.last().record.key;
+
+ // Drop already-fetched records up to `count` to see if we've already fetched the record we're looking for.
+ bool hadCurrentRecord = !m_fetchedRecords.isEmpty();
+ for (; count && !m_fetchedRecords.isEmpty(); --count) {
+ if (m_fetchedRecords.first().isTerminalRecord())
+ break;
+
+ m_fetchedRecords.removeFirst();
+ }
+
+ // If we still have any records left, the first record is our new current record.
+ if (!m_fetchedRecords.isEmpty())
+ return true;
+
+ ASSERT(m_fetchedRecords.isEmpty());
+
+ // If we started out with a current record, we burnt a count on removing it.
+ // Replace that count now.
+ if (hadCurrentRecord)
+ ++count;
+
+ for (; count; --count) {
+ if (!m_fetchedRecords.isEmpty()) {
+ ASSERT(m_fetchedRecords.size() == 1);
+ m_currentKeyForUniqueness = m_fetchedRecords.first().record.key;
+ m_fetchedRecords.removeFirst();
+ }
+
+ if (!fetch())
+ return false;
+
+ ASSERT(!m_fetchedRecords.isEmpty());
+ ASSERT(!m_fetchedRecords.first().errored);
+ if (m_fetchedRecords.first().completed)
+ break;
+ }
+
+ return true;
+}
+
+bool SQLiteIDBCursor::fetch()
+{
+ ASSERT(m_fetchedRecords.isEmpty() || !m_fetchedRecords.last().isTerminalRecord());
+
+ m_fetchedRecords.append({ });
+
+ bool isUnique = m_cursorDirection == IndexedDB::CursorDirection::Nextunique || m_cursorDirection == IndexedDB::CursorDirection::Prevunique;
+ if (!isUnique)
+ return fetchNextRecord(m_fetchedRecords.last());
+
+ while (!m_fetchedRecords.last().completed) {
+ if (!fetchNextRecord(m_fetchedRecords.last()))
+ return false;
+
+ // If the new current key is different from the old current key, we're done.
+ if (m_currentKeyForUniqueness.compare(m_fetchedRecords.last().record.key))
+ return true;
+ }
+
+ return false;
+}
+
+bool SQLiteIDBCursor::fetchNextRecord(SQLiteCursorRecord& record)
+{
+ if (m_statementNeedsReset)
+ resetAndRebindStatement();
+
+ FetchResult result;
+ do {
+ result = internalFetchNextRecord(record);
+ } while (result == FetchResult::ShouldFetchAgain);
+
+ return result == FetchResult::Success;
+}
+
+void SQLiteIDBCursor::markAsErrored(SQLiteCursorRecord& record)
+{
+ record.record = { };
+ record.completed = true;
+ record.errored = true;
+ record.rowID = 0;
+}
+
+SQLiteIDBCursor::FetchResult SQLiteIDBCursor::internalFetchNextRecord(SQLiteCursorRecord& record)
+{
+ ASSERT(m_transaction->sqliteTransaction());
+ ASSERT(m_statement);
+ ASSERT(!m_fetchedRecords.isEmpty());
+ ASSERT(!m_fetchedRecords.last().isTerminalRecord());
+
+ record.record.value = nullptr;
+
+ int result = m_statement->step();
+ if (result == SQLITE_DONE) {
+ // When a cursor reaches its end, that is indicated by having undefined keys/values
+ record = { };
+ record.completed = true;
+
+ return FetchResult::Success;
+ }
+
+ if (result != SQLITE_ROW) {
+ LOG_ERROR("Error advancing cursor - (%i) %s", result, m_transaction->sqliteTransaction()->database().lastErrorMsg());
+ markAsErrored(record);
+ return FetchResult::Failure;
+ }
+
+ record.rowID = m_statement->getColumnInt64(0);
+ ASSERT(record.rowID);
+
+ Vector<uint8_t> keyData;
+ m_statement->getColumnBlobAsVector(1, keyData);
+
+ if (!deserializeIDBKeyData(keyData.data(), keyData.size(), record.record.key)) {
+ LOG_ERROR("Unable to deserialize key data from database while advancing cursor");
+ markAsErrored(record);
+ return FetchResult::Failure;
+ }
+
+ m_statement->getColumnBlobAsVector(2, keyData);
+
+ // The primaryKey of an ObjectStore cursor is the same as its key.
+ if (m_indexID == IDBIndexInfo::InvalidId) {
+ record.record.primaryKey = record.record.key;
+
+ Vector<String> blobURLs, blobFilePaths;
+ auto error = m_transaction->backingStore().getBlobRecordsForObjectStoreRecord(record.rowID, blobURLs, blobFilePaths);
+ if (!error.isNull()) {
+ LOG_ERROR("Unable to fetch blob records from database while advancing cursor");
+ markAsErrored(record);
+ return FetchResult::Failure;
+ }
+
+ if (m_cursorType == IndexedDB::CursorType::KeyAndValue)
+ record.record.value = std::make_unique<IDBValue>(ThreadSafeDataBuffer::adoptVector(keyData), blobURLs, blobFilePaths);
+ } else {
+ if (!deserializeIDBKeyData(keyData.data(), keyData.size(), record.record.primaryKey)) {
+ LOG_ERROR("Unable to deserialize value data from database while advancing index cursor");
+ markAsErrored(record);
+ return FetchResult::Failure;
+ }
+
+ SQLiteStatement objectStoreStatement(m_statement->database(), "SELECT value FROM Records WHERE key = CAST(? AS TEXT) and objectStoreID = ?;");
+
+ if (objectStoreStatement.prepare() != SQLITE_OK
+ || objectStoreStatement.bindBlob(1, keyData.data(), keyData.size()) != SQLITE_OK
+ || objectStoreStatement.bindInt64(2, m_objectStoreID) != SQLITE_OK) {
+ LOG_ERROR("Could not create index cursor statement into object store records (%i) '%s'", m_statement->database().lastError(), m_statement->database().lastErrorMsg());
+ markAsErrored(record);
+ return FetchResult::Failure;
+ }
+
+ int result = objectStoreStatement.step();
+
+ if (result == SQLITE_ROW) {
+ objectStoreStatement.getColumnBlobAsVector(0, keyData);
+ record.record.value = std::make_unique<IDBValue>(ThreadSafeDataBuffer::adoptVector(keyData));
+ } else if (result == SQLITE_DONE) {
+ // This indicates that the record we're trying to retrieve has been removed from the object store.
+ // Skip over it.
+ return FetchResult::ShouldFetchAgain;
+ } else {
+ LOG_ERROR("Could not step index cursor statement into object store records (%i) '%s'", m_statement->database().lastError(), m_statement->database().lastErrorMsg());
+ markAsErrored(record);
+ return FetchResult::Failure;
+
+ }
+ }
+
+ return FetchResult::Success;
+}
+
+bool SQLiteIDBCursor::iterate(const IDBKeyData& targetKey, const IDBKeyData& targetPrimaryKey)
+{
+ ASSERT(m_transaction->sqliteTransaction());
+ ASSERT(m_statement);
+
+ bool result = advance(1);
+ ASSERT(!m_fetchedRecords.isEmpty());
+
+ // Iterating with no key is equivalent to advancing 1 step.
+ if (targetKey.isNull() || !result)
+ return result;
+
+ while (!m_fetchedRecords.first().isTerminalRecord()) {
+ if (!result)
+ return false;
+
+ // Search for the next key >= the target if the cursor is a Next cursor, or the next key <= if the cursor is a Previous cursor.
+ if (m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::Nextunique) {
+ if (m_fetchedRecords.first().record.key.compare(targetKey) >= 0)
+ break;
+ } else if (m_fetchedRecords.first().record.key.compare(targetKey) <= 0)
+ break;
+
+ result = advance(1);
+ }
+
+ if (targetPrimaryKey.isValid()) {
+ while (!m_fetchedRecords.first().isTerminalRecord() && !m_fetchedRecords.first().record.key.compare(targetKey)) {
+ if (!result)
+ return false;
+
+ // Search for the next primary key >= the primary target if the cursor is a Next cursor, or the next key <= if the cursor is a Previous cursor.
+ if (m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::Nextunique) {
+ if (m_fetchedRecords.first().record.primaryKey.compare(targetPrimaryKey) >= 0)
+ break;
+ } else if (m_fetchedRecords.first().record.primaryKey.compare(targetPrimaryKey) <= 0)
+ break;
+
+ result = advance(1);
+ }
+ }
+
+ return result;
+}
+
+const IDBKeyData& SQLiteIDBCursor::currentKey() const
+{
+ ASSERT(!m_fetchedRecords.isEmpty());
+ return m_fetchedRecords.first().record.key;
+}
+
+const IDBKeyData& SQLiteIDBCursor::currentPrimaryKey() const
+{
+ ASSERT(!m_fetchedRecords.isEmpty());
+ return m_fetchedRecords.first().record.primaryKey;
+}
+
+IDBValue* SQLiteIDBCursor::currentValue() const
+{
+ ASSERT(!m_fetchedRecords.isEmpty());
+ return m_fetchedRecords.first().record.value.get();
+}
+
+bool SQLiteIDBCursor::didComplete() const
+{
+ ASSERT(!m_fetchedRecords.isEmpty());
+ return m_fetchedRecords.first().completed;
+}
+
+bool SQLiteIDBCursor::didError() const
+{
+ ASSERT(!m_fetchedRecords.isEmpty());
+ return m_fetchedRecords.first().errored;
+}
+
+int64_t SQLiteIDBCursor::currentRecordRowID() const
+{
+ ASSERT(!m_fetchedRecords.isEmpty());
+ return m_fetchedRecords.first().rowID;
+}
+
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.h b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.h
new file mode 100644
index 000000000..4a673003b
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2014, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorRecord.h"
+#include "IDBIndexInfo.h"
+#include "IDBKeyData.h"
+#include "IDBKeyRangeData.h"
+#include "IDBResourceIdentifier.h"
+#include "IDBValue.h"
+#include "SQLiteStatement.h"
+#include <wtf/Deque.h>
+#include <wtf/Noncopyable.h>
+
+namespace WebCore {
+
+class IDBCursorInfo;
+class IDBGetResult;
+
+namespace IDBServer {
+
+class SQLiteIDBTransaction;
+
+class SQLiteIDBCursor {
+ WTF_MAKE_NONCOPYABLE(SQLiteIDBCursor);
+public:
+ static std::unique_ptr<SQLiteIDBCursor> maybeCreate(SQLiteIDBTransaction&, const IDBCursorInfo&);
+ static std::unique_ptr<SQLiteIDBCursor> maybeCreateBackingStoreCursor(SQLiteIDBTransaction&, const uint64_t objectStoreIdentifier, const uint64_t indexIdentifier, const IDBKeyRangeData&);
+
+ SQLiteIDBCursor(SQLiteIDBTransaction&, const IDBCursorInfo&);
+ SQLiteIDBCursor(SQLiteIDBTransaction&, uint64_t objectStoreID, uint64_t indexID, const IDBKeyRangeData&);
+
+ ~SQLiteIDBCursor();
+
+ const IDBResourceIdentifier& identifier() const { return m_cursorIdentifier; }
+ SQLiteIDBTransaction* transaction() const { return m_transaction; }
+
+ int64_t objectStoreID() const { return m_objectStoreID; }
+ int64_t currentRecordRowID() const;
+
+ const IDBKeyData& currentKey() const;
+ const IDBKeyData& currentPrimaryKey() const;
+ IDBValue* currentValue() const;
+
+ bool advance(uint64_t count);
+ bool iterate(const IDBKeyData& targetKey, const IDBKeyData& targetPrimaryKey);
+ bool prefetch();
+
+ bool didComplete() const;
+ bool didError() const;
+
+ void objectStoreRecordsChanged();
+
+ void currentData(IDBGetResult&);
+
+private:
+ bool establishStatement();
+ bool createSQLiteStatement(const String& sql);
+ bool bindArguments();
+
+ void resetAndRebindStatement();
+
+ enum class FetchResult {
+ Success,
+ Failure,
+ ShouldFetchAgain
+ };
+
+ bool fetch();
+
+ struct SQLiteCursorRecord {
+ IDBCursorRecord record;
+ bool completed { false };
+ bool errored { false };
+ int64_t rowID { 0 };
+ bool isTerminalRecord() const { return completed || errored; }
+ };
+ bool fetchNextRecord(SQLiteCursorRecord&);
+ FetchResult internalFetchNextRecord(SQLiteCursorRecord&);
+
+ void markAsErrored(SQLiteCursorRecord&);
+
+ SQLiteIDBTransaction* m_transaction;
+ IDBResourceIdentifier m_cursorIdentifier;
+ int64_t m_objectStoreID;
+ int64_t m_indexID { IDBIndexInfo::InvalidId };
+ IndexedDB::CursorDirection m_cursorDirection { IndexedDB::CursorDirection::Next };
+ IndexedDB::CursorType m_cursorType;
+ IDBKeyRangeData m_keyRange;
+
+ IDBKeyData m_currentLowerKey;
+ IDBKeyData m_currentUpperKey;
+
+ Deque<SQLiteCursorRecord> m_fetchedRecords;
+ IDBKeyData m_currentKeyForUniqueness;
+
+ std::unique_ptr<SQLiteStatement> m_statement;
+ bool m_statementNeedsReset { true };
+ int64_t m_boundID { 0 };
+
+ bool m_backingStoreCursor { false };
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.cpp b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.cpp
new file mode 100644
index 000000000..fc1dd17dd
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2013, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "config.h"
+#include "SQLiteIDBTransaction.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "FileSystem.h"
+#include "IDBCursorInfo.h"
+#include "IndexedDB.h"
+#include "Logging.h"
+#include "SQLiteIDBBackingStore.h"
+#include "SQLiteIDBCursor.h"
+#include "SQLiteTransaction.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+SQLiteIDBTransaction::SQLiteIDBTransaction(SQLiteIDBBackingStore& backingStore, const IDBTransactionInfo& info)
+ : m_info(info)
+ , m_backingStore(backingStore)
+{
+}
+
+SQLiteIDBTransaction::~SQLiteIDBTransaction()
+{
+ if (inProgress())
+ m_sqliteTransaction->rollback();
+
+ // Explicitly clear cursors, as that also unregisters them from the backing store.
+ clearCursors();
+}
+
+
+IDBError SQLiteIDBTransaction::begin(SQLiteDatabase& database)
+{
+ ASSERT(!m_sqliteTransaction);
+
+ m_sqliteTransaction = std::make_unique<SQLiteTransaction>(database, m_info.mode() == IDBTransactionMode::Readonly);
+ m_sqliteTransaction->begin();
+
+ if (m_sqliteTransaction->inProgress())
+ return { };
+
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Could not start SQLite transaction in database backend") };
+}
+
+IDBError SQLiteIDBTransaction::commit()
+{
+ LOG(IndexedDB, "SQLiteIDBTransaction::commit");
+ if (!m_sqliteTransaction || !m_sqliteTransaction->inProgress())
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No SQLite transaction in progress to commit") };
+
+ m_sqliteTransaction->commit();
+
+ if (m_sqliteTransaction->inProgress())
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to commit SQLite transaction in database backend") };
+
+ deleteBlobFilesIfNecessary();
+ moveBlobFilesIfNecessary();
+
+ reset();
+ return { };
+}
+
+void SQLiteIDBTransaction::moveBlobFilesIfNecessary()
+{
+ String databaseDirectory = m_backingStore.fullDatabaseDirectory();
+ for (auto& entry : m_blobTemporaryAndStoredFilenames) {
+ m_backingStore.temporaryFileHandler().prepareForAccessToTemporaryFile(entry.first);
+
+ if (!hardLinkOrCopyFile(entry.first, pathByAppendingComponent(databaseDirectory, entry.second)))
+ LOG_ERROR("Failed to link/copy temporary blob file '%s' to location '%s'", entry.first.utf8().data(), pathByAppendingComponent(databaseDirectory, entry.second).utf8().data());
+
+ m_backingStore.temporaryFileHandler().accessToTemporaryFileComplete(entry.first);
+ }
+
+ m_blobTemporaryAndStoredFilenames.clear();
+}
+
+void SQLiteIDBTransaction::deleteBlobFilesIfNecessary()
+{
+ if (m_blobRemovedFilenames.isEmpty())
+ return;
+
+ String databaseDirectory = m_backingStore.fullDatabaseDirectory();
+ for (auto& entry : m_blobRemovedFilenames) {
+ String fullPath = pathByAppendingComponent(databaseDirectory, entry);
+ m_backingStore.temporaryFileHandler().prepareForAccessToTemporaryFile(fullPath);
+ m_backingStore.temporaryFileHandler().accessToTemporaryFileComplete(fullPath);
+ }
+
+ m_blobRemovedFilenames.clear();
+}
+
+IDBError SQLiteIDBTransaction::abort()
+{
+ for (auto& entry : m_blobTemporaryAndStoredFilenames) {
+ m_backingStore.temporaryFileHandler().prepareForAccessToTemporaryFile(entry.first);
+ m_backingStore.temporaryFileHandler().accessToTemporaryFileComplete(entry.first);
+ }
+
+ m_blobTemporaryAndStoredFilenames.clear();
+
+ if (!m_sqliteTransaction || !m_sqliteTransaction->inProgress())
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("No SQLite transaction in progress to abort") };
+
+ m_sqliteTransaction->rollback();
+
+ if (m_sqliteTransaction->inProgress())
+ return { IDBDatabaseException::UnknownError, ASCIILiteral("Unable to abort SQLite transaction in database backend") };
+
+ reset();
+ return { };
+}
+
+void SQLiteIDBTransaction::reset()
+{
+ m_sqliteTransaction = nullptr;
+ clearCursors();
+ ASSERT(m_blobTemporaryAndStoredFilenames.isEmpty());
+}
+
+std::unique_ptr<SQLiteIDBCursor> SQLiteIDBTransaction::maybeOpenBackingStoreCursor(uint64_t objectStoreID, uint64_t indexID, const IDBKeyRangeData& range)
+{
+ ASSERT(m_sqliteTransaction);
+ ASSERT(m_sqliteTransaction->inProgress());
+
+ auto cursor = SQLiteIDBCursor::maybeCreateBackingStoreCursor(*this, objectStoreID, indexID, range);
+
+ if (cursor)
+ m_backingStoreCursors.add(cursor.get());
+
+ return cursor;
+}
+
+SQLiteIDBCursor* SQLiteIDBTransaction::maybeOpenCursor(const IDBCursorInfo& info)
+{
+ ASSERT(m_sqliteTransaction);
+ if (!m_sqliteTransaction->inProgress())
+ return nullptr;
+
+ auto addResult = m_cursors.add(info.identifier(), SQLiteIDBCursor::maybeCreate(*this, info));
+
+ ASSERT(addResult.isNewEntry);
+
+ // It is possible the cursor failed to create and we just stored a null value.
+ if (!addResult.iterator->value) {
+ m_cursors.remove(addResult.iterator);
+ return nullptr;
+ }
+
+ return addResult.iterator->value.get();
+}
+
+void SQLiteIDBTransaction::closeCursor(SQLiteIDBCursor& cursor)
+{
+ auto backingStoreTake = m_backingStoreCursors.take(&cursor);
+ if (backingStoreTake) {
+ ASSERT(!m_cursors.contains(cursor.identifier()));
+ return;
+ }
+
+ ASSERT(m_cursors.contains(cursor.identifier()));
+
+ m_backingStore.unregisterCursor(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();
+ }
+
+ for (auto* cursor : m_backingStoreCursors) {
+ if (cursor->objectStoreID() == objectStoreID)
+ cursor->objectStoreRecordsChanged();
+ }
+}
+
+void SQLiteIDBTransaction::clearCursors()
+{
+ for (auto& cursor : m_cursors.values())
+ m_backingStore.unregisterCursor(*cursor);
+
+ m_cursors.clear();
+}
+
+bool SQLiteIDBTransaction::inProgress() const
+{
+ return m_sqliteTransaction && m_sqliteTransaction->inProgress();
+}
+
+void SQLiteIDBTransaction::addBlobFile(const String& temporaryPath, const String& storedFilename)
+{
+ m_blobTemporaryAndStoredFilenames.append({ temporaryPath, storedFilename });
+}
+
+void SQLiteIDBTransaction::addRemovedBlobFile(const String& removedFilename)
+{
+ ASSERT(!m_blobRemovedFilenames.contains(removedFilename));
+ m_blobRemovedFilenames.add(removedFilename);
+}
+
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.h b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.h
new file mode 100644
index 000000000..a2753d6c7
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/SQLiteIDBTransaction.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBError.h"
+#include "IDBResourceIdentifier.h"
+#include "IDBTransactionInfo.h"
+#include "IndexedDB.h"
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+#include <wtf/Noncopyable.h>
+
+namespace WebCore {
+
+class IDBCursorInfo;
+class SQLiteDatabase;
+class SQLiteTransaction;
+struct IDBKeyRangeData;
+
+namespace IDBServer {
+
+class SQLiteIDBBackingStore;
+class SQLiteIDBCursor;
+
+class SQLiteIDBTransaction {
+ WTF_MAKE_NONCOPYABLE(SQLiteIDBTransaction);
+public:
+ SQLiteIDBTransaction(SQLiteIDBBackingStore&, const IDBTransactionInfo&);
+ ~SQLiteIDBTransaction();
+
+ const IDBResourceIdentifier& transactionIdentifier() const { return m_info.identifier(); }
+
+ IDBError begin(SQLiteDatabase&);
+ IDBError commit();
+ IDBError abort();
+
+ std::unique_ptr<SQLiteIDBCursor> maybeOpenBackingStoreCursor(uint64_t objectStoreID, uint64_t indexID, const IDBKeyRangeData&);
+ SQLiteIDBCursor* maybeOpenCursor(const IDBCursorInfo&);
+
+ void closeCursor(SQLiteIDBCursor&);
+ void notifyCursorsOfChanges(int64_t objectStoreID);
+
+ IDBTransactionMode mode() const { return m_info.mode(); }
+ bool inProgress() const;
+
+ SQLiteTransaction* sqliteTransaction() const { return m_sqliteTransaction.get(); }
+ SQLiteIDBBackingStore& backingStore() { return m_backingStore; }
+
+ void addBlobFile(const String& temporaryPath, const String& storedFilename);
+ void addRemovedBlobFile(const String& removedFilename);
+
+private:
+ void clearCursors();
+ void reset();
+
+ void moveBlobFilesIfNecessary();
+ void deleteBlobFilesIfNecessary();
+
+ IDBTransactionInfo m_info;
+
+ SQLiteIDBBackingStore& m_backingStore;
+ std::unique_ptr<SQLiteTransaction> m_sqliteTransaction;
+ HashMap<IDBResourceIdentifier, std::unique_ptr<SQLiteIDBCursor>> m_cursors;
+ HashSet<SQLiteIDBCursor*> m_backingStoreCursors;
+ Vector<std::pair<String, String>> m_blobTemporaryAndStoredFilenames;
+ HashSet<String> m_blobRemovedFilenames;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.cpp b/Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.cpp
new file mode 100644
index 000000000..403b4d4a0
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ServerOpenDBRequest.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBResultData.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+Ref<ServerOpenDBRequest> ServerOpenDBRequest::create(IDBConnectionToClient& connection, const IDBRequestData& requestData)
+{
+ return adoptRef(*new ServerOpenDBRequest(connection, requestData));
+}
+
+ServerOpenDBRequest::ServerOpenDBRequest(IDBConnectionToClient& connection, const IDBRequestData& requestData)
+ : m_connection(connection)
+ , m_requestData(requestData)
+{
+}
+
+bool ServerOpenDBRequest::isOpenRequest() const
+{
+ return m_requestData.isOpenRequest();
+}
+
+bool ServerOpenDBRequest::isDeleteRequest() const
+{
+ return m_requestData.isDeleteRequest();
+}
+
+void ServerOpenDBRequest::maybeNotifyRequestBlocked(uint64_t currentVersion)
+{
+ if (m_notifiedBlocked)
+ return;
+
+ uint64_t requestedVersion = isOpenRequest() ? m_requestData.requestedVersion() : 0;
+ m_connection.notifyOpenDBRequestBlocked(m_requestData.requestIdentifier(), currentVersion, requestedVersion);
+
+ m_notifiedBlocked = true;
+}
+
+void ServerOpenDBRequest::notifyDidDeleteDatabase(const IDBDatabaseInfo& info)
+{
+ ASSERT(isDeleteRequest());
+
+ m_connection.didDeleteDatabase(IDBResultData::deleteDatabaseSuccess(m_requestData.requestIdentifier(), info));
+}
+
+void ServerOpenDBRequest::notifiedConnectionsOfVersionChange(HashSet<uint64_t>&& connectionIdentifiers)
+{
+ ASSERT(!m_notifiedConnectionsOfVersionChange);
+
+ m_notifiedConnectionsOfVersionChange = true;
+ m_connectionsPendingVersionChangeEvent = WTFMove(connectionIdentifiers);
+}
+
+void ServerOpenDBRequest::connectionClosedOrFiredVersionChangeEvent(uint64_t connectionIdentifier)
+{
+ m_connectionsPendingVersionChangeEvent.remove(connectionIdentifier);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.h b/Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.h
new file mode 100644
index 000000000..349c8f43b
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/ServerOpenDBRequest.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBConnectionToClient.h"
+#include "IDBRequestData.h"
+#include <wtf/HashSet.h>
+#include <wtf/Ref.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class IDBDatabaseInfo;
+
+namespace IDBServer {
+
+class ServerOpenDBRequest : public RefCounted<ServerOpenDBRequest> {
+public:
+ static Ref<ServerOpenDBRequest> create(IDBConnectionToClient&, const IDBRequestData&);
+
+ IDBConnectionToClient& connection() { return m_connection; }
+ const IDBRequestData& requestData() const { return m_requestData; }
+
+ bool isOpenRequest() const;
+ bool isDeleteRequest() const;
+
+ void maybeNotifyRequestBlocked(uint64_t currentVersion);
+ void notifyDidDeleteDatabase(const IDBDatabaseInfo&);
+
+ uint64_t versionChangeID() const;
+
+ void notifiedConnectionsOfVersionChange(HashSet<uint64_t>&& connectionIdentifiers);
+ void connectionClosedOrFiredVersionChangeEvent(uint64_t connectionIdentifier);
+ bool hasConnectionsPendingVersionChangeEvent() const { return !m_connectionsPendingVersionChangeEvent.isEmpty(); }
+ bool hasNotifiedConnectionsOfVersionChange() const { return m_notifiedConnectionsOfVersionChange; }
+
+
+private:
+ ServerOpenDBRequest(IDBConnectionToClient&, const IDBRequestData&);
+
+ IDBConnectionToClient& m_connection;
+ IDBRequestData m_requestData;
+
+ bool m_notifiedBlocked { false };
+
+ bool m_notifiedConnectionsOfVersionChange { false };
+ HashSet<uint64_t> m_connectionsPendingVersionChangeEvent;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp
new file mode 100644
index 000000000..87863f9a3
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp
@@ -0,0 +1,1908 @@
+/*
+ * Copyright (C) 2015, 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "UniqueIDBDatabase.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBCursorInfo.h"
+#include "IDBGetAllRecordsData.h"
+#include "IDBGetAllResult.h"
+#include "IDBGetRecordData.h"
+#include "IDBIterateCursorData.h"
+#include "IDBKeyRangeData.h"
+#include "IDBResultData.h"
+#include "IDBServer.h"
+#include "IDBTransactionInfo.h"
+#include "IDBValue.h"
+#include "Logging.h"
+#include "ScopeGuard.h"
+#include "SerializedScriptValue.h"
+#include "UniqueIDBDatabaseConnection.h"
+#include <heap/HeapInlines.h>
+#include <heap/StrongInlines.h>
+#include <runtime/AuxiliaryBarrierInlines.h>
+#include <runtime/StructureInlines.h>
+#include <wtf/MainThread.h>
+#include <wtf/NeverDestroyed.h>
+
+using namespace JSC;
+
+namespace WebCore {
+namespace IDBServer {
+
+UniqueIDBDatabase::UniqueIDBDatabase(IDBServer& server, const IDBDatabaseIdentifier& identifier)
+ : m_server(server)
+ , m_identifier(identifier)
+ , m_operationAndTransactionTimer(*this, &UniqueIDBDatabase::operationAndTransactionTimerFired)
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::UniqueIDBDatabase() (%p) %s", this, m_identifier.debugString().utf8().data());
+}
+
+UniqueIDBDatabase::~UniqueIDBDatabase()
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::~UniqueIDBDatabase() (%p) %s", this, m_identifier.debugString().utf8().data());
+ ASSERT(isMainThread());
+ ASSERT(!hasAnyPendingCallbacks());
+ ASSERT(!hasUnfinishedTransactions());
+ ASSERT(m_pendingTransactions.isEmpty());
+ ASSERT(m_openDatabaseConnections.isEmpty());
+ ASSERT(m_clientClosePendingDatabaseConnections.isEmpty());
+ ASSERT(m_serverClosePendingDatabaseConnections.isEmpty());
+ ASSERT(!m_queuedTaskCount);
+}
+
+const IDBDatabaseInfo& UniqueIDBDatabase::info() const
+{
+ RELEASE_ASSERT(m_databaseInfo);
+ return *m_databaseInfo;
+}
+
+void UniqueIDBDatabase::openDatabaseConnection(IDBConnectionToClient& connection, const IDBRequestData& requestData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::openDatabaseConnection");
+ ASSERT(!m_hardClosedForUserDelete);
+
+ m_pendingOpenDBRequests.add(ServerOpenDBRequest::create(connection, requestData));
+
+ // An open operation is already in progress, so we can't possibly handle this one yet.
+ if (m_isOpeningBackingStore)
+ return;
+
+ handleDatabaseOperations();
+}
+
+bool UniqueIDBDatabase::hasAnyPendingCallbacks() const
+{
+ return !m_errorCallbacks.isEmpty()
+ || !m_keyDataCallbacks.isEmpty()
+ || !m_getResultCallbacks.isEmpty()
+ || !m_getAllResultsCallbacks.isEmpty()
+ || !m_countCallbacks.isEmpty();
+}
+
+bool UniqueIDBDatabase::isVersionChangeInProgress()
+{
+#if !LOG_DISABLED
+ if (m_versionChangeTransaction)
+ ASSERT(m_versionChangeDatabaseConnection);
+#endif
+
+ return m_versionChangeDatabaseConnection;
+}
+
+void UniqueIDBDatabase::performCurrentOpenOperation()
+{
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::performCurrentOpenOperation (%p)", this);
+
+ ASSERT(m_currentOpenDBRequest);
+ ASSERT(m_currentOpenDBRequest->isOpenRequest());
+
+ if (!m_databaseInfo) {
+ if (!m_isOpeningBackingStore) {
+ m_isOpeningBackingStore = true;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::openBackingStore, m_identifier));
+ }
+
+ return;
+ }
+
+ // If we previously started a version change operation but were blocked by having open connections,
+ // we might now be unblocked.
+ if (m_versionChangeDatabaseConnection) {
+ if (!m_versionChangeTransaction && !hasAnyOpenConnections())
+ startVersionChangeTransaction();
+ return;
+ }
+
+ // 3.3.1 Opening a database
+ // If requested version is undefined, then let requested version be 1 if db was created in the previous step,
+ // or the current version of db otherwise.
+ uint64_t requestedVersion = m_currentOpenDBRequest->requestData().requestedVersion();
+ if (!requestedVersion)
+ requestedVersion = m_databaseInfo->version() ? m_databaseInfo->version() : 1;
+
+ // 3.3.1 Opening a database
+ // If the database version higher than the requested version, abort these steps and return a VersionError.
+ if (requestedVersion < m_databaseInfo->version()) {
+ auto result = IDBResultData::error(m_currentOpenDBRequest->requestData().requestIdentifier(), IDBError(IDBDatabaseException::VersionError));
+ m_currentOpenDBRequest->connection().didOpenDatabase(result);
+ m_currentOpenDBRequest = nullptr;
+
+ return;
+ }
+
+ if (!m_backingStoreOpenError.isNull()) {
+ auto result = IDBResultData::error(m_currentOpenDBRequest->requestData().requestIdentifier(), m_backingStoreOpenError);
+ m_currentOpenDBRequest->connection().didOpenDatabase(result);
+ m_currentOpenDBRequest = nullptr;
+
+ return;
+ }
+
+ Ref<UniqueIDBDatabaseConnection> connection = UniqueIDBDatabaseConnection::create(*this, *m_currentOpenDBRequest);
+
+ if (requestedVersion == m_databaseInfo->version()) {
+ auto* rawConnection = &connection.get();
+ addOpenDatabaseConnection(WTFMove(connection));
+
+ auto result = IDBResultData::openDatabaseSuccess(m_currentOpenDBRequest->requestData().requestIdentifier(), *rawConnection);
+ m_currentOpenDBRequest->connection().didOpenDatabase(result);
+ m_currentOpenDBRequest = nullptr;
+
+ return;
+ }
+
+ ASSERT(!m_versionChangeDatabaseConnection);
+ m_versionChangeDatabaseConnection = WTFMove(connection);
+
+ // 3.3.7 "versionchange" transaction steps
+ // If there's no other open connections to this database, the version change process can begin immediately.
+ if (!hasAnyOpenConnections()) {
+ startVersionChangeTransaction();
+ return;
+ }
+
+ // Otherwise we have to notify all those open connections and wait for them to close.
+ maybeNotifyConnectionsOfVersionChange();
+}
+
+void UniqueIDBDatabase::performCurrentDeleteOperation()
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::performCurrentDeleteOperation - %s", m_identifier.debugString().utf8().data());
+
+ ASSERT(m_currentOpenDBRequest);
+ ASSERT(m_currentOpenDBRequest->isDeleteRequest());
+
+ if (m_deleteBackingStoreInProgress)
+ return;
+
+ if (hasAnyOpenConnections()) {
+ maybeNotifyConnectionsOfVersionChange();
+ return;
+ }
+
+ if (hasUnfinishedTransactions())
+ return;
+
+ ASSERT(!hasAnyPendingCallbacks());
+ ASSERT(m_pendingTransactions.isEmpty());
+ ASSERT(m_openDatabaseConnections.isEmpty());
+
+ // It's possible to have multiple delete requests queued up in a row.
+ // In that scenario only the first request will actually have to delete the database.
+ // Subsequent requests can immediately notify their completion.
+
+ if (!m_deleteBackingStoreInProgress) {
+ if (!m_databaseInfo && m_mostRecentDeletedDatabaseInfo)
+ didDeleteBackingStore(0);
+ else {
+ m_deleteBackingStoreInProgress = true;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::deleteBackingStore, m_identifier));
+ }
+ }
+}
+
+void UniqueIDBDatabase::deleteBackingStore(const IDBDatabaseIdentifier& identifier)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::deleteBackingStore");
+
+ uint64_t deletedVersion = 0;
+
+ if (m_backingStore) {
+ m_backingStore->deleteBackingStore();
+ m_backingStore = nullptr;
+ m_backingStoreSupportsSimultaneousTransactions = false;
+ m_backingStoreIsEphemeral = false;
+ } else {
+ auto backingStore = m_server.createBackingStore(identifier);
+
+ IDBDatabaseInfo databaseInfo;
+ auto error = backingStore->getOrEstablishDatabaseInfo(databaseInfo);
+ if (!error.isNull())
+ LOG_ERROR("Error getting database info from database %s that we are trying to delete", identifier.debugString().utf8().data());
+
+ deletedVersion = databaseInfo.version();
+ backingStore->deleteBackingStore();
+ }
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didDeleteBackingStore, deletedVersion));
+}
+
+void UniqueIDBDatabase::performUnconditionalDeleteBackingStore()
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performUnconditionalDeleteBackingStore");
+
+ if (!m_backingStore)
+ return;
+
+ m_backingStore->deleteBackingStore();
+ m_backingStore = nullptr;
+ m_backingStoreSupportsSimultaneousTransactions = false;
+ m_backingStoreIsEphemeral = false;
+}
+
+void UniqueIDBDatabase::didDeleteBackingStore(uint64_t deletedVersion)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didDeleteBackingStore");
+
+ ASSERT(!hasAnyPendingCallbacks());
+ ASSERT(!hasUnfinishedTransactions());
+ ASSERT(m_pendingTransactions.isEmpty());
+ ASSERT(m_openDatabaseConnections.isEmpty());
+
+ // It's possible that the openDBRequest was cancelled from client-side after the delete was already dispatched to the backingstore.
+ // So it's okay if we don't have a currentOpenDBRequest, but if we do it has to be a deleteRequest.
+ ASSERT(!m_currentOpenDBRequest || m_currentOpenDBRequest->isDeleteRequest());
+
+ if (m_databaseInfo)
+ m_mostRecentDeletedDatabaseInfo = WTFMove(m_databaseInfo);
+
+ // If this UniqueIDBDatabase was brought into existence for the purpose of deleting the file on disk,
+ // we won't have a m_mostRecentDeletedDatabaseInfo. In that case, we'll manufacture one using the
+ // passed in deletedVersion argument.
+ if (!m_mostRecentDeletedDatabaseInfo)
+ m_mostRecentDeletedDatabaseInfo = std::make_unique<IDBDatabaseInfo>(m_identifier.databaseName(), deletedVersion);
+
+ if (m_currentOpenDBRequest) {
+ m_currentOpenDBRequest->notifyDidDeleteDatabase(*m_mostRecentDeletedDatabaseInfo);
+ m_currentOpenDBRequest = nullptr;
+ }
+
+ m_deleteBackingStoreInProgress = false;
+
+ if (m_clientClosePendingDatabaseConnections.isEmpty() && m_pendingOpenDBRequests.isEmpty()) {
+ m_server.closeUniqueIDBDatabase(*this);
+ return;
+ }
+
+ invokeOperationAndTransactionTimer();
+}
+
+void UniqueIDBDatabase::didPerformUnconditionalDeleteBackingStore()
+{
+ // This function is a placeholder so the database thread can message back to the main thread.
+ ASSERT(m_hardClosedForUserDelete);
+}
+
+void UniqueIDBDatabase::handleDatabaseOperations()
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::handleDatabaseOperations - There are %u pending", m_pendingOpenDBRequests.size());
+ ASSERT(!m_hardClosedForUserDelete);
+
+ if (m_deleteBackingStoreInProgress)
+ return;
+
+ if (m_versionChangeDatabaseConnection || m_versionChangeTransaction || m_currentOpenDBRequest) {
+ // We can't start any new open-database operations right now, but we might be able to start handling a delete operation.
+ if (!m_currentOpenDBRequest && !m_pendingOpenDBRequests.isEmpty() && m_pendingOpenDBRequests.first()->isDeleteRequest())
+ m_currentOpenDBRequest = m_pendingOpenDBRequests.takeFirst();
+
+ // Some operations (such as the first open operation after a delete) require multiple passes to completely handle
+ if (m_currentOpenDBRequest)
+ handleCurrentOperation();
+
+ return;
+ }
+
+ if (m_pendingOpenDBRequests.isEmpty())
+ return;
+
+ m_currentOpenDBRequest = m_pendingOpenDBRequests.takeFirst();
+ LOG(IndexedDB, "UniqueIDBDatabase::handleDatabaseOperations - Popped an operation, now there are %u pending", m_pendingOpenDBRequests.size());
+
+ handleCurrentOperation();
+}
+
+void UniqueIDBDatabase::handleCurrentOperation()
+{
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::handleCurrentOperation");
+ ASSERT(!m_hardClosedForUserDelete);
+ ASSERT(m_currentOpenDBRequest);
+
+ RefPtr<UniqueIDBDatabase> protectedThis(this);
+
+ if (m_currentOpenDBRequest->isOpenRequest())
+ performCurrentOpenOperation();
+ else if (m_currentOpenDBRequest->isDeleteRequest())
+ performCurrentDeleteOperation();
+ else
+ ASSERT_NOT_REACHED();
+
+ if (!m_currentOpenDBRequest)
+ invokeOperationAndTransactionTimer();
+}
+
+bool UniqueIDBDatabase::hasAnyOpenConnections() const
+{
+ return !m_openDatabaseConnections.isEmpty();
+}
+
+bool UniqueIDBDatabase::allConnectionsAreClosedOrClosing() const
+{
+ for (auto& connection : m_openDatabaseConnections) {
+ if (!connection->connectionIsClosing())
+ return false;
+ }
+
+ return true;
+}
+
+static uint64_t generateUniqueCallbackIdentifier()
+{
+ ASSERT(isMainThread());
+ static uint64_t currentID = 0;
+ return ++currentID;
+}
+
+uint64_t UniqueIDBDatabase::storeCallbackOrFireError(ErrorCallback callback)
+{
+ if (m_hardClosedForUserDelete) {
+ callback(IDBError::userDeleteError());
+ return 0;
+ }
+
+ uint64_t identifier = generateUniqueCallbackIdentifier();
+ ASSERT(!m_errorCallbacks.contains(identifier));
+ m_errorCallbacks.add(identifier, callback);
+ return identifier;
+}
+
+uint64_t UniqueIDBDatabase::storeCallbackOrFireError(KeyDataCallback callback)
+{
+ if (m_hardClosedForUserDelete) {
+ callback(IDBError::userDeleteError(), { });
+ return 0;
+ }
+
+ uint64_t identifier = generateUniqueCallbackIdentifier();
+ ASSERT(!m_keyDataCallbacks.contains(identifier));
+ m_keyDataCallbacks.add(identifier, callback);
+ return identifier;
+}
+
+uint64_t UniqueIDBDatabase::storeCallbackOrFireError(GetResultCallback callback)
+{
+ if (m_hardClosedForUserDelete) {
+ callback(IDBError::userDeleteError(), { });
+ return 0;
+ }
+
+ uint64_t identifier = generateUniqueCallbackIdentifier();
+ ASSERT(!m_getResultCallbacks.contains(identifier));
+ m_getResultCallbacks.add(identifier, callback);
+ return identifier;
+}
+
+uint64_t UniqueIDBDatabase::storeCallbackOrFireError(GetAllResultsCallback callback)
+{
+ if (m_hardClosedForUserDelete) {
+ callback(IDBError::userDeleteError(), { });
+ return 0;
+ }
+
+ uint64_t identifier = generateUniqueCallbackIdentifier();
+ ASSERT(!m_getAllResultsCallbacks.contains(identifier));
+ m_getAllResultsCallbacks.add(identifier, callback);
+ return identifier;
+}
+
+uint64_t UniqueIDBDatabase::storeCallbackOrFireError(CountCallback callback)
+{
+ if (m_hardClosedForUserDelete) {
+ callback(IDBError::userDeleteError(), 0);
+ return 0;
+ }
+
+ uint64_t identifier = generateUniqueCallbackIdentifier();
+ ASSERT(!m_countCallbacks.contains(identifier));
+ m_countCallbacks.add(identifier, callback);
+ return identifier;
+}
+
+void UniqueIDBDatabase::handleDelete(IDBConnectionToClient& connection, const IDBRequestData& requestData)
+{
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::handleDelete");
+ ASSERT(!m_hardClosedForUserDelete);
+
+ m_pendingOpenDBRequests.add(ServerOpenDBRequest::create(connection, requestData));
+ handleDatabaseOperations();
+}
+
+void UniqueIDBDatabase::startVersionChangeTransaction()
+{
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::startVersionChangeTransaction");
+
+ ASSERT(!m_versionChangeTransaction);
+ ASSERT(m_currentOpenDBRequest);
+ ASSERT(m_currentOpenDBRequest->isOpenRequest());
+ ASSERT(m_versionChangeDatabaseConnection);
+
+ auto operation = WTFMove(m_currentOpenDBRequest);
+
+ uint64_t requestedVersion = operation->requestData().requestedVersion();
+ if (!requestedVersion)
+ requestedVersion = m_databaseInfo->version() ? m_databaseInfo->version() : 1;
+
+ addOpenDatabaseConnection(*m_versionChangeDatabaseConnection);
+
+ m_versionChangeTransaction = &m_versionChangeDatabaseConnection->createVersionChangeTransaction(requestedVersion);
+ m_databaseInfo->setVersion(requestedVersion);
+
+ m_inProgressTransactions.set(m_versionChangeTransaction->info().identifier(), m_versionChangeTransaction);
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::beginTransactionInBackingStore, m_versionChangeTransaction->info()));
+
+ auto result = IDBResultData::openDatabaseUpgradeNeeded(operation->requestData().requestIdentifier(), *m_versionChangeTransaction);
+ operation->connection().didOpenDatabase(result);
+}
+
+void UniqueIDBDatabase::beginTransactionInBackingStore(const IDBTransactionInfo& info)
+{
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::beginTransactionInBackingStore");
+ m_backingStore->beginTransaction(info);
+}
+
+void UniqueIDBDatabase::maybeNotifyConnectionsOfVersionChange()
+{
+ ASSERT(m_currentOpenDBRequest);
+
+ if (m_currentOpenDBRequest->hasNotifiedConnectionsOfVersionChange())
+ return;
+
+ uint64_t newVersion = m_currentOpenDBRequest->isOpenRequest() ? m_currentOpenDBRequest->requestData().requestedVersion() : 0;
+ auto requestIdentifier = m_currentOpenDBRequest->requestData().requestIdentifier();
+
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::notifyConnectionsOfVersionChange - %" PRIu64, newVersion);
+
+ // 3.3.7 "versionchange" transaction steps
+ // Fire a versionchange event at each connection in m_openDatabaseConnections that is open.
+ // The event must not be fired on connections which has the closePending flag set.
+ HashSet<uint64_t> connectionIdentifiers;
+ for (auto connection : m_openDatabaseConnections) {
+ if (connection->closePending())
+ continue;
+
+ connection->fireVersionChangeEvent(requestIdentifier, newVersion);
+ connectionIdentifiers.add(connection->identifier());
+ }
+
+ if (!connectionIdentifiers.isEmpty())
+ m_currentOpenDBRequest->notifiedConnectionsOfVersionChange(WTFMove(connectionIdentifiers));
+ else
+ m_currentOpenDBRequest->maybeNotifyRequestBlocked(m_databaseInfo->version());
+}
+
+void UniqueIDBDatabase::notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent(uint64_t connectionIdentifier)
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent - %" PRIu64, connectionIdentifier);
+
+ ASSERT(m_currentOpenDBRequest);
+
+ m_currentOpenDBRequest->connectionClosedOrFiredVersionChangeEvent(connectionIdentifier);
+
+ if (m_currentOpenDBRequest->hasConnectionsPendingVersionChangeEvent())
+ return;
+
+ if (!hasAnyOpenConnections() || allConnectionsAreClosedOrClosing()) {
+ invokeOperationAndTransactionTimer();
+ return;
+ }
+
+ // Since all open connections have fired their version change events but not all of them have closed,
+ // this request is officially blocked.
+ m_currentOpenDBRequest->maybeNotifyRequestBlocked(m_databaseInfo->version());
+}
+
+void UniqueIDBDatabase::didFireVersionChangeEvent(UniqueIDBDatabaseConnection& connection, const IDBResourceIdentifier& requestIdentifier)
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::didFireVersionChangeEvent");
+
+ if (!m_currentOpenDBRequest)
+ return;
+
+ ASSERT_UNUSED(requestIdentifier, m_currentOpenDBRequest->requestData().requestIdentifier() == requestIdentifier);
+
+ notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent(connection.identifier());
+}
+
+void UniqueIDBDatabase::openDBRequestCancelled(const IDBResourceIdentifier& requestIdentifier)
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::openDBRequestCancelled - %s", requestIdentifier.loggingString().utf8().data());
+
+ if (m_currentOpenDBRequest && m_currentOpenDBRequest->requestData().requestIdentifier() == requestIdentifier)
+ m_currentOpenDBRequest = nullptr;
+
+ if (m_versionChangeDatabaseConnection && m_versionChangeDatabaseConnection->openRequestIdentifier() == requestIdentifier) {
+ ASSERT(!m_versionChangeTransaction || m_versionChangeTransaction->databaseConnection().openRequestIdentifier() == requestIdentifier);
+ ASSERT(!m_versionChangeTransaction || &m_versionChangeTransaction->databaseConnection() == m_versionChangeDatabaseConnection);
+
+ connectionClosedFromClient(*m_versionChangeDatabaseConnection);
+ }
+
+ for (auto& request : m_pendingOpenDBRequests) {
+ if (request->requestData().requestIdentifier() == requestIdentifier) {
+ m_pendingOpenDBRequests.remove(request);
+ return;
+ }
+ }
+}
+
+void UniqueIDBDatabase::addOpenDatabaseConnection(Ref<UniqueIDBDatabaseConnection>&& connection)
+{
+ ASSERT(!m_openDatabaseConnections.contains(&connection.get()));
+ m_openDatabaseConnections.add(adoptRef(connection.leakRef()));
+}
+
+void UniqueIDBDatabase::openBackingStore(const IDBDatabaseIdentifier& identifier)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::openBackingStore (%p)", this);
+
+ ASSERT(!m_backingStore);
+ m_backingStore = m_server.createBackingStore(identifier);
+ m_backingStoreSupportsSimultaneousTransactions = m_backingStore->supportsSimultaneousTransactions();
+ m_backingStoreIsEphemeral = m_backingStore->isEphemeral();
+
+ IDBDatabaseInfo databaseInfo;
+ auto error = m_backingStore->getOrEstablishDatabaseInfo(databaseInfo);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didOpenBackingStore, databaseInfo, error));
+}
+
+void UniqueIDBDatabase::didOpenBackingStore(const IDBDatabaseInfo& info, const IDBError& error)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didOpenBackingStore");
+
+ m_databaseInfo = std::make_unique<IDBDatabaseInfo>(info);
+ m_backingStoreOpenError = error;
+
+ ASSERT(m_isOpeningBackingStore);
+ m_isOpeningBackingStore = false;
+
+ handleDatabaseOperations();
+}
+
+void UniqueIDBDatabase::createObjectStore(UniqueIDBDatabaseTransaction& transaction, const IDBObjectStoreInfo& info, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::createObjectStore");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performCreateObjectStore, callbackID, transaction.info().identifier(), info));
+}
+
+void UniqueIDBDatabase::performCreateObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo& info)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performCreateObjectStore");
+
+ ASSERT(m_backingStore);
+ m_backingStore->createObjectStore(transactionIdentifier, info);
+
+ IDBError error;
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCreateObjectStore, callbackIdentifier, error, info));
+}
+
+void UniqueIDBDatabase::didPerformCreateObjectStore(uint64_t callbackIdentifier, const IDBError& error, const IDBObjectStoreInfo& info)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformCreateObjectStore");
+
+ if (error.isNull())
+ m_databaseInfo->addExistingObjectStore(info);
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::deleteObjectStore(UniqueIDBDatabaseTransaction& transaction, const String& objectStoreName, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::deleteObjectStore");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ auto* info = m_databaseInfo->infoForExistingObjectStore(objectStoreName);
+ if (!info) {
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete non-existant object store") });
+ return;
+ }
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performDeleteObjectStore, callbackID, transaction.info().identifier(), info->identifier()));
+}
+
+void UniqueIDBDatabase::performDeleteObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performDeleteObjectStore");
+
+ ASSERT(m_backingStore);
+ m_backingStore->deleteObjectStore(transactionIdentifier, objectStoreIdentifier);
+
+ IDBError error;
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformDeleteObjectStore, callbackIdentifier, error, objectStoreIdentifier));
+}
+
+void UniqueIDBDatabase::didPerformDeleteObjectStore(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformDeleteObjectStore");
+
+ if (error.isNull())
+ m_databaseInfo->deleteObjectStore(objectStoreIdentifier);
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::renameObjectStore(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, const String& newName, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::renameObjectStore");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ auto* info = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ if (!info) {
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename non-existant object store") });
+ return;
+ }
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performRenameObjectStore, callbackID, transaction.info().identifier(), objectStoreIdentifier, newName));
+}
+
+void UniqueIDBDatabase::performRenameObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performRenameObjectStore");
+
+ ASSERT(m_backingStore);
+ m_backingStore->renameObjectStore(transactionIdentifier, objectStoreIdentifier, newName);
+
+ IDBError error;
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformRenameObjectStore, callbackIdentifier, error, objectStoreIdentifier, newName));
+}
+
+void UniqueIDBDatabase::didPerformRenameObjectStore(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier, const String& newName)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformRenameObjectStore");
+
+ if (error.isNull())
+ m_databaseInfo->renameObjectStore(objectStoreIdentifier, newName);
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::clearObjectStore(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::clearObjectStore");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performClearObjectStore, callbackID, transaction.info().identifier(), objectStoreIdentifier));
+}
+
+void UniqueIDBDatabase::performClearObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performClearObjectStore");
+
+ ASSERT(m_backingStore);
+ m_backingStore->clearObjectStore(transactionIdentifier, objectStoreIdentifier);
+
+ IDBError error;
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformClearObjectStore, callbackIdentifier, error));
+}
+
+void UniqueIDBDatabase::didPerformClearObjectStore(uint64_t callbackIdentifier, const IDBError& error)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformClearObjectStore");
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::createIndex(UniqueIDBDatabaseTransaction& transaction, const IDBIndexInfo& info, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::createIndex");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performCreateIndex, callbackID, transaction.info().identifier(), info));
+}
+
+void UniqueIDBDatabase::performCreateIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo& info)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performCreateIndex");
+
+ ASSERT(m_backingStore);
+ IDBError error = m_backingStore->createIndex(transactionIdentifier, info);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCreateIndex, callbackIdentifier, error, info));
+}
+
+void UniqueIDBDatabase::didPerformCreateIndex(uint64_t callbackIdentifier, const IDBError& error, const IDBIndexInfo& info)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformCreateIndex");
+
+ if (error.isNull()) {
+ ASSERT(m_databaseInfo);
+ auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(info.objectStoreIdentifier());
+ ASSERT(objectStoreInfo);
+ objectStoreInfo->addExistingIndex(info);
+ }
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::deleteIndex(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, const String& indexName, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::deleteIndex");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ if (!objectStoreInfo) {
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete index from non-existant object store") });
+ return;
+ }
+
+ auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexName);
+ if (!indexInfo) {
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to delete non-existant index") });
+ return;
+ }
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performDeleteIndex, callbackID, transaction.info().identifier(), objectStoreIdentifier, indexInfo->identifier()));
+}
+
+void UniqueIDBDatabase::performDeleteIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const uint64_t indexIdentifier)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performDeleteIndex");
+
+ ASSERT(m_backingStore);
+ m_backingStore->deleteIndex(transactionIdentifier, objectStoreIdentifier, indexIdentifier);
+
+ IDBError error;
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformDeleteIndex, callbackIdentifier, error, objectStoreIdentifier, indexIdentifier));
+}
+
+void UniqueIDBDatabase::didPerformDeleteIndex(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier, uint64_t indexIdentifier)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformDeleteIndex");
+
+ if (error.isNull()) {
+ auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ if (objectStoreInfo)
+ objectStoreInfo->deleteIndex(indexIdentifier);
+ }
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::renameIndex(UniqueIDBDatabaseTransaction& transaction, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::renameIndex");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ if (!objectStoreInfo) {
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename index in non-existant object store") });
+ return;
+ }
+
+ auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexIdentifier);
+ if (!indexInfo) {
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to rename non-existant index") });
+ return;
+ }
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performRenameIndex, callbackID, transaction.info().identifier(), objectStoreIdentifier, indexIdentifier, newName));
+}
+
+void UniqueIDBDatabase::performRenameIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performRenameIndex");
+
+ ASSERT(m_backingStore);
+ m_backingStore->renameIndex(transactionIdentifier, objectStoreIdentifier, indexIdentifier, newName);
+
+ IDBError error;
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformRenameIndex, callbackIdentifier, error, objectStoreIdentifier, indexIdentifier, newName));
+}
+
+void UniqueIDBDatabase::didPerformRenameIndex(uint64_t callbackIdentifier, const IDBError& error, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformRenameIndex");
+
+ if (error.isNull()) {
+ auto* objectStoreInfo = m_databaseInfo->infoForExistingObjectStore(objectStoreIdentifier);
+ ASSERT(objectStoreInfo);
+ if (objectStoreInfo) {
+ auto* indexInfo = objectStoreInfo->infoForExistingIndex(indexIdentifier);
+ ASSERT(indexInfo);
+ indexInfo->rename(newName);
+ }
+ }
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::putOrAdd(const IDBRequestData& requestData, const IDBKeyData& keyData, const IDBValue& value, IndexedDB::ObjectStoreOverwriteMode overwriteMode, KeyDataCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::putOrAdd");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPutOrAdd, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), keyData, value, overwriteMode));
+}
+
+VM& UniqueIDBDatabase::databaseThreadVM()
+{
+ ASSERT(!isMainThread());
+ static VM* vm = &VM::create().leakRef();
+ return *vm;
+}
+
+ExecState& UniqueIDBDatabase::databaseThreadExecState()
+{
+ ASSERT(!isMainThread());
+
+ static NeverDestroyed<Strong<JSGlobalObject>> globalObject(databaseThreadVM(), JSGlobalObject::create(databaseThreadVM(), JSGlobalObject::createStructure(databaseThreadVM(), jsNull())));
+
+ RELEASE_ASSERT(globalObject.get()->globalExec());
+ return *globalObject.get()->globalExec();
+}
+
+void UniqueIDBDatabase::performPutOrAdd(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData& keyData, const IDBValue& originalRecordValue, IndexedDB::ObjectStoreOverwriteMode overwriteMode)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performPutOrAdd");
+
+ ASSERT(m_backingStore);
+ ASSERT(objectStoreIdentifier);
+
+ IDBKeyData usedKey;
+ IDBError error;
+
+ auto* objectStoreInfo = m_backingStore->infoForObjectStore(objectStoreIdentifier);
+ if (!objectStoreInfo) {
+ error = IDBError(IDBDatabaseException::InvalidStateError, ASCIILiteral("Object store cannot be found in the backing store"));
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
+ return;
+ }
+
+ bool usedKeyIsGenerated = false;
+ ScopeGuard generatedKeyResetter;
+ if (objectStoreInfo->autoIncrement() && !keyData.isValid()) {
+ uint64_t keyNumber;
+ error = m_backingStore->generateKeyNumber(transactionIdentifier, objectStoreIdentifier, keyNumber);
+ if (!error.isNull()) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
+ return;
+ }
+
+ usedKey.setNumberValue(keyNumber);
+ usedKeyIsGenerated = true;
+ generatedKeyResetter.enable([this, transactionIdentifier, objectStoreIdentifier, keyNumber]() {
+ m_backingStore->revertGeneratedKeyNumber(transactionIdentifier, objectStoreIdentifier, keyNumber);
+ });
+ } else
+ usedKey = keyData;
+
+ if (overwriteMode == IndexedDB::ObjectStoreOverwriteMode::NoOverwrite) {
+ bool keyExists;
+ error = m_backingStore->keyExistsInObjectStore(transactionIdentifier, objectStoreIdentifier, usedKey, keyExists);
+ if (error.isNull() && keyExists)
+ error = IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Key already exists in the object store"));
+
+ if (!error.isNull()) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
+ return;
+ }
+ }
+
+ // 3.4.1.2 Object Store Storage Operation
+ // If ObjectStore has a key path and the key is autogenerated, then inject the key into the value
+ // using steps to assign a key to a value using a key path.
+ ThreadSafeDataBuffer injectedRecordValue;
+ if (usedKeyIsGenerated && objectStoreInfo->keyPath()) {
+ VM& vm = databaseThreadVM();
+ JSLockHolder locker(vm);
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ auto value = deserializeIDBValueToJSValue(databaseThreadExecState(), originalRecordValue.data());
+ if (value.isUndefined()) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Unable to deserialize record value for record key injection")), usedKey));
+ return;
+ }
+
+ if (!injectIDBKeyIntoScriptValue(databaseThreadExecState(), usedKey, value, objectStoreInfo->keyPath().value())) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Unable to inject record key into record value")), usedKey));
+ return;
+ }
+
+ auto serializedValue = SerializedScriptValue::create(databaseThreadExecState(), value);
+ if (UNLIKELY(scope.exception())) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, IDBError(IDBDatabaseException::ConstraintError, ASCIILiteral("Unable to serialize record value after injecting record key")), usedKey));
+ return;
+ }
+
+ injectedRecordValue = ThreadSafeDataBuffer::copyVector(serializedValue->data());
+ }
+
+ // 3.4.1 Object Store Storage Operation
+ // ...If a record already exists in store ...
+ // then remove the record from store using the steps for deleting records from an object store...
+ // This is important because formally deleting it from from the object store also removes it from the appropriate indexes.
+ error = m_backingStore->deleteRange(transactionIdentifier, objectStoreIdentifier, usedKey);
+ if (!error.isNull()) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
+ return;
+ }
+
+ if (injectedRecordValue.data())
+ error = m_backingStore->addRecord(transactionIdentifier, *objectStoreInfo, usedKey, { injectedRecordValue, originalRecordValue.blobURLs(), originalRecordValue.blobFilePaths() });
+ else
+ error = m_backingStore->addRecord(transactionIdentifier, *objectStoreInfo, usedKey, originalRecordValue);
+
+ if (!error.isNull()) {
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
+ return;
+ }
+
+ if (overwriteMode != IndexedDB::ObjectStoreOverwriteMode::OverwriteForCursor && objectStoreInfo->autoIncrement() && keyData.type() == IndexedDB::KeyType::Number)
+ error = m_backingStore->maybeUpdateKeyGeneratorNumber(transactionIdentifier, objectStoreIdentifier, keyData.number());
+
+ generatedKeyResetter.disable();
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformPutOrAdd, callbackIdentifier, error, usedKey));
+}
+
+void UniqueIDBDatabase::didPerformPutOrAdd(uint64_t callbackIdentifier, const IDBError& error, const IDBKeyData& resultKey)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformPutOrAdd");
+
+ performKeyDataCallback(callbackIdentifier, error, resultKey);
+}
+
+void UniqueIDBDatabase::getRecord(const IDBRequestData& requestData, const IDBGetRecordData& getRecordData, GetResultCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::getRecord");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ if (uint64_t indexIdentifier = requestData.indexIdentifier())
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetIndexRecord, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), indexIdentifier, requestData.indexRecordType(), getRecordData.keyRangeData));
+ else
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetRecord, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), getRecordData.keyRangeData, getRecordData.type));
+}
+
+void UniqueIDBDatabase::getAllRecords(const IDBRequestData& requestData, const IDBGetAllRecordsData& getAllRecordsData, GetAllResultsCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::getAllRecords");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetAllRecords, callbackID, requestData.transactionIdentifier(), getAllRecordsData));
+}
+
+void UniqueIDBDatabase::performGetRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData& keyRangeData, IDBGetRecordDataType type)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetRecord");
+
+ ASSERT(m_backingStore);
+
+ IDBGetResult result;
+ IDBError error = m_backingStore->getRecord(transactionIdentifier, objectStoreIdentifier, keyRangeData, type, result);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetRecord, callbackIdentifier, error, result));
+}
+
+void UniqueIDBDatabase::performGetIndexRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, IndexedDB::IndexRecordType recordType, const IDBKeyRangeData& range)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetIndexRecord");
+
+ ASSERT(m_backingStore);
+
+ IDBGetResult result;
+ IDBError error = m_backingStore->getIndexRecord(transactionIdentifier, objectStoreIdentifier, indexIdentifier, recordType, range, result);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetRecord, callbackIdentifier, error, result));
+}
+
+void UniqueIDBDatabase::didPerformGetRecord(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& result)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformGetRecord");
+
+ performGetResultCallback(callbackIdentifier, error, result);
+}
+
+void UniqueIDBDatabase::performGetAllRecords(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData& getAllRecordsData)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetAllRecords");
+
+ ASSERT(m_backingStore);
+
+ IDBGetAllResult result;
+ IDBError error = m_backingStore->getAllRecords(transactionIdentifier, getAllRecordsData, result);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetAllRecords, callbackIdentifier, error, WTFMove(result)));
+}
+
+void UniqueIDBDatabase::didPerformGetAllRecords(uint64_t callbackIdentifier, const IDBError& error, const IDBGetAllResult& result)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformGetAllRecords");
+
+ performGetAllResultsCallback(callbackIdentifier, error, result);
+}
+
+void UniqueIDBDatabase::getCount(const IDBRequestData& requestData, const IDBKeyRangeData& range, CountCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::getCount");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performGetCount, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), requestData.indexIdentifier(), range));
+}
+
+void UniqueIDBDatabase::performGetCount(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData& keyRangeData)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performGetCount");
+
+ ASSERT(m_backingStore);
+ ASSERT(objectStoreIdentifier);
+
+ uint64_t count;
+ IDBError error = m_backingStore->getCount(transactionIdentifier, objectStoreIdentifier, indexIdentifier, keyRangeData, count);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformGetCount, callbackIdentifier, error, count));
+}
+
+void UniqueIDBDatabase::didPerformGetCount(uint64_t callbackIdentifier, const IDBError& error, uint64_t count)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformGetCount");
+
+ performCountCallback(callbackIdentifier, error, count);
+}
+
+void UniqueIDBDatabase::deleteRecord(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::deleteRecord");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performDeleteRecord, callbackID, requestData.transactionIdentifier(), requestData.objectStoreIdentifier(), keyRangeData));
+}
+
+void UniqueIDBDatabase::performDeleteRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData& range)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performDeleteRecord");
+
+ IDBError error = m_backingStore->deleteRange(transactionIdentifier, objectStoreIdentifier, range);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformDeleteRecord, callbackIdentifier, error));
+}
+
+void UniqueIDBDatabase::didPerformDeleteRecord(uint64_t callbackIdentifier, const IDBError& error)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformDeleteRecord");
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+void UniqueIDBDatabase::openCursor(const IDBRequestData& requestData, const IDBCursorInfo& info, GetResultCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::openCursor");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performOpenCursor, callbackID, requestData.transactionIdentifier(), info));
+}
+
+void UniqueIDBDatabase::performOpenCursor(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo& info)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performOpenCursor");
+
+ IDBGetResult result;
+ IDBError error = m_backingStore->openCursor(transactionIdentifier, info, result);
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformOpenCursor, callbackIdentifier, error, result));
+}
+
+void UniqueIDBDatabase::didPerformOpenCursor(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& result)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformOpenCursor");
+
+ performGetResultCallback(callbackIdentifier, error, result);
+}
+
+void UniqueIDBDatabase::iterateCursor(const IDBRequestData& requestData, const IDBIterateCursorData& data, GetResultCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::iterateCursor");
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performIterateCursor, callbackID, requestData.transactionIdentifier(), requestData.cursorIdentifier(), data));
+}
+
+void UniqueIDBDatabase::performIterateCursor(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData& data)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performIterateCursor");
+
+ IDBGetResult result;
+ IDBError error = m_backingStore->iterateCursor(transactionIdentifier, cursorIdentifier, data, result);
+
+ if (error.isNull()) {
+ auto addResult = m_prefetchProtectors.add(cursorIdentifier, nullptr);
+ if (addResult.isNewEntry) {
+ addResult.iterator->value = this;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPrefetchCursor, transactionIdentifier, cursorIdentifier));
+ }
+ }
+
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformIterateCursor, callbackIdentifier, error, result));
+}
+
+void UniqueIDBDatabase::performPrefetchCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier)
+{
+ ASSERT(!isMainThread());
+ ASSERT(m_prefetchProtectors.contains(cursorIdentifier));
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performPrefetchCursor");
+
+ if (m_backingStore->prefetchCursor(transactionIdentifier, cursorIdentifier))
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performPrefetchCursor, transactionIdentifier, cursorIdentifier));
+ else
+ postDatabaseTaskReply(WTF::Function<void ()>([prefetchProtector = m_prefetchProtectors.take(cursorIdentifier)]() { }));
+}
+
+void UniqueIDBDatabase::didPerformIterateCursor(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& result)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformIterateCursor");
+
+ performGetResultCallback(callbackIdentifier, error, result);
+}
+
+bool UniqueIDBDatabase::prepareToFinishTransaction(UniqueIDBDatabaseTransaction& transaction)
+{
+ auto takenTransaction = m_inProgressTransactions.take(transaction.info().identifier());
+ if (!takenTransaction)
+ return false;
+
+ ASSERT(!m_finishingTransactions.contains(transaction.info().identifier()));
+ m_finishingTransactions.set(transaction.info().identifier(), WTFMove(takenTransaction));
+
+ return true;
+}
+
+void UniqueIDBDatabase::commitTransaction(UniqueIDBDatabaseTransaction& transaction, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::commitTransaction - %s", transaction.info().identifier().loggingString().utf8().data());
+
+ ASSERT(&transaction.databaseConnection().database() == this);
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ if (!prepareToFinishTransaction(transaction)) {
+ if (!m_openDatabaseConnections.contains(&transaction.databaseConnection())) {
+ // This database connection is closing or has already closed, so there is no point in messaging back to it about the commit failing.
+ forgetErrorCallback(callbackID);
+ return;
+ }
+
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to commit transaction that is already finishing") });
+ return;
+ }
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performCommitTransaction, callbackID, transaction.info().identifier()));
+}
+
+void UniqueIDBDatabase::performCommitTransaction(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performCommitTransaction - %s", transactionIdentifier.loggingString().utf8().data());
+
+ IDBError error = m_backingStore->commitTransaction(transactionIdentifier);
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCommitTransaction, callbackIdentifier, error, transactionIdentifier));
+}
+
+void UniqueIDBDatabase::didPerformCommitTransaction(uint64_t callbackIdentifier, const IDBError& error, const IDBResourceIdentifier& transactionIdentifier)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformCommitTransaction - %s", transactionIdentifier.loggingString().utf8().data());
+
+ performErrorCallback(callbackIdentifier, error);
+
+ transactionCompleted(m_finishingTransactions.take(transactionIdentifier));
+}
+
+void UniqueIDBDatabase::abortTransaction(UniqueIDBDatabaseTransaction& transaction, ErrorCallback callback)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::abortTransaction - %s", transaction.info().identifier().loggingString().utf8().data());
+
+ ASSERT(&transaction.databaseConnection().database() == this);
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+
+ if (!prepareToFinishTransaction(transaction)) {
+ if (!m_openDatabaseConnections.contains(&transaction.databaseConnection())) {
+ // This database connection is closing or has already closed, so there is no point in messaging back to it about the abort failing.
+ forgetErrorCallback(callbackID);
+ return;
+ }
+
+ performErrorCallback(callbackID, { IDBDatabaseException::UnknownError, ASCIILiteral("Attempt to abort transaction that is already finishing") });
+ return;
+ }
+
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performAbortTransaction, callbackID, transaction.info().identifier()));
+}
+
+void UniqueIDBDatabase::didFinishHandlingVersionChange(UniqueIDBDatabaseConnection& connection, const IDBResourceIdentifier& transactionIdentifier)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didFinishHandlingVersionChange");
+
+ ASSERT_UNUSED(transactionIdentifier, !m_versionChangeTransaction || m_versionChangeTransaction->info().identifier() == transactionIdentifier);
+ ASSERT_UNUSED(connection, !m_versionChangeDatabaseConnection || m_versionChangeDatabaseConnection.get() == &connection);
+
+ m_versionChangeTransaction = nullptr;
+ m_versionChangeDatabaseConnection = nullptr;
+
+ if (m_hardClosedForUserDelete) {
+ maybeFinishHardClose();
+ return;
+ }
+
+ invokeOperationAndTransactionTimer();
+}
+
+void UniqueIDBDatabase::performAbortTransaction(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier)
+{
+ ASSERT(!isMainThread());
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performAbortTransaction - %s", transactionIdentifier.loggingString().utf8().data());
+
+ IDBError error = m_backingStore->abortTransaction(transactionIdentifier);
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformAbortTransaction, callbackIdentifier, error, transactionIdentifier));
+}
+
+void UniqueIDBDatabase::didPerformAbortTransaction(uint64_t callbackIdentifier, const IDBError& error, const IDBResourceIdentifier& transactionIdentifier)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformAbortTransaction - %s", transactionIdentifier.loggingString().utf8().data());
+
+ auto transaction = m_finishingTransactions.take(transactionIdentifier);
+ ASSERT(transaction);
+
+ if (m_versionChangeTransaction && m_versionChangeTransaction->info().identifier() == transactionIdentifier) {
+ ASSERT(m_versionChangeTransaction == transaction);
+ ASSERT(!m_versionChangeDatabaseConnection || &m_versionChangeTransaction->databaseConnection() == m_versionChangeDatabaseConnection);
+ ASSERT(m_versionChangeTransaction->originalDatabaseInfo());
+ m_databaseInfo = std::make_unique<IDBDatabaseInfo>(*m_versionChangeTransaction->originalDatabaseInfo());
+ }
+
+ performErrorCallback(callbackIdentifier, error);
+
+ transactionCompleted(WTFMove(transaction));
+}
+
+void UniqueIDBDatabase::transactionDestroyed(UniqueIDBDatabaseTransaction& transaction)
+{
+ if (m_versionChangeTransaction == &transaction)
+ m_versionChangeTransaction = nullptr;
+}
+
+void UniqueIDBDatabase::connectionClosedFromClient(UniqueIDBDatabaseConnection& connection)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::connectionClosedFromClient - %s (%" PRIu64 ")", connection.openRequestIdentifier().loggingString().utf8().data(), connection.identifier());
+
+ Ref<UniqueIDBDatabaseConnection> protectedConnection(connection);
+ m_openDatabaseConnections.remove(&connection);
+
+ if (m_versionChangeDatabaseConnection == &connection) {
+ if (m_versionChangeTransaction) {
+ m_clientClosePendingDatabaseConnections.add(WTFMove(m_versionChangeDatabaseConnection));
+
+ auto transactionIdentifier = m_versionChangeTransaction->info().identifier();
+ if (m_inProgressTransactions.contains(transactionIdentifier)) {
+ ASSERT(!m_finishingTransactions.contains(transactionIdentifier));
+ connection.abortTransactionWithoutCallback(*m_versionChangeTransaction);
+ }
+
+ return;
+ }
+
+ m_versionChangeDatabaseConnection = nullptr;
+ }
+
+ Deque<RefPtr<UniqueIDBDatabaseTransaction>> pendingTransactions;
+ while (!m_pendingTransactions.isEmpty()) {
+ auto transaction = m_pendingTransactions.takeFirst();
+ if (&transaction->databaseConnection() != &connection)
+ pendingTransactions.append(WTFMove(transaction));
+ }
+
+ if (!pendingTransactions.isEmpty())
+ m_pendingTransactions.swap(pendingTransactions);
+
+ Deque<RefPtr<UniqueIDBDatabaseTransaction>> transactionsToAbort;
+ for (auto& transaction : m_inProgressTransactions.values()) {
+ if (&transaction->databaseConnection() == &connection)
+ transactionsToAbort.append(transaction);
+ }
+
+ for (auto& transaction : transactionsToAbort)
+ transaction->abortWithoutCallback();
+
+ if (m_currentOpenDBRequest)
+ notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent(connection.identifier());
+
+ if (connection.hasNonFinishedTransactions()) {
+ m_clientClosePendingDatabaseConnections.add(WTFMove(protectedConnection));
+ return;
+ }
+
+ if (m_hardClosedForUserDelete) {
+ maybeFinishHardClose();
+ return;
+ }
+
+ // Now that a database connection has closed, previously blocked operations might be runnable.
+ invokeOperationAndTransactionTimer();
+}
+
+void UniqueIDBDatabase::connectionClosedFromServer(UniqueIDBDatabaseConnection& connection)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "UniqueIDBDatabase::connectionClosedFromServer - %s (%" PRIu64 ")", connection.openRequestIdentifier().loggingString().utf8().data(), connection.identifier());
+
+ if (m_clientClosePendingDatabaseConnections.contains(&connection)) {
+ ASSERT(!m_openDatabaseConnections.contains(&connection));
+ ASSERT(!m_serverClosePendingDatabaseConnections.contains(&connection));
+ return;
+ }
+
+ Ref<UniqueIDBDatabaseConnection> protectedConnection(connection);
+ m_openDatabaseConnections.remove(&connection);
+
+ connection.connectionToClient().didCloseFromServer(connection, IDBError::userDeleteError());
+
+ m_serverClosePendingDatabaseConnections.add(WTFMove(protectedConnection));
+}
+
+void UniqueIDBDatabase::confirmDidCloseFromServer(UniqueIDBDatabaseConnection& connection)
+{
+ ASSERT(isMainThread());
+ LOG(IndexedDB, "UniqueIDBDatabase::confirmDidCloseFromServer - %s (%" PRIu64 ")", connection.openRequestIdentifier().loggingString().utf8().data(), connection.identifier());
+
+ ASSERT(m_serverClosePendingDatabaseConnections.contains(&connection));
+ m_serverClosePendingDatabaseConnections.remove(&connection);
+}
+
+void UniqueIDBDatabase::enqueueTransaction(Ref<UniqueIDBDatabaseTransaction>&& transaction)
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::enqueueTransaction - %s", transaction->info().loggingString().utf8().data());
+ ASSERT(!m_hardClosedForUserDelete);
+
+ ASSERT(transaction->info().mode() != IDBTransactionMode::Versionchange);
+
+ m_pendingTransactions.append(WTFMove(transaction));
+
+ invokeOperationAndTransactionTimer();
+}
+
+bool UniqueIDBDatabase::isCurrentlyInUse() const
+{
+ return !m_openDatabaseConnections.isEmpty() || !m_clientClosePendingDatabaseConnections.isEmpty() || !m_pendingOpenDBRequests.isEmpty() || m_currentOpenDBRequest || m_versionChangeDatabaseConnection || m_versionChangeTransaction || m_isOpeningBackingStore || m_deleteBackingStoreInProgress;
+}
+
+bool UniqueIDBDatabase::hasUnfinishedTransactions() const
+{
+ return !m_inProgressTransactions.isEmpty() || !m_finishingTransactions.isEmpty();
+}
+
+void UniqueIDBDatabase::invokeOperationAndTransactionTimer()
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::invokeOperationAndTransactionTimer()");
+ ASSERT(!m_hardClosedForUserDelete);
+
+ if (!m_operationAndTransactionTimer.isActive())
+ m_operationAndTransactionTimer.startOneShot(0);
+}
+
+void UniqueIDBDatabase::operationAndTransactionTimerFired()
+{
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::operationAndTransactionTimerFired");
+ ASSERT(!m_hardClosedForUserDelete);
+
+ RefPtr<UniqueIDBDatabase> protectedThis(this);
+
+ // This UniqueIDBDatabase might be no longer in use by any web page.
+ // Assuming it is not ephemeral, the server should now close it to free up resources.
+ if (!m_backingStoreIsEphemeral && !isCurrentlyInUse()) {
+ ASSERT(m_pendingTransactions.isEmpty());
+ ASSERT(!hasUnfinishedTransactions());
+ m_server.closeUniqueIDBDatabase(*this);
+ return;
+ }
+
+ // The current operation might require multiple attempts to handle, so try to
+ // make further progress on it now.
+ if (m_currentOpenDBRequest)
+ handleCurrentOperation();
+
+ if (!m_currentOpenDBRequest)
+ handleDatabaseOperations();
+
+ bool hadDeferredTransactions = false;
+ auto transaction = takeNextRunnableTransaction(hadDeferredTransactions);
+
+ if (transaction) {
+ m_inProgressTransactions.set(transaction->info().identifier(), transaction);
+ for (auto objectStore : transaction->objectStoreIdentifiers()) {
+ m_objectStoreTransactionCounts.add(objectStore);
+ if (!transaction->isReadOnly()) {
+ m_objectStoreWriteTransactions.add(objectStore);
+ ASSERT(m_objectStoreTransactionCounts.count(objectStore) == 1);
+ }
+ }
+
+ activateTransactionInBackingStore(*transaction);
+
+ // If no transactions were deferred, it's possible we can start another transaction right now.
+ if (!hadDeferredTransactions)
+ invokeOperationAndTransactionTimer();
+ }
+}
+
+void UniqueIDBDatabase::activateTransactionInBackingStore(UniqueIDBDatabaseTransaction& transaction)
+{
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::activateTransactionInBackingStore");
+
+ RefPtr<UniqueIDBDatabase> protectedThis(this);
+ RefPtr<UniqueIDBDatabaseTransaction> refTransaction(&transaction);
+
+ auto callback = [this, protectedThis, refTransaction](const IDBError& error) {
+ refTransaction->didActivateInBackingStore(error);
+ };
+
+ uint64_t callbackID = storeCallbackOrFireError(callback);
+ if (!callbackID)
+ return;
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performActivateTransactionInBackingStore, callbackID, transaction.info()));
+}
+
+void UniqueIDBDatabase::performActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBTransactionInfo& info)
+{
+ LOG(IndexedDB, "(db) UniqueIDBDatabase::performActivateTransactionInBackingStore");
+
+ IDBError error = m_backingStore->beginTransaction(info);
+ postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformActivateTransactionInBackingStore, callbackIdentifier, error));
+}
+
+void UniqueIDBDatabase::didPerformActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBError& error)
+{
+ LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformActivateTransactionInBackingStore");
+
+ invokeOperationAndTransactionTimer();
+
+ performErrorCallback(callbackIdentifier, error);
+}
+
+template<typename T> bool scopesOverlap(const T& aScopes, const Vector<uint64_t>& bScopes)
+{
+ for (auto scope : bScopes) {
+ if (aScopes.contains(scope))
+ return true;
+ }
+
+ return false;
+}
+
+RefPtr<UniqueIDBDatabaseTransaction> UniqueIDBDatabase::takeNextRunnableTransaction(bool& hadDeferredTransactions)
+{
+ hadDeferredTransactions = false;
+
+ if (m_pendingTransactions.isEmpty())
+ return nullptr;
+
+ if (!m_backingStoreSupportsSimultaneousTransactions && hasUnfinishedTransactions()) {
+ LOG(IndexedDB, "UniqueIDBDatabase::takeNextRunnableTransaction - Backing store only supports 1 transaction, and we already have 1");
+ return nullptr;
+ }
+
+ Deque<RefPtr<UniqueIDBDatabaseTransaction>> deferredTransactions;
+ RefPtr<UniqueIDBDatabaseTransaction> currentTransaction;
+
+ HashSet<uint64_t> deferredReadWriteScopes;
+
+ while (!m_pendingTransactions.isEmpty()) {
+ currentTransaction = m_pendingTransactions.takeFirst();
+
+ switch (currentTransaction->info().mode()) {
+ case IDBTransactionMode::Readonly: {
+ bool hasOverlappingScopes = scopesOverlap(deferredReadWriteScopes, currentTransaction->objectStoreIdentifiers());
+ hasOverlappingScopes |= scopesOverlap(m_objectStoreWriteTransactions, currentTransaction->objectStoreIdentifiers());
+
+ if (hasOverlappingScopes)
+ deferredTransactions.append(WTFMove(currentTransaction));
+
+ break;
+ }
+ case IDBTransactionMode::Readwrite: {
+ bool hasOverlappingScopes = scopesOverlap(m_objectStoreTransactionCounts, currentTransaction->objectStoreIdentifiers());
+ hasOverlappingScopes |= scopesOverlap(deferredReadWriteScopes, currentTransaction->objectStoreIdentifiers());
+
+ if (hasOverlappingScopes) {
+ for (auto objectStore : currentTransaction->objectStoreIdentifiers())
+ deferredReadWriteScopes.add(objectStore);
+ deferredTransactions.append(WTFMove(currentTransaction));
+ }
+
+ break;
+ }
+ case IDBTransactionMode::Versionchange:
+ // Version change transactions should never be scheduled in the traditional manner.
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+
+ // If we didn't defer the currentTransaction above, it can be run now.
+ if (currentTransaction)
+ break;
+ }
+
+ hadDeferredTransactions = !deferredTransactions.isEmpty();
+ if (!hadDeferredTransactions)
+ return currentTransaction;
+
+ // Prepend the deferred transactions back on the beginning of the deque for future scheduling passes.
+ while (!deferredTransactions.isEmpty())
+ m_pendingTransactions.prepend(deferredTransactions.takeLast());
+
+ return currentTransaction;
+}
+
+void UniqueIDBDatabase::transactionCompleted(RefPtr<UniqueIDBDatabaseTransaction>&& transaction)
+{
+ ASSERT(transaction);
+ ASSERT(!m_inProgressTransactions.contains(transaction->info().identifier()));
+ ASSERT(!m_finishingTransactions.contains(transaction->info().identifier()));
+
+ for (auto objectStore : transaction->objectStoreIdentifiers()) {
+ if (!transaction->isReadOnly()) {
+ m_objectStoreWriteTransactions.remove(objectStore);
+ ASSERT(m_objectStoreTransactionCounts.count(objectStore) == 1);
+ }
+ m_objectStoreTransactionCounts.remove(objectStore);
+ }
+
+ if (!transaction->databaseConnection().hasNonFinishedTransactions())
+ m_clientClosePendingDatabaseConnections.remove(&transaction->databaseConnection());
+
+ if (m_versionChangeTransaction == transaction)
+ m_versionChangeTransaction = nullptr;
+
+ // It's possible that this database had its backing store deleted but there were a few outstanding asynchronous operations.
+ // If this transaction completing was the last of those operations, we can finally delete this UniqueIDBDatabase.
+ if (m_clientClosePendingDatabaseConnections.isEmpty() && m_pendingOpenDBRequests.isEmpty() && !m_databaseInfo) {
+ m_server.closeUniqueIDBDatabase(*this);
+ return;
+ }
+
+ // Previously blocked operations might be runnable.
+ if (!m_hardClosedForUserDelete)
+ invokeOperationAndTransactionTimer();
+ else
+ maybeFinishHardClose();
+}
+
+void UniqueIDBDatabase::postDatabaseTask(CrossThreadTask&& task)
+{
+ m_databaseQueue.append([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable {
+ task.performTask();
+ });
+ ++m_queuedTaskCount;
+
+ m_server.postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::executeNextDatabaseTask));
+}
+
+void UniqueIDBDatabase::postDatabaseTaskReply(CrossThreadTask&& task)
+{
+ ASSERT(!isMainThread());
+
+ m_databaseReplyQueue.append([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable {
+ task.performTask();
+ });
+ ++m_queuedTaskCount;
+
+ m_server.postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::executeNextDatabaseTaskReply));
+}
+
+void UniqueIDBDatabase::executeNextDatabaseTask()
+{
+ ASSERT(!isMainThread());
+ ASSERT(m_queuedTaskCount);
+
+ auto task = m_databaseQueue.tryGetMessage();
+ ASSERT(task);
+
+ (*task)();
+ --m_queuedTaskCount;
+
+ // Release the task on the main thread in case it holds the last reference to this,
+ // as UniqueIDBDatabase objects must be deleted on the main thread.
+ callOnMainThread([task = WTFMove(task)] {
+ });
+}
+
+void UniqueIDBDatabase::executeNextDatabaseTaskReply()
+{
+ ASSERT(isMainThread());
+ ASSERT(m_queuedTaskCount);
+
+ auto task = m_databaseReplyQueue.tryGetMessage();
+ ASSERT(task);
+
+ (*task)();
+ --m_queuedTaskCount;
+
+ // If this database was force closed (e.g. for a user delete) and there are no more
+ // cleanup tasks left, delete this.
+ maybeFinishHardClose();
+}
+
+void UniqueIDBDatabase::maybeFinishHardClose()
+{
+ if (m_hardCloseProtector && isDoneWithHardClose()) {
+ callOnMainThread([this] {
+ ASSERT(isDoneWithHardClose());
+ m_hardCloseProtector = nullptr;
+ });
+ }
+}
+
+bool UniqueIDBDatabase::isDoneWithHardClose()
+{
+ return !m_queuedTaskCount && m_clientClosePendingDatabaseConnections.isEmpty() && m_serverClosePendingDatabaseConnections.isEmpty();
+}
+
+static void errorOpenDBRequestForUserDelete(ServerOpenDBRequest& request)
+{
+ auto result = IDBResultData::error(request.requestData().requestIdentifier(), IDBError::userDeleteError());
+ if (request.isOpenRequest())
+ request.connection().didOpenDatabase(result);
+ else
+ request.connection().didDeleteDatabase(result);
+}
+
+void UniqueIDBDatabase::immediateCloseForUserDelete()
+{
+ LOG(IndexedDB, "UniqueIDBDatabase::immediateCloseForUserDelete - Cancelling (%i, %i, %i, %i) callbacks", m_errorCallbacks.size(), m_keyDataCallbacks.size(), m_getResultCallbacks.size(), m_countCallbacks.size());
+
+ // Error out all transactions
+ Vector<IDBResourceIdentifier> inProgressIdentifiers;
+ copyKeysToVector(m_inProgressTransactions, inProgressIdentifiers);
+ for (auto& identifier : inProgressIdentifiers)
+ m_inProgressTransactions.get(identifier)->abortWithoutCallback();
+
+ ASSERT(m_inProgressTransactions.isEmpty());
+
+ m_pendingTransactions.clear();
+ m_objectStoreTransactionCounts.clear();
+ m_objectStoreWriteTransactions.clear();
+
+ // Error out all pending callbacks
+ Vector<uint64_t> callbackIdentifiers;
+ IDBError error = IDBError::userDeleteError();
+ IDBKeyData keyData;
+ IDBGetResult getResult;
+
+ copyKeysToVector(m_errorCallbacks, callbackIdentifiers);
+ for (auto identifier : callbackIdentifiers)
+ performErrorCallback(identifier, error);
+
+ callbackIdentifiers.clear();
+ copyKeysToVector(m_keyDataCallbacks, callbackIdentifiers);
+ for (auto identifier : callbackIdentifiers)
+ performKeyDataCallback(identifier, error, keyData);
+
+ callbackIdentifiers.clear();
+ copyKeysToVector(m_getResultCallbacks, callbackIdentifiers);
+ for (auto identifier : callbackIdentifiers)
+ performGetResultCallback(identifier, error, getResult);
+
+ callbackIdentifiers.clear();
+ copyKeysToVector(m_countCallbacks, callbackIdentifiers);
+ for (auto identifier : callbackIdentifiers)
+ performCountCallback(identifier, error, 0);
+
+ // Error out all IDBOpenDBRequests
+ if (m_currentOpenDBRequest) {
+ errorOpenDBRequestForUserDelete(*m_currentOpenDBRequest);
+ m_currentOpenDBRequest = nullptr;
+ }
+
+ for (auto& request : m_pendingOpenDBRequests)
+ errorOpenDBRequestForUserDelete(*request);
+
+ m_pendingOpenDBRequests.clear();
+
+ // Close all open connections
+ ListHashSet<RefPtr<UniqueIDBDatabaseConnection>> openDatabaseConnections = m_openDatabaseConnections;
+ for (auto& connection : openDatabaseConnections)
+ connectionClosedFromServer(*connection);
+
+ // Cancel the operation timer
+ m_operationAndTransactionTimer.stop();
+
+ // Set up the database to remain alive-but-inert until all of its background activity finishes and all
+ // database connections confirm that they have closed.
+ m_hardClosedForUserDelete = true;
+ m_hardCloseProtector = this;
+
+ // Have the database unconditionally delete itself on the database task queue.
+ postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performUnconditionalDeleteBackingStore));
+
+ // Remove the database from the IDBServer's set of open databases.
+ // If there is no in-progress background thread activity for this database, it will be deleted here.
+ m_server.closeUniqueIDBDatabase(*this);
+}
+
+void UniqueIDBDatabase::performErrorCallback(uint64_t callbackIdentifier, const IDBError& error)
+{
+ auto callback = m_errorCallbacks.take(callbackIdentifier);
+ ASSERT(callback || m_hardClosedForUserDelete);
+ if (callback)
+ callback(error);
+}
+
+void UniqueIDBDatabase::performKeyDataCallback(uint64_t callbackIdentifier, const IDBError& error, const IDBKeyData& resultKey)
+{
+ auto callback = m_keyDataCallbacks.take(callbackIdentifier);
+ ASSERT(callback || m_hardClosedForUserDelete);
+ if (callback)
+ callback(error, resultKey);
+}
+
+void UniqueIDBDatabase::performGetResultCallback(uint64_t callbackIdentifier, const IDBError& error, const IDBGetResult& resultData)
+{
+ auto callback = m_getResultCallbacks.take(callbackIdentifier);
+ ASSERT(callback || m_hardClosedForUserDelete);
+ if (callback)
+ callback(error, resultData);
+}
+
+void UniqueIDBDatabase::performGetAllResultsCallback(uint64_t callbackIdentifier, const IDBError& error, const IDBGetAllResult& resultData)
+{
+ auto callback = m_getAllResultsCallbacks.take(callbackIdentifier);
+ ASSERT(callback || m_hardClosedForUserDelete);
+ if (callback)
+ callback(error, resultData);
+}
+
+void UniqueIDBDatabase::performCountCallback(uint64_t callbackIdentifier, const IDBError& error, uint64_t count)
+{
+ auto callback = m_countCallbacks.take(callbackIdentifier);
+ ASSERT(callback || m_hardClosedForUserDelete);
+ if (callback)
+ callback(error, count);
+}
+
+void UniqueIDBDatabase::forgetErrorCallback(uint64_t callbackIdentifier)
+{
+ ASSERT(m_errorCallbacks.contains(callbackIdentifier));
+ m_errorCallbacks.remove(callbackIdentifier);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h
new file mode 100644
index 000000000..93edced5c
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBBackingStore.h"
+#include "IDBBindingUtilities.h"
+#include "IDBDatabaseIdentifier.h"
+#include "IDBDatabaseInfo.h"
+#include "IDBGetResult.h"
+#include "ServerOpenDBRequest.h"
+#include "ThreadSafeDataBuffer.h"
+#include "Timer.h"
+#include "UniqueIDBDatabaseConnection.h"
+#include "UniqueIDBDatabaseTransaction.h"
+#include <wtf/CrossThreadQueue.h>
+#include <wtf/CrossThreadTask.h>
+#include <wtf/Deque.h>
+#include <wtf/HashCountedSet.h>
+#include <wtf/HashSet.h>
+#include <wtf/ListHashSet.h>
+#include <wtf/Ref.h>
+#include <wtf/ThreadSafeRefCounted.h>
+
+namespace JSC {
+class VM;
+}
+
+namespace WebCore {
+
+class IDBError;
+class IDBGetAllResult;
+class IDBRequestData;
+class IDBTransactionInfo;
+
+enum class IDBGetRecordDataType;
+
+namespace IndexedDB {
+enum class IndexRecordType;
+}
+
+namespace IDBServer {
+
+class IDBConnectionToClient;
+class IDBServer;
+
+typedef std::function<void(const IDBError&)> ErrorCallback;
+typedef std::function<void(const IDBError&, const IDBKeyData&)> KeyDataCallback;
+typedef std::function<void(const IDBError&, const IDBGetResult&)> GetResultCallback;
+typedef std::function<void(const IDBError&, const IDBGetAllResult&)> GetAllResultsCallback;
+typedef std::function<void(const IDBError&, uint64_t)> CountCallback;
+
+class UniqueIDBDatabase : public ThreadSafeRefCounted<UniqueIDBDatabase> {
+public:
+ static Ref<UniqueIDBDatabase> create(IDBServer& server, const IDBDatabaseIdentifier& identifier)
+ {
+ return adoptRef(*new UniqueIDBDatabase(server, identifier));
+ }
+
+ WEBCORE_EXPORT ~UniqueIDBDatabase();
+
+ void openDatabaseConnection(IDBConnectionToClient&, const IDBRequestData&);
+
+ const IDBDatabaseInfo& info() const;
+ IDBServer& server() { return m_server; }
+ const IDBDatabaseIdentifier& identifier() const { return m_identifier; }
+
+ void createObjectStore(UniqueIDBDatabaseTransaction&, const IDBObjectStoreInfo&, ErrorCallback);
+ void deleteObjectStore(UniqueIDBDatabaseTransaction&, const String& objectStoreName, ErrorCallback);
+ void renameObjectStore(UniqueIDBDatabaseTransaction&, uint64_t objectStoreIdentifier, const String& newName, ErrorCallback);
+ void clearObjectStore(UniqueIDBDatabaseTransaction&, uint64_t objectStoreIdentifier, ErrorCallback);
+ void createIndex(UniqueIDBDatabaseTransaction&, const IDBIndexInfo&, ErrorCallback);
+ void deleteIndex(UniqueIDBDatabaseTransaction&, uint64_t objectStoreIdentifier, const String& indexName, ErrorCallback);
+ void renameIndex(UniqueIDBDatabaseTransaction&, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName, ErrorCallback);
+ void putOrAdd(const IDBRequestData&, const IDBKeyData&, const IDBValue&, IndexedDB::ObjectStoreOverwriteMode, KeyDataCallback);
+ void getRecord(const IDBRequestData&, const IDBGetRecordData&, GetResultCallback);
+ void getAllRecords(const IDBRequestData&, const IDBGetAllRecordsData&, GetAllResultsCallback);
+ void getCount(const IDBRequestData&, const IDBKeyRangeData&, CountCallback);
+ void deleteRecord(const IDBRequestData&, const IDBKeyRangeData&, ErrorCallback);
+ void openCursor(const IDBRequestData&, const IDBCursorInfo&, GetResultCallback);
+ void iterateCursor(const IDBRequestData&, const IDBIterateCursorData&, GetResultCallback);
+ void commitTransaction(UniqueIDBDatabaseTransaction&, ErrorCallback);
+ void abortTransaction(UniqueIDBDatabaseTransaction&, ErrorCallback);
+ void didFinishHandlingVersionChange(UniqueIDBDatabaseConnection&, const IDBResourceIdentifier& transactionIdentifier);
+ void transactionDestroyed(UniqueIDBDatabaseTransaction&);
+ void connectionClosedFromClient(UniqueIDBDatabaseConnection&);
+ void confirmConnectionClosedOnServer(UniqueIDBDatabaseConnection&);
+ void didFireVersionChangeEvent(UniqueIDBDatabaseConnection&, const IDBResourceIdentifier& requestIdentifier);
+ void openDBRequestCancelled(const IDBResourceIdentifier& requestIdentifier);
+ void confirmDidCloseFromServer(UniqueIDBDatabaseConnection&);
+
+ void enqueueTransaction(Ref<UniqueIDBDatabaseTransaction>&&);
+
+ void handleDelete(IDBConnectionToClient&, const IDBRequestData&);
+ void immediateCloseForUserDelete();
+
+ static JSC::VM& databaseThreadVM();
+ static JSC::ExecState& databaseThreadExecState();
+
+ bool hardClosedForUserDelete() const { return m_hardClosedForUserDelete; }
+
+private:
+ UniqueIDBDatabase(IDBServer&, const IDBDatabaseIdentifier&);
+
+ void handleDatabaseOperations();
+ void handleCurrentOperation();
+ void performCurrentOpenOperation();
+ void performCurrentDeleteOperation();
+ void addOpenDatabaseConnection(Ref<UniqueIDBDatabaseConnection>&&);
+ bool hasAnyOpenConnections() const;
+ bool allConnectionsAreClosedOrClosing() const;
+
+ void startVersionChangeTransaction();
+ void maybeNotifyConnectionsOfVersionChange();
+ void notifyCurrentRequestConnectionClosedOrFiredVersionChangeEvent(uint64_t connectionIdentifier);
+ bool isVersionChangeInProgress();
+
+ void activateTransactionInBackingStore(UniqueIDBDatabaseTransaction&);
+ void transactionCompleted(RefPtr<UniqueIDBDatabaseTransaction>&&);
+
+ void connectionClosedFromServer(UniqueIDBDatabaseConnection&);
+
+ // Database thread operations
+ void deleteBackingStore(const IDBDatabaseIdentifier&);
+ void openBackingStore(const IDBDatabaseIdentifier&);
+ void performCommitTransaction(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier);
+ void performAbortTransaction(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier);
+ void beginTransactionInBackingStore(const IDBTransactionInfo&);
+ void performCreateObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&);
+ void performDeleteObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier);
+ void performRenameObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const String& newName);
+ void performClearObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier);
+ void performCreateIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBIndexInfo&);
+ void performDeleteIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier);
+ void performRenameIndex(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName);
+ void performPutOrAdd(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData&, const IDBValue&, IndexedDB::ObjectStoreOverwriteMode);
+ void performGetRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&, IDBGetRecordDataType);
+ void performGetAllRecords(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBGetAllRecordsData&);
+ void performGetIndexRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, IndexedDB::IndexRecordType, const IDBKeyRangeData&);
+ void performGetCount(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const IDBKeyRangeData&);
+ void performDeleteRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyRangeData&);
+ void performOpenCursor(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBCursorInfo&);
+ void performIterateCursor(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier, const IDBIterateCursorData&);
+ void performPrefetchCursor(const IDBResourceIdentifier& transactionIdentifier, const IDBResourceIdentifier& cursorIdentifier);
+
+ void performActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBTransactionInfo&);
+ void performUnconditionalDeleteBackingStore();
+
+ // Main thread callbacks
+ void didDeleteBackingStore(uint64_t deletedVersion);
+ void didOpenBackingStore(const IDBDatabaseInfo&, const IDBError&);
+ void didPerformCreateObjectStore(uint64_t callbackIdentifier, const IDBError&, const IDBObjectStoreInfo&);
+ void didPerformDeleteObjectStore(uint64_t callbackIdentifier, const IDBError&, uint64_t objectStoreIdentifier);
+ void didPerformRenameObjectStore(uint64_t callbackIdentifier, const IDBError&, uint64_t objectStoreIdentifier, const String& newName);
+ void didPerformClearObjectStore(uint64_t callbackIdentifier, const IDBError&);
+ void didPerformCreateIndex(uint64_t callbackIdentifier, const IDBError&, const IDBIndexInfo&);
+ void didPerformDeleteIndex(uint64_t callbackIdentifier, const IDBError&, uint64_t objectStoreIdentifier, uint64_t indexIdentifier);
+ void didPerformRenameIndex(uint64_t callbackIdentifier, const IDBError&, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName);
+ void didPerformPutOrAdd(uint64_t callbackIdentifier, const IDBError&, const IDBKeyData&);
+ void didPerformGetRecord(uint64_t callbackIdentifier, const IDBError&, const IDBGetResult&);
+ void didPerformGetAllRecords(uint64_t callbackIdentifier, const IDBError&, const IDBGetAllResult&);
+ void didPerformGetCount(uint64_t callbackIdentifier, const IDBError&, uint64_t);
+ void didPerformDeleteRecord(uint64_t callbackIdentifier, const IDBError&);
+ void didPerformOpenCursor(uint64_t callbackIdentifier, const IDBError&, const IDBGetResult&);
+ void didPerformIterateCursor(uint64_t callbackIdentifier, const IDBError&, const IDBGetResult&);
+ void didPerformCommitTransaction(uint64_t callbackIdentifier, const IDBError&, const IDBResourceIdentifier& transactionIdentifier);
+ void didPerformAbortTransaction(uint64_t callbackIdentifier, const IDBError&, const IDBResourceIdentifier& transactionIdentifier);
+ void didPerformActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBError&);
+ void didPerformUnconditionalDeleteBackingStore();
+
+ uint64_t storeCallbackOrFireError(ErrorCallback);
+ uint64_t storeCallbackOrFireError(KeyDataCallback);
+ uint64_t storeCallbackOrFireError(GetAllResultsCallback);
+ uint64_t storeCallbackOrFireError(GetResultCallback);
+ uint64_t storeCallbackOrFireError(CountCallback);
+
+ void performErrorCallback(uint64_t callbackIdentifier, const IDBError&);
+ void performKeyDataCallback(uint64_t callbackIdentifier, const IDBError&, const IDBKeyData&);
+ void performGetResultCallback(uint64_t callbackIdentifier, const IDBError&, const IDBGetResult&);
+ void performGetAllResultsCallback(uint64_t callbackIdentifier, const IDBError&, const IDBGetAllResult&);
+ void performCountCallback(uint64_t callbackIdentifier, const IDBError&, uint64_t);
+
+ void forgetErrorCallback(uint64_t callbackIdentifier);
+
+ bool hasAnyPendingCallbacks() const;
+ bool isCurrentlyInUse() const;
+ bool hasUnfinishedTransactions() const;
+
+ void invokeOperationAndTransactionTimer();
+ void operationAndTransactionTimerFired();
+ RefPtr<UniqueIDBDatabaseTransaction> takeNextRunnableTransaction(bool& hadDeferredTransactions);
+
+ bool prepareToFinishTransaction(UniqueIDBDatabaseTransaction&);
+
+ void postDatabaseTask(CrossThreadTask&&);
+ void postDatabaseTaskReply(CrossThreadTask&&);
+ void executeNextDatabaseTask();
+ void executeNextDatabaseTaskReply();
+
+ void maybeFinishHardClose();
+ bool isDoneWithHardClose();
+
+ IDBServer& m_server;
+ IDBDatabaseIdentifier m_identifier;
+
+ ListHashSet<RefPtr<ServerOpenDBRequest>> m_pendingOpenDBRequests;
+ RefPtr<ServerOpenDBRequest> m_currentOpenDBRequest;
+
+ ListHashSet<RefPtr<UniqueIDBDatabaseConnection>> m_openDatabaseConnections;
+ HashSet<RefPtr<UniqueIDBDatabaseConnection>> m_clientClosePendingDatabaseConnections;
+ HashSet<RefPtr<UniqueIDBDatabaseConnection>> m_serverClosePendingDatabaseConnections;
+
+ RefPtr<UniqueIDBDatabaseConnection> m_versionChangeDatabaseConnection;
+ RefPtr<UniqueIDBDatabaseTransaction> m_versionChangeTransaction;
+
+ bool m_isOpeningBackingStore { false };
+ IDBError m_backingStoreOpenError;
+ std::unique_ptr<IDBBackingStore> m_backingStore;
+ std::unique_ptr<IDBDatabaseInfo> m_databaseInfo;
+ std::unique_ptr<IDBDatabaseInfo> m_mostRecentDeletedDatabaseInfo;
+
+ bool m_backingStoreSupportsSimultaneousTransactions { false };
+ bool m_backingStoreIsEphemeral { false };
+
+ HashMap<uint64_t, ErrorCallback> m_errorCallbacks;
+ HashMap<uint64_t, KeyDataCallback> m_keyDataCallbacks;
+ HashMap<uint64_t, GetResultCallback> m_getResultCallbacks;
+ HashMap<uint64_t, GetAllResultsCallback> m_getAllResultsCallbacks;
+ HashMap<uint64_t, CountCallback> m_countCallbacks;
+
+ Timer m_operationAndTransactionTimer;
+
+ Deque<RefPtr<UniqueIDBDatabaseTransaction>> m_pendingTransactions;
+ HashMap<IDBResourceIdentifier, RefPtr<UniqueIDBDatabaseTransaction>> m_inProgressTransactions;
+ HashMap<IDBResourceIdentifier, RefPtr<UniqueIDBDatabaseTransaction>> m_finishingTransactions;
+
+ // The keys into these sets are the object store ID.
+ // These sets help to decide which transactions can be started and which must be deferred.
+ HashCountedSet<uint64_t> m_objectStoreTransactionCounts;
+ HashSet<uint64_t> m_objectStoreWriteTransactions;
+
+ bool m_deleteBackingStoreInProgress { false };
+
+ CrossThreadQueue<Function<void ()>> m_databaseQueue;
+ CrossThreadQueue<Function<void ()>> m_databaseReplyQueue;
+ std::atomic<uint64_t> m_queuedTaskCount { 0 };
+
+ bool m_hardClosedForUserDelete { false };
+ RefPtr<UniqueIDBDatabase> m_hardCloseProtector;
+
+ HashMap<IDBResourceIdentifier, RefPtr<UniqueIDBDatabase>> m_prefetchProtectors;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.cpp b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.cpp
new file mode 100644
index 000000000..694b7171d
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "UniqueIDBDatabaseConnection.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBConnectionToClient.h"
+#include "IDBServer.h"
+#include "IDBTransactionInfo.h"
+#include "Logging.h"
+#include "ServerOpenDBRequest.h"
+#include "UniqueIDBDatabase.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+static uint64_t nextDatabaseConnectionIdentifier()
+{
+ static uint64_t nextIdentifier = 0;
+ return ++nextIdentifier;
+}
+
+Ref<UniqueIDBDatabaseConnection> UniqueIDBDatabaseConnection::create(UniqueIDBDatabase& database, ServerOpenDBRequest& request)
+{
+ return adoptRef(*new UniqueIDBDatabaseConnection(database, request));
+}
+
+UniqueIDBDatabaseConnection::UniqueIDBDatabaseConnection(UniqueIDBDatabase& database, ServerOpenDBRequest& request)
+ : m_identifier(nextDatabaseConnectionIdentifier())
+ , m_database(database)
+ , m_connectionToClient(request.connection())
+ , m_openRequestIdentifier(request.requestData().requestIdentifier())
+{
+ m_database.server().registerDatabaseConnection(*this);
+ m_connectionToClient.registerDatabaseConnection(*this);
+}
+
+UniqueIDBDatabaseConnection::~UniqueIDBDatabaseConnection()
+{
+ m_database.server().unregisterDatabaseConnection(*this);
+ m_connectionToClient.unregisterDatabaseConnection(*this);
+}
+
+bool UniqueIDBDatabaseConnection::hasNonFinishedTransactions() const
+{
+ return !m_transactionMap.isEmpty();
+}
+
+void UniqueIDBDatabaseConnection::abortTransactionWithoutCallback(UniqueIDBDatabaseTransaction& transaction)
+{
+ ASSERT(m_transactionMap.contains(transaction.info().identifier()));
+
+ const auto& transactionIdentifier = transaction.info().identifier();
+ RefPtr<UniqueIDBDatabaseConnection> protectedThis(this);
+
+ m_database.abortTransaction(transaction, [this, protectedThis, transactionIdentifier](const IDBError&) {
+ ASSERT(m_transactionMap.contains(transactionIdentifier));
+ m_transactionMap.remove(transactionIdentifier);
+ });
+}
+
+void UniqueIDBDatabaseConnection::connectionPendingCloseFromClient()
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::connectionPendingCloseFromClient - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+
+ m_closePending = true;
+}
+
+void UniqueIDBDatabaseConnection::connectionClosedFromClient()
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::connectionClosedFromClient - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+
+ m_database.connectionClosedFromClient(*this);
+}
+
+void UniqueIDBDatabaseConnection::confirmDidCloseFromServer()
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::confirmDidCloseFromServer - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+
+ m_database.confirmDidCloseFromServer(*this);
+}
+
+void UniqueIDBDatabaseConnection::didFireVersionChangeEvent(const IDBResourceIdentifier& requestIdentifier)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didFireVersionChangeEvent - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+
+ m_database.didFireVersionChangeEvent(*this, requestIdentifier);
+}
+
+void UniqueIDBDatabaseConnection::didFinishHandlingVersionChange(const IDBResourceIdentifier& transactionIdentifier)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didFinishHandlingVersionChange - %s - %" PRIu64, transactionIdentifier.loggingString().utf8().data(), m_identifier);
+
+ m_database.didFinishHandlingVersionChange(*this, transactionIdentifier);
+}
+
+void UniqueIDBDatabaseConnection::fireVersionChangeEvent(const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion)
+{
+ ASSERT(!m_closePending);
+ m_connectionToClient.fireVersionChangeEvent(*this, requestIdentifier, requestedVersion);
+}
+
+UniqueIDBDatabaseTransaction& UniqueIDBDatabaseConnection::createVersionChangeTransaction(uint64_t newVersion)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::createVersionChangeTransaction - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+ ASSERT(!m_closePending);
+
+ IDBTransactionInfo info = IDBTransactionInfo::versionChange(m_connectionToClient, m_database.info(), newVersion);
+
+ Ref<UniqueIDBDatabaseTransaction> transaction = UniqueIDBDatabaseTransaction::create(*this, info);
+ m_transactionMap.set(transaction->info().identifier(), &transaction.get());
+
+ return transaction.get();
+}
+
+void UniqueIDBDatabaseConnection::establishTransaction(const IDBTransactionInfo& info)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::establishTransaction - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+
+ ASSERT(info.mode() != IDBTransactionMode::Versionchange);
+
+ // No transactions should ever come from the client after the client has already told us
+ // the connection is closing.
+ ASSERT(!m_closePending);
+
+ Ref<UniqueIDBDatabaseTransaction> transaction = UniqueIDBDatabaseTransaction::create(*this, info);
+ m_transactionMap.set(transaction->info().identifier(), &transaction.get());
+ m_database.enqueueTransaction(WTFMove(transaction));
+}
+
+void UniqueIDBDatabaseConnection::didAbortTransaction(UniqueIDBDatabaseTransaction& transaction, const IDBError& error)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didAbortTransaction - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+
+ auto transactionIdentifier = transaction.info().identifier();
+ auto takenTransaction = m_transactionMap.take(transactionIdentifier);
+
+ ASSERT(takenTransaction || m_database.hardClosedForUserDelete());
+ if (takenTransaction)
+ m_connectionToClient.didAbortTransaction(transactionIdentifier, error);
+}
+
+void UniqueIDBDatabaseConnection::didCommitTransaction(UniqueIDBDatabaseTransaction& transaction, const IDBError& error)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didCommitTransaction - %s - %" PRIu64, m_openRequestIdentifier.loggingString().utf8().data(), m_identifier);
+
+ auto transactionIdentifier = transaction.info().identifier();
+
+ ASSERT(m_transactionMap.contains(transactionIdentifier));
+ m_transactionMap.remove(transactionIdentifier);
+
+ m_connectionToClient.didCommitTransaction(transactionIdentifier, error);
+}
+
+void UniqueIDBDatabaseConnection::didCreateObjectStore(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didCreateObjectStore");
+
+ m_connectionToClient.didCreateObjectStore(resultData);
+}
+
+void UniqueIDBDatabaseConnection::didDeleteObjectStore(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didDeleteObjectStore");
+
+ m_connectionToClient.didDeleteObjectStore(resultData);
+}
+
+void UniqueIDBDatabaseConnection::didRenameObjectStore(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didRenameObjectStore");
+
+ m_connectionToClient.didRenameObjectStore(resultData);
+}
+
+void UniqueIDBDatabaseConnection::didClearObjectStore(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didClearObjectStore");
+
+ m_connectionToClient.didClearObjectStore(resultData);
+}
+
+void UniqueIDBDatabaseConnection::didCreateIndex(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didCreateIndex");
+
+ m_connectionToClient.didCreateIndex(resultData);
+}
+
+void UniqueIDBDatabaseConnection::didDeleteIndex(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didDeleteIndex");
+
+ m_connectionToClient.didDeleteIndex(resultData);
+}
+
+void UniqueIDBDatabaseConnection::didRenameIndex(const IDBResultData& resultData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseConnection::didRenameIndex");
+
+ m_connectionToClient.didRenameIndex(resultData);
+}
+
+bool UniqueIDBDatabaseConnection::connectionIsClosing() const
+{
+ return m_closePending;
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.h b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.h
new file mode 100644
index 000000000..27d5884b3
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "UniqueIDBDatabaseTransaction.h"
+#include <wtf/HashMap.h>
+#include <wtf/Ref.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class IDBError;
+class IDBResultData;
+
+namespace IDBServer {
+
+class IDBConnectionToClient;
+class ServerOpenDBRequest;
+class UniqueIDBDatabase;
+class UniqueIDBDatabaseTransaction;
+
+class UniqueIDBDatabaseConnection : public RefCounted<UniqueIDBDatabaseConnection> {
+public:
+ static Ref<UniqueIDBDatabaseConnection> create(UniqueIDBDatabase&, ServerOpenDBRequest&);
+
+ ~UniqueIDBDatabaseConnection();
+
+ uint64_t identifier() const { return m_identifier; }
+ const IDBResourceIdentifier& openRequestIdentifier() { return m_openRequestIdentifier; }
+ UniqueIDBDatabase& database() { return m_database; }
+ IDBConnectionToClient& connectionToClient() { return m_connectionToClient; }
+
+ void connectionPendingCloseFromClient();
+ void connectionClosedFromClient();
+
+ bool closePending() const { return m_closePending; }
+
+ bool hasNonFinishedTransactions() const;
+
+ void fireVersionChangeEvent(const IDBResourceIdentifier& requestIdentifier, uint64_t requestedVersion);
+ UniqueIDBDatabaseTransaction& createVersionChangeTransaction(uint64_t newVersion);
+
+ void establishTransaction(const IDBTransactionInfo&);
+ void didAbortTransaction(UniqueIDBDatabaseTransaction&, const IDBError&);
+ void didCommitTransaction(UniqueIDBDatabaseTransaction&, const IDBError&);
+ void didCreateObjectStore(const IDBResultData&);
+ void didDeleteObjectStore(const IDBResultData&);
+ void didRenameObjectStore(const IDBResultData&);
+ void didClearObjectStore(const IDBResultData&);
+ void didCreateIndex(const IDBResultData&);
+ void didDeleteIndex(const IDBResultData&);
+ void didRenameIndex(const IDBResultData&);
+ void didFireVersionChangeEvent(const IDBResourceIdentifier& requestIdentifier);
+ void didFinishHandlingVersionChange(const IDBResourceIdentifier& transactionIdentifier);
+ void confirmDidCloseFromServer();
+
+ void abortTransactionWithoutCallback(UniqueIDBDatabaseTransaction&);
+
+ bool connectionIsClosing() const;
+
+private:
+ UniqueIDBDatabaseConnection(UniqueIDBDatabase&, ServerOpenDBRequest&);
+
+ uint64_t m_identifier { 0 };
+ UniqueIDBDatabase& m_database;
+ IDBConnectionToClient& m_connectionToClient;
+ IDBResourceIdentifier m_openRequestIdentifier;
+
+ bool m_closePending { false };
+
+ HashMap<IDBResourceIdentifier, RefPtr<UniqueIDBDatabaseTransaction>> m_transactionMap;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.cpp b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.cpp
new file mode 100644
index 000000000..214f8c238
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.cpp
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "UniqueIDBDatabaseTransaction.h"
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBError.h"
+#include "IDBIterateCursorData.h"
+#include "IDBResultData.h"
+#include "IDBServer.h"
+#include "Logging.h"
+#include "UniqueIDBDatabase.h"
+
+namespace WebCore {
+namespace IDBServer {
+
+Ref<UniqueIDBDatabaseTransaction> UniqueIDBDatabaseTransaction::create(UniqueIDBDatabaseConnection& connection, const IDBTransactionInfo& info)
+{
+ return adoptRef(*new UniqueIDBDatabaseTransaction(connection, info));
+}
+
+UniqueIDBDatabaseTransaction::UniqueIDBDatabaseTransaction(UniqueIDBDatabaseConnection& connection, const IDBTransactionInfo& info)
+ : m_databaseConnection(connection)
+ , m_transactionInfo(info)
+{
+ if (m_transactionInfo.mode() == IDBTransactionMode::Versionchange)
+ m_originalDatabaseInfo = std::make_unique<IDBDatabaseInfo>(m_databaseConnection->database().info());
+
+ m_databaseConnection->database().server().registerTransaction(*this);
+}
+
+UniqueIDBDatabaseTransaction::~UniqueIDBDatabaseTransaction()
+{
+ m_databaseConnection->database().transactionDestroyed(*this);
+ m_databaseConnection->database().server().unregisterTransaction(*this);
+}
+
+IDBDatabaseInfo* UniqueIDBDatabaseTransaction::originalDatabaseInfo() const
+{
+ ASSERT(m_transactionInfo.mode() == IDBTransactionMode::Versionchange);
+ return m_originalDatabaseInfo.get();
+}
+
+void UniqueIDBDatabaseTransaction::abort()
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::abort");
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().abortTransaction(*this, [this, protectedThis](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::abort (callback)");
+ m_databaseConnection->didAbortTransaction(*this, error);
+ });
+}
+
+void UniqueIDBDatabaseTransaction::abortWithoutCallback()
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::abortWithoutCallback");
+
+ m_databaseConnection->abortTransactionWithoutCallback(*this);
+}
+
+bool UniqueIDBDatabaseTransaction::isVersionChange() const
+{
+ return m_transactionInfo.mode() == IDBTransactionMode::Versionchange;
+}
+
+bool UniqueIDBDatabaseTransaction::isReadOnly() const
+{
+ return m_transactionInfo.mode() == IDBTransactionMode::Readonly;
+}
+
+void UniqueIDBDatabaseTransaction::commit()
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::commit");
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().commitTransaction(*this, [this, protectedThis](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::commit (callback)");
+ m_databaseConnection->didCommitTransaction(*this, error);
+ });
+}
+
+void UniqueIDBDatabaseTransaction::createObjectStore(const IDBRequestData& requestData, const IDBObjectStoreInfo& info)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::createObjectStore");
+
+ ASSERT(isVersionChange());
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().createObjectStore(*this, info, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::createObjectStore (callback)");
+ if (error.isNull())
+ m_databaseConnection->didCreateObjectStore(IDBResultData::createObjectStoreSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->didCreateObjectStore(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::deleteObjectStore(const IDBRequestData& requestData, const String& objectStoreName)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::deleteObjectStore");
+
+ ASSERT(isVersionChange());
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().deleteObjectStore(*this, objectStoreName, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::deleteObjectStore (callback)");
+ if (error.isNull())
+ m_databaseConnection->didDeleteObjectStore(IDBResultData::deleteObjectStoreSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->didDeleteObjectStore(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::renameObjectStore(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::renameObjectStore");
+
+ ASSERT(isVersionChange());
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().renameObjectStore(*this, objectStoreIdentifier, newName, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::renameObjectStore (callback)");
+ if (error.isNull())
+ m_databaseConnection->didRenameObjectStore(IDBResultData::renameObjectStoreSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->didRenameObjectStore(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::clearObjectStore(const IDBRequestData& requestData, uint64_t objectStoreIdentifier)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::clearObjectStore");
+
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().clearObjectStore(*this, objectStoreIdentifier, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::clearObjectStore (callback)");
+ if (error.isNull())
+ m_databaseConnection->didClearObjectStore(IDBResultData::clearObjectStoreSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->didClearObjectStore(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::createIndex(const IDBRequestData& requestData, const IDBIndexInfo& info)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::createIndex");
+
+ ASSERT(isVersionChange());
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().createIndex(*this, info, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::createIndex (callback)");
+ if (error.isNull())
+ m_databaseConnection->didCreateIndex(IDBResultData::createIndexSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->didCreateIndex(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::deleteIndex(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, const String& indexName)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::deleteIndex");
+
+ ASSERT(isVersionChange());
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().deleteIndex(*this, objectStoreIdentifier, indexName, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::createIndex (callback)");
+ if (error.isNull())
+ m_databaseConnection->didDeleteIndex(IDBResultData::deleteIndexSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->didDeleteIndex(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::renameIndex(const IDBRequestData& requestData, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::renameIndex");
+
+ ASSERT(isVersionChange());
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().renameIndex(*this, objectStoreIdentifier, indexIdentifier, newName, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::renameIndex (callback)");
+ if (error.isNull())
+ m_databaseConnection->didRenameIndex(IDBResultData::renameIndexSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->didRenameIndex(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+
+void UniqueIDBDatabaseTransaction::putOrAdd(const IDBRequestData& requestData, const IDBKeyData& keyData, const IDBValue& value, IndexedDB::ObjectStoreOverwriteMode overwriteMode)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::putOrAdd");
+
+ ASSERT(!isReadOnly());
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().putOrAdd(requestData, keyData, value, overwriteMode, [this, protectedThis, requestData](const IDBError& error, const IDBKeyData& key) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::putOrAdd (callback)");
+
+ if (error.isNull())
+ m_databaseConnection->connectionToClient().didPutOrAdd(IDBResultData::putOrAddSuccess(requestData.requestIdentifier(), key));
+ else
+ m_databaseConnection->connectionToClient().didPutOrAdd(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::getRecord(const IDBRequestData& requestData, const IDBGetRecordData& getRecordData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::getRecord");
+
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().getRecord(requestData, getRecordData, [this, protectedThis, requestData](const IDBError& error, const IDBGetResult& result) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::getRecord (callback)");
+
+ if (error.isNull())
+ m_databaseConnection->connectionToClient().didGetRecord(IDBResultData::getRecordSuccess(requestData.requestIdentifier(), result));
+ else
+ m_databaseConnection->connectionToClient().didGetRecord(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::getAllRecords(const IDBRequestData& requestData, const IDBGetAllRecordsData& getAllRecordsData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::getAllRecords");
+
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().getAllRecords(requestData, getAllRecordsData, [this, protectedThis, requestData](const IDBError& error, const IDBGetAllResult& result) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::getAllRecords (callback)");
+
+ if (error.isNull())
+ m_databaseConnection->connectionToClient().didGetAllRecords(IDBResultData::getAllRecordsSuccess(requestData.requestIdentifier(), result));
+ else
+ m_databaseConnection->connectionToClient().didGetAllRecords(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::getCount(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::getCount");
+
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().getCount(requestData, keyRangeData, [this, protectedThis, requestData](const IDBError& error, uint64_t count) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::getCount (callback)");
+
+ if (error.isNull())
+ m_databaseConnection->connectionToClient().didGetCount(IDBResultData::getCountSuccess(requestData.requestIdentifier(), count));
+ else
+ m_databaseConnection->connectionToClient().didGetCount(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::deleteRecord(const IDBRequestData& requestData, const IDBKeyRangeData& keyRangeData)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::deleteRecord");
+
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().deleteRecord(requestData, keyRangeData, [this, protectedThis, requestData](const IDBError& error) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::deleteRecord (callback)");
+
+ if (error.isNull())
+ m_databaseConnection->connectionToClient().didDeleteRecord(IDBResultData::deleteRecordSuccess(requestData.requestIdentifier()));
+ else
+ m_databaseConnection->connectionToClient().didDeleteRecord(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::openCursor(const IDBRequestData& requestData, const IDBCursorInfo& info)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::openCursor");
+
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().openCursor(requestData, info, [this, protectedThis, requestData](const IDBError& error, const IDBGetResult& result) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::openCursor (callback)");
+
+ if (error.isNull())
+ m_databaseConnection->connectionToClient().didOpenCursor(IDBResultData::openCursorSuccess(requestData.requestIdentifier(), result));
+ else
+ m_databaseConnection->connectionToClient().didOpenCursor(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+void UniqueIDBDatabaseTransaction::iterateCursor(const IDBRequestData& requestData, const IDBIterateCursorData& data)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::iterateCursor");
+
+ ASSERT(m_transactionInfo.identifier() == requestData.transactionIdentifier());
+
+ RefPtr<UniqueIDBDatabaseTransaction> protectedThis(this);
+ m_databaseConnection->database().iterateCursor(requestData, data, [this, protectedThis, requestData](const IDBError& error, const IDBGetResult& result) {
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::iterateCursor (callback)");
+
+ if (error.isNull())
+ m_databaseConnection->connectionToClient().didIterateCursor(IDBResultData::iterateCursorSuccess(requestData.requestIdentifier(), result));
+ else
+ m_databaseConnection->connectionToClient().didIterateCursor(IDBResultData::error(requestData.requestIdentifier(), error));
+ });
+}
+
+const Vector<uint64_t>& UniqueIDBDatabaseTransaction::objectStoreIdentifiers()
+{
+ if (!m_objectStoreIdentifiers.isEmpty())
+ return m_objectStoreIdentifiers;
+
+ auto& info = m_databaseConnection->database().info();
+ for (auto objectStoreName : info.objectStoreNames()) {
+ auto objectStoreInfo = info.infoForExistingObjectStore(objectStoreName);
+ ASSERT(objectStoreInfo);
+ if (!objectStoreInfo)
+ continue;
+
+ if (m_transactionInfo.objectStores().contains(objectStoreName))
+ m_objectStoreIdentifiers.append(objectStoreInfo->identifier());
+ }
+
+ return m_objectStoreIdentifiers;
+}
+
+void UniqueIDBDatabaseTransaction::didActivateInBackingStore(const IDBError& error)
+{
+ LOG(IndexedDB, "UniqueIDBDatabaseTransaction::didActivateInBackingStore");
+
+ m_databaseConnection->connectionToClient().didStartTransaction(m_transactionInfo.identifier(), error);
+}
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)
diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.h b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.h
new file mode 100644
index 000000000..511d57124
--- /dev/null
+++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(INDEXED_DATABASE)
+
+#include "IDBTransactionInfo.h"
+#include "UniqueIDBDatabaseConnection.h"
+#include <wtf/Ref.h>
+#include <wtf/RefCounted.h>
+
+namespace WebCore {
+
+class IDBCursorInfo;
+class IDBDatabaseInfo;
+class IDBError;
+class IDBIndexInfo;
+class IDBKeyData;
+class IDBObjectStoreInfo;
+class IDBRequestData;
+class IDBValue;
+
+struct IDBGetAllRecordsData;
+struct IDBGetRecordData;
+struct IDBIterateCursorData;
+struct IDBKeyRangeData;
+
+namespace IDBServer {
+
+class UniqueIDBDatabaseConnection;
+
+class UniqueIDBDatabaseTransaction : public RefCounted<UniqueIDBDatabaseTransaction> {
+public:
+ static Ref<UniqueIDBDatabaseTransaction> create(UniqueIDBDatabaseConnection&, const IDBTransactionInfo&);
+
+ ~UniqueIDBDatabaseTransaction();
+
+ UniqueIDBDatabaseConnection& databaseConnection() { return m_databaseConnection.get(); }
+ const IDBTransactionInfo& info() const { return m_transactionInfo; }
+ bool isVersionChange() const;
+ bool isReadOnly() const;
+
+ IDBDatabaseInfo* originalDatabaseInfo() const;
+
+ void abort();
+ void abortWithoutCallback();
+ void commit();
+
+ void createObjectStore(const IDBRequestData&, const IDBObjectStoreInfo&);
+ void deleteObjectStore(const IDBRequestData&, const String& objectStoreName);
+ void renameObjectStore(const IDBRequestData&, uint64_t objectStoreIdentifier, const String& newName);
+ void clearObjectStore(const IDBRequestData&, uint64_t objectStoreIdentifier);
+ void createIndex(const IDBRequestData&, const IDBIndexInfo&);
+ void deleteIndex(const IDBRequestData&, uint64_t objectStoreIdentifier, const String& indexName);
+ void renameIndex(const IDBRequestData&, uint64_t objectStoreIdentifier, uint64_t indexIdentifier, const String& newName);
+ void putOrAdd(const IDBRequestData&, const IDBKeyData&, const IDBValue&, IndexedDB::ObjectStoreOverwriteMode);
+ void getRecord(const IDBRequestData&, const IDBGetRecordData&);
+ void getAllRecords(const IDBRequestData&, const IDBGetAllRecordsData&);
+ void getCount(const IDBRequestData&, const IDBKeyRangeData&);
+ void deleteRecord(const IDBRequestData&, const IDBKeyRangeData&);
+ void openCursor(const IDBRequestData&, const IDBCursorInfo&);
+ void iterateCursor(const IDBRequestData&, const IDBIterateCursorData&);
+
+ void didActivateInBackingStore(const IDBError&);
+
+ const Vector<uint64_t>& objectStoreIdentifiers();
+
+private:
+ UniqueIDBDatabaseTransaction(UniqueIDBDatabaseConnection&, const IDBTransactionInfo&);
+
+ Ref<UniqueIDBDatabaseConnection> m_databaseConnection;
+ IDBTransactionInfo m_transactionInfo;
+
+ std::unique_ptr<IDBDatabaseInfo> m_originalDatabaseInfo;
+
+ Vector<uint64_t> m_objectStoreIdentifiers;
+};
+
+} // namespace IDBServer
+} // namespace WebCore
+
+#endif // ENABLE(INDEXED_DATABASE)