Skip to content

Commit 622d9d5

Browse files
committed
Overhaul Documents
Remove different internal types for MaybeDocument, Document, NoDocument, and UnknownDocument. These are all now just Documents. Also make Documents non-nullable nearly everywhere. The change introduces a DocumentType enum and maps the old classes like so: null => DocumentType.UNKNOWN UnknownDocument => DocumentType.CONTENTS_UNKNOWN NoDocument => DocumentType.MISSING Document => DocumentType.EXISTING Part of why this change works is that most of the system only cares whether or not a document exists. We have several refinements of non-existing documents for the purposes of caching but everywhere else we can ignore it and just check Document.exists. The instanceof checks were usually overconstraining things. The motivation for this change is in two parts: * It's always bugged me that we had so many instanceof checks around * In C++ we don't have instanceof anyway, so we have to do something else. Also, this definitely addresses b/32275378.
1 parent 9f109f8 commit 622d9d5

40 files changed

+916
-945
lines changed

packages/firestore/src/api/database.ts

Lines changed: 35 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ import { Transaction as InternalTransaction } from '../core/transaction';
4040
import { ChangeType, ViewSnapshot } from '../core/view_snapshot';
4141
import { IndexedDbPersistence } from '../local/indexeddb_persistence';
4242
import { LruParams } from '../local/lru_garbage_collector';
43-
import { Document, MaybeDocument, NoDocument } from '../model/document';
43+
import { getDocument } from '../model/collections';
44+
import { Document } from '../model/document';
4445
import { DocumentKey } from '../model/document_key';
4546
import {
4647
ArrayValue,
@@ -79,7 +80,7 @@ import { LogLevel } from '../util/log';
7980
import { AutoId } from '../util/misc';
8081
import * as objUtils from '../util/obj';
8182
import { Rejecter, Resolver } from '../util/promise';
82-
import { Deferred } from './../util/promise';
83+
import { Deferred } from '../util/promise';
8384
import { FieldPath as ExternalFieldPath } from './field_path';
8485

8586
import {
@@ -661,37 +662,18 @@ export class Transaction implements firestore.Transaction {
661662
documentRef,
662663
this._firestore
663664
);
664-
return this._transaction
665-
.lookup([ref._key])
666-
.then((docs: MaybeDocument[]) => {
667-
if (!docs || docs.length !== 1) {
668-
return fail('Mismatch in docs returned from document lookup.');
669-
}
670-
const doc = docs[0];
671-
if (doc instanceof NoDocument) {
672-
return new DocumentSnapshot(
673-
this._firestore,
674-
ref._key,
675-
null,
676-
/* fromCache= */ false,
677-
/* hasPendingWrites= */ false
678-
);
679-
} else if (doc instanceof Document) {
680-
return new DocumentSnapshot(
681-
this._firestore,
682-
ref._key,
683-
doc,
684-
/* fromCache= */ false,
685-
/* hasPendingWrites= */ false
686-
);
687-
} else {
688-
throw fail(
689-
`BatchGetDocumentsRequest returned unexpected document type: ${
690-
doc.constructor.name
691-
}`
692-
);
693-
}
694-
});
665+
return this._transaction.lookup([ref._key]).then((docs: Document[]) => {
666+
if (!docs || docs.length !== 1) {
667+
return fail('Mismatch in docs returned from document lookup.');
668+
}
669+
const doc = docs[0];
670+
return new DocumentSnapshot(
671+
this._firestore,
672+
doc,
673+
/* fromCache= */ false,
674+
/* hasPendingWrites= */ false
675+
);
676+
});
695677
}
696678

697679
set(
@@ -1130,12 +1112,11 @@ export class DocumentReference implements firestore.DocumentReference {
11301112
snapshot.docs.size <= 1,
11311113
'Too many documents returned on a document query'
11321114
);
1133-
const doc = snapshot.docs.get(this._key);
1115+
const doc = getDocument(this._key, snapshot.docs);
11341116

11351117
observer.next(
11361118
new DocumentSnapshot(
11371119
this.firestore,
1138-
this._key,
11391120
doc,
11401121
snapshot.fromCache,
11411122
snapshot.hasPendingWrites
@@ -1170,10 +1151,9 @@ export class DocumentReference implements firestore.DocumentReference {
11701151
resolve(
11711152
new DocumentSnapshot(
11721153
this.firestore,
1173-
this._key,
11741154
doc,
11751155
/*fromCache=*/ true,
1176-
doc instanceof Document ? doc.hasLocalMutations : false
1156+
doc.hasLocalMutations
11771157
)
11781158
);
11791159
}, reject);
@@ -1262,8 +1242,7 @@ export interface SnapshotOptions extends firestore.SnapshotOptions {}
12621242
export class DocumentSnapshot implements firestore.DocumentSnapshot {
12631243
constructor(
12641244
private _firestore: Firestore,
1265-
private _key: DocumentKey,
1266-
public _document: Document | null,
1245+
public _document: Document,
12671246
private _fromCache: boolean,
12681247
private _hasPendingWrites: boolean
12691248
) {}
@@ -1273,7 +1252,7 @@ export class DocumentSnapshot implements firestore.DocumentSnapshot {
12731252
): firestore.DocumentData | undefined {
12741253
validateBetweenNumberOfArgs('DocumentSnapshot.data', arguments, 0, 1);
12751254
options = validateSnapshotOptions('DocumentSnapshot.data', options);
1276-
return !this._document
1255+
return !this._document.exists
12771256
? undefined
12781257
: this.convertObject(
12791258
this._document.data,
@@ -1290,33 +1269,31 @@ export class DocumentSnapshot implements firestore.DocumentSnapshot {
12901269
): unknown {
12911270
validateBetweenNumberOfArgs('DocumentSnapshot.get', arguments, 1, 2);
12921271
options = validateSnapshotOptions('DocumentSnapshot.get', options);
1293-
if (this._document) {
1294-
const value = this._document.data.field(
1295-
fieldPathFromArgument('DocumentSnapshot.get', fieldPath)
1272+
const value = this._document.data.field(
1273+
fieldPathFromArgument('DocumentSnapshot.get', fieldPath)
1274+
);
1275+
if (value !== undefined) {
1276+
return this.convertValue(
1277+
value,
1278+
FieldValueOptions.fromSnapshotOptions(
1279+
options,
1280+
this._firestore._areTimestampsInSnapshotsEnabled()
1281+
)
12961282
);
1297-
if (value !== undefined) {
1298-
return this.convertValue(
1299-
value,
1300-
FieldValueOptions.fromSnapshotOptions(
1301-
options,
1302-
this._firestore._areTimestampsInSnapshotsEnabled()
1303-
)
1304-
);
1305-
}
13061283
}
13071284
return undefined;
13081285
}
13091286

13101287
get id(): string {
1311-
return this._key.path.lastSegment();
1288+
return this._document.key.path.lastSegment();
13121289
}
13131290

13141291
get ref(): firestore.DocumentReference {
1315-
return new DocumentReference(this._key, this._firestore);
1292+
return new DocumentReference(this._document.key, this._firestore);
13161293
}
13171294

13181295
get exists(): boolean {
1319-
return this._document !== null;
1296+
return this._document.exists;
13201297
}
13211298

13221299
get metadata(): firestore.SnapshotMetadata {
@@ -1330,10 +1307,7 @@ export class DocumentSnapshot implements firestore.DocumentSnapshot {
13301307
return (
13311308
this._firestore === other._firestore &&
13321309
this._fromCache === other._fromCache &&
1333-
this._key.isEqual(other._key) &&
1334-
(this._document === null
1335-
? other._document === null
1336-
: this._document.isEqual(other._document))
1310+
this._document.isEqual(other._document)
13371311
);
13381312
}
13391313

@@ -1359,7 +1333,7 @@ export class DocumentSnapshot implements firestore.DocumentSnapshot {
13591333
if (!value.databaseId.isEqual(database)) {
13601334
// TODO(b/64130202): Somehow support foreign references.
13611335
log.error(
1362-
`Document ${this._key.path} contains a document ` +
1336+
`Document ${this._document.key.path} contains a document ` +
13631337
`reference within a different database (` +
13641338
`${value.databaseId.projectId}/${
13651339
value.databaseId.database
@@ -1389,12 +1363,11 @@ export class QueryDocumentSnapshot extends DocumentSnapshot
13891363
implements firestore.QueryDocumentSnapshot {
13901364
constructor(
13911365
firestore: Firestore,
1392-
key: DocumentKey,
13931366
document: Document,
13941367
fromCache: boolean,
13951368
hasPendingWrites: boolean
13961369
) {
1397-
super(firestore, key, document, fromCache, hasPendingWrites);
1370+
super(firestore, document, fromCache, hasPendingWrites);
13981371
}
13991372

14001373
data(options?: SnapshotOptions): firestore.DocumentData {
@@ -2136,7 +2109,6 @@ export class QuerySnapshot implements firestore.QuerySnapshot {
21362109
private convertToDocumentImpl(doc: Document): QueryDocumentSnapshot {
21372110
return new QueryDocumentSnapshot(
21382111
this._firestore,
2139-
doc.key,
21402112
doc,
21412113
this.metadata.fromCache,
21422114
this._snapshot.mutatedKeys.has(doc.key)
@@ -2348,7 +2320,6 @@ export function changesFromSnapshot(
23482320
return snapshot.docChanges.map(change => {
23492321
const doc = new QueryDocumentSnapshot(
23502322
firestore,
2351-
change.doc.key,
23522323
change.doc,
23532324
snapshot.fromCache,
23542325
snapshot.mutatedKeys.has(change.doc.key)
@@ -2380,7 +2351,6 @@ export function changesFromSnapshot(
23802351
.map(change => {
23812352
const doc = new QueryDocumentSnapshot(
23822353
firestore,
2383-
change.doc.key,
23842354
change.doc,
23852355
snapshot.fromCache,
23862356
snapshot.mutatedKeys.has(change.doc.key)

packages/firestore/src/core/firestore_client.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
documentKeySet,
2727
DocumentMap
2828
} from '../model/collections';
29-
import { Document, MaybeDocument, NoDocument } from '../model/document';
29+
import { Document } from '../model/document';
3030
import { DocumentKey } from '../model/document_key';
3131
import { Mutation } from '../model/mutation';
3232
import { Platform } from '../platform/platform';
@@ -228,6 +228,7 @@ export class FirestoreClient {
228228
* result of enabling offline persistence. This method will reject this if
229229
* IndexedDB fails to start for any reason. If usePersistence is false
230230
* this is unconditionally resolved.
231+
* @param user The identity of the user that's currently active.
231232
* @returns a Promise indicating whether or not initialization should
232233
* continue, i.e. that one of the persistence implementations actually
233234
* succeeded.
@@ -480,7 +481,7 @@ export class FirestoreClient {
480481
}
481482
});
482483

483-
// When a user calls clearPersistence() in one client, all other clientfs
484+
// When a user calls clearPersistence() in one client, all other clients
484485
// need to shut down to allow the delete to succeed.
485486
await this.persistence.setDatabaseDeletedListener(async () => {
486487
await this.shutdown();
@@ -543,17 +544,15 @@ export class FirestoreClient {
543544
});
544545
}
545546

546-
getDocumentFromLocalCache(docKey: DocumentKey): Promise<Document | null> {
547+
getDocumentFromLocalCache(docKey: DocumentKey): Promise<Document> {
547548
this.verifyNotShutdown();
548549
return this.asyncQueue
549550
.enqueue(() => {
550551
return this.localStore.readDocument(docKey);
551552
})
552-
.then((maybeDoc: MaybeDocument | null) => {
553-
if (maybeDoc instanceof Document) {
553+
.then((maybeDoc: Document) => {
554+
if (maybeDoc.definite) {
554555
return maybeDoc;
555-
} else if (maybeDoc instanceof NoDocument) {
556-
return null;
557556
} else {
558557
throw new FirestoreError(
559558
Code.UNAVAILABLE,

packages/firestore/src/core/query.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ export class Query {
323323

324324
matches(doc: Document): boolean {
325325
return (
326+
doc.exists &&
326327
this.matchesPathAndCollectionGroup(doc) &&
327328
this.matchesOrderBy(doc) &&
328329
this.matchesFilters(doc) &&

packages/firestore/src/core/snapshot_version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class SnapshotVersion {
3636
return new SnapshotVersion(value);
3737
}
3838

39-
static forDeletedDoc(): SnapshotVersion {
39+
static forMissingDoc(): SnapshotVersion {
4040
return SnapshotVersion.MIN;
4141
}
4242

packages/firestore/src/core/sync_engine.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import { ReferenceSet } from '../local/reference_set';
2323
import {
2424
documentKeySet,
2525
DocumentKeySet,
26-
MaybeDocumentMap
26+
DocumentMap
2727
} from '../model/collections';
28-
import { MaybeDocument, NoDocument } from '../model/document';
28+
import { Document } from '../model/document';
2929
import { DocumentKey } from '../model/document_key';
3030
import { Mutation } from '../model/mutation';
3131
import { MutationBatchResult } from '../model/mutation_batch';
@@ -526,12 +526,12 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
526526
// This is kind of a hack. Ideally, we would have a method in the local
527527
// store to purge a document. However, it would be tricky to keep all of
528528
// the local store's invariants with another method.
529-
let documentUpdates = new SortedMap<DocumentKey, MaybeDocument>(
529+
let documentUpdates = new SortedMap<DocumentKey, Document>(
530530
DocumentKey.comparator
531531
);
532532
documentUpdates = documentUpdates.insert(
533533
limboKey,
534-
new NoDocument(limboKey, SnapshotVersion.forDeletedDoc())
534+
Document.missing(limboKey, SnapshotVersion.forMissingDoc())
535535
);
536536
const resolvedLimboDocuments = documentKeySet().add(limboKey);
537537
const event = new RemoteEvent(
@@ -758,7 +758,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
758758
}
759759

760760
private async emitNewSnapsAndNotifyLocalStore(
761-
changes: MaybeDocumentMap,
761+
changes: DocumentMap,
762762
remoteEvent?: RemoteEvent
763763
): Promise<void> {
764764
const newSnaps: ViewSnapshot[] = [];

packages/firestore/src/core/transaction.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717

1818
import { ParsedSetData, ParsedUpdateData } from '../api/user_data_converter';
1919
import { documentVersionMap } from '../model/collections';
20-
import { Document, NoDocument } from '../model/document';
21-
import { MaybeDocument } from '../model/document';
20+
import { Document } from '../model/document';
2221
import { DocumentKey } from '../model/document_key';
2322
import { DeleteMutation, Mutation, Precondition } from '../model/mutation';
2423
import { Datastore } from '../remote/datastore';
@@ -38,16 +37,14 @@ export class Transaction {
3837

3938
constructor(private datastore: Datastore) {}
4039

41-
private recordVersion(doc: MaybeDocument): void {
40+
private recordVersion(doc: Document): void {
4241
let docVersion: SnapshotVersion;
4342

44-
if (doc instanceof Document) {
43+
if (doc.exists) {
4544
docVersion = doc.version;
46-
} else if (doc instanceof NoDocument) {
47-
// For deleted docs, we must use baseVersion 0 when we overwrite them.
48-
docVersion = SnapshotVersion.forDeletedDoc();
4945
} else {
50-
throw fail('Document in a transaction was a ' + doc.constructor.name);
46+
// For deleted docs, we must use baseVersion 0 when we overwrite them.
47+
docVersion = SnapshotVersion.forMissingDoc();
5148
}
5249

5350
const existingVersion = this.readVersions.get(doc.key);
@@ -64,23 +61,21 @@ export class Transaction {
6461
}
6562
}
6663

67-
lookup(keys: DocumentKey[]): Promise<MaybeDocument[]> {
64+
lookup(keys: DocumentKey[]): Promise<Document[]> {
6865
if (this.committed) {
69-
return Promise.reject<MaybeDocument[]>(
70-
'Transaction has already completed.'
71-
);
66+
return Promise.reject<Document[]>('Transaction has already completed.');
7267
}
7368
if (this.mutations.length > 0) {
74-
return Promise.reject<MaybeDocument[]>(
69+
return Promise.reject<Document[]>(
7570
'Transactions lookups are invalid after writes.'
7671
);
7772
}
7873
return this.datastore.lookup(keys).then(docs => {
7974
docs.forEach(doc => {
80-
if (doc instanceof NoDocument || doc instanceof Document) {
75+
if (doc.definite) {
8176
this.recordVersion(doc);
8277
} else {
83-
fail('Document in a transaction was a ' + doc.constructor.name);
78+
fail('Document in a transaction was a ' + doc.toString());
8479
}
8580
});
8681
return docs;
@@ -115,7 +110,7 @@ export class Transaction {
115110
*/
116111
private preconditionForUpdate(key: DocumentKey): Precondition {
117112
const version = this.readVersions.get(key);
118-
if (version && version.isEqual(SnapshotVersion.forDeletedDoc())) {
113+
if (version && version.isEqual(SnapshotVersion.forMissingDoc())) {
119114
// The document doesn't exist, so fail the transaction.
120115
throw new FirestoreError(
121116
Code.FAILED_PRECONDITION,
@@ -145,7 +140,7 @@ export class Transaction {
145140
// ensure that the precondition for the next write will be exists: false.
146141
this.readVersions = this.readVersions.insert(
147142
key,
148-
SnapshotVersion.forDeletedDoc()
143+
SnapshotVersion.forMissingDoc()
149144
);
150145
}
151146

0 commit comments

Comments
 (0)