Skip to content

Make Documents mutable #4548

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 16 commits into from
Mar 20, 2021
Merged

Make Documents mutable #4548

merged 16 commits into from
Mar 20, 2021

Conversation

schmidt-sebastian
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian commented Feb 26, 2021

This is a port of firebase/firebase-android-sdk#2383 and firebase/firebase-android-sdk#2468. The goal is to reduce the number of object creations, so that we can have clear object ownership in the C++ clients. With this change, the documents are owned either by the RemoteDocumentCache or by the View that contains them. Mutations mutate these existing documents and do not create copies.

The existing document hierarchy is removed and replaced by a single MutableDocument type that allows documents to transition between four states:

  • INVALID, which is a document we only created so we can later apply a mutation on it. This allows documents to be created by SetMutations
  • FOUND, corresponds to the old Document
  • NO_DOCUMENT, corresponds to the old NoDocument
  • UNKNOWN_DOCUMENT, corresponds to the old UnknownDocument

I kept Document, but it is now an interface that makes sure that we no longer modify the documents once we store them in View. MutableDocuments are produces by the RemoteDocumentCache and via RemoteEvents. We then apply all mutations and from then onwards treat them as Documents.

ObjectValue and ObjectValueBuilder are also merged to reduce immutable object creation.

There should be no functional changes. firebase/firebase-android-sdk#2383 contains a bunch of discussions that might help with this review.

@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2021

⚠️ No Changeset found

Latest commit: 723b942

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/mutabledocuments branch from 3993f15 to abda8e9 Compare February 26, 2021 20:13
@google-oss-bot
Copy link
Contributor

google-oss-bot commented Feb 26, 2021

Binary Size Report

Affected SDKs

  • @firebase/firestore

    Type Base (5019599) Head (ca59ec1) Diff
    browser 259 kB 258 kB -863 B (-0.3%)
    esm2017 201 kB 200 kB -941 B (-0.5%)
    main 530 kB 529 kB -832 B (-0.2%)
    module 259 kB 258 kB -863 B (-0.3%)
    react-native 201 kB 201 kB -941 B (-0.5%)
  • @firebase/firestore-exp

    Type Base (5019599) Head (ca59ec1) Diff
    browser 199 kB 198 kB -938 B (-0.5%)
    main 505 kB 504 kB -832 B (-0.2%)
    module 199 kB 198 kB -938 B (-0.5%)
    react-native 200 kB 199 kB -937 B (-0.5%)
  • @firebase/firestore-lite

    Type Base (5019599) Head (ca59ec1) Diff
    browser 64.9 kB 65.0 kB +131 B (+0.2%)
    main 143 kB 144 kB +1.17 kB (+0.8%)
    module 64.9 kB 65.0 kB +131 B (+0.2%)
    react-native 65.1 kB 65.2 kB +131 B (+0.2%)
  • @firebase/firestore/bundle

    Type Base (5019599) Head (ca59ec1) Diff
    browser 266 kB 265 kB -862 B (-0.3%)
    esm2017 155 kB 154 kB -855 B (-0.6%)
    main 526 kB 525 kB -832 B (-0.2%)
    module 266 kB 265 kB -862 B (-0.3%)
    react-native 155 kB 154 kB -855 B (-0.6%)
  • @firebase/firestore/memory

    Type Base (5019599) Head (ca59ec1) Diff
    browser 196 kB 195 kB -782 B (-0.4%)
    esm2017 152 kB 151 kB -852 B (-0.6%)
    main 324 kB 323 kB -706 B (-0.2%)
    module 196 kB 195 kB -782 B (-0.4%)
    react-native 152 kB 151 kB -852 B (-0.6%)
  • @firebase/firestore/memory-bundle

    Type Base (5019599) Head (ca59ec1) Diff
    browser 204 kB 204 kB -787 B (-0.4%)
    esm2017 155 kB 154 kB -855 B (-0.6%)
    main 321 kB 320 kB -706 B (-0.2%)
    module 204 kB 204 kB -787 B (-0.4%)
    react-native 155 kB 154 kB -855 B (-0.6%)
  • firebase

    Type Base (5019599) Head (ca59ec1) Diff
    firebase-firestore.js 303 kB 303 kB -740 B (-0.2%)
    firebase-firestore.memory.js 243 kB 242 kB -687 B (-0.3%)
    firebase.js 845 kB 844 kB -691 B (-0.1%)

Test Logs

