In-fragment SQL mutations for a free-standing dynamic/1,2 call — the condition a user
builds ahead of time and splices later:
d = dynamic([p], p.views > 100)
Repo.all(where(query, ^d))The condition body is the same SQL fragment a hosted where/having contains, so the same catalog
walks it (Mutare.Ecto.Fragment, scalar and aggregate swaps folded in — sum↔avg for a
dynamic used in a having). At the where(q, ^d) splice site, a top-level ^d is hosted
like any other condition, but the catalog stops at the pin. Its interior contains only a bare
variable, which core does not mutate (Mutare.Ecto.Island). The splice therefore produces no
mutations; this module mutates the dynamic where it is built.
Delivery differs from the hosted path. dynamic is registered :raw
(Mutare.Ecto.Surface.macro_registrations/0), so core never descends into its DSL arguments —
but core still offers the whole call to mutate/2 (which is why it is :raw, not the
call-level :skip: a skipped call is never passed to a mutator), and a free-standing dynamic
sits in an ordinary expression position (its value is a runtime %Ecto.Query.DynamicExpr{}, not a spliced
query clause). So each mutant is the whole call rebuilt with exactly one point of the condition
swapped, delivered by Mutare's ordinary in-place selector case — the same Bucket-1 delivery as
the whole-from rewrites in Mutare.Ecto.Query. No host / ^-weaving is needed, each selector
branch invokes the dynamic macro independently, and exactly one DynamicExpr builds per run.
Each mutant is still reported at the mutated expression — the walk anchors it
(Mutare.Ecto.Walk) — so a line-scoped # mutare:ignore reaches one comparison of a
multi-line dynamic.
The written binding list is re-emitted byte-for-byte (Mutare.Ecto.BindingReorder handles
positional reordering). A ^ pin's interior — nested (p.x > ^(min + 1)) or the whole
body (dynamic([p], ^(if params.sort, do: a, else: b))) — is never mutated by this catalog: it is
passed to core through the same interface the host uses (Mutare.Ecto.Island), only
delivered as this module's whole-call rewrite instead of a weave.
Summary
Functions
Every single-point in-fragment mutant of a free-standing dynamic/1,2 call, each the whole
call rebuilt with one condition position swapped — the plugin's own catalog mutants as
Mutare.Ecto.Tags, the island mutants returned by core as producer-attributed
Mutare.Mutator.Mutations — or [] when there is nothing to mutate. The condition is located
by Mutare.Ecto.Host.Condition.locate/2, exactly as for a standalone where — but its
declaration is never read: the whole-call rebuild re-emits the written list as it stands, so
even one the plugin cannot interpret (which a woven dynamic/2 could not re-declare) is
mutated here.
Functions
@spec mutations(Mutare.Ecto.AST.QueryCall.t(), Mutare.Ecto.Context.t()) :: [ Mutare.Ecto.SubMutator.tagged() | Mutare.Mutator.Mutation.t() ]
Every single-point in-fragment mutant of a free-standing dynamic/1,2 call, each the whole
call rebuilt with one condition position swapped — the plugin's own catalog mutants as
Mutare.Ecto.Tags, the island mutants returned by core as producer-attributed
Mutare.Mutator.Mutations — or [] when there is nothing to mutate. The condition is located
by Mutare.Ecto.Host.Condition.locate/2, exactly as for a standalone where — but its
declaration is never read: the whole-call rebuild re-emits the written list as it stands, so
even one the plugin cannot interpret (which a woven dynamic/2 could not re-declare) is
mutated here.