Title: [98749] trunk
Revision
98749
Author
[email protected]
Date
2011-10-28 12:22:27 -0700 (Fri, 28 Oct 2011)

Log Message

IndexedDB: Database metadata changes should be tied to transaction
https://bugs.webkit.org/show_bug.cgi?id=70974

Patch by Joshua Bell <[email protected]> on 2011-10-28
Reviewed by Tony Chang.

Source/WebCore:

Database metadata (that is, the version string) was rolled back
on abort by an abort task. If the abort task didn't run e.g.
due to a crash, the metadata would not be reverted. All of the
other store/index/data changes were written into the transaction
itself, so the metadata now is too. Refactored the metadata
get/create/update methods for clarity as well.

Note that the new tests don't actually verify that the code handles
this case; that will need to be done with persistence tests
that span multiple runs of the browser and induce crashes.
The new tests do verify that these changes don't cause regressions
not caught by other tests.

Tests: storage/indexeddb/open-during-transaction.html
       storage/indexeddb/version-change-abort.html

* storage/IDBBackingStore.h:
* storage/IDBDatabaseBackendImpl.cpp:
(WebCore::IDBDatabaseBackendImpl::IDBDatabaseBackendImpl):
(WebCore::IDBDatabaseBackendImpl::setVersionInternal):
* storage/IDBLevelDBBackingStore.cpp:
(WebCore::IDBLevelDBBackingStore::getIDBDatabaseMetaData):
(WebCore::IDBLevelDBBackingStore::createIDBDatabaseMetaData):
(WebCore::IDBLevelDBBackingStore::updateIDBDatabaseMetaData):
* storage/IDBLevelDBBackingStore.h:

LayoutTests:

* storage/indexeddb/open-during-transaction-expected.txt: Added.
* storage/indexeddb/open-during-transaction.html: Added.
* storage/indexeddb/version-change-abort-expected.txt: Added.
* storage/indexeddb/version-change-abort.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (98748 => 98749)


--- trunk/LayoutTests/ChangeLog	2011-10-28 19:15:26 UTC (rev 98748)
+++ trunk/LayoutTests/ChangeLog	2011-10-28 19:22:27 UTC (rev 98749)
@@ -1,3 +1,15 @@
+2011-10-28  Joshua Bell  <[email protected]>
+
+        IndexedDB: Database metadata changes should be tied to transaction
+        https://bugs.webkit.org/show_bug.cgi?id=70974
+
+        Reviewed by Tony Chang.
+
+        * storage/indexeddb/open-during-transaction-expected.txt: Added.
+        * storage/indexeddb/open-during-transaction.html: Added.
+        * storage/indexeddb/version-change-abort-expected.txt: Added.
+        * storage/indexeddb/version-change-abort.html: Added.
+
 2011-10-28  Simon Fraser  <[email protected]>
 
         Added some missing results for compositing visibilty tests.

Added: trunk/LayoutTests/storage/indexeddb/open-during-transaction-expected.txt (0 => 98749)


--- trunk/LayoutTests/storage/indexeddb/open-during-transaction-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/open-during-transaction-expected.txt	2011-10-28 19:22:27 UTC (rev 98749)
@@ -0,0 +1,42 @@
+Test IndexedDB opening database connections during transactions
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+PASS indexedDB == null is false
+IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
+PASS IDBTransaction == null is false
+
+prepare database
+openreq1 = indexedDB.open('db1')
+dbc1 = openreq1.result
+setverreq = dbc1.setVersion('1.0')
+dbc1.createObjectStore('storeName')
+database preparation complete
+
+starting transaction
+state = 'starting'
+trans = dbc1.transaction('storeName', IDBTransaction.READ_WRITE)
+trans.objectStore('storeName').put('value', 'key')
+
+trying to open the same database
+openreq2 = indexedDB.open('db1')
+
+trying to open a different database
+openreq3 = indexedDB.open('db2')
+
+openreq2.onsuccess
+PASS state is "starting"
+state = 'open2complete'
+
+openreq3.onsuccess
+PASS state is "open2complete"
+state = 'open3complete'
+
+transaction complete
+PASS state is "open3complete"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/open-during-transaction.html (0 => 98749)


