
# クエリ

`RecordQuery`は`stream()`と組み合わせて使うストリーミングフィルターです。追記型のアクセスパターンに対応し、カーソル以降の再開、時間範囲の読み取り、フィールドの等価条件による絞り込み、件数制限による終了ができます。より複雑な条件、並べ替え、インデックスを使った検索には[クエリビルダー](https://storh.inline0.dev/ja/docs/query-builder)を使ってください。ストリームに対する通常の`foreach`でできる操作だけなら、`RecordQuery`のほうが低コストです。

`RecordQuery`は不変です。各修飾メソッドはクエリの複製を返します。

```php
$query = Storh\RecordQuery::all()
    ->after($lastSeenId)
    ->time_range_ms($fromMs, $untilMs)
    ->where_equal('kind', 'page')
    ->limit(100);

foreach ($store->stream($query) as $record) {
    // Records are yielded lazily.
}
```

`after()`と`time_range_ms()`はUUID順で処理されるため、レコードデータにタイムスタンプフィールドは不要です。`SegmentedLog`では処理も省けます。IDの範囲が時間範囲外のセグメントは開かず、セグメントごとのスパースインデックスで該当レコードの近くまでシークするため、先頭からスキャンする必要はありません。

`where_equal()`はトップレベルのデータフィールドを`===`と比較し、スカラー値または`null`を受け付けます。`null`フィルターは、フィールドが存在して値がnullのレコードに一致します。フィールドがないレコードには一致しません。

## カーソルページネーション

IDは作成時刻順に並ぶため、あるページの最後のIDを次のページのカーソルにできます。

```php
$page = iterator_to_array($store->stream(
    Storh\RecordQuery::all()->after($lastSeenId)->limit(100)
), false);

$lastSeenId = [] === $page ? $lastSeenId : end($page)->id();
```

## 破損レコードを含めた読み取り

既定では、スキャン中に読み取れないレコードが見つかると、そこで例外が発生します。スキャンを続けて失敗を報告するには、代わりに`continue_on_error()`を使います。

```php
$query = Storh\RecordQuery::all()->continue_on_error(
    static function (string $location, Throwable $error): void {
        error_log($location . ': ' . $error->getMessage());
    },
);
```

`DocStore`と`segment-file:byte-offset`では位置がレコードIDを示し、`SegmentedLog`では`repair()`を示します。破損レコードを実際にクリーンアップするには、[CLI](https://storh.inline0.dev/ja/docs/cli)を使用してください。
