Skip to content

Commit 5748169

Browse files
Improved handling of reads after writes in transactions if the get is… (#7715)
* Improved handling of reads after writes in transactions if the get is not awaited or returned.
1 parent bcf70a8 commit 5748169

File tree

2 files changed

+46
-9
lines changed

2 files changed

+46
-9
lines changed

packages/firestore/src/core/transaction.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class Transaction {
4848
* A deferred usage error that occurred previously in this transaction that
4949
* will cause the transaction to fail once it actually commits.
5050
*/
51-
private lastWriteError: FirestoreError | null = null;
51+
private lastTransactionError: FirestoreError | null = null;
5252

5353
/**
5454
* Set of documents that have been written in the transaction.
@@ -64,10 +64,11 @@ export class Transaction {
6464
this.ensureCommitNotCalled();
6565

6666
if (this.mutations.length > 0) {
67-
throw new FirestoreError(
67+
this.lastTransactionError = new FirestoreError(
6868
Code.INVALID_ARGUMENT,
6969
'Firestore transactions require all reads to be executed before all writes.'
7070
);
71+
throw this.lastTransactionError;
7172
}
7273
const docs = await invokeBatchGetDocumentsRpc(this.datastore, keys);
7374
docs.forEach(doc => this.recordVersion(doc));
@@ -83,7 +84,7 @@ export class Transaction {
8384
try {
8485
this.write(data.toMutation(key, this.preconditionForUpdate(key)));
8586
} catch (e) {
86-
this.lastWriteError = e as FirestoreError | null;
87+
this.lastTransactionError = e as FirestoreError | null;
8788
}
8889
this.writtenDocs.add(key.toString());
8990
}
@@ -96,8 +97,8 @@ export class Transaction {
9697
async commit(): Promise<void> {
9798
this.ensureCommitNotCalled();
9899

99-
if (this.lastWriteError) {
100-
throw this.lastWriteError;
100+
if (this.lastTransactionError) {
101+
throw this.lastTransactionError;
101102
}
102103
const unwritten = this.readVersions;
103104
// For each mutation, note that the doc was written.

packages/firestore/test/integration/api/transactions.test.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -506,10 +506,11 @@ apiDescribe('Database transactions', persistence => {
506506
});
507507
});
508508

509-
it('cannot read after writing', () => {
510-
return withTestDb(persistence, db => {
511-
return runTransaction(db, transaction => {
512-
const docRef = doc(collection(db, 'anything'));
509+
it('cannot read after writing and does not commit', () => {
510+
return withTestDb(persistence, async db => {
511+
const docRef = doc(collection(db, '00000-anything'));
512+
await setDoc(docRef, { foo: 'baz' });
513+
await runTransaction(db, async transaction => {
513514
transaction.set(docRef, { foo: 'bar' });
514515
return transaction.get(docRef);
515516
})
@@ -523,6 +524,41 @@ apiDescribe('Database transactions', persistence => {
523524
'Firestore transactions require all reads to be executed'
524525
);
525526
});
527+
528+
const postSnap = await getDoc(docRef);
529+
expect(postSnap.get('foo')).to.equal('baz');
530+
});
531+
});
532+
533+
it('cannot read after writing and does not commit, even if the user transaction does not bubble up the error', () => {
534+
return withTestDb(persistence, async db => {
535+
const docRef = doc(collection(db, '00000-anything'));
536+
await setDoc(docRef, { foo: 'baz' });
537+
await runTransaction(db, async transaction => {
538+
transaction.set(docRef, { foo: 'bar' });
539+
540+
// The following statement `transaction.get(...)` is problematic because
541+
// it occurs after `transaction.set(...)`. In previous versions of the
542+
// SDK this un-awaited `transaction.get(...)` failed but the transaction
543+
// still committed successfully. This regression test ensures that the
544+
// commit will fail even if the code does not await
545+
// `transaction.get(...)`.
546+
// eslint-disable-next-line
547+
transaction.get(docRef);
548+
})
549+
.then(() => {
550+
expect.fail('transaction should fail');
551+
})
552+
.catch((err: FirestoreError) => {
553+
expect(err).to.exist;
554+
expect(err.code).to.equal('invalid-argument');
555+
expect(err.message).to.contain(
556+
'Firestore transactions require all reads to be executed'
557+
);
558+
});
559+
560+
const postSnap = await getDoc(docRef);
561+
expect(postSnap.get('foo')).to.equal('baz');
526562
});
527563
});
528564

0 commit comments

Comments
 (0)