I'm creating a firestore document that's not at the root level of an existing collection but somewhat deeper:
const response = await addDoc(
collection(
firestore,
'stats', //collection
'dayStats', //document
'years', //collection
'2023', //document
'january', //collection
),
statData
);
At the time of creation, the entire collection structure does not yet exist, but only a part of it (stats/dayStats/years).
What ends up happening is that stat document is successfully created in the correct place, but not the intermediary documents. They are in italics and there's a warning for them saying "This document does not exist, it will not appear in queries or snapshots".
I suppose this means that I need to be explicit about force creating these intermediary documents? Is there some kind of a config I can pass to the function, that would just create the intermediary documents in the path, if they to not yet exist? Or do I need to do this by hand each time I'm creating a document, that has a ancestor document that might not exist?
The intermediary documents are in italics and there's a warning for them saying "This document does not exist, it will not appear in queries or snapshots". I suppose this means that I need to be explicit about force creating
these intermediary documents?
Indeed the intermediary parent documents don't exist and you need to create them.
Is there some kind of a config I can pass to the function, that would
just create the intermediary documents in the path, if they to not yet
exist?
No
Do I need to do this by hand each time I'm creating a document, that
has a ancestor document that might not exist?
Yes you need, in your code, to create the parent documents. The best is to use a Batched Write which ensures that both the parent and children docs are created via an atomic operation.
For example:
import { writeBatch, doc } from "firebase/firestore";
const batch = writeBatch(firestore);
const ref1 = doc(firestore, "stats", "dayStats");
batch.set(ref1, {...});
const ref2 = doc(ref1, "years", "2023");
batch.set(ref2, {...});
const ref3 = doc(ref2, "january"); // DocID will be auto-generated
batch.set(ref3, statData);
await batch.commit();
Related
I Wanted To Ask If It Is Possible To Make A New Document With A UID If It DOESN'T Exist But if it exists NOT To Do Anything (and if possible return an error) In Firestore. (In Modular Javascript)
And If It Is Possible How?
Note: I Already Read This Question:StackOverFlow 46888701 But It Doesn't Fit My Requirements because after creating the document I want to be able to update it too.
Edit: I Wanted To Know Without Using getDoc because when i use it acts like a read and i don't want to spend lots of my no of reads from my limit.
You should first try to get the document and check if it exists then proceed to your document set/update. See sample code below:
import { doc, getDoc } from "firebase/firestore";
const docRef = doc(db, "<collection>", "<UID>");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("Document exist!");
// Throws an error.
throw new Error('Document Exist!');
} else {
await setDoc(docRef, {
// Document Data
});
}
For more relevant information, check out these documentations:
Get a document
Update a document
Set a document
Edit:
If you don' t want to use getDoc then you have the option to use updateDoc, it will produce an error but you can still execute a setDoc method on the catch method. On this approach, you're doing a fail-safe practice that you're responding in the event of failure. See code below:
const docRef = doc(db, "<collection>", "<UID>");
// Produces error log if no document to update
updateDoc(docRef, {
// document data
})
.catch((error) => {
// console.log(error);
setDoc(docRef, {
// document data
});
});
According to the documentation, an update is just a write operation:
Charges for writes and deletes are straightforward. For writes, each set or update operation counts a single write.
We have established that an update is just a write operation (there's no reading involved). A write is a change in a document, since you're not changing anything because the document didn't exist then you won't be charged at all.
In web version 9, the function that can help you create a document is named setDoc(), which creates or overwrites a document at a specific document reference.
How to create a document if the document doesn't exist or else don't do anything?
If you want to achieve that, you have to check if the document already exists. If it doesn't exist, create it using setDoc(), otherwise, take no action, but do not use the updateDoc() function in this case.
Remember that the updateDoc() function helps only when you want to update some fields of a document without overwriting the entire document. If the document doesn't exist, the update operation will fail.
Edit:
According to your edited question, please note that there is no way you can know if a document exists, without checking it explicitly. You can indeed not do that check, but you'll end up overwriting the document over and over again. Please also note, that a write operation is more expensive than a read operation. So that's the best option that you have.
I have a document MAIN which holds an array of objects. On every change of this array i want to update related documents. So in the example below If I add something to the targets of MAIN i want to grab all documents where the field "parent" holds a reference to MAIN and then update their targets accordingly. I wanted to do this with cloud functions so the client does not have to care about updating all related documents himself. But as stated in the docs cloud-funcion triggers do not guarantee order. So if f.e. a user adds a new object to the targets of MAIN and then removes it, cloud trigger would maybe receive the remove event before the add event and thus RELATED documents would be left with inconsistent data. As stated by Doug Stevenson in this stackoverflow post this could happen, even when using transactions. Am I right so far?
const MAIN = {
ID:"MAIN"
targets:[{name: "House"}, {name:"Car"}]
}
const RELATED_1 = {
parent: "MAIN",
targets:[{name: "House"}, {name:"Car"}]
}
const RELATED_2 = {
parent: "MAIN",
targets:[{name: "House"}, {name:"Car"}]
}
If yes, I was thinking about adding a servertimestamp to object MAIN whenever I modify the Document. I would use this timestamp to only update RELATED Documents if their timestamp is smaller then the one of the parent. If yes I update the array and set the timestamp of the parent.
const MAIN = {
ID:"MAIN",
targets:[{name: "House"}, {name:"Car"}],
modifiedAt: 11.04.2022 10:25:33:233
}
const RELATED_1 = {
parent: "MAIN",
lastSync: 11.04.2022 10:25:33:233,
targets:[{name: "House"}, {name:"Car"}]
}
const RELATED_1 = {
parent: "MAIN",
lastSync: 11.04.2022 10:25:33:233,
targets:[{name: "House"}, {name:"Car"}]
}
Would that work? Or how could one sync denormalized data with cloud functions and keep data consistent? Is this even possible?
Using the Cloud Firestore client libraries, you can group multiple operations into a single transaction. Transactions are useful when you want to update a field's value based on its current value, or the value of some other field.
A transaction consists of any number of get() operations followed by any number of write operations such as set(), update(), or delete(). In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.
Transactions are a way to always ensure a write occurs with the latest information available on the server. Transactions never partially apply writes & all writes execute at the end of a successful transaction.
For a transaction to succeed, the documents retrieved by its read operations must remain unmodified by operations outside the transaction. If another operation attempts to change one of those documents, that operations enters a state of data contention with the transaction.
A transaction works differently than a regular update. It goes like this:
Firestore runs the transaction.
You get the document ready to update whatever property you want to update.
Firestore checks if the document has changed. If not, you’re good, and your update goes through.
If the document has changed, let’s say a new update happened before yours did, then Firestore gets you the new version of the document and repeats the process until it finally updates the document.
The following example from firebase documentation shows how to create and run a transaction:
// Initialize document
const cityRef = db.collection('cities').doc('SF');
await cityRef.set({
name: 'San Francisco',
state: 'CA',
country: 'USA',
capital: false,
population: 860000
});
try {
await db.runTransaction(async (t) => {
const doc = await t.get(cityRef);
// Add one person to the city population.
// Note: this could be done without a transaction
// by updating the population using FieldValue.increment()
const newPopulation = doc.data().population + 1;
t.update(cityRef, {population: newPopulation});
});
console.log('Transaction success!');
} catch (e) {
console.log('Transaction failure:', e);
}
For more information on the above can refer to how do transactions work ,updating data and fastest way to perform bulk data creation
'chatrooms' collection is include documents for example document name is 'PNJAHph61ZStU7IANgBfXAwW66l2-e8GhTlpLyWSdmmBuMlyrfgiJYlI3'
'PNJAHph61ZStU7IANgBfXAwW66l2-e8GhTlpLyWSdmmBuMlyrfgiJYlI3' document include 'messeges' document and 'messeges' document include sub document
I need to get the document name list include in the 'chatrooms' collection.
In a normal case, this function works like that but in nested case, it does not work
firebase.firestore().collection("chatrooms")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.data()); // For data inside doc
console.log(doc.id); // For doc id
}
)});
You will need to create a symbiotic link through data aggregation, as paths in the collection and sub-collections are not linked beyond the name path reference. Additionally, you cannot simply fetch all document names in a collection without reading all the document's data() which would incur reads with billing
so if you need a list of all names, you must create an array of all sub-collection names in the parent document or in a dedicated document called _index
i m bulding a scalable chat app with RTDB and firestore
here is my raw structure of shards
SHARD1
Chats {
chat01: {
Info: {
// some info about this chatroom
},
Messages ...
}, ....
}
SHARD2...
now i have write triggers on all the info nodes of all the shards.
i want get the ID of the shard
How do i know what shard it actually ran on ?
[EDIT]
console.log(admin.app().name); // it prints "[DEFAULT]" in console
Puf and team please help
When a Realtime Database trigger is invoked, the second argument is an EventContext object that contains information about the database and node that was updated. That object contains a resource string, which has what you're looking for. According to the documentation for that string, it's name property will be formatted as:
projects/_/instances/<databaseInstance>/refs/<databasePath>
The databaseInstance string is what you're looking for. So, you can just split the string on "/" and take the 4th element of that array:
export const yourFunction = functions.database
.instance('yourShard')
.ref('yourNode')
.onCreate((snap, context) => {
const parts = context.resource.name.split('/')
const shard = parts[3]
console.log(shard)
})
If all you need is a reference to the location of the change, in order to perform some changes there, you can just use the ref property on the DataSnapshot that was delivered in the first argument, and build a path relative to there.
I am monitoring for changes in node leaf jobs/{jobid}/proposals. Whenever I remove the proposals the function gets executed and reinsert proposals (this is the expected behavior).
The problem is When I remove its parent {job}, proposals gets reinserted in a new object with same parent ID. Is there a way to do a check if the parent exists? If so, reinsert proposal otherwise not.
exports.RecountProposals = functions.database.ref("/jobs/{jobid}/proposals").onWrite(event => {
const jobid = event.params.jobid;
if (!event.data.exists() && event.data.ref.parent.exists()) {
const propRef = admin.database().ref(`proposals/${jobid}`);
const counterRef = event.data.ref;
const collectionRef = counterRef.parent.child('proposals');
// Return the promise from counterRef.set() so our function
// waits for this async event to complete before it exits.
return propRef.once('value')
.then(messagesData => collectionRef.set(messagesData.numChildren()));
}
});
I am checking if parent exists but it is showing an error:
event.data.ref.parent.exists()
TypeError: event.data.ref.parent.exists is not a function
event.data.ref.parent is a Reference type object. As you can see from the linked doc, there is no exists() method on Reference. In Realtime Database, if you want to know if there is any data at a node, simply fetch the snapshot there and call val() on it to check to see if it's null. Reference objects are just paths, they don't contain any knowledge of data.
To put it another way, there is no such concept as a node that "exists" but contains no data, like an empty folder in a filesystem. For any given path that you can construct, the snapshot of the data there is either available (non-null) or not (null).