diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp')
-rw-r--r-- | Source/WebCore/Modules/indexeddb/server/SQLiteIDBCursor.cpp | 580 |
1 files changed, 580 insertions, 0 deletions
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) |