メインコンテンツまでスキップ

クエリ

リアルタイムクエリのサポートにより、複数のデータベース間のデータ結合を含め、あらゆるソースのデータに対してきめ細かなクエリを作成できます。
Note

Squid はストリームと observable の処理に RxJs を使用します。RxJS とストリーミング更新については、 RxJs documentation を参照してください。

ドキュメントをクエリする際、単一のスナップショット、またはスナップショットのストリームを利用することを選べます。

  • スナップショットを利用する場合、ドキュメントの最新バージョンを Promise として受け取ります。
  • スナップショットのストリームを利用する場合、結果は RxJsObservable となり、クエリ結果が変化するたびに新しいスナップショットを emit します。

Squid Client SDK におけるスナップショットとデータストリームの利用により、最小限のオーバーヘッドとセットアップで、データソースからのリアルタイム更新を継続的に受け取れます。

単一ドキュメントのクエリ

単一ドキュメントをクエリするには、document reference に対して snapshot または snapshots メソッドを呼び出します。その後 data getter を使用してドキュメントのデータにアクセスできます。

Client code
const docRef = await squid.collection<User>('users').doc('user_id').snapshot();
if (docRef) {
console.log(docRef.data);
}

または、snapshots メソッドを使ってこのドキュメントの変更を subscribe できます。ドキュメントが変更されるたびに、observable が新しい値を emit します。

Client code
squid
.collection<User>('users')
.doc('user_id')
.snapshots()
.subscribe((docRef) => {
console.log(docRef.data);
});
クエリのセキュア化

Squid の backend security rules により、各クエリを実行できるユーザーを制御できます。これらのルールは QueryContext をパラメータとして受け取り、その中にクエリが含まれます。特定のコレクションや database connector への read アクセスを制限するように backend security を設定してください。データベースに対する read/write 権限を制限する方法については、security rules のドキュメント を参照してください。

コレクションから複数ドキュメントをクエリ

コレクションからドキュメントをクエリする場合は、query メソッドを使用してクエリを構築します。

Squid では、snapshot メソッドで単一のクエリ結果を取得するか、snapshots メソッドでクエリ結果のストリームを取得するかを選べます。この方法でクエリ結果のストリームを利用すると、クエリ結果が変化するたびに observable が新しい値を emit します。

次は、18歳より上の admin をすべて返す単一のクエリスナップショットを取得する例です。

Client code
const users = await squid.collection<User>('users').query().gt('age', 18).eq('role', 'admin').snapshot();

次の例は、snapshots メソッドを使ってストリーミングのクエリ結果を受け取ります。

Client code
const usersObs = squid.collection<User>('users').query().gt('age', 18).eq('role', 'admin').snapshots();

/* Subscribe to the users observable, and log the data each time a new value is received */
usersObs.subscribe((users) => {
console.log(
'Got new snapshot:',
users.map((user) => user.data)
);
});

Query は changes メソッドで変更のストリームを返すこともサポートします。このメソッドが返す observable には、コレクションに加えられた変更を追跡する 3 つの配列が含まれます。

  • inserts: コレクションへの新規挿入に対応する document reference を含みます
  • updates: コレクション内の更新に対応する document reference を含みます
  • deletes: 削除されたドキュメントのデータを含みます
Client code
const usersObs = squid.collection<User>('users').query().gt('age', 18).eq('role', 'admin').changes();

usersObs.subscribe((changes) => {
// Logs all new insertions into the collection of admins who are 18+
console.log(
'Inserts:',
changes.inserts.map((user) => user.data)
);
// Logs new updates in the collection where the user is now an admin who is 18+
console.log(
'Updates:',
changes.updates.map((user) => user.data)
);
// The deletes array contains the actual deleted data without a doc reference
console.log('Deletes:', changes.deletes);
});

document reference のデリファレンス

コレクションからデータをクエリすると、document reference を受け取ります。data getter を呼び出すことでドキュメントのデータにアクセスできます。

Client code
const usersObs = squid
.collection<User>('users')
.query()
.snapshots()
.pipe(map((user) => user.data));

data getter を呼ばずにドキュメントデータを直接受け取るには、dereference メソッドを呼び出します。

Client code
const usersDataObs = squid.collection<User>('users').query().dereference().snapshots();

コレクションおよび connector をまたいだデータの結合

Squid では複数のクエリを join し、結果の変更を監視できます。この機能は、異なるデータソース間のデータを join できることでさらに強力になります。

