The condition the host cannot weave, and its whole-call delivery.
The host delivers a hosted condition's mutants by weaving: the condition becomes a
^-pinned dynamic/2 that re-declares the condition's bindings (Mutare.Ecto.Host). Two
kinds of condition cannot take that form, though each is valid as written.
A subquery in a having. Weaving changes how Ecto builds the clause — from the
compile-time filter builder to the runtime dynamic path — and the two do not accept the same
expressions:
from p in "posts",
group_by: p.user_id,
having: count(p.id) > subquery(from t in "thresholds", select: max(t.value))Written statically this is valid; as having: ^dynamic([p], …) Ecto raises "subqueries are
not allowed in having expressions". It raises when the query is built, not when the
module compiles, and it raises on the woven original branch as readily as on a mutant — so
weaving it would break the unmutated code path of every test that reaches the function.
A binding declaration the plugin cannot re-declare — a computed index ([{p, index}]) or
an interpolated name that calls a function ([{^name(), p}]), each of which a re-declaration
would evaluate a second time (Mutare.Ecto.Host.Bindings). The woven dynamic/2 has no
faithful binding list to carry, and an empty one would leave the condition's variables
unbound. A condition that is itself a ^ pin is the exception: it is woven pin-only, with no
dynamic/2 and so no bindings (Mutare.Ecto.Host.Target's root-pin rule), whatever the
declaration.
The rule
delivery/4 decides, for one condition, from the clause receiving it, the expression, its
predicate kind, and the declaration it is read under. A condition is rebuilt when the
expression carries a subquery (Mutare.Ecto.Subquery.present?/1) that the clause rejects in a
dynamic (Mutare.Ecto.Surface.dynamic_subqueries?/1 — everything but where/or_where), or when
it is not a :root_pin and its declaration does not read (t:Mutare.Ecto.Host.Bindings.result/0
is :error); every other condition is woven. Both the host and mutations/2 enumerate the
same conditions (Mutare.Ecto.Host.Condition) and ask delivery/4 of each, so each condition is
delivered once: woven, or rebuilt here.
Only a predicate is either's, and Mutare.Ecto.Host.Condition locates nothing else
(Mutare.Ecto.Host.Condition.shape/1). A keyword filter carries a subquery as readily
(having: [score: subquery(…)] — Ecto's filter builder accumulates a pair value's), and sits
under an unread declaration as readily, but the weave would carry nothing for it — its pairs
are routed to core one by one — so neither is this module's rebuild, which would otherwise
read the list as a predicate and rename its column keys.
Delivery
Hosting is a delivery optimization, not a semantic category. The mutants are the ones the
weave would have carried — the plugin's own catalog (Mutare.Ecto.Host.Catalog.own_catalog/2)
and the ^-pin interiors sub-contracted to core (Mutare.Ecto.Island) — each delivered as
the whole call rebuilt around one mutated condition, through Mutare's ordinary in-place
selector (the delivery Mutare.Ecto.Dynamic and Mutare.Ecto.Query use). Every selector
branch is then a statically built clause under the written declaration, which Ecto accepts. A
catalog mutant still reports at the expression it changed (Mutare.Ecto.Walk anchors it); a
pin-interior mutant reports at the whole call, as it does from Mutare.Ecto.Dynamic. The
families: filter and equivalence notes apply as on any mutate/2 path.
The subquery rule follows Ecto's current behaviour without depending on it: were a later Ecto to accept the dynamic form, this delivery would remain valid, only more verbose than the weave.
Summary
Types
How one hosted condition's mutants are delivered — see delivery/4.
Functions
How the host delivers condition, a predicate of kind (Mutare.Ecto.Host.Condition.shape/1)
received by clause (a condition macro's name, a from clause key, or :on) under the
declaration bindings reads as: {:woven, list}, carrying the binding list the woven
dynamic/2 re-declares (none for a :root_pin, which is woven without one), or :rebuilt, by
mutations/2 — see "The rule" in the moduledoc.
Every single-point mutant of each hosted condition in call that delivery/4 rebuilds, as
the whole call rebuilt around the mutated condition — a condition macro
(having(q, [p], …), direct or piped), a standalone join's on:, or a from's keyword
clauses. [] for a call with no such condition, which is nearly every call.
The half of delivery/4 read off the clause and the expression alone: whether clause
accepts condition as a ^-pinned dynamic/2 — false only for a subquery in a clause that
rejects a dynamic one.
Types
@type delivery() :: {:woven, bindings :: [Macro.t()]} | :rebuilt
How one hosted condition's mutants are delivered — see delivery/4.
Functions
@spec delivery( atom(), Macro.t(), Mutare.Ecto.Host.Condition.predicate_kind(), Mutare.Ecto.Host.Bindings.result() ) :: delivery()
How the host delivers condition, a predicate of kind (Mutare.Ecto.Host.Condition.shape/1)
received by clause (a condition macro's name, a from clause key, or :on) under the
declaration bindings reads as: {:woven, list}, carrying the binding list the woven
dynamic/2 re-declares (none for a :root_pin, which is woven without one), or :rebuilt, by
mutations/2 — see "The rule" in the moduledoc.
@spec mutations(Mutare.Ecto.AST.QueryCall.t(), Mutare.Ecto.Context.t()) :: [ Mutare.Ecto.SubMutator.tagged() | Mutare.Mutator.Mutation.t() ]
Every single-point mutant of each hosted condition in call that delivery/4 rebuilds, as
the whole call rebuilt around the mutated condition — a condition macro
(having(q, [p], …), direct or piped), a standalone join's on:, or a from's keyword
clauses. [] for a call with no such condition, which is nearly every call.
The half of delivery/4 read off the clause and the expression alone: whether clause
accepts condition as a ^-pinned dynamic/2 — false only for a subquery in a clause that
rejects a dynamic one.