idb-bridge: indexes belong to object stores

This commit is contained in:
Florian Dold 2019-08-17 01:03:55 +02:00
parent 3263d05ce9
commit d947b90df3
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 186 additions and 160 deletions

View File

@ -44,7 +44,7 @@ export class BridgeIDBFactory {
queueTask(async () => { queueTask(async () => {
const databases = await this.backend.getDatabases(); const databases = await this.backend.getDatabases();
const dbInfo = databases.find((x) => x.name == name); const dbInfo = databases.find(x => x.name == name);
if (!dbInfo) { if (!dbInfo) {
// Database already doesn't exist, success! // Database already doesn't exist, success!
const event = new BridgeIDBVersionChangeEvent("success", { const event = new BridgeIDBVersionChangeEvent("success", {
@ -58,7 +58,10 @@ export class BridgeIDBFactory {
try { try {
const dbconn = await this.backend.connectDatabase(name); const dbconn = await this.backend.connectDatabase(name);
const backendTransaction = await this.backend.enterVersionChange(dbconn, 0); const backendTransaction = await this.backend.enterVersionChange(
dbconn,
0,
);
await this.backend.deleteDatabase(backendTransaction, name); await this.backend.deleteDatabase(backendTransaction, name);
await this.backend.commit(backendTransaction); await this.backend.commit(backendTransaction);
await this.backend.close(dbconn); await this.backend.close(dbconn);
@ -120,6 +123,11 @@ export class BridgeIDBFactory {
const requestedVersion = version; const requestedVersion = version;
BridgeIDBFactory.enableTracing &&
console.log(
`TRACE: existing version ${existingVersion}, requested version ${requestedVersion}`,
);
if (existingVersion > requestedVersion) { if (existingVersion > requestedVersion) {
request._finishWithError(new VersionError()); request._finishWithError(new VersionError());
return; return;
@ -127,6 +135,18 @@ export class BridgeIDBFactory {
const db = new BridgeIDBDatabase(this.backend, dbconn); const db = new BridgeIDBDatabase(this.backend, dbconn);
if (existingVersion == requestedVersion) {
request.result = db;
request.readyState = "done";
const event2 = new FakeEvent("success", {
bubbles: false,
cancelable: false,
});
event2.eventPath = [request];
request.dispatchEvent(event2);
}
if (existingVersion < requestedVersion) { if (existingVersion < requestedVersion) {
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
@ -146,7 +166,10 @@ export class BridgeIDBFactory {
request.dispatchEvent(event); request.dispatchEvent(event);
} }
const backendTransaction = await this.backend.enterVersionChange(dbconn, requestedVersion); const backendTransaction = await this.backend.enterVersionChange(
dbconn,
requestedVersion,
);
db._runningVersionchangeTransaction = true; db._runningVersionchangeTransaction = true;
const transaction = db._internalTransaction( const transaction = db._internalTransaction(

View File

@ -59,15 +59,15 @@ export class BridgeIDBIndex {
} }
get keyPath(): KeyPath { get keyPath(): KeyPath {
return this._schema.indexes[this._name].keyPath; return this._schema.objectStores[this.objectStore.name].indexes[this._name].keyPath;
} }
get multiEntry(): boolean { get multiEntry(): boolean {
return this._schema.indexes[this._name].multiEntry; return this._schema.objectStores[this.objectStore.name].indexes[this._name].multiEntry;
} }
get unique(): boolean { get unique(): boolean {
return this._schema.indexes[this._name].unique; return this._schema.objectStores[this.objectStore.name].indexes[this._name].unique;
} }
get _backend(): Backend { get _backend(): Backend {
@ -112,7 +112,7 @@ export class BridgeIDBIndex {
return; return;
} }
this._backend.renameIndex(btx, oldName, newName); this._backend.renameIndex(btx, this.objectStore.name, oldName, newName);
if (this.objectStore.indexNames.indexOf(name) >= 0) { if (this.objectStore.indexNames.indexOf(name) >= 0) {
throw new ConstraintError(); throw new ConstraintError();

View File

@ -63,7 +63,7 @@ class BridgeIDBObjectStore {
get indexNames(): FakeDOMStringList { get indexNames(): FakeDOMStringList {
return fakeDOMStringList( return fakeDOMStringList(
this._schema.objectStores[this._name].indexes, Object.keys(this._schema.objectStores[this._name].indexes),
).sort(); ).sort();
} }
@ -404,7 +404,7 @@ class BridgeIDBObjectStore {
return new BridgeIDBIndex(this, name); return new BridgeIDBIndex(this, name);
} }
public deleteIndex(name: string) { public deleteIndex(indexName: string) {
if (arguments.length === 0) { if (arguments.length === 0) {
throw new TypeError(); throw new TypeError();
} }
@ -419,12 +419,12 @@ class BridgeIDBObjectStore {
const { btx } = this._confirmActiveTransaction(); const { btx } = this._confirmActiveTransaction();
const index = this._indexesCache.get(name); const index = this._indexesCache.get(indexName);
if (index !== undefined) { if (index !== undefined) {
index._deleted = true; index._deleted = true;
} }
this._backend.deleteIndex(btx, name); this._backend.deleteIndex(btx, this._name, indexName);
} }
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-count-IDBRequest-any-key // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-count-IDBRequest-any-key

View File

@ -57,6 +57,8 @@ interface ObjectStore {
deleted: boolean; deleted: boolean;
originalKeyGenerator: number; originalKeyGenerator: number;
modifiedKeyGenerator: number | undefined; modifiedKeyGenerator: number | undefined;
committedIndexes: { [name: string]: Index };
modifiedIndexes: { [name: string]: Index };
} }
interface Index { interface Index {
@ -70,8 +72,6 @@ interface Index {
interface Database { interface Database {
committedObjectStores: { [name: string]: ObjectStore }; committedObjectStores: { [name: string]: ObjectStore };
modifiedObjectStores: { [name: string]: ObjectStore }; modifiedObjectStores: { [name: string]: ObjectStore };
committedIndexes: { [name: string]: Index };
modifiedIndexes: { [name: string]: Index };
committedSchema: Schema; committedSchema: Schema;
/** /**
* Was the transaction deleted during the running transaction? * Was the transaction deleted during the running transaction?
@ -83,27 +83,32 @@ interface Database {
connectionCookie: string | undefined; connectionCookie: string | undefined;
} }
interface ObjectStoreDump {
name: string;
keyGenerator: number;
records: ObjectStoreRecord[];
}
interface IndexDump { interface IndexDump {
name: string; name: string;
records: IndexRecord[]; records: IndexRecord[];
} }
interface ObjectStoreDump {
name: string;
keyGenerator: number;
records: ObjectStoreRecord[];
indexes: { [name: string]: IndexDump };
}
interface DatabaseDump { interface DatabaseDump {
schema: Schema; schema: Schema;
objectStores: { [name: string]: ObjectStoreDump }; objectStores: { [name: string]: ObjectStoreDump };
indexes: { [name: string]: IndexDump };
} }
interface MemoryBackendDump { interface MemoryBackendDump {
databases: { [name: string]: DatabaseDump }; databases: { [name: string]: DatabaseDump };
} }
interface ObjectStoreMapEntry {
store: ObjectStore;
indexMap: { [currentName: string]: Index };
}
interface Connection { interface Connection {
dbName: string; dbName: string;
@ -118,8 +123,7 @@ interface Connection {
* Map from the effective name of an object store during * Map from the effective name of an object store during
* the transaction to the real name. * the transaction to the real name.
*/ */
objectStoreMap: { [currentName: string]: ObjectStore }; objectStoreMap: { [currentName: string]: ObjectStoreMapEntry };
indexMap: { [currentName: string]: Index };
} }
interface IndexRecord { interface IndexRecord {
@ -253,25 +257,27 @@ export class MemoryBackend implements Backend {
if (typeof schema !== "object") { if (typeof schema !== "object") {
throw Error("DB dump corrupt"); throw Error("DB dump corrupt");
} }
const indexes: { [name: string]: Index } = {};
const objectStores: { [name: string]: ObjectStore } = {}; const objectStores: { [name: string]: ObjectStore } = {};
for (const indexName of Object.keys(data.databases[dbName].indexes)) {
const dumpedIndex = data.databases[dbName].indexes[indexName];
const pairs = dumpedIndex.records.map((r: any) => {
return structuredClone([r.indexKey, r]);
});
const indexData: ISortedMapF<Key, IndexRecord> = new BTree(pairs, compareKeys);
const index: Index = {
deleted: false,
modifiedData: undefined,
modifiedName: undefined,
originalName: indexName,
originalData: indexData,
}
indexes[indexName] = index;
}
for (const objectStoreName of Object.keys(data.databases[dbName].objectStores)) { for (const objectStoreName of Object.keys(data.databases[dbName].objectStores)) {
const dumpedObjectStore = data.databases[dbName].objectStores[objectStoreName]; const dumpedObjectStore = data.databases[dbName].objectStores[objectStoreName];
const indexes: { [name: string]: Index } = {};
for (const indexName of Object.keys(dumpedObjectStore.indexes)) {
const dumpedIndex = dumpedObjectStore.indexes[indexName];
const pairs = dumpedIndex.records.map((r: any) => {
return structuredClone([r.indexKey, r]);
});
const indexData: ISortedMapF<Key, IndexRecord> = new BTree(pairs, compareKeys);
const index: Index = {
deleted: false,
modifiedData: undefined,
modifiedName: undefined,
originalName: indexName,
originalData: indexData,
}
indexes[indexName] = index;
}
const pairs = dumpedObjectStore.records.map((r: any) => { const pairs = dumpedObjectStore.records.map((r: any) => {
return structuredClone([r.primaryKey, r]); return structuredClone([r.primaryKey, r]);
}); });
@ -284,16 +290,16 @@ export class MemoryBackend implements Backend {
originalData: objectStoreData, originalData: objectStoreData,
originalName: objectStoreName, originalName: objectStoreName,
originalKeyGenerator: dumpedObjectStore.keyGenerator, originalKeyGenerator: dumpedObjectStore.keyGenerator,
committedIndexes: indexes,
modifiedIndexes: {},
} }
objectStores[objectStoreName] = objectStore; objectStores[objectStoreName] = objectStore;
} }
const db: Database = { const db: Database = {
committedIndexes: indexes,
deleted: false, deleted: false,
committedObjectStores: objectStores, committedObjectStores: objectStores,
committedSchema: structuredClone(schema), committedSchema: structuredClone(schema),
connectionCookie: undefined, connectionCookie: undefined,
modifiedIndexes: {},
modifiedObjectStores: {}, modifiedObjectStores: {},
txLevel: TransactionLevel.Disconnected, txLevel: TransactionLevel.Disconnected,
}; };
@ -301,6 +307,19 @@ export class MemoryBackend implements Backend {
} }
} }
private makeObjectStoreMap(database: Database): { [currentName: string]: ObjectStoreMapEntry } {
let map: { [currentName: string]: ObjectStoreMapEntry } = {}
for (let objectStoreName in database.committedObjectStores) {
const store = database.committedObjectStores[objectStoreName];
const entry: ObjectStoreMapEntry = {
store,
indexMap: Object.assign({}, store.committedIndexes)
};
map[objectStoreName] = entry;
}
return map;
}
/** /**
* Export the contents of the database to JSON. * Export the contents of the database to JSON.
* *
@ -310,18 +329,19 @@ export class MemoryBackend implements Backend {
const dbDumps: { [name: string]: DatabaseDump } = {}; const dbDumps: { [name: string]: DatabaseDump } = {};
for (const dbName of Object.keys(this.databases)) { for (const dbName of Object.keys(this.databases)) {
const db = this.databases[dbName]; const db = this.databases[dbName];
const indexes: { [name: string]: IndexDump } = {};
const objectStores: { [name: string]: ObjectStoreDump } = {}; const objectStores: { [name: string]: ObjectStoreDump } = {};
for (const indexName of Object.keys(db.committedIndexes)) {
const index = db.committedIndexes[indexName];
const indexRecords: IndexRecord[] = [];
index.originalData.forEach((v: IndexRecord) => {
indexRecords.push(structuredClone(v));
});
indexes[indexName] = { name: indexName, records: indexRecords };
}
for (const objectStoreName of Object.keys(db.committedObjectStores)) { for (const objectStoreName of Object.keys(db.committedObjectStores)) {
const objectStore = db.committedObjectStores[objectStoreName]; const objectStore = db.committedObjectStores[objectStoreName];
const indexes: { [name: string]: IndexDump } = {};
for (const indexName of Object.keys(objectStore.committedIndexes)) {
const index = objectStore.committedIndexes[indexName];
const indexRecords: IndexRecord[] = [];
index.originalData.forEach((v: IndexRecord) => {
indexRecords.push(structuredClone(v));
});
indexes[indexName] = { name: indexName, records: indexRecords };
}
const objectStoreRecords: ObjectStoreRecord[] = []; const objectStoreRecords: ObjectStoreRecord[] = [];
objectStore.originalData.forEach((v: ObjectStoreRecord) => { objectStore.originalData.forEach((v: ObjectStoreRecord) => {
objectStoreRecords.push(structuredClone(v)); objectStoreRecords.push(structuredClone(v));
@ -330,10 +350,10 @@ export class MemoryBackend implements Backend {
name: objectStoreName, name: objectStoreName,
records: objectStoreRecords, records: objectStoreRecords,
keyGenerator: objectStore.originalKeyGenerator, keyGenerator: objectStore.originalKeyGenerator,
indexes: indexes,
}; };
} }
const dbDump: DatabaseDump = { const dbDump: DatabaseDump = {
indexes,
objectStores, objectStores,
schema: structuredClone(this.databases[dbName].committedSchema), schema: structuredClone(this.databases[dbName].committedSchema),
}; };
@ -391,15 +411,12 @@ export class MemoryBackend implements Backend {
if (!database) { if (!database) {
const schema: Schema = { const schema: Schema = {
databaseName: name, databaseName: name,
indexes: {},
databaseVersion: 0, databaseVersion: 0,
objectStores: {}, objectStores: {},
}; };
database = { database = {
committedSchema: schema, committedSchema: schema,
deleted: false, deleted: false,
modifiedIndexes: {},
committedIndexes: {},
committedObjectStores: {}, committedObjectStores: {},
modifiedObjectStores: {}, modifiedObjectStores: {},
txLevel: TransactionLevel.Disconnected, txLevel: TransactionLevel.Disconnected,
@ -418,8 +435,7 @@ export class MemoryBackend implements Backend {
const myConn: Connection = { const myConn: Connection = {
dbName: name, dbName: name,
deleted: false, deleted: false,
indexMap: Object.assign({}, database.committedIndexes), objectStoreMap: this.makeObjectStoreMap(database),
objectStoreMap: Object.assign({}, database.committedObjectStores),
modifiedSchema: structuredClone(database.committedSchema), modifiedSchema: structuredClone(database.committedSchema),
}; };
@ -532,6 +548,7 @@ export class MemoryBackend implements Backend {
renameIndex( renameIndex(
btx: DatabaseTransaction, btx: DatabaseTransaction,
objectStoreName: string,
oldName: string, oldName: string,
newName: string, newName: string,
): void { ): void {
@ -553,34 +570,25 @@ export class MemoryBackend implements Backend {
if (!schema) { if (!schema) {
throw Error(); throw Error();
} }
if (schema.indexes[newName]) { const indexesSchema = schema.objectStores[objectStoreName].indexes;
if (indexesSchema[newName]) {
throw new Error("new index name already used"); throw new Error("new index name already used");
} }
if (!schema.indexes[oldName]) { if (!indexesSchema) {
throw new Error("new index name already used"); throw new Error("new index name already used");
} }
const index: Index = myConn.indexMap[oldName]; const index: Index = myConn.objectStoreMap[objectStoreName].indexMap[oldName];
if (!index) { if (!index) {
throw Error("old index missing in connection's index map"); throw Error("old index missing in connection's index map");
} }
schema.indexes[newName] = schema.indexes[newName]; indexesSchema[newName] = indexesSchema[newName];
delete schema.indexes[oldName]; delete indexesSchema[oldName];
for (const storeName in schema.objectStores) { myConn.objectStoreMap[objectStoreName].indexMap[newName] = index;
const store = schema.objectStores[storeName]; delete myConn.objectStoreMap[objectStoreName].indexMap[oldName];
store.indexes = store.indexes.map(x => {
if (x == oldName) {
return newName;
} else {
return x;
}
});
}
myConn.indexMap[newName] = index;
delete myConn.indexMap[oldName];
index.modifiedName = newName; index.modifiedName = newName;
} }
deleteIndex(btx: DatabaseTransaction, indexName: string): void { deleteIndex(btx: DatabaseTransaction, objectStoreName: string, indexName: string): void {
if (this.enableTracing) { if (this.enableTracing) {
console.log(`TRACING: deleteIndex(${indexName})`); console.log(`TRACING: deleteIndex(${indexName})`);
} }
@ -599,22 +607,16 @@ export class MemoryBackend implements Backend {
if (!schema) { if (!schema) {
throw Error(); throw Error();
} }
if (!schema.indexes[indexName]) { if (!schema.objectStores[objectStoreName].indexes[indexName]) {
throw new Error("index does not exist"); throw new Error("index does not exist");
} }
const index: Index = myConn.indexMap[indexName]; const index: Index = myConn.objectStoreMap[objectStoreName].indexMap[indexName];
if (!index) { if (!index) {
throw Error("old index missing in connection's index map"); throw Error("old index missing in connection's index map");
} }
index.deleted = true; index.deleted = true;
delete schema.indexes[indexName]; delete schema.objectStores[objectStoreName].indexes[indexName];
delete myConn.indexMap[indexName]; delete myConn.objectStoreMap[objectStoreName].indexMap[indexName];
for (const storeName in schema.objectStores) {
const store = schema.objectStores[storeName];
store.indexes = store.indexes.filter(x => {
return x !== indexName;
});
}
} }
deleteObjectStore(btx: DatabaseTransaction, name: string): void { deleteObjectStore(btx: DatabaseTransaction, name: string): void {
@ -640,16 +642,16 @@ export class MemoryBackend implements Backend {
if (!objectStoreProperties) { if (!objectStoreProperties) {
throw Error("object store not found"); throw Error("object store not found");
} }
const objectStore = myConn.objectStoreMap[name]; const objectStoreMapEntry = myConn.objectStoreMap[name];
if (!objectStore) { if (!objectStoreMapEntry) {
throw Error("object store not found in map"); throw Error("object store not found in map");
} }
const indexNames = objectStoreProperties.indexes; const indexNames = Object.keys(objectStoreProperties.indexes);
for (const indexName of indexNames) { for (const indexName of indexNames) {
this.deleteIndex(btx, indexName); this.deleteIndex(btx, name, indexName);
} }
objectStore.deleted = true; objectStoreMapEntry.store.deleted = true;
delete myConn.objectStoreMap[name]; delete myConn.objectStoreMap[name];
delete schema.objectStores[name]; delete schema.objectStores[name];
} }
@ -684,15 +686,15 @@ export class MemoryBackend implements Backend {
if (schema.objectStores[newName]) { if (schema.objectStores[newName]) {
throw Error("new object store already exists"); throw Error("new object store already exists");
} }
const objectStore = myConn.objectStoreMap[oldName]; const objectStoreMapEntry = myConn.objectStoreMap[oldName];
if (!objectStore) { if (!objectStoreMapEntry) {
throw Error("object store not found in map"); throw Error("object store not found in map");
} }
objectStore.modifiedName = newName; objectStoreMapEntry.store.modifiedName = newName;
schema.objectStores[newName] = schema.objectStores[oldName]; schema.objectStores[newName] = schema.objectStores[oldName];
delete schema.objectStores[oldName]; delete schema.objectStores[oldName];
delete myConn.objectStoreMap[oldName]; delete myConn.objectStoreMap[oldName];
myConn.objectStoreMap[newName] = objectStore; myConn.objectStoreMap[newName] = objectStoreMapEntry;
} }
createObjectStore( createObjectStore(
@ -725,6 +727,8 @@ export class MemoryBackend implements Backend {
originalData: new BTree([], compareKeys), originalData: new BTree([], compareKeys),
modifiedKeyGenerator: undefined, modifiedKeyGenerator: undefined,
originalKeyGenerator: 1, originalKeyGenerator: 1,
committedIndexes: {},
modifiedIndexes: {},
}; };
const schema = myConn.modifiedSchema; const schema = myConn.modifiedSchema;
if (!schema) { if (!schema) {
@ -733,9 +737,9 @@ export class MemoryBackend implements Backend {
schema.objectStores[name] = { schema.objectStores[name] = {
autoIncrement, autoIncrement,
keyPath, keyPath,
indexes: [], indexes: {},
}; };
myConn.objectStoreMap[name] = newObjectStore; myConn.objectStoreMap[name] = { store: newObjectStore, indexMap: {} };
db.modifiedObjectStores[name] = newObjectStore; db.modifiedObjectStores[name] = newObjectStore;
} }
@ -773,8 +777,8 @@ export class MemoryBackend implements Backend {
originalData: new BTree([], compareKeys), originalData: new BTree([], compareKeys),
originalName: indexName, originalName: indexName,
}; };
myConn.indexMap[indexName] = newIndex; myConn.objectStoreMap[objectStoreName].indexMap[indexName] = newIndex;
db.modifiedIndexes[indexName] = newIndex; db.modifiedObjectStores[objectStoreName].modifiedIndexes[indexName] = newIndex;
const schema = myConn.modifiedSchema; const schema = myConn.modifiedSchema;
if (!schema) { if (!schema) {
throw Error("no schema in versionchange tx"); throw Error("no schema in versionchange tx");
@ -783,15 +787,14 @@ export class MemoryBackend implements Backend {
if (!objectStoreProperties) { if (!objectStoreProperties) {
throw Error("object store not found"); throw Error("object store not found");
} }
objectStoreProperties.indexes.push(indexName); objectStoreProperties.indexes[indexName] = indexProperties;
schema.indexes[indexName] = indexProperties;
const objectStore = myConn.objectStoreMap[objectStoreName]; const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName];
if (!objectStore) { if (!objectStoreMapEntry) {
throw Error("object store does not exist"); throw Error("object store does not exist");
} }
const storeData = objectStore.modifiedData || objectStore.originalData; const storeData = objectStoreMapEntry.store.modifiedData || objectStoreMapEntry.store.originalData;
storeData.forEach((v, k) => { storeData.forEach((v, k) => {
this.insertIntoIndex(newIndex, k, v.value, indexProperties); this.insertIntoIndex(newIndex, k, v.value, indexProperties);
@ -827,13 +830,13 @@ export class MemoryBackend implements Backend {
} }
const schema = myConn.modifiedSchema; const schema = myConn.modifiedSchema;
const objectStore = myConn.objectStoreMap[objectStoreName]; const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName];
if (!objectStore.modifiedData) { if (!objectStoreMapEntry.store.modifiedData) {
objectStore.modifiedData = objectStore.originalData; objectStoreMapEntry.store.modifiedData = objectStoreMapEntry.store.originalData;
} }
let modifiedData = objectStore.modifiedData; let modifiedData = objectStoreMapEntry.store.modifiedData;
let currKey: Key | undefined; let currKey: Key | undefined;
if (range.lower === undefined || range.lower === null) { if (range.lower === undefined || range.lower === null) {
@ -870,12 +873,13 @@ export class MemoryBackend implements Backend {
throw Error("assertion failed"); throw Error("assertion failed");
} }
for (const indexName of schema.objectStores[objectStoreName].indexes) {
const index = myConn.indexMap[indexName]; for (const indexName of Object.keys(schema.objectStores[objectStoreName].indexes)) {
const index = myConn.objectStoreMap[objectStoreName].indexMap[indexName];
if (!index) { if (!index) {
throw Error("index referenced by object store does not exist"); throw Error("index referenced by object store does not exist");
} }
const indexProperties = schema.indexes[indexName]; const indexProperties = schema.objectStores[objectStoreName].indexes[indexName];
this.deleteFromIndex( this.deleteFromIndex(
index, index,
storeEntry.primaryKey, storeEntry.primaryKey,
@ -889,7 +893,7 @@ export class MemoryBackend implements Backend {
currKey = modifiedData.nextHigherKey(currKey); currKey = modifiedData.nextHigherKey(currKey);
} }
objectStore.modifiedData = modifiedData; objectStoreMapEntry.store.modifiedData = modifiedData;
} }
private deleteFromIndex( private deleteFromIndex(
@ -951,8 +955,8 @@ export class MemoryBackend implements Backend {
if (db.txLevel < TransactionLevel.Read) { if (db.txLevel < TransactionLevel.Read) {
throw Error("only allowed while running a transaction"); throw Error("only allowed while running a transaction");
} }
const objectStore = myConn.objectStoreMap[req.objectStoreName]; const objectStoreMapEntry = myConn.objectStoreMap[req.objectStoreName];
if (!objectStore) { if (!objectStoreMapEntry) {
throw Error("object store not found"); throw Error("object store not found");
} }
@ -985,12 +989,12 @@ export class MemoryBackend implements Backend {
const unique: boolean = const unique: boolean =
req.direction === "prevunique" || req.direction === "nextunique"; req.direction === "prevunique" || req.direction === "nextunique";
const storeData = objectStore.modifiedData || objectStore.originalData; const storeData = objectStoreMapEntry.store.modifiedData || objectStoreMapEntry.store.originalData;
const haveIndex = req.indexName !== undefined; const haveIndex = req.indexName !== undefined;
if (haveIndex) { if (haveIndex) {
const index = myConn.indexMap[req.indexName!]; const index = myConn.objectStoreMap[req.objectStoreName].indexMap[req.indexName!];
const indexData = index.modifiedData || index.originalData; const indexData = index.modifiedData || index.originalData;
let indexPos = req.lastIndexPosition; let indexPos = req.lastIndexPosition;
@ -1160,7 +1164,9 @@ export class MemoryBackend implements Backend {
for (let i = 0; i < numResults; i++) { for (let i = 0; i < numResults; i++) {
const result = storeData.get(primaryKeys[i]); const result = storeData.get(primaryKeys[i]);
if (!result) { if (!result) {
throw Error("invariant violated"); console.error("invariant violated during read");
console.error("request was", req);
throw Error("invariant violated during read");
} }
values.push(structuredClone(result.value)); values.push(structuredClone(result.value));
} }
@ -1262,12 +1268,12 @@ export class MemoryBackend implements Backend {
throw Error("only allowed while running a transaction"); throw Error("only allowed while running a transaction");
} }
const schema = myConn.modifiedSchema; const schema = myConn.modifiedSchema;
const objectStore = myConn.objectStoreMap[storeReq.objectStoreName]; const objectStoreMapEntry = myConn.objectStoreMap[storeReq.objectStoreName];
if (!objectStore.modifiedData) { if (!objectStoreMapEntry.store.modifiedData) {
objectStore.modifiedData = objectStore.originalData; objectStoreMapEntry.store.modifiedData = objectStoreMapEntry.store.originalData;
} }
const modifiedData = objectStore.modifiedData; const modifiedData = objectStoreMapEntry.store.modifiedData;
let key; let key;
let value; let value;
@ -1277,7 +1283,7 @@ export class MemoryBackend implements Backend {
throw Error("invalid update request (key not given)"); throw Error("invalid update request (key not given)");
} }
if (!objectStore.modifiedData.has(storeReq.key)) { if (!objectStoreMapEntry.store.modifiedData.has(storeReq.key)) {
throw Error("invalid update request (record does not exist)"); throw Error("invalid update request (record does not exist)");
} }
key = storeReq.key; key = storeReq.key;
@ -1286,13 +1292,13 @@ export class MemoryBackend implements Backend {
const storeKeyResult: StoreKeyResult = makeStoreKeyValue( const storeKeyResult: StoreKeyResult = makeStoreKeyValue(
storeReq.value, storeReq.value,
storeReq.key, storeReq.key,
objectStore.modifiedKeyGenerator || objectStore.originalKeyGenerator, objectStoreMapEntry.store.modifiedKeyGenerator || objectStoreMapEntry.store.originalKeyGenerator,
schema.objectStores[storeReq.objectStoreName].autoIncrement, schema.objectStores[storeReq.objectStoreName].autoIncrement,
schema.objectStores[storeReq.objectStoreName].keyPath, schema.objectStores[storeReq.objectStoreName].keyPath,
); );
key = storeKeyResult.key; key = storeKeyResult.key;
value = storeKeyResult.value; value = storeKeyResult.value;
objectStore.modifiedKeyGenerator = storeKeyResult.updatedKeyGenerator; objectStoreMapEntry.store.modifiedKeyGenerator = storeKeyResult.updatedKeyGenerator;
const hasKey = modifiedData.has(key); const hasKey = modifiedData.has(key);
if (hasKey && storeReq.storeLevel !== StoreLevel.AllowOverwrite) { if (hasKey && storeReq.storeLevel !== StoreLevel.AllowOverwrite) {
@ -1305,15 +1311,15 @@ export class MemoryBackend implements Backend {
value: structuredClone(value), value: structuredClone(value),
}; };
objectStore.modifiedData = modifiedData.with(key, objectStoreRecord, true); objectStoreMapEntry.store.modifiedData = modifiedData.with(key, objectStoreRecord, true);
for (const indexName of schema.objectStores[storeReq.objectStoreName] for (const indexName of Object.keys(schema.objectStores[storeReq.objectStoreName]
.indexes) { .indexes)) {
const index = myConn.indexMap[indexName]; const index = myConn.objectStoreMap[storeReq.objectStoreName] .indexMap[indexName];
if (!index) { if (!index) {
throw Error("index referenced by object store does not exist"); throw Error("index referenced by object store does not exist");
} }
const indexProperties = schema.indexes[indexName]; const indexProperties = schema.objectStores[storeReq.objectStoreName].indexes[indexName];
this.insertIntoIndex(index, key, value, indexProperties); this.insertIntoIndex(index, key, value, indexProperties);
} }
@ -1376,24 +1382,24 @@ export class MemoryBackend implements Backend {
if (db.txLevel < TransactionLevel.Read) { if (db.txLevel < TransactionLevel.Read) {
throw Error("only allowed while running a transaction"); throw Error("only allowed while running a transaction");
} }
db.modifiedIndexes = {};
db.modifiedObjectStores = {}; db.modifiedObjectStores = {};
db.txLevel = TransactionLevel.Connected; db.txLevel = TransactionLevel.Connected;
myConn.modifiedSchema = structuredClone(db.committedSchema); myConn.modifiedSchema = structuredClone(db.committedSchema);
myConn.indexMap = Object.assign({}, db.committedIndexes); myConn.objectStoreMap = this.makeObjectStoreMap(db);
myConn.objectStoreMap = Object.assign({}, db.committedObjectStores);
for (const indexName in db.committedIndexes) {
const index = db.committedIndexes[indexName];
index.deleted = false;
index.modifiedData = undefined;
index.modifiedName = undefined;
}
for (const objectStoreName in db.committedObjectStores) { for (const objectStoreName in db.committedObjectStores) {
const objectStore = db.committedObjectStores[objectStoreName]; const objectStore = db.committedObjectStores[objectStoreName];
objectStore.deleted = false; objectStore.deleted = false;
objectStore.modifiedData = undefined; objectStore.modifiedData = undefined;
objectStore.modifiedName = undefined; objectStore.modifiedName = undefined;
objectStore.modifiedKeyGenerator = undefined; objectStore.modifiedKeyGenerator = undefined;
objectStore.modifiedIndexes = {}
for (const indexName in Object.keys(db.committedSchema.objectStores[objectStoreName].indexes)) {
const index = objectStore.committedIndexes[indexName];
index.deleted = false;
index.modifiedData = undefined;
index.modifiedName = undefined;
}
} }
delete this.connectionsByTransaction[btx.transactionCookie]; delete this.connectionsByTransaction[btx.transactionCookie];
this.transactionDoneCond.trigger(); this.transactionDoneCond.trigger();
@ -1419,34 +1425,32 @@ export class MemoryBackend implements Backend {
db.committedSchema = structuredClone(myConn.modifiedSchema); db.committedSchema = structuredClone(myConn.modifiedSchema);
db.txLevel = TransactionLevel.Connected; db.txLevel = TransactionLevel.Connected;
db.committedIndexes = {};
db.committedObjectStores = {}; db.committedObjectStores = {};
db.modifiedIndexes = {};
db.committedObjectStores = {}; db.committedObjectStores = {};
for (const indexName in myConn.indexMap) {
const index = myConn.indexMap[indexName];
index.deleted = false;
index.originalData = index.modifiedData || index.originalData;
index.originalName = index.modifiedName || index.originalName;
db.committedIndexes[indexName] = index;
}
for (const objectStoreName in myConn.objectStoreMap) { for (const objectStoreName in myConn.objectStoreMap) {
const objectStore = myConn.objectStoreMap[objectStoreName]; const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName];
objectStore.deleted = false; const store = objectStoreMapEntry.store;
objectStore.originalData = store.deleted = false;
objectStore.modifiedData || objectStore.originalData; store.originalData = store.modifiedData || store.originalData;
objectStore.originalName = store.originalName = store.modifiedName || store.originalName;
objectStore.modifiedName || objectStore.originalName; store.modifiedIndexes = {};
if (objectStore.modifiedKeyGenerator !== undefined) { if (store.modifiedKeyGenerator !== undefined) {
objectStore.originalKeyGenerator = objectStore.modifiedKeyGenerator; store.originalKeyGenerator = store.modifiedKeyGenerator;
} }
db.committedObjectStores[objectStoreName] = objectStore; db.committedObjectStores[objectStoreName] = store;
for (const indexName in objectStoreMapEntry.indexMap) {
const index = objectStoreMapEntry.indexMap[indexName];
index.deleted = false;
index.originalData = index.modifiedData || index.originalData;
index.originalName = index.modifiedName || index.originalName;
store.committedIndexes[indexName] = index;
}
} }
myConn.indexMap = Object.assign({}, db.committedIndexes); myConn.objectStoreMap = this.makeObjectStoreMap(db);
myConn.objectStoreMap = Object.assign({}, db.committedObjectStores);
delete this.connectionsByTransaction[btx.transactionCookie]; delete this.connectionsByTransaction[btx.transactionCookie];
this.transactionDoneCond.trigger(); this.transactionDoneCond.trigger();

View File

@ -27,7 +27,7 @@ import BridgeIDBKeyRange from "./BridgeIDBKeyRange";
export interface ObjectStoreProperties { export interface ObjectStoreProperties {
keyPath: KeyPath | null; keyPath: KeyPath | null;
autoIncrement: boolean; autoIncrement: boolean;
indexes: string[]; indexes: { [nameame: string]: IndexProperties };
} }
export interface IndexProperties { export interface IndexProperties {
@ -40,7 +40,6 @@ export interface Schema {
databaseName: string; databaseName: string;
databaseVersion: number; databaseVersion: number;
objectStores: { [name: string]: ObjectStoreProperties }; objectStores: { [name: string]: ObjectStoreProperties };
indexes: { [name: string]: IndexProperties };
} }
export interface DatabaseConnection { export interface DatabaseConnection {
@ -153,9 +152,9 @@ export interface Backend {
getSchema(db: DatabaseConnection): Schema; getSchema(db: DatabaseConnection): Schema;
renameIndex(btx: DatabaseTransaction, oldName: string, newName: string): void; renameIndex(btx: DatabaseTransaction, objectStoreName: string, oldName: string, newName: string): void;
deleteIndex(btx: DatabaseTransaction, indexName: string): void; deleteIndex(btx: DatabaseTransaction, objectStoreName: string, indexName: string): void;
rollback(btx: DatabaseTransaction): Promise<void>; rollback(btx: DatabaseTransaction): Promise<void>;