--- trunk/LayoutTests/storage/indexeddb/open-during-transaction.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/open-during-transaction.html	2011-10-28 19:22:27 UTC (rev 98749)
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Test IndexedDB opening database connections during transactions");
+if (window.layoutTestController)
+    layoutTestController.waitUntilDone();
+
+function test()
+{
+    evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+    shouldBeFalse("indexedDB == null");
+    evalAndLog("IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;");
+    shouldBeFalse("IDBTransaction == null");
+    debug("");
+    prepareDatabase();
+}
+
+function prepareDatabase()
+{
+    debug("prepare database");
+    evalAndLog("openreq1 = indexedDB.open('db1')");
+    openreq1._onerror_ = unexpectedErrorCallback;
+    openreq1._onsuccess_ = function (e) {
+        evalAndLog("dbc1 = openreq1.result");
+        evalAndLog("setverreq = dbc1.setVersion('1.0')");
+        setverreq._onerror_ = unexpectedErrorCallback;
+        setverreq._onsuccess_ = function (e) {
+            evalAndLog("dbc1.createObjectStore('storeName')");
+            setverreq.result._oncomplete_ = function (e) {
+                debug("database preparation complete");
+                debug("");
+                startTransaction();
+            };
+        };
+    };
+}
+
+function startTransaction()
+{
+    debug("starting transaction");
+    evalAndLog("state = 'starting'");
+    evalAndLog("trans = dbc1.transaction('storeName', IDBTransaction.READ_WRITE)");
+    evalAndLog("trans.objectStore('storeName').put('value', 'key')");
+    trans._onabort_ = unexpectedAbortCallback;
+    trans._onerror_ = unexpectedErrorCallback;
+    trans._oncomplete_ = function (e) {
+        debug("transaction complete");
+        shouldBeEqualToString("state", "open3complete");
+        done();
+    };
+
+    debug("");
+    tryOpens();
+}
+
+
+function tryOpens()
+{
+    debug("trying to open the same database");
+    evalAndLog("openreq2 = indexedDB.open('db1')");
+    openreq2._onerror_ = unexpectedErrorCallback;
+    openreq2._onsuccess_ = function (e) {
+        debug("openreq2.onsuccess");
+        shouldBeEqualToString("state", "starting");
+        evalAndLog("state = 'open2complete'");
+        debug("");
+    }
+    debug("");
+
+    debug("trying to open a different database");
+    evalAndLog("openreq3 = indexedDB.open('db2')");
+    openreq3._onerror_ = unexpectedErrorCallback;
+    openreq3._onsuccess_ = function (e) {
+        debug("openreq3.onsuccess");
+        shouldBeEqualToString("state", "open2complete");
+        evalAndLog("state = 'open3complete'");
+        debug("");
+    }
+    debug("");
+}
+
+test();
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/storage/indexeddb/version-change-abort-expected.txt (0 => 98749)


--- trunk/LayoutTests/storage/indexeddb/version-change-abort-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/version-change-abort-expected.txt	2011-10-28 19:22:27 UTC (rev 98749)
@@ -0,0 +1,37 @@
+CONSOLE MESSAGE: line 86: Uncaught Error: This should *NOT* be caught!
+Ensure that aborted VERSION_CHANGE transactions are completely rolled back
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
+IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
+PASS indexedDB == null is false
+PASS IDBTransaction == null is false
+
+indexedDB.open('version-change-abort')
+db = event.target.result
+
+vcreq = db.setVersion('version 1')
+setVersion1() callback
+PASS vcreq.result instanceof IDBTransaction is true
+store = db.createObjectStore('store1')
+setVersion1 complete
+PASS db.version is "version 1"
+
+vcreq = db.setVersion('version 2')
+setVersion2() callback
+PASS db.version is "version 2"
+PASS vcreq.result instanceof IDBTransaction is true
+store = db.deleteObjectStore('store1')
+store = db.createObjectStore('store2')
+raising exception
+
+setVersion2Abort() callback
+PASS db.version is "version 1"
+PASS db.objectStoreNames.contains('store1') is true
+PASS db.objectStoreNames.contains('store2') is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/storage/indexeddb/version-change-abort.html (0 => 98749)


