• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
IDBSQLiteBackingStore(String identifier,IDBFactoryBackendImpl * factory)42 IDBSQLiteBackingStore::IDBSQLiteBackingStore(String identifier, IDBFactoryBackendImpl* factory)
43     : m_identifier(identifier)
44     , m_factory(factory)
45 {
46     m_factory->addIDBBackingStore(identifier, this);
47 }
48 
~IDBSQLiteBackingStore()49 IDBSQLiteBackingStore::~IDBSQLiteBackingStore()
50 {
51     m_factory->removeIDBBackingStore(m_identifier);
52 }
53 
runCommands(SQLiteDatabase & sqliteDatabase,const char ** commands,size_t numberOfCommands)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 
createTables(SQLiteDatabase & sqliteDatabase)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 
createMetaDataTable(SQLiteDatabase & sqliteDatabase)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 
getDatabaseSchemaVersion(SQLiteDatabase & sqliteDatabase,int * databaseVersion)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 
migrateDatabase(SQLiteDatabase & sqliteDatabase)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 
open(SecurityOrigin * securityOrigin,const String & pathBase,int64_t maximumSize,const String & fileIdentifier,IDBFactoryBackendImpl * factory)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 
extractIDBDatabaseMetaData(const String & name,String & foundVersion,int64_t & foundId)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 
setIDBDatabaseMetaData(const String & name,const String & version,int64_t & rowId,bool invalidRowId)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 
getObjectStores(int64_t databaseId,Vector<int64_t> & foundIds,Vector<String> & foundNames,Vector<String> & foundKeyPaths,Vector<bool> & foundAutoIncrementFlags)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 
createObjectStore(int64_t databaseId,const String & name,const String & keyPath,bool autoIncrement,int64_t & assignedObjectStoreId)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 
doDelete(SQLiteDatabase & db,const char * sql,int64_t id)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 
deleteObjectStore(int64_t,int64_t objectStoreId)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:
create()305     static PassRefPtr<SQLiteRecordIdentifier> create() { return adoptRef(new SQLiteRecordIdentifier()); }
create(int64_t id)306     static PassRefPtr<SQLiteRecordIdentifier> create(int64_t id) { return adoptRef(new SQLiteRecordIdentifier(id)); }
isValid() const307     virtual bool isValid() const { return m_id != -1; }
id() const308     int64_t id() const { return m_id; }
setId(int64_t id)309     void setId(int64_t id) { m_id = id; }
310 private:
SQLiteRecordIdentifier()311     SQLiteRecordIdentifier() : m_id(-1) { }
SQLiteRecordIdentifier(int64_t id)312     SQLiteRecordIdentifier(int64_t id) : m_id(id) { }
313     int64_t m_id;
314 };
315 }
316 
createInvalidRecordIdentifier()317 PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> IDBSQLiteBackingStore::createInvalidRecordIdentifier()
318 {
319     return SQLiteRecordIdentifier::create();
320 }
321 
whereSyntaxForKey(const IDBKey & key,String qualifiedTableName="")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.
bindKeyToQuery(SQLiteStatement & query,int column,const IDBKey & key)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 
lowerCursorWhereFragment(const IDBKey & key,String comparisonOperator,String qualifiedTableName="")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 
upperCursorWhereFragment(const IDBKey & key,String comparisonOperator,String qualifiedTableName="")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 
getObjectStoreRecord(int64_t,int64_t objectStoreId,const IDBKey & key)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 
bindKeyToQueryWithNulls(SQLiteStatement & query,int baseColumn,const IDBKey & key)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 
putObjectStoreRecord(int64_t,int64_t objectStoreId,const IDBKey & key,const String & value,ObjectStoreRecordIdentifier * recordIdentifier)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 
clearObjectStore(int64_t,int64_t objectStoreId)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 
deleteObjectStoreRecord(int64_t,int64_t objectStoreId,const ObjectStoreRecordIdentifier * recordIdentifier)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 
nextAutoIncrementNumber(int64_t,int64_t objectStoreId)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 
keyExistsInObjectStore(int64_t,int64_t objectStoreId,const IDBKey & key,ObjectStoreRecordIdentifier * foundRecordIdentifier)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 
forEachObjectStoreRecord(int64_t,int64_t objectStoreId,ObjectStoreRecordCallback & callback)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 
getIndexes(int64_t,int64_t objectStoreId,Vector<int64_t> & foundIds,Vector<String> & foundNames,Vector<String> & foundKeyPaths,Vector<bool> & foundUniqueFlags)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 
createIndex(int64_t,int64_t objectStoreId,const String & name,const String & keyPath,bool isUnique,int64_t & indexId)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 
deleteIndex(int64_t,int64_t,int64_t indexId)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 
putIndexDataForRecord(int64_t,int64_t,int64_t indexId,const IDBKey & key,const ObjectStoreRecordIdentifier * recordIdentifier)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 
deleteIndexDataForRecord(int64_t,int64_t,int64_t indexId,const ObjectStoreRecordIdentifier * recordIdentifier)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 
getObjectViaIndex(int64_t,int64_t,int64_t indexId,const IDBKey & key)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 
keyFromQuery(SQLiteStatement & query,int baseColumn)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 
getPrimaryKeyViaIndex(int64_t,int64_t,int64_t indexId,const IDBKey & key)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 
keyExistsInIndex(int64_t,int64_t,int64_t indexId,const IDBKey & key)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:
CursorImplCommon(SQLiteDatabase & sqliteDatabase,String query,bool uniquenessConstraint,bool iterateForward)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     }
~CursorImplCommon()702     virtual ~CursorImplCommon() {}
703 
704     // IDBBackingStore::Cursor
705     virtual bool continueFunction(const IDBKey*);
key()706     virtual PassRefPtr<IDBKey> key() { return m_currentKey; }
primaryKey()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 
continueFunction(const IDBKey * key)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:
ObjectStoreCursorImpl(SQLiteDatabase & sqliteDatabase,String query,bool uniquenessConstraint,bool iterateForward)761     ObjectStoreCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
762         : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
763     {
764     }
765 
766     // CursorImplCommon.
value()767     virtual String value() { return m_currentValue; }
objectStoreRecordIdentifier()768     virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { return SQLiteRecordIdentifier::create(m_currentId); }
indexDataId()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 
loadCurrentRow()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 
currentRowExists()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:
IndexKeyCursorImpl(SQLiteDatabase & sqliteDatabase,String query,bool uniquenessConstraint,bool iterateForward)798     IndexKeyCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
799         : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
800     {
801     }
802 
803     // CursorImplCommon
primaryKey()804     virtual PassRefPtr<IDBKey> primaryKey() { return m_currentPrimaryKey; }
value()805     virtual String value() { ASSERT_NOT_REACHED(); return String(); }
objectStoreRecordIdentifier()806     virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { ASSERT_NOT_REACHED(); return 0; }
indexDataId()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 
loadCurrentRow()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 
currentRowExists()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:
IndexCursorImpl(SQLiteDatabase & sqliteDatabase,String query,bool uniquenessConstraint,bool iterateForward)836     IndexCursorImpl(SQLiteDatabase& sqliteDatabase, String query, bool uniquenessConstraint, bool iterateForward)
837         : CursorImplCommon(sqliteDatabase, query, uniquenessConstraint, iterateForward)
838     {
839     }
840 
841     // CursorImplCommon
primaryKey()842     virtual PassRefPtr<IDBKey> primaryKey() { return m_currentPrimaryKey; }
value()843     virtual String value() { return m_currentValue; }
objectStoreRecordIdentifier()844     virtual PassRefPtr<IDBBackingStore::ObjectStoreRecordIdentifier> objectStoreRecordIdentifier() { ASSERT_NOT_REACHED(); return 0; }
indexDataId()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 
loadCurrentRow()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 
currentRowExists()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 
openObjectStoreCursor(int64_t,int64_t objectStoreId,const IDBKeyRange * range,IDBCursor::Direction direction)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 
openIndexKeyCursor(int64_t,int64_t,int64_t indexId,const IDBKeyRange * range,IDBCursor::Direction direction)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 
openIndexCursor(int64_t,int64_t,int64_t indexId,const IDBKeyRange * range,IDBCursor::Direction direction)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:
TransactionImpl(SQLiteDatabase & db)997     TransactionImpl(SQLiteDatabase& db)
998         : m_transaction(db)
999     {
1000     }
1001 
1002     // IDBBackingStore::Transaction
begin()1003     virtual void begin() { m_transaction.begin(); }
commit()1004     virtual void commit() { m_transaction.commit(); }
rollback()1005     virtual void rollback() { m_transaction.rollback(); }
1006 
1007 private:
1008     SQLiteTransaction m_transaction;
1009 };
1010 
1011 } // namespace
1012 
createTransaction()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