Skip to content

Commit d66d66e

Browse files
Adding Transactions.getAll() (#125)
1 parent a4c5508 commit d66d66e

File tree

6 files changed

+187
-49
lines changed

6 files changed

+187
-49
lines changed

src/index.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -566,28 +566,25 @@ class Firestore extends commonGrpc.Service {
566566
* @private
567567
* @param {Array.<DocumentReference>} docRefs - The documents
568568
* to receive.
569-
* @param {object=} readOptions - The options to use for this request.
570-
* @param {bytes|null} readOptions.transactionId - The transaction ID to use
569+
* @param {bytes=} transactionId - transactionId - The transaction ID to use
571570
* for this read.
572-
* @returns {Array.<DocumentSnapshot>} A Promise that contains an array with
571+
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that contains an array with
573572
* the resulting documents.
574573
*/
575-
getAll_(docRefs, readOptions) {
574+
getAll_(docRefs, transactionId) {
576575
const requestedDocuments = new Set();
577576
const retrievedDocuments = new Map();
578577

579578
let request = {
580579
database: this.formattedName,
580+
transaction: transactionId,
581581
};
582582

583583
for (let docRef of docRefs) {
584584
requestedDocuments.add(docRef.formattedName);
585585
}
586-
request.documents = Array.from(requestedDocuments);
587586

588-
if (readOptions && readOptions.transactionId) {
589-
request.transaction = readOptions.transactionId;
590-
}
587+
request.documents = Array.from(requestedDocuments);
591588

592589
let self = this;
593590

src/transaction.js

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
const is = require('is');
2020

21-
const validate = require('./validate')();
21+
/*! Injected. */
22+
let validate;
2223

2324
/*!
2425
* Injected.
@@ -34,6 +35,13 @@ let DocumentReference;
3435
*/
3536
let Query;
3637

38+
/*!
39+
* Error message for transactional reads that were executed after performing
40+
* writes.
41+
*/
42+
const WRITE_AFTER_READ_ERR_MSG =
43+
'Firestore transactions require all reads to be executed before all writes.';
44+
3745
/**
3846
* A reference to a transaction.
3947
*
@@ -82,15 +90,12 @@ class Transaction {
8290
*/
8391
get(refOrQuery) {
8492
if (!this._writeBatch.isEmpty) {
85-
throw new Error(
86-
'Firestore transactions require all reads to be ' +
87-
'executed before all writes.'
88-
);
93+
throw new Error(WRITE_AFTER_READ_ERR_MSG);
8994
}
9095

9196
if (is.instance(refOrQuery, DocumentReference)) {
9297
return this._firestore
93-
.getAll_([refOrQuery], {transactionId: this._transactionId})
98+
.getAll_([refOrQuery], this._transactionId)
9499
.then(res => {
95100
return Promise.resolve(res[0]);
96101
});
@@ -103,6 +108,44 @@ class Transaction {
103108
throw new Error('Argument "refOrQuery" must be a DocumentRef or a Query.');
104109
}
105110

111+
/**
112+
* Retrieves multiple documents from Firestore. Holds a pessimistic lock on
113+
* all returned documents.
114+
*
115+
* @param {...DocumentReference} documents - The document references
116+
* to receive.
117+
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
118+
* contains an array with the resulting document snapshots.
119+
*
120+
* @example
121+
* let firstDoc = firestore.doc('col/doc1');
122+
* let secondDoc = firestore.doc('col/doc2');
123+
* let resultDoc = firestore.doc('col/doc2');
124+
*
125+
* firestore.runTransaction(transaction => {
126+
* return transaction.getAll(firstDoc, secondDoc).then(docs => {
127+
* transaction.set(resultDoc, {
128+
* sum: docs[1].get('count') + docs[2].get('count')
129+
* });
130+
* });
131+
* });
132+
*/
133+
getAll(documents) {
134+
if (!this._writeBatch.isEmpty) {
135+
throw new Error(WRITE_AFTER_READ_ERR_MSG);
136+
}
137+
138+
documents = is.array(arguments[0])
139+
? arguments[0].slice()
140+
: Array.prototype.slice.call(arguments);
141+
142+
for (let i = 0; i < documents.length; ++i) {
143+
validate.isDocumentReference(i, documents[i]);
144+
}
145+
146+
return this._firestore.getAll_(documents, this._transactionId);
147+
}
148+
106149
/**
107150
* Create the document referred to by the provided
108151
* [DocumentReference]{@link DocumentReference}. The operation will
@@ -297,11 +340,8 @@ module.exports = FirestoreType => {
297340
let reference = require('./reference')(FirestoreType);
298341
DocumentReference = reference.DocumentReference;
299342
Query = reference.Query;
300-
let document = require('./document')(DocumentReference);
301-
require('./validate')({
302-
Document: document.validateDocumentData,
343+
validate = require('./validate')({
303344
DocumentReference: reference.validateDocumentReference,
304-
Precondition: document.validatePrecondition,
305345
});
306346
return Transaction;
307347
};

system-test/firestore.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,22 @@ describe('Transaction class', function() {
13021302
});
13031303
});
13041304

1305+
it('has getAll() method', function() {
1306+
let ref1 = randomCol.doc('doc1');
1307+
let ref2 = randomCol.doc('doc2');
1308+
return Promise.all([ref1.set({}), ref2.set({})])
1309+
.then(() => {
1310+
return firestore.runTransaction(updateFunction => {
1311+
return updateFunction.getAll(ref1, ref2).then(docs => {
1312+
return Promise.resolve(docs.length);
1313+
});
1314+
});
1315+
})
1316+
.then(res => {
1317+
assert.equal(2, res);
1318+
});
1319+
});
1320+
13051321
it('has get() with query', function() {
13061322
let ref = randomCol.doc('doc');
13071323
let query = randomCol.where('foo', '==', 'bar');

test/transaction.js

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ const Firestore = require('../');
2424
let firestore;
2525

2626
const DATABASE_ROOT = 'projects/test-project/databases/(default)';
27-
const DOCUMENT_NAME = `${DATABASE_ROOT}/documents/collectionId/documentId`;
27+
const COLLECTION_ROOT = `${DATABASE_ROOT}/documents/collectionId`;
28+
const DOCUMENT_NAME = `${COLLECTION_ROOT}/documentId`;
2829

2930
// Change the argument to 'console.log' to enable debug output.
3031
Firestore.setLogFunction(() => {});
@@ -134,6 +135,42 @@ function getDocument(transaction) {
134135
};
135136
}
136137

138+
function getAll(docs) {
139+
let request = {
140+
database: DATABASE_ROOT,
141+
documents: [],
142+
transaction: 'foo',
143+
};
144+
145+
let stream = through.obj();
146+
147+
for (const doc of docs) {
148+
const name = `${COLLECTION_ROOT}/${doc}`;
149+
request.documents.push(name);
150+
151+
setImmediate(() => {
152+
stream.push({
153+
found: {
154+
name: name,
155+
createTime: {seconds: 1, nanos: 2},
156+
updateTime: {seconds: 3, nanos: 4},
157+
},
158+
readTime: {seconds: 5, nanos: 6},
159+
});
160+
});
161+
}
162+
163+
setImmediate(function() {
164+
stream.push(null);
165+
});
166+
167+
return {
168+
type: 'getDocument',
169+
request: request,
170+
stream: stream,
171+
};
172+
}
173+
137174
function query(transaction) {
138175
let request = {
139176
parent: DATABASE_ROOT,
@@ -424,8 +461,8 @@ describe('transaction operations', function() {
424461

425462
it('support get with document ref', function() {
426463
return runTransaction(
427-
updateFunction => {
428-
return updateFunction.get(docRef).then(doc => {
464+
transaction => {
465+
return transaction.get(docRef).then(doc => {
429466
assert.equal(doc.id, 'documentId');
430467
});
431468
},
@@ -437,13 +474,13 @@ describe('transaction operations', function() {
437474

438475
it('requires a query or document for get', function() {
439476
return runTransaction(
440-
updateFunction => {
477+
transaction => {
441478
assert.throws(() => {
442-
updateFunction.get();
479+
transaction.get();
443480
}, /Argument "refOrQuery" must be a DocumentRef or a Query\./);
444481

445482
assert.throws(() => {
446-
updateFunction.get('foo');
483+
transaction.get('foo');
447484
}, /Argument "refOrQuery" must be a DocumentRef or a Query\./);
448485

449486
return Promise.resolve();
@@ -454,9 +491,9 @@ describe('transaction operations', function() {
454491
});
455492

456493
it('enforce that gets come before writes', function() {
457-
return runTransaction(updateFunction => {
458-
updateFunction.set(docRef, {foo: 'bar'});
459-
return updateFunction.get(docRef);
494+
return runTransaction(transaction => {
495+
transaction.set(docRef, {foo: 'bar'});
496+
return transaction.get(docRef);
460497
}, begin())
461498
.then(() => {
462499
throw new Error('Unexpected success in Promise');
@@ -472,9 +509,9 @@ describe('transaction operations', function() {
472509

473510
it('support get with query', function() {
474511
return runTransaction(
475-
updateFunction => {
512+
transaction => {
476513
let query = firestore.collection('col').where('foo', '==', 'bar');
477-
return updateFunction.get(query).then(results => {
514+
return transaction.get(query).then(results => {
478515
assert.equal(results.docs[0].id, 'documentId');
479516
});
480517
},
@@ -484,6 +521,41 @@ describe('transaction operations', function() {
484521
);
485522
});
486523

524+
it('support getAll', function() {
525+
const firstDoc = firestore.doc('collectionId/firstDocument');
526+
const secondDoc = firestore.doc('collectionId/secondDocument');
527+
528+
return runTransaction(
529+
transaction => {
530+
return transaction.getAll(firstDoc, secondDoc).then(docs => {
531+
assert.equal(docs.length, 2);
532+
assert.equal(docs[0].id, 'firstDocument');
533+
assert.equal(docs[1].id, 'secondDocument');
534+
});
535+
},
536+
begin(),
537+
getAll(['firstDocument', 'secondDocument']),
538+
commit()
539+
);
540+
});
541+
542+
it('enforce that getAll come before writes', function() {
543+
return runTransaction(transaction => {
544+
transaction.set(docRef, {foo: 'bar'});
545+
return transaction.getAll(docRef);
546+
}, begin())
547+
.then(() => {
548+
throw new Error('Unexpected success in Promise');
549+
})
550+
.catch(err => {
551+
assert.equal(
552+
err.message,
553+
'Firestore transactions require all reads to ' +
554+
'be executed before all writes.'
555+
);
556+
});
557+
});
558+
487559
it('support create', function() {
488560
let create = {
489561
currentDocument: {
@@ -496,8 +568,8 @@ describe('transaction operations', function() {
496568
};
497569

498570
return runTransaction(
499-
updateFunction => {
500-
updateFunction.create(docRef, {});
571+
transaction => {
572+
transaction.create(docRef, {});
501573
return Promise.resolve();
502574
},
503575
begin(),
@@ -532,10 +604,10 @@ describe('transaction operations', function() {
532604
};
533605

534606
return runTransaction(
535-
updateFunction => {
536-
updateFunction.update(docRef, {'a.b': 'c'});
537-
updateFunction.update(docRef, 'a.b', 'c');
538-
updateFunction.update(docRef, new Firestore.FieldPath('a', 'b'), 'c');
607+
transaction => {
608+
transaction.update(docRef, {'a.b': 'c'});
609+
transaction.update(docRef, 'a.b', 'c');
610+
transaction.update(docRef, new Firestore.FieldPath('a', 'b'), 'c');
539611
return Promise.resolve();
540612
},
541613
begin(),
@@ -557,8 +629,8 @@ describe('transaction operations', function() {
557629
};
558630

559631
return runTransaction(
560-
updateFunction => {
561-
updateFunction.set(docRef, {'a.b': 'c'});
632+
transaction => {
633+
transaction.set(docRef, {'a.b': 'c'});
562634
return Promise.resolve();
563635
},
564636
begin(),
@@ -583,8 +655,8 @@ describe('transaction operations', function() {
583655
};
584656

585657
return runTransaction(
586-
updateFunction => {
587-
updateFunction.set(docRef, {'a.b': 'c'}, {merge: true});
658+
transaction => {
659+
transaction.set(docRef, {'a.b': 'c'}, {merge: true});
588660
return Promise.resolve();
589661
},
590662
begin(),
@@ -598,8 +670,8 @@ describe('transaction operations', function() {
598670
};
599671

600672
return runTransaction(
601-
updateFunction => {
602-
updateFunction.delete(docRef);
673+
transaction => {
674+
transaction.delete(docRef);
603675
return Promise.resolve();
604676
},
605677
begin(),
@@ -620,8 +692,8 @@ describe('transaction operations', function() {
620692
};
621693

622694
return runTransaction(
623-
updateFunction => {
624-
updateFunction.delete(docRef).set(docRef, {});
695+
transaction => {
696+
transaction.delete(docRef).set(docRef, {});
625697
return Promise.resolve();
626698
},
627699
begin(),

test/typescript.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ xdescribe('firestore.d.ts', function() {
6767

6868
it('has typings for Transaction', () => {
6969
return firestore.runTransaction((transaction: Transaction) => {
70+
transaction.get(collRef).then((snapshot: QuerySnapshot) => {
71+
});
7072
transaction.get(docRef).then((doc: DocumentSnapshot) => {
7173
});
72-
transaction.get(collRef).then((snapshot: QuerySnapshot) => {
74+
transaction.getAll(docRef, docRef).then((docs: DocumentSnapshot[]) => {
7375
});
7476
transaction = transaction.create(docRef, documentData);
7577
transaction = transaction.set(docRef, documentData);

0 commit comments

Comments
 (0)