前置き
Firestore の性質上、ランダム取得は少し工夫が必要です。
StackOverflow "Firestore: How to get random documents in a collection" で Google の人が回答しているようにいくつかの方法がありますが、CollectionGroupQuery への言及がなかったので書きます。
結論
基本的に無理。
特殊な条件を満たせば可能だが、それは CollectionGroup を使う価値が無い場面。
具体例をもとに順を追って検証
例として Flutter の cloud_firestore を使います。
データ構造は、トップに User
Collection、その SubCollecion に Tweet
を置いているとします。
この時、「User
を限定せず、Tweet
の中からランダムに1つ document を取得したい」場面を考えます。
これは一筋縄ではいきません。
それっぽいのを書いてみる
と、こうなるかと思いますが、これはダメです。
// 比較パラメータとして使うためだけに document ID を生成
final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;
// それを使って検索
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
Firestore.instance
.collectionGroup('user')
.orderBy('__name__')
.where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
.limit(1);
// 後略
__name__
の実体は document ID ではなく、完全に一意な Path です。1
そのため、こうやって怒られます。 2
Invalid query.
When querying a collection group and ordering by document ID,
you must pass a value that results in a valid document path,
but 'jM5rRdSflGyfk8Qy9Puu' is not because it contains an odd number of segments.
つまり、以下のように完全に一意な path を指定すれば動くわけです。実際期待した動きをします。
final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;
Firestore.instance
.collectionGroup('user')
.orderBy('__name__')
- .where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
+ .where('__name__', isGreaterThanOrEqualTo: 'user/XXX/tweet/${dummyTweetDocumentId}')
.limit(1);
しかし、これじゃあダメですね....
なぜなら、この条件を指定できるということは、client が User の document ID XXX
を知っていることを意味するからです。
User を限定せず
を達成できていないということです。
だから、階層分 Query を発行する
しかないと思います。あくまで、Field を追加しないことにこだわるなら の話をしています。
今回だと2~4回です。
// まず User をランダムに取得する
final dummyUserDocumentId = Firestore.instance.collection('').document().documentID;
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
final userQuerySnapshot = await Firestore.instance
.collection('user')
.orderBy('__name__')
.where('__name__', isGreaterThanOrEqualTo: dummyUserDocumentId)
.limit(1)
.getDocuments();
final user = userQuerySnapshot.documents.first;
// そして、Tweet をランダムに取得する
final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
Firestore.instance
.collection('user/${user.documentID}/tweet')
.orderBy('__name__')
.where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
.limit(1);
// 後略
結果、どうするのがベストか?
Field を追加するのがシンプルだなーと思いました。
ただ、そこに document ID の生成ロジックを使う場合は少しだけ悩ましいです。
どういうことかと言うと、document 自体の ID と一致させるには add
ではなく set
で document を追加することになるからです。
set
には update
の意もあるので、できれば利用は避けたいものです。
なので、document 自体の ID とは別個に(同様のロジックを使って)値を作って入れておくのが良いかなと考えています。そうすれば add
で書けるので。
万が一、その Field と document の ID が一致してないといけない仕様があるなら set
で書くしかないとは思いますが...
-
ソースコードを詳かく見るとこまではまだできていないのですが、https://stackoverflow.com/a/56189687/10928938 や https://firebase.google.com/docs/firestore/security/secure-data?hl=ja#the_resource_variable からも確かかと思います。 ↩
-
iOS だと https://github.com/firebase/firebase-ios-sdk/blob/330a55f9de8ab9a5cc86a5a934c68264e29b5268/Firestore/Source/API/FIRQuery.mm#L526-L531 にあたります。 ↩