たとえば、dept コレクションと employees コレクションを join し、18歳より上の従業員を部署情報付きで返すクエリを作成できます。

Client code
const departmentCollection = squid.collection<Dept>('dept');
const employeeCollection = squid.collection<Employee>('employees');
const employeeQuery = employeeCollection.query().gt('age', 18);

const joinObs = departmentCollection
.joinQuery('d')
.join(employeeQuery, 'e', {
left: 'id',
right: 'deptId',
})
.snapshots();

joinObs.subscribe((joinResult) => {
// Use the join result here
});

上記コードでは、各クエリにエイリアスが割り当てられています。dept コレクションには demployees コレクションには e です。join 条件は、employees コレクションの deptId フィールドと、dept コレクションの id フィールドを結合します。

この例では組み込みデータベースの 2 つのコレクションを join していますが、コレクション参照で connector ID を指定することで、別々の database connector 間でも join できます。たとえば connector ID が connectorAconnectorB の 2 つの database connector がある場合、次のように connector ID を追加することで同じ join を実行できます。

Client code
const departmentCollection = squid.collection<Dept>('dept', 'integrationA');
const employeeCollection = squid.collection<Employee>('employees', 'integrationB');

デフォルトでは、Squid は left join を実行します。つまり、この例では空の部署も含め、すべての部署が join 結果に含まれます。たとえば、部署 A には 18 歳超の人が 2 人いるが、部署 B には 18 歳超の人がいないとします。join クエリを実行すると、結果は次のようになります。

Client code
type ResultType = Array<{
d: DocumentReference<Dept>;
e: DocumentReference<Employee> | undefined;
}>;

joinResult ===
[
{ d: { data: { id: 'A' } }, e: { data: { id: 'employee1' } } },
{ d: { data: { id: 'A' } }, e: { data: { id: 'employee2' } } },
{ d: { data: { id: 'B' } }, e: undefined },
];

undefined のデータを含む結果を除外するには inner join を行います。inner join を行うには、join メソッドの第 4 引数として { isInner: true } を渡します。次の例では、部署 B は返りません。

Client code
const departmentCollection = squid.collection<Dept>('dept');
const employeeCollection = squid.collection<Employee>('employees');
const employeeQuery = employeeCollection.query().gt('age', 18);

const joinObs = departmentCollection
.joinQuery('d')
.join(
employeeQuery,
'e',
{
left: 'id',
right: 'deptId',
},
{
isInner: true,
}
)
.snapshots();

joinObs.subscribe((joinResult) => {
// Use the join result here
});

type ResultType = Array<{
d: DocumentReference<Dept>;
e: DocumentReference<Employee>; // Note no `| undefined`
}>;

joinResult ===
[
{ d: { data: { id: 'A' } }, e: { data: { id: 'employee1' } } },
{ d: { data: { id: 'A' } }, e: { data: { id: 'employee2' } } },
];

3 つのコレクション間で join を書くには、クエリにもう 1 つ join を追加します。たとえば employeesdeptcompany のコレクションがある場合、次の join を実行できます。

Client code
const departmentCollection = squid.collection<Dept>('dept');
const employeeCollection = squid.collection<Employee>('employees');
const employeeQuery = employeeCollection.query().gt('age', 18);
const companyQuery = squid.collection<Company>('company').query();

const joinObs = departmentCollection
.joinQuery('d')
.join(employeeQuery, 'e', {
left: 'id',
right: 'deptId',
})
.join(companyQuery, 'c', {
left: 'companyId',
right: 'id',
})
.snapshots();

上記例では、employeesdept、および deptcompany を join します。結果のオブジェクトは次の型になります。

Client code
type Result = Array<{
e: DocumentReference<Employee>;
d: DocumentReference<Dept> | undefined;
c: DocumentReference<Company> | undefined;
}>;

join の左側を選ぶ

join の左側を選ぶには、join メソッドの第 4 引数 options オブジェクトに leftAlias を渡します。たとえば employeedept を join し、さらに employeecompany を join したい場合、次のように company コレクションの join で左側を選びます。

Client code
const departmentCollection = squid.collection<Dept>('dept');
const employeeCollection = squid.collection<Employee>('employees');
const employeeQuery = employeeCollection.query().gt('age', 18);
const companyQuery = squid.collection<Company>('company').query();