--- trunk/LayoutTests/storage/indexeddb/version-change-abort.html	                        (rev 0)
+++ trunk/LayoutTests/storage/indexeddb/version-change-abort.html	2011-10-28 19:22:27 UTC (rev 98749)
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description("Ensure that aborted VERSION_CHANGE transactions are completely rolled back");
+if (window.layoutTestController)
+    layoutTestController.waitUntilDone();
+
+
+function test() {
+    evalAndLog("indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;");
+    evalAndLog("IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;");
+    shouldBeFalse("indexedDB == null");
+    shouldBeFalse("IDBTransaction == null");
+    debug("");
+    openDBConnection();
+}
+
+function openDBConnection()
+{
+    request = evalAndLog("indexedDB.open('version-change-abort')");
+    request._onsuccess_ = openSuccess;
+    request._onerror_ = unexpectedErrorCallback;
+}
+
+function openSuccess()
+{
+    window.db = evalAndLog("db = event.target.result");
+    debug("");
+
+    evalAndLog("vcreq = db.setVersion('version 1')");
+    vcreq._onsuccess_ = inSetVersion1;
+    vcreq._onerror_ = unexpectedErrorCallback;
+}
+
+function inSetVersion1()
+{
+    debug("setVersion1() callback");
+    shouldBeTrue("vcreq.result instanceof IDBTransaction");
+    trans = vcreq.result;
+    trans._onabort_ = unexpectedAbortCallback;
+    trans._onerror_ = unexpectedErrorCallback;
+    trans._oncomplete_ = setVersion1Complete;
+
+    evalAndLog("store = db.createObjectStore('store1')");
+}
+
+function setVersion1Complete()
+{
+    debug("setVersion1 complete");
+    shouldBeEqualToString("db.version", "version 1");
+    debug("");
+
+    evalAndLog("vcreq = db.setVersion('version 2')");
+    vcreq._onsuccess_ = inSetVersion2;
+    vcreq._onerror_ = unexpectedErrorCallback;
+}
+
+function inSetVersion2()
+{
+    debug("setVersion2() callback");
+    shouldBeEqualToString("db.version", "version 2");
+    shouldBeTrue("vcreq.result instanceof IDBTransaction");
+    trans = vcreq.result;
+    trans._onabort_ = setVersion2Abort;
+    trans._onerror_ = unexpectedErrorCallback;
+    trans._oncomplete_ = unexpectedCompleteCallback;
+
+    evalAndLog("store = db.deleteObjectStore('store1')");
+    evalAndLog("store = db.createObjectStore('store2')");
+
+    // Ensure the test harness error handler is not invoked.
+    window.originalWindowOnError = window.onerror;
+    window._onerror_ = null;
+
+    debug("raising exception");
+    throw new Error("This should *NOT* be caught!");
+}
+
+function setVersion2Abort()
+{
+    debug("");
+    debug("setVersion2Abort() callback");
+
+    // Restore test harness error handler.
+    window._onerror_ = window.originalWindowOnError;
+
+    shouldBeEqualToString("db.version", "version 1");
+    shouldBeTrue("db.objectStoreNames.contains('store1')");
+    shouldBeFalse("db.objectStoreNames.contains('store2')");
+
+    done();
+}
+
+test();
+
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (98748 => 98749)


