Skip to content

Use ObjectMap instead of Map with proper comparator. #6018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions packages/firestore/src/local/document_overlay_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
* limitations under the License.
*/

import { DocumentKeySet } from '../model/collections';
import { DocumentKeySet, MutationMap, OverlayMap } from '../model/collections';
import { DocumentKey } from '../model/document_key';
import { Mutation } from '../model/mutation';
import { Overlay } from '../model/overlay';
import { ResourcePath } from '../model/path';

Expand Down Expand Up @@ -51,7 +50,7 @@ export interface DocumentOverlayCache {
saveOverlays(
transaction: PersistenceTransaction,
largestBatchId: number,
overlays: Map<DocumentKey, Mutation>
overlays: MutationMap
): PersistencePromise<void>;

/** Removes overlays for the given document keys and batch ID. */
Expand All @@ -74,7 +73,7 @@ export interface DocumentOverlayCache {
transaction: PersistenceTransaction,
collection: ResourcePath,
sinceBatchId: number
): PersistencePromise<Map<DocumentKey, Overlay>>;
): PersistencePromise<OverlayMap>;

/**
* Returns `count` overlays with a batch ID higher than `sinceBatchId` for the
Expand All @@ -95,5 +94,5 @@ export interface DocumentOverlayCache {
collectionGroup: string,
sinceBatchId: number,
count: number
): PersistencePromise<Map<DocumentKey, Overlay>>;
): PersistencePromise<OverlayMap>;
}
22 changes: 13 additions & 9 deletions packages/firestore/src/local/indexeddb_document_overlay_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
*/

import { User } from '../auth/user';
import { DocumentKeySet } from '../model/collections';
import {
DocumentKeySet,
MutationMap,
OverlayMap,
newOverlayMap
} from '../model/collections';
import { DocumentKey } from '../model/document_key';
import { Mutation } from '../model/mutation';
import { Overlay } from '../model/overlay';
import { ResourcePath } from '../model/path';

