Bug 692630: Support multi-entry indexes. r=bent

This commit is contained in:
Jonas Sicking 2011-12-04 09:39:01 -08:00
parent f8d6fe9362
commit 9a4a46521c
15 changed files with 512 additions and 147 deletions

View File

@ -1641,6 +1641,7 @@ jsid nsDOMClassInfo::sURL_id = JSID_VOID;
jsid nsDOMClassInfo::sKeyPath_id = JSID_VOID;
jsid nsDOMClassInfo::sAutoIncrement_id = JSID_VOID;
jsid nsDOMClassInfo::sUnique_id = JSID_VOID;
jsid nsDOMClassInfo::sMultiEntry_id = JSID_VOID;
jsid nsDOMClassInfo::sOnload_id = JSID_VOID;
jsid nsDOMClassInfo::sOnerror_id = JSID_VOID;
@ -1904,6 +1905,7 @@ nsDOMClassInfo::DefineStaticJSVals(JSContext *cx)
SET_JSID_TO_STRING(sKeyPath_id, cx, "keyPath");
SET_JSID_TO_STRING(sAutoIncrement_id, cx, "autoIncrement");
SET_JSID_TO_STRING(sUnique_id, cx, "unique");
SET_JSID_TO_STRING(sMultiEntry_id, cx, "multiEntry");
SET_JSID_TO_STRING(sOnload_id, cx, "onload");
SET_JSID_TO_STRING(sOnerror_id, cx, "onerror");
@ -4902,6 +4904,7 @@ nsDOMClassInfo::ShutDown()
sKeyPath_id = JSID_VOID;
sAutoIncrement_id = JSID_VOID;
sUnique_id = JSID_VOID;
sMultiEntry_id = JSID_VOID;
sOnload_id = JSID_VOID;
sOnerror_id = JSID_VOID;

View File

@ -295,6 +295,7 @@ public:
static jsid sKeyPath_id;
static jsid sAutoIncrement_id;
static jsid sUnique_id;
static jsid sMultiEntry_id;
static jsid sOnload_id;
static jsid sOnerror_id;

View File

@ -97,7 +97,8 @@ DatabaseInfo::~DatabaseInfo()
IndexInfo::IndexInfo()
: id(LL_MININT),
unique(false),
autoIncrement(false)
autoIncrement(false),
multiEntry(false)
{
MOZ_COUNT_CTOR(IndexInfo);
}
@ -107,7 +108,8 @@ IndexInfo::IndexInfo(const IndexInfo& aOther)
name(aOther.name),
keyPath(aOther.keyPath),
unique(aOther.unique),
autoIncrement(aOther.autoIncrement)
autoIncrement(aOther.autoIncrement),
multiEntry(aOther.multiEntry)
{
MOZ_COUNT_CTOR(IndexInfo);
}

View File

@ -121,6 +121,7 @@ struct IndexInfo
nsString keyPath;
bool unique;
bool autoIncrement;
bool multiEntry;
};
struct ObjectStoreInfo
@ -149,7 +150,8 @@ struct IndexUpdateInfo
~IndexUpdateInfo();
#endif
IndexInfo info;
PRInt64 indexId;
bool indexUnique;
Key value;
};

View File

@ -273,7 +273,7 @@ IDBFactory::LoadDatabaseInformation(mozIStorageConnection* aConnection,
// Load index information
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
"SELECT object_store_id, id, name, key_path, unique_index, "
"SELECT object_store_id, id, name, key_path, unique_index, multientry, "
"object_store_autoincrement "
"FROM object_store_index"
), getter_AddRefs(stmt));
@ -307,7 +307,8 @@ IDBFactory::LoadDatabaseInformation(mozIStorageConnection* aConnection,
NS_ENSURE_SUCCESS(rv, rv);
indexInfo->unique = !!stmt->AsInt32(4);
indexInfo->autoIncrement = !!stmt->AsInt32(5);
indexInfo->multiEntry = !!stmt->AsInt32(5);
indexInfo->autoIncrement = !!stmt->AsInt32(6);
}
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -318,6 +318,7 @@ IDBIndex::Create(IDBObjectStore* aObjectStore,
index->mName = aIndexInfo->name;
index->mKeyPath = aIndexInfo->keyPath;
index->mUnique = aIndexInfo->unique;
index->mMultiEntry = aIndexInfo->multiEntry;
index->mAutoIncrement = aIndexInfo->autoIncrement;
return index.forget();
@ -396,6 +397,15 @@ IDBIndex::GetUnique(bool* aUnique)
return NS_OK;
}
NS_IMETHODIMP
IDBIndex::GetMultiEntry(bool* aMultiEntry)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
*aMultiEntry = mMultiEntry;
return NS_OK;
}
NS_IMETHODIMP
IDBIndex::GetObjectStore(nsIIDBObjectStore** aObjectStore)
{

View File

@ -87,6 +87,11 @@ public:
return mUnique;
}
bool IsMultiEntry() const
{
return mMultiEntry;
}
bool IsAutoIncrement() const
{
return mAutoIncrement;
@ -110,6 +115,7 @@ private:
nsString mName;
nsString mKeyPath;
bool mUnique;
bool mMultiEntry;
bool mAutoIncrement;
};

View File