--- trunk/Source/WebCore/ChangeLog	2011-10-28 19:15:26 UTC (rev 98748)
+++ trunk/Source/WebCore/ChangeLog	2011-10-28 19:22:27 UTC (rev 98749)
@@ -1,3 +1,36 @@
+2011-10-28  Joshua Bell  <[email protected]>
+
+        IndexedDB: Database metadata changes should be tied to transaction
+        https://bugs.webkit.org/show_bug.cgi?id=70974
+
+        Reviewed by Tony Chang.
+
+        Database metadata (that is, the version string) was rolled back
+        on abort by an abort task. If the abort task didn't run e.g.
+        due to a crash, the metadata would not be reverted. All of the
+        other store/index/data changes were written into the transaction
+        itself, so the metadata now is too. Refactored the metadata
+        get/create/update methods for clarity as well.
+
+        Note that the new tests don't actually verify that the code handles
+        this case; that will need to be done with persistence tests
+        that span multiple runs of the browser and induce crashes.
+        The new tests do verify that these changes don't cause regressions
+        not caught by other tests.
+
+        Tests: storage/indexeddb/open-during-transaction.html
+               storage/indexeddb/version-change-abort.html
+
+        * storage/IDBBackingStore.h:
+        * storage/IDBDatabaseBackendImpl.cpp:
+        (WebCore::IDBDatabaseBackendImpl::IDBDatabaseBackendImpl):
+        (WebCore::IDBDatabaseBackendImpl::setVersionInternal):
+        * storage/IDBLevelDBBackingStore.cpp:
+        (WebCore::IDBLevelDBBackingStore::getIDBDatabaseMetaData):
+        (WebCore::IDBLevelDBBackingStore::createIDBDatabaseMetaData):
+        (WebCore::IDBLevelDBBackingStore::updateIDBDatabaseMetaData):
+        * storage/IDBLevelDBBackingStore.h:
+
 2011-10-28  Luke Macpherson   <[email protected]>
 
         Use enum instead of bool to represent -webkit-column-span property.

Modified: trunk/Source/WebCore/storage/IDBBackingStore.h (98748 => 98749)


--- trunk/Source/WebCore/storage/IDBBackingStore.h	2011-10-28 19:15:26 UTC (rev 98748)
+++ trunk/Source/WebCore/storage/IDBBackingStore.h	2011-10-28 19:22:27 UTC (rev 98749)
@@ -47,8 +47,9 @@
     virtual ~IDBBackingStore() {};
 
     virtual void getDatabaseNames(Vector<String>& foundNames) = 0;
-    virtual bool extractIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId) = 0;
-    virtual bool setIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId, bool invalidRowId) = 0;
+    virtual bool getIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId) = 0;
+    virtual bool createIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId) = 0;
+    virtual bool updateIDBDatabaseMetaData(int64_t rowId, const String& version) = 0;
 
     virtual void getObjectStores(int64_t databaseId, Vector<int64_t>& foundIds, Vector<String>& foundNames, Vector<String>& foundKeyPaths, Vector<bool>& foundAutoIncrementFlags) = 0;
     virtual bool createObjectStore(int64_t databaseId, const String& name, const String& keyPath, bool autoIncrement, int64_t& assignedObjectStoreId) = 0;

Modified: trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp (98748 => 98749)


--- trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp	2011-10-28 19:15:26 UTC (rev 98748)
+++ trunk/Source/WebCore/storage/IDBDatabaseBackendImpl.cpp	2011-10-28 19:22:27 UTC (rev 98749)
@@ -88,9 +88,9 @@
 {
     ASSERT(!m_name.isNull());
 
-    bool success = m_backingStore->extractIDBDatabaseMetaData(m_name, m_version, m_id);
-    ASSERT_UNUSED(success, success == (m_id != InvalidId));
-    if (!m_backingStore->setIDBDatabaseMetaData(m_name, m_version, m_id, m_id == InvalidId))
+    bool success = m_backingStore->getIDBDatabaseMetaData(m_name, m_version, m_id);
+    ASSERT(success == (m_id != InvalidId));
+    if (!success && !m_backingStore->createIDBDatabaseMetaData(m_name, m_version, m_id))
         ASSERT_NOT_REACHED(); // FIXME: Need better error handling.
     loadObjectStores();
 }
