CollectionGroupQuery but limit search to subcollections under a particular document

This is my database structure.
cities -> landmarks -> attractions(each one is a collection)

Query
List all attractions under a particular city.

Solution
Save city_id in each attraction document
Use a collection group query and filter based on city_id to get attractions of a particular city.

My Question
Instead of saving city_id in each attraction document, can I just specify the document to the collectionGroupQuery which now only searches in subcollections under this particular document.

In the above example, I specify the city document's full path and i should be able to list all attractions without filtering based on city_id.


Solution 1:

This should work using FieldPath.documentId().

Each document has a hidden __name__ field in its data and this is the target of FieldPath.documentId().

For a normal collection query, FieldPath.documentId() would just be the document's ID. However, for a collection group query, this value is the document's path.

We should be able to use this to find all matching document paths that start with the given city's document path like so:

const cityRef = firebase.firestore().doc('cities/cityId');

firebase.firestore().collectionGroup('attractions')
  .orderBy(firebase.firestore.FieldPath.documentId())
  .startAt(cityRef.path + "/"),
  .endAt(cityRef.path + "/\uf8ff")
  .get()
  .then((querySnapshot) => {
    console.log("Found " + querySnapshot.size + " docs");
    querySnapshot.forEach((doc) => console.log("> " + doc.ref.path))
  })
  .catch((err) => {
    console.error("Failed to execute query", err);
  })

Edit: While the above code would function if the SDK allowed it, it currently throws an error about having an odd number of segments because of the extra /.

For now, as long as all your city IDs are the same length (as they would be if using the default docRef.add()), the below code would function:

const cityRef = firebase.firestore().doc('cities/cityId');

firebase.firestore().collectionGroup('attractions')
  .orderBy(firebase.firestore.FieldPath.documentId())
  .startAt(cityRef.path),
  .endAt(cityRef.path + "\uf8ff")
  .get()
  .then((querySnapshot) => {
    console.log("Found " + querySnapshot.size + " docs");
    querySnapshot.forEach((doc) => console.log("> " + doc.ref.path))
  })
  .catch((err) => {
    console.error("Failed to execute query", err);
  })

Where the above block fails is if the document ID have different lengths. Let's say you only wanted documents under "/cities/New York", if another city called "New Yorkshire" was also in the cities collection, it would have it's results included too.