Firestore
制限
Timestamp
{seconds: 1589920163, nanoseconds: 472000000} のようなオブジェクト
instance.toDate() / Timestamp.fromDate() で変換できる
where in
いつのまにかきていた
db.collection(...).where('name', 'in', ['Alice', 'Bob']).orderBy('date', 'desc') みたいなのもなげれる
否定条件 !=
否定はないので
.where('name', '<', 'pokutuna').where('name', '>', 'pokutuna') のようにする
Prefix search
否定条件と似たような感じに
.where('url', '>=', 'https://website.test').where('url', '<=', 'https://website.test\u{10ffff}')
単一フィールドの index 除外
デフォルトで単一フィールドの index も作られるので、でかい文字列フィールド(記事の本文とか)、でかい配列とかは除外する
firestore.indexes.json 内の fieldOverrides を使う
code:fieldOverrides.json
...
"fieldOverrides": [
{
"collectionGroup": "entries",
"fieldPath": "item.content",
"indexes": []
},
...
]
index の確認 & 反映
firebase-tools で
$ firebase firestore:indexes > firestore.indexes.json
$ firebase deploy --only firestore:indexes
array に含まれる値でソート
citiesRef.where("regions", "array-contains", "west_coast")
documentId 順でソート、次のid のものを取る
.orderBy('__name__', 'desc').startAfter(currentReportId).limit(1)
しかしコケる
https://gyazo.com/adcce4eb2f20851bb04542c28a4a1ae0
asc なら取れる
デフォルトで貼られてる index は asc だけ? 昇順降順両方貼られてるイメージだが...
単一フィールドの index は作れない
import {FieldPath} from '@google-cloud/firestore'; FieldPath.documentId() でも
値と ID でソート
タイムライン的なものなど、同時刻のデータが複数あることを想定する場合は時刻と ID (など別のフィールド)でソートしたい場合は以下のようにする
code:timeline.ts
const snapshot = await db
.collection("entries")
.orderBy("created", "desc")
.orderBy(FieldPath.documentId(), "desc")
.startAfter(cursor.startAfter, cursor.id)
.limit(10)
.get()
documentId なら追加の index はたぶんいらない(どうなっている?)
フィールドの id を使うなら create 降順, id 降順 の index が要る
ホットスポットを避けるために documentId はちらしておきたいので実装上別になることがある
前のページや前のページの存在を確認するには、同じ orderBy しつつ endAt(endAfter) & limitToLast を使う
db.collection('entries').orderBy(...).endAt(cursor.startAfter, cursor.id).limitToLast(1)
前のページがあるかどうか、startAfter は exclusive なので endAt で inclusive に前を探す
limitToLast を使うことでソートした末尾から取れる
逆順のインデックスは必要、内部では asc になっている
collectionGroup クエリ
同じ名前のサブコレクションを横断したクエリを投げられる
{ tag: ["anime", "game"], timestamp: ... }
のようなタグを含むドキュメントを時刻順でソートしたい、みたいなのは実現できない
{ tag: { "anime": 12345, "game": 12345 } } のようにすれば
db.collection('entries').where('tag.anime', '>', 0).orderBy('tag.anime') で引ける
う〜〜ん、複数の値でソートしたい時に困りそう & データ構造によりすぎている気がする
Elasticsearch でも検索で使いたい型にあわせて text と text.raw に分けてフィールド分けて作ってクエリ時に指定していたりしたしこういうのでもよいのかもしれない
code:forsort.json
{
"tagsToTimestamp": {
"anime": 123,
"sports": 123,
"game": 123
}
}
db.collection('entries').where('tags', 'array-contains', 'anime').orderBy('tagsToTimestamp.anime')
とかで引ける? (未確認)
firestore と Cloud Functions 間での cold start が遅い話
local emulator
firestore-tools を使っていなくても gloud で立てられる
$ gcloud beta emulators firestore start --host-port=localhost:8080
FIRESTORE_EMULATOR_HOST=localhost:8080 環境変数でクライアントライブラリを使う
👇 でもうまくいく? なんかエラー出たことあるような気がする
new Firestore({ projectId: '<PROJECT_ID>', host: 'localhost:8080' })
projectId をなんでもいいから must で渡さないといけない?
id に slash 使えない
path で参照できるので
firestore.doc('users/pokutuna')
firestore.collection('users/pokutuna/comments')
URL やらを入れたいときは encodeURIComponent するなど
v9 やら前後で新しいやつ
サブコレクションの参照
collection(db, "shelfs", shelfId, "books") のようにする
Query
query(collectionRef, queryParts...) のような感じ
query(collection(db, "users"), where(...), orderBy(...), endoBefore(...), limit(...)) みたいな
lite ができた
ストリーミングできない & オフラインで書いて復帰したときに反映などの機能がない
素朴な read write はこちら
FirestoreDataConverter
DocumentData からモデルオブジェクトへの変換
collectionRef や query に withConverter でくっつける
doc.data() のときに Converter を通ったオブジェクトが返ってくる