2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
32 #include "ChangeVersionWrapper.h"
34 #include "DatabaseAuthorizer.h"
35 #include "DatabaseTask.h"
36 #include "DatabaseThread.h"
37 #include "DatabaseTracker.h"
39 #include "ExceptionCode.h"
40 #include "FileSystem.h"
42 #include "InspectorController.h"
43 #include "JSDOMWindow.h"
45 #include "NotImplemented.h"
47 #include "OriginQuotaManager.h"
48 #include "SQLiteDatabase.h"
49 #include "SQLiteStatement.h"
50 #include "SQLResultSet.h"
51 #include <runtime/InitializeThreading.h>
52 #include <wtf/MainThread.h>
56 static Mutex
& guidMutex()
58 // Note: We don't have to use AtomicallyInitializedStatic here because
59 // this function is called once in the constructor on the main thread
60 // before any other threads that call this function are used.
61 static Mutex
& mutex
= *new Mutex
;
65 static HashMap
<int, String
>& guidToVersionMap()
67 static HashMap
<int, String
>& map
= *new HashMap
<int, String
>;
71 static HashMap
<int, HashSet
<Database
*>*>& guidToDatabaseMap()
73 static HashMap
<int, HashSet
<Database
*>*>& map
= *new HashMap
<int, HashSet
<Database
*>*>;
77 const String
& Database::databaseInfoTableName()
79 static String
& name
= *new String("__WebKitDatabaseInfoTable__");
83 static const String
& databaseVersionKey()
85 static String
& key
= *new String("WebKitDatabaseVersionKey");
89 static int guidForOriginAndName(const String
& origin
, const String
& name
);
91 PassRefPtr
<Database
> Database::openDatabase(Document
* document
, const String
& name
, const String
& expectedVersion
, const String
& displayName
, unsigned long estimatedSize
, ExceptionCode
& e
)
93 if (!DatabaseTracker::tracker().canEstablishDatabase(document
, name
, displayName
, estimatedSize
)) {
94 // FIXME: There should be an exception raised here in addition to returning a null Database object. The question has been raised with the WHATWG.
95 LOG(StorageAPI
, "Database %s for origin %s not allowed to be established", name
.ascii().data(), document
->securityOrigin()->toString().ascii().data());
99 RefPtr
<Database
> database
= adoptRef(new Database(document
, name
, expectedVersion
));
101 if (!database
->openAndVerifyVersion(e
)) {
102 LOG(StorageAPI
, "Failed to open and verify version (expected %s) of database %s", expectedVersion
.ascii().data(), database
->databaseDebugName().ascii().data());
106 DatabaseTracker::tracker().setDatabaseDetails(document
->securityOrigin(), name
, displayName
, estimatedSize
);
108 document
->setHasOpenDatabases();
110 if (Page
* page
= document
->frame()->page())
111 page
->inspectorController()->didOpenDatabase(database
.get(), document
->domain(), name
, expectedVersion
);
116 Database::Database(Document
* document
, const String
& name
, const String
& expectedVersion
)
117 : m_transactionInProgress(false)
118 , m_document(document
)
119 , m_name(name
.copy())
121 , m_expectedVersion(expectedVersion
)
126 m_securityOrigin
= document
->securityOrigin();
131 JSC::initializeThreading();
132 // Database code violates the normal JSCore contract by calling jsUnprotect from a secondary thread, and thus needs additional locking.
133 JSDOMWindow::commonJSGlobalData()->heap
.setGCProtectNeedsLocking();
135 m_guid
= guidForOriginAndName(m_securityOrigin
->toString(), name
);
138 MutexLocker
locker(guidMutex());
140 HashSet
<Database
*>* hashSet
= guidToDatabaseMap().get(m_guid
);
142 hashSet
= new HashSet
<Database
*>;
143 guidToDatabaseMap().set(m_guid
, hashSet
);
149 ASSERT(m_document
->databaseThread());
151 m_filename
= DatabaseTracker::tracker().fullPathForDatabase(m_securityOrigin
.get(), m_name
);
153 DatabaseTracker::tracker().addOpenDatabase(this);
154 m_document
->addOpenDatabase(this);
157 Database::~Database()
160 MutexLocker
locker(guidMutex());
162 HashSet
<Database
*>* hashSet
= guidToDatabaseMap().get(m_guid
);
164 ASSERT(hashSet
->contains(this));
165 hashSet
->remove(this);
166 if (hashSet
->isEmpty()) {
167 guidToDatabaseMap().remove(m_guid
);
169 guidToVersionMap().remove(m_guid
);
173 if (m_document
->databaseThread())
174 m_document
->databaseThread()->unscheduleDatabaseTasks(this);
176 DatabaseTracker::tracker().removeOpenDatabase(this);
177 m_document
->removeOpenDatabase(this);
180 bool Database::openAndVerifyVersion(ExceptionCode
& e
)
182 m_databaseAuthorizer
= DatabaseAuthorizer::create();
184 RefPtr
<DatabaseOpenTask
> task
= DatabaseOpenTask::create(this);
186 task
->lockForSynchronousScheduling();
187 m_document
->databaseThread()->scheduleImmediateTask(task
);
188 task
->waitForSynchronousCompletion();
190 ASSERT(task
->isComplete());
191 e
= task
->exceptionCode();
192 return task
->openSuccessful();
196 static bool retrieveTextResultFromDatabase(SQLiteDatabase
& db
, const String
& query
, String
& resultString
)
198 SQLiteStatement
statement(db
, query
);
199 int result
= statement
.prepare();
201 if (result
!= SQLResultOk
) {
202 LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result
, query
.ascii().data());
206 result
= statement
.step();
207 if (result
== SQLResultRow
) {
208 resultString
= statement
.getColumnText(0);
210 } else if (result
== SQLResultDone
) {
211 resultString
= String();
214 LOG_ERROR("Error (%i) reading text result from database (%s)", result
, query
.ascii().data());
219 bool Database::getVersionFromDatabase(String
& version
)
221 static String
& getVersionQuery
= *new String("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';");
223 m_databaseAuthorizer
->disable();
225 bool result
= retrieveTextResultFromDatabase(m_sqliteDatabase
, getVersionQuery
.copy(), version
);
227 LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data());
229 m_databaseAuthorizer
->enable();
234 static bool setTextValueInDatabase(SQLiteDatabase
& db
, const String
& query
, const String
& value
)
236 SQLiteStatement
statement(db
, query
);
237 int result
= statement
.prepare();
239 if (result
!= SQLResultOk
) {
240 LOG_ERROR("Failed to prepare statement to set value in database (%s)", query
.ascii().data());
244 statement
.bindText(1, value
);
246 result
= statement
.step();
247 if (result
!= SQLResultDone
) {
248 LOG_ERROR("Failed to step statement to set value in database (%s)", query
.ascii().data());
255 bool Database::setVersionInDatabase(const String
& version
)
257 static String
& setVersionQuery
= *new String("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);");
259 m_databaseAuthorizer
->disable();
261 bool result
= setTextValueInDatabase(m_sqliteDatabase
, setVersionQuery
.copy(), version
);
263 LOG_ERROR("Failed to set version %s in database (%s)", version
.ascii().data(), setVersionQuery
.ascii().data());
265 m_databaseAuthorizer
->enable();
270 bool Database::versionMatchesExpected() const
272 if (!m_expectedVersion
.isEmpty()) {
273 MutexLocker
locker(guidMutex());
274 return m_expectedVersion
== guidToVersionMap().get(m_guid
);
280 void Database::markAsDeletedAndClose()
285 LOG(StorageAPI
, "Marking %s (%p) as deleted", stringIdentifier().ascii().data(), this);
288 if (m_document
->databaseThread()->terminationRequested()) {
289 LOG(StorageAPI
, "Database handle %p is on a terminated DatabaseThread, cannot be marked for normal closure\n", this);
293 document()->databaseThread()->unscheduleDatabaseTasks(this);
295 RefPtr
<DatabaseCloseTask
> task
= DatabaseCloseTask::create(this);
297 task
->lockForSynchronousScheduling();
298 m_document
->databaseThread()->scheduleImmediateTask(task
);
299 task
->waitForSynchronousCompletion();
302 void Database::close()
304 m_sqliteDatabase
.close();
307 void Database::stop()
309 // FIXME: The net effect of the following code is to remove all pending transactions and statements, but allow the current statement
310 // to run to completion. In the future we can use the sqlite3_progress_handler or sqlite3_interrupt interfaces to cancel the current
311 // statement in response to close(), as well.
313 // This method is meant to be used as an analog to cancelling a loader, and is used when a document is shut down as the result of
314 // a page load or closing the page
318 MutexLocker
locker(m_transactionInProgressMutex
);
319 m_transactionQueue
.kill();
320 m_transactionInProgress
= false;
324 unsigned long long Database::databaseSize() const
327 if (!getFileSize(m_filename
, size
))
332 unsigned long long Database::maximumSize() const
334 // The maximum size for this database is the full quota for this origin, minus the current usage within this origin,
335 // except for the current usage of this database
337 OriginQuotaManager
& manager(DatabaseTracker::tracker().originQuotaManager());
338 Locker
<OriginQuotaManager
> locker(manager
);
340 return DatabaseTracker::tracker().quotaForOrigin(m_securityOrigin
.get()) - manager
.diskUsage(m_securityOrigin
.get()) + databaseSize();
343 void Database::disableAuthorizer()
345 ASSERT(m_databaseAuthorizer
);
346 m_databaseAuthorizer
->disable();
349 void Database::enableAuthorizer()
351 ASSERT(m_databaseAuthorizer
);
352 m_databaseAuthorizer
->enable();
355 static int guidForOriginAndName(const String
& origin
, const String
& name
)
358 if (origin
.endsWith("/"))
359 stringID
= origin
+ name
;
361 stringID
= origin
+ "/" + name
;
363 // Note: We don't have to use AtomicallyInitializedStatic here because
364 // this function is called once in the constructor on the main thread
365 // before any other threads that call this function are used.
366 static Mutex
& stringIdentifierMutex
= *new Mutex
;
367 MutexLocker
locker(stringIdentifierMutex
);
368 static HashMap
<String
, int>& stringIdentifierToGUIDMap
= *new HashMap
<String
, int>;
369 int guid
= stringIdentifierToGUIDMap
.get(stringID
);
371 static int currentNewGUID
= 1;
372 guid
= currentNewGUID
++;
373 stringIdentifierToGUIDMap
.set(stringID
, guid
);
379 void Database::resetAuthorizer()
381 if (m_databaseAuthorizer
)
382 m_databaseAuthorizer
->reset();
385 void Database::performPolicyChecks()
387 // FIXME: Code similar to the following will need to be run to enforce the per-origin size limit the spec mandates.
388 // Additionally, we might need a way to pause the database thread while the UA prompts the user for permission to
389 // increase the size limit
392 if (m_databaseAuthorizer->lastActionIncreasedSize())
393 DatabaseTracker::scheduleFileSizeCheckOnMainThread(this);
399 bool Database::performOpenAndVerify(ExceptionCode
& e
)
401 if (!m_sqliteDatabase
.open(m_filename
)) {
402 LOG_ERROR("Unable to open database at path %s", m_filename
.ascii().data());
403 e
= INVALID_STATE_ERR
;
407 ASSERT(m_databaseAuthorizer
);
408 m_sqliteDatabase
.setAuthorizer(m_databaseAuthorizer
);
410 if (!m_sqliteDatabase
.tableExists(databaseInfoTableName())) {
411 if (!m_sqliteDatabase
.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
412 LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data());
413 e
= INVALID_STATE_ERR
;
419 String currentVersion
;
421 MutexLocker
locker(guidMutex());
422 currentVersion
= guidToVersionMap().get(m_guid
);
424 if (currentVersion
.isNull())
425 LOG(StorageAPI
, "Current cached version for guid %i is null", m_guid
);
427 LOG(StorageAPI
, "Current cached version for guid %i is %s", m_guid
, currentVersion
.ascii().data());
429 if (currentVersion
.isNull()) {
430 if (!getVersionFromDatabase(currentVersion
)) {
431 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data());
432 e
= INVALID_STATE_ERR
;
435 if (currentVersion
.length()) {
436 LOG(StorageAPI
, "Retrieved current version %s from database %s", currentVersion
.ascii().data(), databaseDebugName().ascii().data());
438 LOG(StorageAPI
, "Setting version %s in database %s that was just created", m_expectedVersion
.ascii().data(), databaseDebugName().ascii().data());
439 if (!setVersionInDatabase(m_expectedVersion
)) {
440 LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion
.ascii().data(), databaseDebugName().ascii().data());
441 e
= INVALID_STATE_ERR
;
445 currentVersion
= m_expectedVersion
;
448 guidToVersionMap().set(m_guid
, currentVersion
.copy());
452 if (currentVersion
.isNull()) {
453 LOG(StorageAPI
, "Database %s does not have its version set", databaseDebugName().ascii().data());
457 // FIXME: For now, the spec says that if the database has no version, it is valid for any "Expected version" string. That seems silly and I think it should be
458 // changed, and here's where we would change it
459 if (m_expectedVersion
.length()) {
460 if (currentVersion
.length() && m_expectedVersion
!= currentVersion
) {
461 LOG(StorageAPI
, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion
.ascii().data(),
462 databaseDebugName().ascii().data(), currentVersion
.ascii().data());
463 e
= INVALID_STATE_ERR
;
471 void Database::changeVersion(const String
& oldVersion
, const String
& newVersion
,
472 PassRefPtr
<SQLTransactionCallback
> callback
, PassRefPtr
<SQLTransactionErrorCallback
> errorCallback
,
473 PassRefPtr
<VoidCallback
> successCallback
)
475 m_transactionQueue
.append(SQLTransaction::create(this, callback
, errorCallback
, successCallback
, ChangeVersionWrapper::create(oldVersion
, newVersion
)));
476 MutexLocker
locker(m_transactionInProgressMutex
);
477 if (!m_transactionInProgress
)
478 scheduleTransaction();
481 void Database::transaction(PassRefPtr
<SQLTransactionCallback
> callback
, PassRefPtr
<SQLTransactionErrorCallback
> errorCallback
,
482 PassRefPtr
<VoidCallback
> successCallback
)
484 m_transactionQueue
.append(SQLTransaction::create(this, callback
, errorCallback
, successCallback
, 0));
485 MutexLocker
locker(m_transactionInProgressMutex
);
486 if (!m_transactionInProgress
)
487 scheduleTransaction();
490 void Database::scheduleTransaction()
492 ASSERT(!m_transactionInProgressMutex
.tryLock()); // Locked by caller.
493 RefPtr
<SQLTransaction
> transaction
;
494 if (m_transactionQueue
.tryGetMessage(transaction
) && m_document
->databaseThread()) {
495 RefPtr
<DatabaseTransactionTask
> task
= DatabaseTransactionTask::create(transaction
);
496 LOG(StorageAPI
, "Scheduling DatabaseTransactionTask %p for transaction %p\n", task
.get(), task
->transaction());
497 m_transactionInProgress
= true;
498 m_document
->databaseThread()->scheduleTask(task
.release());
500 m_transactionInProgress
= false;
503 void Database::scheduleTransactionStep(SQLTransaction
* transaction
)
505 if (m_document
->databaseThread()) {
506 RefPtr
<DatabaseTransactionTask
> task
= DatabaseTransactionTask::create(transaction
);
507 LOG(StorageAPI
, "Scheduling DatabaseTransactionTask %p for the transaction step\n", task
.get());
508 m_document
->databaseThread()->scheduleTask(task
.release());
512 void Database::scheduleTransactionCallback(SQLTransaction
* transaction
)
515 callOnMainThread(deliverPendingCallback
, transaction
);
518 Vector
<String
> Database::performGetTableNames()
522 SQLiteStatement
statement(m_sqliteDatabase
, "SELECT name FROM sqlite_master WHERE type='table';");
523 if (statement
.prepare() != SQLResultOk
) {
524 LOG_ERROR("Unable to retrieve list of tables for database %s", databaseDebugName().ascii().data());
526 return Vector
<String
>();
529 Vector
<String
> tableNames
;
531 while ((result
= statement
.step()) == SQLResultRow
) {
532 String name
= statement
.getColumnText(0);
533 if (name
!= databaseInfoTableName())
534 tableNames
.append(name
);
539 if (result
!= SQLResultDone
) {
540 LOG_ERROR("Error getting tables for database %s", databaseDebugName().ascii().data());
541 return Vector
<String
>();
547 String
Database::version() const
551 MutexLocker
locker(guidMutex());
552 return guidToVersionMap().get(m_guid
).copy();
555 void Database::deliverPendingCallback(void* context
)
557 SQLTransaction
* transaction
= static_cast<SQLTransaction
*>(context
);
558 transaction
->performPendingCallback();
559 transaction
->deref(); // Was ref'd in scheduleTransactionCallback().
562 Vector
<String
> Database::tableNames()
564 RefPtr
<DatabaseTableNamesTask
> task
= DatabaseTableNamesTask::create(this);
566 task
->lockForSynchronousScheduling();
567 m_document
->databaseThread()->scheduleImmediateTask(task
);
568 task
->waitForSynchronousCompletion();
570 return task
->tableNames();
573 void Database::setExpectedVersion(const String
& version
)
575 m_expectedVersion
= version
.copy();
578 PassRefPtr
<SecurityOrigin
> Database::securityOriginCopy() const
580 return m_securityOrigin
->copy();
583 String
Database::stringIdentifier() const
585 // Return a deep copy for ref counting thread safety
586 return m_name
.copy();