Commit 71fb8a94 authored by dumi@chromium.org's avatar dumi@chromium.org

WebCore: 1. Fix a bug in SQLiteTransaction: do not assume that COMMIT always

succeeds.
2. Jump straight to the transaction error callback when a
statement fails in a way that makes sqlite automatically rollback
the transaction.
3. Fix the code that handles the "quota reached" failure, as it is
one of the failures that lead to an automatic transaction
rollback.

Reviewed by Eric Seidel.

https://bugs.webkit.org/show_bug.cgi?id=34280

* platform/sql/SQLiteDatabase.cpp:
(WebCore::SQLiteDatabase::isAutoCommitOn):
* platform/sql/SQLiteDatabase.h:
* platform/sql/SQLiteTransaction.cpp:
(WebCore::SQLiteTransaction::begin):
(WebCore::SQLiteTransaction::commit):
(WebCore::SQLiteTransaction::rollback):
(WebCore::SQLiteTransaction::transactionWasRolledBackBySqlite):
* platform/sql/SQLiteTransaction.h:
* storage/SQLTransaction.cpp:
(WebCore::SQLTransaction::SQLTransaction):
(WebCore::SQLTransaction::runStatements):
(WebCore::SQLTransaction::runCurrentStatement):
(WebCore::SQLTransaction::handleCurrentStatementError):
(WebCore::SQLTransaction::deliverQuotaIncreaseCallback):

LayoutTests: 1. Enhance quota-tracking.html: if sqlite automatically rolls back
a transaction because of a statement failure, make sure the rest
of the statements in the transaction are not executed.
2. Fix the expectations for quota-tracking.html. Sqlite cannot
recover from reaching a DB's max size.

Reviewed by Eric Seidel.

* storage/quota-tracking-expected.txt:
* storage/quota-tracking.html:



