# `Bylaw.Ecto.Query.Checks.HalfOpenTemporalIntervals`
[🔗](https://github.com/ryanzidago/bylaw/blob/v0.1.0-alpha.1/lib/bylaw/ecto/query/checks/half_open_temporal_intervals.ex#L1)

Validates that root temporal interval predicates are half-open.

Half-open temporal intervals include the start boundary and exclude the end
boundary.

## Examples

Bad:

    from(Event, as: :event)
    |> where([event: e], e.occurred_at > ^start_at)
    |> where([event: e], e.occurred_at <= ^end_at)

Why this is bad:

The query excludes events exactly at `start_at` and includes events exactly at
`end_at`. Adjacent windows built this way leave a gap at the start boundary
and can double-count rows at the end boundary.

Better:

    from(Event, as: :event)
    |> where([event: e], e.occurred_at >= ^start_at)
    |> where([event: e], e.occurred_at < ^end_at)

Why this is better:

The query uses a half-open interval, `[start_at, end_at)`. Adjacent windows
compose without gaps or overlap because each boundary value belongs to exactly
one window.

## Notes

This check inspects direct root temporal field comparisons in `where`
expressions. It does not prove interval correctness when boundaries are hidden
inside fragments, non-root bindings, subqueries, or field-to-field
comparisons.

This catches the common off-by-one interval boundary shapes `>` for a lower
bound and `<=` for an upper bound on root temporal fields.

## Options

  * `:validate` - explicit `false` disables the check. Defaults to `true`.
  * `:fields` - optional non-empty list of root fields to validate. When
    omitted, the check validates temporal fields reflected from the root Ecto
    schema.

Example check spec:

    {Bylaw.Ecto.Query.Checks.HalfOpenTemporalIntervals,
     fields: [:inserted_at, :updated_at]}

The check is static. It inspects direct root field comparisons in `where`
expressions and ignores field-to-field comparisons, non-root bindings,
fragments that hide field access, and schema-less queries without configured
fields.

## Usage

Add this module to the explicit check list passed through `Bylaw.Ecto.Query`.
See `Bylaw.Ecto.Query` for the full `c:Ecto.Repo.prepare_query/3` setup.

# `validate`

```elixir
@spec validate(
  Bylaw.Ecto.Query.Check.operation(),
  Bylaw.Ecto.Query.Check.query(),
  opts()
) ::
  Bylaw.Ecto.Query.Check.result()
```

Implements the `Bylaw.Ecto.Query.Check` validation callback.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