const joinObs = departmentCollection
.joinQuery('d')
.join(employeeQuery, 'e', {
left: 'id',
right: 'deptId',
})
.join(companyQuery, 'c', {
{ left: 'companyId', right: 'id' },
{ leftAlias: 'e' }
)
.snapshots();

join 結果のグルーピング

重複エントリをまとめて join 結果をグループ化するには、join クエリで grouped() メソッドを呼び出します。 たとえば employeesdept を join し、さらに deptcompany を join する際、grouped を使うことでユーザーごとに 1 つのエントリだけを受け取れます。

Client code
const departmentCollection = squid.collection<Dept>('dept');
const employeeCollection = squid.collection<Employee>('employees');
const employeeQuery = employeeCollection.query().gt('age', 18);
const companyQuery = squid.collection<Company>('company').query();

const joinObs = departmentCollection
.joinQuery('d')
.join(employeeQuery, 'e', {
left: 'id',
right: 'deptId',
})
.join(companyQuery, 'c', {
left: 'companyId',
right: 'id',
})
.grouped()
.snapshots();

grouped() メソッドなしでは、このクエリは次の型の結果を返します。

Client code
type Result = Array<{
// The same user may return more than once (one for each department and company)
e: DocumentReference<Employee>;
// The same department may return more than once (one for each company)
d: DocumentReference<Dept> | undefined;
c: DocumentReference<Company> | undefined;
}>;

grouped() メソッドありでは、このクエリは次の型の結果を返します。

Client code
type Result = Array<{
e: DocumentReference<Employee>;
d: Array<{
d: DocumentReference<Dept>;
c: Array<DocumentReference<Company>>;
}>;
}>;

grouped() メソッドは dereference() と一緒に使うことで、DocumentReference なしで結果データを取得できます。

Client code
const departmentCollection = squid.collection<Dept>('dept');
const employeeCollection = squid.collection<Employee>('employees');
const employeeQuery = employeeCollection.query().gt('age', 18);
const companyQuery = squid.collection<Company>('company').query();

const joinObs = departmentCollection
.joinQuery('d')
.join(employeeQuery, 'e', {
left: 'id',
right: 'deptId',
})
.join(companyQuery, 'c', {
left: 'companyId',
right: 'id',
})
.grouped()
.dereference()
.snapshots();

このクエリは次の型の結果を返します。

Client code
type Result = Array<{
e: Employee;
d: Array<{
d: Dept;
c: Array<Company>;
}>;
}>;

制限とソート

Squid はクエリのソートと制限(limit)を行う機能を提供しており、アプリケーションのパフォーマンス最適化やユーザー体験の改善に役立ちます。

クエリをソートするには sortBy メソッドを使用し、ソート対象のフィールドと、任意でソート順を指定します。ソート順を指定しない場合、クエリはデフォルトで昇順になります。

次は年齢を降順でソートする例です。

Client code
const users = await squid.collection<User>('users').query().gt('age', 18).eq('role', 'admin').sortBy('age', false).snapshot();

クエリが返す結果数を制限するには limit メソッドを使用し、返す結果の最大数を指定します。limit を指定しない場合、クエリはデフォルトで 1000 になり、これが許可される最大値でもあります。

次は結果を 10 件に制限する例です。

Client code
const users = await squid.collection<User>('users').query().gt('age', 18).eq('role', 'admin').limit(10).snapshot();

次の例のように、同じクエリでソートと制限を組み合わせることもできます。

Client code
const users = await squid.collection<User>('users').query().gt('age', 18).eq('role', 'admin').sortBy('age', false).limit(10).snapshot();

別の機能として limitBy があります。これは、fields 内の各フィールドで同じ値を持つドキュメントについて、先頭から limit 件のみを返します。これにより「各都市ごとに最年少のユーザーを 5 人返す」といったクエリが可能になります(例を参照)。

Client code
const users = await squid.collection<User>('users').query().sortBy('state').sortBy('city').sortBy('age').sortBy('name').limitBy(5, ['state', 'city']).snapshot();

返されるクエリは、statecity の組み合わせごとに最大 5 件のドキュメントを持ちます。実質的に、このクエリは各都市の最年少ユーザー 5 人を返します(年齢の同値は name でタイブレークされます)。

Note

limitBy 句の fields に含まれるすべてのフィールドは、クエリ内の最初の n 個の sortBy 句に登場している必要があります(n はフィールド数)。上記例では、statecitylimitBy に登場する)はクエリ内で sortBy() を持つ必要があり、さらにそれらの sortByagename のものより前にある必要があります。

