Bug 692614: Support all spec'ed key-types, including Arrays. Patch by Jan Varga and me. r=janv/bent/me

This commit is contained in:
Jonas Sicking 2011-12-20 02:58:44 -08:00
parent 15504d8795
commit 09d6e71f40
12 changed files with 1188 additions and 256 deletions

View File

@ -457,6 +457,6 @@ IDBFactory::Cmp(const jsval& aFirst,
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
}
*_retval = first == second ? 0 : first < second ? -1 : 1;
*_retval = Key::CompareKeys(first, second);
return NS_OK;
}

View File

@ -272,7 +272,9 @@ IDBKeyRange::FromJSVal(JSContext* aCx,
if (JSVAL_IS_VOID(aVal) || JSVAL_IS_NULL(aVal)) {
// undefined and null returns no IDBKeyRange.
}
else if (JSVAL_IS_PRIMITIVE(aVal)) {
else if (JSVAL_IS_PRIMITIVE(aVal) ||
JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(aVal)) ||
JS_ObjectIsDate(aCx, JSVAL_TO_OBJECT(aVal))) {
// A valid key returns an 'only' IDBKeyRange.
keyRange = new IDBKeyRange(false, false, true);

View File

@ -55,6 +55,8 @@
#include "nsThreadUtils.h"
#include "snappy/snappy.h"
#include "test_quota.h"
#include "xpcprivate.h"
#include "XPCQuickStubs.h"
#include "AsyncConnectionHelper.h"
#include "IDBCursor.h"
@ -1908,10 +1910,10 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
}
mKey.SetFromInteger(autoIncrementNum);
}
else if (mKey.IsInteger() &&
mKey.ToInteger() >= mObjectStore->Info()->nextAutoIncrementId) {
else if (mKey.IsFloat() &&
mKey.ToFloat() >= mObjectStore->Info()->nextAutoIncrementId) {
// XXX Once we support floats, we should use floor(mKey.ToFloat()) here
autoIncrementNum = mKey.ToInteger();
autoIncrementNum = floor(mKey.ToFloat());
}
if (keyUnset && !keyPath.IsEmpty()) {
@ -1925,7 +1927,7 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
PRUint64 u;
} pun;
pun.d = SwapBytes(static_cast<PRUint64>(mKey.ToInteger()));
pun.d = SwapBytes(static_cast<PRUint64>(autoIncrementNum));
JSAutoStructuredCloneBuffer& buffer = mCloneWriteInfo.mCloneBuffer;
PRUint64 offsetToKeyProp = mCloneWriteInfo.mOffsetToKeyProp;

View File

@ -56,6 +56,7 @@
#include "nsXPCOM.h"
#include "nsXPCOMPrivate.h"
#include "test_quota.h"
#include "xpcprivate.h"
#include "AsyncConnectionHelper.h"
#include "CheckQuotaHelper.h"

443
dom/indexedDB/Key.cpp Normal file
View File

@ -0,0 +1,443 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Indexed Database.
*
* The Initial Developer of the Original Code is The Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Ben Turner <bent.mozilla@gmail.com>
*
* 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
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "Key.h"
#include "nsIStreamBufferAccess.h"
#include "jsdate.h"
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "xpcprivate.h"
#include "XPCQuickStubs.h"
USING_INDEXEDDB_NAMESPACE
/*
Here's how we encode keys:
Basic strategy is the following
Numbers: 1 n n n n n n n n ("n"s are encoded 64bit float)
Dates: 2 n n n n n n n n ("n"s are encoded 64bit float)
Strings: 3 s s s ... 0 ("s"s are encoded unicode bytes)
Arrays: 4 i i i ... 0 ("i"s are encoded array items)
When encoding floats, 64bit IEEE 754 are almost sortable, except that
positive sort lower than negative, and negative sort descending. So we use
the following encoding:
value < 0 ?
(-to64bitInt(value)) :
(to64bitInt(value) | 0x8000000000000000)
When encoding strings, we use variable-size encoding per the following table
Chars 0 - 7E are encoded as 0xxxxxxx with 1 added
Chars 7F - (3FFF+7F) are encoded as 10xxxxxx xxxxxxxx with 7F subtracted
Chars (3FFF+80) - FFFF are encoded as 11xxxxxx xxxxxxxx xx000000
This ensures that the first byte is never encoded as 0, which means that the
string terminator (per basic-stategy table) sorts before any character.
The reason that (3FFF+80) - FFFF is encoded "shifted up" 6 bits is to maximize
the chance that the last character is 0. See below for why.
When encoding Arrays, we use an additional trick. Rather than adding a byte
containing the value '4' to indicate type, we instead add 4 to the next byte.
This is usually the byte containing the type of the first item in the array.
So simple examples are
["foo"] 7 s s s 0 0 // 7 is 3 + 4
[1, 2] 5 n n n n n n n n 1 n n n n n n n n 0 // 5 is 1 + 4
Whe do this iteratively if the first item in the array is also an array
[["foo"]] 11 s s s 0 0 0
However, to avoid overflow in the byte, we only do this 3 times. If the first
item in an array is an array, and that array also has an array as first item,
we simply write out the total value accumulated so far and then follow the
"normal" rules.
[[["foo"]]] 12 3 s s s 0 0 0 0
There is another edge case that can happen though, which is that the array
doesn't have a first item to which we can add 4 to the type. Instead the
next byte would normally be the array terminator (per basic-strategy table)
so we simply add the 4 there.
[[]] 8 0 // 8 is 4 + 4 + 0
[] 4 // 4 is 4 + 0
[[], "foo"] 8 3 s s s 0 0 // 8 is 4 + 4 + 0
Note that the max-3-times rule kicks in before we get a chance to add to the
array terminator
[[[]]] 12 0 0 0 // 12 is 4 + 4 + 4
We could use a much higher number than 3 at no complexity or performance cost,
however it seems unlikely that it'll make a practical difference, and the low
limit makes testing eaiser.
As a final optimization we do a post-encoding step which drops all 0s at the
end of the encoded buffer.
"foo" // 3 s s s
1 // 1 bf f0
["a", "b"] // 7 s 3 s
[1, 2] // 5 bf f0 0 0 0 0 0 0 1 c0
[[]] // 8
*/
const int MaxArrayCollapse = 3;
nsresult
Key::EncodeJSVal(JSContext* aCx, const jsval aVal, PRUint8 aTypeOffset)
{
PR_STATIC_ASSERT(eMaxType * MaxArrayCollapse < 256);
if (JSVAL_IS_STRING(aVal)) {
nsDependentJSString str;
if (!str.init(aCx, aVal)) {
return NS_ERROR_OUT_OF_MEMORY;
}
EncodeString(str, aTypeOffset);
return NS_OK;
}
if (JSVAL_IS_INT(aVal)) {
EncodeNumber((double)JSVAL_TO_INT(aVal), eFloat + aTypeOffset);
return NS_OK;
}
if (JSVAL_IS_DOUBLE(aVal)) {
double d = JSVAL_TO_DOUBLE(aVal);
if (DOUBLE_IS_NaN(d)) {
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
}
EncodeNumber(d, eFloat + aTypeOffset);
return NS_OK;
}
if (!JSVAL_IS_PRIMITIVE(aVal)) {
JSObject* obj = JSVAL_TO_OBJECT(aVal);
if (JS_IsArrayObject(aCx, obj)) {
aTypeOffset += eMaxType;
if (aTypeOffset == eMaxType * MaxArrayCollapse) {
mBuffer.Append(aTypeOffset);
aTypeOffset = 0;
}
NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
aTypeOffset < (eMaxType * MaxArrayCollapse),
"Wrong typeoffset");
jsuint length;
if (!JS_GetArrayLength(aCx, obj, &length)) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
for (jsuint index = 0; index < length; index++) {
jsval val;
if (!JS_GetElement(aCx, obj, index, &val)) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
nsresult rv = EncodeJSVal(aCx, val, aTypeOffset);
NS_ENSURE_SUCCESS(rv, rv);
aTypeOffset = 0;
}
mBuffer.Append(eTerminator + aTypeOffset);
return NS_OK;
}
if (JS_ObjectIsDate(aCx, obj)) {
EncodeNumber(js_DateGetMsecSinceEpoch(aCx, obj), eDate + aTypeOffset);
return NS_OK;
}
}
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
}
// static
nsresult
Key::DecodeJSVal(const unsigned char*& aPos, const unsigned char* aEnd,
JSContext* aCx, PRUint8 aTypeOffset, jsval* aVal)
{
if (*aPos - aTypeOffset >= eArray) {
JSObject* array = JS_NewArrayObject(aCx, 0, nsnull);
if (!array) {
NS_WARNING("Failed to make array!");
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
aTypeOffset += eMaxType;
if (aTypeOffset == eMaxType * MaxArrayCollapse) {
++aPos;
aTypeOffset = 0;
}
jsuint index = 0;
while (aPos < aEnd && *aPos - aTypeOffset != eTerminator) {
jsval val;
nsresult rv = DecodeJSVal(aPos, aEnd, aCx, aTypeOffset, &val);
NS_ENSURE_SUCCESS(rv, rv);
aTypeOffset = 0;
if (!JS_SetElement(aCx, array, index++, &val)) {
NS_WARNING("Failed to set array element!");
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
}
NS_ASSERTION(aPos >= aEnd || (*aPos % eMaxType) == eTerminator,
"Should have found end-of-array marker");
++aPos;
*aVal = OBJECT_TO_JSVAL(array);
}
else if (*aPos - aTypeOffset == eString) {
nsString key;
DecodeString(aPos, aEnd, key);
if (!xpc_qsStringToJsval(aCx, key, aVal)) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
}
else if (*aPos - aTypeOffset == eDate) {
jsdouble msec = static_cast<jsdouble>(DecodeNumber(aPos, aEnd));
JSObject* date = JS_NewDateObjectMsec(aCx, msec);
if (!date) {
NS_WARNING("Failed to make date!");
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
*aVal = OBJECT_TO_JSVAL(date);
}
else if (*aPos - aTypeOffset == eFloat) {
*aVal = DOUBLE_TO_JSVAL(DecodeNumber(aPos, aEnd));
}
else {
NS_NOTREACHED("Unknown key type!");
}
return NS_OK;
}
#define ONE_BYTE_LIMIT 0x7E
#define TWO_BYTE_LIMIT (0x3FFF+0x7F)
#define ONE_BYTE_ADJUST 1
#define TWO_BYTE_ADJUST (-0x7F)
#define THREE_BYTE_SHIFT 6
void
Key::EncodeString(const nsAString& aString, PRUint8 aTypeOffset)
{
// First measure how long the encoded string will be.
// The +2 is for initial 3 and trailing 0. We'll compensate for multi-byte
// chars below.
PRUint32 size = aString.Length() + 2;
const PRUnichar* start = aString.BeginReading();
const PRUnichar* end = aString.EndReading();
for (const PRUnichar* iter = start; iter < end; ++iter) {
if (*iter > ONE_BYTE_LIMIT) {
size += *iter > TWO_BYTE_LIMIT ? 2 : 1;
}
}
// Allocate memory for the new size
PRUint32 oldLen = mBuffer.Length();
char* buffer;
if (!mBuffer.GetMutableData(&buffer, oldLen + size)) {
return;
}
buffer += oldLen;
// Write type marker
*(buffer++) = eString + aTypeOffset;
// Encode string
for (const PRUnichar* iter = start; iter < end; ++iter) {
if (*iter <= ONE_BYTE_LIMIT) {
*(buffer++) = *iter + ONE_BYTE_ADJUST;
}
else if (*iter <= TWO_BYTE_LIMIT) {
PRUnichar c = PRUnichar(*iter) + TWO_BYTE_ADJUST + 0x8000;
*(buffer++) = (char)(c >> 8);
*(buffer++) = (char)(c & 0xFF);
}
else {
PRUint32 c = (PRUint32(*iter) << THREE_BYTE_SHIFT) | 0x00C00000;
*(buffer++) = (char)(c >> 16);
*(buffer++) = (char)(c >> 8);
*(buffer++) = (char)c;
}
}
// Write end marker
*(buffer++) = eTerminator;
NS_ASSERTION(buffer == mBuffer.EndReading(), "Wrote wrong number of bytes");
}
// static
void
Key::DecodeString(const unsigned char*& aPos, const unsigned char* aEnd,
nsString& aString)
{
NS_ASSERTION(*aPos % eMaxType == eString, "Don't call me!");
const unsigned char* buffer = aPos + 1;
// First measure how big the decoded string will be.
PRUint32 size = 0;
const unsigned char* iter;
for (iter = buffer; iter < aEnd && *iter != eTerminator; ++iter) {
if (*iter & 0x80) {
iter += (*iter & 0x40) ? 2 : 1;
}
++size;
}
// Set end so that we don't have to check for null termination in the loop
// below
if (iter < aEnd) {
aEnd = iter;
}
PRUnichar* out;
if (size && !aString.GetMutableData(&out, size)) {
return;
}
for (iter = buffer; iter < aEnd;) {
if (!(*iter & 0x80)) {
*out = *(iter++) - ONE_BYTE_ADJUST;
}
else if (!(*iter & 0x40)) {
PRUnichar c = (PRUnichar(*(iter++)) << 8);
if (iter < aEnd) {
c |= *(iter++);
}
*out = c - TWO_BYTE_ADJUST - 0x8000;
}
else {
PRUint32 c = PRUint32(*(iter++)) << (16 - THREE_BYTE_SHIFT);
if (iter < aEnd) {
c |= PRUint32(*(iter++)) << (8 - THREE_BYTE_SHIFT);
}
if (iter < aEnd) {
c |= *(iter++) >> THREE_BYTE_SHIFT;
}
*out = (PRUnichar)c;
}
++out;
}
NS_ASSERTION(!size || out == aString.EndReading(),
"Should have written the whole string");
aPos = iter + 1;
}
union Float64Union {
double d;
PRUint64 u;
};
void
Key::EncodeNumber(double aFloat, PRUint8 aType)
{
// Allocate memory for the new size
PRUint32 oldLen = mBuffer.Length();
char* buffer;
if (!mBuffer.GetMutableData(&buffer, oldLen + 1 + sizeof(double))) {
return;
}
buffer += oldLen;
*(buffer++) = aType;
Float64Union pun;
pun.d = aFloat;
PRUint64 number = pun.u & PR_UINT64(0x8000000000000000) ?
-pun.u :
(pun.u | PR_UINT64(0x8000000000000000));
*reinterpret_cast<PRUint64*>(buffer) = NS_SWAP64(number);
}
// static
double
Key::DecodeNumber(const unsigned char*& aPos, const unsigned char* aEnd)
{
NS_ASSERTION(*aPos % eMaxType == eFloat ||
*aPos % eMaxType == eDate, "Don't call me!");
++aPos;
PRUint64 number = 0;
for (PRInt32 n = 7; n >= 0; --n) {
number <<= 8;
if (aPos < aEnd) {
number |= *(aPos++);
}
else {
number <<= 8 * n;
break;
}
}
Float64Union pun;
pun.u = number & PR_UINT64(0x8000000000000000) ?
(number & ~PR_UINT64(0x8000000000000000)) :
-number;
return pun.d;
}

View File

@ -43,10 +43,6 @@
#include "mozilla/dom/indexedDB/IndexedDatabase.h"
#include "mozIStorageStatement.h"
#include "nsJSUtils.h"
#include "xpcprivate.h"
#include "XPCQuickStubs.h"
BEGIN_INDEXEDDB_NAMESPACE
@ -72,179 +68,139 @@ public:
bool operator==(const Key& aOther) const
{
NS_ASSERTION(mType != KEYTYPE_VOID && aOther.mType != KEYTYPE_VOID,
NS_ASSERTION(!mBuffer.IsVoid() && !aOther.mBuffer.IsVoid(),
"Don't compare unset keys!");
if (mType == aOther.mType) {
switch (mType) {
case KEYTYPE_STRING:
return ToString() == aOther.ToString();
case KEYTYPE_INTEGER:
return ToInteger() == aOther.ToInteger();
default:
NS_NOTREACHED("Unknown type!");
}
}
return false;
return mBuffer.Equals(aOther.mBuffer);
}
bool operator!=(const Key& aOther) const
{
return !(*this == aOther);
NS_ASSERTION(!mBuffer.IsVoid() && !aOther.mBuffer.IsVoid(),
"Don't compare unset keys!");
return !mBuffer.Equals(aOther.mBuffer);
}
bool operator<(const Key& aOther) const
{
NS_ASSERTION(mType != KEYTYPE_VOID && aOther.mType != KEYTYPE_VOID,
NS_ASSERTION(!mBuffer.IsVoid() && !aOther.mBuffer.IsVoid(),
"Don't compare unset keys!");
switch (mType) {
case KEYTYPE_STRING: {
if (aOther.mType == KEYTYPE_INTEGER) {
return false;
}
NS_ASSERTION(aOther.mType == KEYTYPE_STRING, "Unknown type!");
return ToString() < aOther.ToString();
}
case KEYTYPE_INTEGER:
if (aOther.mType == KEYTYPE_STRING) {
return true;
}
NS_ASSERTION(aOther.mType == KEYTYPE_INTEGER, "Unknown type!");
return ToInteger() < aOther.ToInteger();
default:
NS_NOTREACHED("Unknown type!");
}
return false;
return Compare(mBuffer, aOther.mBuffer) < 0;
}
bool operator>(const Key& aOther) const
{
return !(*this == aOther || *this < aOther);
NS_ASSERTION(!mBuffer.IsVoid() && !aOther.mBuffer.IsVoid(),
"Don't compare unset keys!");
return Compare(mBuffer, aOther.mBuffer) > 0;
}
bool operator<=(const Key& aOther) const
{
return (*this == aOther || *this < aOther);
NS_ASSERTION(!mBuffer.IsVoid() && !aOther.mBuffer.IsVoid(),
"Don't compare unset keys!");
return Compare(mBuffer, aOther.mBuffer) <= 0;
}
bool operator>=(const Key& aOther) const
{
return (*this == aOther || !(*this < aOther));
NS_ASSERTION(!mBuffer.IsVoid() && !aOther.mBuffer.IsVoid(),
"Don't compare unset keys!");
return Compare(mBuffer, aOther.mBuffer) >= 0;
}
void
Unset()
{
mType = KEYTYPE_VOID;
mStringKey.SetIsVoid(true);
mIntKey = 0;
mBuffer.SetIsVoid(true);
}
bool IsUnset() const { return mType == KEYTYPE_VOID; }
bool IsString() const { return mType == KEYTYPE_STRING; }
bool IsInteger() const { return mType == KEYTYPE_INTEGER; }
nsresult SetFromString(const nsAString& aString)
bool IsUnset() const
{
mType = KEYTYPE_STRING;
mStringKey = aString;
mIntKey = 0;
return NS_OK;
return mBuffer.IsVoid();
}
nsresult SetFromInteger(PRInt64 aInt)
bool IsFloat() const
{
mType = KEYTYPE_INTEGER;
mStringKey.SetIsVoid(true);
mIntKey = aInt;
return NS_OK;
return !mBuffer.IsVoid() && mBuffer.First() == eFloat;
}
double ToFloat() const
{
NS_ASSERTION(IsFloat(), "Why'd you call this?");
const unsigned char* pos = BufferStart();
double res = DecodeNumber(pos, BufferEnd());
NS_ASSERTION(pos >= BufferEnd(), "Should consume whole buffer");
return res;
}
void SetFromString(const nsAString& aString)
{
mBuffer.Truncate();
EncodeString(aString, 0);
TrimBuffer();
}
void SetFromInteger(PRInt64 aInt)
{
mBuffer.Truncate();
EncodeNumber(double(aInt), eFloat);
TrimBuffer();
}
nsresult SetFromJSVal(JSContext* aCx,
jsval aVal)
const jsval aVal)
{
if (JSVAL_IS_STRING(aVal)) {
nsDependentJSString str;
if (!str.init(aCx, aVal)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return SetFromString(str);
}
if (JSVAL_IS_INT(aVal)) {
SetFromInteger(JSVAL_TO_INT(aVal));
return NS_OK;
}
if (JSVAL_IS_DOUBLE(aVal)) {
jsdouble doubleActual = JSVAL_TO_DOUBLE(aVal);
int64 doubleAsInt = static_cast<int64>(doubleActual);
if (doubleActual == doubleAsInt) {
SetFromInteger(doubleAsInt);
return NS_OK;
}
}
mBuffer.Truncate();
if (JSVAL_IS_NULL(aVal) || JSVAL_IS_VOID(aVal)) {
Unset();
return NS_OK;
}
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
nsresult rv = EncodeJSVal(aCx, aVal, 0);
if (NS_FAILED(rv)) {
Unset();
return rv;
}
TrimBuffer();
return NS_OK;
}
nsresult ToJSVal(JSContext* aCx,
jsval* aVal) const
{
if (IsString()) {
nsString key = ToString();
if (!xpc_qsStringToJsval(aCx, key, aVal)) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
}
else if (IsInteger()) {
if (!JS_NewNumberValue(aCx, static_cast<jsdouble>(ToInteger()), aVal)) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
}
else if (IsUnset()) {
if (IsUnset()) {
*aVal = JSVAL_VOID;
return NS_OK;
}
else {
NS_NOTREACHED("Unknown key type!");
}
const unsigned char* pos = BufferStart();
nsresult rv = DecodeJSVal(pos, BufferEnd(), aCx, 0, aVal);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(pos >= BufferEnd(),
"Didn't consume whole buffer");
return NS_OK;
}
PRInt64 ToInteger() const
const nsCString& GetBuffer() const
{
NS_ASSERTION(IsInteger(), "Don't call me!");
return mIntKey;
}
const nsString& ToString() const
{
NS_ASSERTION(IsString(), "Don't call me!");
return mStringKey;
return mBuffer;
}
nsresult BindToStatement(mozIStorageStatement* aStatement,
const nsACString& aParamName) const
{
nsresult rv;
if (IsString()) {
rv = aStatement->BindStringByName(aParamName, ToString());
}
else {
NS_ASSERTION(IsInteger(), "Bad key!");
rv = aStatement->BindInt64ByName(aParamName, ToInteger());
}
nsresult rv = aStatement->BindBlobByName(aParamName,
reinterpret_cast<const PRUint8*>(mBuffer.get()), mBuffer.Length());
return NS_SUCCEEDED(rv) ? NS_OK : NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
@ -252,60 +208,83 @@ public:
nsresult SetFromStatement(mozIStorageStatement* aStatement,
PRUint32 aIndex)
{
PRInt32 columnType;
nsresult rv = aStatement->GetTypeOfIndex(aIndex, &columnType);
PRUint8* data;
PRUint32 dataLength = 0;
nsresult rv = aStatement->GetBlob(aIndex, &dataLength, &data);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
NS_ASSERTION(columnType == mozIStorageStatement::VALUE_TYPE_INTEGER ||
columnType == mozIStorageStatement::VALUE_TYPE_TEXT,
"Unsupported column type!");
mBuffer.Adopt(
reinterpret_cast<char*>(const_cast<PRUint8*>(data)), dataLength);
return SetFromStatement(aStatement, aIndex, columnType);
}
nsresult SetFromStatement(mozIStorageStatement* aStatement,
PRUint32 aIndex,
PRInt32 aColumnType)
{
if (aColumnType == mozIStorageStatement::VALUE_TYPE_INTEGER) {
return SetFromInteger(aStatement->AsInt64(aIndex));
}
if (aColumnType == mozIStorageStatement::VALUE_TYPE_TEXT) {
nsString keyString;
nsresult rv = aStatement->GetString(aIndex, keyString);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return SetFromString(keyString);
}
NS_NOTREACHED("Unsupported column type!");
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
return NS_OK;
}
static
bool CanBeConstructedFromJSVal(jsval aVal)
PRInt16 CompareKeys(Key& aFirst, Key& aSecond)
{
return JSVAL_IS_INT(aVal) || JSVAL_IS_DOUBLE(aVal) || JSVAL_IS_STRING(aVal);
PRInt32 result = Compare(aFirst.mBuffer, aSecond.mBuffer);
if (result < 0) {
return -1;
}
if (result > 0) {
return 1;
}
return 0;
}
private:
// Wish we could use JSType here but we will end up supporting types like Date
// which JSType can't really identify. Rolling our own for now.
enum Type {
KEYTYPE_VOID,
KEYTYPE_STRING,
KEYTYPE_INTEGER
const unsigned char* BufferStart() const
{
return reinterpret_cast<const unsigned char*>(mBuffer.BeginReading());
}
const unsigned char* BufferEnd() const
{
return reinterpret_cast<const unsigned char*>(mBuffer.EndReading());
}
enum {
eTerminator = 0,
eFloat = 1,
eDate = 2,
eString = 3,
eArray = 4,
eMaxType = eArray
};
// Type of value in mJSVal.
Type mType;
// Encoding helper. Trims trailing zeros off of mBuffer as a post-processing
// step.
void TrimBuffer()
{
const char* end = mBuffer.EndReading() - 1;
while (!*end) {
--end;
}
// The string if mType is KEYTYPE_STRING, otherwise a void string.
nsString mStringKey;
mBuffer.Truncate(end + 1 - mBuffer.BeginReading());
}
// The integer value if mType is KEYTYPE_INTEGER, otherwise 0.
int64 mIntKey;
// Encoding functions. These append the encoded value to the end of mBuffer
nsresult EncodeJSVal(JSContext* aCx, const jsval aVal, PRUint8 aTypeOffset);
void EncodeString(const nsAString& aString, PRUint8 aTypeOffset);
void EncodeNumber(double aFloat, PRUint8 aType);
// Decoding functions. aPos points into mBuffer and is adjusted to point
// past the consumed value.
static nsresult DecodeJSVal(const unsigned char*& aPos,
const unsigned char* aEnd, JSContext* aCx,
PRUint8 aTypeOffset, jsval* aVal);
static void DecodeString(const unsigned char*& aPos,
const unsigned char* aEnd,
nsString& aString);
static double DecodeNumber(const unsigned char*& aPos,
const unsigned char* aEnd);
nsCString mBuffer;
};
END_INDEXEDDB_NAMESPACE

View File

@ -71,6 +71,7 @@ CPPSRCS = \
LazyIdleThread.cpp \
OpenDatabaseHelper.cpp \
TransactionThreadPool.cpp \
Key.cpp \
$(NULL)
EXPORTS_mozilla/dom/indexedDB = \

View File

@ -60,7 +60,7 @@ namespace {
PR_STATIC_ASSERT(JS_STRUCTURED_CLONE_VERSION == 1);
// Major schema version. Bump for almost everything.
const PRUint32 kMajorSchemaVersion = 11;
const PRUint32 kMajorSchemaVersion = 12;
// Minor schema version. Should almost always be 0 (maybe bump on release
// branches if we have to).
@ -215,7 +215,7 @@ CreateTables(mozIStorageConnection* aDBConn)
"CREATE TABLE object_data ("
"id INTEGER PRIMARY KEY, "
"object_store_id INTEGER NOT NULL, "
"key_value DEFAULT NULL, "
"key_value BLOB DEFAULT NULL, "
"data BLOB NOT NULL, "
"file_ids TEXT, "
"UNIQUE (object_store_id, key_value), "
@ -245,8 +245,8 @@ CreateTables(mozIStorageConnection* aDBConn)
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE index_data ("
"index_id INTEGER NOT NULL, "
"value NOT NULL, "
"object_data_key NOT NULL, "
"value BLOB NOT NULL, "
"object_data_key BLOB 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 "
@ -268,8 +268,8 @@ CreateTables(mozIStorageConnection* aDBConn)
rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE unique_index_data ("
"index_id INTEGER NOT NULL, "
"value NOT NULL, "
"object_data_key NOT NULL, " // NONE affinity
"value BLOB NOT NULL, "
"object_data_key BLOB NOT NULL, "
"object_data_id INTEGER NOT NULL, "
"PRIMARY KEY (index_id, value, object_data_key), "
"UNIQUE (index_id, value), "
@ -1078,6 +1078,269 @@ UpgradeSchemaFrom10_0To11_0(mozIStorageConnection* aConnection)
return NS_OK;
}
class EncodeKeysFunction : public mozIStorageFunction
{
public:
NS_DECL_ISUPPORTS
NS_IMETHOD
OnFunctionCall(mozIStorageValueArray* aArguments,
nsIVariant** aResult)
{
PRUint32 argc;
nsresult rv = aArguments->GetNumEntries(&argc);
NS_ENSURE_SUCCESS(rv, rv);
if (argc != 1) {
NS_WARNING("Don't call me with the wrong number of arguments!");
return NS_ERROR_UNEXPECTED;
}
PRInt32 type;
rv = aArguments->GetTypeOfIndex(0, &type);
NS_ENSURE_SUCCESS(rv, rv);
Key key;
if (type == mozIStorageStatement::VALUE_TYPE_INTEGER) {
PRInt64 intKey;
aArguments->GetInt64(0, &intKey);
key.SetFromInteger(intKey);
}
else if (type == mozIStorageStatement::VALUE_TYPE_TEXT) {
nsString stringKey;
aArguments->GetString(0, stringKey);
key.SetFromString(stringKey);
}
else {
NS_WARNING("Don't call me with the wrong type of arguments!");
return NS_ERROR_UNEXPECTED;
}
const nsCString& buffer = key.GetBuffer();
std::pair<const void *, int> data(static_cast<const void*>(buffer.get()),
int(buffer.Length()));
nsCOMPtr<nsIVariant> result = new mozilla::storage::BlobVariant(data);
result.forget(aResult);
return NS_OK;
}
};
NS_IMPL_ISUPPORTS1(EncodeKeysFunction, mozIStorageFunction)
nsresult
UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection)
{
NS_NAMED_LITERAL_CSTRING(encoderName, "encode");
nsCOMPtr<mozIStorageFunction> encoder = new EncodeKeysFunction();
nsresult rv = aConnection->CreateFunction(encoderName, 1, encoder);
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMPORARY TABLE temp_upgrade ("
"id INTEGER PRIMARY KEY, "
"object_store_id, "
"key_value, "
"data, "
"file_ids "
");"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO temp_upgrade "
"SELECT id, object_store_id, encode(key_value), data, file_ids "
"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 BLOB DEFAULT NULL, "
"data BLOB NOT NULL, "
"file_ids TEXT, "
"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, file_ids "
"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 TRIGGER object_data_insert_trigger "
"AFTER INSERT ON object_data "
"FOR EACH ROW "
"WHEN NEW.file_ids IS NOT NULL "
"BEGIN "
"SELECT update_refcount(NULL, NEW.file_ids); "
"END;"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TRIGGER object_data_update_trigger "
"AFTER UPDATE OF file_ids ON object_data "
"FOR EACH ROW "
"WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
"BEGIN "
"SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
"END;"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TRIGGER object_data_delete_trigger "
"AFTER DELETE ON object_data "
"FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
"BEGIN "
"SELECT update_refcount(OLD.file_ids, NULL); "
"END;"
));
NS_ENSURE_SUCCESS(rv, rv);
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, encode(value), encode(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 BLOB NOT NULL, "
"object_data_key BLOB 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);
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, encode(value), encode(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 BLOB NOT NULL, "
"object_data_key BLOB 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);
rv = aConnection->RemoveFunction(encoderName);
NS_ENSURE_SUCCESS(rv, rv);
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(12, 0));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
class VersionChangeEventsRunnable;
class SetVersionHelper : public AsyncConnectionHelper,
@ -1529,7 +1792,7 @@ OpenDatabaseHelper::CreateDatabaseConnection(
}
else {
// This logic needs to change next time we change the schema!
PR_STATIC_ASSERT(kSQLiteSchemaVersion == PRInt32((11 << 4) + 0));
PR_STATIC_ASSERT(kSQLiteSchemaVersion == PRInt32((12 << 4) + 0));
while (schemaVersion != kSQLiteSchemaVersion) {
if (schemaVersion == 4) {
@ -1554,6 +1817,9 @@ OpenDatabaseHelper::CreateDatabaseConnection(
else if (schemaVersion == MakeSchemaVersion(10, 0)) {
rv = UpgradeSchemaFrom10_0To11_0(connection);
}
else if (schemaVersion == MakeSchemaVersion(11, 0)) {
rv = UpgradeSchemaFrom11_0To12_0(connection);
}
else {
NS_WARNING("Unable to open IndexedDB database, no upgrade path is "
"available!");

View File

@ -60,7 +60,6 @@ TEST_FILES = \
test_autoIncrement.html \
test_bfcache.html \
test_clear.html \
test_cmp.html \
test_complex_keyPaths.html \
test_count.html \
test_create_index.html \
@ -94,6 +93,7 @@ TEST_FILES = \
test_indexes.html \
test_indexes_bad_values.html \
test_key_requirements.html \
test_keys.html \
test_leaving_page.html \
test_multientry.html \
test_objectCursors.html \

View File

@ -12,7 +12,8 @@
<script type="text/javascript;version=1.7">
function genCheck(key, value, test, options) {
return function(event) {
is(event.target.result, key, "correct returned key in " + test);
is(JSON.stringify(event.target.result), JSON.stringify(key),
"correct returned key in " + test);
if (options && options.store) {
is(event.target.source, options.store, "correct store in " + test);
}
@ -120,6 +121,14 @@
genCheck(c1++, { explicit: 8 }, "eighth" + test);
yield;
trans = db.transaction("store1", RW);
trans.objectStore("store1").add({ explicit: 7 }, [100000]).onsuccess =
genCheck([100000], { explicit: 7 }, "seventh" + test);
yield;
trans.objectStore("store1").add({ explicit: 8 }).onsuccess =
genCheck(c1++, { explicit: 8 }, "eighth" + test);
yield;
trans = db.transaction("store1", RW);
trans.objectStore("store1").add({ explicit: 9 }, -100000).onsuccess =
genCheck(-100000, { explicit: 9 }, "ninth" + test);
@ -166,6 +175,15 @@
c2++;
yield;
trans = db.transaction("store2", RW);
trans.objectStore("store2").add({ explicit: 7, id: [100000] }).onsuccess =
genCheck([100000], { explicit: 7, id: [100000] }, "seventh store2" + test);
yield;
trans.objectStore("store2").add({ explicit: 8 }).onsuccess =
genCheck(c2, { explicit: 8, id: c2 }, "eighth store2" + test);
c2++;
yield;
trans = db.transaction("store2", RW);
trans.objectStore("store2").add({ explicit: 9, id: -100000 }).onsuccess =
genCheck(-100000, { explicit: 9, id: -100000 }, "ninth store2" + test);

View File

@ -1,85 +0,0 @@
<!--
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()
{
function compare(key1, key2, expected, exception) {
function maybeQuote(key) {
return typeof(key) == "string" ? "\"" + key + "\"" : key;
}
var cmp = "cmp(" + maybeQuote(key1) + ", " + maybeQuote(key2) + ")";
if (exception) {
var caught;
try {
var result = mozIndexedDB.cmp(key1, key2);
}
catch(e) {
caught = e;
}
ok(caught, "Got an exception for " + cmp);
is(caught instanceof IDBDatabaseException, true,
"Got IDBDatabaseException for " + cmp);
is(caught.code, IDBDatabaseException.DATA_ERR,
"Got correct exception code for " + cmp);
}
else {
is(mozIndexedDB.cmp(key1, key2), expected,
"Correct result for " + cmp);
}
}
compare(NaN, 0, 0, true);
compare(0, NaN, 0, true);
compare(undefined, 0, 0, true);
compare(0, undefined, 0, true);
compare(null, 0, 0, true);
compare(0, null, 0, true);
compare(0, 0, 0);
compare(1, 0, 1);
compare(0, 1, -1);
compare(1, 1, 0);
compare(2, 1, 1);
compare(1, 2, -1);
compare(-1, -1, 0);
compare(0, -1, 1);
compare(-1, 0, -1);
compare("", "", 0);
compare("a", "", 1);
compare("", "a", -1);
compare("a", "a", 0);
compare("a", "b", -1);
compare("b", "a", 1);
compare("a", "aa", -1);
compare("aa", "a", 1);
compare(0, "", -1);
compare("", 0, 1);
compare(0, "a", -1);
compare("a", 0, 1);
compare(99999, "", -1);
compare("", 99999, 1);
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>

View File

@ -0,0 +1,305 @@
<!--
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 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;
const RW = IDBTransaction.READ_WRITE
let c1 = 1;
let c2 = 1;
let openRequest = mozIndexedDB.open(dbname, 1);
openRequest.onerror = errorHandler;
openRequest.onupgradeneeded = grabEventAndContinueHandler;
openRequest.onsuccess = unexpectedSuccessHandler;
let event = yield;
let db = event.target.result;
let trans = event.target.transaction;
// Create test stores
let store = db.createObjectStore("store");
// Test simple inserts
var keys = [
-1/0,
-1.7e308,
-10000,
-2,
-1.5,
-1,
-1.00001e-200,
-1e-200,
0,
1e-200,
1.00001e-200,
1,
2,
10000,
1.7e308,
1/0,
new Date("1750-01-02"),
new Date("1800-12-31T12:34:56.001"),
new Date(-1000),
new Date(-10),
new Date(-1),
new Date(0),
new Date(1),
new Date(2),
new Date(1000),
new Date("1971-01-01"),
new Date("1971-01-01T01:01:01"),
new Date("1971-01-01T01:01:01.001"),
new Date("1971-01-01T01:01:01.01"),
new Date("1971-01-01T01:01:01.1"),
new Date("1980-02-02"),
new Date("3333-03-19T03:33:33.333"),
"",
"\x00",
"\x00\x00",
"\x00\x01",
"\x01",
"\x02",
"\x03",
"\x04",
"\x07",
"\x08",
"\x0F",
"\x10",
"\x1F",
"\x20",
"01234",
"\x3F",
"\x40",
"A",
"A\x00",
"A1",
"ZZZZ",
"a",
"a\x00",
"aa",
"azz",
"}",
"\x7E",
"\x7F",
"\x80",
"\xFF",
"\u0100",
"\u01FF",
"\u0200",
"\u03FF",
"\u0400",
"\u07FF",
"\u0800",
"\u0FFF",
"\u1000",
"\u1FFF",
"\u2000",
"\u3FFF",
"\u4000",
"\u7FFF",
"\u8000",
"\uD800",
"\uD800a",
"\uD800\uDC01",
"\uDBFF",
"\uDC00",
"\uDFFF\uD800",
"\uFFFE",
"\uFFFF",
"\uFFFF\x00",
"\uFFFFZZZ",
[],
[-1/0],
[-1],
[0],
[1],
[1, "a"],
[1, []],
[1, [""]],
[2, 3],
[2, 3.0000000000001],
[12, [[]]],
[12, [[[]]]],
[12, [[[""]]]],
[12, [[["foo"]]]],
[12, [[[[[3]]]]]],
[12, [[[[[[3]]]]]]],
[new Date(-1)],
[new Date(1)],
[""],
["", [[]]],
["", [[[]]]],
["abc"],
["abc", "def"],
["abc\x00"],
["abc\x00", "\x00\x01"],
["abc\x00", "\x00def"],
["abc\x00\x00def"],
["x", [[]]],
["x", [[[]]]],
[[]],
[[],"foo"],
[[],[]],
[[[]]],
[[[]], []],
[[[]], [[]]],
[[[]], [[1]]],
[[[]], [[[]]]],
[[[1]]],
[[[[]], []]],
];
for (var i = 0; i < keys.length; ++i) {
let keyI = keys[i];
is(mozIndexedDB.cmp(keyI, keyI), 0, i + " compared to self");
function doCompare(keyI) {
for (var j = i-1; j >= i-10 && j >= 0; --j) {
is(mozIndexedDB.cmp(keyI, keys[j]), 1, i + " compared to " + j);
is(mozIndexedDB.cmp(keys[j], keyI), -1, j + " compared to " + i);
}
}
doCompare(keyI);
store.add(i, keyI).onsuccess = function(e) {
is(mozIndexedDB.cmp(e.target.result, keyI), 0,
"Returned key should cmp as equal");
ok(compareKeys(e.target.result, keyI),
"Returned key should actually be equal");
};
// Test that -0 compares the same as 0
if (keyI === 0) {
doCompare(-0);
let req = store.add(i, -0);
req.onerror = new ExpectError(IDBDatabaseException.CONSTRAINT_ERR);
req.onsuccess = unexpectedSuccessHandler;
yield;
}
else if (Array.isArray(keyI) && keyI.length === 1 && keyI[0] === 0) {
doCompare([-0]);
let req = store.add(i, [-0]);
req.onerror = new ExpectError(IDBDatabaseException.CONSTRAINT_ERR);
req.onsuccess = unexpectedSuccessHandler;
yield;
}
}
store.openCursor().onsuccess = grabEventAndContinueHandler;
for (i = 0; i < keys.length; ++i) {
event = yield;
let cursor = event.target.result;
is(mozIndexedDB.cmp(cursor.key, keys[i]), 0,
"Read back key should cmp as equal");
ok(compareKeys(cursor.key, keys[i]),
"Read back key should actually be equal");
is(cursor.value, i, "Stored with right value");
cursor.continue();
}
event = yield;
is(event.target.result, undefined, "no more results expected");
var nan = 0/0;
var invalidKeys = [
nan,
undefined,
null,
/x/,
{},
[nan],
[undefined],
[null],
[/x/],
[{}],
[1, nan],
[1, undefined],
[1, null],
[1, /x/],
[1, {}],
[1, [nan]],
[1, [undefined]],
[1, [null]],
[1, [/x/]],
[1, [{}]],
];
for (i = 0; i < invalidKeys.length; ++i) {
try {
mozIndexedDB.cmp(invalidKeys[i], 1);
ok(false, "didn't throw");
}
catch(ex) {
ok(ex instanceof IDBDatabaseException, "Threw IDBDatabaseException");
is(ex.code, IDBDatabaseException.DATA_ERR, "Threw right IDBDatabaseException");
}
try {
mozIndexedDB.cmp(1, invalidKeys[i]);
ok(false, "didn't throw2");
}
catch(ex) {
ok(ex instanceof IDBDatabaseException, "Threw IDBDatabaseException2");
is(ex.code, IDBDatabaseException.DATA_ERR, "Threw right IDBDatabaseException2");
}
try {
store.put(1, invalidKeys[i]);
ok(false, "didn't throw3");
}
catch(ex) {
ok(ex instanceof IDBDatabaseException, "Threw IDBDatabaseException3");
is(ex.code, IDBDatabaseException.DATA_ERR, "Threw right IDBDatabaseException3");
}
}
openRequest.onsuccess = grabEventAndContinueHandler;
yield;
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>