git-svn-id: svn://svn.chromium.org/blink/trunk@54393 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 1b20de7f
2010-02-04 Dumitru Daniliuc <dumi@chromium.org>
Reviewed by Eric Seidel.
1. Enhance quota-tracking.html: if sqlite automatically rolls back
a transaction because of a statement failure, make sure the rest
of the statements in the transaction are not executed.
2. Fix the expectations for quota-tracking.html. Sqlite cannot
recover from reaching a DB's max size.
* storage/quota-tracking-expected.txt:
* storage/quota-tracking.html:
2010-02-04 Csaba Osztrogonác <ossy@webkit.org> 2010-02-04 Csaba Osztrogonác <ossy@webkit.org>
Unreviewed typo fix for r54379. Unreviewed typo fix for r54379.
......
UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{file, , 0} database:QuotaManagementDatabase2 UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{file, , 0} database:QuotaManagementDatabase2
This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983. This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983.
The test clears all databases, sets the quota for the origin to 32k, then tries to insert 17k of data into two databases. If things go as planned, the UI Delegate will be informed of the exceeded quota. The test clears all databases, sets the quota for the origin to 32k, then tries to insert 17k of data into two databases. If things go as planned, the UI Delegate will be informed of the exceeded quota and should increase the quota for this origin. Inserting 17k of data the third time should succeed again.
Adding a table Adding a table
Inserting some data Inserting some data
Done adding data Done adding data
Adding a table Adding a table
Inserting some data Inserting some data
Expected quota exception - there was not enough remaining storage space, or the storage quota was reached and the user declined to allow more space
Done adding data
Adding a table
Inserting some data
Done adding data Done adding data
Test Complete Test Complete
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
<script> <script>
var database1; var database1;
var database2; var database2;
var database3;
function log(message) function log(message)
{ {
...@@ -16,19 +17,33 @@ function finishTest() ...@@ -16,19 +17,33 @@ function finishTest()
layoutTestController.notifyDone(); layoutTestController.notifyDone();
} }
function errorFunction(error) function statementErrorFunction(tx, error)
{ {
log("Test failed - " + error.message); log("Unexpected exception - " + error.message);
finishTest(); finishTest();
} }
function transactionErrorFunction(db, error)
{
// We only expect an error message for database2
if (db == database2) {
log("Expected quota exception - " + error.message);
checkCompletion(db);
} else {
log("Unexpected exception - " + error.message);
finishTest();
}
}
function checkCompletion(db) function checkCompletion(db)
{ {
log("Done adding data"); log("Done adding data");
db.complete = true; db.complete = true;
if (database1.complete && database2.complete) if (database1.complete && database2.complete && database3.complete)
finishTest(); finishTest();
else if (database2.complete)
testDatabase(database3);
else else
testDatabase(database2); testDatabase(database2);
} }
...@@ -38,9 +53,20 @@ function addData(db) ...@@ -38,9 +53,20 @@ function addData(db)
db.transaction(function(tx) { db.transaction(function(tx) {
log("Inserting some data"); log("Inserting some data");
tx.executeSql("INSERT INTO DataTest (randomData) VALUES (ZEROBLOB(17408))", [], tx.executeSql("INSERT INTO DataTest (randomData) VALUES (ZEROBLOB(17408))", [],
function(tx, result) { }, function(tx, result) { }, statementErrorFunction);
function(tx, error) { errorFunction(error); }); if (db == database2) {
}, errorFunction, function() { // Try to run this statement on 'database2' only.
// It should not be run, because the previous statement should've
// resulted in a failure that made sqlite roll back the entire transaction.
tx.executeSql("INSERT INTO DataTest (randomData) VALUES (ZEROBLOB(10))", [],
function(tx, result) {
log("This statement should not have been run.");
finishTest();
}, statementErrorFunction);
}
}, function(error) {
transactionErrorFunction(db, error);
}, function() {
checkCompletion(db); checkCompletion(db);
}); });
} }
...@@ -50,9 +76,10 @@ function testDatabase(db) ...@@ -50,9 +76,10 @@ function testDatabase(db)
db.transaction(function(tx) { db.transaction(function(tx) {
log("Adding a table"); log("Adding a table");
tx.executeSql("CREATE TABLE DataTest (randomData)", [], tx.executeSql("CREATE TABLE DataTest (randomData)", [],
function(tx, result) { }, function(tx, result) { }, statementErrorFunction);
function(tx, error) { errorFunction(error); }); }, function(error) {
}, errorFunction, function() { transactionErrorFunction(db, error);
}, function() {
addData(db); addData(db);
}); });
} }
...@@ -69,8 +96,10 @@ function runTest() ...@@ -69,8 +96,10 @@ function runTest()
database1 = openDatabase("QuotaManagementDatabase1", "1.0", "Test for quota management <rdar://5628468>", 1); database1 = openDatabase("QuotaManagementDatabase1", "1.0", "Test for quota management <rdar://5628468>", 1);
database2 = openDatabase("QuotaManagementDatabase2", "1.0", "Test for quota management <rdar://5628468>", 1); database2 = openDatabase("QuotaManagementDatabase2", "1.0", "Test for quota management <rdar://5628468>", 1);
database3 = openDatabase("QuotaManagementDatabase3", "1.0", "Test for quota management <rdar://5628468>", 1);
database1.complete = false; database1.complete = false;
database2.complete = false; database2.complete = false;
database3.complete = false;
testDatabase(database1); testDatabase(database1);
} }
...@@ -80,7 +109,7 @@ function runTest() ...@@ -80,7 +109,7 @@ function runTest()
<body onload="runTest()"> <body onload="runTest()">
This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983.<br> This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983.<br>
The test clears all databases, sets the quota for the origin to 32k, then tries to insert 17k of data into two databases. If things go as planned, the UI Delegate will be informed of the exceeded quota. The test clears all databases, sets the quota for the origin to 32k, then tries to insert 17k of data into two databases. If things go as planned, the UI Delegate will be informed of the exceeded quota and should increase the quota for this origin. Inserting 17k of data the third time should succeed again.
<pre id="console"> <pre id="console">
</pre> </pre>
</body> </body>
......
2010-02-04 Dumitru Daniliuc <dumi@chromium.org>
Reviewed by Eric Seidel.
1. Fix a bug in SQLiteTransaction: do not assume that COMMIT always
succeeds.
2. Jump straight to the transaction error callback when a
statement fails in a way that makes sqlite automatically rollback
the transaction.
3. Fix the code that handles the "quota reached" failure, as it is
one of the failures that lead to an automatic transaction
rollback.
https://bugs.webkit.org/show_bug.cgi?id=34280
* platform/sql/SQLiteDatabase.cpp:
(WebCore::SQLiteDatabase::isAutoCommitOn):
* platform/sql/SQLiteDatabase.h:
* platform/sql/SQLiteTransaction.cpp:
(WebCore::SQLiteTransaction::begin):
(WebCore::SQLiteTransaction::commit):
(WebCore::SQLiteTransaction::rollback):
(WebCore::SQLiteTransaction::transactionWasRolledBackBySqlite):
* platform/sql/SQLiteTransaction.h:
* storage/SQLTransaction.cpp:
(WebCore::SQLTransaction::SQLTransaction):
(WebCore::SQLTransaction::runStatements):
(WebCore::SQLTransaction::runCurrentStatement):
(WebCore::SQLTransaction::handleCurrentStatementError):
(WebCore::SQLTransaction::deliverQuotaIncreaseCallback):
2010-02-04 Peter Kasting <pkasting@google.com> 2010-02-04 Peter Kasting <pkasting@google.com>
Not reviewed, rollback. Not reviewed, rollback.
......
...@@ -361,4 +361,9 @@ void SQLiteDatabase::unlock() ...@@ -361,4 +361,9 @@ void SQLiteDatabase::unlock()
m_lockingMutex.unlock(); m_lockingMutex.unlock();
} }
bool SQLiteDatabase::isAutoCommitOn() const
{
return sqlite3_get_autocommit(m_db);
}
} // namespace WebCore } // namespace WebCore
...@@ -106,6 +106,7 @@ public: ...@@ -106,6 +106,7 @@ public:
// (un)locks the database like a mutex // (un)locks the database like a mutex
void lock(); void lock();
void unlock(); void unlock();
bool isAutoCommitOn() const;
private: private:
static int authorizerFunction(void*, int, const char*, const char*, const char*, const char*); static int authorizerFunction(void*, int, const char*, const char*, const char*, const char*);
......
...@@ -55,33 +55,31 @@ void SQLiteTransaction::begin() ...@@ -55,33 +55,31 @@ void SQLiteTransaction::begin()
// http://www.sqlite.org/lang_transaction.html // http://www.sqlite.org/lang_transaction.html
// http://www.sqlite.org/lockingv3.html#locking // http://www.sqlite.org/lockingv3.html#locking
if (m_readOnly) if (m_readOnly)
m_inProgress = m_db.executeCommand("BEGIN;"); m_inProgress = m_db.executeCommand("BEGIN");
else else
m_inProgress = m_db.executeCommand("BEGIN IMMEDIATE;"); m_inProgress = m_db.executeCommand("BEGIN IMMEDIATE");
m_db.m_transactionInProgress = m_inProgress; m_db.m_transactionInProgress = m_inProgress;
} }
} }
void SQLiteTransaction::commit() void SQLiteTransaction::commit()
{ {
// FIXME: this code is buggy; it assumes that COMMIT always succeeds which is not the case:
// the transaction could've been silently rolled back before getting to the COMMIT statement
// (https://bugs.webkit.org/show_bug.cgi?id=34280). However, the rest of the code does not
// know how to deal with a premature rollback and a failed COMMIT at this moment, so until
// we figure out what to do with bug 34280, it's better to leave this code as it is.
if (m_inProgress) { if (m_inProgress) {
ASSERT(m_db.m_transactionInProgress); ASSERT(m_db.m_transactionInProgress);
m_db.executeCommand("COMMIT;"); m_inProgress = !m_db.executeCommand("COMMIT");
m_inProgress = false; m_db.m_transactionInProgress = m_inProgress;
m_db.m_transactionInProgress = false;
} }
} }
void SQLiteTransaction::rollback() void SQLiteTransaction::rollback()
{ {
// We do not use the 'm_inProgress = m_db.executeCommand("ROLLBACK")' construct here,
// because m_inProgress should always be set to false after a ROLLBACK, and
// m_db.executeCommand("ROLLBACK") can sometimes harmlessly fail, thus returning
// a non-zero/true result (http://www.sqlite.org/lang_transaction.html).
if (m_inProgress) { if (m_inProgress) {
ASSERT(m_db.m_transactionInProgress); ASSERT(m_db.m_transactionInProgress);
m_db.executeCommand("ROLLBACK;"); m_db.executeCommand("ROLLBACK");
m_inProgress = false; m_inProgress = false;
m_db.m_transactionInProgress = false; m_db.m_transactionInProgress = false;
} }
...@@ -95,4 +93,11 @@ void SQLiteTransaction::stop() ...@@ -95,4 +93,11 @@ void SQLiteTransaction::stop()
} }
} }
bool SQLiteTransaction::wasRolledBackBySqlite() const
{
// According to http://www.sqlite.org/c3ref/get_autocommit.html,
// the auto-commit flag should be off in the middle of a transaction
return m_inProgress && m_db.isAutoCommitOn();
}
} // namespace WebCore } // namespace WebCore
...@@ -44,6 +44,7 @@ public: ...@@ -44,6 +44,7 @@ public:
void stop(); void stop();
bool inProgress() const { return m_inProgress; } bool inProgress() const { return m_inProgress; }
bool wasRolledBackBySqlite() const;
private: private:
SQLiteDatabase& m_db; SQLiteDatabase& m_db;
bool m_inProgress; bool m_inProgress;
......
...@@ -313,7 +313,7 @@ void SQLTransaction::runStatements() ...@@ -313,7 +313,7 @@ void SQLTransaction::runStatements()
// If there is a series of statements queued up that are all successful and have no associated // If there is a series of statements queued up that are all successful and have no associated
// SQLStatementCallback objects, then we can burn through the queue // SQLStatementCallback objects, then we can burn through the queue
do { do {
if (m_shouldRetryCurrentStatement) { if (m_shouldRetryCurrentStatement && !m_sqliteTransaction->wasRolledBackBySqlite()) {
m_shouldRetryCurrentStatement = false; m_shouldRetryCurrentStatement = false;
// FIXME - Another place that needs fixing up after <rdar://problem/5628468> is addressed. // FIXME - Another place that needs fixing up after <rdar://problem/5628468> is addressed.
// See ::openTransactionAndPreflight() for discussion // See ::openTransactionAndPreflight() for discussion
...@@ -393,8 +393,8 @@ bool SQLTransaction::runCurrentStatement() ...@@ -393,8 +393,8 @@ bool SQLTransaction::runCurrentStatement()
void SQLTransaction::handleCurrentStatementError() void SQLTransaction::handleCurrentStatementError()
{ {
// Transaction Steps 6.error - Call the statement's error callback, but if there was no error callback, // Transaction Steps 6.error - Call the statement's error callback, but if there was no error callback,
// jump to the transaction error callback // or the transaction was rolled back, jump to the transaction error callback
if (m_currentStatement->hasStatementErrorCallback()) { if (m_currentStatement->hasStatementErrorCallback() && !m_sqliteTransaction->wasRolledBackBySqlite()) {
m_nextStep = &SQLTransaction::deliverStatementCallback; m_nextStep = &SQLTransaction::deliverStatementCallback;
LOG(StorageAPI, "Scheduling deliverStatementCallback for transaction %p\n", this); LOG(StorageAPI, "Scheduling deliverStatementCallback for transaction %p\n", this);
m_database->scheduleTransactionCallback(this); m_database->scheduleTransactionCallback(this);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment