зеркало из https://github.com/mozilla/pjs.git
Bug 694138: Support an array of strings as keyPath on objectStores and indexes. r=janv/bent
This commit is contained in:
Родитель
fe44081f28
Коммит
9d8687d0bf
|
@ -117,6 +117,7 @@ IndexInfo::IndexInfo(const IndexInfo& aOther)
|
|||
: id(aOther.id),
|
||||
name(aOther.name),
|
||||
keyPath(aOther.keyPath),
|
||||
keyPathArray(aOther.keyPathArray),
|
||||
unique(aOther.unique),
|
||||
multiEntry(aOther.multiEntry)
|
||||
{
|
||||
|
|
|
@ -121,6 +121,7 @@ struct IndexInfo
|
|||
PRInt64 id;
|
||||
nsString name;
|
||||
nsString keyPath;
|
||||
nsTArray<nsString> keyPathArray;
|
||||
bool unique;
|
||||
bool multiEntry;
|
||||
};
|
||||
|
@ -148,6 +149,7 @@ public:
|
|||
nsString name;
|
||||
PRInt64 id;
|
||||
nsString keyPath;
|
||||
nsTArray<nsString> keyPathArray;
|
||||
|
||||
// Main-thread only members. This must *not* be touced on the database thread
|
||||
nsTArray<IndexInfo> indexes;
|
||||
|
|
|
@ -388,6 +388,7 @@ IDBDatabase::CreateObjectStore(const nsAString& aName,
|
|||
|
||||
nsString keyPath;
|
||||
keyPath.SetIsVoid(true);
|
||||
nsTArray<nsString> keyPathArray;
|
||||
bool autoIncrement = false;
|
||||
|
||||
if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
|
||||
|
@ -399,6 +400,7 @@ IDBDatabase::CreateObjectStore(const nsAString& aName,
|
|||
NS_ASSERTION(JSVAL_IS_OBJECT(aOptions), "Huh?!");
|
||||
JSObject* options = JSVAL_TO_OBJECT(aOptions);
|
||||
|
||||
// Get keyPath
|
||||
jsval val;
|
||||
if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sKeyPath_id, &val)) {
|
||||
NS_WARNING("JS_GetPropertyById failed!");
|
||||
|
@ -406,17 +408,55 @@ IDBDatabase::CreateObjectStore(const nsAString& aName,
|
|||
}
|
||||
|
||||
if (!JSVAL_IS_VOID(val) && !JSVAL_IS_NULL(val)) {
|
||||
JSString* str = JS_ValueToString(aCx, val);
|
||||
if (!str) {
|
||||
NS_WARNING("JS_ValueToString failed!");
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
if (!JSVAL_IS_PRIMITIVE(val) &&
|
||||
JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(val))) {
|
||||
|
||||
JSObject* obj = JSVAL_TO_OBJECT(val);
|
||||
|
||||
jsuint length;
|
||||
if (!JS_GetArrayLength(aCx, obj, &length)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!length) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
|
||||
keyPathArray.SetCapacity(length);
|
||||
|
||||
for (jsuint index = 0; index < length; index++) {
|
||||
jsval val;
|
||||
JSString* jsstr;
|
||||
nsDependentJSString str;
|
||||
if (!JS_GetElement(aCx, obj, index, &val) ||
|
||||
!(jsstr = JS_ValueToString(aCx, val)) ||
|
||||
!str.init(aCx, jsstr)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!IDBObjectStore::IsValidKeyPath(aCx, str)) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
|
||||
keyPathArray.AppendElement(str);
|
||||
}
|
||||
|
||||
NS_ASSERTION(!keyPathArray.IsEmpty(), "This shouldn't have happened!");
|
||||
}
|
||||
nsDependentJSString dependentKeyPath;
|
||||
if (!dependentKeyPath.init(aCx, str)) {
|
||||
NS_WARNING("Initializing keyPath failed!");
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
else {
|
||||
JSString* jsstr;
|
||||
nsDependentJSString str;
|
||||
if (!(jsstr = JS_ValueToString(aCx, val)) ||
|
||||
!str.init(aCx, jsstr)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!IDBObjectStore::IsValidKeyPath(aCx, str)) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
|
||||
keyPath = str;
|
||||
}
|
||||
keyPath = dependentKeyPath;
|
||||
}
|
||||
|
||||
if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sAutoIncrement_id,
|
||||
|
@ -437,13 +477,9 @@ IDBDatabase::CreateObjectStore(const nsAString& aName,
|
|||
return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
|
||||
}
|
||||
|
||||
if (!keyPath.IsVoid()) {
|
||||
if (keyPath.IsEmpty() && autoIncrement) {
|
||||
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
||||
}
|
||||
if (!IDBObjectStore::IsValidKeyPath(aCx, keyPath)) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
if (autoIncrement &&
|
||||
((!keyPath.IsVoid() && keyPath.IsEmpty()) || !keyPathArray.IsEmpty())) {
|
||||
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
||||
}
|
||||
|
||||
nsRefPtr<ObjectStoreInfo> newInfo(new ObjectStoreInfo());
|
||||
|
@ -451,6 +487,7 @@ IDBDatabase::CreateObjectStore(const nsAString& aName,
|
|||
newInfo->name = aName;
|
||||
newInfo->id = databaseInfo->nextObjectStoreId++;
|
||||
newInfo->keyPath = keyPath;
|
||||
newInfo->keyPathArray = keyPathArray;
|
||||
newInfo->nextAutoIncrementId = autoIncrement ? 1 : 0;
|
||||
newInfo->comittedAutoIncrementId = newInfo->nextAutoIncrementId;
|
||||
|
||||
|
@ -746,11 +783,30 @@ CreateObjectStoreHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
|
|||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mObjectStore->Name());
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
||||
rv = mObjectStore->HasKeyPath() ?
|
||||
stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
|
||||
mObjectStore->KeyPath()) :
|
||||
stmt->BindNullByName(NS_LITERAL_CSTRING("key_path"));
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
if (mObjectStore->UsesKeyPathArray()) {
|
||||
// We use a comma in the beginning to indicate that it's an array of
|
||||
// key paths. This is to be able to tell a string-keypath from an
|
||||
// array-keypath which contains only one item.
|
||||
// It also makes serializing easier :-)
|
||||
nsAutoString keyPath;
|
||||
const nsTArray<nsString>& keyPaths = mObjectStore->KeyPathArray();
|
||||
for (PRUint32 i = 0; i < keyPaths.Length(); ++i) {
|
||||
keyPath.Append(NS_LITERAL_STRING(",") + keyPaths[i]);
|
||||
}
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
|
||||
keyPath);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
else if (mObjectStore->HasKeyPath()) {
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
|
||||
mObjectStore->KeyPath());
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
else {
|
||||
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("key_path"));
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
|
||||
|
||||
rv = stmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDOMClassInfoID.h"
|
||||
|
@ -202,6 +203,13 @@ IDBFactory::GetDirectoryForOrigin(const nsACString& aASCIIOrigin,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
inline
|
||||
bool
|
||||
IgnoreWhitespace(PRUnichar c)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
IDBFactory::LoadDatabaseInformation(mozIStorageConnection* aConnection,
|
||||
|
@ -245,8 +253,27 @@ IDBFactory::LoadDatabaseInformation(mozIStorageConnection* aConnection,
|
|||
else {
|
||||
NS_ASSERTION(columnType == mozIStorageStatement::VALUE_TYPE_TEXT,
|
||||
"Should be a string");
|
||||
rv = stmt->GetString(2, info->keyPath);
|
||||
nsString keyPath;
|
||||
rv = stmt->GetString(2, keyPath);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!keyPath.IsEmpty() && keyPath.First() == ',') {
|
||||
// We use a comma in the beginning to indicate that it's an array of
|
||||
// key paths. This is to be able to tell a string-keypath from an
|
||||
// array-keypath which contains only one item.
|
||||
nsCharSeparatedTokenizerTemplate<IgnoreWhitespace>
|
||||
tokenizer(keyPath, ',');
|
||||
tokenizer.nextToken();
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
info->keyPathArray.AppendElement(tokenizer.nextToken());
|
||||
}
|
||||
NS_ASSERTION(!info->keyPathArray.IsEmpty(),
|
||||
"Should have at least one keypath");
|
||||
}
|
||||
else {
|
||||
info->keyPath = keyPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
info->nextAutoIncrementId = stmt->AsInt64(3);
|
||||
|
@ -291,8 +318,25 @@ IDBFactory::LoadDatabaseInformation(mozIStorageConnection* aConnection,
|
|||
rv = stmt->GetString(2, indexInfo->name);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = stmt->GetString(3, indexInfo->keyPath);
|
||||
nsString keyPath;
|
||||
rv = stmt->GetString(3, keyPath);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!keyPath.IsEmpty() && keyPath.First() == ',') {
|
||||
// We use a comma in the beginning to indicate that it's an array of
|
||||
// key paths. This is to be able to tell a string-keypath from an
|
||||
// array-keypath which contains only one item.
|
||||
nsCharSeparatedTokenizerTemplate<IgnoreWhitespace>
|
||||
tokenizer(keyPath, ',');
|
||||
tokenizer.nextToken();
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
indexInfo->keyPathArray.AppendElement(tokenizer.nextToken());
|
||||
}
|
||||
NS_ASSERTION(!indexInfo->keyPathArray.IsEmpty(),
|
||||
"Should have at least one keypath");
|
||||
}
|
||||
else {
|
||||
indexInfo->keyPath = keyPath;
|
||||
}
|
||||
|
||||
indexInfo->unique = !!stmt->AsInt32(4);
|
||||
indexInfo->multiEntry = !!stmt->AsInt32(5);
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
#include "nsEventDispatcher.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/storage.h"
|
||||
#include "xpcprivate.h"
|
||||
#include "XPCQuickStubs.h"
|
||||
|
||||
#include "AsyncConnectionHelper.h"
|
||||
#include "IDBCursor.h"
|
||||
|
@ -319,6 +321,7 @@ IDBIndex::Create(IDBObjectStore* aObjectStore,
|
|||
index->mId = aIndexInfo->id;
|
||||
index->mName = aIndexInfo->name;
|
||||
index->mKeyPath = aIndexInfo->keyPath;
|
||||
index->mKeyPathArray = aIndexInfo->keyPathArray;
|
||||
index->mUnique = aIndexInfo->unique;
|
||||
index->mMultiEntry = aIndexInfo->multiEntry;
|
||||
|
||||
|
@ -380,11 +383,38 @@ IDBIndex::GetStoreName(nsAString& aStoreName)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
IDBIndex::GetKeyPath(nsAString& aKeyPath)
|
||||
IDBIndex::GetKeyPath(JSContext* aCx,
|
||||
jsval* aVal)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
aKeyPath.Assign(mKeyPath);
|
||||
if (UsesKeyPathArray()) {
|
||||
JSObject* array = JS_NewArrayObject(aCx, mKeyPathArray.Length(), nsnull);
|
||||
if (!array) {
|
||||
NS_WARNING("Failed to make array!");
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
for (PRUint32 i = 0; i < mKeyPathArray.Length(); ++i) {
|
||||
jsval val;
|
||||
nsString tmp(mKeyPathArray[i]);
|
||||
if (!xpc_qsStringToJsval(aCx, tmp, &val)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!JS_SetElement(aCx, array, i, &val)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
*aVal = OBJECT_TO_JSVAL(array);
|
||||
}
|
||||
else {
|
||||
nsString tmp(mKeyPath);
|
||||
if (!xpc_qsStringToJsval(aCx, tmp, aVal)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,16 @@ public:
|
|||
return mKeyPath;
|
||||
}
|
||||
|
||||
bool UsesKeyPathArray() const
|
||||
{
|
||||
return !mKeyPathArray.IsEmpty();
|
||||
}
|
||||
|
||||
const nsTArray<nsString>& KeyPathArray() const
|
||||
{
|
||||
return mKeyPathArray;
|
||||
}
|
||||
|
||||
private:
|
||||
IDBIndex();
|
||||
~IDBIndex();
|
||||
|
@ -109,6 +119,7 @@ private:
|
|||
PRInt64 mId;
|
||||
nsString mName;
|
||||
nsString mKeyPath;
|
||||
nsTArray<nsString> mKeyPathArray;
|
||||
bool mUnique;
|
||||
bool mMultiEntry;
|
||||
};
|
||||
|
|
|
@ -475,6 +475,32 @@ GetKeyFromValue(JSContext* aCx,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
inline
|
||||
nsresult
|
||||
GetKeyFromValue(JSContext* aCx,
|
||||
jsval aVal,
|
||||
const nsTArray<nsString>& aKeyPathArray,
|
||||
Key& aKey)
|
||||
{
|
||||
NS_ASSERTION(!aKeyPathArray.IsEmpty(),
|
||||
"Should not use empty keyPath array");
|
||||
for (PRUint32 i = 0; i < aKeyPathArray.Length(); ++i) {
|
||||
jsval key;
|
||||
nsresult rv = GetJSValFromKeyPath(aCx, aVal, aKeyPathArray[i], key);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (NS_FAILED(aKey.AppendArrayItem(aCx, i == 0, key))) {
|
||||
NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
aKey.FinishArray();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
already_AddRefed<IDBRequest>
|
||||
GenerateRequest(IDBObjectStore* aObjectStore)
|
||||
|
@ -513,6 +539,7 @@ IDBObjectStore::Create(IDBTransaction* aTransaction,
|
|||
objectStore->mName = aStoreInfo->name;
|
||||
objectStore->mId = aStoreInfo->id;
|
||||
objectStore->mKeyPath = aStoreInfo->keyPath;
|
||||
objectStore->mKeyPathArray = aStoreInfo->keyPathArray;
|
||||
objectStore->mAutoIncrement = !!aStoreInfo->nextAutoIncrementId;
|
||||
objectStore->mDatabaseId = aDatabaseId;
|
||||
objectStore->mInfo = aStoreInfo;
|
||||
|
@ -564,14 +591,31 @@ IDBObjectStore::IsValidKeyPath(JSContext* aCx,
|
|||
nsresult
|
||||
IDBObjectStore::AppendIndexUpdateInfo(PRInt64 aIndexID,
|
||||
const nsAString& aKeyPath,
|
||||
const nsTArray<nsString>& aKeyPathArray,
|
||||
bool aUnique,
|
||||
bool aMultiEntry,
|
||||
JSContext* aCx,
|
||||
jsval aObject,
|
||||
jsval aVal,
|
||||
nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
|
||||
{
|
||||
nsresult rv;
|
||||
if (!aKeyPathArray.IsEmpty()) {
|
||||
Key arrayKey;
|
||||
rv = GetKeyFromValue(aCx, aVal, aKeyPathArray, arrayKey);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!arrayKey.IsUnset()) {
|
||||
IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
|
||||
updateInfo->indexId = aIndexID;
|
||||
updateInfo->indexUnique = aUnique;
|
||||
updateInfo->value = arrayKey;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
jsval key;
|
||||
nsresult rv = GetJSValFromKeyPath(aCx, aObject, aKeyPath, key);
|
||||
rv = GetJSValFromKeyPath(aCx, aVal, aKeyPath, key);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (aMultiEntry && !JSVAL_IS_PRIMITIVE(key) &&
|
||||
|
@ -1128,7 +1172,12 @@ IDBObjectStore::GetAddInfo(JSContext* aCx,
|
|||
else if (!mAutoIncrement) {
|
||||
// Inline keys live on the object. Make sure that the value passed in is an
|
||||
// object.
|
||||
rv = GetKeyFromValue(aCx, aValue, mKeyPath, aKey);
|
||||
if (UsesKeyPathArray()) {
|
||||
rv = GetKeyFromValue(aCx, aValue, mKeyPathArray, aKey);
|
||||
}
|
||||
else {
|
||||
rv = GetKeyFromValue(aCx, aValue, mKeyPath, aKey);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
|
@ -1145,8 +1194,9 @@ IDBObjectStore::GetAddInfo(JSContext* aCx,
|
|||
const IndexInfo& indexInfo = mInfo->indexes[indexesIndex];
|
||||
|
||||
rv = AppendIndexUpdateInfo(indexInfo.id, indexInfo.keyPath,
|
||||
indexInfo.unique, indexInfo.multiEntry,
|
||||
aCx, aValue, aUpdateInfoArray);
|
||||
indexInfo.keyPathArray, indexInfo.unique,
|
||||
indexInfo.multiEntry, aCx, aValue,
|
||||
aUpdateInfoArray);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
|
@ -1364,11 +1414,38 @@ IDBObjectStore::GetName(nsAString& aName)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
IDBObjectStore::GetKeyPath(nsAString& aKeyPath)
|
||||
IDBObjectStore::GetKeyPath(JSContext* aCx,
|
||||
jsval* aVal)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
aKeyPath.Assign(mKeyPath);
|
||||
if (UsesKeyPathArray()) {
|
||||
JSObject* array = JS_NewArrayObject(aCx, mKeyPathArray.Length(), nsnull);
|
||||
if (!array) {
|
||||
NS_WARNING("Failed to make array!");
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
for (PRUint32 i = 0; i < mKeyPathArray.Length(); ++i) {
|
||||
jsval val;
|
||||
nsString tmp(mKeyPathArray[i]);
|
||||
if (!xpc_qsStringToJsval(aCx, tmp, &val)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!JS_SetElement(aCx, array, i, &val)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
*aVal = OBJECT_TO_JSVAL(array);
|
||||
}
|
||||
else {
|
||||
nsString tmp(mKeyPath);
|
||||
if (!xpc_qsStringToJsval(aCx, tmp, aVal)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1613,17 +1690,69 @@ IDBObjectStore::OpenCursor(const jsval& aKey,
|
|||
|
||||
NS_IMETHODIMP
|
||||
IDBObjectStore::CreateIndex(const nsAString& aName,
|
||||
const nsAString& aKeyPath,
|
||||
const jsval& aKeyPath,
|
||||
const jsval& aOptions,
|
||||
JSContext* aCx,
|
||||
nsIIDBIndex** _retval)
|
||||
{
|
||||
NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
if (!IsValidKeyPath(aCx, aKeyPath)) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
// Get KeyPath
|
||||
nsString keyPath;
|
||||
nsTArray<nsString> keyPathArray;
|
||||
|
||||
// See if this is a JS array.
|
||||
if (!JSVAL_IS_PRIMITIVE(aKeyPath) &&
|
||||
JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(aKeyPath))) {
|
||||
|
||||
JSObject* obj = JSVAL_TO_OBJECT(aKeyPath);
|
||||
|
||||
jsuint length;
|
||||
if (!JS_GetArrayLength(aCx, obj, &length)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!length) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
|
||||
keyPathArray.SetCapacity(length);
|
||||
|
||||
for (jsuint index = 0; index < length; index++) {
|
||||
jsval val;
|
||||
JSString* jsstr;
|
||||
nsDependentJSString str;
|
||||
if (!JS_GetElement(aCx, obj, index, &val) ||
|
||||
!(jsstr = JS_ValueToString(aCx, val)) ||
|
||||
!str.init(aCx, jsstr)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!IsValidKeyPath(aCx, str)) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
|
||||
keyPathArray.AppendElement(str);
|
||||
}
|
||||
|
||||
NS_ASSERTION(!keyPathArray.IsEmpty(), "This shouldn't have happened!");
|
||||
}
|
||||
else {
|
||||
JSString* jsstr;
|
||||
nsDependentJSString str;
|
||||
if (!(jsstr = JS_ValueToString(aCx, aKeyPath)) ||
|
||||
!str.init(aCx, jsstr)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
if (!IsValidKeyPath(aCx, str)) {
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
|
||||
keyPath = str;
|
||||
}
|
||||
|
||||
// Check name and current mode
|
||||
IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();
|
||||
|
||||
if (!transaction ||
|
||||
|
@ -1685,12 +1814,17 @@ IDBObjectStore::CreateIndex(const nsAString& aName,
|
|||
multiEntry = !!boolVal;
|
||||
}
|
||||
|
||||
if (multiEntry && !keyPathArray.IsEmpty()) {
|
||||
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
}
|
||||
|
||||
DatabaseInfo* databaseInfo = mTransaction->DBInfo();
|
||||
|
||||
IndexInfo* indexInfo = mInfo->indexes.AppendElement();
|
||||
indexInfo->id = databaseInfo->nextIndexId++;
|
||||
indexInfo->name = aName;
|
||||
indexInfo->keyPath = aKeyPath;
|
||||
indexInfo->keyPath = keyPath;
|
||||
indexInfo->keyPathArray.SwapElements(keyPathArray);
|
||||
indexInfo->unique = unique;
|
||||
indexInfo->multiEntry = multiEntry;
|
||||
|
||||
|
@ -1912,7 +2046,6 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
|
|||
}
|
||||
else if (mKey.IsFloat() &&
|
||||
mKey.ToFloat() >= mObjectStore->Info()->nextAutoIncrementId) {
|
||||
// XXX Once we support floats, we should use floor(mKey.ToFloat()) here
|
||||
autoIncrementNum = floor(mKey.ToFloat());
|
||||
}
|
||||
|
||||
|
@ -2423,9 +2556,25 @@ CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
|
|||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mIndex->Name());
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
|
||||
mIndex->KeyPath());
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
if (mIndex->UsesKeyPathArray()) {
|
||||
// We use a comma in the beginning to indicate that it's an array of
|
||||
// key paths. This is to be able to tell a string-keypath from an
|
||||
// array-keypath which contains only one item.
|
||||
// It also makes serializing easier :-)
|
||||
nsAutoString keyPath;
|
||||
const nsTArray<nsString>& keyPaths = mIndex->KeyPathArray();
|
||||
for (PRUint32 i = 0; i < keyPaths.Length(); ++i) {
|
||||
keyPath.Append(NS_LITERAL_STRING(",") + keyPaths[i]);
|
||||
}
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
|
||||
keyPath);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
else {
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
|
||||
mIndex->KeyPath());
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
|
||||
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("unique"),
|
||||
mIndex->IsUnique() ? 1 : 0);
|
||||
|
@ -2521,6 +2670,7 @@ CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection)
|
|||
nsTArray<IndexUpdateInfo> updateInfo;
|
||||
rv = IDBObjectStore::AppendIndexUpdateInfo(mIndex->Id(),
|
||||
mIndex->KeyPath(),
|
||||
mIndex->KeyPathArray(),
|
||||
mIndex->IsUnique(),
|
||||
mIndex->IsMultiEntry(),
|
||||
tlsEntry->Context(),
|
||||
|
|
|
@ -81,6 +81,7 @@ public:
|
|||
static nsresult
|
||||
AppendIndexUpdateInfo(PRInt64 aIndexID,
|
||||
const nsAString& aKeyPath,
|
||||
const nsTArray<nsString>& aKeyPathArray,
|
||||
bool aUnique,
|
||||
bool aMultiEntry,
|
||||
JSContext* aCx,
|
||||
|
@ -159,7 +160,17 @@ public:
|
|||
|
||||
const bool HasKeyPath() const
|
||||
{
|
||||
return !mKeyPath.IsVoid();
|
||||
return !mKeyPath.IsVoid() || !mKeyPathArray.IsEmpty();
|
||||
}
|
||||
|
||||
bool UsesKeyPathArray() const
|
||||
{
|
||||
return !mKeyPathArray.IsEmpty();
|
||||
}
|
||||
|
||||
const nsTArray<nsString>& KeyPathArray() const
|
||||
{
|
||||
return mKeyPathArray;
|
||||
}
|
||||
|
||||
IDBTransaction* Transaction()
|
||||
|
@ -199,6 +210,7 @@ private:
|
|||
PRInt64 mId;
|
||||
nsString mName;
|
||||
nsString mKeyPath;
|
||||
nsTArray<nsString> mKeyPathArray;
|
||||
bool mAutoIncrement;
|
||||
nsCOMPtr<nsIAtom> mDatabaseId;
|
||||
nsRefPtr<ObjectStoreInfo> mInfo;
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Ben Turner <bent.mozilla@gmail.com>
|
||||
* Jan Varga <Jan.Varga@gmail.com>
|
||||
* Jonas Sicking <jonas@sicking.cc>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
|
|
@ -191,6 +191,28 @@ public:
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult AppendArrayItem(JSContext* aCx,
|
||||
bool aFirst,
|
||||
const jsval aVal)
|
||||
{
|
||||
if (aFirst) {
|
||||
Unset();
|
||||
}
|
||||
|
||||
nsresult rv = EncodeJSVal(aCx, aVal, aFirst ? eMaxType : 0);
|
||||
if (NS_FAILED(rv)) {
|
||||
Unset();
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void FinishArray()
|
||||
{
|
||||
TrimBuffer();
|
||||
}
|
||||
|
||||
const nsCString& GetBuffer() const
|
||||
{
|
||||
return mBuffer;
|
||||
|
|
|
@ -216,8 +216,8 @@ CreateTables(mozIStorageConnection* aDBConn)
|
|||
"id INTEGER PRIMARY KEY, "
|
||||
"object_store_id INTEGER NOT NULL, "
|
||||
"key_value BLOB DEFAULT NULL, "
|
||||
"data BLOB NOT NULL, "
|
||||
"file_ids TEXT, "
|
||||
"data BLOB NOT NULL, "
|
||||
"UNIQUE (object_store_id, key_value), "
|
||||
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
||||
"CASCADE"
|
||||
|
@ -1168,8 +1168,8 @@ UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection)
|
|||
"id INTEGER PRIMARY KEY, "
|
||||
"object_store_id INTEGER NOT NULL, "
|
||||
"key_value BLOB DEFAULT NULL, "
|
||||
"data BLOB NOT NULL, "
|
||||
"file_ids TEXT, "
|
||||
"data BLOB NOT NULL, "
|
||||
"UNIQUE (object_store_id, key_value), "
|
||||
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
||||
"CASCADE"
|
||||
|
@ -1179,7 +1179,7 @@ UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection)
|
|||
|
||||
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"INSERT INTO object_data "
|
||||
"SELECT id, object_store_id, key_value, data, file_ids "
|
||||
"SELECT id, object_store_id, key_value, file_ids, data "
|
||||
"FROM temp_upgrade;"
|
||||
));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -1259,7 +1259,7 @@ UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"INSERT OR IGNORE INTO index_data "
|
||||
"INSERT INTO index_data "
|
||||
"SELECT index_id, value, object_data_key, object_data_id "
|
||||
"FROM temp_upgrade;"
|
||||
));
|
||||
|
|
|
@ -47,14 +47,15 @@ interface nsIIDBRequest;
|
|||
* http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-IDBIndex for more
|
||||
* information.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(fcb9a158-833e-4aa9-ab19-ab90cbb50afc)]
|
||||
[scriptable, builtinclass, uuid(233ec586-7b34-4263-b27e-a4991b757597)]
|
||||
interface nsIIDBIndex : nsISupports
|
||||
{
|
||||
readonly attribute DOMString name;
|
||||
|
||||
readonly attribute DOMString storeName;
|
||||
|
||||
readonly attribute DOMString keyPath;
|
||||
[implicit_jscontext]
|
||||
readonly attribute jsval keyPath;
|
||||
|
||||
readonly attribute boolean unique;
|
||||
|
||||
|
|
|
@ -50,12 +50,13 @@ interface nsIDOMDOMStringList;
|
|||
* http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-nsIIDBObjectStore
|
||||
* for more information.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(e93c5ca4-89da-4eb4-b839-271ba4f65a27)]
|
||||
[scriptable, builtinclass, uuid(e0d308ea-b804-4962-918a-28ec0aa4e42b)]
|
||||
interface nsIIDBObjectStore : nsISupports
|
||||
{
|
||||
readonly attribute DOMString name;
|
||||
|
||||
readonly attribute DOMString keyPath;
|
||||
[implicit_jscontext]
|
||||
readonly attribute jsval keyPath;
|
||||
|
||||
readonly attribute nsIDOMDOMStringList indexNames;
|
||||
|
||||
|
@ -111,7 +112,7 @@ interface nsIIDBObjectStore : nsISupports
|
|||
[implicit_jscontext]
|
||||
nsIIDBIndex
|
||||
createIndex([Null(Stringify)] in DOMString name,
|
||||
[Null(Stringify)] in DOMString keyPath,
|
||||
in jsval keyPath,
|
||||
[optional /* none */] in jsval options);
|
||||
|
||||
// Returns object immediately
|
||||
|
|
|
@ -85,6 +85,36 @@ ExpectError.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
function compareKeys(k1, k2) {
|
||||
let t = typeof k1;
|
||||
if (t != typeof k2)
|
||||
return false;
|
||||
|
||||
if (t !== "object")
|
||||
return k1 === k2;
|
||||
|
||||
if (k1 instanceof Date) {
|
||||
return (k2 instanceof Date) &&
|
||||
k1.getTime() === k2.getTime();
|
||||
}
|
||||
|
||||
if (k1 instanceof Array) {
|
||||
if (!(k2 instanceof Array) ||
|
||||
k1.length != k2.length)
|
||||
return false;
|
||||
|
||||
for (let i = 0; i < k1.length; ++i) {
|
||||
if (!compareKeys(k1[i], k2[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function addPermission(type, allow, url)
|
||||
{
|
||||
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
||||
|
|
|
@ -48,6 +48,26 @@
|
|||
{ keyPath: "foo.2.bar", exception: true },
|
||||
{ keyPath: "foo. .bar", exception: true },
|
||||
{ keyPath: ".bar", exception: true },
|
||||
|
||||
{ keyPath: ["foo", "bar"], value: { foo: 1, bar: 2 }, key: [1, 2] },
|
||||
{ keyPath: ["foo"], value: { foo: 1, bar: 2 }, key: [1] },
|
||||
{ keyPath: ["foo", "bar", "bar"], value: { foo: 1, bar: "x" }, key: [1, "x", "x"] },
|
||||
{ keyPath: ["x", "y"], value: { x: [], y: "x" }, key: [[], "x"] },
|
||||
{ keyPath: ["x", "y"], value: { x: [[1]], y: "x" }, key: [[[1]], "x"] },
|
||||
{ keyPath: ["x", "y"], value: { x: [[1]], y: new Date(1) }, key: [[[1]], new Date(1)] },
|
||||
{ keyPath: ["x", "y"], value: { x: [[1]], y: [new Date(3)] }, key: [[[1]], [new Date(3)]] },
|
||||
{ keyPath: ["x", "y.bar"], value: { x: "hi", y: { bar: "x"} }, key: ["hi", "x"] },
|
||||
{ keyPath: ["x.y", "y.bar"], value: { x: { y: "hello" }, y: { bar: "nurse"} }, key: ["hello", "nurse"] },
|
||||
{ keyPath: ["", ""], value: 5, key: [5, 5] },
|
||||
{ keyPath: ["x", "y"], value: { x: 1 } },
|
||||
{ keyPath: ["x", "y"], value: { y: 1 } },
|
||||
{ keyPath: ["x", "y"], value: { x: 1, y: undefined } },
|
||||
{ keyPath: ["x", "y"], value: { x: null, y: 1 } },
|
||||
{ keyPath: ["x", "y.bar"], value: { x: null, y: { bar: "x"} } },
|
||||
{ keyPath: ["x", "y"], value: { x: 1, y: false } },
|
||||
{ keyPath: ["x", "y", "z"], value: { x: 1, y: false, z: "a" } },
|
||||
{ keyPath: [".x", "y", "z"], exception: true },
|
||||
{ keyPath: ["x", "y ", "z"], exception: true },
|
||||
];
|
||||
|
||||
let openRequest = mozIndexedDB.open(name, 1);
|
||||
|
@ -62,13 +82,16 @@
|
|||
// Test creating object stores and inserting data
|
||||
for (let i = 0; i < keyPaths.length; i++) {
|
||||
let info = keyPaths[i];
|
||||
|
||||
let test = " for objectStore test " + JSON.stringify(info);
|
||||
if (!stores[info.keyPath]) {
|
||||
let indexName = JSON.stringify(info.keyPath);
|
||||
if (!stores[indexName]) {
|
||||
try {
|
||||
let objectStore = db.createObjectStore(info.keyPath, { keyPath: info.keyPath });
|
||||
let objectStore = db.createObjectStore(indexName, { keyPath: info.keyPath });
|
||||
ok(!("exception" in info), "shouldn't throw" + test);
|
||||
is(objectStore.keyPath, info.keyPath, "correct keyPath property" + test);
|
||||
stores[info.keyPath] = objectStore;
|
||||
is(JSON.stringify(objectStore.keyPath), JSON.stringify(info.keyPath),
|
||||
"correct keyPath property" + test);
|
||||
stores[indexName] = objectStore;
|
||||
} catch (e) {
|
||||
ok("exception" in info, "should throw" + test);
|
||||
ok(e instanceof DOMException, "Got a DOM Exception" + test);
|
||||
|
@ -77,7 +100,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
let store = stores[info.keyPath];
|
||||
let store = stores[indexName];
|
||||
|
||||
try {
|
||||
request = store.add(info.value);
|
||||
|
@ -95,7 +118,12 @@
|
|||
let e = yield;
|
||||
is(e.type, "success", "inserted successfully" + test);
|
||||
is(e.target, request, "expected target" + test);
|
||||
is(request.result, info.key, "found correct key" + test);
|
||||
ok(compareKeys(request.result, info.key), "found correct key" + test);
|
||||
is(mozIndexedDB.cmp(request.result, info.key), 0, "returned key compares correctly" + test);
|
||||
|
||||
store.get(info.key).onsuccess = grabEventAndContinueHandler;
|
||||
e = yield;
|
||||
isnot(e.target.result, undefined, "Did find entry");
|
||||
|
||||
store.clear().onsuccess = grabEventAndContinueHandler;
|
||||
yield;
|
||||
|
@ -107,12 +135,14 @@
|
|||
for (let i = 0; i < keyPaths.length; i++) {
|
||||
let test = " for index test " + JSON.stringify(info);
|
||||
let info = keyPaths[i];
|
||||
if (!indexes[info.keyPath]) {
|
||||
let indexName = JSON.stringify(info.keyPath);
|
||||
if (!indexes[indexName]) {
|
||||
try {
|
||||
let index = store.createIndex(info.keyPath, info.keyPath);
|
||||
let index = store.createIndex(indexName, info.keyPath);
|
||||
ok(!("exception" in info), "shouldn't throw" + test);
|
||||
is(index.keyPath, info.keyPath, "index has correct keyPath property" + test);
|
||||
indexes[info.keyPath] = index;
|
||||
is(JSON.stringify(index.keyPath), JSON.stringify(info.keyPath),
|
||||
"index has correct keyPath property" + test);
|
||||
indexes[indexName] = index;
|
||||
} catch (e) {
|
||||
ok("exception" in info, "should throw" + test);
|
||||
ok(e instanceof DOMException, "Got a DOM Exception" + test);
|
||||
|
@ -121,7 +151,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
let index = indexes[info.keyPath];
|
||||
let index = indexes[indexName];
|
||||
|
||||
request = store.add(info.value, 1);
|
||||
if ("key" in info) {
|
||||
|
|
|
@ -50,6 +50,22 @@
|
|||
ok(true, "createIndex with no keyPath should throw");
|
||||
}
|
||||
|
||||
try {
|
||||
request = objectStore.createIndex("Hola", ["foo"], { multiEntry: true });
|
||||
ok(false, "createIndex with array keyPath and multiEntry should throw");
|
||||
}
|
||||
catch(e) {
|
||||
ok(true, "createIndex with array keyPath and multiEntry should throw");
|
||||
}
|
||||
|
||||
try {
|
||||
request = objectStore.createIndex("Hola", []);
|
||||
ok(false, "createIndex with empty array keyPath should throw");
|
||||
}
|
||||
catch(e) {
|
||||
ok(true, "createIndex with empty array keyPath should throw");
|
||||
}
|
||||
|
||||
try {
|
||||
request = objectStore.createIndex("foo", "bar", 10);
|
||||
ok(false, "createIndex with bad options should throw");
|
||||
|
|
|
@ -52,6 +52,14 @@
|
|||
ok(true, "createObjectStore with bad options");
|
||||
}
|
||||
|
||||
try {
|
||||
db.createObjectStore("foo", { keyPath: ["foo"], autoIncrement: true });
|
||||
ok(false, "createObjectStore with keyPath-array and autoIncrement should throw");
|
||||
}
|
||||
catch(e) {
|
||||
ok(true, "createObjectStore with keyPath-array and autoIncrement should throw");
|
||||
}
|
||||
|
||||
ok(db.createObjectStore("foo", { foo: "" }),
|
||||
"createObjectStore with unknown options should not throw");
|
||||
db.deleteObjectStore("foo");
|
||||
|
|
|
@ -11,35 +11,6 @@
|
|||
|
||||
<script type="text/javascript;version=1.7">
|
||||
|
||||
function compareKeys(k1, k2) {
|
||||
let t = typeof k1;
|
||||
if (t != typeof k2)
|
||||
return false;
|
||||
|
||||
if (t !== "object")
|
||||
return k1 === k2;
|
||||
|
||||
if (k1 instanceof Date) {
|
||||
return (k2 instanceof Date) &&
|
||||
k1.getTime() === k2.getTime();
|
||||
}
|
||||
|
||||
if (k1 instanceof Array) {
|
||||
if (!(k2 instanceof Array) ||
|
||||
k1.length != k2.length)
|
||||
return false;
|
||||
|
||||
for (let i = 0; i < k1.length; ++i) {
|
||||
if (!compareKeys(k1[i], k2[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function testSteps()
|
||||
{
|
||||
const dbname = window.location.pathname;
|
||||
|
|
Загрузка…
Ссылка в новой задаче