1/*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25#include "config.h"
26#include "SQLiteIDBCursor.h"
27
28#if ENABLE(INDEXED_DATABASE) && ENABLE(DATABASE_PROCESS)
29
30#include "IDBSerialization.h"
31#include "Logging.h"
32#include "SQLiteIDBTransaction.h"
33#include <WebCore/SQLiteStatement.h>
34#include <WebCore/SQLiteTransaction.h>
35#include <sqlite3.h>
36
37using namespace WebCore;
38
39namespace WebKit {
40
41std::unique_ptr<SQLiteIDBCursor> SQLiteIDBCursor::maybeCreate(SQLiteIDBTransaction* transaction, const IDBIdentifier& cursorIdentifier, int64_t objectStoreID, int64_t indexID, IndexedDB::CursorDirection cursorDirection, IndexedDB::CursorType cursorType, IDBDatabaseBackend::TaskType taskType, const IDBKeyRangeData& keyRange)
42{
43    auto cursor = std::unique_ptr<SQLiteIDBCursor>(new SQLiteIDBCursor(transaction, cursorIdentifier, objectStoreID, indexID, cursorDirection, cursorType, taskType, keyRange));
44
45    if (!cursor->establishStatement())
46        return nullptr;
47
48    if (!cursor->advance(1))
49        return nullptr;
50
51    return cursor;
52}
53
54SQLiteIDBCursor::SQLiteIDBCursor(SQLiteIDBTransaction* transaction, const IDBIdentifier& cursorIdentifier, int64_t objectStoreID, int64_t indexID, IndexedDB::CursorDirection cursorDirection, IndexedDB::CursorType, IDBDatabaseBackend::TaskType, const IDBKeyRangeData& keyRange)
55    : m_transaction(transaction)
56    , m_cursorIdentifier(cursorIdentifier)
57    , m_objectStoreID(objectStoreID)
58    , m_indexID(indexID)
59    , m_cursorDirection(cursorDirection)
60    , m_keyRange(keyRange)
61    , m_currentRecordID(-1)
62    , m_statementNeedsReset(false)
63    , m_boundID(0)
64    , m_completed(false)
65    , m_errored(false)
66{
67    ASSERT(m_objectStoreID);
68}
69
70static const String& getIndexStatement(bool hasLowerKey, bool isLowerOpen, bool hasUpperKey, bool isUpperOpen, bool descending, bool unique)
71{
72    DEPRECATED_DEFINE_STATIC_LOCAL(Vector<String>, indexStatements, ());
73
74    if (indexStatements.isEmpty()) {
75        indexStatements.reserveInitialCapacity(12);
76
77        // Lower missing/open, upper missing/open.
78        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key, value;"));
79        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC, value DESC;"));
80        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC, value;"));
81
82        // Lower missing/open, upper closed.
83        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key, value;"));
84        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC, value DESC;"));
85        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC, value;"));
86
87        // Lower closed, upper missing/open.
88        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key, value;"));
89        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC, value DESC;"));
90        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC, value;"));
91
92        // Lower closed, upper closed.
93        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key, value;"));
94        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC, value DESC;"));
95        indexStatements.uncheckedAppend(ASCIILiteral("SELECT rowid, key, value FROM IndexRecords WHERE indexID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC, value;"));
96    }
97
98    size_t i = 0;
99
100    if (hasLowerKey && !isLowerOpen)
101        i += 6;
102
103    if (hasUpperKey && !isUpperOpen)
104        i += 3;
105
106    if (descending) {
107        if (!unique)
108            i += 1;
109        else
110            i += 2;
111    }
112
113    return indexStatements[i];
114}
115
116static const String& getObjectStoreStatement(bool hasLowerKey, bool isLowerOpen, bool hasUpperKey, bool isUpperOpen, bool descending)
117{
118    DEPRECATED_DEFINE_STATIC_LOCAL(Vector<String>, statements, ());
119
120    if (statements.isEmpty()) {
121        statements.reserveCapacity(8);
122
123        // Lower missing/open, upper missing/open.
124        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
125        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
126
127        // Lower missing/open, upper closed.
128        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
129        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key > CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
130
131        // Lower closed, upper missing/open.
132        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key;"));
133        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key < CAST(? AS TEXT) ORDER BY key DESC;"));
134
135        // Lower closed, upper closed.
136        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key;"));
137        statements.append(ASCIILiteral("SELECT rowid, key, value FROM Records WHERE objectStoreID = ? AND key >= CAST(? AS TEXT) AND key <= CAST(? AS TEXT) ORDER BY key DESC;"));
138    }
139
140    size_t i = 0;
141
142    if (hasLowerKey && !isLowerOpen)
143        i += 4;
144
145    if (hasUpperKey && !isUpperOpen)
146        i += 2;
147
148    if (descending)
149        i += 1;
150
151    return statements[i];
152}
153
154bool SQLiteIDBCursor::establishStatement()
155{
156    ASSERT(!m_statement);
157    String sql;
158
159    if (m_indexID != IDBIndexMetadata::InvalidId) {
160        sql = getIndexStatement(!m_keyRange.lowerKey.isNull, m_keyRange.lowerOpen, !m_keyRange.upperKey.isNull, m_keyRange.upperOpen, m_cursorDirection == IndexedDB::CursorDirection::Prev || m_cursorDirection == IndexedDB::CursorDirection::PrevNoDuplicate, m_cursorDirection == IndexedDB::CursorDirection::NextNoDuplicate || m_cursorDirection == IndexedDB::CursorDirection::PrevNoDuplicate);
161        m_boundID = m_indexID;
162    } else {
163        sql = getObjectStoreStatement(!m_keyRange.lowerKey.isNull, m_keyRange.lowerOpen, !m_keyRange.upperKey.isNull, m_keyRange.upperOpen, m_cursorDirection == IndexedDB::CursorDirection::Prev || m_cursorDirection == IndexedDB::CursorDirection::PrevNoDuplicate);
164        m_boundID = m_objectStoreID;
165    }
166
167    m_currentLowerKey = m_keyRange.lowerKey.isNull ? IDBKeyData::minimum() : m_keyRange.lowerKey;
168    m_currentUpperKey = m_keyRange.upperKey.isNull ? IDBKeyData::maximum() : m_keyRange.upperKey;
169
170    return createSQLiteStatement(sql);
171}
172
173bool SQLiteIDBCursor::createSQLiteStatement(const String& sql)
174{
175    LOG(IDB, "Creating cursor with SQL query: \"%s\"", sql.utf8().data());
176
177    ASSERT(!m_currentLowerKey.isNull);
178    ASSERT(!m_currentUpperKey.isNull);
179    ASSERT(m_transaction->sqliteTransaction());
180
181    m_statement = std::make_unique<SQLiteStatement>(m_transaction->sqliteTransaction()->database(), sql);
182
183    if (m_statement->prepare() != SQLResultOk) {
184        LOG_ERROR("Could not create cursor statement (prepare/id) - '%s'", m_transaction->sqliteTransaction()->database().lastErrorMsg());
185        return false;
186    }
187
188    return bindArguments();
189}
190
191void SQLiteIDBCursor::objectStoreRecordsChanged()
192{
193    // If ObjectStore or Index contents changed, we need to reset the statement and bind new parameters to it.
194    // This is to pick up any changes that might exist.
195
196    m_statementNeedsReset = true;
197}
198
199void SQLiteIDBCursor::resetAndRebindStatement()
200{
201    ASSERT(!m_currentLowerKey.isNull);
202    ASSERT(!m_currentUpperKey.isNull);
203    ASSERT(m_transaction->sqliteTransaction());
204    ASSERT(m_statement);
205    ASSERT(m_statementNeedsReset);
206
207    m_statementNeedsReset = false;
208
209    // If this cursor never fetched any records, we don't need to reset the statement.
210    if (m_currentKey.isNull)
211        return;
212
213    // Otherwise update the lower key or upper key used for the cursor range.
214    // This is so the cursor can pick up where we left off.
215    if (m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::NextNoDuplicate)
216        m_currentLowerKey = m_currentKey;
217    else
218        m_currentUpperKey = m_currentKey;
219
220    if (m_statement->reset() != SQLResultOk) {
221        LOG_ERROR("Could not reset cursor statement to respond to object store changes");
222        return;
223    }
224
225    bindArguments();
226}
227
228bool SQLiteIDBCursor::bindArguments()
229{
230    if (m_statement->bindInt64(1, m_boundID) != SQLResultOk) {
231        LOG_ERROR("Could not bind id argument (bound ID)");
232        return false;
233    }
234
235    RefPtr<SharedBuffer> buffer = serializeIDBKeyData(m_currentLowerKey);
236    if (m_statement->bindBlob(2, buffer->data(), buffer->size()) != SQLResultOk) {
237        LOG_ERROR("Could not create cursor statement (lower key)");
238        return false;
239    }
240
241    buffer = serializeIDBKeyData(m_currentUpperKey);
242    if (m_statement->bindBlob(3, buffer->data(), buffer->size()) != SQLResultOk) {
243        LOG_ERROR("Could not create cursor statement (upper key)");
244        return false;
245    }
246
247    return true;
248}
249
250bool SQLiteIDBCursor::advance(uint64_t count)
251{
252    bool isUnique = m_cursorDirection == IndexedDB::CursorDirection::NextNoDuplicate || m_cursorDirection == IndexedDB::CursorDirection::PrevNoDuplicate;
253
254    for (uint64_t i = 0; i < count; ++i) {
255        if (!isUnique) {
256            if (!advanceOnce())
257                return false;
258        } else {
259            if (!advanceUnique())
260                return false;
261        }
262    }
263
264    return true;
265}
266
267bool SQLiteIDBCursor::advanceUnique()
268{
269    IDBKeyData currentKey = m_currentKey;
270
271    while (!m_completed) {
272        if (!advanceOnce())
273            return false;
274
275        // If the new current key is different from the old current key, we're done.
276        if (currentKey.compare(m_currentKey))
277            return true;
278    }
279
280    return false;
281}
282
283bool SQLiteIDBCursor::advanceOnce()
284{
285    if (m_statementNeedsReset)
286        resetAndRebindStatement();
287
288    AdvanceResult result;
289    do {
290        result = internalAdvanceOnce();
291    } while (result == AdvanceResult::ShouldAdvanceAgain);
292
293    return result == AdvanceResult::Success;
294}
295
296SQLiteIDBCursor::AdvanceResult SQLiteIDBCursor::internalAdvanceOnce()
297{
298    ASSERT(m_transaction->sqliteTransaction());
299    ASSERT(m_statement);
300
301    if (m_completed) {
302        LOG_ERROR("Attempt to advance a completed cursor");
303        return AdvanceResult::Failure;
304    }
305
306    int result = m_statement->step();
307    if (result == SQLResultDone) {
308        m_completed = true;
309
310        // When a cursor reaches its end, that is indicated by having undefined keys/values
311        m_currentKey = IDBKeyData();
312        m_currentPrimaryKey = IDBKeyData();
313        m_currentValueBuffer.clear();
314
315        return AdvanceResult::Success;
316    }
317
318    if (result != SQLResultRow) {
319        LOG_ERROR("Error advancing cursor - (%i) %s", result, m_transaction->sqliteTransaction()->database().lastErrorMsg());
320        m_completed = true;
321        m_errored = true;
322        return AdvanceResult::Failure;
323    }
324
325    int64_t recordID = m_statement->getColumnInt64(0);
326
327    // If the recordID of the record just fetched is the same as the current record ID
328    // then this statement must have been re-prepared in response to an object store change.
329    // We don't want to re-use the current record so we'll move on to the next one.
330    if (recordID == m_currentRecordID)
331        return AdvanceResult::ShouldAdvanceAgain;
332
333    m_currentRecordID = recordID;
334
335    Vector<uint8_t> keyData;
336    m_statement->getColumnBlobAsVector(1, keyData);
337
338    if (!deserializeIDBKeyData(keyData.data(), keyData.size(), m_currentKey)) {
339        LOG_ERROR("Unable to deserialize key data from database while advancing cursor");
340        m_completed = true;
341        m_errored = true;
342        return AdvanceResult::Failure;
343    }
344
345    m_statement->getColumnBlobAsVector(2, keyData);
346    m_currentValueBuffer = keyData;
347
348    // The primaryKey of an ObjectStore cursor is the same as its key.
349    if (m_indexID == IDBIndexMetadata::InvalidId)
350        m_currentPrimaryKey = m_currentKey;
351    else {
352        if (!deserializeIDBKeyData(keyData.data(), keyData.size(), m_currentPrimaryKey)) {
353            LOG_ERROR("Unable to deserialize value data from database while advancing index cursor");
354            m_completed = true;
355            m_errored = true;
356            return AdvanceResult::Failure;
357        }
358
359        SQLiteStatement objectStoreStatement(*m_statement->database(), "SELECT value FROM Records WHERE key = CAST(? AS TEXT) and objectStoreID = ?;");
360
361        if (objectStoreStatement.prepare() != SQLResultOk
362            || objectStoreStatement.bindBlob(1, m_currentValueBuffer.data(), m_currentValueBuffer.size()) != SQLResultOk
363            || objectStoreStatement.bindInt64(2, m_objectStoreID) != SQLResultOk) {
364            LOG_ERROR("Could not create index cursor statement into object store records (%i) '%s'", m_statement->database()->lastError(), m_statement->database()->lastErrorMsg());
365            m_completed = true;
366            m_errored = true;
367            return AdvanceResult::Failure;
368        }
369
370        int result = objectStoreStatement.step();
371
372        if (result == SQLResultRow)
373            objectStoreStatement.getColumnBlobAsVector(0, m_currentValueBuffer);
374        else if (result == SQLResultDone) {
375            // This indicates that the record we're trying to retrieve has been removed from the object store.
376            // Skip over it.
377            return AdvanceResult::ShouldAdvanceAgain;
378        } else {
379            LOG_ERROR("Could not step index cursor statement into object store records (%i) '%s'", m_statement->database()->lastError(), m_statement->database()->lastErrorMsg());
380            m_completed = true;
381            m_errored = true;
382            return AdvanceResult::Failure;
383
384        }
385    }
386
387    return AdvanceResult::Success;
388}
389
390bool SQLiteIDBCursor::iterate(const WebCore::IDBKeyData& targetKey)
391{
392    ASSERT(m_transaction->sqliteTransaction());
393    ASSERT(m_statement);
394
395    bool result = advance(1);
396
397    // Iterating with no key is equivalent to advancing 1 step.
398    if (targetKey.isNull || !result)
399        return result;
400
401    while (!m_completed) {
402        if (!result)
403            return false;
404
405        // 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.
406        if (m_cursorDirection == IndexedDB::CursorDirection::Next || m_cursorDirection == IndexedDB::CursorDirection::NextNoDuplicate) {
407            if (m_currentKey.compare(targetKey) >= 0)
408                break;
409        } else if (m_currentKey.compare(targetKey) <= 0)
410            break;
411
412        result = advance(1);
413    }
414
415    return result;
416}
417
418} // namespace WebKit
419
420#endif // ENABLE(INDEXED_DATABASE) && ENABLE(DATABASE_PROCESS)
421