@@ -218,7 +218,7 @@
 {
     int64_t databaseId = database->id();
     database->m_version = version;
-    if (!database->m_backingStore->setIDBDatabaseMetaData(database->m_name, database->m_version, databaseId, databaseId == InvalidId)) {
+    if (!database->m_backingStore->updateIDBDatabaseMetaData(databaseId, database->m_version)) {
         // FIXME: The Indexed Database specification does not have an error code dedicated to I/O errors.
         callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage."));
         transaction->abort();

Modified: trunk/Source/WebCore/storage/IDBLevelDBBackingStore.cpp (98748 => 98749)


--- trunk/Source/WebCore/storage/IDBLevelDBBackingStore.cpp	2011-10-28 19:15:26 UTC (rev 98748)
+++ trunk/Source/WebCore/storage/IDBLevelDBBackingStore.cpp	2011-10-28 19:22:27 UTC (rev 98749)
@@ -186,7 +186,7 @@
     }
 }
 
-bool IDBLevelDBBackingStore::extractIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId)
+bool IDBLevelDBBackingStore::getIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId)
 {
     const Vector<char> key = DatabaseNameKey::encode(m_identifier, name);
 
@@ -216,21 +216,26 @@
     return databaseId;
 }
 
-bool IDBLevelDBBackingStore::setIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId, bool invalidRowId)
+bool IDBLevelDBBackingStore::createIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId)
 {
-    if (invalidRowId) {
-        rowId = getNewDatabaseId(m_db.get());
-        if (rowId < 0)
-            return false;
+    rowId = getNewDatabaseId(m_db.get());
+    if (rowId < 0)
+        return false;
 
-        const Vector<char> key = DatabaseNameKey::encode(m_identifier, name);
-        if (!putInt(m_db.get(), key, rowId))
-            return false;
-    }
-
+    const Vector<char> key = DatabaseNameKey::encode(m_identifier, name);
+    if (!putInt(m_db.get(), key, rowId))
+        return false;
     if (!putString(m_db.get(), DatabaseMetaDataKey::encode(rowId, DatabaseMetaDataKey::kUserVersion), version))
         return false;
+    return true;
+}
 
+bool IDBLevelDBBackingStore::updateIDBDatabaseMetaData(int64_t rowId, const String& version)
+{
+    ASSERT(m_currentTransaction);
+    if (!putString(m_currentTransaction.get(), DatabaseMetaDataKey::encode(rowId, DatabaseMetaDataKey::kUserVersion), version))
+        return false;
+
     return true;
 }
 

Modified: trunk/Source/WebCore/storage/IDBLevelDBBackingStore.h (98748 => 98749)


--- trunk/Source/WebCore/storage/IDBLevelDBBackingStore.h	2011-10-28 19:15:26 UTC (rev 98748)
+++ trunk/Source/WebCore/storage/IDBLevelDBBackingStore.h	2011-10-28 19:22:27 UTC (rev 98749)
@@ -45,8 +45,9 @@
     virtual ~IDBLevelDBBackingStore();
 
     virtual void getDatabaseNames(Vector<String>& foundNames);
-    virtual bool extractIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId);
-    virtual bool setIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId, bool invalidRowId);
+    virtual bool getIDBDatabaseMetaData(const String& name, String& foundVersion, int64_t& foundId);
+    virtual bool createIDBDatabaseMetaData(const String& name, const String& version, int64_t& rowId);
+    virtual bool updateIDBDatabaseMetaData(int64_t rowId, const String& version);
 
     virtual void getObjectStores(int64_t databaseId, Vector<int64_t>& foundIds, Vector<String>& foundNames, Vector<String>& foundKeyPaths, Vector<bool>& foundAutoIncrementFlags);
     virtual bool createObjectStore(int64_t databaseId, const String& name, const String& keyPath, bool autoIncrement, int64_t& assignedObjectStoreId);
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to