Bug 1404276 - Support IDB key extraction with autoincremented primary key. r=dom-storage-reviewers,janv

This patch is originally authored by Bevis Tseng.

Differential Revision: https://phabricator.services.mozilla.com/D204361
This commit is contained in:
Jari Jalkanen 2024-04-08 15:25:27 +00:00
parent 65be157489
commit 17acc6cd4d
11 changed files with 125 additions and 199 deletions

View File

@ -3637,7 +3637,7 @@ class ObjectStoreAddOrPutRequestOp final : public NormalTransactionOp {
};
class SCInputStream;
const ObjectStoreAddPutParams mParams;
ObjectStoreAddPutParams mParams;
Maybe<UniqueIndexTable> mUniqueIndexTable;
// This must be non-const so that we can update the mNextAutoIncrementId field
@ -6453,9 +6453,9 @@ class DeserializeIndexValueHelper final : public Runnable {
value.setUndefined();
ErrorResult rv;
IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mMultiEntry,
mLocale, jsapi.cx(), value,
&mUpdateInfoArray, &rv);
IDBObjectStore::AppendIndexUpdateInfo(
mIndexID, mKeyPath, mMultiEntry, &mUpdateInfoArray,
/* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &rv);
return rv.Failed() ? rv.StealNSResult() : NS_OK;
}
#endif
@ -6493,9 +6493,9 @@ class DeserializeIndexValueHelper final : public Runnable {
[this](const nsresult rv) { OperationCompleted(rv); });
ErrorResult errorResult;
IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mMultiEntry,
mLocale, cx, value, &mUpdateInfoArray,
&errorResult);
IDBObjectStore::AppendIndexUpdateInfo(
mIndexID, mKeyPath, mMultiEntry, mLocale, cx, value, &mUpdateInfoArray,
/* aAutoIncrementedObjectStoreKeyPath */ VoidString(), &errorResult);
QM_TRY(OkIf(!errorResult.Failed()), NS_OK,
([this, &errorResult](const NotOk) {
OperationCompleted(errorResult.StealNSResult());
@ -18653,6 +18653,11 @@ nsresult ObjectStoreAddOrPutRequestOp::DoDatabaseWork(
}
QM_TRY(key.SetFromInteger(autoIncrementNum));
// Update index keys if primary key is preserved in child.
for (auto& updateInfo : mParams.indexUpdateInfos()) {
updateInfo.value().MaybeUpdateAutoIncrementKey(autoIncrementNum);
}
} else if (key.IsFloat()) {
double numericKey = key.ToFloat();
numericKey = std::min(numericKey, double(1LL << 53));

View File

@ -410,13 +410,20 @@ RefPtr<IDBObjectStore> IDBObjectStore::Create(
void IDBObjectStore::AppendIndexUpdateInfo(
const int64_t aIndexID, const KeyPath& aKeyPath, const bool aMultiEntry,
const nsCString& aLocale, JSContext* const aCx, JS::Handle<JS::Value> aVal,
nsTArray<IndexUpdateInfo>* const aUpdateInfoArray, ErrorResult* const aRv) {
nsTArray<IndexUpdateInfo>* const aUpdateInfoArray,
const VoidOrObjectStoreKeyPathString& aAutoIncrementedObjectStoreKeyPath,
ErrorResult* const aRv) {
// This precondition holds when `aVal` is the result of a structured clone.
js::AutoAssertNoContentJS noContentJS(aCx);
static_assert(std::is_same_v<IDBObjectStore::VoidOrObjectStoreKeyPathString,
KeyPath::VoidOrObjectStoreKeyPathString>,
"Inconsistent types");
if (!aMultiEntry) {
Key key;
*aRv = aKeyPath.ExtractKey(aCx, aVal, key);
*aRv =
aKeyPath.ExtractKey(aCx, aVal, key, aAutoIncrementedObjectStoreKeyPath);
// If an index's keyPath doesn't match an object, we ignore that object.
if (aRv->ErrorCodeIs(NS_ERROR_DOM_INDEXEDDB_DATA_ERR) || key.IsUnset()) {
@ -625,6 +632,21 @@ void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper,
const nsTArray<IndexMetadata>& indexes = mSpec->indexes();
const uint32_t idxCount = indexes.Length();
const auto& autoIncrementedObjectStoreKeyPath =
[this]() -> const nsAString& {
if (AutoIncrement() && GetKeyPath().IsValid()) {
// By https://w3c.github.io/IndexedDB/#database-interface ,
// createObjectStore algorithm, step 8, neither arrays nor empty paths
// are allowed for autoincremented object stores.
// See also KeyPath::IsAllowedForObjectStore.
MOZ_ASSERT(GetKeyPath().IsString());
MOZ_ASSERT(!GetKeyPath().IsEmpty());
return GetKeyPath().mStrings[0];
}
return VoidString();
}();
aUpdateInfoArray.SetCapacity(idxCount); // Pretty good estimate
for (uint32_t idxIndex = 0; idxIndex < idxCount; idxIndex++) {
@ -632,7 +654,8 @@ void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper,
AppendIndexUpdateInfo(metadata.id(), metadata.keyPath(),
metadata.multiEntry(), metadata.locale(), aCx,
aValueWrapper.Value(), &aUpdateInfoArray, &aRv);
aValueWrapper.Value(), &aUpdateInfoArray,
autoIncrementedObjectStoreKeyPath, &aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}

View File

@ -48,6 +48,7 @@ class IDBObjectStore final : public nsISupports, public nsWrapperCache {
using KeyPath = indexedDB::KeyPath;
using ObjectStoreSpec = indexedDB::ObjectStoreSpec;
using StructuredCloneReadInfoChild = indexedDB::StructuredCloneReadInfoChild;
using VoidOrObjectStoreKeyPathString = nsAString;
// For AddOrPut() and DeleteInternal().
// TODO Consider removing this, and making the functions public?
@ -98,11 +99,12 @@ class IDBObjectStore final : public nsISupports, public nsWrapperCache {
[[nodiscard]] static RefPtr<IDBObjectStore> Create(
SafeRefPtr<IDBTransaction> aTransaction, ObjectStoreSpec& aSpec);
static void AppendIndexUpdateInfo(int64_t aIndexID, const KeyPath& aKeyPath,
bool aMultiEntry, const nsCString& aLocale,
JSContext* aCx, JS::Handle<JS::Value> aVal,
nsTArray<IndexUpdateInfo>* aUpdateInfoArray,
ErrorResult* aRv);
static void AppendIndexUpdateInfo(
int64_t aIndexID, const KeyPath& aKeyPath, bool aMultiEntry,
const nsCString& aLocale, JSContext* aCx, JS::Handle<JS::Value> aVal,
nsTArray<IndexUpdateInfo>* aUpdateInfoArray,
const VoidOrObjectStoreKeyPathString& aAutoIncrementedObjectStoreKeyPath,
ErrorResult* aRv);
static void ClearCloneReadInfo(
indexedDB::StructuredCloneReadInfoChild& aReadInfo);

View File

@ -560,6 +560,51 @@ Result<Ok, nsresult> Key::EncodeString(const Span<const T> aInput,
#define KEY_MAXIMUM_BUFFER_LENGTH \
::mozilla::detail::nsTStringLengthStorage<char>::kMax
void Key::ReserveAutoIncrementKey(bool aFirstOfArray) {
// Allocate memory for the new size
uint32_t oldLen = mBuffer.Length();
char* buffer;
if (!mBuffer.GetMutableData(&buffer, oldLen + 1 + sizeof(double))) {
return;
}
// Remember the offset of the buffer to be updated later.
mAutoIncrementKeyOffsets.AppendElement(oldLen + 1);
// Fill the type.
buffer += oldLen;
*(buffer++) = aFirstOfArray ? (eMaxType + eFloat) : eFloat;
// Fill up with 0xFF to reserve the buffer in fixed size because the encoded
// string could be trimmed if ended with padding zeros.
mozilla::BigEndian::writeUint64(buffer, UINT64_MAX);
}
void Key::MaybeUpdateAutoIncrementKey(int64_t aKey) {
if (mAutoIncrementKeyOffsets.IsEmpty()) {
return;
}
for (uint32_t offset : mAutoIncrementKeyOffsets) {
char* buffer;
MOZ_ALWAYS_TRUE(mBuffer.GetMutableData(&buffer));
buffer += offset;
WriteDoubleToUint64(buffer, double(aKey));
}
TrimBuffer();
}
void Key::WriteDoubleToUint64(char* aBuffer, double aValue) {
MOZ_ASSERT(aBuffer);
uint64_t bits = BitwiseCast<uint64_t>(aValue);
const uint64_t signbit = FloatingPoint<double>::kSignBit;
uint64_t number = bits & signbit ? (-bits) : (bits | signbit);
mozilla::BigEndian::writeUint64(aBuffer, number);
}
template <typename T>
Result<Ok, nsresult> Key::EncodeAsString(const Span<const T> aInput,
uint8_t aType) {
@ -797,13 +842,8 @@ Result<Ok, nsresult> Key::EncodeNumber(double aFloat, uint8_t aType) {
*(buffer++) = aType;
uint64_t bits = BitwiseCast<uint64_t>(aFloat);
// Note: The subtraction from 0 below is necessary to fix
// MSVC build warning C4146 (negating an unsigned value).
const uint64_t signbit = FloatingPoint<double>::kSignBit;
uint64_t number = bits & signbit ? (0 - bits) : (bits | signbit);
WriteDoubleToUint64(buffer, aFloat);
mozilla::BigEndian::writeUint64(buffer, number);
return Ok();
}

View File

@ -25,6 +25,7 @@ class Key {
friend struct IPC::ParamTraits<Key>;
nsCString mBuffer;
CopyableTArray<uint32_t> mAutoIncrementKeyOffsets;
public:
enum {
@ -86,7 +87,10 @@ class Key {
return Compare(mBuffer, aOther.mBuffer) >= 0;
}
void Unset() { mBuffer.SetIsVoid(true); }
void Unset() {
mBuffer.SetIsVoid(true);
mAutoIncrementKeyOffsets.Clear();
}
bool IsUnset() const { return mBuffer.IsVoid(); }
@ -174,6 +178,10 @@ class Key {
return 0;
}
void ReserveAutoIncrementKey(bool aFirstOfArray);
void MaybeUpdateAutoIncrementKey(int64_t aKey);
private:
class MOZ_STACK_CLASS ArrayValueEncoder;
@ -273,6 +281,8 @@ class Key {
template <typename T>
nsresult SetFromSource(T* aSource, uint32_t aIndex);
void WriteDoubleToUint64(char* aBuffer, double aValue);
};
} // namespace mozilla::dom::indexedDB

View File

@ -329,8 +329,9 @@ bool KeyPath::AppendStringWithValidation(const nsAString& aString) {
return false;
}
nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue,
Key& aKey) const {
nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey,
const VoidOrObjectStoreKeyPathString&
aAutoIncrementedObjectStoreKeyPath) const {
uint32_t len = mStrings.Length();
JS::Rooted<JS::Value> value(aCx);
@ -341,6 +342,17 @@ nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue,
GetJSValFromKeyPathString(aCx, aValue, mStrings[i], value.address(),
DoNotCreateProperties, nullptr, nullptr);
if (NS_FAILED(rv)) {
if (!aAutoIncrementedObjectStoreKeyPath.IsVoid() &&
mStrings[i].Equals(aAutoIncrementedObjectStoreKeyPath)) {
// We are extracting index keys of an object to be added if
// object store key path for a string key is provided.
// Because the autoIncrement primary key is part of
// this index key but is not defined in |aValue|, so we reserve
// the space here to update the key later in parent.
aKey.ReserveAutoIncrementKey(IsArray() && i == 0);
continue;
}
return rv;
}

View File

@ -45,6 +45,8 @@ class KeyPath {
KeyPath() : mType(KeyPathType::NonExistent) { MOZ_COUNT_CTOR(KeyPath); }
public:
using VoidOrObjectStoreKeyPathString = nsAString;
enum class KeyPathType { NonExistent, String, Array, EndGuard };
void SetType(KeyPathType aType);
@ -76,7 +78,10 @@ class KeyPath {
static Result<KeyPath, nsresult> Parse(
const Nullable<OwningStringOrStringSequence>& aValue);
nsresult ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const;
nsresult ExtractKey(
JSContext* aCx, const JS::Value& aValue, Key& aKey,
const VoidOrObjectStoreKeyPathString& aAutoIncrementedObjectStoreKeyPath =
VoidString()) const;
nsresult ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
JS::Value* aOutVal) const;

View File

@ -31,10 +31,12 @@ struct ParamTraits<mozilla::dom::indexedDB::Key> {
static void Write(MessageWriter* aWriter, const paramType& aParam) {
WriteParam(aWriter, aParam.mBuffer);
WriteParam(aWriter, aParam.mAutoIncrementKeyOffsets);
}
static bool Read(MessageReader* aReader, paramType* aResult) {
return ReadParam(aReader, &aResult->mBuffer);
return ReadParam(aReader, &aResult->mBuffer) &&
ReadParam(aReader, &aResult->mAutoIncrementKeyOffsets);
}
};

View File

@ -1,8 +0,0 @@
[idbobjectstore_createIndex15-autoincrement.htm]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Auto-Increment Primary Key]
expected: FAIL
[Auto-Increment Primary Key - invalid key values elsewhere]
expected: FAIL

View File

@ -1,48 +0,0 @@
[reading-autoincrement-indexes-cursors.any.serviceworker.html]
[IDBIndex.openCursor() iterates over an index on the autoincrement key]
expected: FAIL
[IDBIndex.openKeyCursor() iterates over an index on the autoincrement key]
expected: FAIL
[reading-autoincrement-indexes-cursors.any.worker.html]
expected:
if (processor == "x86") and (os == "win") and not debug: [OK, TIMEOUT]
[IDBIndex.openCursor() iterates over an index on the autoincrement key]
expected: FAIL
[IDBIndex.openKeyCursor() iterates over an index on the autoincrement key]
expected:
if (processor == "x86") and (os == "win") and not debug: [FAIL, TIMEOUT]
FAIL
[IDBIndex.openKeyCursor() iterates over an index not covering the autoincrement key]
expected:
if (processor == "x86") and (os == "win") and not debug: [PASS, NOTRUN]
[IDBIndex.openCursor() iterates over an index not covering the autoincrement key]
expected:
if (processor == "x86") and (os == "win") and not debug: [PASS, NOTRUN]
[reading-autoincrement-indexes-cursors.any.sharedworker.html]
expected:
if (processor == "x86") and (os == "win") and not debug: [OK, TIMEOUT]
[IDBIndex.openCursor() iterates over an index on the autoincrement key]
expected: FAIL
[IDBIndex.openKeyCursor() iterates over an index on the autoincrement key]
expected: FAIL
[IDBIndex.openKeyCursor() iterates over an index not covering the autoincrement key]
expected:
if (processor == "x86") and (os == "win") and not debug: [PASS, TIMEOUT]
[reading-autoincrement-indexes-cursors.any.html]
[IDBIndex.openCursor() iterates over an index on the autoincrement key]
expected: FAIL
[IDBIndex.openKeyCursor() iterates over an index on the autoincrement key]
expected: FAIL

View File

@ -1,117 +0,0 @@
[reading-autoincrement-indexes.any.serviceworker.html]
expected:
if (os == "win") and not debug: [OK, TIMEOUT]
[IDBIndex.getAll() for an index on the autoincrement key]
expected: FAIL
[IDBIndex.getAllKeys() for an index on the autoincrement key]
expected: FAIL
[IDBIndex.get() for an index on the autoincrement key]
expected: FAIL
[IDBIndex.getAllKeys() returns correct result for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug: [PASS, TIMEOUT]
[IDBIndex.get() for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug: [PASS, NOTRUN]
[reading-autoincrement-indexes.any.html]
expected:
if (os == "win") and not debug: [OK, TIMEOUT]
[IDBIndex.getAll() for an index on the autoincrement key]
expected:
if (processor == "x86") and not debug: [FAIL, TIMEOUT]
FAIL
[IDBIndex.getAllKeys() for an index on the autoincrement key]
expected:
if (processor == "x86") and not debug: [FAIL, NOTRUN]
FAIL
[IDBIndex.get() for an index on the autoincrement key]
expected:
if (processor == "x86") and not debug: [FAIL, NOTRUN]
FAIL
[IDBIndex.getAll() for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [PASS, TIMEOUT]
if (os == "win") and not debug and (processor == "x86"): [PASS, TIMEOUT, NOTRUN]
[IDBIndex.get() for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug: [PASS, NOTRUN]
[IDBIndex.getAllKeys() returns correct result for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug: [PASS, NOTRUN]
[reading-autoincrement-indexes.any.sharedworker.html]
expected:
if (os == "win") and not debug: [OK, TIMEOUT]
[IDBIndex.getAll() for an index on the autoincrement key]
expected:
if (processor == "x86") and not debug: [FAIL, TIMEOUT]
FAIL
[IDBIndex.getAllKeys() for an index on the autoincrement key]
expected:
if (processor == "x86") and not debug: [FAIL, NOTRUN]
FAIL
[IDBIndex.get() for an index on the autoincrement key]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [FAIL, TIMEOUT]
if (os == "win") and not debug and (processor == "x86"): [FAIL, NOTRUN]
FAIL
[IDBIndex.getAll() for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug: [PASS, NOTRUN]
[IDBIndex.get() for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug: [PASS, NOTRUN]
[IDBIndex.getAllKeys() returns correct result for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug: [PASS, NOTRUN]
[reading-autoincrement-indexes.any.worker.html]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [OK, TIMEOUT]
if (os == "win") and not debug and (processor == "x86"): [TIMEOUT, OK]
[IDBIndex.getAll() for an index on the autoincrement key]
expected: FAIL
[IDBIndex.getAllKeys() for an index on the autoincrement key]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [FAIL, TIMEOUT]
FAIL
[IDBIndex.get() for an index on the autoincrement key]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [FAIL, NOTRUN]
if (os == "win") and not debug and (processor == "x86"): TIMEOUT
FAIL
[IDBIndex.get() for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [PASS, NOTRUN]
if (os == "win") and not debug and (processor == "x86"): [NOTRUN, PASS]
[IDBIndex.getAllKeys() returns correct result for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [PASS, NOTRUN]
if (os == "win") and not debug and (processor == "x86"): [NOTRUN, PASS, TIMEOUT]
[IDBIndex.getAll() for an index not covering the autoincrement key]
expected:
if (os == "win") and not debug and (processor == "x86_64"): [PASS, NOTRUN]
if (os == "win") and not debug and (processor == "x86"): NOTRUN