Firestore get value of Field.increment after update without reading the document data - firebase

Is there a way to retrieve the updated value of a document field updated using firestore.FieldValue.increment without asking for the document?
var countersRef = db.collection('system').doc('counters');
await countersRef.update({
nextOrderCode: firebase.firestore.FieldValue.increment(1)
});
// Get the updated nextOrderCode without asking for the document data?
This is not cost related, but for reliability. For example if I want to create a code that increases for each order, there is no guaranty that if >= 2 orders happen at the same time, will have different codes if I read the incremental value right after the doc update resolves, because if >= 2 writes happen before the first read, then at least 2 docs will have the same code even if the nextOrderCode will have proper advance increment.

Update
Possible now, check other answer.
It's not possible. You will have to read the document after the update if you want to know the value.
If you need to control the value of the number to prevent it from being invalid, you will have to use a transaction instead to make sure that the increment will not write an invalid value. FieldValue.increment() would not be a good choice for this case.

We can do it by using Firestore Transactions, like incremental worked before Field.increment feature:
try {
const orderCodesRef = admin.firestore().doc('system/counters/order/codes');
let orderCode = null;
await admin.firestore().runTransaction(async transaction => {
const orderCodesDoc = await transaction.get(orderCodesRef);
if(!orderCodesDoc.exists) {
throw { reason: 'no-order-codes-doc' };
}
let { next } = orderCodesDoc.data();
orderCode = next++;
transaction.update(orderCodesRef, { next });
});
if(orderCode !== null) {
newOrder.code = orderCode;
const orderRef = await admin.firestore().collection('orders').add(newOrder);
return success({ orderId: orderRef.id });
} else {
return fail('no-order-code-result');
}
} catch(error) {
console.error('commitOrder::ERROR', error);
throw errors.CantWriteDatabase({ error });
}

Had the same question and looks like Firestore Python client
doc_ref.update() returns WriteResult that has transform_results attribute with the updated field value

Related

2 updates within one Firestore transaction

I am new to Firestore transaction, and would like to update a document field based the current data of the document.
My planned transaction is given below:
const cityRef = db.collection('cities').doc('SF');
try {
await db.runTransaction(async (t) => {
const doc = await t.get(cityRef);
let status = doc.data().myStatus;
if (status == "one") {
throw "err";
} else {
// run some function - next status is based on the return
let output = await someFunction();
if (output) {
await t.update(cityRef, { myStatus: "two" });
return output;
} else {
await t.update(cityRef, { myStatus: "three" });
return output;
}
}
});
console.log("transaction successful");
} catch (err) {
console.log("Alreadu updated");
output = "one";
return output;
}
My queries are given below:
As per the documentation I have returned the data after update, however it does not seem to be working as expected.
Can we have 2 updates within one single transaction (both are updating the same field in the firestore)?
Thank you
You make the following clarification in the comments above:
someFunction() does some processing on other firestore
collection/documents (not the one I am updating) and returns either
true or false.
As you read in the doc on Transactions, "Read operations must come before write operations". If you want to read some docs in the transaction, you need to use the get() method of the Transaction, like you did with the first document. You cannot call a function that is using other Firestore methods like the get() method of a DocumentReference.

Mention in Discord

I ran into a strange problem.
When I take the user id from my database and try to mention the user by it, the id digit is rounded, therefore the mention will not work.
async Run(guild_id) {
return new Promise((resolve, reject) => {
this.participantRepository.GetRandomParticipant(guild_id).then(participant => {
if (participant === null) {
reject("u cant do this");
return;
}
this.gamesRepository.SaveGameInformation(guild_id, participant.id);
this.participantRepository.ScoreParticipant(participant.id);
resolve(Misc.GetRandomElement(resultPhrases) + "<#" + participant.discord_user_id+ ">"); //here is problem!
});
});
}
How it looks like in discord
In database everything is fine
see? in database last three numbers is 784, but in discord they become 800
I'm at a loss and can't figure out what can affect this. Help pls
UPD:
GetRandomParticipant() takes 1 random user from a sqlite table
async GetRandomParticipant(guild_id) {
return this.dbAdapter.get(
"SELECT id, discord_user_id, discord_user_name FROM participants WHERE discord_guild_id = ?1 ORDER BY random() LIMIT 1",
{
1: guild_id
}
).then(result => {
if (result === undefined) {
return null;
}
return result;
});
}
UPD2:
I found one interesting detail, if I put the real type in db, then it also starts to output 800 in the database table. Could it be that I have somehow converted to the real type during program execution?
***800.0
I ABSOLUTELY don't understand how it worked, but when I just went through the options, set
varchar(50) data type for the field discord_user_id - everything worked :/

