/* * Copyright (C) 2010 Google 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 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 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 "IDBObjectStoreBackendImpl.h" #if ENABLE(INDEXED_DATABASE) #include "CrossThreadTask.h" #include "DOMStringList.h" #include "IDBBindingUtilities.h" #include "IDBCallbacks.h" #include "IDBCursorBackendImpl.h" #include "IDBDatabaseBackendImpl.h" #include "IDBDatabaseException.h" #include "IDBIndexBackendImpl.h" #include "IDBKey.h" #include "IDBKeyPath.h" #include "IDBKeyPathBackendImpl.h" #include "IDBKeyRange.h" #include "IDBTransactionBackendInterface.h" #include "ScriptExecutionContext.h" #include "SQLiteDatabase.h" #include "SQLiteStatement.h" #include "SQLiteTransaction.h" namespace WebCore { IDBObjectStoreBackendImpl::~IDBObjectStoreBackendImpl() { } IDBObjectStoreBackendImpl::IDBObjectStoreBackendImpl(IDBDatabaseBackendImpl* database, int64_t id, const String& name, const String& keyPath, bool autoIncrement) : m_database(database) , m_id(id) , m_name(name) , m_keyPath(keyPath) , m_autoIncrement(autoIncrement) { loadIndexes(); } IDBObjectStoreBackendImpl::IDBObjectStoreBackendImpl(IDBDatabaseBackendImpl* database, const String& name, const String& keyPath, bool autoIncrement) : m_database(database) , m_id(InvalidId) , m_name(name) , m_keyPath(keyPath) , m_autoIncrement(autoIncrement) { } PassRefPtr IDBObjectStoreBackendImpl::indexNames() const { RefPtr indexNames = DOMStringList::create(); for (IndexMap::const_iterator it = m_indexes.begin(); it != m_indexes.end(); ++it) indexNames->append(it->first); return indexNames.release(); } static String whereClause(IDBKey* key) { return "WHERE objectStoreId = ? AND " + key->whereSyntax(); } static void bindWhereClause(SQLiteStatement& query, int64_t id, IDBKey* key) { query.bindInt64(1, id); key->bind(query, 2); } void IDBObjectStoreBackendImpl::get(PassRefPtr prpKey, PassRefPtr prpCallbacks, IDBTransactionBackendInterface* transaction, ExceptionCode& ec) { RefPtr objectStore = this; RefPtr key = prpKey; RefPtr callbacks = prpCallbacks; if (!transaction->scheduleTask(createCallbackTask(&IDBObjectStoreBackendImpl::getInternal, objectStore, key, callbacks))) ec = IDBDatabaseException::NOT_ALLOWED_ERR; } void IDBObjectStoreBackendImpl::getInternal(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr key, PassRefPtr callbacks) { SQLiteStatement query(objectStore->sqliteDatabase(), "SELECT keyString, keyDate, keyNumber, value FROM ObjectStoreData " + whereClause(key.get())); bool ok = query.prepare() == SQLResultOk; ASSERT_UNUSED(ok, ok); // FIXME: Better error handling? bindWhereClause(query, objectStore->id(), key.get()); if (query.step() != SQLResultRow) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::NOT_FOUND_ERR, "Key does not exist in the object store.")); return; } ASSERT((key->type() == IDBKey::StringType) != query.isColumnNull(0)); // FIXME: Implement date. ASSERT((key->type() == IDBKey::NumberType) != query.isColumnNull(2)); callbacks->onSuccess(SerializedScriptValue::createFromWire(query.getColumnText(3))); ASSERT(query.step() != SQLResultRow); } static PassRefPtr fetchKeyFromKeyPath(SerializedScriptValue* value, const String& keyPath) { Vector > values; values.append(value); Vector > keys; IDBKeyPathBackendImpl::createIDBKeysFromSerializedValuesAndKeyPath(values, keyPath, keys); if (keys.isEmpty()) return 0; ASSERT(keys.size() == 1); return keys[0].release(); } static bool putObjectStoreData(SQLiteDatabase& db, IDBKey* key, SerializedScriptValue* value, int64_t objectStoreId, int64_t* dataRowId) { String sql = *dataRowId != -1 ? "UPDATE ObjectStoreData SET keyString = ?, keyDate = ?, keyNumber = ?, value = ? WHERE id = ?" : "INSERT INTO ObjectStoreData (keyString, keyDate, keyNumber, value, objectStoreId) VALUES (?, ?, ?, ?, ?)"; SQLiteStatement query(db, sql); if (query.prepare() != SQLResultOk) return false; key->bindWithNulls(query, 1); query.bindText(4, value->toWireString()); if (*dataRowId != -1) query.bindInt(5, *dataRowId); else query.bindInt64(5, objectStoreId); if (query.step() != SQLResultDone) return false; if (*dataRowId == -1) *dataRowId = db.lastInsertRowID(); return true; } static bool deleteIndexData(SQLiteDatabase& db, int64_t objectStoreDataId) { SQLiteStatement deleteQuery(db, "DELETE FROM IndexData WHERE objectStoreDataId = ?"); if (deleteQuery.prepare() != SQLResultOk) return false; deleteQuery.bindInt64(1, objectStoreDataId); return deleteQuery.step() == SQLResultDone; } static bool putIndexData(SQLiteDatabase& db, IDBKey* key, int64_t indexId, int64_t objectStoreDataId) { SQLiteStatement putQuery(db, "INSERT INTO IndexData (keyString, keyDate, keyNumber, indexId, objectStoreDataId) VALUES (?, ?, ?, ?, ?)"); if (putQuery.prepare() != SQLResultOk) return false; key->bindWithNulls(putQuery, 1); putQuery.bindInt64(4, indexId); putQuery.bindInt64(5, objectStoreDataId); return putQuery.step() == SQLResultDone; } void IDBObjectStoreBackendImpl::put(PassRefPtr prpValue, PassRefPtr prpKey, bool addOnly, PassRefPtr prpCallbacks, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec) { RefPtr objectStore = this; RefPtr value = prpValue; RefPtr key = prpKey; RefPtr callbacks = prpCallbacks; RefPtr transaction = transactionPtr; // FIXME: This should throw a SERIAL_ERR on structured clone problems. // FIXME: This should throw a DATA_ERR when the wrong key/keyPath data is supplied. if (!transaction->scheduleTask(createCallbackTask(&IDBObjectStoreBackendImpl::putInternal, objectStore, value, key, addOnly, callbacks, transaction))) ec = IDBDatabaseException::NOT_ALLOWED_ERR; } void IDBObjectStoreBackendImpl::putInternal(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr prpValue, PassRefPtr prpKey, bool addOnly, PassRefPtr callbacks, PassRefPtr transaction) { RefPtr value = prpValue; RefPtr key = prpKey; // FIXME: Support auto-increment. if (!objectStore->m_keyPath.isNull()) { if (key) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "A key was supplied for an objectStore that has a keyPath.")); return; } key = fetchKeyFromKeyPath(value.get(), objectStore->m_keyPath); if (!key) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "The key could not be fetched from the keyPath.")); return; } } else if (!key) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::DATA_ERR, "No key supplied.")); return; } Vector > indexKeys; for (IndexMap::iterator it = objectStore->m_indexes.begin(); it != objectStore->m_indexes.end(); ++it) { RefPtr key = fetchKeyFromKeyPath(value.get(), it->second->keyPath()); if (!key) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "The key could not be fetched from an index's keyPath.")); return; } if (!it->second->addingKeyAllowed(key.get())) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "One of the derived (from a keyPath) keys for an index does not satisfy its uniqueness requirements.")); return; } indexKeys.append(key.release()); } SQLiteStatement getQuery(objectStore->sqliteDatabase(), "SELECT id FROM ObjectStoreData " + whereClause(key.get())); bool ok = getQuery.prepare() == SQLResultOk; ASSERT_UNUSED(ok, ok); // FIXME: Better error handling? bindWhereClause(getQuery, objectStore->id(), key.get()); bool isExistingValue = getQuery.step() == SQLResultRow; if (addOnly && isExistingValue) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::CONSTRAINT_ERR, "Key already exists in the object store.")); return; } // Before this point, don't do any mutation. After this point, rollback the transaction in case of error. int64_t dataRowId = isExistingValue ? getQuery.getColumnInt(0) : -1; if (!putObjectStoreData(objectStore->sqliteDatabase(), key.get(), value.get(), objectStore->id(), &dataRowId)) { // FIXME: The Indexed Database specification does not have an error code dedicated to I/O errors. callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage.")); transaction->abort(); return; } if (!deleteIndexData(objectStore->sqliteDatabase(), dataRowId)) { // FIXME: The Indexed Database specification does not have an error code dedicated to I/O errors. callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage.")); transaction->abort(); return; } int i = 0; for (IndexMap::iterator it = objectStore->m_indexes.begin(); it != objectStore->m_indexes.end(); ++it, ++i) { if (!putIndexData(objectStore->sqliteDatabase(), indexKeys[i].get(), it->second->id(), dataRowId)) { // FIXME: The Indexed Database specification does not have an error code dedicated to I/O errors. callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage.")); transaction->abort(); return; } } callbacks->onSuccess(key.get()); } void IDBObjectStoreBackendImpl::remove(PassRefPtr prpKey, PassRefPtr prpCallbacks, IDBTransactionBackendInterface* transaction, ExceptionCode& ec) { RefPtr objectStore = this; RefPtr key = prpKey; RefPtr callbacks = prpCallbacks; if (!transaction->scheduleTask(createCallbackTask(&IDBObjectStoreBackendImpl::removeInternal, objectStore, key, callbacks))) ec = IDBDatabaseException::NOT_ALLOWED_ERR; } void IDBObjectStoreBackendImpl::removeInternal(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr key, PassRefPtr callbacks) { SQLiteStatement query(objectStore->sqliteDatabase(), "DELETE FROM ObjectStoreData " + whereClause(key.get())); bool ok = query.prepare() == SQLResultOk; ASSERT_UNUSED(ok, ok); // FIXME: Better error handling? bindWhereClause(query, objectStore->id(), key.get()); if (query.step() != SQLResultDone) { callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::NOT_FOUND_ERR, "Key does not exist in the object store.")); return; } callbacks->onSuccess(); } PassRefPtr IDBObjectStoreBackendImpl::createIndex(const String& name, const String& keyPath, bool unique, IDBTransactionBackendInterface* transaction, ExceptionCode& ec) { if (m_indexes.contains(name)) { ec = IDBDatabaseException::CONSTRAINT_ERR; return 0; } if (transaction->mode() != IDBTransaction::VERSION_CHANGE) { ec = IDBDatabaseException::NOT_ALLOWED_ERR; return 0; } RefPtr index = IDBIndexBackendImpl::create(this, name, keyPath, unique); ASSERT(index->name() == name); RefPtr objectStore = this; RefPtr transactionPtr = transaction; if (!transaction->scheduleTask(createCallbackTask(&IDBObjectStoreBackendImpl::createIndexInternal, objectStore, index, transaction), createCallbackTask(&IDBObjectStoreBackendImpl::removeIndexFromMap, objectStore, index))) { ec = IDBDatabaseException::NOT_ALLOWED_ERR; return 0; } m_indexes.set(name, index); return index.release(); } void IDBObjectStoreBackendImpl::createIndexInternal(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr index, PassRefPtr transaction) { SQLiteStatement insert(objectStore->sqliteDatabase(), "INSERT INTO Indexes (objectStoreId, name, keyPath, isUnique) VALUES (?, ?, ?, ?)"); if (insert.prepare() != SQLResultOk) { transaction->abort(); return; } insert.bindInt64(1, objectStore->m_id); insert.bindText(2, index->name()); insert.bindText(3, index->keyPath()); insert.bindInt(4, static_cast(index->unique())); if (insert.step() != SQLResultDone) { transaction->abort(); return; } int64_t id = objectStore->sqliteDatabase().lastInsertRowID(); index->setId(id); transaction->didCompleteTaskEvents(); } PassRefPtr IDBObjectStoreBackendImpl::index(const String& name, ExceptionCode& ec) { RefPtr index = m_indexes.get(name); if (!index) { ec = IDBDatabaseException::NOT_FOUND_ERR; return 0; } return index.release(); } static void doDelete(SQLiteDatabase& db, const char* sql, int64_t id) { SQLiteStatement deleteQuery(db, sql); bool ok = deleteQuery.prepare() == SQLResultOk; ASSERT_UNUSED(ok, ok); // FIXME: Better error handling. deleteQuery.bindInt64(1, id); ok = deleteQuery.step() == SQLResultDone; ASSERT_UNUSED(ok, ok); // FIXME: Better error handling. } void IDBObjectStoreBackendImpl::removeIndex(const String& name, IDBTransactionBackendInterface* transaction, ExceptionCode& ec) { RefPtr index = m_indexes.get(name); if (!index) { ec = IDBDatabaseException::NOT_FOUND_ERR; return; } RefPtr objectStore = this; RefPtr transactionPtr = transaction; if (!transaction->scheduleTask(createCallbackTask(&IDBObjectStoreBackendImpl::removeIndexInternal, objectStore, index, transactionPtr), createCallbackTask(&IDBObjectStoreBackendImpl::addIndexToMap, objectStore, index))) { ec = IDBDatabaseException::NOT_ALLOWED_ERR; return; } m_indexes.remove(name); } void IDBObjectStoreBackendImpl::removeIndexInternal(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr index, PassRefPtr transaction) { doDelete(objectStore->sqliteDatabase(), "DELETE FROM Indexes WHERE id = ?", index->id()); doDelete(objectStore->sqliteDatabase(), "DELETE FROM IndexData WHERE indexId = ?", index->id()); transaction->didCompleteTaskEvents(); } void IDBObjectStoreBackendImpl::openCursor(PassRefPtr prpRange, unsigned short direction, PassRefPtr prpCallbacks, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec) { RefPtr objectStore = this; RefPtr range = prpRange; RefPtr callbacks = prpCallbacks; RefPtr transaction = transactionPtr; if (!transaction->scheduleTask(createCallbackTask(&IDBObjectStoreBackendImpl::openCursorInternal, objectStore, range, direction, callbacks, transaction))) ec = IDBDatabaseException::NOT_ALLOWED_ERR; } void IDBObjectStoreBackendImpl::openCursorInternal(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr range, unsigned short tmpDirection, PassRefPtr callbacks, PassRefPtr transaction) { // Several files depend on this order of selects. String sql = "SELECT id, keyString, keyDate, keyNumber, value FROM ObjectStoreData WHERE "; if (range->flags() & IDBKeyRange::LEFT_BOUND || range->flags() == IDBKeyRange::SINGLE) sql += range->left()->leftCursorWhereFragment(range->leftWhereClauseComparisonOperator()); if (range->flags() & IDBKeyRange::RIGHT_BOUND || range->flags() == IDBKeyRange::SINGLE) sql += range->right()->rightCursorWhereFragment(range->rightWhereClauseComparisonOperator()); sql += "objectStoreId = ? ORDER BY "; IDBCursor::Direction direction = static_cast(tmpDirection); if (direction == IDBCursor::NEXT || direction == IDBCursor::NEXT_NO_DUPLICATE) sql += "keyString, keyDate, keyNumber"; else sql += "keyString DESC, keyDate DESC, keyNumber DESC"; OwnPtr query = adoptPtr(new SQLiteStatement(objectStore->sqliteDatabase(), sql)); bool ok = query->prepare() == SQLResultOk; ASSERT_UNUSED(ok, ok); // FIXME: Better error handling? int currentColumn = 1; if (range->flags() & IDBKeyRange::LEFT_BOUND || range->flags() == IDBKeyRange::SINGLE) currentColumn += range->left()->bind(*query, currentColumn); if (range->flags() & IDBKeyRange::RIGHT_BOUND || range->flags() == IDBKeyRange::SINGLE) currentColumn += range->right()->bind(*query, currentColumn); query->bindInt64(currentColumn, objectStore->id()); if (query->step() != SQLResultRow) { callbacks->onSuccess(); return; } RefPtr cursor = IDBCursorBackendImpl::create(objectStore, range, direction, query.release(), transaction.get()); callbacks->onSuccess(cursor.release()); } void IDBObjectStoreBackendImpl::loadIndexes() { SQLiteStatement indexQuery(sqliteDatabase(), "SELECT id, name, keyPath, isUnique FROM Indexes WHERE objectStoreId = ?"); bool ok = indexQuery.prepare() == SQLResultOk; ASSERT_UNUSED(ok, ok); // FIXME: Better error handling? indexQuery.bindInt64(1, m_id); while (indexQuery.step() == SQLResultRow) { int64_t id = indexQuery.getColumnInt64(0); String name = indexQuery.getColumnText(1); String keyPath = indexQuery.getColumnText(2); bool unique = !!indexQuery.getColumnInt(3); m_indexes.set(name, IDBIndexBackendImpl::create(this, id, name, keyPath, unique)); } } SQLiteDatabase& IDBObjectStoreBackendImpl::sqliteDatabase() const { return m_database->sqliteDatabase(); } void IDBObjectStoreBackendImpl::removeIndexFromMap(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr index) { ASSERT(objectStore->m_indexes.contains(index->name())); objectStore->m_indexes.remove(index->name()); } void IDBObjectStoreBackendImpl::addIndexToMap(ScriptExecutionContext*, PassRefPtr objectStore, PassRefPtr index) { RefPtr indexPtr = index; ASSERT(!objectStore->m_indexes.contains(indexPtr->name())); objectStore->m_indexes.set(indexPtr->name(), indexPtr); } } // namespace WebCore #endif