Bylaw.Ecto.Query.Checks.LeftJoinWherePredicates (bylaw_ecto_query v0.1.0-alpha.1)

Copy Markdown View Source

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

Summary

Functions

validate(operation, query, opts)

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