Firebase Functions onUpdate circular problem

I've this situation with a circular function, having trouble finding a solution.
Have a collection where I have a flag that tells if the data has changed. Also want to log the changes.
export async function landWrite(change, context) {
const newDocument = change.after.exists ? change.after.data() : null
const oldDocument = change.before.data()
const log = {
time: FieldValue.serverTimestamp(),
oldDocument: oldDocument,
newDocument: newDocument
}
const landid = change.after.id
const batch = db.batch()
const updated = newDocument && newDocument.updated === oldDocument.updated
if (!updated) {
const landRef = db.collection('land').doc(landid)
batch.update(landRef, {'updated': true })
}
const logRef = db.collection('land').doc(landid).collection('logs').doc()
batch.set(logRef, log)
return batch.commit()
.then(success => {
return true
})
.catch(error => {
return error
})
}
The problem is that this writes the log twice when the UPDATED flag is false.
But also cannot put the log write in the ELSE statement because the flag can already be UPDATED and a new document update be made so a new log has to be written.
Trigger:
import * as landFunctions from './lands/index'
export const landWrite = functions.firestore
.document('land/{land}')
.onWrite((change, context) => {
return landFunctions.landWrite(change, context)
})
If I understand correctly, the problem here is that the updated flag does not specify which event the update is in response to (as you can't really do this with a boolean). In other words - you may have multiple simultaneous "first-stage" writes to lands, and need a way to disambiguate them.
Here are a few possible options that I would try - from (IMHO) worst to best:
The first option is not very elegant to implement
The first and second options both result in your function being
called twice.
The third option means that your function is only
called once, however you must maintain a separate parallel
document/collection alongside lands.
Option 1
Save some sort of unique identifier in the updated field (e.g. a hash of the stringified JSON event - e.g. hash(JSON.stringify(oldDocument)), or a custom event ID [if you have one]).
Option 2
Try checking the updateMask property of the incoming event, and discard any write events that only affect that property.
Option 3
Store your update status in a different document path/collection (e.g. a landUpdates collection at the same level as your lands collection), and configure your Cloud Function to not trigger on that path. (If you need to, you can always create a second Cloud Function that does trigger on the landUpdates path and add either the same logic or different logic to it.)
Hope this helps!
The main problem here is the inability of differentiating changes that are made by this server function or by a client. Whenever you are in this situation, you should try to explicitly differentiate between them. You can even consider having an extra field like fromServer: true that goes with server's updates and helps the server ignore the related trigger. Having said that, I think I have identified the issue and provided a clear solution below.
This line is misleading:
const updated = newDocument && newDocument.updated === oldDocument.updated
It should be named:
const updateStatusDidNotChange = newDocument && newDocument.updated === oldDocument.updated
I understand that you want the updated flag to be managed by this function, not the client. Let me know if this is not the case.
Therefore, the update field is only changed in this function. Since you want to log only changes made outside of this function, you want to log only when updated did not change.
Here's my attempt at fixing your code in this light:
export async function landWrite(change, context) {
const newDocument = change.after.exists ? change.after.data() : null
const oldDocument = change.before.data()
const updateStatusDidNotChange = newDocument && newDocument.updated === oldDocument.updated
if (!updateStatusDidNotChange) return true; //this was a change made by me, ignore
const batch = db.batch()
if (!oldDocument.updated) {
const landid = change.after.id
const landRef = db.collection('land').doc(landid)
batch.update(landRef, {'updated': true })
}
const log = {
time: FieldValue.serverTimestamp(),
oldDocument: oldDocument,
newDocument: newDocument
}
const logRef = db.collection('land').doc(landid).collection('logs').doc()
batch.set(logRef, log)
return batch.commit()
.then(success => {
return true
})
.catch(error => {
return error
})
}
Edit
I had the exact problem and I had to differentiate changes by the server and the client, and ignore the ones that were from the server. I hope you give my suggestion a try.

firebase firestore adding new document inside a transaction - transaction.add is not a function

I was assuming that it was possible to do something like:
transaction.add(collectionRef,{
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
But apparently it is not:
transaction.add is not a function
The above message is displayed inside the chrome console.
I see that we can use the set method of the transaction to add a new document transactionally. see: https://firebase.google.com/docs/firestore/manage-data/transactions
The thing is if I use set instead of add(which is not supported anyways), the id of the document should be created by me manually, firestore won't create it.
see: https://firebase.google.com/docs/firestore/manage-data/add-data
Do you see any downside of this not having an add method that generates the id for you automatically?
For example, is it possible that the id generated by the firestore itself is somehow optimized considering various concerns including performance?
Which library/method do you use to create your document IDs in react-native while using transaction.set?
Thanks
If you want to generate a unique ID for later use in creating a document in a transaction, all you have to do is use CollectionReference.doc() with no parameters to generate a DocumentReference which you can set() later in a transaction.
(What you're proposing in your answer is way more work for the same effect.)
// Create a reference to a document that doesn't exist yet, it has a random id
const newDocRef = db.collection('coll').doc();
// Then, later in a transaction:
transaction.set(newDocRef, { ... });
after some more digging I found in the source code of the firestore itself the below class/method for id generation:
export class AutoId {
static newId(): string {
// Alphanumeric characters
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
}
assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
return autoId;
}
}
see: https://github.com/firebase/firebase-js-sdk/blob/73a586c92afe3f39a844b2be86086fddb6877bb7/packages/firestore/src/util/misc.ts#L36
I extracted the method (except the assert statement) and put it inside a method in my code. Then I used the set method of the transaction as below:
generateFirestoreId(){
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
}
//assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
return autoId;
}
then,
newDocRef = db.collection("PARENTCOLL").doc(PARENTDOCID).collection('SUBCOLL').doc(this.generateFirestoreId());
transaction.set(newDocRef,{
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
Since I am using the same algo for the id generation as the firestore itself I feel better.
Hope this helps/guides someone.
Cheers.
Based on the answer from Doug Stevenson, this is how I got it worked with #angular/fire:
// Create a reference to a document and provide it a random id, e.g. by using uuidv4
const newDocRef = this.db.collection('coll').doc(uuidv4()).ref;
// In the transaction:
transaction.set(newDocRef, { ... });
To complete Stefan's answer. For those using Angularfire, earlier to version 5.2 using CollectionReference.doc() results in an error "CollectionReference.doc() requires its first argument to be of type non-empty string".
This workaround worked for me:
const id = this.afs.createId();
const ref = this.afs.collection(this.collectionRef).doc(id);
transaction.set(ref, { ... });
Credit: https://github.com/angular/angularfire/issues/1974#issuecomment-448449448
I'd like to add an answer solving the id problem. There's no need to generate your own ids. The documentReference is updated after the transaction.set() is called, so in order to access the Firestore's id you need to just do the following:
const docRef = collectionRef.doc();
const result = await transaction.set(docRef, input);
const id = docRef.id;
First of all, firestore transaction object has 4 (get,set,update,delete) methods and doesnt has "add" method. However, the "set" method can be used instead.
import { collection,doc,runTransaction } from "firebase/firestore";
On the other hand documentReference must be created for "set" method.
Steps :
1-) collection method create a collectionReference object.
const collectionRef = collection(FirebaseDb,"[colpath]");
2-) doc method create a documentReference object with unique random id for specified collectionReference.
const documentRef = doc(collectionRef);
3-) add operation can be performed with the transaction set method
try {
await runTransaction(FirebaseDb,async (transaction) => {
await transaction.set(documentRef, {
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
})
} catch (e) {
console.error("Error : ", e);
}

How can I keep track of the last updated at time for a document in Firestore?

I want to have an updatedAt field in my pizza document that should be updated every time there's an update happening in this particular document. I think the best place to handle this is in an onUpdate trigger:
exports.onUpdatePizza = functions.firestore
.document('pizzas/{pizzaId}')
.onUpdate(async (change, context) => {
return change.after.ref.update({ updatedAt: new Date() });
});
However, the above code will fall into an infinite loop. How can I implement this without the undesired side effect?
There is a good explanation there https://medium.com/#krngd2/prevent-infinity-loop-in-firebase-cloud-functions-ea8083afbd35
Add this inside the function:
// simply input data
const after: any = change.after.exists ? change.after.data() : null;
const before: any = change.before.exists ? change.before.data() : null;
const canUpdate = () => {
// if Update Trigger
if (before.updatedAt && after.updatedAt) {
if (after.updatedAt._seconds !== before.updatedAt._seconds) {
return false;
}
}
// if Create Trigger <--- assuming you use it
if (!before.createdAt && after.createdAt) {
return false;
}
return true;
}
if (canUpdate()) {
// update code here
}
Here is my code for universal updates: https://stackoverflow.com/a/60963531/271450
In your function, check to see if the update date is within some threshhold of the current time before updating it again. This will defend against unwanted writes, but the downside is that the update time may lag by that threshold, if there are very frequent updates.

Resources