Reviewed by Tim Hatcher.
[WebKit-https.git] / WebCore / storage / SQLTransaction.cpp
1 /*
2  * Copyright (C) 2007, 2008 Apple 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  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "SQLTransaction.h"
31
32 #include "ChromeClient.h"
33 #include "Database.h"
34 #include "DatabaseAuthorizer.h"
35 #include "DatabaseDetails.h"
36 #include "DatabaseTracker.h"
37 #include "Document.h"
38 #include "ExceptionCode.h"
39 #include "Logging.h"
40 #include "Page.h"
41 #include "PlatformString.h"
42 #include "SecurityOrigin.h"
43 #include "SQLError.h"
44 #include "SQLiteTransaction.h"
45 #include "SQLResultSet.h"
46 #include "SQLStatement.h"
47 #include "SQLStatementCallback.h"
48 #include "SQLStatementErrorCallback.h"
49 #include "SQLValue.h"
50
51 // There's no way of knowing exactly how much more space will be required when a statement hits the quota limit.  
52 // For now, we'll arbitrarily choose currentQuota + 1mb.
53 // In the future we decide to track if a size increase wasn't enough, and ask for larger-and-larger increases until its enough.
54 static const int DefaultQuotaSizeIncrease = 1048576;
55
56 namespace WebCore {
57
58 SQLTransaction::SQLTransaction(Database* db, PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, PassRefPtr<SQLTransactionWrapper> wrapper)
59     : m_nextStep(&SQLTransaction::openTransactionAndPreflight)
60     , m_executeSqlAllowed(false)
61     , m_database(db)
62     , m_wrapper(wrapper)
63     , m_callback(callback)
64     , m_errorCallback(errorCallback)
65     , m_shouldRetryCurrentStatement(false)
66     , m_shouldCommitAfterErrorCallback(true)
67     , m_modifiedDatabase(false)
68 {
69     ASSERT(m_database);
70 }
71
72 void SQLTransaction::executeSQL(const String& sqlStatement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> callbackError, ExceptionCode& e)
73 {
74     if (!m_executeSqlAllowed) {
75         e = INVALID_STATE_ERR;
76         return;
77     }
78     
79     RefPtr<SQLStatement> statement = new SQLStatement(sqlStatement.copy(), arguments, callback, callbackError);
80     
81     if (!m_database->versionMatchesExpected())
82         statement->setVersionMismatchedError();
83         
84     enqueueStatement(statement);
85 }
86
87 void SQLTransaction::enqueueStatement(PassRefPtr<SQLStatement> statement)
88 {
89     MutexLocker locker(m_statementMutex);
90     m_statementQueue.append(statement);
91 }
92
93 bool SQLTransaction::performNextStep()
94 {
95     ASSERT(m_nextStep == &SQLTransaction::openTransactionAndPreflight ||
96            m_nextStep == &SQLTransaction::runStatements ||
97            m_nextStep == &SQLTransaction::postflightAndCommit ||
98            m_nextStep == &SQLTransaction::cleanupAfterTransactionErrorCallback);
99                
100     (this->*m_nextStep)();
101
102     // If there is no nextStep after performing the above step, the transaction is complete
103     return !m_nextStep;
104 }
105
106 void SQLTransaction::performPendingCallback()
107 {
108     ASSERT(m_nextStep == &SQLTransaction::deliverTransactionCallback ||
109            m_nextStep == &SQLTransaction::deliverTransactionErrorCallback ||
110            m_nextStep == &SQLTransaction::deliverStatementCallback ||
111            m_nextStep == &SQLTransaction::deliverQuotaIncreaseCallback);
112            
113     (this->*m_nextStep)();
114 }
115
116 void SQLTransaction::openTransactionAndPreflight()
117 {
118     ASSERT(!m_database->m_sqliteDatabase.transactionInProgress());
119
120     LOG(StorageAPI, "Opening and preflighting transaction %p", this);
121     
122     // FIXME: This is a glaring bug that gives each database in an origin the full quota of that origin
123     // An efficient way to track the size of individual databases in an origin will need to be developed
124     // before we can know
125     // <rdar://problem/5628468> tracks this task
126     m_database->m_sqliteDatabase.setMaximumSize(DatabaseTracker::tracker().quotaForOrigin(m_database->securityOriginCopy().get()));
127     
128     ASSERT(!m_sqliteTransaction);
129     m_sqliteTransaction.set(new SQLiteTransaction(m_database->m_sqliteDatabase));
130     
131     m_database->m_databaseAuthorizer->disable();
132     m_sqliteTransaction->begin();
133     m_database->m_databaseAuthorizer->enable();    
134     
135     // Transaction Steps 1+2 - Open a transaction to the database, jumping to the error callback if that fails
136     if (!m_sqliteTransaction->inProgress()) {
137         ASSERT(!m_database->m_sqliteDatabase.transactionInProgress());
138         m_sqliteTransaction.clear();
139         m_transactionError = new SQLError(0, "unable to open a transaction to the database");
140         handleTransactionError(false);
141         return;
142     }
143     
144     // Transaction Steps 3 - Peform preflight steps, jumping to the error callback if they fail
145     if (m_wrapper && !m_wrapper->performPreflight(this)) {
146         ASSERT(!m_database->m_sqliteDatabase.transactionInProgress());
147         m_sqliteTransaction.clear();
148         m_transactionError = m_wrapper->sqlError();
149         if (!m_transactionError)
150             m_transactionError = new SQLError(0, "unknown error occured setting up transaction");
151
152         handleTransactionError(false);
153         return;
154     }
155     
156     // Transaction Step 4 - Invoke the transaction callback with the new SQLTransaction object
157     m_nextStep = &SQLTransaction::deliverTransactionCallback;
158     m_database->scheduleTransactionCallback(this);
159 }
160
161 void SQLTransaction::deliverTransactionCallback()
162 {
163     bool shouldDeliverErrorCallback = false;
164
165     if (m_callback) {
166         m_executeSqlAllowed = true;
167         m_callback->handleEvent(this, shouldDeliverErrorCallback);
168         m_executeSqlAllowed = false;
169     } else
170         shouldDeliverErrorCallback = true;
171
172     // Transaction Step 5 - If the transaction callback was null or raised an exception, jump to the error callback
173     if (shouldDeliverErrorCallback) {
174         m_transactionError = new SQLError(0, "the SQLTransactionCallback was null or threw an exception");
175         deliverTransactionErrorCallback();
176     } else
177         scheduleToRunStatements();
178 }
179
180 void SQLTransaction::scheduleToRunStatements()
181 {
182     m_currentStatement = 0;
183     m_nextStep = &SQLTransaction::runStatements;
184     m_database->scheduleTransactionStep();
185 }
186
187 void SQLTransaction::runStatements()
188 {
189     // If there is a series of statements queued up that are all successful and have no associated
190     // SQLStatementCallback objects, then we can burn through the queue
191     do {
192         if (m_shouldRetryCurrentStatement) {
193             m_shouldRetryCurrentStatement = false;
194             // FIXME - Another place that needs fixing up after <rdar://problem/5628468> is addressed.
195             // See ::openTransactionAndPreflight() for discussion
196             
197             // Reset the maximum size here, as it was increased to allow us to retry this statement
198             m_database->m_sqliteDatabase.setMaximumSize(DatabaseTracker::tracker().quotaForOrigin(m_database->securityOriginCopy().get()));
199         } else {
200             // If the current statement has already been run, failed due to quota constraints, and we're not retrying it,
201             // that means it ended in an error.  Handle it now
202             if (m_currentStatement && m_currentStatement->lastExecutionFailedDueToQuota()) {
203                 handleCurrentStatementError();
204                 break;
205             }
206             
207             // Otherwise, advance to the next statement
208             getNextStatement();
209         }
210     } while (runCurrentStatement());
211     
212     // If runCurrentStatement() returned false, that means either there was no current statement to run,
213     // or the current statement requires a callback to complete.  In the later case, it also scheduled 
214     // the callback or performed any other additional work so we can return
215     if (!m_currentStatement)
216         postflightAndCommit();
217 }
218
219 void SQLTransaction::getNextStatement()
220 {
221     m_currentStatement = 0;
222     
223     MutexLocker locker(m_statementMutex);
224     if (!m_statementQueue.isEmpty()) {
225         m_currentStatement = m_statementQueue.first();
226         m_statementQueue.removeFirst();
227     }
228 }
229
230 bool SQLTransaction::runCurrentStatement()
231 {
232     if (!m_currentStatement)
233         return false;
234         
235     m_database->m_databaseAuthorizer->reset();
236     
237     if (m_currentStatement->execute(m_database)) {
238         // Flag this transaction as having changed the database for later delegate notification
239         if (m_database->m_databaseAuthorizer->lastActionChangedDatabase())
240             m_modifiedDatabase = true;
241             
242         if (m_currentStatement->hasStatementCallback()) {
243             m_nextStep = &SQLTransaction::deliverStatementCallback;
244             m_database->scheduleTransactionCallback(this);
245             return false;
246         }
247         return true;
248     }
249     
250     if (m_currentStatement->lastExecutionFailedDueToQuota()) {
251         m_nextStep = &SQLTransaction::deliverQuotaIncreaseCallback;
252         m_database->scheduleTransactionCallback(this);
253         return false;
254     }
255     
256     handleCurrentStatementError();
257     
258     return false;
259 }
260
261 void SQLTransaction::handleCurrentStatementError()
262 {
263     // Transaction Steps 6.error - Call the statement's error callback, but if there was no error callback,
264     // jump to the transaction error callback
265     if (m_currentStatement->hasStatementErrorCallback()) {
266         m_nextStep = &SQLTransaction::deliverStatementCallback;
267         m_database->scheduleTransactionCallback(this);
268     } else {
269         m_transactionError = m_currentStatement->sqlError();
270         if (!m_transactionError)
271             m_transactionError = new SQLError(1, "the statement failed to execute");
272         handleTransactionError(false);
273     }
274 }
275
276 void SQLTransaction::deliverStatementCallback()
277 {
278     ASSERT(m_currentStatement);
279     
280     // Transaction Step 6.6 and 6.3(error) - If the statement callback went wrong, jump to the transaction error callback
281     // Otherwise, continue to loop through the statement queue
282     m_executeSqlAllowed = true;
283     bool result = m_currentStatement->performCallback(this);
284     m_executeSqlAllowed = false;
285
286     if (result) {
287         m_transactionError = new SQLError(0, "the statement callback raised an exception or statement error callback did not return false");
288         handleTransactionError(true);
289     } else
290         scheduleToRunStatements();
291 }
292
293 void SQLTransaction::deliverQuotaIncreaseCallback()
294 {
295     ASSERT(m_currentStatement);
296     ASSERT(!m_shouldRetryCurrentStatement);
297     
298     Page* page = m_database->document()->page();
299     ASSERT(page);
300     
301     RefPtr<SecurityOrigin> origin = m_database->securityOriginCopy();
302     
303     unsigned long long currentQuota = DatabaseTracker::tracker().quotaForOrigin(origin.get());
304     page->chrome()->client()->exceededDatabaseQuota(m_database->document()->frame(), m_database->stringIdentifier());
305     unsigned long long newQuota = DatabaseTracker::tracker().quotaForOrigin(origin.get());
306     
307     // If the new quota ended up being larger than the old quota, we will retry the statement.
308     if (newQuota > currentQuota)
309         m_shouldRetryCurrentStatement = true;
310         
311     m_nextStep = &SQLTransaction::runStatements;
312     m_database->scheduleTransactionStep();
313 }
314
315 void SQLTransaction::postflightAndCommit()
316 {    
317     // Transaction Step 7 - Peform postflight steps, jumping to the error callback if they fail
318     if (m_wrapper && !m_wrapper->performPostflight(this)) {
319         m_transactionError = m_wrapper->sqlError();
320         if (!m_transactionError)
321             m_transactionError = new SQLError(0, "unknown error occured setting up transaction");
322         handleTransactionError(false);
323         return;
324     }
325     
326     // Transacton Step 8+9 - Commit the transaction, jumping to the error callback if that fails
327     ASSERT(m_sqliteTransaction);
328     
329     m_database->m_databaseAuthorizer->disable();
330     m_sqliteTransaction->commit();
331     m_database->m_databaseAuthorizer->enable();    
332         
333     // If the commit failed, the transaction will still be marked as "in progress"
334     if (m_sqliteTransaction->inProgress()) {
335         m_shouldCommitAfterErrorCallback = false;
336         m_transactionError = new SQLError(0, "failed to commit the transaction");
337         handleTransactionError(false);
338         return;
339     }
340     
341     // The commit was successful, notify the delegates if the transaction modified this database
342     if (m_modifiedDatabase)
343         DatabaseTracker::tracker().scheduleNotifyDatabaseChanged(m_database->m_securityOrigin.get(), m_database->m_name);
344     
345     // Transaction Step 10 - End transaction steps
346     // There is no next step
347     ASSERT(!m_database->m_sqliteDatabase.transactionInProgress());
348     m_nextStep = 0;
349
350     // Now release our callbacks, to break reference cycles.
351     m_callback = 0;
352     m_errorCallback = 0;
353 }
354
355 void SQLTransaction::handleTransactionError(bool inCallback)
356 {
357     if (m_errorCallback) {
358         if (inCallback)
359             deliverTransactionErrorCallback();
360         else {
361             m_nextStep = &SQLTransaction::deliverTransactionErrorCallback;
362             m_database->scheduleTransactionCallback(this);
363         }
364         return;
365     }
366     
367     // Transaction Step 11 - If the callback couldn't be called, then rollback the transaction.
368     m_shouldCommitAfterErrorCallback = false;
369     if (inCallback) {
370         m_nextStep = &SQLTransaction::cleanupAfterTransactionErrorCallback;
371         m_database->scheduleTransactionStep();
372     } else {
373         cleanupAfterTransactionErrorCallback();
374     }
375 }
376
377 void SQLTransaction::deliverTransactionErrorCallback()
378 {
379     ASSERT(m_transactionError);
380     
381     // Transaction Step 11 - If the callback didn't return false, then rollback the transaction.
382     // This includes the callback not existing, returning true, or throwing an exception
383     if (!m_errorCallback || m_errorCallback->handleEvent(m_transactionError.get()))
384         m_shouldCommitAfterErrorCallback = false;
385
386     m_nextStep = &SQLTransaction::cleanupAfterTransactionErrorCallback;
387     m_database->scheduleTransactionStep();
388 }
389
390 void SQLTransaction::cleanupAfterTransactionErrorCallback()
391 {
392     m_database->m_databaseAuthorizer->disable();
393     if (m_sqliteTransaction) {
394         // Transaction Step 11 -If the error callback returned false, and the last error wasn't itself a 
395         // failure when committing the transaction, then try to commit the transaction
396         if (m_shouldCommitAfterErrorCallback)
397             m_sqliteTransaction->commit();
398         
399         if (m_sqliteTransaction->inProgress()) {
400             // Transaction Step 11 - If that fails, or if the callback couldn't be called 
401             // or if it didn't return false, then rollback the transaction.
402             m_sqliteTransaction->rollback();
403         } else if (m_modifiedDatabase) {
404             // But if the commit was successful, notify the delegates if the transaction modified this database
405             DatabaseTracker::tracker().scheduleNotifyDatabaseChanged(m_database->m_securityOrigin.get(), m_database->m_name);
406         }
407         
408         ASSERT(!m_database->m_sqliteDatabase.transactionInProgress());
409         m_sqliteTransaction.clear();
410     }
411     m_database->m_databaseAuthorizer->enable();
412     
413     // Transaction Step 11 - Any still-pending statements in the transaction are discarded.
414     {
415         MutexLocker locker(m_statementMutex);
416         m_statementQueue.clear();
417     }
418     
419     // Transaction is complete!  There is no next step
420     ASSERT(!m_database->m_sqliteDatabase.transactionInProgress());
421     m_nextStep = 0;
422
423     // Now release our callbacks, to break reference cycles.
424     m_callback = 0;
425     m_errorCallback = 0;
426 }
427
428 } // namespace WebCore