@ -97,9 +97,6 @@ public:
AsyncConnectionHelper::ReleaseMainThreadObjects();
}
nsresult UpdateIndexes(mozIStorageConnection* aConnection,
PRInt64 aObjectDataId);
private:
// In-params.
nsRefPtr<IDBObjectStore> mObjectStore;
@ -422,10 +419,10 @@ typedef nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> KeyPathTokenizer;
inline
nsresult
GetKeyFromValue(JSContext* aCx,
jsval aVal,
const nsAString& aKeyPath,
Key& aKey)
GetJSValFromKeyPath(JSContext* aCx,
jsval aVal,
const nsAString& aKeyPath,
jsval& aKey)
{
NS_ASSERTION(aCx, "Null pointer!");
// aVal can be primitive iff the key path is empty.
@ -452,8 +449,23 @@ GetKeyFromValue(JSContext* aCx,
keyPathChars, keyPathLen, &intermediate);
NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
}
aKey = intermediate;
return NS_OK;
}
if (NS_FAILED(aKey.SetFromJSVal(aCx, intermediate))) {
inline
nsresult
GetKeyFromValue(JSContext* aCx,
jsval aVal,
const nsAString& aKeyPath,
Key& aKey)
{
jsval key;
nsresult rv = GetJSValFromKeyPath(aCx, aVal, aKeyPath, key);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(aKey.SetFromJSVal(aCx, key))) {
aKey.Unset();
}
@ -571,78 +583,63 @@ IDBObjectStore::IsValidKeyPath(JSContext* aCx,
// static
nsresult
IDBObjectStore::GetKeyPathValueFromStructuredData(const PRUint8* aData,
PRUint32 aDataLength,
const nsAString& aKeyPath,
JSContext* aCx,
Key& aValue)
IDBObjectStore::AppendIndexUpdateInfo(PRInt64 aIndexID,
const nsAString& aKeyPath,
bool aUnique,
bool aMultiEntry,
JSContext* aCx,
jsval aObject,
nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
{
NS_ASSERTION(aData, "Null pointer!");
NS_ASSERTION(aDataLength, "Empty data!");
NS_ASSERTION(aCx, "Null pointer!");
JSAutoRequest ar(aCx);
jsval clone;
if (!JS_ReadStructuredClone(aCx, reinterpret_cast<const uint64*>(aData),
aDataLength, JS_STRUCTURED_CLONE_VERSION,
&clone, NULL, NULL)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
if (JSVAL_IS_PRIMITIVE(clone) && !aKeyPath.IsEmpty()) {
// This isn't an object, so just leave the key unset.
aValue.Unset();
return NS_OK;
}
nsresult rv = GetKeyFromValue(aCx, clone, aKeyPath, aValue);
jsval key;
nsresult rv = GetJSValFromKeyPath(aCx, aObject, aKeyPath, key);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/* static */
nsresult
IDBObjectStore::GetIndexUpdateInfo(ObjectStoreInfo* aObjectStoreInfo,
JSContext* aCx,
jsval aObject,
nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
{
JSObject* cloneObj = nsnull;
PRUint32 count = aObjectStoreInfo->indexes.Length();
if (count) {
if (!aUpdateInfoArray.SetCapacity(count)) {
NS_ERROR("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
if (aMultiEntry && !JSVAL_IS_PRIMITIVE(key) &&
JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(key))) {
JSObject* array = JSVAL_TO_OBJECT(key);
jsuint arrayLength;
if (!JS_GetArrayLength(aCx, array, &arrayLength)) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
for (PRUint32 indexesIndex = 0; indexesIndex < count; indexesIndex++) {
const IndexInfo& indexInfo = aObjectStoreInfo->indexes[indexesIndex];
for (jsuint arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
jsval arrayItem;
if (!JS_GetElement(aCx, array, arrayIndex, &arrayItem)) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
Key value;
nsresult rv = GetKeyFromValue(aCx, aObject, indexInfo.keyPath, value);
NS_ENSURE_SUCCESS(rv, rv);
if (value.IsUnset()) {
if (NS_FAILED(value.SetFromJSVal(aCx, arrayItem)) ||
value.IsUnset()) {
// Not a value we can do anything with, ignore it.
continue;
}
IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
updateInfo->info = indexInfo;
updateInfo->indexId = aIndexID;
updateInfo->indexUnique = aUnique;
updateInfo->value = value;
}
}
else {
aUpdateInfoArray.Clear();
Key value;
if (NS_FAILED(value.SetFromJSVal(aCx, key)) ||
value.IsUnset()) {
// Not a value we can do anything with, ignore it.
return NS_OK;
}
IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
updateInfo->indexId = aIndexID;
updateInfo->indexUnique = aUnique;
updateInfo->value = value;
}
return NS_OK;
}
/* static */
// static
nsresult
IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction,
PRInt64 aObjectStoreId,
@ -652,21 +649,13 @@ IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction,
PRInt64 aObjectDataId,
const nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
{
#ifdef DEBUG
if (aAutoIncrement) {
NS_ASSERTION(aObjectDataId != LL_MININT, "Bad objectData id!");
}
else {
NS_ASSERTION(aObjectDataId == LL_MININT, "Bad objectData id!");
}
#endif
PRUint32 indexCount = aUpdateInfoArray.Length();
NS_ASSERTION(!aAutoIncrement || aObjectDataId != LL_MININT,
"Bad objectData id!");
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv;
if (!aAutoIncrement) {
if (aObjectDataId == LL_MININT) {
stmt = aTransaction->GetCachedStatement(
"SELECT id "
"FROM object_data "
@ -728,25 +717,24 @@ IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction,
NS_ENSURE_SUCCESS(rv, rv);
}
for (PRUint32 indexIndex = 0; indexIndex < indexCount; indexIndex++) {
const IndexUpdateInfo& updateInfo = aUpdateInfoArray[indexIndex];
NS_ASSERTION(updateInfo.info.autoIncrement == aAutoIncrement, "Huh?!");
PRUint32 infoCount = aUpdateInfoArray.Length();
for (PRUint32 i = 0; i < infoCount; i++) {
const IndexUpdateInfo& updateInfo = aUpdateInfoArray[i];
// Insert new values.
stmt = aTransaction->IndexDataInsertStatement(aAutoIncrement,
updateInfo.info.unique);
updateInfo.indexUnique);
NS_ENSURE_TRUE(stmt, NS_ERROR_FAILURE);
mozStorageStatementScoper scoper4(stmt);
rv = stmt->BindInt64ByName(indexId, updateInfo.info.id);
rv = stmt->BindInt64ByName(indexId, updateInfo.indexId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(objectDataId, aObjectDataId);
NS_ENSURE_SUCCESS(rv, rv);
if (!updateInfo.info.autoIncrement) {
if (!aAutoIncrement) {
rv = aObjectStoreKey.BindToStatement(stmt, objectDataKey);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -755,6 +743,23 @@ IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction,
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
if (rv == NS_ERROR_STORAGE_CONSTRAINT && updateInfo.indexUnique) {
// If we're inserting multiple entries for the same unique index, then
// we might have failed to insert due to colliding with another entry for
// the same index in which case we should ignore it.
for (PRInt32 j = (PRInt32)i - 1;
j >= 0 && aUpdateInfoArray[j].indexId == updateInfo.indexId;
--j) {
if (updateInfo.value == aUpdateInfoArray[j].value) {
// We found a key with the same value for the same index. So we
// must have had a collision with a value we just inserted.
rv = NS_OK;
break;
}
}
}
if (NS_FAILED(rv)) {
return rv;
}
@ -929,8 +934,17 @@ IDBObjectStore::GetAddInfo(JSContext* aCx,
NS_ERROR("This should never fail!");
}
rv = GetIndexUpdateInfo(info, aCx, aValue, aUpdateInfoArray);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
PRUint32 count = info->indexes.Length();
aUpdateInfoArray.SetCapacity(count); // Pretty good estimate
for (PRUint32 indexesIndex = 0; indexesIndex < count; indexesIndex++) {
const IndexInfo& indexInfo = info->indexes[indexesIndex];
rv = AppendIndexUpdateInfo(indexInfo.id, indexInfo.keyPath,
indexInfo.unique, indexInfo.multiEntry,
aCx, aValue, aUpdateInfoArray);
NS_ENSURE_SUCCESS(rv, rv);
}
const jschar* keyPathChars =
reinterpret_cast<const jschar*>(mKeyPath.get());
@ -1365,6 +1379,7 @@ IDBObjectStore::CreateIndex(const nsAString& aName,
NS_ASSERTION(mTransaction->IsOpen(), "Impossible!");
bool unique = false;
bool multiEntry = false;
// Get optional arguments.
if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
@ -1388,6 +1403,17 @@ IDBObjectStore::CreateIndex(const nsAString& aName,
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
unique = !!boolVal;
if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sMultiEntry_id, &val)) {
NS_WARNING("JS_GetPropertyById failed!");
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
NS_WARNING("JS_ValueToBoolean failed!");
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
multiEntry = !!boolVal;
}
DatabaseInfo* databaseInfo = mTransaction->Database()->Info();
@ -1402,6 +1428,7 @@ IDBObjectStore::CreateIndex(const nsAString& aName,
indexInfo->name = aName;
indexInfo->keyPath = aKeyPath;
indexInfo->unique = unique;
indexInfo->multiEntry = multiEntry;
indexInfo->autoIncrement = mAutoIncrement;
// Don't leave this in the list if we fail below!
@ -2153,8 +2180,9 @@ CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
nsCOMPtr<mozIStorageStatement> stmt =
mTransaction->GetCachedStatement(
"INSERT INTO object_store_index (id, name, key_path, unique_index, "
"object_store_id, object_store_autoincrement) "
"VALUES (:id, :name, :key_path, :unique, :osid, :os_auto_increment)"
"multientry, object_store_id, object_store_autoincrement) "
"VALUES (:id, :name, :key_path, :unique, :multientry, :osid, "
":os_auto_increment)"
);
NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@ -2175,6 +2203,10 @@ CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
mIndex->IsUnique() ? 1 : 0);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("multientry"),
mIndex->IsMultiEntry() ? 1 : 0);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"),
mIndex->ObjectStore()->Id());
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@ -2231,69 +2263,69 @@ CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection)
mIndex->ObjectStore()->Id());
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
NS_ENSURE_TRUE(sTLSIndex != BAD_TLS_INDEX, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
bool hasResult;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
nsCOMPtr<mozIStorageStatement> insertStmt =
mTransaction->IndexDataInsertStatement(mIndex->IsAutoIncrement(),
mIndex->IsUnique());
NS_ENSURE_TRUE(insertStmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
if (!hasResult) {
// Bail early if we have no data to avoid creating the below runtime
return NS_OK;
}
mozStorageStatementScoper scoper2(insertStmt);
ThreadLocalJSRuntime* tlsEntry =
reinterpret_cast<ThreadLocalJSRuntime*>(PR_GetThreadPrivate(sTLSIndex));
rv = insertStmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
mIndex->Id());
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
if (!tlsEntry) {
tlsEntry = ThreadLocalJSRuntime::Create();
NS_ENSURE_TRUE(tlsEntry, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
rv = insertStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_data_id"),
stmt->AsInt64(0));
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
PR_SetThreadPrivate(sTLSIndex, tlsEntry);
}
if (!mIndex->IsAutoIncrement()) {
NS_NAMED_LITERAL_CSTRING(objectDataKey, "object_data_key");
Key key;
rv = key.SetFromStatement(stmt, 2);
NS_ENSURE_SUCCESS(rv, rv);
rv =
key.BindToStatement(insertStmt, NS_LITERAL_CSTRING("object_data_key"));
NS_ENSURE_SUCCESS(rv, rv);
}
JSContext* cx = tlsEntry->Context();
JSAutoRequest ar(cx);
do {
const PRUint8* data;
PRUint32 dataLength;
rv = stmt->GetSharedBlob(1, &dataLength, &data);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
NS_ENSURE_TRUE(sTLSIndex != BAD_TLS_INDEX, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
ThreadLocalJSRuntime* tlsEntry =
reinterpret_cast<ThreadLocalJSRuntime*>(PR_GetThreadPrivate(sTLSIndex));
if (!tlsEntry) {
tlsEntry = ThreadLocalJSRuntime::Create();
NS_ENSURE_TRUE(tlsEntry, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
PR_SetThreadPrivate(sTLSIndex, tlsEntry);
jsval clone;
if (!JS_ReadStructuredClone(cx, reinterpret_cast<const uint64*>(data),
dataLength, JS_STRUCTURED_CLONE_VERSION,
&clone, NULL, NULL)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
nsTArray<IndexUpdateInfo> updateInfo;
rv = IDBObjectStore::AppendIndexUpdateInfo(mIndex->Id(),
mIndex->KeyPath(),
mIndex->IsUnique(),
mIndex->IsMultiEntry(),
tlsEntry->Context(),
clone, updateInfo);
NS_ENSURE_SUCCESS(rv, rv);
PRInt64 objectDataID = stmt->AsInt64(0);
Key key;
rv = IDBObjectStore::GetKeyPathValueFromStructuredData(data, dataLength,
mIndex->KeyPath(),
tlsEntry->Context(),
key);
NS_ENSURE_SUCCESS(rv, rv);
if (key.IsUnset()) {
continue;
if (!mIndex->IsAutoIncrement()) {
rv = key.SetFromStatement(stmt, 2);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
key.SetFromInteger(objectDataID);
}
rv = key.BindToStatement(insertStmt, NS_LITERAL_CSTRING("value"));
rv = IDBObjectStore::UpdateIndexes(mTransaction, mIndex->Id(),
key, mIndex->IsAutoIncrement(),
false, objectDataID, updateInfo);
NS_ENSURE_SUCCESS(rv, rv);
rv = insertStmt->Execute();
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
}
} while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return NS_OK;
}

View File

@ -76,17 +76,13 @@ public:
IsValidKeyPath(JSContext* aCx, const nsAString& aKeyPath);
static nsresult
GetKeyPathValueFromStructuredData(const PRUint8* aData,
PRUint32 aDataLength,
const nsAString& aKeyPath,
JSContext* aCx,
Key& aValue);
static nsresult
GetIndexUpdateInfo(ObjectStoreInfo* aObjectStoreInfo,
JSContext* aCx,
jsval aObject,
nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
AppendIndexUpdateInfo(PRInt64 aIndexID,
const nsAString& aKeyPath,
bool aUnique,
bool aMultiEntry,
JSContext* aCx,
jsval aObject,
nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
static nsresult
UpdateIndexes(IDBTransaction* aTransaction,

View File

@ -406,7 +406,7 @@ IDBTransaction::IndexDataInsertStatement(bool aAutoIncrement,
);
}
return GetCachedStatement(
"INSERT INTO ai_index_data "
"INSERT OR IGNORE INTO ai_index_data "
"(index_id, ai_object_data_id, value) "
"VALUES (:index_id, :object_data_id, :value)"
);
@ -419,7 +419,7 @@ IDBTransaction::IndexDataInsertStatement(bool aAutoIncrement,
);
}
return GetCachedStatement(
"INSERT INTO index_data ("
"INSERT OR IGNORE INTO index_data ("
"index_id, object_data_id, object_data_key, value) "
"VALUES (:index_id, :object_data_id, :object_data_key, :value)"
);

View File

@ -51,7 +51,7 @@
#include "nsStringGlue.h"
#include "nsTArray.h"
#define DB_SCHEMA_VERSION 7
#define DB_SCHEMA_VERSION 8
#define BEGIN_INDEXEDDB_NAMESPACE \
namespace mozilla { namespace dom { namespace indexedDB {

View File

@ -165,6 +165,7 @@ CreateTables(mozIStorageConnection* aDBConn)
"name TEXT NOT NULL, "
"key_path TEXT NOT NULL, "
"unique_index INTEGER NOT NULL, "
"multientry INTEGER NOT NULL DEFAULT 0, "
"object_store_autoincrement INTERGER NOT NULL, "
"PRIMARY KEY (id), "
"UNIQUE (object_store_id, name), "
@ -819,6 +820,83 @@ UpgradeSchemaFrom6To7(mozIStorageConnection* aConnection)
return NS_OK;
}
nsresult
UpgradeSchemaFrom7To8(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);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMPORARY TABLE temp_upgrade ("
"id, "
"object_store_id, "
"name, "
"key_path, "
"unique_index, "
"object_store_autoincrement, "
");"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO temp_upgrade "
"SELECT id, object_store_id, name, key_path, "
"unique_index, object_store_autoincrement, "
"FROM object_store_index;"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE object_store_index;"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE object_store_index ("
"id INTEGER, "
"object_store_id INTEGER NOT NULL, "
"name TEXT NOT NULL, "
"key_path TEXT NOT NULL, "
"unique_index INTEGER NOT NULL, "
"multientry INTEGER NOT NULL, "
"object_store_autoincrement INTERGER NOT NULL, "
"PRIMARY KEY (id), "
"UNIQUE (object_store_id, name), "
"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_store_index "
"SELECT id, object_store_id, name, key_path, "
"unique_index, 0, object_store_autoincrement, "
"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->SetSchemaVersion(8);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
CreateDatabaseConnection(const nsAString& aName,
nsIFile* aDBFile,
@ -870,7 +948,7 @@ CreateDatabaseConnection(const nsAString& aName,
}
else if (schemaVersion != DB_SCHEMA_VERSION) {
// This logic needs to change next time we change the schema!
PR_STATIC_ASSERT(DB_SCHEMA_VERSION == 7);
PR_STATIC_ASSERT(DB_SCHEMA_VERSION == 8);
#define UPGRADE_SCHEMA_CASE(_from, _to) \
if (schemaVersion == _from) { \
@ -886,6 +964,7 @@ CreateDatabaseConnection(const nsAString& aName,
UPGRADE_SCHEMA_CASE(4, 5)
UPGRADE_SCHEMA_CASE(5, 6)
UPGRADE_SCHEMA_CASE(6, 7)
UPGRADE_SCHEMA_CASE(7, 8)
#undef UPGRADE_SCHEMA_CASE

View File

@ -47,7 +47,7 @@ interface nsIIDBRequest;
* http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-IDBIndex for more
* information.
*/
[scriptable, builtinclass, uuid(1da60889-3db4-4f66-9fd7-b78c1e7969b7)]
[scriptable, builtinclass, uuid(fcb9a158-833e-4aa9-ab19-ab90cbb50afc)]
interface nsIIDBIndex : nsISupports
{
readonly attribute DOMString name;
@ -58,6 +58,8 @@ interface nsIIDBIndex : nsISupports
readonly attribute boolean unique;
readonly attribute boolean multiEntry;
readonly attribute nsIIDBObjectStore objectStore;
[implicit_jscontext]

View File

@ -84,6 +84,7 @@ TEST_FILES = \
test_indexes_bad_values.html \
test_key_requirements.html \
test_leaving_page.html \
test_multientry.html \
test_objectCursors.html \
test_objectStore_inline_autoincrement_key_added_on_put.html \
test_objectStore_remove_values.html \

View File

@ -0,0 +1,230 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Property Test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript;version=1.7">
function testSteps()
{
// Test object stores
let openRequest = mozIndexedDB.open(window.location.pathname, 1);
openRequest.onerror = errorHandler;
openRequest.onupgradeneeded = grabEventAndContinueHandler;
openRequest.onsuccess = unexpectedSuccessHandler;
let event = yield;
let db = event.target.result;
db.onerror = errorHandler;
let tests =
[{ add: { x: 1, id: 1 },
indexes:[{ v: 1, k: 1 }] },
{ add: { x: [2, 3], id: 2 },
indexes:[{ v: 1, k: 1 },
{ v: 2, k: 2 },
{ v: 3, k: 2 }] },
{ put: { x: [2, 4], id: 1 },
indexes:[{ v: 2, k: 1 },
{ v: 2, k: 2 },
{ v: 3, k: 2 },
{ v: 4, k: 1 }] },
{ add: { x: [5, 6, 5, -2, 3], id: 3 },
indexes:[{ v:-2, k: 3 },
{ v: 2, k: 1 },
{ v: 2, k: 2 },
{ v: 3, k: 2 },
{ v: 3, k: 3 },
{ v: 4, k: 1 },
{ v: 5, k: 3 },
{ v: 6, k: 3 }] },
{ delete: IDBKeyRange.bound(1, 3),
indexes:[] },
{ put: { x: ["food", {}, false, undefined, /x/, [73, false]], id: 2 },
indexes:[{ v: "food", k: 2 }] },
{ add: { x: [{}, /x/, -12, "food", null, [false], undefined], id: 3 },
indexes:[{ v: -12, k: 3 },
{ v: "food", k: 2 },
{ v: "food", k: 3 }] },
{ put: { x: [], id: 2 },
indexes:[{ v: -12, k: 3 },
{ v: "food", k: 3 }] },
{ put: { x: { y: 3 }, id: 3 },
indexes:[] },
{ add: { x: false, id: 7 },
indexes:[] },
{ delete: IDBKeyRange.lowerBound(0),
indexes:[] },
];
let store = db.createObjectStore("mystore", { keyPath: "id" });
let index = store.createIndex("myindex", "x", { multiEntry: true });
is(index.multiEntry, true, "index created with multiEntry");
let i;
for (i = 0; i < tests.length; ++i) {
let test = tests[i];
let testName = " for " + JSON.stringify(test);
let req;
if (test.add) {
req = store.add(test.add);
}
else if (test.put) {
req = store.put(test.put);
}
else if (test.delete) {
req = store.delete(test.delete);
}
else {
ok(false, "borked test");
}
req.onsuccess = grabEventAndContinueHandler;
let e = yield;
req = index.openKeyCursor();
req.onsuccess = grabEventAndContinueHandler;
for (let j = 0; j < test.indexes.length; ++j) {
e = yield;
is(req.result.key, test.indexes[j].v, "found expected index key at index " + j + testName);
is(req.result.primaryKey, test.indexes[j].k, "found expected index primary key at index " + j + testName);
req.result.continue();
}
e = yield;
is(req.result, undefined, "exhausted indexes");
let tempIndex = store.createIndex("temp index", "x", { multiEntry: true });
req = tempIndex.openKeyCursor();
req.onsuccess = grabEventAndContinueHandler;
for (let j = 0; j < test.indexes.length; ++j) {
e = yield;
is(req.result.key, test.indexes[j].v, "found expected temp index key at index " + j + testName);
is(req.result.primaryKey, test.indexes[j].k, "found expected temp index primary key at index " + j + testName);
req.result.continue();
}
e = yield;
is(req.result, undefined, "exhausted temp index");
store.deleteIndex("temp index");
}
// Unique indexes
tests =
[{ add: { x: 1, id: 1 },
indexes:[{ v: 1, k: 1 }] },
{ add: { x: [2, 3], id: 2 },
indexes:[{ v: 1, k: 1 },
{ v: 2, k: 2 },
{ v: 3, k: 2 }] },
{ put: { x: [2, 4], id: 3 },
fail: true },
{ put: { x: [1, 4], id: 1 },
indexes:[{ v: 1, k: 1 },
{ v: 2, k: 2 },
{ v: 3, k: 2 },
{ v: 4, k: 1 }] },
{ add: { x: [5, 0, 5, 5, 5], id: 3 },
indexes:[{ v: 0, k: 3 },
{ v: 1, k: 1 },
{ v: 2, k: 2 },
{ v: 3, k: 2 },
{ v: 4, k: 1 },
{ v: 5, k: 3 }] },
{ delete: IDBKeyRange.bound(1, 2),
indexes:[{ v: 0, k: 3 },
{ v: 5, k: 3 }] },
{ add: { x: [0, 6], id: 8 },
fail: true },
{ add: { x: 5, id: 8 },
fail: true },
{ put: { x: 0, id: 8 },
fail: true },
];
store.deleteIndex("myindex");
index = store.createIndex("myindex", "x", { multiEntry: true, unique: true });
is(index.multiEntry, true, "index created with multiEntry");
let i;
let indexes;
for (i = 0; i < tests.length; ++i) {
let test = tests[i];
let testName = " for " + JSON.stringify(test);
let req;
if (test.add) {
req = store.add(test.add);
}
else if (test.put) {
req = store.put(test.put);
}
else if (test.delete) {
req = store.delete(test.delete);
}
else {
ok(false, "borked test");
}
if (!test.fail) {
req.onsuccess = grabEventAndContinueHandler;
let e = yield;
indexes = test.indexes;
}
else {
req.onsuccess = unexpectedSuccessHandler;
req.onerror = grabEventAndContinueHandler;
ok(true, "waiting for error");
let e = yield;
ok(true, "got error: " + e.type);
e.preventDefault();
e.stopPropagation();
}
let e;
req = index.openKeyCursor();
req.onsuccess = grabEventAndContinueHandler;
for (let j = 0; j < indexes.length; ++j) {
e = yield;
is(req.result.key, indexes[j].v, "found expected index key at index " + j + testName);
is(req.result.primaryKey, indexes[j].k, "found expected index primary key at index " + j + testName);
req.result.continue();
}
e = yield;
is(req.result, undefined, "exhausted indexes");
let tempIndex = store.createIndex("temp index", "x", { multiEntry: true, unique: true });
req = tempIndex.openKeyCursor();
req.onsuccess = grabEventAndContinueHandler;
for (let j = 0; j < indexes.length; ++j) {
e = yield;
is(req.result.key, indexes[j].v, "found expected temp index key at index " + j + testName);
is(req.result.primaryKey, indexes[j].k, "found expected temp index primary key at index " + j + testName);
req.result.continue();
}
e = yield;
is(req.result, undefined, "exhausted temp index");
store.deleteIndex("temp index");
}
openRequest.onsuccess = grabEventAndContinueHandler;
yield;
let trans = db.transaction(["mystore"], IDBTransaction.READ_WRITE);
store = trans.objectStore("mystore");
index = store.index("myindex");
is(index.multiEntry, true, "index still is multiEntry");
trans.oncomplete = grabEventAndContinueHandler;
yield;
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>