ページネーション

Squid は、クエリの paginate メソッドを通じて、クエリ結果をページネーションする強力な方法を提供します。 paginate メソッドは PaginationOptions オブジェクトをパラメータとして受け取り、次のプロパティを含みます。

  • pageSize: デフォルト 100 の数値。
  • subscribe: クエリのリアルタイム更新に subscribe するかどうかを示す boolean。デフォルトは true

呼び出すと、paginate メソッドは次のプロパティを持つ Pagination オブジェクトを返します。

  • observeState: 現在のページネーション状態(PaginationState)を emit する observable。
  • next: 次のページ状態で解決される promise を返す関数。
  • prev: 前のページ状態で解決される promise を返す関数。
  • waitForData: ローディング処理が完了した後、現在のページネーション状態で解決される promise を返す関数。
  • unsubscribe: ページネーションオブジェクトに対し、クエリの unsubscribe と内部状態のクリアを指示する関数。

PaginationState オブジェクトには次のプロパティがあります。

  • data: 現在ページのデータを保持する配列。
  • hasNext: 次ページが利用可能かどうかを示す boolean。
  • hasPrev: 前ページが利用可能かどうかを示す boolean。
  • isLoading: ページネーションがデータをロード中かどうかを示す boolean。

次はページネーションの利用例です。

Client code
const pagination = (this.query = squid.collection<User>('users').query().gt('age', 18).eq('role', 'admin').sortBy('age', false).dereference().paginate({ pageSize: 10 }));

let data = await pagination.waitForData();
console.log(data); // Outputs the first page of data
data = await pagination.next();
console.log(data); // Outputs the second page of data
pagination.unsubscribe();

要求された場合、ページネーションオブジェクトはクエリのリアルタイム更新に能動的に subscribe し、最新の状態を維持します。つまり、PaginationState オブジェクトはデータが変化するたびに最新データで更新されます。

Note

リアルタイム更新を維持する、またはサーバー更新により空ページを受け取るといったエッジケースに対応するために、ページネーションオブジェクトは 1 ページを表示するために複数回クエリを実行する場合があります。

クエリヘルパー

ヘルパー関数を使うだけでなく、where 関数を使ってクエリを構築することもできます。where 関数は 3 つのパラメータ(クエリするフィールド、使用する演算子、比較対象の値)を受け取ります。

HelperBeforeAfter説明
eqwhere('foo', '==', 'bar')eq('foo', 'bar')foobar と等しいかを確認します
neqwhere('foo', '!=', 'bar')neq('foo', 'bar')foobar と等しくないかを確認します
inwhere('foo', 'in', ['bar'])in('foo', ['bar'])foo が指定リストに含まれるかを確認します
ninwhere('foo', 'not in', ['bar'])nin('foo', ['bar'])foo が指定リストに含まれないかを確認します
gtwhere('foo', '>', 'bar')gt('foo', 'bar')foobar より大きいかを確認します
gtewhere('foo', '>=', 'bar')gte('foo', 'bar')foobar 以上かを確認します
ltwhere('foo', '<', 'bar')lt('foo', 'bar')foobar より小さいかを確認します
ltewhere('foo', '<=', 'bar')lte('foo', 'bar')foobar 以下かを確認します
like (case sensitive)where('foo', 'like_cs', '%bar%')like('foo', '%bar%')foo がパターン %bar% に一致するかを確認します(CS)
likewhere('foo', 'like', '%bar%')like('foo', '%bar%', false)foo がパターン %bar% に一致するかを確認します(CI)
notLike (case sensitive)where('foo', 'not like_cs', '%bar%')notLike('foo', '%bar%')foo がパターン %bar% に一致しないかを確認します(CS)
notLikewhere('foo', 'not like', '%bar%')notLike('foo', '%bar%', false)foo がパターン %bar% に一致しないかを確認します(CI)
arrayIncludesSomewhere('foo', 'array_includes_some', ['bar'])arrayIncludesSome('foo', ['bar'])foo 配列が値の一部を含むかを確認します
arrayIncludesAllwhere('foo', 'array_includes_all', ['bar'])arrayIncludesAll('foo', ['bar'])foo 配列が値のすべてを含むかを確認します
arrayNotIncludeswhere('foo', 'array_not_includes', ['bar'])arrayNotIncludes('foo', ['bar'])foo 配列がいずれの値も含まないことを確認します