Expand Down Expand Up @@ -74,10 +78,10 @@ export class IndexedDbDocumentOverlayCache implements DocumentOverlayCache {
saveOverlays(
transaction: PersistenceTransaction,
largestBatchId: number,
overlays: Map<DocumentKey, Mutation>
overlays: MutationMap
): PersistencePromise<void> {
const promises: Array<PersistencePromise<void>> = [];
overlays.forEach(mutation => {
overlays.forEach((_, mutation) => {
const overlay = new Overlay(largestBatchId, mutation);
promises.push(this.saveOverlay(transaction, overlay));
});
Expand Down Expand Up @@ -118,8 +122,8 @@ export class IndexedDbDocumentOverlayCache implements DocumentOverlayCache {
transaction: PersistenceTransaction,
collection: ResourcePath,
sinceBatchId: number
): PersistencePromise<Map<DocumentKey, Overlay>> {
const result = new Map<DocumentKey, Overlay>();
): PersistencePromise<OverlayMap> {
const result = newOverlayMap();
const collectionPath = encodeResourcePath(collection);
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
// is not inclusive.
Expand All @@ -144,8 +148,8 @@ export class IndexedDbDocumentOverlayCache implements DocumentOverlayCache {
collectionGroup: string,
sinceBatchId: number,
count: number
): PersistencePromise<Map<DocumentKey, Overlay>> {
const result = new Map<DocumentKey, Overlay>();
): PersistencePromise<OverlayMap> {
const result = newOverlayMap();
let currentBatchId: number | undefined = undefined;
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
// is not inclusive.
Expand All @@ -167,7 +171,7 @@ export class IndexedDbDocumentOverlayCache implements DocumentOverlayCache {
// `count` if there are more overlays from the `currentBatchId`.
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
if (
result.size < count ||
result.size() < count ||
overlay.largestBatchId === currentBatchId
) {
result.set(overlay.getKey(), overlay);
Expand Down
39 changes: 24 additions & 15 deletions packages/firestore/src/local/memory_document_overlay_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
* limitations under the License.
*/

import { DocumentKeySet } from '../model/collections';
import {
documentKeySet,
DocumentKeySet,
MutationMap,
OverlayMap,
newOverlayMap
} from '../model/collections';
import { DocumentKey } from '../model/document_key';
import { Mutation } from '../model/mutation';
import { Overlay } from '../model/overlay';
Expand All @@ -35,7 +41,7 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
private overlays = new SortedMap<DocumentKey, Overlay>(
DocumentKey.comparator
);
private overlayByBatchId = new Map<number, Set<DocumentKey>>();
private overlayByBatchId = new Map<number, DocumentKeySet>();

getOverlay(
transaction: PersistenceTransaction,
Expand All @@ -47,9 +53,9 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
saveOverlays(
transaction: PersistenceTransaction,
largestBatchId: number,
overlays: Map<DocumentKey, Mutation>
overlays: MutationMap
): PersistencePromise<void> {
overlays.forEach(mutation => {
overlays.forEach((_, mutation) => {
this.saveOverlay(transaction, largestBatchId, mutation);
});
return PersistencePromise.resolve();
Expand All @@ -72,8 +78,8 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
transaction: PersistenceTransaction,
collection: ResourcePath,
sinceBatchId: number
): PersistencePromise<Map<DocumentKey, Overlay>> {
const result = new Map<DocumentKey, Overlay>();
): PersistencePromise<OverlayMap> {
const result = newOverlayMap();

const immediateChildrenPathLength = collection.length + 1;
const prefix = new DocumentKey(collection.child(''));
Expand Down Expand Up @@ -102,8 +108,8 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
collectionGroup: string,
sinceBatchId: number,
count: number
): PersistencePromise<Map<DocumentKey, Overlay>> {
let batchIdToOverlays = new SortedMap<number, Map<DocumentKey, Overlay>>(
): PersistencePromise<OverlayMap> {
let batchIdToOverlays = new SortedMap<number, OverlayMap>(
(key1: number, key2: number) => key1 - key2
);

Expand All @@ -118,7 +124,7 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
if (overlay.largestBatchId > sinceBatchId) {
let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
if (overlaysForBatchId === null) {
overlaysForBatchId = new Map<DocumentKey, Overlay>();
overlaysForBatchId = newOverlayMap();
batchIdToOverlays = batchIdToOverlays.insert(
overlay.largestBatchId,
overlaysForBatchId
Expand All @@ -128,13 +134,13 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
}
}

const result = new Map<DocumentKey, Overlay>();
const result = newOverlayMap();
const batchIter = batchIdToOverlays.getIterator();
while (batchIter.hasNext()) {
const entry = batchIter.getNext();
const overlays = entry.value;
overlays.forEach((overlay, key) => result.set(key, overlay));
if (result.size >= count) {
overlays.forEach((key, overlay) => result.set(key, overlay));
if (result.size() >= count) {
break;
}
}
Expand All @@ -153,7 +159,10 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
// Remove the association of the overlay to its batch id.
const existing = this.overlays.get(mutation.key);
if (existing !== null) {
this.overlayByBatchId.get(existing.largestBatchId)!.delete(mutation.key);
const newSet = this.overlayByBatchId
.get(existing.largestBatchId)!
.delete(mutation.key);
this.overlayByBatchId.set(existing.largestBatchId, newSet);
}

this.overlays = this.overlays.insert(
Expand All @@ -164,9 +173,9 @@ export class MemoryDocumentOverlayCache implements DocumentOverlayCache {
// Create the association of this overlay to the given largestBatchId.
let batch = this.overlayByBatchId.get(largestBatchId);
if (batch === undefined) {
batch = new Set<DocumentKey>();
batch = documentKeySet();
this.overlayByBatchId.set(largestBatchId, batch);
}
batch.add(mutation.key);
this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
}
}
19 changes: 19 additions & 0 deletions packages/firestore/src/model/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
import { SnapshotVersion } from '../core/snapshot_version';
import { TargetId } from '../core/types';
import { primitiveComparator } from '../util/misc';
import { ObjectMap } from '../util/obj_map';
import { SortedMap } from '../util/sorted_map';
import { SortedSet } from '../util/sorted_set';

import { Document, MutableDocument } from './document';
import { DocumentKey } from './document_key';
import { Mutation } from './mutation';
import { Overlay } from './overlay';

/** Miscellaneous collection types / constants. */

Expand All @@ -47,6 +50,22 @@ export function documentMap(): DocumentMap {
return EMPTY_DOCUMENT_MAP;
}

export type OverlayMap = ObjectMap<DocumentKey, Overlay>;
export function newOverlayMap(): OverlayMap {
return new ObjectMap<DocumentKey, Overlay>(
key => key.toString(),
(l, r) => l.isEqual(r)
);
}

export type MutationMap = ObjectMap<DocumentKey, Mutation>;
export function newMutationMap(): MutationMap {
return new ObjectMap<DocumentKey, Mutation>(
key => key.toString(),
(l, r) => l.isEqual(r)
);
}

export type DocumentVersionMap = SortedMap<DocumentKey, SnapshotVersion>;
const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap<DocumentKey, SnapshotVersion>(
DocumentKey.comparator
Expand Down
11 changes: 11 additions & 0 deletions packages/firestore/src/util/obj_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export class ObjectMap<KeyType, ValueType> {
[canonicalId: string]: Array<Entry<KeyType, ValueType>>;
} = {};

/** The number of entries stored in the map */
private innerSize = 0;

constructor(
private mapKeyFn: (key: KeyType) => string,
private equalsFn: (l: KeyType, r: KeyType) => boolean
Expand Down Expand Up @@ -66,15 +69,18 @@ export class ObjectMap<KeyType, ValueType> {
const matches = this.inner[id];
if (matches === undefined) {
this.inner[id] = [[key, value]];
this.innerSize++;
return;
}
for (let i = 0; i < matches.length; i++) {
if (this.equalsFn(matches[i][0], key)) {
// This is updating an existing entry and does not increase `innerSize`.
matches[i] = [key, value];
return;
}
}
matches.push([key, value]);
this.innerSize++;
}

/**
Expand All @@ -93,6 +99,7 @@ export class ObjectMap<KeyType, ValueType> {
} else {
matches.splice(i, 1);
}
this.innerSize--;
return true;
}
}
Expand All @@ -110,4 +117,8 @@ export class ObjectMap<KeyType, ValueType> {
isEmpty(): boolean {
return isEmpty(this.inner);
}

size(): number {
return this.innerSize;
}
}
27 changes: 14 additions & 13 deletions packages/firestore/test/unit/local/document_overlay_cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ import { expect } from 'chai';
import { User } from '../../../src/auth/user';
import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence';
import { Persistence } from '../../../src/local/persistence';
import { documentKeySet, DocumentKeySet } from '../../../src/model/collections';
import { DocumentKey } from '../../../src/model/document_key';
import {
documentKeySet,
MutationMap,
OverlayMap,
newMutationMap
} from '../../../src/model/collections';
import { Mutation, mutationEquals } from '../../../src/model/mutation';
import { Overlay } from '../../../src/model/overlay';
import { addEqualityMatcher } from '../../util/equality_matcher';
import {
deleteMutation,
Expand Down Expand Up @@ -86,7 +89,7 @@ function genericDocumentOverlayCacheTests(): void {
largestBatch: number,
...mutations: Mutation[]
): Promise<void> {
const data: Map<DocumentKey, Mutation> = new Map<DocumentKey, Mutation>();
const data: MutationMap = newMutationMap();
for (const mutation of mutations) {
data.set(mutation.key, mutation);
}
Expand All @@ -97,7 +100,7 @@ function genericDocumentOverlayCacheTests(): void {
largestBatch: number,
...overlayKeys: string[]
): Promise<void> {
const data: Map<DocumentKey, Mutation> = new Map<DocumentKey, Mutation>();
const data: MutationMap = newMutationMap();
for (const overlayKey of overlayKeys) {
data.set(key(overlayKey), setMutation(overlayKey, {}));
}
Expand All @@ -115,16 +118,14 @@ function genericDocumentOverlayCacheTests(): void {
}

function verifyOverlayContains(
overlays: Map<DocumentKey, Overlay>,
overlays: OverlayMap,
...keys: string[]
): void {
const overlayKeys: DocumentKeySet = documentKeySet(
...Array.from(overlays.keys())
);
const expectedKeys: DocumentKeySet = documentKeySet(
...Array.from(keys.map(value => key(value)))
);
expect(overlayKeys.isEqual(expectedKeys)).to.equal(true);
let overlayKeys = documentKeySet();
overlays.forEach(overlayKey => (overlayKeys = overlayKeys.add(overlayKey)));
let expectedKeys = documentKeySet();
keys.forEach(value => (expectedKeys = expectedKeys.add(key(value))));
expect(overlayKeys.isEqual(expectedKeys)).to.deep.equal(true);
}

it('returns null when overlay is not found', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@

import { DocumentOverlayCache } from '../../../src/local/document_overlay_cache';
import { Persistence } from '../../../src/local/persistence';
import { DocumentKeySet } from '../../../src/model/collections';
import {
DocumentKeySet,
MutationMap,
OverlayMap
} from '../../../src/model/collections';
import { DocumentKey } from '../../../src/model/document_key';
import { Mutation } from '../../../src/model/mutation';
import { Overlay } from '../../../src/model/overlay';
Expand All @@ -34,10 +38,7 @@ export class TestDocumentOverlayCache {
private cache: DocumentOverlayCache
) {}

saveOverlays(
largestBatch: number,
data: Map<DocumentKey, Mutation>
): Promise<void> {
saveOverlays(largestBatch: number, data: MutationMap): Promise<void> {
return this.persistence.runTransaction('saveOverlays', 'readwrite', txn => {
return this.cache.saveOverlays(txn, largestBatch, data);
});
Expand All @@ -61,7 +62,7 @@ export class TestDocumentOverlayCache {
getOverlaysForCollection(
path: ResourcePath,
sinceBatchId: number
): Promise<Map<DocumentKey, Overlay>> {
): Promise<OverlayMap> {
return this.persistence.runTransaction(
'getOverlaysForCollection',
'readonly',
Expand All @@ -75,7 +76,7 @@ export class TestDocumentOverlayCache {
collectionGroup: string,
sinceBatchId: number,
count: number
): Promise<Map<DocumentKey, Overlay>> {
): Promise<OverlayMap> {
return this.persistence.runTransaction(
'getOverlaysForCollectionGroup',
'readonly',
Expand Down
Loading