Using firestore meta createTime and updateTime - firebase

I've realized that there is private meta information "createTime" and "updateTime" in every Firestore document.
{
...
"_createTime": {
"_seconds": 1657794552,
"_nanoseconds": 15469000
},
"_updateTime": {
"_seconds": 1657794552,
"_nanoseconds": 200837000
}
}
Simply, Is there any reason I have to use my own "createdAt" and "updatedAt" fields?
May I use these already existing "_updateTime,_createTime" values in my app logic?

I don't believe you can query by metadata,you can only access it. As mentioned in this stackoverflow Answer, There is no way to query on the metadata that Firestore automatically maintains. If you need to query then you will need to add a field with that value to the document's data.
Firebase Firestore provides create_time and update_time timestamps in v1, if you only need output.Have a look at this document
create_time
Output only. The time at which the document was created.
This value increases monotonically when a document is deleted then recreated. It can also be compared to values from other documents and the read_time of a query.
update_time
Output only. The time at which the document was last changed.
This value is initially set to the create_time then increases monotonically with each change to the document. It can also be compared to values from other documents and the read_time of a query.

Related

Firebase snapshot listener filter

I have a Firestore DB. Is it possible to filter snapshots coming from it based on some field if add add a listener? What I need is: “send me an updated document only if this field equals this value”
What I surely can do is just check manually each new snapshot and return/propagate document if it passes the filter but I was thinking about sparing some transferred data and hit less limits
You can make a query by filtering something then adding a listener for this like the below code:
// get all document in collection "cities" that has attribute "state" equal to "CA"
db.collection("cities").whereField("state", isEqualTo: "CA")
.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
let cities = documents.map { $0["name"]! }
print("Current cities in CA: \(cities)")
}
Ref: https://firebase.google.com/docs/firestore/query-data/listen#listen_to_multiple_documents_in_a_collection
Cloud Firestore listeners fire on the document level. So if you have multiple fields in a document and the value of the fields in the document changes, your listener will fire. So you'll have to pay a read operation, each time something in the document changes.
It's true that you can attach a listener and get only the documents that have set a field to a particular value, but that doesn't mean you can restrict the SDK to read the value only when that field is changed to a value of your choice.
There is no way you can read a document, only when a field gets a particular value. You're always charged with a read operation, each time a value changes, no matter what the value is. So if the new value is the value that passes your filters, then you can go ahead with your logic.
I was thinking about sparing some transferred data and hitting less limits.
Everything in Firestore it's about the number of reads, writes, and deletes you perform. And the amount of bandwidth you are using. But unfortunately, you cannot reduce the costs that way.

increment document id by timestamp in firestore

My cloud firestore database has an "orders" collection and in HTML I have a 'save' button to add document(s) into that "orders" collection upon clicking. Now, using add will assign auto-generated ID for each document.
What if I want to customise such ID by timestamp? So that the document created yesterday will be assigned an index as '1', and the following document created will be '2', etc...
What you're trying to do is not compatible with the way Cloud Firestore was designed. Firestore will not assign monotonically increasing numbers for document IDs. This just doesn't scale massively as required by Firestore and would introduce performance bottlenecks.
If you want to be able to sort documents by timestamp, the best strategy is to add a timestamp field to each document, then use that field in an ordered query.
Note that you could try to write a lot of code to get this done the way you want, but you are MUCH better off accepting the random IDs and using fields to filter and order data.
in some case, when you need to save several docs in different collection due to an event occurs, it's better to same all docs with same id in different collections with single firestore server's timestamp. you get the timestamp like below:
const admin = require('firebase-admin')
const ts = admin.firestore.Timestamp.now().toMillis().toString()
by doing this, when you need to read all those docs, you only need to query once to get timestamp, then read all other doc by timestamp directly.
it should be faster than query the timestamp inside document fields for each collections

Is it possible to fetch all documents whose sub-collection contains a specific document ID?

