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