Skip to content

Commit 9d6917e

Browse files
Remove Mutation tombstones (#1354)
1 parent f4f0520 commit 9d6917e

File tree

3 files changed

+36
-105
lines changed

3 files changed

+36
-105
lines changed

packages/firestore/src/local/memory_mutation_queue.ts

Lines changed: 15 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ export class MemoryMutationQueue implements MutationQueue {
7171
);
7272

7373
const batchIndex = this.indexOfExistingBatchId(batchId, 'acknowledged');
74+
assert(
75+
batchIndex === 0,
76+
'Can only acknowledge the first batch in the mutation queue'
77+
);
7478

7579
// Verify that the batch in the queue is the one to be acknowledged.
7680
const check = this.mutationQueue[batchIndex];
@@ -81,10 +85,6 @@ export class MemoryMutationQueue implements MutationQueue {
8185
', got batch ' +
8286
check.batchId
8387
);
84-
assert(
85-
!check.isTombstone(),
86-
"Can't acknowledge a previously removed batch"
87-
);
8888

8989
this.highestAcknowledgedBatchId = batchId;
9090
this.lastStreamToken = streamToken;
@@ -149,17 +149,15 @@ export class MemoryMutationQueue implements MutationQueue {
149149
): PersistencePromise<DocumentKeySet | null> {
150150
const mutationBatch = this.findMutationBatch(batchId);
151151
assert(mutationBatch != null, 'Failed to find local mutation batch.');
152-
return PersistencePromise.resolve(
153-
!mutationBatch!.isTombstone() ? mutationBatch!.keys() : null
152+
return PersistencePromise.resolve<DocumentKeySet | null>(
153+
mutationBatch!.keys()
154154
);
155155
}
156156

157157
getNextMutationBatchAfterBatchId(
158158
transaction: PersistenceTransaction,
159159
batchId: BatchId
160160
): PersistencePromise<MutationBatch | null> {
161-
const size = this.mutationQueue.length;
162-
163161
// All batches with batchId <= this.highestAcknowledgedBatchId have been
164162
// acknowledged so the first unacknowledged batch after batchID will have a
165163
// batchID larger than both of these values.
@@ -168,24 +166,16 @@ export class MemoryMutationQueue implements MutationQueue {
168166
// The requested batchId may still be out of range so normalize it to the
169167
// start of the queue.
170168
const rawIndex = this.indexOfBatchId(nextBatchId);
171-
let index = rawIndex < 0 ? 0 : rawIndex;
172-
173-
// Finally return the first non-tombstone batch.
174-
for (; index < size; index++) {
175-
const batch = this.mutationQueue[index];
176-
if (!batch.isTombstone()) {
177-
return PersistencePromise.resolve<MutationBatch | null>(batch);
178-
}
179-
}
180-
return PersistencePromise.resolve<MutationBatch | null>(null);
169+
const index = rawIndex < 0 ? 0 : rawIndex;
170+
return PersistencePromise.resolve(
171+
this.mutationQueue.length > index ? this.mutationQueue[index] : null
172+
);
181173
}
182174

183175
getAllMutationBatches(
184176
transaction: PersistenceTransaction
185177
): PersistencePromise<MutationBatch[]> {
186-
return PersistencePromise.resolve(
187-
this.getAllLiveMutationBatchesBeforeIndex(this.mutationQueue.length)
188-
);
178+
return PersistencePromise.resolve(this.mutationQueue.slice());
189179
}
190180

191181
getAllMutationBatchesAffectingDocumentKey(
@@ -298,27 +288,10 @@ export class MemoryMutationQueue implements MutationQueue {
298288
// first entry in the queue.
299289
const batchIndex = this.indexOfExistingBatchId(batch.batchId, 'removed');
300290
assert(
301-
this.mutationQueue[batchIndex].batchId === batch.batchId,
302-
'Removed batches must exist in the queue'
291+
batchIndex === 0,
292+
'Can only remove the first entry of the mutation queue'
303293
);
304-
305-
// Only actually remove batches if removing at the front of the queue.
306-
// Previously rejected batches may have left tombstones in the queue, so
307-
// expand the removal range to include any tombstones.
308-
if (batchIndex === 0) {
309-
let endIndex = 1;
310-
for (; endIndex < this.mutationQueue.length; endIndex++) {
311-
const batch = this.mutationQueue[endIndex];
312-
if (!batch.isTombstone()) {
313-
break;
314-
}
315-
}
316-
this.mutationQueue.splice(0, endIndex);
317-
} else {
318-
this.mutationQueue[batchIndex] = this.mutationQueue[
319-
batchIndex
320-
].toTombstone();
321-
}
294+
this.mutationQueue.shift();
322295

323296
let references = this.batchesByDocumentKey;
324297
return PersistencePromise.forEach(batch.mutations, mutation => {
@@ -358,26 +331,6 @@ export class MemoryMutationQueue implements MutationQueue {
358331
return PersistencePromise.resolve();
359332
}
360333

361-
/**
362-
* A private helper that collects all the mutations batches in the queue up to
363-
* but not including the given endIndex. All tombstones in the queue are
364-
* excluded.
365-
*/
366-
private getAllLiveMutationBatchesBeforeIndex(
367-
endIndex: number
368-
): MutationBatch[] {
369-
const result: MutationBatch[] = [];
370-
371-
for (let i = 0; i < endIndex; i++) {
372-
const batch = this.mutationQueue[i];
373-
if (!batch.isTombstone()) {
374-
result.push(batch);
375-
}
376-
}
377-
378-
return result;
379-
}
380-
381334
/**
382335
* Finds the index of the given batchId in the mutation queue and asserts that
383336
* the resulting index is within the bounds of the queue.
@@ -430,6 +383,6 @@ export class MemoryMutationQueue implements MutationQueue {
430383

431384
const batch = this.mutationQueue[index];
432385
assert(batch.batchId === batchId, 'If found batch must match');
433-
return batch.isTombstone() ? null : batch;
386+
return batch;
434387
}
435388
}

packages/firestore/src/model/mutation_batch.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export class MutationBatch {
3939
public batchId: BatchId,
4040
public localWriteTime: Timestamp,
4141
public mutations: Mutation[]
42-
) {}
42+
) {
43+
assert(mutations.length > 0, 'Cannot create an empty mutation batch');
44+
}
4345

4446
/**
4547
* Applies all the mutations in this MutationBatch to the specified document
@@ -129,23 +131,6 @@ export class MutationBatch {
129131
misc.arrayEquals(this.mutations, other.mutations)
130132
);
131133
}
132-
133-
/**
134-
* Returns true if this mutation batch has already been removed from the
135-
* mutation queue.
136-
*
137-
* Note that not all implementations of the MutationQueue necessarily use
138-
* tombstones as part of their implementation and generally speaking no code
139-
* outside the mutation queues should really care about this.
140-
*/
141-
isTombstone(): boolean {
142-
return this.mutations.length === 0;
143-
}
144-
145-
/** Converts this batch into a tombstone */
146-
toTombstone(): MutationBatch {
147-
return new MutationBatch(this.batchId, this.localWriteTime, []);
148-
}
149134
}
150135

151136
/** The result of applying a mutation batch to the backend. */

packages/firestore/test/unit/local/mutation_queue.test.ts

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -109,26 +109,22 @@ function genericMutationQueueTests(): void {
109109
}
110110

111111
/**
112-
* Removes entries from from the given a batches and returns them.
112+
* Removes the first n entries from the given batches and returns them.
113113
*
114-
* @param holes An array of indexes in the batches array; in increasing order.
115-
* Indexes are relative to the original state of the batches array, not any
116-
* intermediate state that might occur.
114+
* @param n The number of batches to remove.
117115
* @param batches The array to mutate, removing entries from it.
118116
* @return A new array containing all the entries that were removed from
119117
* batches.
120118
*/
121-
async function makeHolesInBatches(
122-
holes: number[],
119+
async function removeFirstBatches(
120+
n: number,
123121
batches: MutationBatch[]
124122
): Promise<MutationBatch[]> {
125123
const removed: MutationBatch[] = [];
126-
for (let i = 0; i < holes.length; i++) {
127-
const index = holes[i] - i;
128-
const batch = batches[index];
124+
for (let i = 0; i < n; i++) {
125+
const batch = batches[0];
129126
await mutationQueue.removeMutationBatch(batch);
130-
131-
batches.splice(index, 1);
127+
batches.shift();
132128
removed.push(batch);
133129
}
134130
return removed;
@@ -145,10 +141,10 @@ function genericMutationQueueTests(): void {
145141
const batch2 = await addMutationBatch();
146142
expect(await mutationQueue.countBatches()).to.equal(2);
147143

148-
await mutationQueue.removeMutationBatch(batch2);
144+
await mutationQueue.removeMutationBatch(batch1);
149145
expect(await mutationQueue.countBatches()).to.equal(1);
150146

151-
await mutationQueue.removeMutationBatch(batch1);
147+
await mutationQueue.removeMutationBatch(batch2);
152148
expect(await mutationQueue.countBatches()).to.equal(0);
153149
});
154150

@@ -168,7 +164,7 @@ function genericMutationQueueTests(): void {
168164
expect(notFound).to.be.null;
169165

170166
const batches = await createBatches(10);
171-
const removed = await makeHolesInBatches([2, 6, 7], batches);
167+
const removed = await removeFirstBatches(3, batches);
172168

173169
// After removing, a batch should not be found
174170
for (const batch of removed) {
@@ -189,10 +185,7 @@ function genericMutationQueueTests(): void {
189185

190186
it('can getNextMutationBatchAfterBatchId()', async () => {
191187
const batches = await createBatches(10);
192-
193-
// This is an array of successors assuming the removals below will happen:
194-
const afters = [batches[3], batches[8], batches[8]];
195-
const removed = await makeHolesInBatches([2, 6, 7], batches);
188+
const removed = await removeFirstBatches(3, batches);
196189

197190
for (let i = 0; i < batches.length - 1; i++) {
198191
const current = batches[i];
@@ -205,7 +198,7 @@ function genericMutationQueueTests(): void {
205198

206199
for (let i = 0; i < removed.length; i++) {
207200
const current = removed[i];
208-
const next = afters[i];
201+
const next = batches[0];
209202
const found = await mutationQueue.getNextMutationBatchAfterBatchId(
210203
current.batchId
211204
);
@@ -372,20 +365,20 @@ function genericMutationQueueTests(): void {
372365
expectEqualArrays(found, batches);
373366
expect(found.length).to.equal(6);
374367

375-
await mutationQueue.removeMutationBatch(batches[batches.length - 1]);
376-
batches.splice(batches.length - 1, 1);
368+
await mutationQueue.removeMutationBatch(batches[0]);
369+
batches.shift();
377370
expect(await mutationQueue.countBatches()).to.equal(5);
378371

379372
found = await mutationQueue.getAllMutationBatches();
380373
expectEqualArrays(found, batches);
381374
expect(found.length).to.equal(5);
382375

383-
await mutationQueue.removeMutationBatch(batches[3]);
384-
batches.splice(3, 1);
376+
await mutationQueue.removeMutationBatch(batches[0]);
377+
batches.shift();
385378
expect(await mutationQueue.countBatches()).to.equal(4);
386379

387-
await mutationQueue.removeMutationBatch(batches[1]);
388-
batches.splice(1, 1);
380+
await mutationQueue.removeMutationBatch(batches[0]);
381+
batches.shift();
389382
expect(await mutationQueue.countBatches()).to.equal(3);
390383

391384
found = await mutationQueue.getAllMutationBatches();

0 commit comments

Comments
 (0)