
# クエリビルダー

`DocStore`と`SegmentedLog`は`query()`を提供します。`DocStore`はセカンダリーインデックスを利用できます。`SegmentedLog`はストリーミングされたレコードに同じ条件を適用します。

```php
$records = $docs
    ->query()
    ->where('kind')->eq('page')
    ->where('status')->in(['draft', 'published'])
    ->where('publishedAt')->between($from, $until)
    ->orderBy('publishedAt', 'desc')
    ->limit(50)
    ->get();
```

便利な終端メソッド：

```php
$docs->query()->where('slug')->eq('home')->first();
$docs->query()->where('kind')->eq('page')->count();
$docs->query()->cursor($lastId)->page(100)->get();
$docs->query()->where('slug')->eq('home')->explain();
```

`DocStore`では、一意でない等価インデックス付きフィールドに対する`eq`条件を2つ指定すると、自動複合バケットを利用できます。単一の等価条件、`in`、範囲演算子は、引き続き設定済みのフィールドインデックスを使います。

演算子：

- `eq`, `neq`
- `in`, `notIn`
- `gt`, `gte`, `lt`, `lte`, `between`
- `exists`, `missing`
- `prefix`
- `andWhere`, `orWhere`
- `orderBy`, `limit`, `cursor`, `page`

`andWhere`と`orWhere`は、`and`と`or`が予約済みのPHPキーワードであるため、コールバックを使います。

```php
$docs->query()
    ->where('kind')->eq('page')
    ->orWhere(fn ($q) => $q->where('kind')->eq('post')->where('featured')->eq(true))
    ->get();
```

連結した`where()`の呼び出しはAND条件になり、各`orWhere()`分岐は別の選択肢を追加します。

## 処理方法

すべての条件はどのコレクションでも使えます。インデックスによって変わるのは処理コストだけです。`DocStore`では、`eq`と`in`は等価インデックスで処理されます。範囲演算子と`prefix`は範囲インデックスで処理されます。また、範囲インデックス付きフィールドに対する`orderBy`と`limit`は、インデックス順に読み取ります。それ以外はレコードスキャンで絞り込みます。プランナーがインデックスを使えないフィールドに対する`orderBy`は、一致したレコードをPHPでソートします。大規模なコレクションでは、インデックス付き条件と組み合わせてください。フィルターが単一の等価条件の場合、`count()`は`DocStore`のインデックス項目数と、`SegmentedLog`のメモリ内カウンターを使います。

`cursor($id)`は指定したIDより後のレコードに結果を絞り込みます。`page()`と組み合わせると、レコードID順の安定したページネーションができます。ビルダーを使わない通常のストリーミングフィルターについては、[クエリ](https://storh.inline0.dev/ja/docs/queries)をご覧ください。
