From 0fd52be7101902217c30c7d883ab7451a9ff36e0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 21:55:33 +0200 Subject: [PATCH] encode idb array keys as sortable strings that's why numeric parts of the keys have to be encoded as a fixed length, "big-endian" ordered strings, so string sorting will also sort the numeric keys correctly. this also assumes room ids don't contain the "|" character, we should probably escape the separator at some point. --- src/matrix/storage/idb/create.js | 20 ++++----- .../storage/idb/stores/RoomStateStore.js | 6 ++- .../storage/idb/stores/TimelineEventStore.js | 44 ++++++++++++++----- .../idb/stores/TimelineFragmentStore.js | 13 ++++-- 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/matrix/storage/idb/create.js b/src/matrix/storage/idb/create.js index ce8b6aca..42b41ae0 100644 --- a/src/matrix/storage/idb/create.js +++ b/src/matrix/storage/idb/create.js @@ -12,18 +12,14 @@ function createStores(db) { db.createObjectStore("roomSummary", {keyPath: "roomId"}); // need index to find live fragment? prooobably ok without for now - db.createObjectStore("timelineFragments", {keyPath: ["roomId", "id"]}); - const timelineEvents = db.createObjectStore("timelineEvents", {keyPath: ["roomId", "fragmentId", "eventIndex"]}); - timelineEvents.createIndex("byEventId", [ - "roomId", - "event.event_id" - ], {unique: true}); - - db.createObjectStore("roomState", {keyPath: [ - "roomId", - "event.type", - "event.state_key" - ]}); + //key = room_id | fragment_id + db.createObjectStore("timelineFragments", {keyPath: "key"}); + //key = room_id | fragment_id | event_index + const timelineEvents = db.createObjectStore("timelineEvents", {keyPath: "key"}); + //eventIdKey = room_id | event_id + timelineEvents.createIndex("byEventId", "eventIdKey", {unique: true}); + //key = room_id | event.type | event.state_key, + db.createObjectStore("roomState", {keyPath: "key"}); // const roomMembers = db.createObjectStore("roomMembers", {keyPath: [ // "event.room_id", diff --git a/src/matrix/storage/idb/stores/RoomStateStore.js b/src/matrix/storage/idb/stores/RoomStateStore.js index 0a6ca949..09f3cd6d 100644 --- a/src/matrix/storage/idb/stores/RoomStateStore.js +++ b/src/matrix/storage/idb/stores/RoomStateStore.js @@ -12,6 +12,8 @@ export default class RoomStateStore { } async setStateEvent(roomId, event) { - return this._roomStateStore.put({roomId, event}); + const key = `${roomId}|${event.type}|${event.state_key}`; + const entry = {roomId, event, key}; + return this._roomStateStore.put(entry); } -} \ No newline at end of file +} diff --git a/src/matrix/storage/idb/stores/TimelineEventStore.js b/src/matrix/storage/idb/stores/TimelineEventStore.js index e53f6e70..1b4e6e00 100644 --- a/src/matrix/storage/idb/stores/TimelineEventStore.js +++ b/src/matrix/storage/idb/stores/TimelineEventStore.js @@ -1,6 +1,25 @@ import EventKey from "../../../room/timeline/EventKey.js"; import Platform from "../../../../Platform.js"; +// storage keys are defined to be unsigned 32bit numbers in WebPlatform.js, which is assumed by idb +function encodeUint32(n) { + const hex = n.toString(16); + return "0".repeat(8 - hex.length) + hex; +} + +function encodeKey(roomId, fragmentId, eventIndex) { + return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`; +} + +function encodeEventIdKey(roomId, eventId) { + return `${roomId}|${eventId}`; +} + +function decodeEventIdKey(eventIdKey) { + const [roomId, eventId] = eventIdKey.split("|"); + return {roomId, eventId}; +} + class Range { constructor(only, lower, upper, lowerOpen, upperOpen) { this._only = only; @@ -13,14 +32,14 @@ class Range { asIDBKeyRange(roomId) { // only if (this._only) { - return IDBKeyRange.only([roomId, this._only.fragmentId, this._only.eventIndex]); + return IDBKeyRange.only(encodeKey(roomId, this._only.fragmentId, this._only.eventIndex)); } // lowerBound // also bound as we don't want to move into another roomId if (this._lower && !this._upper) { return IDBKeyRange.bound( - [roomId, this._lower.fragmentId, this._lower.eventIndex], - [roomId, this._lower.fragmentId, Platform.maxStorageKey], + encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), + encodeKey(roomId, this._lower.fragmentId, Platform.maxStorageKey), this._lowerOpen, false ); @@ -29,8 +48,8 @@ class Range { // also bound as we don't want to move into another roomId if (!this._lower && this._upper) { return IDBKeyRange.bound( - [roomId, this._upper.fragmentId, Platform.minStorageKey], - [roomId, this._upper.fragmentId, this._upper.eventIndex], + encodeKey(roomId, this._upper.fragmentId, Platform.minStorageKey), + encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), false, this._upperOpen ); @@ -38,8 +57,8 @@ class Range { // bound if (this._lower && this._upper) { return IDBKeyRange.bound( - [roomId, this._lower.fragmentId, this._lower.eventIndex], - [roomId, this._upper.fragmentId, this._upper.eventIndex], + encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), + encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), this._lowerOpen, this._upperOpen ); @@ -170,7 +189,7 @@ export default class TimelineEventStore { // also passing them in chronological order makes sense as that's how we'll receive them almost always. async findFirstOccurringEventId(roomId, eventIds) { const byEventId = this._timelineStore.index("byEventId"); - const keys = eventIds.map(eventId => [roomId, eventId]); + const keys = eventIds.map(eventId => encodeEventIdKey(roomId, eventId)); const results = new Array(keys.length); let firstFoundKey; @@ -191,8 +210,7 @@ export default class TimelineEventStore { firstFoundKey = firstFoundAndPrecedingResolved(); return !!firstFoundKey; }); - // key of index is [roomId, eventId], so pick out eventId - return firstFoundKey && firstFoundKey[1]; + return firstFoundKey && decodeEventIdKey(firstFoundKey).eventId; } /** Inserts a new entry into the store. The combination of roomId and eventKey should not exist yet, or an error is thrown. @@ -201,6 +219,8 @@ export default class TimelineEventStore { * @throws {StorageError} ... */ insert(entry) { + entry.key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex); + entry.eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id); // TODO: map error? or in idb/store? return this._timelineStore.add(entry); } @@ -215,7 +235,7 @@ export default class TimelineEventStore { } get(roomId, eventKey) { - return this._timelineStore.get([roomId, eventKey.fragmentId, eventKey.eventIndex]); + return this._timelineStore.get(encodeKey(roomId, eventKey.fragmentId, eventKey.eventIndex)); } // returns the entries as well!! (or not always needed? I guess not always needed, so extra method) removeRange(roomId, range) { @@ -224,6 +244,6 @@ export default class TimelineEventStore { } getByEventId(roomId, eventId) { - return this._timelineStore.index("byEventId").get([roomId, eventId]); + return this._timelineStore.index("byEventId").get(encodeEventIdKey(roomId, eventId)); } } diff --git a/src/matrix/storage/idb/stores/TimelineFragmentStore.js b/src/matrix/storage/idb/stores/TimelineFragmentStore.js index 1e08bbec..2d86a100 100644 --- a/src/matrix/storage/idb/stores/TimelineFragmentStore.js +++ b/src/matrix/storage/idb/stores/TimelineFragmentStore.js @@ -1,5 +1,11 @@ import Platform from "../../../../Platform.js"; +function encodeKey(roomId, fragmentId) { + let fragmentIdHex = fragmentId.toString(16); + fragmentIdHex = "0".repeat(8 - fragmentIdHex.length) + fragmentIdHex; + return `${roomId}|${fragmentIdHex}`; +} + export default class RoomFragmentStore { constructor(store) { this._store = store; @@ -7,8 +13,8 @@ export default class RoomFragmentStore { _allRange(roomId) { return IDBKeyRange.bound( - [roomId, Platform.minStorageKey], - [roomId, Platform.maxStorageKey] + encodeKey(roomId, Platform.minStorageKey), + encodeKey(roomId, Platform.maxStorageKey) ); } @@ -35,6 +41,7 @@ export default class RoomFragmentStore { // depends if we want to do anything smart with fragment ids, // like give them meaning depending on range. not for now probably ... add(fragment) { + fragment.key = encodeKey(fragment.roomId, fragment.id); return this._store.add(fragment); } @@ -43,6 +50,6 @@ export default class RoomFragmentStore { } get(roomId, fragmentId) { - return this._store.get([roomId, fragmentId]); + return this._store.get(encodeKey(roomId, fragmentId)); } }