Reputation: 315
I have a subcollection named requests
inside my root collection tradeRequests
I want to query my requests
subcollection using transactions in firebase
This code below will fail because Transaction.get() requires its first argument to be a DocumentReference
const getOne = (itemID) => db.runTransaction(async (transaction) => {
const tradeRequestItemRef = tradeRequestsRef.doc(itemID);
const requestsRef = tradeRequestItemRef.collection('requests');
const rawTradeRequestItem = await transaction.get(tradeRequestItemRef);
if (!rawTradeRequestItem) return null;
// something like this ???
// But this line will break everything
// Error: Transaction.get() requires its first argument to be a DocumentReference
const rawRequests = transaction.get(requestsRef);
return {
...normalizeData(rawTradeRequestItem),
requests: normalizeData(rawRequests),
};
});
Upvotes: 2
Views: 382
Reputation: 83103
As explained in the doc on "Transaction serializability and isolation" for Firestore, the mobile/web SDKs (iOS, Android, Web, C++) use optimistic concurrency controls.
The doc explains that:
The transaction keeps track of all the documents you read inside the transaction ... and completes its write operations only if none of those documents changed during the transaction's execution. If any document did change, the transaction handler retries the transaction. If the transaction can't get a clean result after a few retries, the transaction fails due to data contention.
So the risk of having too many data contention failures when keeping track of all the documents returned by a Query is considered too high, therefore the mobile/web SDKs do not give the possibility to execute a Query in a Transaction.
On the other hand, the Admin SDKs use pessimistic concurrency controls, therefore you can execute a query within a Transaction and place a pessimistic lock on all the documents returned by the Query. So one solution is to use a Callable Cloud Function (which uses the Admin SDK) and call this Cloud Function from your Client.
More concretely, you would use, in your Cloud Function, the get()
method from the Node.js Server SDK for Google Cloud Firestore, which can be passed a Query
and will hold a pessimistic lock on all returned documents.
Upvotes: 1
Reputation: 317467
In the web and mobile client SDKs, you can't transact on the results of a query within that same transaction. You can only get() each document individually using a DocumentReference. So, what you can do instead is perform the query first, then get()
each individual document reference from that query in the transaction to make sure it is being operated on atomically.
// query for requests before the transaction
const requests = await db.collection("requests").get()
await db.runTransaction(async transaction => {
// get each document individually again in the transaction
const requestsInTransaction = []
for (const doc of requests.docs) {
const requestInTransaction = await transction.get(doc.ref)
requestsInTransaction.push(requestInTransaction)
}
// Work with each document as needed...
return whatever_you_want
})
Note that if the results of the original query change during the transaction, any new or removed documents will not be seen by the transaction.
Upvotes: 1