idb: more tests working

This commit is contained in:
Florian Dold 2019-06-23 22:16:03 +02:00
parent a4e4125cca
commit 859a9e72e1
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
9 changed files with 212 additions and 125 deletions

View File

@ -52,9 +52,9 @@ class BridgeIDBCursor {
private _gotValue: boolean = false; private _gotValue: boolean = false;
private _range: CursorRange; private _range: CursorRange;
private _position = undefined; // Key of previously returned record private _indexPosition = undefined; // Key of previously returned record
private _objectStorePosition = undefined; private _objectStorePosition = undefined;
private _keyOnly: boolean = false; private _keyOnly: boolean;
private _source: CursorSource; private _source: CursorSource;
private _direction: BridgeIDBCursorDirection; private _direction: BridgeIDBCursorDirection;
@ -63,6 +63,8 @@ class BridgeIDBCursor {
private _indexName: string | undefined; private _indexName: string | undefined;
private _objectStoreName: string; private _objectStoreName: string;
protected _value: Value = undefined;
constructor( constructor(
source: CursorSource, source: CursorSource,
objectStoreName: string, objectStoreName: string,
@ -128,7 +130,7 @@ class BridgeIDBCursor {
const recordGetRequest: RecordGetRequest = { const recordGetRequest: RecordGetRequest = {
direction: this.direction, direction: this.direction,
indexName: this._indexName, indexName: this._indexName,
lastIndexPosition: this._position, lastIndexPosition: this._indexPosition,
lastObjectStorePosition: this._objectStorePosition, lastObjectStorePosition: this._objectStorePosition,
limit: 1, limit: 1,
range: this._range, range: this._range,
@ -140,15 +142,38 @@ class BridgeIDBCursor {
const { btx } = this.source._confirmActiveTransaction(); const { btx } = this.source._confirmActiveTransaction();
let response = await this._backend.getRecords( let response = await this._backend.getRecords(btx, recordGetRequest);
btx,
recordGetRequest,
);
if (response.count === 0) { if (response.count === 0) {
console.log("cursor is returning empty result");
return null; return null;
} }
if (response.count !== 1) {
throw Error("invariant failed");
}
console.log("request is:", JSON.stringify(recordGetRequest));
console.log("get response is:", JSON.stringify(response));
if (this._indexName !== undefined) {
this._key = response.indexKeys![0];
} else {
this._key = response.primaryKeys![0];
}
this._primaryKey = response.primaryKeys![0];
if (!this._keyOnly) {
this._value = response.values![0];
}
this._gotValue = true;
this._objectStorePosition = structuredClone(response.primaryKeys![0]);
if (response.indexKeys !== undefined && response.indexKeys.length > 0) {
this._indexPosition = structuredClone(response.indexKeys[0]);
}
return this; return this;
} }
@ -171,6 +196,7 @@ class BridgeIDBCursor {
if (this._effectiveObjectStore._deleted) { if (this._effectiveObjectStore._deleted) {
throw new InvalidStateError(); throw new InvalidStateError();
} }
if ( if (
!(this.source instanceof BridgeIDBObjectStore) && !(this.source instanceof BridgeIDBObjectStore) &&
this.source._deleted this.source._deleted
@ -232,8 +258,12 @@ class BridgeIDBCursor {
if (key !== undefined) { if (key !== undefined) {
key = valueToKey(key); key = valueToKey(key);
let lastKey =
this._indexName === undefined
? this._objectStorePosition
: this._indexPosition;
const cmpResult = compareKeys(key, this._position); const cmpResult = compareKeys(key, lastKey);
if ( if (
(cmpResult <= 0 && (cmpResult <= 0 &&
@ -250,7 +280,7 @@ class BridgeIDBCursor {
} }
const operation = async () => { const operation = async () => {
this._iterate(key); return this._iterate(key);
}; };
transaction._execRequestAsync({ transaction._execRequestAsync({

View File

@ -22,8 +22,11 @@ import {
Value, Value,
} from "./util/types"; } from "./util/types";
class FDBCursorWithValue extends BridgeIDBCursor { class BridgeIDBCursorWithValue extends BridgeIDBCursor {
public value: Value = undefined;
get value(): Value {
return this._value;
}
constructor( constructor(
source: CursorSource, source: CursorSource,
@ -33,7 +36,7 @@ class FDBCursorWithValue extends BridgeIDBCursor {
direction: BridgeIDBCursorDirection, direction: BridgeIDBCursorDirection,
request?: any, request?: any,
) { ) {
super(source, objectStoreName, indexName, range, direction, request, true); super(source, objectStoreName, indexName, range, direction, request, false);
} }
public toString() { public toString() {
@ -41,4 +44,4 @@ class FDBCursorWithValue extends BridgeIDBCursor {
} }
} }
export default FDBCursorWithValue; export default BridgeIDBCursorWithValue;

View File

@ -19,7 +19,6 @@ import BridgeIDBCursorWithValue from "./BridgeIDBCursorWithValue";
import BridgeIDBKeyRange from "./BridgeIDBKeyRange"; import BridgeIDBKeyRange from "./BridgeIDBKeyRange";
import BridgeIDBObjectStore from "./BridgeIDBObjectStore"; import BridgeIDBObjectStore from "./BridgeIDBObjectStore";
import BridgeIDBRequest from "./BridgeIDBRequest"; import BridgeIDBRequest from "./BridgeIDBRequest";
import enforceRange from "./util/enforceRange";
import { import {
ConstraintError, ConstraintError,
InvalidStateError, InvalidStateError,

View File

@ -215,6 +215,9 @@ class BridgeIDBTransaction extends FakeEventTarget {
let event; let event;
try { try {
const result = await operation(); const result = await operation();
if (BridgeIDBFactory.enableTracing) {
console.log("TRACE: tx operation finished with success");
}
request.readyState = "done"; request.readyState = "done";
request.result = result; request.result = result;
request.error = undefined; request.error = undefined;

View File

@ -4,7 +4,8 @@ import BridgeIDBFactory from "./BridgeIDBFactory";
import BridgeIDBRequest from "./BridgeIDBRequest"; import BridgeIDBRequest from "./BridgeIDBRequest";
import BridgeIDBDatabase from "./BridgeIDBDatabase"; import BridgeIDBDatabase from "./BridgeIDBDatabase";
import BridgeIDBTransaction from "./BridgeIDBTransaction"; import BridgeIDBTransaction from "./BridgeIDBTransaction";
import BridgeIDBKeyRange from "./BridgeIDBKeyRange";
import BridgeIDBCursorWithValue from "./BridgeIDBCursorWithValue";
function promiseFromRequest(request: BridgeIDBRequest): Promise<any> { function promiseFromRequest(request: BridgeIDBRequest): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -17,11 +18,13 @@ function promiseFromRequest(request: BridgeIDBRequest): Promise<any> {
}); });
} }
function promiseFromTransaction(transaction: BridgeIDBTransaction): Promise<any> { function promiseFromTransaction(
transaction: BridgeIDBTransaction,
): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log("attaching event handlers"); console.log("attaching event handlers");
transaction.oncomplete = () => { transaction.oncomplete = () => {
console.log("oncomplete was called from promise") console.log("oncomplete was called from promise");
resolve(); resolve();
}; };
transaction.onerror = () => { transaction.onerror = () => {
@ -51,7 +54,6 @@ test("Spec: Example 1 Part 1", async t => {
t.pass(); t.pass();
}); });
test("Spec: Example 1 Part 2", async t => { test("Spec: Example 1 Part 2", async t => {
const backend = new MemoryBackend(); const backend = new MemoryBackend();
const idb = new BridgeIDBFactory(backend); const idb = new BridgeIDBFactory(backend);
@ -70,21 +72,20 @@ test("Spec: Example 1 Part 2", async t => {
const tx = db.transaction("books", "readwrite"); const tx = db.transaction("books", "readwrite");
tx.oncomplete = () => { tx.oncomplete = () => {
console.log("oncomplete called") console.log("oncomplete called");
}; };
const store = tx.objectStore("books"); const store = tx.objectStore("books");
store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
await promiseFromTransaction(tx); await promiseFromTransaction(tx);
t.pass(); t.pass();
}); });
test("Spec: Example 1 Part 3", async t => { test("Spec: Example 1 Part 3", async t => {
const backend = new MemoryBackend(); const backend = new MemoryBackend();
const idb = new BridgeIDBFactory(backend); const idb = new BridgeIDBFactory(backend);
@ -102,15 +103,12 @@ test("Spec: Example 1 Part 3", async t => {
t.is(db.name, "library"); t.is(db.name, "library");
const tx = db.transaction("books", "readwrite"); const tx = db.transaction("books", "readwrite");
tx.oncomplete = () => {
console.log("oncomplete called")
};
const store = tx.objectStore("books"); const store = tx.objectStore("books");
store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
await promiseFromTransaction(tx); await promiseFromTransaction(tx);
@ -122,5 +120,38 @@ test("Spec: Example 1 Part 3", async t => {
t.is(result2.author, "Barney"); t.is(result2.author, "Barney");
const tx3 = db.transaction(["books"], "readonly");
const store3 = tx3.objectStore("books");
const index3 = store3.index("by_author");
const request3 = index3.openCursor(BridgeIDBKeyRange.only("Fred"));
await promiseFromRequest(request3);
let cursor: BridgeIDBCursorWithValue;
cursor = request3.result as BridgeIDBCursorWithValue;
t.is(cursor.value.author, "Fred");
t.is(cursor.value.isbn, 123456);
cursor.continue();
await promiseFromRequest(request3);
cursor = request3.result as BridgeIDBCursorWithValue;
t.is(cursor.value.author, "Fred");
t.is(cursor.value.isbn, 234567);
await promiseFromTransaction(tx3);
const tx4 = db.transaction("books", "readonly");
const store4 = tx4.objectStore("books");
const request4 = store4.openCursor();
await promiseFromRequest(request4);
cursor = request4.result;
t.is(cursor.value.isbn, 123456);
db.close();
t.pass(); t.pass();
}); });

View File

@ -132,8 +132,11 @@ function nextStoreKey<T>(
return res[1].primaryKey; return res[1].primaryKey;
} }
function furthestKey(
function furthestKey(forward: boolean, key1: Key | undefined, key2: Key | undefined) { forward: boolean,
key1: Key | undefined,
key2: Key | undefined,
) {
if (key1 === undefined) { if (key1 === undefined) {
return key2; return key2;
} }
@ -668,6 +671,7 @@ export class MemoryBackend implements Backend {
): Promise<RecordGetResponse> { ): Promise<RecordGetResponse> {
if (this.enableTracing) { if (this.enableTracing) {
console.log(`TRACING: getRecords`); console.log(`TRACING: getRecords`);
console.log("query", req);
} }
const myConn = this.connectionsByTransaction[btx.transactionCookie]; const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) { if (!myConn) {
@ -687,15 +691,15 @@ export class MemoryBackend implements Backend {
let range; let range;
if (req.range == null || req.range === undefined) { if (req.range == null || req.range === undefined) {
range = new BridgeIDBKeyRange(null, null, true, true); range = new BridgeIDBKeyRange(undefined, undefined, true, true);
} else { } else {
range = req.range; range = req.range;
} }
let numResults = 0; let numResults = 0;
let indexKeys: Key[] = []; let indexKeys: Key[] = [];
let primaryKeys = []; let primaryKeys: Key[] = [];
let values = []; let values: Value[] = [];
const forward: boolean = const forward: boolean =
req.direction === "next" || req.direction === "nextunique"; req.direction === "next" || req.direction === "nextunique";
@ -797,8 +801,6 @@ export class MemoryBackend implements Backend {
primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1; primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
} }
// FIXME: filter out duplicates
while (1) { while (1) {
if (req.limit != 0 && numResults == req.limit) { if (req.limit != 0 && numResults == req.limit) {
break; break;
@ -821,6 +823,16 @@ export class MemoryBackend implements Backend {
break; break;
} }
} }
if (
unique &&
indexKeys.length > 0 &&
compareKeys(indexEntry.indexKey, indexKeys[indexKeys.length - 1]) ===
0
) {
// We only return the first result if subsequent index keys are the same.
continue;
}
indexKeys.push(indexEntry.indexKey);
primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]); primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]);
numResults++; numResults++;
primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1; primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
@ -850,6 +862,7 @@ export class MemoryBackend implements Backend {
storePos = furthestKey(forward, req.advancePrimaryKey, storePos); storePos = furthestKey(forward, req.advancePrimaryKey, storePos);
if (storePos !== null && storePos !== undefined) {
// Advance store position if we are either still at the last returned // Advance store position if we are either still at the last returned
// store key, or if we are currently not on a key. // store key, or if we are currently not on a key.
const storeEntry = storeData.get(storePos); const storeEntry = storeData.get(storePos);
@ -860,8 +873,11 @@ export class MemoryBackend implements Backend {
) { ) {
storePos = storeData.nextHigherKey(storePos); storePos = storeData.nextHigherKey(storePos);
} }
} else {
storePos = forward ? storeData.minKey() : storeData.maxKey();
console.log("setting starting store store pos to", storePos);
}
if (req.lastObjectStorePosition)
while (1) { while (1) {
if (req.limit != 0 && numResults == req.limit) { if (req.limit != 0 && numResults == req.limit) {
break; break;
@ -875,23 +891,24 @@ export class MemoryBackend implements Backend {
const res = storeData.get(storePos); const res = storeData.get(storePos);
if (!res) { if (res === undefined) {
break; break;
} }
if (req.resultLevel >= ResultLevel.OnlyKeys) { if (req.resultLevel >= ResultLevel.OnlyKeys) {
primaryKeys.push(res.primaryKey); primaryKeys.push(structuredClone(storePos));
} }
if (req.resultLevel >= ResultLevel.Full) { if (req.resultLevel >= ResultLevel.Full) {
values.push(res.value); values.push(res);
} }
numResults++; numResults++;
storePos = nextStoreKey(forward, storeData, storePos); storePos = nextStoreKey(forward, storeData, storePos);
} }
} }
if (this.enableTracing) { if (this.enableTracing) {
console.log(`TRACING: getRecords got ${numResults} results`) console.log(`TRACING: getRecords got ${numResults} results`);
} }
return { return {
count: numResults, count: numResults,
@ -962,7 +979,7 @@ export class MemoryBackend implements Backend {
} }
} }
insertIntoIndex( private insertIntoIndex(
index: Index, index: Index,
primaryKey: Key, primaryKey: Key,
value: Value, value: Value,
@ -987,7 +1004,9 @@ export class MemoryBackend implements Backend {
} else { } else {
const newIndexRecord = { const newIndexRecord = {
indexKey: indexKey, indexKey: indexKey,
primaryKeys: [primaryKey].concat(existingRecord.primaryKeys), primaryKeys: [primaryKey]
.concat(existingRecord.primaryKeys)
.sort(compareKeys),
}; };
index.modifiedData = indexData.with(indexKey, newIndexRecord, true); index.modifiedData = indexData.with(indexKey, newIndexRecord, true);
} }

View File

@ -36,9 +36,9 @@ export interface DatabaseTransaction {
} }
export enum ResultLevel { export enum ResultLevel {
Full,
OnlyKeys,
OnlyCount, OnlyCount,
OnlyKeys,
Full,
} }
export interface RecordGetRequest { export interface RecordGetRequest {

View File

@ -39,6 +39,8 @@ const getType = (x: any) => {
// https://w3c.github.io/IndexedDB/#compare-two-keys // https://w3c.github.io/IndexedDB/#compare-two-keys
const compareKeys = (first: any, second: any): -1 | 0 | 1 => { const compareKeys = (first: any, second: any): -1 | 0 | 1 => {
console.log("comparing keys", first, second);
if (second === undefined) { if (second === undefined) {
throw new TypeError(); throw new TypeError();
} }

View File

@ -14,7 +14,6 @@
permissions and limitations under the License. permissions and limitations under the License.
*/ */
import { DataError } from "./errors"; import { DataError } from "./errors";
import { Key } from "./types"; import { Key } from "./types";
@ -63,8 +62,9 @@ function valueToKey(input: any, seen?: Set<object>): Key | Key[] {
} }
return keys; return keys;
} else { } else {
throw new DataError(); throw new DataError();
} }
}; }
export default valueToKey; export default valueToKey;