Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2011 Google 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  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "IDBSQLiteBackingStore.h"
     28 
     29 #if ENABLE(INDEXED_DATABASE)
     30 
     31 #include "FileSystem.h"
     32 #include "IDBFactoryBackendImpl.h"
     33 #include "IDBKey.h"
     34 #include "IDBKeyRange.h"
     35 #include "SQLiteDatabase.h"
     36 #include "SQLiteStatement.h"
     37 #include "SQLiteTransaction.h"
     38 #include "SecurityOrigin.h"
     39 
     40 namespace WebCore {
     41 
     42 IDBSQLiteBackingStore::IDBSQLiteBackingStore(String identifier, IDBFactoryBackendImpl* factory)
     43     : m_identifier(identifier)
     44     , m_factory(factory)
     45 {
     46     m_factory->addIDBBackingStore(identifier, this);
     47 }
     48 
     49 IDBSQLiteBackingStore::~IDBSQLiteBackingStore()
     50 {
     51     m_factory->removeIDBBackingStore(m_identifier);
     52 }
     53 
     54 static bool runCommands(SQLiteDatabase& sqliteDatabase, const char** commands, size_t numberOfCommands)
     55 {
     56     SQLiteTransaction transaction(sqliteDatabase, false);
     57     transaction.begin();
     58     for (size_t i = 0; i < numberOfCommands; ++i) {
     59         if (!sqliteDatabase.executeCommand(commands[i])) {
     60             LOG_ERROR("Failed to run the following command for IndexedDB: %s", commands[i]);
     61             return false;
     62         }
     63     }
     64     transaction.commit();
     65     return true;
     66 }
     67 
     68 static bool createTables(SQLiteDatabase& sqliteDatabase)
     69 {
     70     if (sqliteDatabase.tableExists("Databases"))
     71         return true;
     72     static const char* commands[] = {
     73         "CREATE TABLE Databases (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, version TEXT NOT NULL)",
     74         "CREATE UNIQUE INDEX Databases_name ON Databases(name)",
     75 
     76         "CREATE TABLE ObjectStores (id INTEGER PRIMARY KEY, name TEXT NOT NULL, keyPath TEXT, doAutoIncrement INTEGER NOT NULL, databaseId INTEGER NOT NULL REFERENCES Databases(id))",
     77         "CREATE UNIQUE INDEX ObjectStores_composit ON ObjectStores(databaseId, name)",
     78 
     79         "CREATE TABLE Indexes (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), name TEXT NOT NULL, keyPath TEXT, isUnique INTEGER NOT NULL)",
     80         "CREATE UNIQUE INDEX Indexes_composit ON Indexes(objectStoreId, name)",
     81 
     82         "CREATE TABLE ObjectStoreData (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), keyString TEXT, keyDate INTEGER, keyNumber INTEGER, value TEXT NOT NULL)",
     83         "CREATE UNIQUE INDEX ObjectStoreData_composit ON ObjectStoreData(keyString, keyDate, keyNumber, objectStoreId)",
     84 
     85         "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))",
     86         "CREATE INDEX IndexData_composit ON IndexData(keyString, keyDate, keyNumber, indexId)",
     87         "CREATE INDEX IndexData_objectStoreDataId ON IndexData(objectStoreDataId)",
     88         "CREATE INDEX IndexData_indexId ON IndexData(indexId)",
     89         };
     90 
     91     return runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0]));
     92 }
     93 
     94 static bool createMetaDataTable(SQLiteDatabase& sqliteDatabase)
     95 {
     96     static const char* commands[] = {
     97         "CREATE TABLE MetaData (name TEXT PRIMARY KEY, value NONE)",
     98         "INSERT INTO MetaData VALUES ('version', 1)",
     99     };
    100 
    101     return runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0]));
    102 }
    103 
    104 static bool getDatabaseSchemaVersion(SQLiteDatabase& sqliteDatabase, int* databaseVersion)
    105 {
    106     SQLiteStatement query(sqliteDatabase, "SELECT value FROM MetaData WHERE name = 'version'");
    107     if (query.prepare() != SQLResultOk || query.step() != SQLResultRow)
    108         return false;
    109 
    110     *databaseVersion = query.getColumnInt(0);
    111     return query.finalize() == SQLResultOk;
    112 }
    113 
    114 static bool migrateDatabase(SQLiteDatabase& sqliteDatabase)
    115 {
    116     if (!sqliteDatabase.tableExists("MetaData")) {
    117         if (!createMetaDataTable(sqliteDatabase))
    118             return false;
    119     }
    120 
    121     int databaseVersion;
    122     if (!getDatabaseSchemaVersion(sqliteDatabase, &databaseVersion))
    123         return false;
    124 
    125     if (databaseVersion == 1) {
    126         static const char* commands[] = {
    127             "DROP TABLE IF EXISTS ObjectStoreData2",
    128             "CREATE TABLE ObjectStoreData2 (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), keyString TEXT, keyDate REAL, keyNumber REAL, value TEXT NOT NULL)",
    129             "INSERT INTO ObjectStoreData2 SELECT * FROM ObjectStoreData",
    130             "DROP TABLE ObjectStoreData", // This depends on SQLite not enforcing referential consistency.
    131             "ALTER TABLE ObjectStoreData2 RENAME TO ObjectStoreData",
    132             "CREATE UNIQUE INDEX ObjectStoreData_composit ON ObjectStoreData(keyString, keyDate, keyNumber, objectStoreId)",
    133             "DROP TABLE IF EXISTS IndexData2", // This depends on SQLite not enforcing referential consistency.
    134             "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))",
    135             "INSERT INTO IndexData2 SELECT * FROM IndexData",
    136             "DROP TABLE IndexData",
    137             "ALTER TABLE IndexData2 RENAME TO IndexData",
    138             "CREATE INDEX IndexData_composit ON IndexData(keyString, keyDate, keyNumber, indexId)",
    139             "CREATE INDEX IndexData_objectStoreDataId ON IndexData(objectStoreDataId)",
    140             "CREATE INDEX IndexData_indexId ON IndexData(indexId)",
    141             "UPDATE MetaData SET value = 2 WHERE name = 'version'",
    142         };
    143 
    144         if (!runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0])))
    145             return false;
    146 
    147         databaseVersion = 2;
    148     }
    149 
    150     if (databaseVersion == 2) {
    151         // We need to make the ObjectStoreData.value be a BLOB instead of TEXT.
    152         static const char* commands[] = {
    153             "DROP TABLE IF EXISTS ObjectStoreData", // This drops associated indices.
    154             "CREATE TABLE ObjectStoreData (id INTEGER PRIMARY KEY, objectStoreId INTEGER NOT NULL REFERENCES ObjectStore(id), keyString TEXT, keyDate REAL, keyNumber REAL, value BLOB NOT NULL)",
    155             "CREATE UNIQUE INDEX ObjectStoreData_composit ON ObjectStoreData(keyString, keyDate, keyNumber, objectStoreId)",
    156             "UPDATE MetaData SET value = 3 WHERE name = 'version'",
    157         };
    158 
    159         if (!runCommands(sqliteDatabase, commands, sizeof(commands) / sizeof(commands[0])))
    160             return false;
    161 
    162         databaseVersion = 3;
    163     }
    164 
    165     return true;
    166 }
    167 
    168 PassRefPtr<IDBBackingStore> IDBSQLiteBackingStore::open(SecurityOrigin* securityOrigin, const String& pathBase, int64_t maximumSize, const String& fileIdentifier, IDBFactoryBackendImpl* factory)
    169 {
    170     RefPtr<IDBSQLiteBackingStore> backingStore(adoptRef(new IDBSQLiteBackingStore(fileIdentifier, factory)));
    171 
    172     String path = ":memory:";
    173     if (!pathBase.isEmpty()) {
    174         if (!makeAllDirectories(pathBase)) {
    175             // FIXME: Is there any other thing we could possibly do to recover at this point? If so, do it rather than just erroring out.
    176             LOG_ERROR("Unable to create Indexed DB database path %s", pathBase.utf8().data());
    177             return 0;
    178         }
    179         path = pathByAppendingComponent(pathBase, securityOrigin->databaseIdentifier() + ".indexeddb");
    180     }
    181 
    182     if (!backingStore->m_db.open(path)) {
    183         // FIXME: Is there any other thing we could possibly do to recover at this point? If so, do it rather than just erroring out.
    184         LOG_ERROR("Failed to open database file %s for IndexedDB", path.utf8().data());
    185         return 0;
    186     }
    187 
    188     // FIXME: Error checking?
    189     backingStore->m_db.setMaximumSize(maximumSize);
    190     backingStore->m_db.turnOnIncrementalAutoVacuum();
    191 
    192     if (!createTables(backingStore->m_db))
    193         return 0;
    194     if (!migrateDatabase(backingStore->m_db))
    195         return 0;
    196 
    197     return backingStore.release();
    198 }
    199 
    200 bool IDBSQLiteBackingStore::extractIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId)
    201 {
    202     SQLiteStatement databaseQuery(m_db, "SELECT id, version FROM Databases WHERE name = ?");
    203     if (databaseQuery.prepare() != SQLResultOk) {
    204         ASSERT_NOT_REACHED();
    205         return false;
    206     }
    207     databaseQuery.bindText(1, name);
    208     if (databaseQuery.step() != SQLResultRow)
    209         return false;
    210 
    211     foundId = databaseQuery.getColumnInt64(0);
    212     foundVersion = databaseQuery.getColumnText(1);
    213 
    214     if (databaseQuery.step() == SQLResultRow)
    215         ASSERT_NOT_REACHED();
    216     return true;
    217 }
    218 
    219 bool IDBSQLiteBackingStore::setIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId, bool invalidRowId)
    220 {
    221     ASSERT(!name.isNull());
    222     ASSERT(!version.isNull());
    223 
    224     String sql = invalidRowId ? "INSERT INTO Databases (name, description, version) VALUES (?, '', ?)" : "UPDATE Databases SET name = ?, version = ? WHERE id = ?";
    225     SQLiteStatement query(m_db, sql);
    226     if (query.prepare() != SQLResultOk) {
    227         ASSERT_NOT_REACHED();
    228         return false;
    229     }
    230 
    231     query.bindText(1, name);
    232     query.bindText(2, version);
    233     if (!invalidRowId)
    234         query.bindInt64(3, rowId);
    235 
    236     if (query.step() != SQLResultDone)
    237         return false;
    238 
    239     if (invalidRowId)
    240         rowId = m_db.lastInsertRowID();
    241 
    242     return true;
    243 }
    244 
    245 void IDBSQLiteBackingStore::getObjectStores(int64_t databaseId, Vector<int64_t>& foundIds, Vector<String>& foundNames, Vector<String>& foundKeyPaths, Vector<bool>& foundAutoIncrementFlags)
    246 {
    247     SQLiteStatement query(m_db, "SELECT id, name, keyPath, doAutoIncrement FROM ObjectStores WHERE databaseId = ?");
    248     bool ok = query.prepare() == SQLResultOk;
    249     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    250 
    251     ASSERT(foundIds.isEmpty());
    252     ASSERT(foundNames.isEmpty());
    253     ASSERT(foundKeyPaths.isEmpty());
    254     ASSERT(foundAutoIncrementFlags.isEmpty());
    255 
    256     query.bindInt64(1, databaseId);
    257 
    258     while (query.step() == SQLResultRow) {
    259         foundIds.append(query.getColumnInt64(0));
    260         foundNames.append(query.getColumnText(1));
    261         foundKeyPaths.append(query.getColumnText(2));
    262         foundAutoIncrementFlags.append(!!query.getColumnInt(3));
    263     }
    264 }
    265 
    266 bool IDBSQLiteBackingStore::createObjectStore(int64_t databaseId, const String& name, const String& keyPath, bool autoIncrement, int64_t& assignedObjectStoreId)
    267 {
    268     SQLiteStatement query(m_db, "INSERT INTO ObjectStores (name, keyPath, doAutoIncrement, databaseId) VALUES (?, ?, ?, ?)");
    269     if (query.prepare() != SQLResultOk)
    270         return false;
    271 
    272     query.bindText(1, name);
    273     query.bindText(2, keyPath);
    274     query.bindInt(3, static_cast<int>(autoIncrement));
    275     query.bindInt64(4, databaseId);
    276 
    277     if (query.step() != SQLResultDone)
    278         return false;
    279 
    280     assignedObjectStoreId = m_db.lastInsertRowID();
    281     return true;
    282 }
    283 
    284 static void doDelete(SQLiteDatabase& db, const char* sql, int64_t id)
    285 {
    286     SQLiteStatement deleteQuery(db, sql);
    287     bool ok = deleteQuery.prepare() == SQLResultOk;
    288     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling.
    289     deleteQuery.bindInt64(1, id);
    290     ok = deleteQuery.step() == SQLResultDone;
    291     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling.
    292 }
    293 
    294 void IDBSQLiteBackingStore::deleteObjectStore(int64_t, int64_t objectStoreId)
    295 {
    296     doDelete(m_db, "DELETE FROM ObjectStores WHERE id = ?", objectStoreId);
    297     doDelete(m_db, "DELETE FROM ObjectStoreData WHERE objectStoreId = ?", objectStoreId);
    298     doDelete(m_db, "DELETE FROM IndexData WHERE indexId IN (SELECT id FROM Indexes WHERE objectStoreId = ?)", objectStoreId);
    299     doDelete(m_db, "DELETE FROM Indexes WHERE objectStoreId = ?", objectStoreId);
    300 }
    301 
    302 namespace {
    303 class SQLiteRecordIdentifier : public IDBBackingStore::ObjectStoreRecordIdentifier {
    304 public:
    305     static PassRefPtr<SQLiteRecordIdentifier> create() { return adoptRef(new SQLiteRecordIdentifier()); }
    306     static PassRefPtr<SQLiteRecordIdentifier> create(int64_t id) { return adoptRef(new SQLiteRecordIdentifier(id)); }
    307     virtual bool isValid() const { return m_id != -1; }
    308     int64_t id() const { return m_id; }
    309     void setId(int64_t id) { m_id = id; }
    310 private:
    311     SQLiteRecordIdentifier() : m_id(-1) { }
    312     SQLiteRecordIdentifier(int64_t id) : m_id(id) { }
    313     int64_t m_id;
    314 };
    315 }
    316 
    317 PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> IDBSQLiteBackingStore::createInvalidRecordIdentifier()
    318 {
    319     return SQLiteRecordIdentifier::create();
    320 }
    321 
    322 static String whereSyntaxForKey(const IDBKey& key, String qualifiedTableName = "")
    323 {
    324     switch (key.type()) {
    325     case IDBKey::StringType:
    326         return qualifiedTableName + "keyString = ?  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL  ";
    327     case IDBKey::NumberType:
    328         return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber = ?  ";
    329     case IDBKey::DateType:
    330         return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate = ?  AND  " + qualifiedTableName + "keyNumber IS NULL  ";
    331     case IDBKey::NullType:
    332         return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL  ";
    333     }
    334 
    335     ASSERT_NOT_REACHED();
    336     return "";
    337 }
    338 
    339 // Returns the number of items bound.
    340 static int bindKeyToQuery(SQLiteStatement& query, int column, const IDBKey& key)
    341 {
    342     switch (key.type()) {
    343     case IDBKey::StringType:
    344         query.bindText(column, key.string());
    345         return 1;
    346     case IDBKey::DateType:
    347         query.bindDouble(column, key.date());
    348         return 1;
    349     case IDBKey::NumberType:
    350         query.bindDouble(column, key.number());
    351         return 1;
    352     case IDBKey::NullType:
    353         return 0;
    354     }
    355 
    356     ASSERT_NOT_REACHED();
    357     return 0;
    358 }
    359 
    360 static String lowerCursorWhereFragment(const IDBKey& key, String comparisonOperator, String qualifiedTableName = "")
    361 {
    362     switch (key.type()) {
    363     case IDBKey::StringType:
    364         return "? " + comparisonOperator + " " + qualifiedTableName + "keyString  AND  ";
    365     case IDBKey::DateType:
    366         return "(? " + comparisonOperator + " " + qualifiedTableName + "keyDate  OR NOT " + qualifiedTableName + "keyString IS NULL)  AND  ";
    367     case IDBKey::NumberType:
    368         return "(? " + comparisonOperator + " " + qualifiedTableName + "keyNumber  OR  NOT " + qualifiedTableName + "keyString IS NULL  OR  NOT " + qualifiedTableName + "keyDate IS NULL)  AND  ";
    369     case IDBKey::NullType:
    370         if (comparisonOperator == "<")
    371             return "NOT(" + qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL)  AND  ";
    372         return ""; // If it's =, the upper bound half will do the constraining. If it's <=, then that's a no-op.
    373     }
    374     ASSERT_NOT_REACHED();
    375     return "";
    376 }
    377 
    378 static String upperCursorWhereFragment(const IDBKey& key, String comparisonOperator, String qualifiedTableName = "")
    379 {
    380     switch (key.type()) {
    381     case IDBKey::StringType:
    382         return "(" + qualifiedTableName + "keyString " + comparisonOperator + " ?  OR  " + qualifiedTableName + "keyString IS NULL)  AND  ";
    383     case IDBKey::DateType:
    384         return "(" + qualifiedTableName + "keyDate " + comparisonOperator + " ? OR " + qualifiedTableName + "keyDate IS NULL)  AND  " + qualifiedTableName + "keyString IS NULL  AND  ";
    385     case IDBKey::NumberType:
    386         return "(" + qualifiedTableName + "keyNumber " + comparisonOperator + " ? OR " + qualifiedTableName + "keyNumber IS NULL)  AND  " + qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  ";
    387     case IDBKey::NullType:
    388         if (comparisonOperator == "<")
    389             return "0 != 0  AND  ";
    390         return qualifiedTableName + "keyString IS NULL  AND  " + qualifiedTableName + "keyDate IS NULL  AND  " + qualifiedTableName + "keyNumber IS NULL  AND  ";
    391     }
    392     ASSERT_NOT_REACHED();
    393     return "";
    394 }
    395 
    396 String IDBSQLiteBackingStore::getObjectStoreRecord(int64_t, int64_t objectStoreId, const IDBKey& key)
    397 {
    398     SQLiteStatement query(m_db, "SELECT keyString, keyDate, keyNumber, value FROM ObjectStoreData WHERE objectStoreId = ? AND " + whereSyntaxForKey(key));
    399     bool ok = query.prepare() == SQLResultOk;
    400     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    401 
    402     query.bindInt64(1, objectStoreId);
    403     bindKeyToQuery(query, 2, key);
    404     if (query.step() != SQLResultRow)
    405         return String(); // Null String means record not found.
    406 
    407     ASSERT((key.type() == IDBKey::StringType) != query.isColumnNull(0));
    408     ASSERT((key.type() == IDBKey::DateType) != query.isColumnNull(1));
    409     ASSERT((key.type() == IDBKey::NumberType) != query.isColumnNull(2));
    410 
    411     String record = query.getColumnBlobAsString(3);
    412     ASSERT(query.step() != SQLResultRow);
    413 
    414     return record;
    415 }
    416 
    417 static void bindKeyToQueryWithNulls(SQLiteStatement& query, int baseColumn, const IDBKey& key)
    418 {
    419     switch (key.type()) {
    420     case IDBKey::StringType:
    421         query.bindText(baseColumn + 0, key.string());
    422         query.bindNull(baseColumn + 1);
    423         query.bindNull(baseColumn + 2);
    424         break;
    425     case IDBKey::DateType:
    426         query.bindNull(baseColumn + 0);
    427         query.bindDouble(baseColumn + 1, key.date());
    428         query.bindNull(baseColumn + 2);
    429         break;
    430     case IDBKey::NumberType:
    431         query.bindNull(baseColumn + 0);
    432         query.bindNull(baseColumn + 1);
    433         query.bindDouble(baseColumn + 2, key.number());
    434         break;
    435     case IDBKey::NullType:
    436         query.bindNull(baseColumn + 0);
    437         query.bindNull(baseColumn + 1);
    438         query.bindNull(baseColumn + 2);
    439         break;
    440     default:
    441         ASSERT_NOT_REACHED();
    442     }
    443 }
    444 
    445 bool IDBSQLiteBackingStore::putObjectStoreRecord(int64_t, int64_t objectStoreId, const IDBKey& key, const String& value, ObjectStoreRecordIdentifier* recordIdentifier)
    446 {
    447     SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<SQLiteRecordIdentifier*>(recordIdentifier);
    448 
    449     String sql = sqliteRecordIdentifier->isValid() ? "UPDATE ObjectStoreData SET keyString = ?, keyDate = ?, keyNumber = ?, value = ? WHERE id = ?"
    450                                                    : "INSERT INTO ObjectStoreData (keyString, keyDate, keyNumber, value, objectStoreId) VALUES (?, ?, ?, ?, ?)";
    451     SQLiteStatement query(m_db, sql);
    452     if (query.prepare() != SQLResultOk)
    453         return false;
    454 
    455     bindKeyToQueryWithNulls(query, 1, key);
    456     query.bindBlob(4, value);
    457     if (sqliteRecordIdentifier->isValid())
    458         query.bindInt64(5, sqliteRecordIdentifier->id());
    459     else
    460         query.bindInt64(5, objectStoreId);
    461 
    462     if (query.step() != SQLResultDone)
    463         return false;
    464 
    465     if (!sqliteRecordIdentifier->isValid())
    466         sqliteRecordIdentifier->setId(m_db.lastInsertRowID());
    467 
    468     return true;
    469 }
    470 
    471 void IDBSQLiteBackingStore::clearObjectStore(int64_t, int64_t objectStoreId)
    472 {
    473     doDelete(m_db, "DELETE FROM IndexData WHERE objectStoreDataId IN (SELECT id FROM ObjectStoreData WHERE objectStoreId = ?)", objectStoreId);
    474     doDelete(m_db, "DELETE FROM ObjectStoreData WHERE objectStoreId = ?", objectStoreId);
    475 }
    476 
    477 void IDBSQLiteBackingStore::deleteObjectStoreRecord(int64_t, int64_t objectStoreId, const ObjectStoreRecordIdentifier* recordIdentifier)
    478 {
    479     const SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<const SQLiteRecordIdentifier*>(recordIdentifier);
    480     ASSERT(sqliteRecordIdentifier->isValid());
    481 
    482     SQLiteStatement osQuery(m_db, "DELETE FROM ObjectStoreData WHERE id = ?");
    483     bool ok = osQuery.prepare() == SQLResultOk;
    484     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    485 
    486     osQuery.bindInt64(1, sqliteRecordIdentifier->id());
    487 
    488     ok = osQuery.step() == SQLResultDone;
    489     ASSERT_UNUSED(ok, ok);
    490 }
    491 
    492 double IDBSQLiteBackingStore::nextAutoIncrementNumber(int64_t, int64_t objectStoreId)
    493 {
    494     SQLiteStatement query(m_db, "SELECT max(keyNumber) + 1 FROM ObjectStoreData WHERE objectStoreId = ? AND keyString IS NULL AND keyDate IS NULL");
    495     bool ok = query.prepare() == SQLResultOk;
    496     ASSERT_UNUSED(ok, ok);
    497 
    498     query.bindInt64(1, objectStoreId);
    499 
    500     if (query.step() != SQLResultRow || query.isColumnNull(0))
    501         return 1;
    502 
    503     return query.getColumnDouble(0);
    504 }
    505 
    506 bool IDBSQLiteBackingStore::keyExistsInObjectStore(int64_t, int64_t objectStoreId, const IDBKey& key, ObjectStoreRecordIdentifier* foundRecordIdentifier)
    507 {
    508     SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<SQLiteRecordIdentifier*>(foundRecordIdentifier);
    509 
    510     String sql = String("SELECT id FROM ObjectStoreData WHERE objectStoreId = ? AND ") + whereSyntaxForKey(key);
    511     SQLiteStatement query(m_db, sql);
    512     bool ok = query.prepare() == SQLResultOk;
    513     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    514 
    515     query.bindInt64(1, objectStoreId);
    516     bindKeyToQuery(query, 2, key);
    517 
    518     if (query.step() != SQLResultRow)
    519         return false;
    520 
    521     sqliteRecordIdentifier->setId(query.getColumnInt64(0));
    522     return true;
    523 }
    524 
    525 bool IDBSQLiteBackingStore::forEachObjectStoreRecord(int64_t, int64_t objectStoreId, ObjectStoreRecordCallback& callback)
    526 {
    527     SQLiteStatement query(m_db, "SELECT id, value FROM ObjectStoreData WHERE objectStoreId = ?");
    528     if (query.prepare() != SQLResultOk)
    529         return false;
    530 
    531     query.bindInt64(1, objectStoreId);
    532 
    533     while (query.step() == SQLResultRow) {
    534         int64_t objectStoreDataId = query.getColumnInt64(0);
    535         String value = query.getColumnBlobAsString(1);
    536         RefPtr<SQLiteRecordIdentifier> recordIdentifier = SQLiteRecordIdentifier::create(objectStoreDataId);
    537         if (!callback.callback(recordIdentifier.get(), value))
    538             return false;
    539     }
    540 
    541     return true;
    542 }
    543 
    544 void IDBSQLiteBackingStore::getIndexes(int64_t, int64_t objectStoreId, Vector<int64_t>& foundIds, Vector<String>& foundNames, Vector<String>& foundKeyPaths, Vector<bool>& foundUniqueFlags)
    545 {
    546     SQLiteStatement query(m_db, "SELECT id, name, keyPath, isUnique FROM Indexes WHERE objectStoreId = ?");
    547     bool ok = query.prepare() == SQLResultOk;
    548     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    549 
    550     ASSERT(foundIds.isEmpty());
    551     ASSERT(foundNames.isEmpty());
    552     ASSERT(foundKeyPaths.isEmpty());
    553     ASSERT(foundUniqueFlags.isEmpty());
    554 
    555     query.bindInt64(1, objectStoreId);
    556 
    557     while (query.step() == SQLResultRow) {
    558         foundIds.append(query.getColumnInt64(0));
    559         foundNames.append(query.getColumnText(1));
    560         foundKeyPaths.append(query.getColumnText(2));
    561         foundUniqueFlags.append(!!query.getColumnInt(3));
    562     }
    563 }
    564 
    565 bool IDBSQLiteBackingStore::createIndex(int64_t, int64_t objectStoreId, const String& name, const String& keyPath, bool isUnique, int64_t& indexId)
    566 {
    567     SQLiteStatement query(m_db, "INSERT INTO Indexes (objectStoreId, name, keyPath, isUnique) VALUES (?, ?, ?, ?)");
    568     if (query.prepare() != SQLResultOk)
    569         return false;
    570 
    571     query.bindInt64(1, objectStoreId);
    572     query.bindText(2, name);
    573     query.bindText(3, keyPath);
    574     query.bindInt(4, static_cast<int>(isUnique));
    575 
    576     if (query.step() != SQLResultDone)
    577         return false;
    578 
    579     indexId = m_db.lastInsertRowID();
    580     return true;
    581 }
    582 
    583 void IDBSQLiteBackingStore::deleteIndex(int64_t, int64_t, int64_t indexId)
    584 {
    585     doDelete(m_db, "DELETE FROM Indexes WHERE id = ?", indexId);
    586     doDelete(m_db, "DELETE FROM IndexData WHERE indexId = ?", indexId);
    587 }
    588 
    589 bool IDBSQLiteBackingStore::putIndexDataForRecord(int64_t, int64_t, int64_t indexId, const IDBKey& key, const ObjectStoreRecordIdentifier* recordIdentifier)
    590 {
    591     const SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<const SQLiteRecordIdentifier*>(recordIdentifier);
    592 
    593     SQLiteStatement query(m_db, "INSERT INTO IndexData (keyString, keyDate, keyNumber, indexId, objectStoreDataId) VALUES (?, ?, ?, ?, ?)");
    594     if (query.prepare() != SQLResultOk)
    595         return false;
    596 
    597     bindKeyToQueryWithNulls(query, 1, key);
    598     query.bindInt64(4, indexId);
    599     query.bindInt64(5, sqliteRecordIdentifier->id());
    600 
    601     return query.step() == SQLResultDone;
    602 }
    603 
    604 bool IDBSQLiteBackingStore::deleteIndexDataForRecord(int64_t, int64_t, int64_t indexId, const ObjectStoreRecordIdentifier* recordIdentifier)
    605 {
    606     const SQLiteRecordIdentifier* sqliteRecordIdentifier = static_cast<const SQLiteRecordIdentifier*>(recordIdentifier);
    607 
    608     SQLiteStatement query(m_db, "DELETE FROM IndexData WHERE objectStoreDataId = ? AND indexId = ?");
    609     if (query.prepare() != SQLResultOk)
    610         return false;
    611 
    612     query.bindInt64(1, sqliteRecordIdentifier->id());
    613     query.bindInt64(2, indexId);
    614     return query.step() == SQLResultDone;
    615 }
    616 
    617 String IDBSQLiteBackingStore::getObjectViaIndex(int64_t, int64_t, int64_t indexId, const IDBKey& key)
    618 {
    619     String sql = String("SELECT ")
    620                  + "ObjectStoreData.value "
    621                  + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id "
    622                  + "WHERE IndexData.indexId = ?  AND  " + whereSyntaxForKey(key, "IndexData.")
    623                  + "ORDER BY IndexData.id LIMIT 1"; // Order by insertion order when all else fails.
    624     SQLiteStatement query(m_db, sql);
    625     bool ok = query.prepare() == SQLResultOk;
    626     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    627 
    628     query.bindInt64(1, indexId);
    629     bindKeyToQuery(query, 2, key);
    630 
    631     if (query.step() != SQLResultRow)
    632         return String();
    633 
    634     String foundValue = query.getColumnBlobAsString(0);
    635     ASSERT(query.step() != SQLResultRow);
    636     return foundValue;
    637 }
    638 
    639 static PassRefPtr<IDBKey> keyFromQuery(SQLiteStatement& query, int baseColumn)
    640 {
    641     if (query.columnCount() <= baseColumn)
    642         return 0;
    643 
    644     if (!query.isColumnNull(baseColumn))
    645         return IDBKey::createString(query.getColumnText(baseColumn));
    646 
    647     if (!query.isColumnNull(baseColumn + 1))
    648         return IDBKey::createDate(query.getColumnDouble(baseColumn + 1));
    649 
    650     if (!query.isColumnNull(baseColumn + 2))
    651         return IDBKey::createNumber(query.getColumnDouble(baseColumn + 2));
    652 
    653     return IDBKey::createNull();
    654 }
    655 
    656 PassRefPtr<IDBKey> IDBSQLiteBackingStore::getPrimaryKeyViaIndex(int64_t, int64_t, int64_t indexId, const IDBKey& key)
    657 {
    658     String sql = String("SELECT ")
    659                  + "ObjectStoreData.keyString, ObjectStoreData.keyDate, ObjectStoreData.keyNumber "
    660                  + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id "
    661                  + "WHERE IndexData.indexId = ?  AND  " + whereSyntaxForKey(key, "IndexData.")
    662                  + "ORDER BY IndexData.id LIMIT 1"; // Order by insertion order when all else fails.
    663     SQLiteStatement query(m_db, sql);
    664     bool ok = query.prepare() == SQLResultOk;
    665     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    666 
    667     query.bindInt64(1, indexId);
    668     bindKeyToQuery(query, 2, key);
    669 
    670     if (query.step() != SQLResultRow)
    671         return 0;
    672 
    673     RefPtr<IDBKey> foundKey = keyFromQuery(query, 0);
    674     ASSERT(query.step() != SQLResultRow);
    675     return foundKey.release();
    676 }
    677 
    678 bool IDBSQLiteBackingStore::keyExistsInIndex(int64_t, int64_t, int64_t indexId, const IDBKey& key)
    679 {
    680     String sql = String("SELECT id FROM IndexData WHERE indexId = ? AND ") + whereSyntaxForKey(key);
    681     SQLiteStatement query(m_db, sql);
    682     bool ok = query.prepare() == SQLResultOk;
    683     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    684 
    685     query.bindInt64(1, indexId);
    686     bindKeyToQuery(query, 2, key);
    687 
    688     return query.step() == SQLResultRow;
    689 }
    690 
    691 namespace {
    692 
    693 class CursorImplCommon : public IDBSQLiteBackingStore::Cursor {
    694 public:
    695     CursorImplCommon(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
    696         : m_query(sqliteDatabase, query)
    697         , m_db(sqliteDatabase)
    698         , m_uniquenessConstraint(uniquenessConstraint)
    699         , m_iterateForward(iterateForward)
    700     {
    701     }
    702     virtual ~CursorImplCommon() {}
    703 
    704     // IDBBackingStore::Cursor
    705     virtual bool continueFunction(const IDBKey*);
    706     virtual PassRefPtr<IDBKey> key() { return m_currentKey; }
    707     virtual PassRefPtr<IDBKey> primaryKey() { return m_currentKey; }
    708     virtual String value() = 0;
    709     virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() = 0;
    710     virtual int64_t indexDataId() = 0;
    711 
    712     virtual void loadCurrentRow() = 0;
    713     virtual bool currentRowExists() = 0;
    714 
    715     SQLiteStatement m_query;
    716 
    717 protected:
    718     SQLiteDatabase& m_db;
    719     bool m_uniquenessConstraint;
    720     bool m_iterateForward;
    721     int64_t m_currentId;
    722     RefPtr<IDBKey> m_currentKey;
    723 };
    724 
    725 bool CursorImplCommon::continueFunction(const IDBKey* key)
    726 {
    727     while (true) {
    728         if (m_query.step() != SQLResultRow)
    729             return false;
    730 
    731         RefPtr<IDBKey> oldKey = m_currentKey;
    732         loadCurrentRow();
    733 
    734         // Skip if this entry has been deleted from the object store.
    735         if (!currentRowExists())
    736             continue;
    737 
    738         // If a key was supplied, we must loop until we find a key greater than or equal to it (or hit the end).
    739         if (key) {
    740             if (m_iterateForward) {
    741                 if (m_currentKey->isLessThan(key))
    742                     continue;
    743             } else {
    744                 if (key->isLessThan(m_currentKey.get()))
    745                     continue;
    746             }
    747         }
    748 
    749         // If we don't have a uniqueness constraint, we can stop now.
    750         if (!m_uniquenessConstraint)
    751             break;
    752         if (!m_currentKey->isEqual(oldKey.get()))
    753             break;
    754     }
    755 
    756     return true;
    757 }
    758 
    759 class ObjectStoreCursorImpl : public CursorImplCommon {
    760 public:
    761     ObjectStoreCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
    762         : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
    763     {
    764     }
    765 
    766     // CursorImplCommon.
    767     virtual String value() { return m_currentValue; }
    768     virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { return SQLiteRecordIdentifier::create(m_currentId); }
    769     virtual int64_t indexDataId() { ASSERT_NOT_REACHED(); return 0; }
    770     virtual void loadCurrentRow();
    771     virtual bool currentRowExists();
    772 
    773 private:
    774     String m_currentValue;
    775 };
    776 
    777 void ObjectStoreCursorImpl::loadCurrentRow()
    778 {
    779     m_currentId = m_query.getColumnInt64(0);
    780     m_currentKey = keyFromQuery(m_query, 1);
    781     m_currentValue = m_query.getColumnBlobAsString(4);
    782 }
    783 
    784 bool ObjectStoreCursorImpl::currentRowExists()
    785 {
    786     String sql = "SELECT id FROM ObjectStoreData WHERE id = ?";
    787     SQLiteStatement statement(m_db, sql);
    788 
    789     bool ok = statement.prepare() == SQLResultOk;
    790     ASSERT_UNUSED(ok, ok);
    791 
    792     statement.bindInt64(1, m_currentId);
    793     return statement.step() == SQLResultRow;
    794 }
    795 
    796 class IndexKeyCursorImpl : public CursorImplCommon {
    797 public:
    798     IndexKeyCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
    799         : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
    800     {
    801     }
    802 
    803     // CursorImplCommon
    804     virtual PassRefPtr<IDBKey> primaryKey() { return m_currentPrimaryKey; }
    805     virtual String value() { ASSERT_NOT_REACHED(); return String(); }
    806     virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { ASSERT_NOT_REACHED(); return 0; }
    807     virtual int64_t indexDataId() { return m_currentId; }
    808     virtual void loadCurrentRow();
    809     virtual bool currentRowExists();
    810 
    811 private:
    812     RefPtr<IDBKey> m_currentPrimaryKey;
    813 };
    814 
    815 void IndexKeyCursorImpl::loadCurrentRow()
    816 {
    817     m_currentId = m_query.getColumnInt64(0);
    818     m_currentKey = keyFromQuery(m_query, 1);
    819     m_currentPrimaryKey = keyFromQuery(m_query, 4);
    820 }
    821 
    822 bool IndexKeyCursorImpl::currentRowExists()
    823 {
    824     String sql = "SELECT id FROM IndexData WHERE id = ?";
    825     SQLiteStatement statement(m_db, sql);
    826 
    827     bool ok = statement.prepare() == SQLResultOk;
    828     ASSERT_UNUSED(ok, ok);
    829 
    830     statement.bindInt64(1, m_currentId);
    831     return statement.step() == SQLResultRow;
    832 }
    833 
    834 class IndexCursorImpl : public CursorImplCommon {
    835 public:
    836     IndexCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
    837         : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
    838     {
    839     }
    840 
    841     // CursorImplCommon
    842     virtual PassRefPtr<IDBKey> primaryKey() { return m_currentPrimaryKey; }
    843     virtual String value() { return m_currentValue; }
    844     virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { ASSERT_NOT_REACHED(); return 0; }
    845     virtual int64_t indexDataId() { return m_currentId; }
    846     virtual void loadCurrentRow();
    847     virtual bool currentRowExists();
    848 
    849 private:
    850     RefPtr<IDBKey> m_currentPrimaryKey;
    851     String m_currentValue;
    852 };
    853 
    854 void IndexCursorImpl::loadCurrentRow()
    855 {
    856     m_currentId = m_query.getColumnInt64(0);
    857     m_currentKey = keyFromQuery(m_query, 1);
    858     m_currentValue = m_query.getColumnBlobAsString(4);
    859     m_currentPrimaryKey = keyFromQuery(m_query, 5);
    860 }
    861 
    862 bool IndexCursorImpl::currentRowExists()
    863 {
    864     String sql = "SELECT id FROM IndexData WHERE id = ?";
    865     SQLiteStatement statement(m_db, sql);
    866 
    867     bool ok = statement.prepare() == SQLResultOk;
    868     ASSERT_UNUSED(ok, ok);
    869 
    870     statement.bindInt64(1, m_currentId);
    871     return statement.step() == SQLResultRow;
    872 }
    873 
    874 } // namespace
    875 
    876 PassRefPtr<IDBBackingStore::Cursor> IDBSQLiteBackingStore::openObjectStoreCursor(int64_t, int64_t objectStoreId, const IDBKeyRange* range, IDBCursor::Direction direction)
    877 {
    878     bool lowerBound = range && range->lower();
    879     bool upperBound = range && range->upper();
    880 
    881     String sql = "SELECT id, keyString, keyDate, keyNumber, value FROM ObjectStoreData WHERE ";
    882     if (lowerBound)
    883         sql += lowerCursorWhereFragment(*range->lower(), range->lowerOpen() ? "<" : "<=");
    884     if (upperBound)
    885         sql += upperCursorWhereFragment(*range->upper(), range->upperOpen() ? "<" : "<=");
    886     sql += "objectStoreId = ? ORDER BY ";
    887 
    888     if (direction == IDBCursor::NEXT || direction == IDBCursor::NEXT_NO_DUPLICATE)
    889         sql += "keyString, keyDate, keyNumber";
    890     else
    891         sql += "keyString DESC, keyDate DESC, keyNumber DESC";
    892 
    893     RefPtr<ObjectStoreCursorImpl> cursor = adoptRef(new ObjectStoreCursorImpl(m_db, sql, direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::PREV_NO_DUPLICATE,
    894                                                                               direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::NEXT));
    895 
    896     bool ok = cursor->m_query.prepare() == SQLResultOk;
    897     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    898 
    899     int currentColumn = 1;
    900     if (lowerBound)
    901         currentColumn += bindKeyToQuery(cursor->m_query, currentColumn, *range->lower());
    902     if (upperBound)
    903         currentColumn += bindKeyToQuery(cursor->m_query, currentColumn, *range->upper());
    904     cursor->m_query.bindInt64(currentColumn, objectStoreId);
    905 
    906     if (cursor->m_query.step() != SQLResultRow)
    907         return 0;
    908 
    909     cursor->loadCurrentRow();
    910     return cursor.release();
    911 }
    912 
    913 PassRefPtr<IDBBackingStore::Cursor> IDBSQLiteBackingStore::openIndexKeyCursor(int64_t, int64_t, int64_t indexId, const IDBKeyRange* range, IDBCursor::Direction direction)
    914 {
    915     String sql = String("SELECT IndexData.id, IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, ")
    916                  + ("ObjectStoreData.keyString, ObjectStoreData.keyDate, ObjectStoreData.keyNumber ")
    917                  + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id WHERE ";
    918 
    919     bool lowerBound = range && range->lower();
    920     bool upperBound = range && range->upper();
    921 
    922     if (lowerBound)
    923         sql += lowerCursorWhereFragment(*range->lower(), range->lowerOpen() ? "<" : "<=", "IndexData.");
    924     if (upperBound)
    925         sql += upperCursorWhereFragment(*range->upper(), range->upperOpen() ? "<" : "<=", "IndexData.");
    926     sql += "IndexData.indexId = ? ORDER BY ";
    927 
    928     if (direction == IDBCursor::NEXT || direction == IDBCursor::NEXT_NO_DUPLICATE)
    929         sql += "IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, IndexData.id";
    930     else
    931         sql += "IndexData.keyString DESC, IndexData.keyDate DESC, IndexData.keyNumber DESC, IndexData.id DESC";
    932 
    933     RefPtr<IndexKeyCursorImpl> cursor = adoptRef(new IndexKeyCursorImpl(m_db, sql, direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::PREV_NO_DUPLICATE,
    934                                                                         direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::NEXT));
    935 
    936     bool ok = cursor->m_query.prepare() == SQLResultOk;
    937     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    938 
    939     int indexColumn = 1;
    940     if (lowerBound)
    941         indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->lower());
    942     if (upperBound)
    943         indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->upper());
    944     cursor->m_query.bindInt64(indexColumn, indexId);
    945 
    946     if (cursor->m_query.step() != SQLResultRow)
    947         return 0;
    948 
    949     cursor->loadCurrentRow();
    950     return cursor.release();
    951 }
    952 
    953 PassRefPtr<IDBBackingStore::Cursor> IDBSQLiteBackingStore::openIndexCursor(int64_t, int64_t, int64_t indexId, const IDBKeyRange* range, IDBCursor::Direction direction)
    954 {
    955     String sql = String("SELECT IndexData.id, IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, ")
    956                  + ("ObjectStoreData.value, ObjectStoreData.keyString, ObjectStoreData.keyDate, ObjectStoreData.keyNumber ")
    957                  + "FROM IndexData INNER JOIN ObjectStoreData ON IndexData.objectStoreDataId = ObjectStoreData.id WHERE ";
    958 
    959     bool lowerBound = range && range->lower();
    960     bool upperBound = range && range->upper();
    961 
    962     if (lowerBound)
    963         sql += lowerCursorWhereFragment(*range->lower(), range->lowerOpen() ? "<" : "<=", "IndexData.");
    964     if (upperBound)
    965         sql += upperCursorWhereFragment(*range->upper(), range->upperOpen() ? "<" : "<=", "IndexData.");
    966     sql += "IndexData.indexId = ? ORDER BY ";
    967 
    968     if (direction == IDBCursor::NEXT || direction == IDBCursor::NEXT_NO_DUPLICATE)
    969         sql += "IndexData.keyString, IndexData.keyDate, IndexData.keyNumber, IndexData.id";
    970     else
    971         sql += "IndexData.keyString DESC, IndexData.keyDate DESC, IndexData.keyNumber DESC, IndexData.id DESC";
    972 
    973     RefPtr<IndexCursorImpl> cursor = adoptRef(new IndexCursorImpl(m_db, sql, direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::PREV_NO_DUPLICATE,
    974                                                                   direction == IDBCursor::NEXT_NO_DUPLICATE || direction == IDBCursor::NEXT));
    975 
    976     bool ok = cursor->m_query.prepare() == SQLResultOk;
    977     ASSERT_UNUSED(ok, ok); // FIXME: Better error handling?
    978 
    979     int indexColumn = 1;
    980     if (lowerBound)
    981         indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->lower());
    982     if (upperBound)
    983         indexColumn += bindKeyToQuery(cursor->m_query, indexColumn, *range->upper());
    984     cursor->m_query.bindInt64(indexColumn, indexId);
    985 
    986     if (cursor->m_query.step() != SQLResultRow)
    987         return 0;
    988 
    989     cursor->loadCurrentRow();
    990     return cursor.release();
    991 }
    992 
    993 namespace {
    994 
    995 class TransactionImpl : public IDBBackingStore::Transaction {
    996 public:
    997     TransactionImpl(SQLiteDatabase& db)
    998         : m_transaction(db)
    999     {
   1000     }
   1001 
   1002     // IDBBackingStore::Transaction
   1003     virtual void begin() { m_transaction.begin(); }
   1004     virtual void commit() { m_transaction.commit(); }
   1005     virtual void rollback() { m_transaction.rollback(); }
   1006 
   1007 private:
   1008     SQLiteTransaction m_transaction;
   1009 };
   1010 
   1011 } // namespace
   1012 
   1013 PassRefPtr<IDBBackingStore::Transaction> IDBSQLiteBackingStore::createTransaction()
   1014 {
   1015     return adoptRef(new TransactionImpl(m_db));
   1016 }
   1017 
   1018 } // namespace WebCore
   1019 
   1020 #endif // ENABLE(INDEXED_DATABASE)
   1021