A small, safe filter language that becomes a SQL WHERE, for the listing functions of
DDD.Repository.Ecto adapters and DDD.Projections (list/1, page/1, slice/1,
count/1).
[owner_id: id, status: :open] # all of them (AND)
[open_count: {:gt, 3}] # a comparison
{:or, [[status: :open], [title: {:like, "Buy%"}]]}
{:not, [status: :done]}
[{:spec, MyApp.Specs.Overdue}] # a specification, see belowForms (field is an atom, value is in its domain form: atoms, Dates, ids as strings):
| form | meaning |
|---|---|
{field, value} | equal (nil means IS NULL) |
{field, {op, value}} or {field, op, value} | op is :eq, :neq, :gt, :gte, :lt, :lte, :in, :not_in, :like, :ilike |
[filter, ...] or a keyword list or a map | every one of them |
{:and, [filter]}, {:or, [filter]}, {:not, filter} | combinators (so :and, :or, :not can't be field names here) |
{:spec, spec} | a DDD.Specification translated by DDD.Specification.to_filter/2 |
Values are never interpolated into SQL: they are bound parameters, converted with
DDD.Type.dump/2 the same way find_by_* does. An unknown field, an unknown operator or a
malformed expression is a business error with code :invalid_filter (so a filter built from a
client's request can be answered with it), never a crash or an injection.
Summary
Functions
Build the Ecto.Query.dynamic/2 for filter. types maps each field to its domain type
(%{title: :string, ...}); only those fields can be filtered on.
Functions
@spec build(term(), %{required(atom()) => term()}) :: {:ok, Ecto.Query.dynamic_expr()} | {:error, DDD.Error.t()}
Build the Ecto.Query.dynamic/2 for filter. types maps each field to its domain type
(%{title: :string, ...}); only those fields can be filtered on.