Firestore コレクショングループクエリのトライ&エラー / 複数階層指定、複数指定など

Firestore コレクショングループクエリのトライ&エラー / 複数階層指定、複数指定など

Firestoreのコレクショングループクエリ(Collection Group Queries)がどんな感じで動作するか知りたかったので、簡単に確認してみました。

目次

はじめに

今回の目的は動作をチェックする事なので、セキュリティルールとかインデックスとかまではやってません。

その辺りはこの辺(↓)の記事を参照してください。

確認項目

  • どの階層のコレクションでも指定できるか?
  • ドキュメント名での指定はできるか?
  • 複数階層の指定はできるか?
  • 複数コレクションの指定はできるか?

確認コード全体

テストコードの全体は以下の通り。

import { FirestoreTestSupporter }
    from "@mm0202-package/firestore-test-supporter";

import * as path from "path";

describe("コレクショングループトライアル", () => {
    const supporter = new FirestoreTestSupporter(
        "my-test-project",
        path.join(__dirname, "../firestore.rules"
        ));

    beforeEach(async () => {
        await supporter.loadRules();

        const adminDb = supporter.getAdminFirestore();
        await adminDb.collection('/target/test/test')
            .doc('dummy1').set({ message: 'success' });
        await adminDb.collection('/staging/target/test')
            .doc('dummy2').set({ message: 'success' });
        await adminDb
            .collection('/production/test/target/test/test')
            .doc('dummy3').set({ message: 'success' });
        await adminDb.collection('/production/test/target')
            .doc('dummy4').set({ message: 'success' });
        await adminDb.collection('/production/test/test')
            .doc('target').set({ message: 'success' });
    });

    afterEach(async () => {
        await supporter.cleanup()
    });

    test('1階層指定', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('target')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

    test('1階層指定 2', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('test')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

    test('2階層指定', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('test/target')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

    test('2コレクション指定 その1', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('target,test')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

    test('2コレクション指定 その2', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('(target|test)')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

    test('複数コレクション指定 その3', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('*')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });
});

@mm0202-package/firestore-test-supporterは非公開パッケージです。

FirestoreTestSupporterは、セキュリティルールのロードやfirestore(もしくはdb)オブジェクトの生成用クラスです。

今回は、コレクショングループの動作チェックが目的なので、FirestoreTestSupporterの詳細は省略します。

全体の流れとしては、

  1. beforeEachでテスト用データの作成
  2. 1階層指定でテスト その1
  3. 1階層指定でテスト その2
  4. 2階層指定でテスト

といった感じです。

各テストではコレクショングループクエリで取得した結果を確認するためにconsole.logで出力しています。

セキュリティルールは以下の通り。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

今回は、コレクショングループクエリの動作を確認するだけなので、全アクセスOKにしてます。

テストデータの作成

テストデータの作成は以下の部分で行っています。

    beforeEach(async () => {
        await supporter.loadRules();

        const adminDb = supporter.getAdminFirestore();
        await adminDb.collection('/target/test/test')
            .doc('dummy1').set({ message: 'success' });
        await adminDb.collection('/staging/target/test')
            .doc('dummy2').set({ message: 'success' });
        await adminDb
            .collection('/production/test/target/test/test')
            .doc('dummy3').set({ message: 'success' });
        await adminDb.collection('/production/test/target')
            .doc('dummy4').set({ message: 'success' });
        await adminDb.collection('/production/test/test')
            .doc('target').set({ message: 'success' });
    });

目標のコレクションはtargetで、.where('message', '==', 'success')で絞り込む想定でデータを追加しています。

最上層や中間階層、最下層にtargetコレクションまたはドキュメントを含むコレクションに、それぞれドキュメントを追加しています。

動作確認用にドキュメントIDはそれぞれ違うものを設定しています。

テスト結果

1階層指定でテスト その1

このテストでは、どの階層のコレクションが指定できるかを確認しています。

テストコードは以下の通り

    test('1階層指定', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('target')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

targetコレクションのみで指定した場合のテストです。

結果は以下の通り

  console.log tests/collectionGroupTrial.test.ts:28
    dummy4 { message: 'success' }

最下層コレクションがtargetであるものだけが取得できました。

コレクションの指定は最下層コレクションだけのようです。

1階層指定でテスト その2

こちらのテストでは、複数のコレクションにまたがってドキュメントを取得できるかを確認しています。

テストは以下の通り

    test('1階層指定 2', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('test')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

ターゲットのコレクションにはtestを指定しています。

「1階層指定でテスト その1」で当てが外れたの追加したテストなので、targetみたいに名前に捻りがないです(^^;)

結果は以下の通り。

  console.log tests/collectionGroupTrial.test.ts:37
    dummy3 { message: 'success' }

  console.log tests/collectionGroupTrial.test.ts:37
    target { message: 'success' }

  console.log tests/collectionGroupTrial.test.ts:37
    dummy2 { message: 'success' }

  console.log tests/collectionGroupTrial.test.ts:37
    dummy1 { message: 'success' }

最下層コレクションがtestであるドキュメントが全て引っかかっています。

最初、ドキュメント名がtargetが入ってて、「あれ?」と思ったんですが、これも最下層のコレクション名はtestなので、引っかかっても問題なしです。

2階層指定でテスト

最後は、2階層指定でのテストです。

個人的には、これが気になってテストしてみたので、今回のメインという事になります。

対象のテストは以下の部分です。

    test('2階層指定', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('test/target')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

コレクションをtest/targetと2階層で指定しています。

結果は…

FirebaseError: Invalid collection ID 'test/target'
 passed to function Firestore.collectionGroup().
Collection IDs must not contain '/'.

おぅふ…

コレクションIDは/を含んじゃダメという事らしいです。

んー、残念

おやっ?Collection IDs must not contain '/'

Collection IDsという事は、もしかして複数指定ができるのだろうか?

というわけで、以下、おまけのテストです。

(がっくり来ている所に、追い打ちをかけられる様をお楽しみください。)

おまけ (複数コレクション指定)

一つ前のテストの複数コレクションの指定ができるかもという期待から、以下のテストを追加してみました。

    test('2コレクション指定 その1', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('target,test')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

    test('2コレクション指定 その2', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('(target|test)')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

    test('複数コレクション指定 その3', async () => {
        const db = supporter.getFirestore();
        const doc = db.collectionGroup('*')
            .where('message', '==', 'success');
        const snapshot = await doc.get()
        snapshot.forEach(function (doc) {
            console.log(doc.id, doc.data())
        })
    });

,|でコレクション名を区切ったり、ワイルドカードを試してみたりと、複数コレクションの指定を試しています。

,|に、最後はワイルドカード。お察し感が…

結果は以下の通り




あれっ?




あれあれっ?




見事に惨敗しました

Collection IDsとは何だったのか…

それとも、知らないだけで、何か指定方法があるのだろうか?

もし、知っている方がいらっしゃったら、コメントなどから教えてもらえるとありがたいです。

まとめ

最初の確認項目をまとめると以下の通り

  • どの階層のコレクションでも指定できるか? => No
  • ドキュメント名での指定はできるか? => No
  • 複数階層の指定はできるか? => No
  • 複数コレクションの指定はできるか? => No

_| ̄|○...

今回の結果は惨敗といった感じでしたが、できないという事がはっきりしただけでも良しとしたいと思います。