
# 쿼리

`RecordQuery`는 `stream()`와 함께 사용하는 스트리밍 필터입니다. 커서 이후부터 재개하거나, 시간 범위를 읽거나,
필드 값이 일치하는 항목을 필터링하거나, 제한에 도달하면 중지하는 등 추가 위주의 접근 패턴을 지원합니다. 더 풍부한 조건,
정렬 및 인덱스 기반 조회에는 [쿼리 빌더](https://storh.inline0.dev/ko/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/ko/docs/cli)입니다.
