# `Patterns.Queryable.Filters`
[🔗](https://github.com/minnasoft/patterns/blob/v0.0.1/lib/patterns/queryable/filters.ex#L1)

Default filter implementations for `Patterns.Queryable` modules.

`apply_filter/2` turns common filter tuples into Ecto query expressions so a
`Patterns.Queryable` module can focus on custom filters and delegate the rest.

## Query Modifiers

Query modifiers affect only the top-level query. They are ignored inside
association filters.

Supported modifiers are:

* `{:distinct, value}`
* `{:limit, value}`
* `{:offset, value}`
* `{:preload, value}`
* `{:select, value}`

Modifier values are forwarded to Ecto's query DSL, so Ecto syntax like
`{:preload, :comments}`, `{:preload, [comments: :post]}`, and
`{:distinct, true}` is supported. Atom selects are treated as fields on the
current binding, so `{:select, :id}` selects the current binding's `id` field.
Non-atom selects are passed through to Ecto as values. Distinct values are
forwarded to Ecto; use `{:distinct, true}` as the portable parent-deduplication
form for association filters.

Modifier behavior is delegated to Ecto and the configured adapter. SQL shape
and support can differ between adapters such as `ecto_sqlite3` and PostgreSQL,
especially for non-boolean `distinct` values and complex preloads.

## Field Comparators

Field comparators apply predicates to the current binding.

Supported comparators are:

* `{field, value}` and `{field, {:eq, value}}`
* `{field, nil}` and `{field, {:eq, nil}}`
* `{field, [value]}` and `{field, {:in, [value]}}`
* `{field, %Regex{}}` and `{field, {:like, %Regex{}}}`
* `{field, {:not, value}}`
* `{field, {:not, nil}}`
* `{field, {:not, [value]}}` and `{field, {:not_in, [value]}}`
* `{field, {:not, %Regex{}}}` and `{field, {:not_like, %Regex{}}}`
* `{field, {:gt, value}}`
* `{field, {:gte, value}}`
* `{field, {:lt, value}}`
* `{field, {:lte, value}}`
* `{field, {:like, pattern}}`
* `{field, {:not_like, pattern}}`

Unsupported comparator tuples raise `ArgumentError` instead of silently
becoming equality comparisons against the tuple value.

### Regex Values

> #### Regex shorthand is LIKE shorthand {: .warning}
>
> Regex values are shorthand for SQL `LIKE` patterns, not full database regex
> predicates. They are converted by translating `.*` to `%` and `.` to `_`.
> Unanchored regexes are padded with `%`; `^` and `$` suppress leading and
> trailing padding respectively.

The `i` regex option lower-cases the field and generated `LIKE` pattern.
Other regex options raise `ArgumentError`.

Matching semantics are still delegated to the database adapter and collation.
For example, plain SQL `LIKE` case-sensitivity can differ between SQLite and
PostgreSQL.

Literal `%` and `_` are rejected in regex shorthand because they are SQL `LIKE`
wildcards. Escaped regex syntax is not supported.

Regex syntax that cannot be represented by this simple conversion raises
`ArgumentError`.

## Association Comparators

Association comparators use `{field, filters}` where `filters` is a map or
keyword-shaped list and `field` is an Ecto association.

Empty filters like `{:comments, []}` and `{:comments, %{}}` still join the
association. When `Patterns.Queryable` adds a new inner join, they match
parents with at least one associated row. When an existing named binding is
reused, that binding's join semantics are preserved.

Treating map and keyword values as association filters is an implementation
detail. Future versions may support map equality for field types that can
encode and compare maps directly, such as PostgreSQL `jsonb` fields.

Association filters require a schema-backed query source because they inspect
association metadata from the current scoped binding. Raw string sources and
subquery sources cannot use association filters.

Join generation and association metadata are delegated to Ecto. Adapter SQL
output may differ, but the filter semantics are based on Ecto's `assoc/2` join
behavior.

Nested association filters are resolved from the current scoped binding, so
`{:comments, [post: [title: "Hello"]]}` filters `:post` as an association of
the joined `:comments` binding.

Association filters use Ecto's `assoc/2` join syntax and keep normal join
semantics. For `has_many` and `many_to_many` associations, one parent row is
returned for each matching associated row. Pass `{:distinct, true}` when parent
row uniqueness matters.

> #### Named binding reuse {: .warning}
>
> If the query already has a named binding for the association, that binding is
> reused instead of adding another join. Reusing an existing binding preserves
> that binding's join semantics, including left-join behavior. Named bindings are
> query-global, so nested association filters can reuse an existing binding with
> the same association name. Implement custom `query/2` clauses when a nested
> association path needs stricter binding control.

When the associated schema exports `query/2`, nested filters are delegated to
that function against the joined association binding. Otherwise, each nested
filter is applied directly to the joined association binding.

Binding resolution uses `Patterns.Queryable.DSL.binding_schema/1`, so nested
association filters are resolved from the current scoped binding rather than
always from the root query source.

# `apply_filter`

```elixir
@spec apply_filter(
  Ecto.Queryable.t(),
  {field :: atom(), value :: term()}
) :: Ecto.Queryable.t()
```

Applies a single query filter.

See the module documentation for supported filter shapes.

---

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