From 8985c6047cf4b470d8c2b2fe865c726c5d25514f Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Mon, 21 Nov 2011 20:18:25 -0800 Subject: [PATCH] Bug 702889 - IndexedDB: Change SQL schema and some cursor queries for faster performance. r=sicking. --HG-- extra : transplant_source : %14T5%5BR%94%8Dn%95%B5%2CMD%E9%EB%F4%EE9%DB5 --- dom/indexedDB/IDBIndex.cpp | 140 ++++--- dom/indexedDB/IDBObjectStore.cpp | 5 +- dom/indexedDB/IndexedDatabase.h | 2 +- dom/indexedDB/OpenDatabaseHelper.cpp | 524 +++++++++++++++++++++++---- 4 files changed, 515 insertions(+), 156 deletions(-) diff --git a/dom/indexedDB/IDBIndex.cpp b/dom/indexedDB/IDBIndex.cpp index 38ebc3a6175..a7d6580f9e3 100644 --- a/dom/indexedDB/IDBIndex.cpp +++ b/dom/indexedDB/IDBIndex.cpp @@ -1160,12 +1160,12 @@ OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) queryStart); mRangeKey = mKeyRange->Upper(); } - mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( value = :") + - currentKey + NS_LITERAL_CSTRING(" AND ") + keyColumn + + mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value >= :") + + currentKey + NS_LITERAL_CSTRING(" AND ( value > :") + + currentKey + NS_LITERAL_CSTRING(" OR ") + keyColumn + NS_LITERAL_CSTRING(" > :") + objectKey + - NS_LITERAL_CSTRING(" ) OR ( value > :") + currentKey + - NS_LITERAL_CSTRING(" ) )") + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); + NS_LITERAL_CSTRING(" )") + directionClause + + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value >= :") + currentKey + NS_LITERAL_CSTRING(" LIMIT "); break; @@ -1178,7 +1178,7 @@ OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) } mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value > :") + currentKey + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value >= :") + currentKey + directionClause + NS_LITERAL_CSTRING(" LIMIT "); @@ -1190,12 +1190,13 @@ OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) queryStart); mRangeKey = mKeyRange->Lower(); } - mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( value = :") + - currentKey + NS_LITERAL_CSTRING(" AND ") + keyColumn + + + mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value <= :") + + currentKey + NS_LITERAL_CSTRING(" AND ( value < :") + + currentKey + NS_LITERAL_CSTRING(" OR ") + keyColumn + NS_LITERAL_CSTRING(" < :") + objectKey + - NS_LITERAL_CSTRING(" ) OR ( value < :") + currentKey + - NS_LITERAL_CSTRING(" ) )") + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); + NS_LITERAL_CSTRING(" ) ") + directionClause + + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value <= :") + currentKey + NS_LITERAL_CSTRING(" LIMIT "); break; @@ -1208,7 +1209,7 @@ OpenKeyCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) } mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value < :") + currentKey + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value <= :") + currentKey + directionClause + NS_LITERAL_CSTRING(" LIMIT "); @@ -1251,7 +1252,7 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) if (mIndex->IsAutoIncrement()) { objectTable.AssignLiteral("ai_object_data"); objectDataIdColumn.AssignLiteral("ai_object_data_id"); - keyValueColumn.AssignLiteral("id"); + keyValueColumn.AssignLiteral("ai_object_data_id"); if (mIndex->IsUnique()) { indexTable.AssignLiteral("ai_unique_index_data"); } @@ -1262,7 +1263,7 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) else { objectTable.AssignLiteral("object_data"); objectDataIdColumn.AssignLiteral("object_data_id"); - keyValueColumn.AssignLiteral("key_value"); + keyValueColumn.AssignLiteral("object_data_key"); if (mIndex->IsUnique()) { indexTable.AssignLiteral("unique_index_data"); } @@ -1271,11 +1272,8 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) } } - NS_NAMED_LITERAL_CSTRING(id, "id"); - nsCString value = indexTable + NS_LITERAL_CSTRING(".value"); - nsCString data = objectTable + NS_LITERAL_CSTRING(".data"); - nsCString keyValue = objectTable + NS_LITERAL_CSTRING(".") + keyValueColumn; + nsCString keyValue = indexTable + NS_LITERAL_CSTRING(".") + keyValueColumn; nsCString keyRangeClause; if (mKeyRange) { @@ -1304,16 +1302,21 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) NS_NOTREACHED("Unknown direction!"); } - nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + value + - NS_LITERAL_CSTRING(", ") + keyValue + - NS_LITERAL_CSTRING(", ") + data + - NS_LITERAL_CSTRING(" FROM ") + objectTable + - NS_LITERAL_CSTRING(" INNER JOIN ") + indexTable + - NS_LITERAL_CSTRING(" ON ") + indexTable + - NS_LITERAL_CSTRING(".") + objectDataIdColumn + - NS_LITERAL_CSTRING(" = ") + objectTable + - NS_LITERAL_CSTRING(".id WHERE ") + indexTable + - NS_LITERAL_CSTRING(".index_id = :id") + + NS_NAMED_LITERAL_CSTRING(id, "id"); + NS_NAMED_LITERAL_CSTRING(dot, "."); + NS_NAMED_LITERAL_CSTRING(commaspace, ", "); + + nsCString data = objectTable + NS_LITERAL_CSTRING(".data"); + + nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + value + commaspace + + keyValue + commaspace + data + + NS_LITERAL_CSTRING(" FROM ") + indexTable + + NS_LITERAL_CSTRING(" INNER JOIN ") + objectTable + + NS_LITERAL_CSTRING(" ON ") + indexTable + dot + + objectDataIdColumn + NS_LITERAL_CSTRING(" = ") + + objectTable + dot + id + + NS_LITERAL_CSTRING(" WHERE ") + indexTable + + NS_LITERAL_CSTRING(".index_id = :") + id + keyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); @@ -1352,20 +1355,29 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) // Now we need to make the query to get the next match. nsCAutoString queryStart = NS_LITERAL_CSTRING("SELECT ") + value + - NS_LITERAL_CSTRING(", ") + keyValue + - NS_LITERAL_CSTRING(", ") + data + - NS_LITERAL_CSTRING(" FROM ") + objectTable + - NS_LITERAL_CSTRING(" INNER JOIN ") + indexTable + - NS_LITERAL_CSTRING(" ON ") + indexTable + - NS_LITERAL_CSTRING(".") + objectDataIdColumn + - NS_LITERAL_CSTRING(" = ") + objectTable + - NS_LITERAL_CSTRING(".id WHERE ") + indexTable + - NS_LITERAL_CSTRING(".index_id = :id"); + commaspace + keyValue + commaspace + data + + NS_LITERAL_CSTRING(" FROM ") + indexTable + + NS_LITERAL_CSTRING(" INNER JOIN ") + objectTable + + NS_LITERAL_CSTRING(" ON ") + indexTable + dot + + objectDataIdColumn + NS_LITERAL_CSTRING(" = ") + + objectTable + dot + id + + NS_LITERAL_CSTRING(" WHERE ") + indexTable + + NS_LITERAL_CSTRING(".index_id = :") + id; NS_NAMED_LITERAL_CSTRING(currentKey, "current_key"); NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key"); NS_NAMED_LITERAL_CSTRING(objectKey, "object_key"); + NS_NAMED_LITERAL_CSTRING(andStr, " AND "); + NS_NAMED_LITERAL_CSTRING(orStr, " OR "); + NS_NAMED_LITERAL_CSTRING(ge, " >= :"); + NS_NAMED_LITERAL_CSTRING(gt, " > :"); + NS_NAMED_LITERAL_CSTRING(le, " <= :"); + NS_NAMED_LITERAL_CSTRING(lt, " < :"); + NS_NAMED_LITERAL_CSTRING(openparen, " ( "); + NS_NAMED_LITERAL_CSTRING(closeparen, " ) "); + NS_NAMED_LITERAL_CSTRING(limit, " LIMIT "); + switch (mDirection) { case nsIIDBCursor::NEXT: if (mKeyRange && !mKeyRange->Upper().IsUnset()) { @@ -1373,18 +1385,11 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) queryStart); mRangeKey = mKeyRange->Upper(); } - mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( ") + - value + NS_LITERAL_CSTRING(" = :") + currentKey + - NS_LITERAL_CSTRING(" AND ") + keyValue + - NS_LITERAL_CSTRING(" > :") + objectKey + - NS_LITERAL_CSTRING(" ) OR ( ") + value + - NS_LITERAL_CSTRING(" > :") + currentKey + - NS_LITERAL_CSTRING(" ) )") + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); - mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND ") + value + - NS_LITERAL_CSTRING(" >= :") + - currentKey + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); + mContinueQuery = queryStart + andStr + value + ge + currentKey + andStr + + openparen + value + gt + currentKey + orStr + keyValue + + gt + objectKey + closeparen + directionClause + limit; + mContinueToQuery = queryStart + andStr + value + ge + currentKey + + directionClause + limit; break; case nsIIDBCursor::NEXT_NO_DUPLICATE: @@ -1393,12 +1398,10 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) queryStart); mRangeKey = mKeyRange->Upper(); } - mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ") + value + - NS_LITERAL_CSTRING(" > :") + currentKey + - directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); - mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND ") + value + - NS_LITERAL_CSTRING(" >= :") + currentKey + - directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); + mContinueQuery = queryStart + andStr + value + gt + currentKey + + directionClause + limit; + mContinueToQuery = queryStart + andStr + value + ge + currentKey + + directionClause + limit; break; case nsIIDBCursor::PREV: @@ -1407,18 +1410,11 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) queryStart); mRangeKey = mKeyRange->Lower(); } - mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( ") + - value + NS_LITERAL_CSTRING(" = :") + currentKey + - NS_LITERAL_CSTRING(" AND ") + keyValue + - NS_LITERAL_CSTRING(" < :") + objectKey + - NS_LITERAL_CSTRING(" ) OR ( ") + value + - NS_LITERAL_CSTRING(" < :") + currentKey + - NS_LITERAL_CSTRING(" ) )") + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); - mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND ") + value + - NS_LITERAL_CSTRING(" <= :") + - currentKey + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); + mContinueQuery = queryStart + andStr + value + le + currentKey + andStr + + openparen + value + lt + currentKey + orStr + keyValue + + lt + objectKey + closeparen + directionClause + limit; + mContinueToQuery = queryStart + andStr + value + le + currentKey + + directionClause + limit; break; case nsIIDBCursor::PREV_NO_DUPLICATE: @@ -1427,12 +1423,10 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) queryStart); mRangeKey = mKeyRange->Lower(); } - mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ") + value + - NS_LITERAL_CSTRING(" < :") + currentKey + - directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); - mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND ") + value + - NS_LITERAL_CSTRING(" <= :") + currentKey + - directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); + mContinueQuery = queryStart + andStr + value + lt + currentKey + + directionClause +limit; + mContinueToQuery = queryStart + andStr + value + le + currentKey + + directionClause + limit; break; default: diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index c68001488ae..181a67aa1e0 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -1954,7 +1954,8 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + NS_LITERAL_CSTRING(", data FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + - id + keyRangeClause + directionClause; + id + keyRangeClause + directionClause + + NS_LITERAL_CSTRING(" LIMIT 1"); nsCOMPtr stmt = mTransaction->GetCachedStatement(firstQuery); @@ -2033,7 +2034,7 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) NS_LITERAL_CSTRING(", data FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id + keyRangeClause + directionClause + - NS_LITERAL_CSTRING(" LIMIT 1"); + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + NS_LITERAL_CSTRING(", data FROM ") + table + diff --git a/dom/indexedDB/IndexedDatabase.h b/dom/indexedDB/IndexedDatabase.h index db956429a15..30bab9803ff 100644 --- a/dom/indexedDB/IndexedDatabase.h +++ b/dom/indexedDB/IndexedDatabase.h @@ -51,7 +51,7 @@ #include "nsStringGlue.h" #include "nsTArray.h" -#define DB_SCHEMA_VERSION 5 +#define DB_SCHEMA_VERSION 6 #define BEGIN_INDEXEDDB_NAMESPACE \ namespace mozilla { namespace dom { namespace indexedDB { diff --git a/dom/indexedDB/OpenDatabaseHelper.cpp b/dom/indexedDB/OpenDatabaseHelper.cpp index 7a620113dc7..55126bac4e3 100644 --- a/dom/indexedDB/OpenDatabaseHelper.cpp +++ b/dom/indexedDB/OpenDatabaseHelper.cpp @@ -121,11 +121,10 @@ CreateTables(mozIStorageConnection* aDBConn) // Table `object_store` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE object_store (" - "id INTEGER, " + "id INTEGER PRIMARY KEY, " "name TEXT NOT NULL, " "key_path TEXT NOT NULL, " "auto_increment INTEGER NOT NULL DEFAULT 0, " - "PRIMARY KEY (id), " "UNIQUE (name)" ");" )); @@ -134,41 +133,30 @@ CreateTables(mozIStorageConnection* aDBConn) // Table `object_data` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE object_data (" - "id INTEGER, " + "id INTEGER PRIMARY KEY, " "object_store_id INTEGER NOT NULL, " + "key_value DEFAULT NULL, " "data BLOB NOT NULL, " - "key_value DEFAULT NULL, " // NONE affinity - "PRIMARY KEY (id), " + "UNIQUE (object_store_id, key_value), " "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " "CASCADE" ");" )); NS_ENSURE_SUCCESS(rv, rv); - rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE UNIQUE INDEX key_index " - "ON object_data (key_value, object_store_id);" - )); - NS_ENSURE_SUCCESS(rv, rv); - // Table `ai_object_data` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE ai_object_data (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "object_store_id INTEGER NOT NULL, " "data BLOB NOT NULL, " + "UNIQUE (object_store_id, id), " "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " "CASCADE" ");" )); NS_ENSURE_SUCCESS(rv, rv); - rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE UNIQUE INDEX ai_key_index " - "ON ai_object_data (id, object_store_id);" - )); - NS_ENSURE_SUCCESS(rv, rv); - // Table `index` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE object_store_index (" @@ -189,12 +177,11 @@ CreateTables(mozIStorageConnection* aDBConn) // Table `index_data` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE index_data (" - "id INTEGER, " "index_id INTEGER NOT NULL, " - "object_data_id INTEGER NOT NULL, " - "object_data_key NOT NULL, " // NONE affinity "value NOT NULL, " - "PRIMARY KEY (id), " + "object_data_key NOT NULL, " + "object_data_id INTEGER NOT NULL, " + "PRIMARY KEY (index_id, value, object_data_key), " "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " "CASCADE, " "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " @@ -203,21 +190,21 @@ CreateTables(mozIStorageConnection* aDBConn) )); NS_ENSURE_SUCCESS(rv, rv); + // Need this to make cascading deletes from object_data and object_store fast. rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX value_index " - "ON index_data (index_id, value);" + "CREATE INDEX index_data_object_data_id_index " + "ON index_data (object_data_id);" )); NS_ENSURE_SUCCESS(rv, rv); // Table `unique_index_data` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE unique_index_data (" - "id INTEGER, " "index_id INTEGER NOT NULL, " - "object_data_id INTEGER NOT NULL, " - "object_data_key NOT NULL, " // NONE affinity "value NOT NULL, " - "PRIMARY KEY (id), " + "object_data_key NOT NULL, " // NONE affinity + "object_data_id INTEGER NOT NULL, " + "PRIMARY KEY (index_id, value, object_data_key), " "UNIQUE (index_id, value), " "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " "CASCADE " @@ -227,14 +214,20 @@ CreateTables(mozIStorageConnection* aDBConn) )); NS_ENSURE_SUCCESS(rv, rv); + // Need this to make cascading deletes from object_data and object_store fast. + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE INDEX unique_index_data_object_data_id_index " + "ON unique_index_data (object_data_id);" + )); + NS_ENSURE_SUCCESS(rv, rv); + // Table `ai_index_data` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE ai_index_data (" - "id INTEGER, " "index_id INTEGER NOT NULL, " - "ai_object_data_id INTEGER NOT NULL, " "value NOT NULL, " - "PRIMARY KEY (id), " + "ai_object_data_id INTEGER NOT NULL, " + "PRIMARY KEY (index_id, value, ai_object_data_id), " "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " "CASCADE, " "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE " @@ -243,21 +236,22 @@ CreateTables(mozIStorageConnection* aDBConn) )); NS_ENSURE_SUCCESS(rv, rv); + // Need this to make cascading deletes from ai_object_data and object_store + // fast. rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX ai_value_index " - "ON ai_index_data (index_id, value);" + "CREATE INDEX ai_index_data_ai_object_data_id_index " + "ON ai_index_data (ai_object_data_id);" )); NS_ENSURE_SUCCESS(rv, rv); // Table `ai_unique_index_data` rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE ai_unique_index_data (" - "id INTEGER, " "index_id INTEGER NOT NULL, " - "ai_object_data_id INTEGER NOT NULL, " "value NOT NULL, " - "PRIMARY KEY (id), " + "ai_object_data_id INTEGER NOT NULL, " "UNIQUE (index_id, value), " + "PRIMARY KEY (index_id, value, ai_object_data_id), " "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " "CASCADE, " "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE " @@ -266,6 +260,14 @@ CreateTables(mozIStorageConnection* aDBConn) )); NS_ENSURE_SUCCESS(rv, rv); + // Need this to make cascading deletes from ai_object_data and object_store + // fast. + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE INDEX ai_unique_index_data_ai_object_data_id_index " + "ON ai_unique_index_data (ai_object_data_id);" + )); + NS_ENSURE_SUCCESS(rv, rv); + rv = aDBConn->SetSchemaVersion(DB_SCHEMA_VERSION); NS_ENSURE_SUCCESS(rv, rv); @@ -377,7 +379,372 @@ UpgradeSchemaFrom4To5(mozIStorageConnection* aConnection) NS_ENSURE_SUCCESS(rv, rv); } - rv = aConnection->SetSchemaVersion(DB_SCHEMA_VERSION); + rv = aConnection->SetSchemaVersion(5); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +UpgradeSchemaFrom5To6(mozIStorageConnection* aConnection) +{ + mozStorageTransaction transaction(aConnection, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + + // Turn off foreign key constraints before we do anything here. + nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA foreign_keys = OFF;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // First, drop all the indexes we're no longer going to use. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP INDEX key_index;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP INDEX ai_key_index;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP INDEX value_index;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP INDEX ai_value_index;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // Now, reorder the columns of object_data to put the blob data last. We do + // this by copying into a temporary table, dropping the original, then copying + // back into a newly created table. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TEMPORARY TABLE temp_upgrade (" + "id INTEGER PRIMARY KEY, " + "object_store_id, " + "key_value, " + "data " + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO temp_upgrade " + "SELECT id, object_store_id, key_value, data " + "FROM object_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE object_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE object_data (" + "id INTEGER PRIMARY KEY, " + "object_store_id INTEGER NOT NULL, " + "key_value DEFAULT NULL, " + "data BLOB NOT NULL, " + "UNIQUE (object_store_id, key_value), " + "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " + "CASCADE" + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO object_data " + "SELECT id, object_store_id, key_value, data " + "FROM temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // We need to add a unique constraint to our ai_object_data table. Copy all + // the data out of it using a temporary table as before. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TEMPORARY TABLE temp_upgrade (" + "id INTEGER PRIMARY KEY, " + "object_store_id, " + "data " + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO temp_upgrade " + "SELECT id, object_store_id, data " + "FROM ai_object_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE ai_object_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE ai_object_data (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "object_store_id INTEGER NOT NULL, " + "data BLOB NOT NULL, " + "UNIQUE (object_store_id, id), " + "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " + "CASCADE" + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO ai_object_data " + "SELECT id, object_store_id, data " + "FROM temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // Fix up the index_data table. We're reordering the columns as well as + // changing the primary key from being a simple id to being a composite. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TEMPORARY TABLE temp_upgrade (" + "index_id, " + "value, " + "object_data_key, " + "object_data_id " + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO temp_upgrade " + "SELECT index_id, value, object_data_key, object_data_id " + "FROM index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE index_data (" + "index_id INTEGER NOT NULL, " + "value NOT NULL, " + "object_data_key NOT NULL, " + "object_data_id INTEGER NOT NULL, " + "PRIMARY KEY (index_id, value, object_data_key), " + "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " + "CASCADE, " + "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " + "CASCADE" + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT OR IGNORE INTO index_data " + "SELECT index_id, value, object_data_key, object_data_id " + "FROM temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE INDEX index_data_object_data_id_index " + "ON index_data (object_data_id);" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // Fix up the unique_index_data table. We're reordering the columns as well as + // changing the primary key from being a simple id to being a composite. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TEMPORARY TABLE temp_upgrade (" + "index_id, " + "value, " + "object_data_key, " + "object_data_id " + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO temp_upgrade " + "SELECT index_id, value, object_data_key, object_data_id " + "FROM unique_index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE unique_index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE unique_index_data (" + "index_id INTEGER NOT NULL, " + "value NOT NULL, " + "object_data_key NOT NULL, " + "object_data_id INTEGER NOT NULL, " + "PRIMARY KEY (index_id, value, object_data_key), " + "UNIQUE (index_id, value), " + "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " + "CASCADE " + "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " + "CASCADE" + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO unique_index_data " + "SELECT index_id, value, object_data_key, object_data_id " + "FROM temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE INDEX unique_index_data_object_data_id_index " + "ON unique_index_data (object_data_id);" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // Fix up the ai_index_data table. We're reordering the columns as well as + // changing the primary key from being a simple id to being a composite. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TEMPORARY TABLE temp_upgrade (" + "index_id, " + "value, " + "ai_object_data_id " + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO temp_upgrade " + "SELECT index_id, value, ai_object_data_id " + "FROM ai_index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE ai_index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE ai_index_data (" + "index_id INTEGER NOT NULL, " + "value NOT NULL, " + "ai_object_data_id INTEGER NOT NULL, " + "PRIMARY KEY (index_id, value, ai_object_data_id), " + "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " + "CASCADE, " + "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE " + "CASCADE" + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT OR IGNORE INTO ai_index_data " + "SELECT index_id, value, ai_object_data_id " + "FROM temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE INDEX ai_index_data_ai_object_data_id_index " + "ON ai_index_data (ai_object_data_id);" + )); + NS_ENSURE_SUCCESS(rv, rv); + + // Fix up the ai_unique_index_data table. We're reordering the columns as well + // as changing the primary key from being a simple id to being a composite. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TEMPORARY TABLE temp_upgrade (" + "index_id, " + "value, " + "ai_object_data_id " + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO temp_upgrade " + "SELECT index_id, value, ai_object_data_id " + "FROM ai_unique_index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE ai_unique_index_data;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE ai_unique_index_data (" + "index_id INTEGER NOT NULL, " + "value NOT NULL, " + "ai_object_data_id INTEGER NOT NULL, " + "UNIQUE (index_id, value), " + "PRIMARY KEY (index_id, value, ai_object_data_id), " + "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " + "CASCADE, " + "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE " + "CASCADE" + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO ai_unique_index_data " + "SELECT index_id, value, ai_object_data_id " + "FROM temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE temp_upgrade;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE INDEX ai_unique_index_data_ai_object_data_id_index " + "ON ai_unique_index_data (ai_object_data_id);" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->SetSchemaVersion(6); NS_ENSURE_SUCCESS(rv, rv); rv = transaction.Commit(); @@ -393,14 +760,6 @@ CreateDatabaseConnection(const nsAString& aName, { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - nsCOMPtr dbDirectory; - nsresult rv = aDBFile->GetParent(getter_AddRefs(dbDirectory)); - NS_ENSURE_SUCCESS(rv, rv); - - bool exists; - rv = aDBFile->Exists(&exists); - NS_ENSURE_SUCCESS(rv, rv); - NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota"); nsCOMPtr ss = @@ -408,15 +767,13 @@ CreateDatabaseConnection(const nsAString& aName, NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); nsCOMPtr connection; - rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, - getter_AddRefs(connection)); + nsresult rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, + getter_AddRefs(connection)); if (rv == NS_ERROR_FILE_CORRUPTED) { // Nuke the database file. The web services can recreate their data. rv = aDBFile->Remove(false); NS_ENSURE_SUCCESS(rv, rv); - exists = false; - rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, getter_AddRefs(connection)); } @@ -427,42 +784,49 @@ CreateDatabaseConnection(const nsAString& aName, rv = connection->GetSchemaVersion(&schemaVersion); NS_ENSURE_SUCCESS(rv, rv); - if (schemaVersion != DB_SCHEMA_VERSION) { + if (!schemaVersion) { + // Brand new file, initialize our tables. + mozStorageTransaction transaction(connection, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + + rv = CreateTables(connection); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CreateMetaData(connection, aName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) && + schemaVersion == DB_SCHEMA_VERSION, + "CreateTables set a bad schema version!"); + } + else if (schemaVersion != DB_SCHEMA_VERSION) { // This logic needs to change next time we change the schema! - PR_STATIC_ASSERT(DB_SCHEMA_VERSION == 5); - if (schemaVersion == 4) { - rv = UpgradeSchemaFrom4To5(connection); - NS_ENSURE_SUCCESS(rv, rv); - } - else { - // Nuke it from orbit, it's the only way to be sure. - if (exists) { - // If the connection is not at the right schema version, nuke it. - rv = aDBFile->Remove(false); - NS_ENSURE_SUCCESS(rv, rv); + PR_STATIC_ASSERT(DB_SCHEMA_VERSION == 6); - rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, - getter_AddRefs(connection)); - NS_ENSURE_SUCCESS(rv, rv); - } - - mozStorageTransaction transaction(connection, false, - mozIStorageConnection::TRANSACTION_IMMEDIATE); - rv = CreateTables(connection); - NS_ENSURE_SUCCESS(rv, rv); - - rv = CreateMetaData(connection, aName); - NS_ENSURE_SUCCESS(rv, rv); - - rv = transaction.Commit(); - NS_ENSURE_SUCCESS(rv, rv); - } +#define UPGRADE_SCHEMA_CASE(_from, _to) \ + if (schemaVersion == _from) { \ + rv = UpgradeSchemaFrom##_from##To##_to (connection); \ + NS_ENSURE_SUCCESS(rv, rv); \ + \ + rv = connection->GetSchemaVersion(&schemaVersion); \ + NS_ENSURE_SUCCESS(rv, rv); \ + \ + NS_ASSERTION(schemaVersion == _to, "Bad upgrade function!"); \ } - // Check to make sure that the database schema is correct again. - NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) && - schemaVersion == DB_SCHEMA_VERSION, - "CreateTables failed!"); + UPGRADE_SCHEMA_CASE(4, 5) + UPGRADE_SCHEMA_CASE(5, 6) + +#undef UPGRADE_SCHEMA_CASE + + if (schemaVersion != DB_SCHEMA_VERSION) { + NS_WARNING("Unable to open IndexedDB database, schema doesn't match"); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + } // Turn on foreign key constraints. rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(