I am trying to fetch all documents whose sub-collection contain a specific document ID. Is there any way to do this?
For example, if the boxed document under 'enquiries' sub-collection exists, then I need the boxed document ID from 'books' collection. I couldn't figure out how to go backwards to get the parent document ID.
I make the assumption that all the sub-collections have the same name, i.e. enquiries. Then, you could do as follows:
Add a field docId in your enquiries document that contains the document ID.
Execute a Collection Group query in order to get all the documents with the desired docId value (Firestore.instance.collectionGroup("enquiries").where("docId", isEqualTo: "ykXB...").getDocuments()).
Then, you loop over the results of the query and for each DocumentReference you call twice the parent() methods (first time you will get the CollectionReference and second time you will get the DocumentReference of the parent document).
You just have to use the id property and you are done.
Try the following:
Firestore.instance.collection("books").where("author", isEqualTo: "Arumugam").getDocuments().then((value) {
value.documents.forEach((result) {
var id = result.documentID;
Firestore.instance.collection("books").document(id).collection("enquiries").getDocuments().then((querySnapshot) {
querySnapshot.documents.forEach((result) {
print(result.data);
});
First you need to retrieve the id under the books collection, to be able to do that you have to do a query for example where("author", isEqualTo: "Arumugam"). After retrieving the id you can then do a query to retrieve the documents inside the collection enquiries
For example, if the boxed document under 'enquiries' sub-collection exists, then I need the boxed document ID from 'books' collection.
There is no way you can do that in a single go.
I couldn't figure out how to go backwards to get the parent document ID.
There is no going back in Firestore as you probably were thinking. In Firebase Realtime Database we have a method named getParent(), which does exactly what you want but in Firestore we don't.
Queries in Firestore are shallow, meaning that it only get items from the collection that the query is run against. Firestore doesn't support queries across different collections in one go. A single query may only use the properties of documents in a single collection. So the solution to solving your problem is to perform two get() calls. The first one would be to check that document for existence in the enquiries subcollection, and if it exists, simply create another get() call to get the document from the books collection.
Renaud Tarnec's answer is great for fetching the IDs of the relevant books.
If you need to fetch more than the ID, there is a trick you could use in some scenarios. I imagine your goal is to show some sort of an index of all books associated with a particular enquiry ID. If the data you'd like to show in that index is not too long (can be serialized in less than 1500 bytes) and if it is not changing frequently, you could try to use the document ID as the placeholder for that data.
For example, let's say you wanted to display a list of book titles and authors corresponding to some enquiryId. You could create the book ID in the collection with something like so:
// Assuming admin SDK
const bookId = nanoid();
const author = 'Brandon Sanderson';
const title = 'Mistborn: The Final Empire';
// If title + author are not unique, you could add the bookId to the array
const uniquePayloadKey = Buffer.from(JSON.stringify([author, title])).toString('base64url');
booksColRef.doc(uniquePayloadKey).set({ bookId })
booksColRef.doc(uniquePayloadKey).collection('enquiries').doc(enquiryId).set({ enquiryId })
Then, after running the collection group query per Renaud Tarnec's answer, you could extract that serialized information with a regexp on the path, and deserialize. E.g.:
// Assuming Web 9 SDK
const books = query(collectionGroup(db, 'enquiries'), where('enquiryId', '==', enquiryId));
return getDocs(books).then(snapshot => {
const data = []
snapshot.forEach(doc => {
const payload = doc.ref.path.match(/books\/(.*)\/enquiries/)[1];
const [author, title] = JSON.parse(atob(details));
data.push({ author, title })
});
return data;
});
The "store payload in ID" trick can be used only to present some basic information for your child-driven search results. If your book document has a lot of information you'd like to display once the user clicks on one of the books returned by the enquiry, you may want to store this in separate documents whose IDs are the real bookIds. The bookId field added under the unique payload key allows such lookups when necessary.
You can reuse the same data structure for returning book results from different starting points, not just enquiries, without duplicating this structure. If you stored many authors per book, for example, you could add an authors sub-collection to search by. As long as the information you want to display in the resulting index page is the same and can be serialized within the 1500-byte limit, you should be good.
The (quite substantial) downside of this approach is that it is not possible to rename document IDs in Firestore. If some of the details in the payload change (e.g. an admin fixes a book titles), you will need to create all the sub-collections under it and delete the old data. This can be quite costly - at least 1 read, 1 write, and 1 delete for every document in every sub-collection. So keep in mind it may not be pragmatic for fast changing data.
The 1500-byte limit for key names is documented in Usage and Limits.
If you are concerned about potential hotspots this can generate per Best Practices for Cloud Firestore, I imagine that adding the bookId as a prefix to the uniquePayloadKey (with a delimiter that allows you to throw it away) would do the trick - but I am not certain.

Firestore query document based on comparison of 2 fields

Say I have this document
userProfile: {
createdAt: '2018-01-01',
updatedAt: '2018-01-04'
}
I want to retrieve all userProfiles that were updated after their creation, that means those that satisfy createdAt < updatedAt.
Can you do that in Firestore?
Something like this, but where the second operand is a field, not a value:
userProfileRef.where("createdAt", "<", "updatedAt")
Thanks
You will not be able to do that with "standard" Firestore queries.
The easiest way would be to either:
Have a default "dummy" date (e.g. 1900-01-01) in the updatedAt field if the doc was never updated after creation. You would then query with userProfileRef.where("updatedAt", ">", "1900-01-01")
or
Have a specific flag in the document that you would update when you change the value of the updatedAt field (i.e. when you update the doc after creation).
Note that you cannot query for documents that don't contain a given field (watch this official video for more details), therefore you cannot rely on the absence of the updatedAt field.

how to add documents to bottom of the document column in Cloud Firestore through the Firebase console

I'm adding data to cloud Firestore through the Firebase Console. I've added a collection and the corresponding documents and fields. When I click on " ADD DOCUMENT", the new document appears randomly (as far as I can make out) on the document column. I want the newly generated document to appear at the bottom of the document column. The order matters when viewing the data on the app. The data is used in a recycler view. Is this possible?
Cloud Firestore does not order documents in the same way as the RTDB. Auto IDs are not time related. You will need to add a timestamp field and order your data by this field.
You can read about this here.
Important: Unlike "push IDs" in the Firebase Realtime Database, Cloud
Firestore auto-generated IDs do not provide any automatic ordering. If
you want to be able to order your documents by creation date, you
should store a timestamp as a field in the documents.
I used Firestore's "set" method instead of "add" and used a numerical date string; it is automatically adding and listing them in numerical order.
// Swift
let dataToSave: [String: Any] = ["example": "example"]
collectionRef.document(dateString).setData(dataToSave) { (error) in
}
If you use the "add" method, the key will be automatically generated with a random alphanumeric string.
The collection orders everthing alphanumerically, so setting your documents using keys of numbers or letters, exclusively, will allow for an ordered list.

Resources