Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firestore collection group query on documentId

In my scenario a user can "like" the profile of another user. As a result I have a subcollection called "likedBy" for each user, where I create a document for a specific user, e.g.:

users(col) -> User A(doc) -> likedBy(col) -> User B (doc), User C (doc)

So in the rare scenario of a user being deleted, I want to delete all the likes issues by that user.

I am just not sure if this is even possible (a workaround could be to just save the userId again in said document and query for that).

What I am basically looking for is something like this:

db.collectionGroup('likedBy').where(firebase.firestore.FieldPath.documentId(), '==', "User A").get();

The problem is, that I can not create an index for the documentId in the Firestore console.

like image 413
Thomas Avatar asked May 15 '19 12:05

Thomas


People also ask

How do I list all Subcollections of a cloud firestore document?

Just enter a document path in the dedicated field and click the button “Get Subcollections”: If the document has one or more subcollections the page will display their id(s). If the document at the path does not exist or if it does not have any subcollection, the Cloud Function will return an empty array.

What is Collection group firestore?

A Collection Group Query allows you to join all the collections that share a similar name, in this case comments , and query across them uniformly. app.js. import * as firebase from 'firebase/app'; const db = firebase. firestore(); const query = db. collectionGroup('comments') .

Is it possible to query by Document ID in Cloud Firestore?

Everything in Cloud Firestore is about the number of read and writes. @AlexMamo The docs you linked to use an example of query by field, but that doesnt mean that collection group query by document ID is not possible.

What is a collection in Cloud Firestore?

In Cloud Firestore, your data is divided up into documents and collections. Documents often point to subcollections that contain other documents, like in this example, where each restaurant document contains a subcollection with all the reviews of that restaurant. In the past, you could query for documents within a single collection.

How to store user ID in Firebase collection group?

A workaround could be to store the id of the user in an array and use array-contains but remember, for each collection group query you use, you need an index and unfortunately you cannot create such an index programmatically. Even if you can create an index in the Firebase console, it won't help you since you get those ids dynamically.

Is it possible to filter fieldpaths in collection group queries?

1 Answer 1 ActiveOldestVotes 27 This is currently not possible with collection group queries. You might be tempted to put a filter on the query for FieldPath.documentId() == 'your-doucment-id', but that just isn't supported.


2 Answers

UPDATE 2020-01-23:

For updates on this, see a conversation with Sam Stern on the group board:https://groups.google.com/d/msgid/google-cloud-firestore-discuss/e1b47358-b106-43a0-91fb-83c97d6244de%40googlegroups.com

Much of the discussion comes from the apparent fact that there is a SINGLE "master index" of ALL records in a database based on the FULLY QUALIFIED path to the document (hence only needing to be unique "within a collection").

To "accelerate" document references, the JS SDK actually "pre-pends" the collection path onto whatever info is passed in .doc to "conveniently" use that master index

i.e. all of these are exactly equivalent

