Commit 7baae9f7 authored by Numfor Mbiziwo-Tiapo's avatar Numfor Mbiziwo-Tiapo Committed by Commit Bot

[IndexedDB] Implement putAllValues with correct error handling

Implements putAllValues where any errors cause the whole request
to fail, instead of just the value/s that failed.

Explainer: https://github.com/nums11/idb-putall/blob/master/Explainer.md

Design Doc: https://docs.google.com/document/d/1wawQ8Pl-Vi6GQN5-y2UVkcghipcOGg0WRAC0ZyBkezg/edit

I2P: https://groups.google.com/a/chromium.org/g/blink-dev/c/TPd_rtgO3_k/m/qZmPy1dfAgAJ

Change-Id: I976db2fe4c441f0ae288e63deb4c69c148721a18
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2324146Reviewed-by: default avatarChristian Biesinger <cbiesinger@chromium.org>
Reviewed-by: default avatarJoshua Bell <jsbell@chromium.org>
Reviewed-by: default avatarChris Palmer <palmer@chromium.org>
Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Reviewed-by: default avatarOlivier Yiptong <oyiptong@chromium.org>
Commit-Queue: Daniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797837}
parent 7b1e5a15
......@@ -6,6 +6,7 @@
#include <math.h>
#include <algorithm>
#include <cstddef>
#include <limits>
#include <set>
#include <utility>
......@@ -143,6 +144,9 @@ Status UpdateKeyGenerator(IndexedDBBackingStore* backing_store,
IndexedDBDatabase::PutOperationParams::PutOperationParams() = default;
IndexedDBDatabase::PutOperationParams::~PutOperationParams() = default;
IndexedDBDatabase::PutAllOperationParams::PutAllOperationParams() = default;
IndexedDBDatabase::PutAllOperationParams::~PutAllOperationParams() = default;
IndexedDBDatabase::OpenCursorOperationParams::OpenCursorOperationParams() =
default;
IndexedDBDatabase::OpenCursorOperationParams::~OpenCursorOperationParams() =
......@@ -1266,6 +1270,131 @@ Status IndexedDBDatabase::PutOperation(
return s;
}
Status IndexedDBDatabase::PutAllOperation(
int64_t object_store_id,
std::vector<std::unique_ptr<PutAllOperationParams>> params,
blink::mojom::IDBTransaction::PutAllCallback callback,
IndexedDBTransaction* transaction) {
base::CheckedNumeric<size_t> size_estimate = 0;
for (const auto& put_param : params) {
size_estimate += put_param->value.SizeEstimate();
}
IDB_TRACE2("IndexedDBDatabase::PutAllOperation", "txn.id", transaction->id(),
"size", base::checked_cast<int64_t>(size_estimate.ValueOrDie()));
DCHECK_NE(transaction->mode(), blink::mojom::IDBTransactionMode::ReadOnly);
bool key_was_generated = false;
Status s = Status::OK();
// TODO(nums): Add checks to prevent overflow and underflow
// https://crbug.com/1116075
transaction->set_in_flight_memory(
transaction->in_flight_memory() -
base::checked_cast<int64_t>(size_estimate.ValueOrDie()));
if (!IsObjectStoreIdInMetadata(object_store_id)) {
IndexedDBDatabaseError error = CreateError(
blink::mojom::IDBException::kUnknownError, "Bad request", transaction);
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return leveldb::Status::InvalidArgument("Invalid object_store_id.");
}
DCHECK(metadata_.object_stores.find(object_store_id) !=
metadata_.object_stores.end());
const IndexedDBObjectStoreMetadata& object_store =
metadata_.object_stores[object_store_id];
for (auto& put_param : params) {
DCHECK(object_store.auto_increment || put_param->key->IsValid());
if (object_store.auto_increment && !put_param->key->IsValid()) {
std::unique_ptr<IndexedDBKey> auto_inc_key =
GenerateKey(backing_store_, transaction, id(), object_store_id);
key_was_generated = true;
if (!auto_inc_key->IsValid()) {
IndexedDBDatabaseError error =
CreateError(blink::mojom::IDBException::kConstraintError,
"Maximum key generator value reached.", transaction);
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return s;
}
put_param->key = std::move(auto_inc_key);
}
DCHECK(put_param->key->IsValid());
}
for (auto& put_param : params) {
std::vector<std::unique_ptr<IndexWriter>> index_writers;
base::string16 error_message;
IndexedDBBackingStore::RecordIdentifier record_identifier;
bool obeys_constraints = false;
bool backing_store_success = MakeIndexWriters(
transaction, backing_store_, id(), object_store, *(put_param->key),
key_was_generated, put_param->index_keys, &index_writers,
&error_message, &obeys_constraints);
if (!backing_store_success) {
IndexedDBDatabaseError error = CreateError(
blink::mojom::IDBException::kUnknownError,
"Internal error: backing store error updating index keys.",
transaction);
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return s;
}
if (!obeys_constraints) {
IndexedDBDatabaseError error =
CreateError(blink::mojom::IDBException::kConstraintError,
error_message, transaction);
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return s;
}
// Before this point, don't do any mutation. After this point, rollback the
// transaction in case of error.
s = backing_store_->PutRecord(transaction->BackingStoreTransaction(), id(),
object_store_id, *(put_param->key),
&put_param->value, &record_identifier);
if (!s.ok())
return s;
for (const auto& writer : index_writers) {
writer->WriteIndexKeys(record_identifier, backing_store_,
transaction->BackingStoreTransaction(), id(),
object_store_id);
}
if (object_store.auto_increment &&
put_param->key->type() == blink::mojom::IDBKeyType::Number) {
s = UpdateKeyGenerator(backing_store_, transaction, id(), object_store_id,
*(put_param->key), !key_was_generated);
if (!s.ok())
return s;
}
}
{
IDB_TRACE1("IndexedDBDatabase::PutAllOperation.Callbacks", "txn.id",
transaction->id());
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(blink::mojom::IDBException::kNoError,
base::string16())));
}
for (auto& put_param : params) {
FilterObservation(transaction, object_store_id,
blink::mojom::IDBOperationType::Put,
IndexedDBKeyRange(*(put_param->key)), &put_param->value);
}
factory_->NotifyIndexedDBContentChanged(
origin(), metadata_.name, metadata_.object_stores[object_store_id].name);
return s;
}
Status IndexedDBDatabase::SetIndexKeysOperation(
int64_t object_store_id,
std::unique_ptr<IndexedDBKey> primary_key,
......
......@@ -246,6 +246,22 @@ class CONTENT_EXPORT IndexedDBDatabase {
leveldb::Status PutOperation(std::unique_ptr<PutOperationParams> params,
IndexedDBTransaction* transaction);
struct CONTENT_EXPORT PutAllOperationParams {
PutAllOperationParams();
~PutAllOperationParams();
IndexedDBValue value;
std::unique_ptr<blink::IndexedDBKey> key;
std::vector<blink::IndexedDBIndexKeys> index_keys;
private:
DISALLOW_COPY_AND_ASSIGN(PutAllOperationParams);
};
leveldb::Status PutAllOperation(
int64_t object_store_id,
std::vector<std::unique_ptr<PutAllOperationParams>> params,
blink::mojom::IDBTransaction::PutAllCallback callback,
IndexedDBTransaction* transaction);
leveldb::Status SetIndexKeysOperation(
int64_t object_store_id,
std::unique_ptr<blink::IndexedDBKey> primary_key,
......
......@@ -4,6 +4,7 @@
#include "content/browser/indexed_db/transaction_impl.h"
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
......@@ -18,6 +19,7 @@
#include "content/browser/indexed_db/indexed_db_transaction.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-forward.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
namespace content {
......@@ -157,6 +159,80 @@ void TransactionImpl::Put(
transaction_->set_size(transaction_->size() + commit_size);
}
void TransactionImpl::PutAll(int64_t object_store_id,
std::vector<blink::mojom::IDBPutParamsPtr> puts,
PutAllCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(dispatcher_host_);
if (!transaction_) {
IndexedDBDatabaseError error(blink::mojom::IDBException::kUnknownError,
"Unknown transaction.");
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
std::vector<std::vector<IndexedDBExternalObject>> external_objects_per_put(
puts.size());
for (size_t i = 0; i < puts.size(); i++) {
if (!puts[i]->value->external_objects.empty())
CreateExternalObjects(puts[i]->value, &external_objects_per_put[i]);
}
IndexedDBConnection* connection = transaction_->connection();
if (!connection->IsConnected()) {
IndexedDBDatabaseError error(blink::mojom::IDBException::kUnknownError,
"Not connected.");
std::move(callback).Run(
blink::mojom::IDBTransactionPutAllResult::NewErrorResult(
blink::mojom::IDBError::New(error.code(), error.message())));
return;
}
base::CheckedNumeric<uint64_t> commit_size = 0;
base::CheckedNumeric<size_t> size_estimate = 0;
std::vector<std::unique_ptr<IndexedDBDatabase::PutAllOperationParams>>
put_params(puts.size());
for (size_t i = 0; i < puts.size(); i++) {
commit_size += puts[i]->value->bits.size();
commit_size += puts[i]->key.size_estimate();
put_params[i] =
std::make_unique<IndexedDBDatabase::PutAllOperationParams>();
// TODO(crbug.com/902498): Use mojom traits to map directly to
// std::string.
put_params[i]->value.bits =
std::string(puts[i]->value->bits.begin(), puts[i]->value->bits.end());
size_estimate += put_params[i]->value.SizeEstimate();
puts[i]->value->bits.clear();
put_params[i]->value.external_objects =
std::move(external_objects_per_put[i]);
put_params[i]->key = std::make_unique<blink::IndexedDBKey>(puts[i]->key);
put_params[i]->index_keys = std::move(puts[i]->index_keys);
}
blink::mojom::IDBTransaction::PutAllCallback aborting_callback =
CreateCallbackAbortOnDestruct<
blink::mojom::IDBTransaction::PutAllCallback,
blink::mojom::IDBTransactionPutAllResultPtr>(
std::move(callback), transaction_->AsWeakPtr());
// TODO(nums): Add checks to prevent overflow and underflow
// https://crbug.com/1116075
transaction_->set_in_flight_memory(
transaction_->in_flight_memory() +
base::checked_cast<int64_t>(size_estimate.ValueOrDie()));
transaction_->ScheduleTask(BindWeakOperation(
&IndexedDBDatabase::PutAllOperation, connection->database()->AsWeakPtr(),
object_store_id, std::move(put_params), std::move(aborting_callback)));
// Size can't be big enough to overflow because it represents the
// actual bytes passed through IPC.
transaction_->set_size(transaction_->size() + base::checked_cast<uint64_t>(
commit_size.ValueOrDie()));
}
void TransactionImpl::CreateExternalObjects(
blink::mojom::IDBValuePtr& value,
std::vector<IndexedDBExternalObject>* external_objects) {
......
......@@ -47,6 +47,9 @@ class TransactionImpl : public blink::mojom::IDBTransaction {
blink::mojom::IDBPutMode mode,
const std::vector<blink::IndexedDBIndexKeys>& index_keys,
blink::mojom::IDBTransaction::PutCallback callback) override;
void PutAll(int64_t object_store_id,
std::vector<blink::mojom::IDBPutParamsPtr> puts,
PutAllCallback callback) override;
void Commit(int64_t num_errors_handled) override;
void OnGotUsageAndQuotaForCommit(blink::mojom::QuotaStatusCode status,
......
......@@ -46,8 +46,8 @@
const store1 = txn.objectStore('books_with_index');
const store2 = txn.objectStore('books');
logToDocumentBody('Starting Benchmark IDB putAll');
store1.putAll(values);
store2.putAll(values);
store1.putAllValues(values);
store2.putAllValues(values);
logToDocumentBody('Finished Benchmark IDB putAll');
txn.oncomplete = () => {
reportDone();
......
......@@ -338,6 +338,17 @@ union IDBTransactionPutResult {
IDBKey key;
};
union IDBTransactionPutAllResult {
IDBError error_result; // |error| is reserved, so call this |error_result|.
array<IDBKey> keys;
};
struct IDBPutParams {
IDBValue value;
IDBKey key;
array<IDBIndexKeys> index_keys;
};
interface IDBTransaction {
CreateObjectStore(int64 object_store_id,
mojo_base.mojom.String16 name,
......@@ -350,6 +361,9 @@ interface IDBTransaction {
IDBPutMode mode,
array<IDBIndexKeys> index_keys)
=> (IDBTransactionPutResult result);
PutAll(int64 object_store_id,
array<IDBPutParams> put_params)
=> (IDBTransactionPutAllResult result);
Commit(int64 num_errors_handled);
};
......
......@@ -123,9 +123,7 @@ IDBRequest* IDBCursor::update(ScriptState* script_state,
IDBObjectStore* object_store = EffectiveObjectStore();
return object_store->DoPut(script_state, mojom::IDBPutMode::CursorUpdate,
IDBRequest::Source::FromIDBCursor(this), value,
IdbPrimaryKey(), exception_state,
/*optional_custom_callback=*/nullptr,
/*blob_handles_out=*/nullptr);
IdbPrimaryKey(), exception_state);
}
void IDBCursor::advance(unsigned count, ExceptionState& exception_state) {
......
......@@ -28,6 +28,7 @@
#include "base/memory/scoped_refptr.h"
#include "third_party/blink/public/common/indexeddb/web_idb_types.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink-forward.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_idb_index_parameters.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_cursor.h"
......@@ -102,9 +103,13 @@ class MODULES_EXPORT IDBObjectStore final : public ScriptWrappable {
const ScriptValue& key,
ExceptionState&);
IDBRequest* put(ScriptState*, const ScriptValue& value, ExceptionState&);
IDBRequest* putAll(ScriptState*,
const HeapVector<ScriptValue>& values,
ExceptionState&);
IDBRequest* putAllValues(ScriptState*,
const HeapVector<ScriptValue>& values,
ExceptionState&);
IDBRequest* DoPutAll(ScriptState* script_state,
const HeapVector<ScriptValue>& values,
const HeapVector<ScriptValue>& key_values,
ExceptionState& exception_state);
IDBRequest* put(ScriptState*,
const ScriptValue& value,
const ScriptValue& key,
......@@ -131,9 +136,7 @@ class MODULES_EXPORT IDBObjectStore final : public ScriptWrappable {
const IDBRequest::Source&,
const ScriptValue&,
const IDBKey*,
ExceptionState&,
std::unique_ptr<WebIDBCallbacks> optional_custom_callback,
Vector<scoped_refptr<BlobDataHandle>>* blob_handles_out);
ExceptionState&);
// Used internally and by InspectorIndexedDBAgent:
IDBRequest* openCursor(
......@@ -209,9 +212,7 @@ class MODULES_EXPORT IDBObjectStore final : public ScriptWrappable {
mojom::IDBPutMode,
const ScriptValue&,
const ScriptValue& key_value,
ExceptionState&,
std::unique_ptr<WebIDBCallbacks> optional_custom_callback,
Vector<scoped_refptr<BlobDataHandle>>* blob_handles_out);
ExceptionState&);
int64_t FindIndexId(const String& name) const;
......
......@@ -42,7 +42,7 @@
MeasureAs=IndexedDBWrite,
RaisesException,
RuntimeEnabled=IDBPutAll
] IDBRequest putAll(sequence<any> values);
] IDBRequest putAllValues(sequence<any> values);
[CallWith=ScriptState, MeasureAs=IndexedDBWrite, NewObject, RaisesException]
IDBRequest add(any value, optional any key);
......
......@@ -79,7 +79,7 @@ class SerializedScriptValue;
// blobs: SSV Blob attachments + [wrapper Blob(SSV byte array)] ->
// LevelDB
class MODULES_EXPORT IDBValueWrapper {
STACK_ALLOCATED();
DISALLOW_NEW();
public:
// Wrapper for an IndexedDB value.
......
......@@ -35,6 +35,10 @@ class MockWebIDBTransaction : public testing::StrictMock<WebIDBTransaction> {
mojom::IDBPutMode,
std::unique_ptr<WebIDBCallbacks>,
Vector<IDBIndexKeys>));
MOCK_METHOD3(PutAll,
void(int64_t object_store_id,
Vector<mojom::blink::IDBPutParamsPtr> puts,
std::unique_ptr<WebIDBCallbacks> callbacks));
MOCK_METHOD1(Commit, void(int64_t num_errors_handled));
mojo::PendingAssociatedReceiver<mojom::blink::IDBTransaction> CreateReceiver()
......
......@@ -53,6 +53,9 @@ class MODULES_EXPORT WebIDBTransaction {
mojom::IDBPutMode,
std::unique_ptr<WebIDBCallbacks>,
Vector<IDBIndexKeys>) = 0;
virtual void PutAll(int64_t object_store_id,
Vector<mojom::blink::IDBPutParamsPtr> puts,
std::unique_ptr<WebIDBCallbacks> callbacks) = 0;
virtual void Commit(int64_t num_errors_handled) = 0;
virtual mojo::PendingAssociatedReceiver<mojom::blink::IDBTransaction>
......
......@@ -4,10 +4,12 @@
#include "third_party/blink/renderer/modules/indexeddb/web_idb_transaction_impl.h"
#include <cstddef>
#include <memory>
#include <utility>
#include "base/format_macros.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_database_error.h"
......@@ -55,6 +57,8 @@ void WebIDBTransactionImpl::Put(int64_t object_store_id,
for (const auto& index_key : index_keys) {
index_keys_size++; // Account for index_key.first (int64_t).
for (const auto& key : index_key.keys) {
// Because all size estimates are based on RAM usage, it is impossible to
// overflow index_keys_size.
index_keys_size += key->SizeEstimate();
}
}
......@@ -94,6 +98,56 @@ void WebIDBTransactionImpl::PutCallback(
}
}
void WebIDBTransactionImpl::PutAll(int64_t object_store_id,
Vector<mojom::blink::IDBPutParamsPtr> puts,
std::unique_ptr<WebIDBCallbacks> callbacks) {
IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id_, nullptr);
base::CheckedNumeric<size_t> index_keys_size = 0;
for (auto& put : puts) {
index_keys_size++;
for (const auto& index_key : put->index_keys) {
for (const auto& key : index_key.keys) {
index_keys_size += key->SizeEstimate();
}
}
}
base::CheckedNumeric<size_t> arg_size;
for (auto& put : puts) {
arg_size += put->value->DataSize();
arg_size += put->key->SizeEstimate();
}
arg_size += index_keys_size.ValueOrDie();
if (arg_size.ValueOrDie() >= max_put_value_size_) {
callbacks->Error(
mojom::blink::IDBException::kUnknownError,
String::Format("The serialized keys and/or values are too large"
" (size=%" PRIuS " bytes, max=%" PRIuS " bytes).",
base::checked_cast<size_t>(arg_size.ValueOrDie()),
max_put_value_size_));
return;
}
callbacks->SetState(nullptr, transaction_id_);
transaction_->PutAll(object_store_id, std::move(puts),
WTF::Bind(&WebIDBTransactionImpl::PutAllCallback,
WTF::Unretained(this), std::move(callbacks)));
}
void WebIDBTransactionImpl::PutAllCallback(
std::unique_ptr<WebIDBCallbacks> callbacks,
mojom::blink::IDBTransactionPutAllResultPtr result) {
DCHECK(result->is_error_result());
if (result->get_error_result()->error_code ==
blink::mojom::IDBException::kNoError) {
callbacks->Success();
} else {
callbacks->Error(result->get_error_result()->error_code,
std::move(result->get_error_result()->error_message));
}
callbacks.reset();
}
void WebIDBTransactionImpl::Commit(int64_t num_errors_handled) {
transaction_->Commit(num_errors_handled);
}
......
......@@ -36,8 +36,13 @@ class MODULES_EXPORT WebIDBTransactionImpl : public WebIDBTransaction {
mojom::IDBPutMode,
std::unique_ptr<WebIDBCallbacks> callbacks,
Vector<IDBIndexKeys>) override;
void PutAll(int64_t object_store_id,
Vector<mojom::blink::IDBPutParamsPtr> puts,
std::unique_ptr<WebIDBCallbacks> callbacks) override;
void PutCallback(std::unique_ptr<WebIDBCallbacks> callbacks,
mojom::blink::IDBTransactionPutResultPtr result);
void PutAllCallback(std::unique_ptr<WebIDBCallbacks> callbacks,
mojom::blink::IDBTransactionPutAllResultPtr result);
void Commit(int64_t num_errors_handled) override;
mojo::PendingAssociatedReceiver<mojom::blink::IDBTransaction> CreateReceiver()
......
......@@ -6,12 +6,13 @@ promise_test(async testCase => {
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
let values = [
const values = [
{isbn: 'one', title: 'title1'},
{isbn: 'two', title: 'title2'},
{isbn: 'three', title: 'title3'}
];
let putAllRequest = objectStore.putAll(values);
const putAllRequest = objectStore.putAllValues(values);
// TODO(nums): Check that correct keys are returned.
await promiseForRequest(testCase, putAllRequest);
await promiseForTransaction(testCase, txn);
......@@ -28,4 +29,152 @@ promise_test(async testCase => {
['title1', 'title2', 'title3'],
'All three retrieved titles should match those that were put.');
db.close();
}, 'Data can be successfully inputted into an object store using putAll.');
}, 'Data can be successfully inserted into an object store using putAll.');
promise_test(async testCase => {
const db = await createDatabase(testCase, db => {
const store = createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const values = [
{isbn: ['one', 'two', 'three'], title: 'title1'},
{isbn: ['four', 'five', 'six'], title: 'title2'},
{isbn: ['seven', 'eight', 'nine'], title: 'title3'}
];
const putAllRequest = objectStore.putAllValues(values);
// TODO(nums): Check that correct keys are returned.
await promiseForRequest(testCase, putAllRequest);
await promiseForTransaction(testCase, txn);
const txn2 = db.transaction(['books'], 'readonly');
const objectStore2 = txn2.objectStore('books');
const getRequest1 = objectStore2.get(['one', 'two', 'three']);
const getRequest2 = objectStore2.get(['four', 'five', 'six']);
const getRequest3 = objectStore2.get(['seven', 'eight', 'nine']);
await promiseForTransaction(testCase, txn2);
assert_array_equals(
[getRequest1.result.title,
getRequest2.result.title,
getRequest3.result.title],
['title1', 'title2', 'title3'],
'All three retrieved titles should match those that were put.');
db.close();
}, 'Values with array keys can be successfully inserted into an object'
+ ' store using putAll.');
promise_test(async testCase => {
const db = await createDatabase(testCase, db => {
const store = createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const putAllRequest = objectStore.putAllValues([]);
await promiseForRequest(testCase, putAllRequest);
await promiseForTransaction(testCase, txn);
// TODO(nums): Check that an empty key array is returned.
db.close();
}, 'Inserting an empty list using putAll.');
promise_test(async testCase => {
const db = await createDatabase(testCase, db => {
const store = createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const putAllRequest = objectStore.putAllValues([{}, {}, {}]);
// TODO(nums): Check that correct keys are returned.
await promiseForRequest(testCase, putAllRequest);
await promiseForTransaction(testCase, txn);
const txn2 = db.transaction(['books'], 'readonly');
const objectStore2 = txn2.objectStore('books');
const getRequest1 = objectStore2.get(1);
const getRequest2 = objectStore2.get(2);
const getRequest3 = objectStore2.get(3);
await Promise.all([
promiseForRequest(testCase, getRequest1),
promiseForRequest(testCase, getRequest2),
promiseForRequest(testCase, getRequest3),
]);
db.close();
}, 'Empty values can be inserted into an objectstore'
+ ' with a key generator using putAll.');
promise_test(async testCase => {
const db = await createDatabase(testCase, db => {
const store = createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readonly');
const objectStore = txn.objectStore('books');
assert_throws_dom('ReadOnlyError',
() => { objectStore.putAllValues([{}]); },
'The transaction is readonly');
db.close();
}, 'Attempting to insert with a read only transaction using putAll throws a '
+ 'ReadOnlyError.');
promise_test(async testCase => {
const db = await createDatabase(testCase, db => {
const store = createBooksStore(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const putRequest = await objectStore.put({isbn: 1, title: "duplicate"});
await promiseForRequest(testCase, putRequest);
const putAllRequest = objectStore.putAllValues([
{isbn: 2, title: "duplicate"},
{isbn: 3, title: "duplicate"}
]);
const errorEvent = await requestWatcher(testCase,
putAllRequest).wait_for('error');
assert_equals(errorEvent.target.error.name, "ConstraintError");
errorEvent.preventDefault();
// The transaction still receives the error event even though it
// isn't aborted.
await transactionWatcher(testCase, txn).wait_for(['error', 'complete']);
const txn2 = db.transaction(['books'], 'readonly');
const objectStore2 = txn2.objectStore('books');
const getRequest1 = objectStore2.get(1);
const getRequest2 = objectStore2.get(2);
const getRequest3 = objectStore2.get(3);
await promiseForTransaction(testCase, txn2);
assert_array_equals(
[getRequest1.result.title, getRequest2.result, getRequest3.result],
["duplicate", undefined, undefined],
'None of the values should have been inserted.');
db.close();
}, 'Inserting duplicate unique keys into a store that already has the key'
+ 'using putAll throws a ConstraintError.');
promise_test(async testCase => {
const db = await createDatabase(testCase, db => {
const store = createBooksStoreWithoutAutoIncrement(testCase, db);
});
const txn = db.transaction(['books'], 'readwrite');
const objectStore = txn.objectStore('books');
const values = [
{title: "title1", isbn: 1},
{title: "title2"}
];
assert_throws_dom('DataError',
() => { const putAllRequest = objectStore.putAllValues(values); },
"Evaluating the object store's key path did not yield a value");
const txn2 = db.transaction(['books'], 'readonly');
const objectStore2 = txn2.objectStore('books');
const getRequest1 = objectStore2.get(1);
const getRequest2 = objectStore2.get(2);
await promiseForTransaction(testCase, txn2);
assert_array_equals(
[getRequest1.result, getRequest2.result],
[undefined, undefined],
'No data should have been inserted');
db.close();
}, 'Inserting values without the key into an object store that'
+ ' does not have generated keys throws an exception.');
// TODO(nums): Add test for insertion into multi entry indexes
// TODO(nums): Add test for inserting unique keys into a store
// that doesn't already have the key https://crbug.com/1115649
......@@ -196,7 +196,19 @@ const createBooksStore = (testCase, database) => {
{ keyPath: 'isbn', autoIncrement: true });
store.createIndex('by_author', 'author');
store.createIndex('by_title', 'title', { unique: true });
for (let record of BOOKS_RECORD_DATA)
for (const record of BOOKS_RECORD_DATA)
store.put(record);
return store;
}
// Creates a 'books' object store whose contents closely resembles the first
// example in the IndexedDB specification, just without autoincrementing.
const createBooksStoreWithoutAutoIncrement = (testCase, database) => {
const store = database.createObjectStore('books',
{ keyPath: 'isbn' });
store.createIndex('by_author', 'author');
store.createIndex('by_title', 'title', { unique: true });
for (const record of BOOKS_RECORD_DATA)
store.put(record);
return store;
}
......
......@@ -730,7 +730,7 @@ interface IDBObjectStore
method openCursor
method openKeyCursor
method put
method putAll
method putAllValues
setter name
interface IDBObservation
attribute @@toStringTag
......
......@@ -664,7 +664,7 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method openCursor
[Worker] method openKeyCursor
[Worker] method put
[Worker] method putAll
[Worker] method putAllValues
[Worker] setter name
[Worker] interface IDBObservation
[Worker] attribute @@toStringTag
......
......@@ -4424,7 +4424,7 @@ interface IDBObjectStore
method openCursor
method openKeyCursor
method put
method putAll
method putAllValues
setter name
interface IDBObservation
attribute @@toStringTag
......
......@@ -659,7 +659,7 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method openCursor
[Worker] method openKeyCursor
[Worker] method put
[Worker] method putAll
[Worker] method putAllValues
[Worker] setter name
[Worker] interface IDBObservation
[Worker] attribute @@toStringTag
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment