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

Validates that `left_join` bindings are not null-rejected by root `where` predicates.

A `where` predicate on a left-joined binding usually turns the join into an
inner join because rows without a matching joined record have `NULL` values
for that binding. Optional joined-record filters belong in the join `on`
clause instead.

## Examples

Bad:

    from(Post, as: :post)
    |> join(:left, [post: p], c in Comment,
      as: :comment,
      on: c.post_id == p.id
    )
    |> where([comment: c], c.status == ^:published)

Why this is bad:

Rows without a matching comment have `NULL` values for the joined binding.
The root `where` predicate rejects those rows, so the left join behaves like
an inner join.

Better:

    from(Post, as: :post)
    |> join(:left, [post: p], c in Comment,
      as: :comment,
      on: c.post_id == p.id and c.status == ^:published
    )

Why this is better:

The optional comment filter stays in the join predicate. Posts are preserved
even when no matching published comment exists.

## Notes

This check detects supported direct field predicates on left-join bindings. It
does not prove predicates hidden inside fragments, subqueries, or arbitrary
functions.

## Options

  * `:validate` - explicit `false` disables the check. Defaults to `true`.

## 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.

The check is static and intentionally supports a small, tested subset of
Ecto's query AST. It detects direct left-join binding fields in comparisons,
`in` predicates, bare predicates, and `not is_nil(field)`. It does not try to
prove predicates hidden inside fragments, subqueries, or arbitrary functions.
Null-preserving anti-join predicates such as `is_nil(left_binding.id)` are
allowed.

# `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*
