Whole-from query mutations — the ones expressible without Mutare's foreign-semantics
DSL host. Each returns a whole mutated from(...) node, which
Mutare's ordinary in-place selector wraps; the localized in-fragment mutations (operator
swaps inside a where, via ^/dynamic) arrive with the host extensions.
- Clause drop — remove one
where/having/or_where/or_havingclause from the query. "Is this filter tested?" Reuses the surviving clauses, so it always compiles, and works for both the binding form (from p in Post, where: p.active) and the bindingless form (from Post, where: [active: true]) since it operates on the clause list, not the clause value. - Order flip — flip an
order_bydirection (:asc↔:desc, and the*_nulls_*variants). "Does any test pin the sort direction?" - Bound (drop) — drop a
limit/offsetclause. "Is the window tested at all?" The family's other half, the±1bump of a literal bound, is hosted pin-only instead (Mutare.Ecto.Bound). Of a repeated bound (limit: 5, limit: 10) only the last — the one Ecto keeps — drops, uncovering the previous; the overridden one never reaches the query, so its drop would be equivalent (Mutare.Ecto.AST.FromCall.effective_clause?/2). - JoinType — narrow a join's kind by rewriting its clause key:
left_join→inner_join, andfull_join→left_join/right_join(plusleft_join↔right_joinsideways). "Does any test exercise the orphan row this join kind keeps that a narrower kind would drop?" An outer join is written because unmatched rows must survive, so seed data built for that reason is likely to already hold the orphan that makes the narrower kind disagree — a strong, killable mutation. The reverse (inner_join/join→left_join,*→full_join) is deliberately not offered: it widens a join the author picked precisely to exclude unmatched rows, and absent a reason to test for an orphan that shouldn't matter, the widened query usually returns identical rows — an equivalent mutant more often than a killable one. Theleft_join↔right_joinswap andfull_join→left_joinare portable (every adapter supportsINNER/LEFT);full_join→right_joinand theRIGHTleg ofleft_join↔right_joinare dialect-gated (:postgres/:mysql— SQLite lacksRIGHT JOIN). - Combination — swap a set-operation clause's key:
intersect:↔except:andintersect_all:↔except_all:. "Does any test pin which rows the combination keeps?" The pairing, its portability, and theunionexclusion are the sharedMutare.Ecto.Combinationcatalog's. - Aggregate (in
select/order_by) — swap an aggregate inside aselect/select_mergeororder_byclause value (sum↔avg,min↔max), via the sharedMutare.Ecto.Aggregatewalker. "Does any test pin which aggregate the column is reduced/sorted by?" (An aggregate inside ahavingis delivered through the host instead — seeMutare.Ecto.Host.) - Scalar (in
select/order_by) — mutate a value-computing form inside aselect/select_mergeororder_byclause value, via the sharedMutare.Ecto.Scalarcatalog: the arithmetic swaps (+↔-,*↔/; "does any test pin the computed value?") and the coalesce fallback drop (coalesce(x, d)→x; "does any test exercise the NULL row the default is for?"). (The same forms inside awhere/havingcondition are hosted instead, alongside the operator swaps.) - Binding reorder (source list) — when the source declares a positional binding list
(
from [a, b] in q, …), transpose each pair of those bindings ([a, b]→[b, a]). "Did the author bind the sources in the right order?" The list was written at the whole-fromlevel, so its reorder is delivered here — the rule and its policy areMutare.Ecto.BindingReorder's.
Each mutation is returned as a Mutare.Ecto.Tag: its label is the finer operator/kind a swap
family names (order/join/aggregate — nil for a structural drop), and its attribution
(Mutare.Mutator.Mutation.at/2/at_drop/1) names the inner clause the rewrite changed,
so the site is reported there rather than at the whole from (see Mutare.Ecto.Walk on
attribution). config carries dialects: for the join gate. The from is read apart and
rebuilt through Mutare.Ecto.AST.FromCall, which keeps the written form (and owns the
emptied-clause-list rule). A scalar from/1 (from(Post), no clauses) yields nothing, while
a source binding list (from([a, b] in query)) can still reorder.
Summary
Types
One whole-from producer, named in Mutare.Ecto.Surface's vocabulary: a from_drop family
walked over the clause list (:filter_drop, :bound), a from capability walked over the
clauses carrying it (the :ordering/:aggregate/:scalar value swaps, the
:join_type/:combination key swaps), or the source-level :binding_reorder.
Functions
Whole-from mutations for a from(...) node as self-tagging Mutare.Ecto.Tags (the label the
finer operator/kind for a swap family, nil for a structural drop; the attribution the inner
clause the site is reported at), or [].
The whole-from mutations for from under an already-resolved %Config{} — the body
mutations/2 delegates to — restricted to producers (default: every producer, in
mutations/2's order) over the clauses whose key clause? admits (default: every clause;
:binding_reorder rewrites the source, not a clause, so the predicate never reaches it).
Types
@type producer() ::
:filter_drop
| :bound
| :ordering
| :join_type
| :combination
| :aggregate
| :scalar
| :binding_reorder
One whole-from producer, named in Mutare.Ecto.Surface's vocabulary: a from_drop family
walked over the clause list (:filter_drop, :bound), a from capability walked over the
clauses carrying it (the :ordering/:aggregate/:scalar value swaps, the
:join_type/:combination key swaps), or the source-level :binding_reorder.
Functions
@spec mutations(Mutare.Ecto.AST.QueryCall.t(), Mutare.Ecto.Context.t()) :: [
Mutare.Ecto.SubMutator.tagged()
]
Whole-from mutations for a from(...) node as self-tagging Mutare.Ecto.Tags (the label the
finer operator/kind for a swap family, nil for a structural drop; the attribution the inner
clause the site is reported at), or [].
@spec mutations_for( Mutare.Ecto.AST.FromCall.t(), Mutare.Ecto.Config.t(), [producer()], (atom() -> boolean()) ) :: [Mutare.Ecto.SubMutator.tagged()]
The whole-from mutations for from under an already-resolved %Config{} — the body
mutations/2 delegates to — restricted to producers (default: every producer, in
mutations/2's order) over the clauses whose key clause? admits (default: every clause;
:binding_reorder rewrites the source, not a clause, so the predicate never reaches it).
Exposed so Mutare.Ecto.Subquery can compose exactly the producers a subquery wrapper
observes into an inner from (where it holds the parsed FromCall and config, not a full
callback context): the row-set producers under every wrapper, and the select projection's
:aggregate/:scalar swaps under a value-wrapper only — rather than running all eight and
filtering what it never wanted. An unknown producer is a programming error and fails loudly.