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