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.
- Filter 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. - Clause drop — remove one
group_by/distinct/preload/lock/select_merge/with_tiesclause, or one set operation (union:,except:, …): thefrom-keyword twin ofMutare.Ecto.ClauseDrop's stage drop, under the same family. "Is what this clause contributes tested?" Afromexpands as a whole when the metamutant compiles, so a key drops here only if the rest of the list cannot need it — it binds no variable, nothing names it, and no plan requires it. That holds the pipeline's other drops back: a join binds a variable the other clauses read, so its drop would fail the single build unless the binding were first proved unreferenced; aselect:is required by a schemaless source, anupdate:byupdate_all, and awindows:is named byover/2. What remains can still depend on a dropped clause at the engine — aselectthat mixes an aggregate with a plain column needs itsgroup_byon Postgres — which raises under that one mutant only (Mutare.Ecto.ClauseDrop, "A drop weakens the query, or breaks it"). - 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 include an orphan for which the narrower kind returns different rows — a 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). The flips are the sharedMutare.Ecto.JoinTypecatalog's, keyed by join qualifier, whichMutare.Ecto.Clauseapplies to a standalonejoin(q, :left, …)under this same policy. - Combination — swap a set-operation clause's key:
intersect:↔except:andintersect_all:↔except_all:. "Does any test pin which rows the combination keeps?" The sharedMutare.Ecto.Combinationcatalog defines the pairing, its portability, and theunionexclusion. - 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 — seeMutare.Ecto.BindingReorderfor the rule and its policy.
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 preserves the written form and handles
emptied clause lists. 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, :clause_drop), 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 satisfies clause? (default: every clause;
:binding_reorder rewrites the source, not a clause, so the predicate never reaches it).
Types
@type producer() ::
:filter_drop
| :bound
| :clause_drop
| :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, :clause_drop), 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 satisfies clause? (default: every clause;
:binding_reorder rewrites the source, not a clause, so the predicate never reaches it).
Exposed so Mutare.Ecto.Subquery can mutate an inner from using just the producers whose
mutations can affect the wrapper's result (with 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 out inapplicable mutations. An unknown producer is a programming error and raises.