db.doc('collection/docId/collection/docId/collection/docId")
db.collection('collection").doc("docId/collection/docId/collection/docId")
db.collection('collection").doc("docId").collection("collection").doc("docId/collection/docId")
db.collection('collection").doc("docId").collection("collection").doc("docId").collection("collection").doc("docId")
db.doc("collection/docId").collection("collection").doc("docId").collection("collection").doc("docId")
db.collection("collection/docId/collection").doc("docId").collection("collection").doc("docId")
db.doc("collection/docId/collection/docId").collection("collection").doc("docId")
db.collection("collection/docId/collection/docId/collection").doc("docId")

--- they ALL create the same index reference 'collection/docId/collection/docId/collection/docId" to find the document in the "master index".

(in my not-at-all-humble-opinion) FieldPath.documentId() was implemented (incorrectly) to "conveniently" match this behavior thus requiring the fully-qualified path, not the docId, when it should have been implemented like any other query, and required creating and maintaining a NEW INDEX to accommodate the query.

The code for this behavior was written BEFORE collectionGroups were implemented - and never documented the hack used didn't match the METHOD NAME used.

The work around is to require the Coder to copy the docId as a field in each document, and write your queries on that.  I already wrote my own layer between Firestore.js and my application to abstract the behavior, and will probably simply implement this as a basic feature of the library.

But this is clearly a MISTAKE, and so far everybody keeps trying to tell me it makes sense, and that they'll change the documentation to match the existing behavior (but not the method name).

As I wrote previously, I keep getting handed a ratty bunch of daisies, and being told "See? these are roses!! The documentation calls them roses!! Roses by any other name smell as sweet, and all that!!"

No Update Expected Unless They Get Embarrassed Enough

UPDATE 2020-01-10: I have built a demo app showing the exact bug, and have sent it to Firebase support as requested. For some dang reason, the support critter considers it a "feature request", in spite of it clearly a bug. When a URL is called in the form "/ShowInfo/showID", the App signs in to Firebase Auth anonymously; then calls a query on the collectionGroup (3 levels deep) using FieldPath.documentId() "==" showID

It makes the query 3 ways:

1) Once with only the showID- which fails with the familiar "Invalid query. When querying a collection group by FieldPath.documentId(), the value provided must result in a valid document path, but 'pqIPV5I7UWne9QjQMm72'(the actual showID) is not because it has an odd number of segments (1)."

2) Once with a "Relative Path" (/Shows/showID), which doesn't have the error, but returns no document.

3) Finally with the "Full Path" (/Artists/ArtistID/Tour/tourID/Shows/showID). This doesn't have an error, and does return a document - but if I have the full path, why do I need the query on the collectionGroup? And I don't have the full path - the showID (above) comes in as part of a URL (a link to the show data, obviously) - I hand-faked it for the test.

Waiting for response.

UPDATE 2019-12-02: Firebase support reached out to ask if I still wanted this solved. Duh.

UPDATE 2019-09-27: Firebase Support has acknowledged this is a bug. No word on when it will be fixed. documentId() should, indeed, be able to be used directly against only the document Id.

documentID can be used as part of a query but, as @greg-ennis notes above, it needs an even number of segments. As it turns out, if you truly need a collectionGroup (I do), then the "Id" to be compared needs to redundantly add the collectionGroup ID/name as a segment:

db.collectionGroup('likedBy')
.where(firebase.firestore.FieldPath.documentId(), '==', "likedBy" + "/" + "User A")
.get();

I've used it and it (*sorta) works. (admittedly, it took me 2 hours to figure it out, and the above question helped focus the search)

On the other hand, this specific case is not where you want to use collectionGroup. Remember what collection group is - a way to refer to a a set of separate collections as if they were one. In this case the "collection" that holds "User A"s likes exists as a collection under "User A"s doc. Simply delete that single collection before deleting "User A"s doc. No need to bring everybody else's likes into it.

Sorta: the field path compared apparently has to be the complete path to the document. If you know the documentId, but for "reasons" you do not know the complete path, including which document the sub-collection is a part of (kinda why you were using the collectionGroup approach in the first place), this now seems a tadly dead-end. Continuing working on it.

Verified and Bug Report filed: FieldPath.documentID() does not compare against the documentId; it compares against the fully segmented document path, exactly as you would have to provide to .doc(path):

To wit: to find a document at "TopCollection/document_this/NextCollection/document_that/Travesty/document_Id_I_want"

using the collectionGroup "Travesty"

db.collectionGroup("Travesty")
.where(firebase.firestore.FieldPath.documentId(), '==', "document_id_I_want")

...will fail, but...

db.collectionGroup("Travesty")
.where(firebase.firestore.FieldPath.documentId(), '==', "TopCollection/document_this/NextCollection/document_that/Travesty/document_Id_I_want")
.get()

...will succeed. Which makes this useless, since if we had all that info, we would just use:

db.doc("TopCollection/document_this/NextCollection/document_that/Travesty/document_Id_I_want")
.get()
like image 144
LeadDreamer Avatar answered Sep 22 '22 04:09

LeadDreamer


There is no way you can use the following query:

db.collectionGroup('likedBy').where(firebase.firestore.FieldPath.documentId(), '==', "User A").get();

And this is because collection group queries work only on document properties and not on document ids. According to the official documentation regarding collection group queries:

db.collectionGroup('landmarks').where('type', '==', 'museum');

You query the landmarks subcollection where the type property holds the value of museum.

A workaround could be to store the id of the user in an array and use array-contains but remember, for each collection group query you use, you need an index and unfortunately you cannot create such an index programmatically. Even if you can create an index in the Firebase console, it won't help you since you get those ids dynamically. So is not an option to create an index for each user separately because you'll reach the maximim number of indexes very quickly.

Maximum number of composite indexes for a database: 200

To solve this kind of problems, you should consider adding an array under each user object and use a query like this:

usersRef.where("usersWhoLikedMe", "array-contains", "someUserId")

Where usersWhoLikedMe is a property of type array.

like image 33
Alex Mamo Avatar answered Sep 22 '22 04:09

Alex Mamo