Database.cpp revision 2fc2651226baac27029e38c9d6ef883fa32084db
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 "Database.h" 31 32#if ENABLE(DATABASE) 33#include "ChangeVersionWrapper.h" 34#include "DatabaseCallback.h" 35#include "DatabaseTask.h" 36#include "DatabaseThread.h" 37#include "DatabaseTracker.h" 38#include "Document.h" 39#include "InspectorDatabaseInstrumentation.h" 40#include "Logging.h" 41#include "NotImplemented.h" 42#include "Page.h" 43#include "SQLTransactionCallback.h" 44#include "SQLTransactionClient.h" 45#include "SQLTransactionCoordinator.h" 46#include "SQLTransactionErrorCallback.h" 47#include "SQLiteStatement.h" 48#include "ScriptController.h" 49#include "ScriptExecutionContext.h" 50#include "SecurityOrigin.h" 51#include "VoidCallback.h" 52#include <wtf/OwnPtr.h> 53#include <wtf/PassOwnPtr.h> 54#include <wtf/PassRefPtr.h> 55#include <wtf/RefPtr.h> 56#include <wtf/StdLibExtras.h> 57#include <wtf/text/CString.h> 58 59#if USE(JSC) 60#include "JSDOMWindow.h" 61#endif 62 63namespace WebCore { 64 65class DatabaseCreationCallbackTask : public ScriptExecutionContext::Task { 66public: 67 static PassOwnPtr<DatabaseCreationCallbackTask> create(PassRefPtr<Database> database, PassRefPtr<DatabaseCallback> creationCallback) 68 { 69 return adoptPtr(new DatabaseCreationCallbackTask(database, creationCallback)); 70 } 71 72 virtual void performTask(ScriptExecutionContext*) 73 { 74 m_creationCallback->handleEvent(m_database.get()); 75 } 76 77private: 78 DatabaseCreationCallbackTask(PassRefPtr<Database> database, PassRefPtr<DatabaseCallback> callback) 79 : m_database(database) 80 , m_creationCallback(callback) 81 { 82 } 83 84 RefPtr<Database> m_database; 85 RefPtr<DatabaseCallback> m_creationCallback; 86}; 87 88PassRefPtr<Database> Database::openDatabase(ScriptExecutionContext* context, const String& name, 89 const String& expectedVersion, const String& displayName, 90 unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback, 91 ExceptionCode& e) 92{ 93 if (!DatabaseTracker::tracker().canEstablishDatabase(context, name, displayName, estimatedSize)) { 94 LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.ascii().data(), context->securityOrigin()->toString().ascii().data()); 95 return 0; 96 } 97 98 RefPtr<Database> database = adoptRef(new Database(context, name, expectedVersion, displayName, estimatedSize)); 99 100 if (!database->openAndVerifyVersion(!creationCallback, e)) { 101 LOG(StorageAPI, "Failed to open and verify version (expected %s) of database %s", expectedVersion.ascii().data(), database->databaseDebugName().ascii().data()); 102 DatabaseTracker::tracker().removeOpenDatabase(database.get()); 103 return 0; 104 } 105 106 DatabaseTracker::tracker().setDatabaseDetails(context->securityOrigin(), name, displayName, estimatedSize); 107 108 context->setHasOpenDatabases(); 109 110 InspectorInstrumentation::didOpenDatabase(context, database, context->securityOrigin()->host(), name, expectedVersion); 111 112 // If it's a new database and a creation callback was provided, reset the expected 113 // version to "" and schedule the creation callback. Because of some subtle String 114 // implementation issues, we have to reset m_expectedVersion here instead of doing 115 // it inside performOpenAndVerify() which is run on the DB thread. 116 if (database->isNew() && creationCallback.get()) { 117 database->m_expectedVersion = ""; 118 LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get()); 119 database->m_scriptExecutionContext->postTask(DatabaseCreationCallbackTask::create(database, creationCallback)); 120 } 121 122 return database; 123} 124 125Database::Database(ScriptExecutionContext* context, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize) 126 : AbstractDatabase(context, name, expectedVersion, displayName, estimatedSize) 127 , m_transactionInProgress(false) 128 , m_isTransactionQueueEnabled(true) 129 , m_deleted(false) 130{ 131 m_databaseThreadSecurityOrigin = m_contextThreadSecurityOrigin->threadsafeCopy(); 132 133 ScriptController::initializeThreading(); 134 ASSERT(m_scriptExecutionContext->databaseThread()); 135} 136 137class DerefContextTask : public ScriptExecutionContext::Task { 138public: 139 static PassOwnPtr<DerefContextTask> create(PassRefPtr<ScriptExecutionContext> context) 140 { 141 return adoptPtr(new DerefContextTask(context)); 142 } 143 144 virtual void performTask(ScriptExecutionContext* context) 145 { 146 ASSERT_UNUSED(context, context == m_context); 147 m_context.clear(); 148 } 149 150 virtual bool isCleanupTask() const { return true; } 151 152private: 153 DerefContextTask(PassRefPtr<ScriptExecutionContext> context) 154 : m_context(context) 155 { 156 } 157 158 RefPtr<ScriptExecutionContext> m_context; 159}; 160 161Database::~Database() 162{ 163 // The reference to the ScriptExecutionContext needs to be cleared on the JavaScript thread. If we're on that thread already, we can just let the RefPtr's destruction do the dereffing. 164 if (!m_scriptExecutionContext->isContextThread()) { 165 // Grab a pointer to the script execution here because we're releasing it when we pass it to 166 // DerefContextTask::create. 167 ScriptExecutionContext* scriptExecutionContext = m_scriptExecutionContext.get(); 168 169 scriptExecutionContext->postTask(DerefContextTask::create(m_scriptExecutionContext.release())); 170 } 171} 172 173String Database::version() const 174{ 175 if (m_deleted) 176 return String(); 177 return AbstractDatabase::version(); 178} 179 180bool Database::openAndVerifyVersion(bool setVersionInNewDatabase, ExceptionCode& e) 181{ 182 DatabaseTaskSynchronizer synchronizer; 183 if (!m_scriptExecutionContext->databaseThread() || m_scriptExecutionContext->databaseThread()->terminationRequested(&synchronizer)) 184 return false; 185 186 bool success = false; 187 OwnPtr<DatabaseOpenTask> task = DatabaseOpenTask::create(this, setVersionInNewDatabase, &synchronizer, e, success); 188 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 189 synchronizer.waitForTaskCompletion(); 190 191 return success; 192} 193 194void Database::markAsDeletedAndClose() 195{ 196 if (m_deleted || !m_scriptExecutionContext->databaseThread()) 197 return; 198 199 LOG(StorageAPI, "Marking %s (%p) as deleted", stringIdentifier().ascii().data(), this); 200 m_deleted = true; 201 202 DatabaseTaskSynchronizer synchronizer; 203 if (m_scriptExecutionContext->databaseThread()->terminationRequested(&synchronizer)) { 204 LOG(StorageAPI, "Database handle %p is on a terminated DatabaseThread, cannot be marked for normal closure\n", this); 205 return; 206 } 207 208 OwnPtr<DatabaseCloseTask> task = DatabaseCloseTask::create(this, &synchronizer); 209 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 210 synchronizer.waitForTaskCompletion(); 211} 212 213void Database::close() 214{ 215 ASSERT(m_scriptExecutionContext->databaseThread()); 216 ASSERT(currentThread() == m_scriptExecutionContext->databaseThread()->getThreadID()); 217 218 { 219 MutexLocker locker(m_transactionInProgressMutex); 220 m_isTransactionQueueEnabled = false; 221 m_transactionInProgress = false; 222 } 223 224 closeDatabase(); 225 226 // Must ref() before calling databaseThread()->recordDatabaseClosed(). 227 RefPtr<Database> protect = this; 228 m_scriptExecutionContext->databaseThread()->recordDatabaseClosed(this); 229 m_scriptExecutionContext->databaseThread()->unscheduleDatabaseTasks(this); 230 DatabaseTracker::tracker().removeOpenDatabase(this); 231} 232 233void Database::closeImmediately() 234{ 235 DatabaseThread* databaseThread = scriptExecutionContext()->databaseThread(); 236 if (databaseThread && !databaseThread->terminationRequested() && opened()) 237 databaseThread->scheduleImmediateTask(DatabaseCloseTask::create(this, 0)); 238} 239 240unsigned long long Database::maximumSize() const 241{ 242 return DatabaseTracker::tracker().getMaxSizeForDatabase(this); 243} 244 245bool Database::performOpenAndVerify(bool setVersionInNewDatabase, ExceptionCode& e) 246{ 247 if (AbstractDatabase::performOpenAndVerify(setVersionInNewDatabase, e)) { 248 if (m_scriptExecutionContext->databaseThread()) 249 m_scriptExecutionContext->databaseThread()->recordDatabaseOpen(this); 250 251 return true; 252 } 253 254 return false; 255} 256 257void Database::changeVersion(const String& oldVersion, const String& newVersion, 258 PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, 259 PassRefPtr<VoidCallback> successCallback) 260{ 261 m_transactionQueue.append(SQLTransaction::create(this, callback, errorCallback, successCallback, ChangeVersionWrapper::create(oldVersion, newVersion))); 262 MutexLocker locker(m_transactionInProgressMutex); 263 if (!m_transactionInProgress) 264 scheduleTransaction(); 265} 266 267void Database::transaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, PassRefPtr<VoidCallback> successCallback) 268{ 269 runTransaction(callback, errorCallback, successCallback, false); 270} 271 272void Database::readTransaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, PassRefPtr<VoidCallback> successCallback) 273{ 274 runTransaction(callback, errorCallback, successCallback, true); 275} 276 277void Database::runTransaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, 278 PassRefPtr<VoidCallback> successCallback, bool readOnly) 279{ 280 m_transactionQueue.append(SQLTransaction::create(this, callback, errorCallback, successCallback, 0, readOnly)); 281 MutexLocker locker(m_transactionInProgressMutex); 282 if (!m_transactionInProgress) 283 scheduleTransaction(); 284} 285 286void Database::inProgressTransactionCompleted() 287{ 288 MutexLocker locker(m_transactionInProgressMutex); 289 m_transactionInProgress = false; 290 scheduleTransaction(); 291} 292 293void Database::scheduleTransaction() 294{ 295 ASSERT(!m_transactionInProgressMutex.tryLock()); // Locked by caller. 296 RefPtr<SQLTransaction> transaction; 297 298 if (m_isTransactionQueueEnabled && !m_transactionQueue.isEmpty()) { 299 transaction = m_transactionQueue.takeFirst(); 300 } 301 302 if (transaction && m_scriptExecutionContext->databaseThread()) { 303 OwnPtr<DatabaseTransactionTask> task = DatabaseTransactionTask::create(transaction); 304 LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for transaction %p\n", task.get(), task->transaction()); 305 m_transactionInProgress = true; 306 m_scriptExecutionContext->databaseThread()->scheduleTask(task.release()); 307 } else 308 m_transactionInProgress = false; 309} 310 311void Database::scheduleTransactionStep(SQLTransaction* transaction, bool immediately) 312{ 313 if (!m_scriptExecutionContext->databaseThread()) 314 return; 315 316 OwnPtr<DatabaseTransactionTask> task = DatabaseTransactionTask::create(transaction); 317 LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for the transaction step\n", task.get()); 318 if (immediately) 319 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 320 else 321 m_scriptExecutionContext->databaseThread()->scheduleTask(task.release()); 322} 323 324class DeliverPendingCallbackTask : public ScriptExecutionContext::Task { 325public: 326 static PassOwnPtr<DeliverPendingCallbackTask> create(PassRefPtr<SQLTransaction> transaction) 327 { 328 return adoptPtr(new DeliverPendingCallbackTask(transaction)); 329 } 330 331 virtual void performTask(ScriptExecutionContext*) 332 { 333 m_transaction->performPendingCallback(); 334 } 335 336private: 337 DeliverPendingCallbackTask(PassRefPtr<SQLTransaction> transaction) 338 : m_transaction(transaction) 339 { 340 } 341 342 RefPtr<SQLTransaction> m_transaction; 343}; 344 345void Database::scheduleTransactionCallback(SQLTransaction* transaction) 346{ 347 m_scriptExecutionContext->postTask(DeliverPendingCallbackTask::create(transaction)); 348} 349 350Vector<String> Database::performGetTableNames() 351{ 352 disableAuthorizer(); 353 354 SQLiteStatement statement(sqliteDatabase(), "SELECT name FROM sqlite_master WHERE type='table';"); 355 if (statement.prepare() != SQLResultOk) { 356 LOG_ERROR("Unable to retrieve list of tables for database %s", databaseDebugName().ascii().data()); 357 enableAuthorizer(); 358 return Vector<String>(); 359 } 360 361 Vector<String> tableNames; 362 int result; 363 while ((result = statement.step()) == SQLResultRow) { 364 String name = statement.getColumnText(0); 365 if (name != databaseInfoTableName()) 366 tableNames.append(name); 367 } 368 369 enableAuthorizer(); 370 371 if (result != SQLResultDone) { 372 LOG_ERROR("Error getting tables for database %s", databaseDebugName().ascii().data()); 373 return Vector<String>(); 374 } 375 376 return tableNames; 377} 378 379SQLTransactionClient* Database::transactionClient() const 380{ 381 return m_scriptExecutionContext->databaseThread()->transactionClient(); 382} 383 384SQLTransactionCoordinator* Database::transactionCoordinator() const 385{ 386 return m_scriptExecutionContext->databaseThread()->transactionCoordinator(); 387} 388 389Vector<String> Database::tableNames() 390{ 391 // FIXME: Not using threadsafeCopy on these strings looks ok since threads take strict turns 392 // in dealing with them. However, if the code changes, this may not be true anymore. 393 Vector<String> result; 394 DatabaseTaskSynchronizer synchronizer; 395 if (!m_scriptExecutionContext->databaseThread() || m_scriptExecutionContext->databaseThread()->terminationRequested(&synchronizer)) 396 return result; 397 398 OwnPtr<DatabaseTableNamesTask> task = DatabaseTableNamesTask::create(this, &synchronizer, result); 399 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 400 synchronizer.waitForTaskCompletion(); 401 402 return result; 403} 404 405SecurityOrigin* Database::securityOrigin() const 406{ 407 if (m_scriptExecutionContext->isContextThread()) 408 return m_contextThreadSecurityOrigin.get(); 409 if (currentThread() == m_scriptExecutionContext->databaseThread()->getThreadID()) 410 return m_databaseThreadSecurityOrigin.get(); 411 return 0; 412} 413 414} // namespace WebCore 415 416#endif // ENABLE(DATABASE) 417