Skip to content

Commit 1488228

Browse files
committed
Fix for 8474
1 parent 2fec016 commit 1488228

File tree

2 files changed

+109
-16
lines changed

2 files changed

+109
-16
lines changed

packages/firestore/src/remote/watch_change.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ export class WatchChangeAggregator {
291291

292292
/** Keeps track of the documents to update since the last raised snapshot. */
293293
private pendingDocumentUpdates = mutableDocumentMap();
294+
private pendingDocumentUpdatesByTarget = documentTargetMap();
294295

295296
/** A mapping of document keys to their set of target IDs. */
296297
private pendingDocumentTargetMapping = documentTargetMap();
@@ -596,7 +597,7 @@ export class WatchChangeAggregator {
596597
// remove this special logic.
597598
const key = new DocumentKey(targetData.target.path);
598599
if (
599-
this.pendingDocumentUpdates.get(key) === null &&
600+
!this.ensureDocumentUpdateByTarget(key).has(targetId) &&
600601
!this.targetContainsDocument(targetId, key)
601602
) {
602603
this.removeDocumentFromTarget(
@@ -655,6 +656,7 @@ export class WatchChangeAggregator {
655656
);
656657

657658
this.pendingDocumentUpdates = mutableDocumentMap();
659+
this.pendingDocumentUpdatesByTarget = documentTargetMap();
658660
this.pendingDocumentTargetMapping = documentTargetMap();
659661
this.pendingTargetResets = new SortedMap<TargetId, TargetPurpose>(
660662
primitiveComparator
@@ -685,6 +687,11 @@ export class WatchChangeAggregator {
685687
document
686688
);
687689

690+
this.pendingDocumentUpdatesByTarget =
691+
this.pendingDocumentUpdatesByTarget.insert(
692+
document.key,
693+
this.ensureDocumentUpdateByTarget(document.key).add(targetId));
694+
688695
this.pendingDocumentTargetMapping =
689696
this.pendingDocumentTargetMapping.insert(
690697
document.key,
@@ -724,6 +731,12 @@ export class WatchChangeAggregator {
724731
this.ensureDocumentTargetMapping(key).delete(targetId)
725732
);
726733

734+
this.pendingDocumentTargetMapping =
735+
this.pendingDocumentTargetMapping.insert(
736+
key,
737+
this.ensureDocumentTargetMapping(key).add(targetId)
738+
);
739+
727740
if (updatedDocument) {
728741
this.pendingDocumentUpdates = this.pendingDocumentUpdates.insert(
729742
key,
@@ -782,6 +795,18 @@ export class WatchChangeAggregator {
782795
return targetMapping;
783796
}
784797

798+
private ensureDocumentUpdateByTarget(key: DocumentKey): SortedSet<TargetId> {
799+
let targetMapping = this.pendingDocumentUpdatesByTarget.get(key);
800+
801+
if (!targetMapping) {
802+
targetMapping = new SortedSet<TargetId>(primitiveComparator);
803+
this.pendingDocumentUpdatesByTarget =
804+
this.pendingDocumentUpdatesByTarget.insert(key, targetMapping);
805+
}
806+
807+
return targetMapping;
808+
}
809+
785810
/**
786811
* Verifies that the user is still interested in this target (by calling
787812
* `getTargetDataForTarget()`) and that we are not waiting for pending ADDs

packages/firestore/test/unit/specs/collection_spec.test.ts

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
* limitations under the License.
1616
*/
1717

18-
import {doc, filter, query} from '../../util/helpers';
18+
import {newQueryForPath} from "../../../src/core/query";
19+
import {deletedDoc, doc, filter, query} from '../../util/helpers';
1920

2021
import { describeSpec, specTest } from './describe_spec';
2122
import { spec } from './spec_builder';
22-
import {newQueryForPath} from "../../../src/core/query";
2323

2424
describeSpec('Collections:', [], () => {
2525

26-
specTest('8474', ['exclusive', 'durable-persistence'], () => {
26+
specTest('8474', [], () => {
2727

2828
// onSnapshot(fullQuery)
2929
const fullQuery = query('collection');
@@ -90,36 +90,104 @@ describeSpec('Collections:', [], () => {
9090
// All changes are current and we get a global snapshot
9191
.watchSnapshots(1010, [])
9292

93-
// UNEXPECTED docC is still in limbo
94-
.expectLimboDocs(docC.key)
93+
// Now docC is out of limbo
94+
.expectLimboDocs()
95+
.expectEvents(fullQuery, {
96+
fromCache: false,
97+
modified: [docA2],
98+
removed: [docC]
99+
})
100+
// Now getDocs(filterQuery) can be resolved
101+
.expectEvents(filterQuery, {
102+
fromCache: false,
103+
removed: [docC],
104+
added: [docA2]
105+
});
106+
});
107+
specTest('8474-deleted', [], () => {
95108

96-
// This causes snapshots for fullQuery and filterQuery
97-
// to be raised as from cache
109+
// onSnapshot(fullQuery)
110+
const fullQuery = query('collection');
111+
112+
// getDocs(filterQuery)
113+
const filterQuery = query('collection', filter('included', '==', true));
114+
115+
const docA = doc('collection/a', 1000, {key: 'a', included: false});
116+
const docA2 = doc('collection/a', 1007, {key: 'a', included: true});
117+
const docC = doc('collection/c', 1002, {key: 'c', included: true});
118+
const docCDeleted = deletedDoc('collection/c', 1009);
119+
120+
const limboQueryC = newQueryForPath(docC.key.path);
121+
122+
return spec()
123+
// onSnapshot(fullQuery) - fullQuery is listening to documents in the collection for the full test
124+
.userListens(fullQuery)
125+
.watchAcksFull(fullQuery, 1001, docA, docC)
126+
.expectEvents(fullQuery, {
127+
fromCache: false,
128+
added: [docA, docC]
129+
})
130+
131+
// docC was deleted, this puts docC in limbo and causes a snapshot from cache (metadata-only change)
132+
.watchSends({removed: [fullQuery]}, docC)
133+
.watchCurrents(fullQuery, 'resume-token-1002')
134+
.watchSnapshots(1002)
135+
.expectLimboDocs(docC.key)
98136
.expectEvents(fullQuery, {
99137
fromCache: true,
100-
modified: [docA2]
101138
})
102139

103-
// getDocs(filterQuery) will not be resolved with this snapsho
104-
// because it is from cache
140+
// User begins getDocs(filterQuery)
141+
.userListensForGet(filterQuery)
142+
143+
// getDocs(filterQuery) will not resolve on the snapshot from cache
105144
.expectEvents(filterQuery, {
106145
fromCache: true,
107-
added: [docA2]
146+
added: [docC]
108147
})
109148

110-
// 45-seconds later on heartbeat/global-snapshot
111-
.watchSnapshots(1100, [])
149+
// Watch acks limbo and filter queries
150+
.watchAcks(limboQueryC)
151+
.watchAcks(filterQuery)
152+
153+
// Watch responds to limboQueryC - docC was deleted
154+
.watchSends({affects: [limboQueryC]}, docCDeleted)
155+
.watchCurrents(limboQueryC, 'resume-token-1009')
156+
.watchSnapshots(1009, [limboQueryC, fullQuery])
157+
158+
// However, docC is still in limbo because there has not been a global snapshot
159+
.expectLimboDocs(docC.key)
160+
161+
// Rapid events of document update and delete caused by application
162+
.watchSends({removed: [filterQuery]}, docA)
163+
.watchCurrents(filterQuery, 'resume-token-1004')
164+
.watchSends({affects: [filterQuery]}, docC)
165+
.watchCurrents(filterQuery, 'resume-token-1005')
166+
.watchSends({removed: [filterQuery]}, docC)
167+
.watchSends({affects: [filterQuery]}, docA2)
168+
.watchCurrents(filterQuery, 'resume-token-1007')
169+
170+
.watchSnapshots(1010, [fullQuery, limboQueryC])
171+
172+
// All changes are current and we get a global snapshot
173+
.watchSnapshots(1010, [])
174+
112175
// Now docC is out of limbo
113176
.expectLimboDocs()
114177
.expectEvents(fullQuery, {
115178
fromCache: false,
179+
modified: [docA2],
116180
removed: [docC]
117181
})
118182
// Now getDocs(filterQuery) can be resolved
119183
.expectEvents(filterQuery, {
120184
fromCache: false,
121-
removed: [docC]
122-
});
185+
removed: [docC],
186+
added: [docA2]
187+
})
188+
189+
// No more expected events
190+
.watchSnapshots(1100, []);
123191
});
124192

125193
specTest('Events are raised after watch ack', [], () => {

0 commit comments

Comments
 (0)