@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/mutabledocuments branch from 306e59a to e83e967 Compare February 27, 2021 03:14
@schmidt-sebastian schmidt-sebastian force-pushed the mrschmidt/mutabledocuments branch from 10587ca to ead9034 Compare March 1, 2021 17:51
@schmidt-sebastian schmidt-sebastian changed the title Mrschmidt/mutabledocuments Make Documents mutable Mar 1, 2021
@schmidt-sebastian schmidt-sebastian marked this pull request as ready for review March 1, 2021 17:57
Comment on lines -160 to -170
let newDoc = newMaybeDoc instanceof Document ? newMaybeDoc : null;
if (newDoc) {
debugAssert(
key.isEqual(newDoc.key),
'Mismatching keys found in document changes: ' +
key +
' != ' +
newDoc.key
);
newDoc = queryMatches(this.query, newDoc) ? newDoc : null;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the only actual change here.

const builder = new ObjectValueBuilder();
const object = builder.build();
expect(object.isEqual(ObjectValue.empty())).to.be.true;
describe('MutableObjectValue', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to rename this file to mutable_object_value.test.ts but GitHub then sees it as a new file, so I will do this after the first review.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I will handle this when I port firebase/firebase-android-sdk#2494

@firebase firebase deleted a comment from google-oss-bot Mar 1, 2021
@firebase firebase deleted a comment from google-oss-bot Mar 2, 2021
Copy link

@thebrianchen thebrianchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly documentation nits with a few questions. Thanks for taking the time to go over this with me!

@@ -140,7 +139,7 @@ export function getDocFromCache<T>(
reference._key,
doc,
new SnapshotMetadata(
doc instanceof Document ? doc.hasLocalMutations : false,
doc !== null && doc.hasLocalMutations,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we have to check the Document's state is FOUND_DOCUMENT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only use Document or null in our API layer. That's why I kept this pattern - anything that is not a Document is returned as null once it leaves FirestoreClient.

txn,
changedDocs
);
return changedDocs;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have to chain changedDocs off of applyLocalViewToDocuments since it's a promise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. Uhm. Yes. That's a pretty bad bug. Thanks for catching.

)
);
);
return changedDocs;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here as above. Do we have to chain it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -48,7 +44,7 @@ export interface RemoteDocumentCache {
getEntry(
transaction: PersistenceTransaction,
documentKey: DocumentKey
): PersistencePromise<MaybeDocument | null>;
): PersistencePromise<MutableDocument>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update jsdoc since we don't have MaybeDocument anymore. Also update the RemoteDocumentCache interface description as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here and on top.

@@ -441,13 +443,13 @@ function fromMissing(
);
const key = fromName(serializer, result.missing);
const version = fromVersion(result.readTime);
return new NoDocument(key, version);
return MutableDocument.newNoDocument(key, version);
}

export function fromMaybeDocument(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update function name since we don't use maybeDocument anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to fromBatchGetDocumentsResponse

this.addDocumentToTarget(targetId, docChange.newDoc);
} else if (docChange.newDoc instanceof NoDocument) {
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: How are we sure that this has type NO_DOCUMENT in order to remove the else if?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the Watch code. Watch can either send as a document or a delete. Unknown documents are only used with latency compensation, and invalid documents are only used for document lookups for keys that are not cached.

@@ -708,7 +713,7 @@ export function documentSet(...args: unknown[]): DocumentSet {
}
for (const doc of args) {
debugAssert(
doc instanceof Document,
doc instanceof MutableDocument,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: It looks a bit weird that this checks for MutableDocument, but the input parameter is Document[]. docSet.add() also takes in Document as a parameter. Change parameter signature to MutableDocument, or assertion to Document?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interfaces can't be used in instanceof checks.

for (const docOrKey of docsOrKeys) {
if (docOrKey instanceof Document) {
if (docOrKey instanceof MutableDocument) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: Same question as comment below, should this use Document instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interfaces can't be used in instanceof checks.

return this.partialValue;
}

private extractNestedValue(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: this method can be static.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (FWIW I just got the same comment on the iOS PR)

Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough review! Someone deserves a vacation!

Copy link

@thebrianchen thebrianchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@@ -134,7 +133,7 @@ export function getDoc<T>(
reference.firestore,
userDataWriter,
reference._key,
maybeDocument instanceof Document ? maybeDocument : null,
maybeDocument.isFoundDocument() ? maybeDocument : null,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rename maybeDocument variable since that concept no longer exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -63,6 +58,7 @@ import {
DbUnknownDocument
} from './indexeddb_schema';
import { TargetData, TargetPurpose } from './target_data';
import { Timestamp } from '../exp/timestamp';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix lint

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was already fixed.

export interface DocumentOptions {
hasLocalMutations?: boolean;
hasCommittedMutations?: boolean;
const enum DocumentType {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: add jsdoc description for `DocumentType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* returned.
* Applies this mutation to the given document for the purposes of computing a
* new remote document. If the input document doesn't match the expected state
* (e.g. it is invalid or outdated), the document state may transition to

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/state/type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@firebase firebase deleted a comment from google-oss-bot Mar 20, 2021
@schmidt-sebastian schmidt-sebastian merged commit 8c56c25 into master Mar 20, 2021
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/mutabledocuments branch March 20, 2021 01:22
@firebase firebase locked and limited conversation to collaborators Apr 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants