IDBSQLiteBackingStore.cpp   [plain text]


/*
 * Copyright (C) 2011 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 "IDBSQLiteBackingStore.h"

#if ENABLE(INDEXED_DATABASE)

#include "FileSystem.h"
#include "IDBFactoryBackendImpl.h"
#include "IDBKey.h"
#include "IDBKeyRange.h"
#include "SQLiteDatabase.h"
#include "SQLiteStatement.h"
#include "SQLiteTransaction.h"
#include "SecurityOrigin.h"

namespace WebCore {

IDBSQLiteBackingStore::IDBSQLiteBackingStore(String identifier, IDBFactoryBackendImpl* factory)
    : m_identifier(identifier)
    , m_factory(factory)
{
    m_factory->addIDBBackingStore(identifier, this);
}

IDBSQLiteBackingStore::~IDBSQLiteBackingStore()
{
    m_factory->removeIDBBackingStore(m_identifier);
}

static bool runCommands(SQLiteDatabase& sqliteDatabase, const char** commands, size_t numberOfCommands)
{
    SQLiteTransaction transaction(sqliteDatabase, false);
    transaction.begin();
    for (size_t i = 0; i < numberOfCommands; ++i) {
        if (!sqliteDatabase.executeCommand(commands[i])) {
            LOG_ERROR("Failed to run the following command for IndexedDB: %s", commands[i]);
            return false;
        }
    }
    transaction.commit();
    return true;
}

static bool createTables(SQLiteDatabase& sqliteDatabase)
{
    if (sqliteDatabase.tableExists("Databases"))
        return true;
    static const char* commands[] = {
        "CREATE TABLE Databases (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, version TEXT NOT NULL)",
        "CREATE UNIQUE INDEX Databases_name ON Databases(name)",

        "CREATE TABLE ObjectStores (id INTEGER PRIMARY KEY, name TEXT NOT NULL, keyPath TEXT, doAutoIncrement INTEGER NOT NULL, databaseId INTEGER NOT NULL REFERENCES Databases(id))",
        "CREATE UNIQUE INDEX ObjectStores_composit ON ObjectStores(databaseId, name)",

        "CREATE TABLE Indexes (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), name TEXT NOT NULL, keyPath TEXT, isUnique INTEGER NOT NULL)",
        "CREATE UNIQUE INDEX Indexes_composit ON Indexes(objectStoreId, name)",

        "CREATE TABLE ObjectStoreData (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), keyString TEXT, keyDate INTEGER, keyNumber INTEGER, value TEXT NOT NULL)",
        "CREATE UNIQUE INDEX ObjectStoreData_composit ON ObjectStoreData(keyString, keyDate, keyNumber, objectStoreId)",

        "CREATE TABLE IndexData (id INTEGER PRIMARY KEY, indexId INTEGER NOT NULL REFERENCES Indexes(id), keyString TEXT, keyDate INTEGER, keyNumber INTEGER, objectStoreDataId INTEGER NOT NULL REFERENCES ObjectStoreData(id))",
        "CREATE INDEX IndexData_composit ON IndexData(keyString, keyDate, keyNumber, indexId)",
        "CREATE INDEX IndexData_objectStoreDataId ON IndexData(objectStoreDataId)",
        "CREATE INDEX IndexData_indexId ON IndexData(indexId)",
        };

    return runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0]));
}

static bool createMetaDataTable(SQLiteDatabase& sqliteDatabase)
{
    static const char* commands[] = {
        "CREATE TABLE MetaData (name TEXT PRIMARY KEY, value NONE)",
        "INSERT INTO MetaData VALUES ('version', 1)",
    };

    return runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0]));
}

static bool getDatabaseSchemaVersion(SQLiteDatabase& sqliteDatabase, int* databaseVersion)
{
    SQLiteStatement query(sqliteDatabase, "SELECT value FROM MetaData WHERE name = 'version'");
    if (query.prepare() != SQLResultOk || query.step() != SQLResultRow)
        return false;

    *databaseVersion = query.getColumnInt(0);
    return query.finalize() == SQLResultOk;
}

static bool migrateDatabase(SQLiteDatabase& sqliteDatabase)
{
    if (!sqliteDatabase.tableExists("MetaData")) {
        if (!createMetaDataTable(sqliteDatabase))
            return false;
    }

    int databaseVersion;
    if (!getDatabaseSchemaVersion(sqliteDatabase, &databaseVersion))
        return false;

    if (databaseVersion == 1) {
        static const char* commands[] = {
            "DROP TABLE IF EXISTS ObjectStoreData2",
            "CREATE TABLE ObjectStoreData2 (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), keyString TEXT, keyDate REAL, keyNumber REAL, value TEXT NOT NULL)",
            "INSERT INTO ObjectStoreData2 SELECT * FROM ObjectStoreData",
            "DROP TABLE ObjectStoreData", // This depends on SQLite not enforcing referential consistency.
            "ALTER TABLE ObjectStoreData2 RENAME TO ObjectStoreData",
            "CREATE UNIQUE INDEX ObjectStoreData_composit ON ObjectStoreData(keyString, keyDate, keyNumber, objectStoreId)",
            "DROP TABLE IF EXISTS IndexData2", // This depends on SQLite not enforcing referential consistency.
            "CREATE TABLE IndexData2 (id INTEGER PRIMARY KEY, indexId INTEGER NOT NULL REFERENCES Indexes(id), keyString TEXT, keyDate REAL, keyNumber REAL, objectStoreDataId INTEGER NOT NULL REFERENCES ObjectStoreData(id))",
            "INSERT INTO IndexData2 SELECT * FROM IndexData",
            "DROP TABLE IndexData",
            "ALTER TABLE IndexData2 RENAME TO IndexData",
            "CREATE INDEX IndexData_composit ON IndexData(keyString, keyDate, keyNumber, indexId)",
            "CREATE INDEX IndexData_objectStoreDataId ON IndexData(objectStoreDataId)",
            "CREATE INDEX IndexData_indexId ON IndexData(indexId)",
            "UPDATE MetaData SET value = 2 WHERE name = 'version'",
        };

        if (!runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0])))
            return false;

        databaseVersion = 2;
    }

    if (databaseVersion == 2) {
        // We need to make the ObjectStoreData.value be a BLOB instead of TEXT.
        static const char* commands[] = {
            "DROP TABLE IF EXISTS ObjectStoreData", // This drops associated indices.
            "CREATE TABLE ObjectStoreData (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), keyString TEXT, keyDate REAL, keyNumber REAL, value BLOB NOT NULL)",
            "CREATE UNIQUE INDEX ObjectStoreData_composit ON ObjectStoreData(keyString, keyDate, keyNumber, objectStoreId)",
            "UPDATE MetaData SET value = 3 WHERE name = 'version'",
        };

        if (!runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0])))
            return false;

        databaseVersion = 3;
    }

    return true;
}

PassRefPtr<IDBBackingStore> IDBSQLiteBackingStore::open(SecurityOrigin* securityOrigin, const String& pathBase, int64_t maximumSize, const String& fileIdentifier, IDBFactoryBackendImpl* factory)
{
    RefPtr<IDBSQLiteBackingStore> backingStore(adoptRef(new IDBSQLiteBackingStore(fileIdentifier, factory)));

    String path = ":memory:";
    if (!pathBase.isEmpty()) {
        if (!makeAllDirectories(pathBase)) {
            // FIXME: Is there any other thing we could possibly do to recover at this point? If so, do it rather than just erroring out.
            LOG_ERROR("Unable to create Indexed DB database path %s", pathBase.utf8().data());
            return 0;
        }
        path = pathByAppendingComponent(pathBase, securityOrigin->databaseIdentifier() + ".indexeddb");
    }

    if (!backingStore->m_db.open(path)) {
        // FIXME: Is there any other thing we could possibly do to recover at this point? If so, do it rather than just erroring out.
        LOG_ERROR("Failed to open database file %s for IndexedDB", path.utf8().data());
        return 0;
    }

    // FIXME: Error checking?
    backingStore->m_db.setMaximumSize(maximumSize);
    backingStore->m_db.turnOnIncrementalAutoVacuum();

    if (!createTables(backingStore->m_db))
        return 0;
    if (!migrateDatabase(backingStore->m_db))
        return 0;

    return backingStore.release();
}

bool IDBSQLiteBackingStore::extractIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId)
{
    SQLiteStatement databaseQuery(m_db, "SELECT id, version FROM Databases WHERE name = ?");
    if (databaseQuery.prepare() != SQLResultOk) {
        ASSERT_NOT_REACHED();
        return false;
    }
    databaseQuery.bindText(1, name);
    if (databaseQuery.step() != SQLResultRow)
        return false;

    foundId = databaseQuery.getColumnInt64(0);
    foundVersion = databaseQuery.getColumnText(1);

    if (databaseQuery.step() == SQLResultRow)
        ASSERT_NOT_REACHED();
    return true;
}

bool IDBSQLiteBackingStore::setIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId, bool invalidRowId)
{
    ASSERT(!name.isNull());
    ASSERT(!version.isNull());

    String sql = invalidRowId ? "INSERT INTO Databases (name, description, version) VALUES (?, '', ?)" : "UPDATE Databases SET name = ?, version = ? WHERE id = ?";
    SQLiteStatement query(m_db, sql);
    if (query.prepare() != SQLResultOk) {
        ASSERT_NOT_REACHED();
        return false;
    }

    query.bindText(1, name);
    query.bindText(2, version);
    if (!invalidRowId)
        query.bindInt64(3, rowId);

    if (query.step() != SQLResultDone)
        return false;

    if (invalidRowId)
        rowId = m_db.lastInsertRowID();

    return true;
}

void IDBSQLiteBackingStore::getObjectStores(int64_t databaseId, Vector<int64_t>& foundIds, Vector<String>& foundNames, Vector<String>& foundKeyPaths, Vector<bool>& foundAutoIncrementFlags)
{
    SQLiteStatement query(m_db, "SELECT id, name, keyPath, doAutoIncrement FROM ObjectStores WHERE databaseId = ?");
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    ASSERT(foundIds.isEmpty());
    ASSERT(foundNames.isEmpty());
    ASSERT(foundKeyPaths.isEmpty());
    ASSERT(foundAutoIncrementFlags.isEmpty());

    query.bindInt64(1, databaseId);

    while (query.step() == SQLResultRow) {
        foundIds.append(query.getColumnInt64(0));
        foundNames.append(query.getColumnText(1));
        foundKeyPaths.append(query.getColumnText(2));
        foundAutoIncrementFlags.append(!!query.getColumnInt(3));
    }
}

bool IDBSQLiteBackingStore::createObjectStore(int64_t databaseId, const String& name, const String& keyPath, bool autoIncrement, int64_t& assignedObjectStoreId)
{
    SQLiteStatement query(m_db, "INSERT INTO ObjectStores (name, keyPath, doAutoIncrement, databaseId) VALUES (?, ?, ?, ?)");
    if (query.prepare() != SQLResultOk)
        return false;

    query.bindText(1, name);
    query.bindText(2, keyPath);
    query.bindInt(3, static_cast<int>(autoIncrement));
    query.bindInt64(4, databaseId);

    if (query.step() != SQLResultDone)
        return false;

    assignedObjectStoreId = m_db.lastInsertRowID();
    return true;
}

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 IDBSQLiteBackingStore::deleteObjectStore(int64_t, int64_t objectStoreId)
{
    doDelete(m_db, "DELETE FROM ObjectStores WHERE id = ?", objectStoreId);
    doDelete(m_db, "DELETE FROM ObjectStoreData WHERE objectStoreId = ?", objectStoreId);
    doDelete(m_db, "DELETE FROM IndexData WHERE indexId IN (SELECT id FROM Indexes WHERE objectStoreId = ?)", objectStoreId);
    doDelete(m_db, "DELETE FROM Indexes WHERE objectStoreId = ?", objectStoreId);
}

namespace {
class SQLiteRecordIdentifier : public IDBBackingStore::ObjectStoreRecordIdentifier {
public:
    static PassRefPtr<SQLiteRecordIdentifier> create() { return adoptRef(new SQLiteRecordIdentifier()); }
    static PassRefPtr<SQLiteRecordIdentifier> create(int64_t id) { return adoptRef(new SQLiteRecordIdentifier(id)); }
    virtual bool isValid() const { return m_id != -1; }
    int64_t id() const { return m_id; }
    void setId(int64_t id) { m_id = id; }
private:
    SQLiteRecordIdentifier() : m_id(-1) { }
    SQLiteRecordIdentifier(int64_t id) : m_id(id) { }
    int64_t m_id;
};
}

PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> IDBSQLiteBackingStore::createInvalidRecordIdentifier()
{
    return SQLiteRecordIdentifier::create();
}

static String whereSyntaxForKey(const IDBKey& key, String qualifiedTableName = "")
{
    switch (key.type()) {
    case IDBKey::StringType:
        return qualifiedTableName + "keyString = ?  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL  ";
    case IDBKey::NumberType:
        return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber = ?  ";
    case IDBKey::DateType:
        return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate = ?  AND  " + qualifiedTableName + "keyNumber IS NULL  ";
    case IDBKey::NullType:
        return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL  ";
    }

    ASSERT_NOT_REACHED();
    return "";
}

// Returns the number of items bound.
static int bindKeyToQuery(SQLiteStatement& query, int column, const IDBKey& key)
{
    switch (key.type()) {
    case IDBKey::StringType:
        query.bindText(column, key.string());
        return 1;
    case IDBKey::DateType:
        query.bindDouble(column, key.date());
        return 1;
    case IDBKey::NumberType:
        query.bindDouble(column, key.number());
        return 1;
    case IDBKey::NullType:
        return 0;
    }

    ASSERT_NOT_REACHED();
    return 0;
}

static String lowerCursorWhereFragment(const IDBKey& key, String comparisonOperator, String qualifiedTableName = "")
{
    switch (key.type()) {
    case IDBKey::StringType:
        return "? " + comparisonOperator + " " + qualifiedTableName + "keyString  AND  ";
    case IDBKey::DateType:
        return "(? " + comparisonOperator + " " + qualifiedTableName + "keyDate  OR NOT " + qualifiedTableName + "keyString IS NULL)  AND  ";
    case IDBKey::NumberType:
        return "(? " + comparisonOperator + " " + qualifiedTableName + "keyNumber  OR  NOT " + qualifiedTableName + "keyString IS NULL  OR  NOT " + qualifiedTableName + "keyDate IS NULL)  AND  ";
    case IDBKey::NullType:
        if (comparisonOperator == "<")
            return "NOT(" + qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL)  AND  ";
        return ""; // If it's =, the upper bound half will do the constraining. If it's <=, then that's a no-op.
    }
    ASSERT_NOT_REACHED();
    return "";
}

static String upperCursorWhereFragment(const IDBKey& key, String comparisonOperator, String qualifiedTableName = "")
{
    switch (key.type()) {
    case IDBKey::StringType:
        return "(" + qualifiedTableName + "keyString " + comparisonOperator + " ?  OR  " + qualifiedTableName + "keyString IS NULL)  AND  ";
    case IDBKey::DateType:
        return "(" + qualifiedTableName + "keyDate " + comparisonOperator + " ? OR " + qualifiedTableName + "keyDate IS NULL)  AND  " + qualifiedTableName + "keyString IS NULL  AND  ";
    case IDBKey::NumberType:
        return "(" + qualifiedTableName + "keyNumber " + comparisonOperator + " ? OR " + qualifiedTableName + "keyNumber IS NULL)  AND  " + qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  ";
    case IDBKey::NullType:
        if (comparisonOperator == "<")
            return "0 != 0  AND  ";
        return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL  AND  ";
    }
    ASSERT_NOT_REACHED();
    return "";
}

String IDBSQLiteBackingStore::getObjectStoreRecord(int64_t, int64_t objectStoreId, const IDBKey& key)
{
    SQLiteStatement query(m_db, "SELECT keyString, keyDate, keyNumber, value FROM ObjectStoreData WHERE objectStoreId = ? AND " + whereSyntaxForKey(key));
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    query.bindInt64(1, objectStoreId);
    bindKeyToQuery(query, 2, key);
    if (query.step() != SQLResultRow)
        return String(); // Null String means record not found.

    ASSERT((key.type() == IDBKey::StringType) != query.isColumnNull(0));
    ASSERT((key.type() == IDBKey::DateType) != query.isColumnNull(1));
    ASSERT((key.type() == IDBKey::NumberType) != query.isColumnNull(2));

    String record = query.getColumnBlobAsString(3);
    ASSERT(query.step() != SQLResultRow);

    return record;
}

static void bindKeyToQueryWithNulls(SQLiteStatement& query, int baseColumn, const IDBKey& key)
{
    switch (key.type()) {
    case IDBKey::StringType:
        query.bindText(baseColumn + 0, key.string());
        query.bindNull(baseColumn + 1);
        query.bindNull(baseColumn + 2);
        break;
    case IDBKey::DateType:
        query.bindNull(baseColumn + 0);
        query.bindDouble(baseColumn + 1, key.date());
        query.bindNull(baseColumn + 2);
        break;
    case IDBKey::NumberType:
        query.bindNull(baseColumn + 0);
        query.bindNull(baseColumn + 1);
        query.bindDouble(baseColumn + 2, key.number());
        break;
    case IDBKey::NullType:
        query.bindNull(baseColumn + 0);
        query.bindNull(baseColumn + 1);
        query.bindNull(baseColumn + 2);
        break;
    default:
        ASSERT_NOT_REACHED();
    }
}

bool IDBSQLiteBackingStore::putObjectStoreRecord(int64_t, int64_t objectStoreId, const IDBKey& key, const String& value, ObjectStoreRecordIdentifier* recordIdentifier)
{
    SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<SQLiteRecordIdentifier*>(recordIdentifier);

    String sql = sqliteRecordIdentifier->isValid() ? "UPDATE ObjectStoreData SET keyString = ?, keyDate = ?, keyNumber = ?, value = ? WHERE id = ?"
                                                   : "INSERT INTO ObjectStoreData (keyString, keyDate, keyNumber, value, objectStoreId) VALUES (?, ?, ?, ?, ?)";
    SQLiteStatement query(m_db, sql);
    if (query.prepare() != SQLResultOk)
        return false;

    bindKeyToQueryWithNulls(query, 1, key);
    query.bindBlob(4, value);
    if (sqliteRecordIdentifier->isValid())
        query.bindInt64(5, sqliteRecordIdentifier->id());
    else
        query.bindInt64(5, objectStoreId);

    if (query.step() != SQLResultDone)
        return false;

    if (!sqliteRecordIdentifier->isValid())
        sqliteRecordIdentifier->setId(m_db.lastInsertRowID());

    return true;
}

void IDBSQLiteBackingStore::clearObjectStore(int64_t, int64_t objectStoreId)
{
    doDelete(m_db, "DELETE FROM IndexData WHERE objectStoreDataId IN (SELECT id FROM ObjectStoreData WHERE objectStoreId = ?)", objectStoreId);
    doDelete(m_db, "DELETE FROM ObjectStoreData WHERE objectStoreId = ?", objectStoreId);
}

void IDBSQLiteBackingStore::deleteObjectStoreRecord(int64_t, int64_t objectStoreId, const ObjectStoreRecordIdentifier* recordIdentifier)
{
    const SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<const SQLiteRecordIdentifier*>(recordIdentifier);
    ASSERT(sqliteRecordIdentifier->isValid());

    SQLiteStatement osQuery(m_db, "DELETE FROM ObjectStoreData WHERE id = ?");
    bool ok = osQuery.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    osQuery.bindInt64(1, sqliteRecordIdentifier->id());

    ok = osQuery.step() == SQLResultDone;
    ASSERT_UNUSED(ok, ok);
}

double IDBSQLiteBackingStore::nextAutoIncrementNumber(int64_t, int64_t objectStoreId)
{
    SQLiteStatement query(m_db, "SELECT max(keyNumber) + 1 FROM ObjectStoreData WHERE objectStoreId = ? AND keyString IS NULL AND keyDate IS NULL");
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok);

    query.bindInt64(1, objectStoreId);

    if (query.step() != SQLResultRow || query.isColumnNull(0))
        return 1;

    return query.getColumnDouble(0);
}

bool IDBSQLiteBackingStore::keyExistsInObjectStore(int64_t, int64_t objectStoreId, const IDBKey& key, ObjectStoreRecordIdentifier* foundRecordIdentifier)
{
    SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<SQLiteRecordIdentifier*>(foundRecordIdentifier);

    String sql = String("SELECT id FROM ObjectStoreData WHERE objectStoreId = ? AND ") + whereSyntaxForKey(key);
    SQLiteStatement query(m_db, sql);
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    query.bindInt64(1, objectStoreId);
    bindKeyToQuery(query, 2, key);

    if (query.step() != SQLResultRow)
        return false;

    sqliteRecordIdentifier->setId(query.getColumnInt64(0));
    return true;
}

bool IDBSQLiteBackingStore::forEachObjectStoreRecord(int64_t, int64_t objectStoreId, ObjectStoreRecordCallback& callback)
{
    SQLiteStatement query(m_db, "SELECT id, value FROM ObjectStoreData WHERE objectStoreId = ?");
    if (query.prepare() != SQLResultOk)
        return false;

    query.bindInt64(1, objectStoreId);

    while (query.step() == SQLResultRow) {
        int64_t objectStoreDataId = query.getColumnInt64(0);
        String value = query.getColumnBlobAsString(1);
        RefPtr<SQLiteRecordIdentifier> recordIdentifier = SQLiteRecordIdentifier::create(objectStoreDataId);
        if (!callback.callback(recordIdentifier.get(), value))
            return false;
    }

    return true;
}

void IDBSQLiteBackingStore::getIndexes(int64_t, int64_t objectStoreId, Vector<int64_t>& foundIds, Vector<String>& foundNames, Vector<String>& foundKeyPaths, Vector<bool>& foundUniqueFlags)
{
    SQLiteStatement query(m_db, "SELECT id, name, keyPath, isUnique FROM Indexes WHERE objectStoreId = ?");
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    ASSERT(foundIds.isEmpty());
    ASSERT(foundNames.isEmpty());
    ASSERT(foundKeyPaths.isEmpty());
    ASSERT(foundUniqueFlags.isEmpty());

    query.bindInt64(1, objectStoreId);

    while (query.step() == SQLResultRow) {
        foundIds.append(query.getColumnInt64(0));
        foundNames.append(query.getColumnText(1));
        foundKeyPaths.append(query.getColumnText(2));
        foundUniqueFlags.append(!!query.getColumnInt(3));
    }
}

bool IDBSQLiteBackingStore::createIndex(int64_t, int64_t objectStoreId, const String& name, const String& keyPath, bool isUnique, int64_t& indexId)
{
    SQLiteStatement query(m_db, "INSERT INTO Indexes (objectStoreId, name, keyPath, isUnique) VALUES (?, ?, ?, ?)");
    if (query.prepare() != SQLResultOk)
        return false;

    query.bindInt64(1, objectStoreId);
    query.bindText(2, name);
    query.bindText(3, keyPath);
    query.bindInt(4, static_cast<int>(isUnique));

    if (query.step() != SQLResultDone)
        return false;

    indexId = m_db.lastInsertRowID();
    return true;
}

void IDBSQLiteBackingStore::deleteIndex(int64_t, int64_t, int64_t indexId)
{
    doDelete(m_db, "DELETE FROM Indexes WHERE id = ?", indexId);
    doDelete(m_db, "DELETE FROM IndexData WHERE indexId = ?", indexId);
}

bool IDBSQLiteBackingStore::putIndexDataForRecord(int64_t, int64_t, int64_t indexId, const IDBKey& key, const ObjectStoreRecordIdentifier* recordIdentifier)
{
    const SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<const SQLiteRecordIdentifier*>(recordIdentifier);

    SQLiteStatement query(m_db, "INSERT INTO IndexData (keyString, keyDate, keyNumber, indexId, objectStoreDataId) VALUES (?, ?, ?, ?, ?)");
    if (query.prepare() != SQLResultOk)
        return false;

    bindKeyToQueryWithNulls(query, 1, key);
    query.bindInt64(4, indexId);
    query.bindInt64(5, sqliteRecordIdentifier->id());

    return query.step() == SQLResultDone;
}

bool IDBSQLiteBackingStore::deleteIndexDataForRecord(int64_t, int64_t, int64_t indexId, const ObjectStoreRecordIdentifier* recordIdentifier)
{
    const SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<const SQLiteRecordIdentifier*>(recordIdentifier);

    SQLiteStatement query(m_db, "DELETE FROM IndexData WHERE objectStoreDataId = ? AND indexId = ?");
    if (query.prepare() != SQLResultOk)
        return false;

    query.bindInt64(1, sqliteRecordIdentifier->id());
    query.bindInt64(2, indexId);
    return query.step() == SQLResultDone;
}

String IDBSQLiteBackingStore::getObjectViaIndex(int64_t, int64_t, int64_t indexId, const IDBKey& key)
{
    String sql = String("SELECT ")
                 + "ObjectStoreData.value "
                 + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id "
                 + "WHERE IndexData.indexId = ?  AND  " + whereSyntaxForKey(key, "IndexData.")
                 + "ORDER BY IndexData.id LIMIT 1"; // Order by insertion order when all else fails.
    SQLiteStatement query(m_db, sql);
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    query.bindInt64(1, indexId);
    bindKeyToQuery(query, 2, key);

    if (query.step() != SQLResultRow)
        return String();

    String foundValue = query.getColumnBlobAsString(0);
    ASSERT(query.step() != SQLResultRow);
    return foundValue;
}

static PassRefPtr<IDBKey> keyFromQuery(SQLiteStatement& query, int baseColumn)
{
    if (query.columnCount() <= baseColumn)
        return 0;

    if (!query.isColumnNull(baseColumn))
        return IDBKey::createString(query.getColumnText(baseColumn));

    if (!query.isColumnNull(baseColumn + 1))
        return IDBKey::createDate(query.getColumnDouble(baseColumn + 1));

    if (!query.isColumnNull(baseColumn + 2))
        return IDBKey::createNumber(query.getColumnDouble(baseColumn + 2));

    return IDBKey::createNull();
}

PassRefPtr<IDBKey> IDBSQLiteBackingStore::getPrimaryKeyViaIndex(int64_t, int64_t, int64_t indexId, const IDBKey& key)
{
    String sql = String("SELECT ")
                 + "ObjectStoreData.keyString, ObjectStoreData.keyDate, ObjectStoreData.keyNumber "
                 + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id "
                 + "WHERE IndexData.indexId = ?  AND  " + whereSyntaxForKey(key, "IndexData.")
                 + "ORDER BY IndexData.id LIMIT 1"; // Order by insertion order when all else fails.
    SQLiteStatement query(m_db, sql);
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    query.bindInt64(1, indexId);
    bindKeyToQuery(query, 2, key);

    if (query.step() != SQLResultRow)
        return 0;

    RefPtr<IDBKey> foundKey = keyFromQuery(query, 0);
    ASSERT(query.step() != SQLResultRow);
    return foundKey.release();
}

bool IDBSQLiteBackingStore::keyExistsInIndex(int64_t, int64_t, int64_t indexId, const IDBKey& key)
{
    String sql = String("SELECT id FROM IndexData WHERE indexId = ? AND ") + whereSyntaxForKey(key);
    SQLiteStatement query(m_db, sql);
    bool ok = query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    query.bindInt64(1, indexId);
    bindKeyToQuery(query, 2, key);

    return query.step() == SQLResultRow;
}

namespace {

class CursorImplCommon : public IDBSQLiteBackingStore::Cursor {
public:
    CursorImplCommon(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
        : m_query(sqliteDatabase, query)
        , m_db(sqliteDatabase)
        , m_uniquenessConstraint(uniquenessConstraint)
        , m_iterateForward(iterateForward)
    {
    }
    virtual ~CursorImplCommon() {}

    // IDBBackingStore::Cursor
    virtual bool continueFunction(const IDBKey*);
    virtual PassRefPtr<IDBKey> key() { return m_currentKey; }
    virtual PassRefPtr<IDBKey> primaryKey() { return m_currentKey; }
    virtual String value() = 0;
    virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() = 0;
    virtual int64_t indexDataId() = 0;
    virtual void close() { m_query.finalize(); }

    virtual void loadCurrentRow() = 0;
    virtual bool currentRowExists() = 0;

    SQLiteStatement m_query;

protected:
    SQLiteDatabase& m_db;
    bool m_uniquenessConstraint;
    bool m_iterateForward;
    int64_t m_currentId;
    RefPtr<IDBKey> m_currentKey;
};

bool CursorImplCommon::continueFunction(const IDBKey* key)
{
    while (true) {
        if (m_query.step() != SQLResultRow)
            return false;

        RefPtr<IDBKey> oldKey = m_currentKey;
        loadCurrentRow();

        // Skip if this entry has been deleted from the object store.
        if (!currentRowExists())
            continue;

        // If a key was supplied, we must loop until we find a key greater than or equal to it (or hit the end).
        if (key) {
            if (m_iterateForward) {
                if (m_currentKey->isLessThan(key))
                    continue;
            } else {
                if (key->isLessThan(m_currentKey.get()))
                    continue;
            }
        }

        // If we don't have a uniqueness constraint, we can stop now.
        if (!m_uniquenessConstraint)
            break;
        if (!m_currentKey->isEqual(oldKey.get()))
            break;
    }

    return true;
}

class ObjectStoreCursorImpl : public CursorImplCommon {
public:
    ObjectStoreCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
        : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
    {
    }

    // CursorImplCommon.
    virtual String value() { return m_currentValue; }
    virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { return SQLiteRecordIdentifier::create(m_currentId); }
    virtual int64_t indexDataId() { ASSERT_NOT_REACHED(); return 0; }
    virtual void loadCurrentRow();
    virtual bool currentRowExists();

private:
    String m_currentValue;
};

void ObjectStoreCursorImpl::loadCurrentRow()
{
    m_currentId = m_query.getColumnInt64(0);
    m_currentKey = keyFromQuery(m_query, 1);
    m_currentValue = m_query.getColumnBlobAsString(4);
}

bool ObjectStoreCursorImpl::currentRowExists()
{
    String sql = "SELECT id FROM ObjectStoreData WHERE id = ?";
    SQLiteStatement statement(m_db, sql);

    bool ok = statement.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok);

    statement.bindInt64(1, m_currentId);
    return statement.step() == SQLResultRow;
}

class IndexKeyCursorImpl : public CursorImplCommon {
public:
    IndexKeyCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
        : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
    {
    }

    // CursorImplCommon
    virtual PassRefPtr<IDBKey> primaryKey() { return m_currentPrimaryKey; }
    virtual String value() { ASSERT_NOT_REACHED(); return String(); }
    virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { ASSERT_NOT_REACHED(); return 0; }
    virtual int64_t indexDataId() { return m_currentId; }
    virtual void loadCurrentRow();
    virtual bool currentRowExists();

private:
    RefPtr<IDBKey> m_currentPrimaryKey;
};

void IndexKeyCursorImpl::loadCurrentRow()
{
    m_currentId = m_query.getColumnInt64(0);
    m_currentKey = keyFromQuery(m_query, 1);
    m_currentPrimaryKey = keyFromQuery(m_query, 4);
}

bool IndexKeyCursorImpl::currentRowExists()
{
    String sql = "SELECT id FROM IndexData WHERE id = ?";
    SQLiteStatement statement(m_db, sql);

    bool ok = statement.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok);

    statement.bindInt64(1, m_currentId);
    return statement.step() == SQLResultRow;
}

class IndexCursorImpl : public CursorImplCommon {
public:
    IndexCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
        : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
    {
    }

    // CursorImplCommon
    virtual PassRefPtr<IDBKey> primaryKey() { return m_currentPrimaryKey; }
    virtual String value() { return m_currentValue; }
    virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { ASSERT_NOT_REACHED(); return 0; }
    virtual int64_t indexDataId() { return m_currentId; }
    virtual void loadCurrentRow();
    virtual bool currentRowExists();

private:
    RefPtr<IDBKey> m_currentPrimaryKey;
    String m_currentValue;
};

void IndexCursorImpl::loadCurrentRow()
{
    m_currentId = m_query.getColumnInt64(0);
    m_currentKey = keyFromQuery(m_query, 1);
    m_currentValue = m_query.getColumnBlobAsString(4);
    m_currentPrimaryKey = keyFromQuery(m_query, 5);
}

bool IndexCursorImpl::currentRowExists()
{
    String sql = "SELECT id FROM IndexData WHERE id = ?";
    SQLiteStatement statement(m_db, sql);

    bool ok = statement.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok);

    statement.bindInt64(1, m_currentId);
    return statement.step() == SQLResultRow;
}

} // namespace

PassRefPtr<IDBBackingStore::Cursor> IDBSQLiteBackingStore::openObjectStoreCursor(int64_t, int64_t objectStoreId, const IDBKeyRange* range, IDBCursor::Direction direction)
{
    bool lowerBound = range && range->lower();
    bool upperBound = range && range->upper();

    String sql = "SELECT id, keyString, keyDate, keyNumber, value FROM ObjectStoreData WHERE ";
    if (lowerBound)
        sql += lowerCursorWhereFragment(*range->lower(), range->lowerOpen() ? "<" : "<=");
    if (upperBound)
        sql += upperCursorWhereFragment(*range->upper(), range->upperOpen() ? "<" : "<=");
    sql += "objectStoreId = ? ORDER BY ";

    if (direction == IDBCursor::NEXT || direction == IDBCursor::NEXT_NO_DUPLICATE)
        sql += "keyString, keyDate, keyNumber";
    else
        sql += "keyString DESC, keyDate DESC, keyNumber DESC";

    RefPtr<ObjectStoreCursorImpl> cursor = adoptRef(new ObjectStoreCursorImpl(m_db, sql, direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::PREV_NO_DUPLICATE,
                                                                              direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::NEXT));

    bool ok = cursor->m_query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    int currentColumn = 1;
    if (lowerBound)
        currentColumn += bindKeyToQuery(cursor->m_query, currentColumn, *range->lower());
    if (upperBound)
        currentColumn += bindKeyToQuery(cursor->m_query, currentColumn, *range->upper());
    cursor->m_query.bindInt64(currentColumn, objectStoreId);

    if (cursor->m_query.step() != SQLResultRow)
        return 0;

    cursor->loadCurrentRow();
    return cursor.release();
}

PassRefPtr<IDBBackingStore::Cursor> IDBSQLiteBackingStore::openIndexKeyCursor(int64_t, int64_t, int64_t indexId, const IDBKeyRange* range, IDBCursor::Direction direction)
{
    String sql = String("SELECT IndexData.id, IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, ")
                 + ("ObjectStoreData.keyString, ObjectStoreData.keyDate, ObjectStoreData.keyNumber ")
                 + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id WHERE ";

    bool lowerBound = range && range->lower();
    bool upperBound = range && range->upper();

    if (lowerBound)
        sql += lowerCursorWhereFragment(*range->lower(), range->lowerOpen() ? "<" : "<=", "IndexData.");
    if (upperBound)
        sql += upperCursorWhereFragment(*range->upper(), range->upperOpen() ? "<" : "<=", "IndexData.");
    sql += "IndexData.indexId = ? ORDER BY ";

    if (direction == IDBCursor::NEXT || direction == IDBCursor::NEXT_NO_DUPLICATE)
        sql += "IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, IndexData.id";
    else
        sql += "IndexData.keyString DESC, IndexData.keyDate DESC, IndexData.keyNumber DESC, IndexData.id DESC";

    RefPtr<IndexKeyCursorImpl> cursor = adoptRef(new IndexKeyCursorImpl(m_db, sql, direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::PREV_NO_DUPLICATE,
                                                                        direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::NEXT));

    bool ok = cursor->m_query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    int indexColumn = 1;
    if (lowerBound)
        indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->lower());
    if (upperBound)
        indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->upper());
    cursor->m_query.bindInt64(indexColumn, indexId);

    if (cursor->m_query.step() != SQLResultRow)
        return 0;

    cursor->loadCurrentRow();
    return cursor.release();
}

PassRefPtr<IDBBackingStore::Cursor> IDBSQLiteBackingStore::openIndexCursor(int64_t, int64_t, int64_t indexId, const IDBKeyRange* range, IDBCursor::Direction direction)
{
    String sql = String("SELECT IndexData.id, IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, ")
                 + ("ObjectStoreData.value, ObjectStoreData.keyString, ObjectStoreData.keyDate, ObjectStoreData.keyNumber ")
                 + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id WHERE ";

    bool lowerBound = range && range->lower();
    bool upperBound = range && range->upper();

    if (lowerBound)
        sql += lowerCursorWhereFragment(*range->lower(), range->lowerOpen() ? "<" : "<=", "IndexData.");
    if (upperBound)
        sql += upperCursorWhereFragment(*range->upper(), range->upperOpen() ? "<" : "<=", "IndexData.");
    sql += "IndexData.indexId = ? ORDER BY ";

    if (direction == IDBCursor::NEXT || direction == IDBCursor::NEXT_NO_DUPLICATE)
        sql += "IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, IndexData.id";
    else
        sql += "IndexData.keyString DESC, IndexData.keyDate DESC, IndexData.keyNumber DESC, IndexData.id DESC";

    RefPtr<IndexCursorImpl> cursor = adoptRef(new IndexCursorImpl(m_db, sql, direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::PREV_NO_DUPLICATE,
                                                                  direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::NEXT));

    bool ok = cursor->m_query.prepare() == SQLResultOk;
    ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?

    int indexColumn = 1;
    if (lowerBound)
        indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->lower());
    if (upperBound)
        indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->upper());
    cursor->m_query.bindInt64(indexColumn, indexId);

    if (cursor->m_query.step() != SQLResultRow)
        return 0;

    cursor->loadCurrentRow();
    return cursor.release();
}

namespace {

class TransactionImpl : public IDBBackingStore::Transaction {
public:
    TransactionImpl(SQLiteDatabase& db)
        : m_transaction(db)
    {
    }

    // IDBBackingStore::Transaction
    virtual void begin() { m_transaction.begin(); }
    virtual void commit() { m_transaction.commit(); }
    virtual void rollback() { m_transaction.rollback(); }

private:
    SQLiteTransaction m_transaction;
};

} // namespace

PassRefPtr<IDBBackingStore::Transaction> IDBSQLiteBackingStore::createTransaction()
{
    return adoptRef(new TransactionImpl(m_db));
}

} // namespace WebCore

#endif // ENABLE(INDEXED_DATABASE)