From 859a9e72e1e0431d729e429865d6272e2fb03ff7 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sun, 23 Jun 2019 22:16:03 +0200 Subject: idb: more tests working --- packages/idb-bridge/src/BridgeIDBCursor.ts | 48 ++++++++-- .../idb-bridge/src/BridgeIDBCursorWithValue.ts | 11 ++- packages/idb-bridge/src/BridgeIDBIndex.ts | 1 - packages/idb-bridge/src/BridgeIDBTransaction.ts | 3 + packages/idb-bridge/src/MemoryBackend.test.ts | 65 +++++++++---- packages/idb-bridge/src/MemoryBackend.ts | 103 ++++++++++++--------- packages/idb-bridge/src/backend-interface.ts | 4 +- packages/idb-bridge/src/util/cmp.ts | 2 + packages/idb-bridge/src/util/valueToKey.ts | 88 +++++++++--------- 9 files changed, 206 insertions(+), 119 deletions(-) (limited to 'packages') diff --git a/packages/idb-bridge/src/BridgeIDBCursor.ts b/packages/idb-bridge/src/BridgeIDBCursor.ts index 8321e2a1d..ed5aa3e88 100644 --- a/packages/idb-bridge/src/BridgeIDBCursor.ts +++ b/packages/idb-bridge/src/BridgeIDBCursor.ts @@ -52,9 +52,9 @@ class BridgeIDBCursor { private _gotValue: boolean = false; private _range: CursorRange; - private _position = undefined; // Key of previously returned record + private _indexPosition = undefined; // Key of previously returned record private _objectStorePosition = undefined; - private _keyOnly: boolean = false; + private _keyOnly: boolean; private _source: CursorSource; private _direction: BridgeIDBCursorDirection; @@ -63,6 +63,8 @@ class BridgeIDBCursor { private _indexName: string | undefined; private _objectStoreName: string; + protected _value: Value = undefined; + constructor( source: CursorSource, objectStoreName: string, @@ -128,7 +130,7 @@ class BridgeIDBCursor { const recordGetRequest: RecordGetRequest = { direction: this.direction, indexName: this._indexName, - lastIndexPosition: this._position, + lastIndexPosition: this._indexPosition, lastObjectStorePosition: this._objectStorePosition, limit: 1, range: this._range, @@ -140,15 +142,38 @@ class BridgeIDBCursor { const { btx } = this.source._confirmActiveTransaction(); - let response = await this._backend.getRecords( - btx, - recordGetRequest, - ); + let response = await this._backend.getRecords(btx, recordGetRequest); if (response.count === 0) { + console.log("cursor is returning empty result"); 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; } @@ -171,6 +196,7 @@ class BridgeIDBCursor { if (this._effectiveObjectStore._deleted) { throw new InvalidStateError(); } + if ( !(this.source instanceof BridgeIDBObjectStore) && this.source._deleted @@ -232,8 +258,12 @@ class BridgeIDBCursor { if (key !== undefined) { key = valueToKey(key); + let lastKey = + this._indexName === undefined + ? this._objectStorePosition + : this._indexPosition; - const cmpResult = compareKeys(key, this._position); + const cmpResult = compareKeys(key, lastKey); if ( (cmpResult <= 0 && @@ -250,7 +280,7 @@ class BridgeIDBCursor { } const operation = async () => { - this._iterate(key); + return this._iterate(key); }; transaction._execRequestAsync({ diff --git a/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts b/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts index 2739a6f14..b2f23147f 100644 --- a/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts +++ b/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts @@ -22,9 +22,12 @@ import { Value, } from "./util/types"; -class FDBCursorWithValue extends BridgeIDBCursor { - public value: Value = undefined; +class BridgeIDBCursorWithValue extends BridgeIDBCursor { + get value(): Value { + return this._value; + } + constructor( source: CursorSource, objectStoreName: string, @@ -33,7 +36,7 @@ class FDBCursorWithValue extends BridgeIDBCursor { direction: BridgeIDBCursorDirection, request?: any, ) { - super(source, objectStoreName, indexName, range, direction, request, true); + super(source, objectStoreName, indexName, range, direction, request, false); } public toString() { @@ -41,4 +44,4 @@ class FDBCursorWithValue extends BridgeIDBCursor { } } -export default FDBCursorWithValue; +export default BridgeIDBCursorWithValue; diff --git a/packages/idb-bridge/src/BridgeIDBIndex.ts b/packages/idb-bridge/src/BridgeIDBIndex.ts index 6001bc051..8179be83f 100644 --- a/packages/idb-bridge/src/BridgeIDBIndex.ts +++ b/packages/idb-bridge/src/BridgeIDBIndex.ts @@ -19,7 +19,6 @@ import BridgeIDBCursorWithValue from "./BridgeIDBCursorWithValue"; import BridgeIDBKeyRange from "./BridgeIDBKeyRange"; import BridgeIDBObjectStore from "./BridgeIDBObjectStore"; import BridgeIDBRequest from "./BridgeIDBRequest"; -import enforceRange from "./util/enforceRange"; import { ConstraintError, InvalidStateError, diff --git a/packages/idb-bridge/src/BridgeIDBTransaction.ts b/packages/idb-bridge/src/BridgeIDBTransaction.ts index 09f324dfa..a9f0201d3 100644 --- a/packages/idb-bridge/src/BridgeIDBTransaction.ts +++ b/packages/idb-bridge/src/BridgeIDBTransaction.ts @@ -215,6 +215,9 @@ class BridgeIDBTransaction extends FakeEventTarget { let event; try { const result = await operation(); + if (BridgeIDBFactory.enableTracing) { + console.log("TRACE: tx operation finished with success"); + } request.readyState = "done"; request.result = result; request.error = undefined; diff --git a/packages/idb-bridge/src/MemoryBackend.test.ts b/packages/idb-bridge/src/MemoryBackend.test.ts index 213bff750..c882b9760 100644 --- a/packages/idb-bridge/src/MemoryBackend.test.ts +++ b/packages/idb-bridge/src/MemoryBackend.test.ts @@ -4,7 +4,8 @@ import BridgeIDBFactory from "./BridgeIDBFactory"; import BridgeIDBRequest from "./BridgeIDBRequest"; import BridgeIDBDatabase from "./BridgeIDBDatabase"; import BridgeIDBTransaction from "./BridgeIDBTransaction"; - +import BridgeIDBKeyRange from "./BridgeIDBKeyRange"; +import BridgeIDBCursorWithValue from "./BridgeIDBCursorWithValue"; function promiseFromRequest(request: BridgeIDBRequest): Promise { return new Promise((resolve, reject) => { @@ -17,11 +18,13 @@ function promiseFromRequest(request: BridgeIDBRequest): Promise { }); } -function promiseFromTransaction(transaction: BridgeIDBTransaction): Promise { +function promiseFromTransaction( + transaction: BridgeIDBTransaction, +): Promise { return new Promise((resolve, reject) => { console.log("attaching event handlers"); transaction.oncomplete = () => { - console.log("oncomplete was called from promise") + console.log("oncomplete was called from promise"); resolve(); }; transaction.onerror = () => { @@ -51,7 +54,6 @@ test("Spec: Example 1 Part 1", async t => { t.pass(); }); - test("Spec: Example 1 Part 2", async t => { const backend = new MemoryBackend(); const idb = new BridgeIDBFactory(backend); @@ -70,21 +72,20 @@ test("Spec: Example 1 Part 2", async t => { const tx = db.transaction("books", "readwrite"); tx.oncomplete = () => { - console.log("oncomplete called") + console.log("oncomplete called"); }; const store = tx.objectStore("books"); - - store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); - store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567}); - store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678}); + + store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 }); + store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 }); + store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 }); await promiseFromTransaction(tx); t.pass(); }); - test("Spec: Example 1 Part 3", async t => { const backend = new MemoryBackend(); const idb = new BridgeIDBFactory(backend); @@ -102,15 +103,12 @@ test("Spec: Example 1 Part 3", async t => { t.is(db.name, "library"); const tx = db.transaction("books", "readwrite"); - tx.oncomplete = () => { - console.log("oncomplete called") - }; const store = tx.objectStore("books"); - - store.put({title: "Quarry Memories", author: "Fred", isbn: 123456}); - 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 }); + store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 }); + store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 }); await promiseFromTransaction(tx); @@ -122,5 +120,38 @@ test("Spec: Example 1 Part 3", async t => { 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(); }); diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts index 831974882..75e583a89 100644 --- a/packages/idb-bridge/src/MemoryBackend.ts +++ b/packages/idb-bridge/src/MemoryBackend.ts @@ -132,8 +132,11 @@ function nextStoreKey( return res[1].primaryKey; } - -function furthestKey(forward: boolean, key1: Key | undefined, key2: Key | undefined) { +function furthestKey( + forward: boolean, + key1: Key | undefined, + key2: Key | undefined, +) { if (key1 === undefined) { return key2; } @@ -668,6 +671,7 @@ export class MemoryBackend implements Backend { ): Promise { if (this.enableTracing) { console.log(`TRACING: getRecords`); + console.log("query", req); } const myConn = this.connectionsByTransaction[btx.transactionCookie]; if (!myConn) { @@ -687,15 +691,15 @@ export class MemoryBackend implements Backend { let range; if (req.range == null || req.range === undefined) { - range = new BridgeIDBKeyRange(null, null, true, true); + range = new BridgeIDBKeyRange(undefined, undefined, true, true); } else { range = req.range; } let numResults = 0; let indexKeys: Key[] = []; - let primaryKeys = []; - let values = []; + let primaryKeys: Key[] = []; + let values: Value[] = []; const forward: boolean = req.direction === "next" || req.direction === "nextunique"; @@ -797,8 +801,6 @@ export class MemoryBackend implements Backend { primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1; } - // FIXME: filter out duplicates - while (1) { if (req.limit != 0 && numResults == req.limit) { break; @@ -821,6 +823,16 @@ export class MemoryBackend implements Backend { 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]); numResults++; primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1; @@ -850,48 +862,53 @@ export class MemoryBackend implements Backend { storePos = furthestKey(forward, req.advancePrimaryKey, storePos); - // Advance store position if we are either still at the last returned - // store key, or if we are currently not on a key. - const storeEntry = storeData.get(storePos); - if ( - !storeEntry || - (req.lastObjectStorePosition !== undefined && - compareKeys(req.lastObjectStorePosition, storeEntry.primaryKey)) - ) { - storePos = storeData.nextHigherKey(storePos); + if (storePos !== null && storePos !== undefined) { + // Advance store position if we are either still at the last returned + // store key, or if we are currently not on a key. + const storeEntry = storeData.get(storePos); + if ( + !storeEntry || + (req.lastObjectStorePosition !== undefined && + compareKeys(req.lastObjectStorePosition, storeEntry.primaryKey)) + ) { + 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) { - if (req.limit != 0 && numResults == req.limit) { - break; - } - if (storePos === null || storePos === undefined) { - break; - } - if (!range.includes(storePos)) { - break; - } + while (1) { + if (req.limit != 0 && numResults == req.limit) { + break; + } + if (storePos === null || storePos === undefined) { + break; + } + if (!range.includes(storePos)) { + break; + } - const res = storeData.get(storePos); + const res = storeData.get(storePos); - if (!res) { - break; - } + if (res === undefined) { + break; + } - if (req.resultLevel >= ResultLevel.OnlyKeys) { - primaryKeys.push(res.primaryKey); - } + if (req.resultLevel >= ResultLevel.OnlyKeys) { + primaryKeys.push(structuredClone(storePos)); + } - if (req.resultLevel >= ResultLevel.Full) { - values.push(res.value); - } - numResults++; - storePos = nextStoreKey(forward, storeData, storePos); + if (req.resultLevel >= ResultLevel.Full) { + values.push(res); } + + numResults++; + storePos = nextStoreKey(forward, storeData, storePos); + } } if (this.enableTracing) { - console.log(`TRACING: getRecords got ${numResults} results`) + console.log(`TRACING: getRecords got ${numResults} results`); } return { count: numResults, @@ -962,7 +979,7 @@ export class MemoryBackend implements Backend { } } - insertIntoIndex( + private insertIntoIndex( index: Index, primaryKey: Key, value: Value, @@ -987,7 +1004,9 @@ export class MemoryBackend implements Backend { } else { const newIndexRecord = { indexKey: indexKey, - primaryKeys: [primaryKey].concat(existingRecord.primaryKeys), + primaryKeys: [primaryKey] + .concat(existingRecord.primaryKeys) + .sort(compareKeys), }; index.modifiedData = indexData.with(indexKey, newIndexRecord, true); } diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts index c963b1896..ab093d9cb 100644 --- a/packages/idb-bridge/src/backend-interface.ts +++ b/packages/idb-bridge/src/backend-interface.ts @@ -36,9 +36,9 @@ export interface DatabaseTransaction { } export enum ResultLevel { - Full, - OnlyKeys, OnlyCount, + OnlyKeys, + Full, } export interface RecordGetRequest { diff --git a/packages/idb-bridge/src/util/cmp.ts b/packages/idb-bridge/src/util/cmp.ts index 9d0dc99a2..078c0a9bd 100644 --- a/packages/idb-bridge/src/util/cmp.ts +++ b/packages/idb-bridge/src/util/cmp.ts @@ -39,6 +39,8 @@ const getType = (x: any) => { // https://w3c.github.io/IndexedDB/#compare-two-keys const compareKeys = (first: any, second: any): -1 | 0 | 1 => { + console.log("comparing keys", first, second); + if (second === undefined) { throw new TypeError(); } diff --git a/packages/idb-bridge/src/util/valueToKey.ts b/packages/idb-bridge/src/util/valueToKey.ts index 3a9e36786..85c8c409f 100644 --- a/packages/idb-bridge/src/util/valueToKey.ts +++ b/packages/idb-bridge/src/util/valueToKey.ts @@ -14,57 +14,57 @@ permissions and limitations under the License. */ - import { DataError } from "./errors"; import { Key } from "./types"; // https://w3c.github.io/IndexedDB/#convert-a-value-to-a-input function valueToKey(input: any, seen?: Set): Key | Key[] { - if (typeof input === "number") { - if (isNaN(input)) { - throw new DataError(); - } - return input; - } else if (input instanceof Date) { - const ms = input.valueOf(); - if (isNaN(ms)) { - throw new DataError(); - } - return new Date(ms); - } else if (typeof input === "string") { - return input; - } else if ( - input instanceof ArrayBuffer || - (typeof ArrayBuffer !== "undefined" && - ArrayBuffer.isView && - ArrayBuffer.isView(input)) - ) { - if (input instanceof ArrayBuffer) { - return new Uint8Array(input).buffer; - } - return new Uint8Array(input.buffer).buffer; - } else if (Array.isArray(input)) { - if (seen === undefined) { - seen = new Set(); - } else if (seen.has(input)) { - throw new DataError(); - } - seen.add(input); + if (typeof input === "number") { + if (isNaN(input)) { + throw new DataError(); + } + return input; + } else if (input instanceof Date) { + const ms = input.valueOf(); + if (isNaN(ms)) { + throw new DataError(); + } + return new Date(ms); + } else if (typeof input === "string") { + return input; + } else if ( + input instanceof ArrayBuffer || + (typeof ArrayBuffer !== "undefined" && + ArrayBuffer.isView && + ArrayBuffer.isView(input)) + ) { + if (input instanceof ArrayBuffer) { + return new Uint8Array(input).buffer; + } + return new Uint8Array(input.buffer).buffer; + } else if (Array.isArray(input)) { + if (seen === undefined) { + seen = new Set(); + } else if (seen.has(input)) { + throw new DataError(); + } + seen.add(input); - const keys = []; - for (let i = 0; i < input.length; i++) { - const hop = input.hasOwnProperty(i); - if (!hop) { - throw new DataError(); - } - const entry = input[i]; - const key = valueToKey(entry, seen); - keys.push(key); - } - return keys; - } else { + const keys = []; + for (let i = 0; i < input.length; i++) { + const hop = input.hasOwnProperty(i); + if (!hop) { throw new DataError(); + } + const entry = input[i]; + const key = valueToKey(entry, seen); + keys.push(key); } -}; + return keys; + } else { + + throw new DataError(); + } +} export default valueToKey; -- cgit v1.2.3