Mutare.Ecto.Dynamic (mutare_ecto v0.4.2)

Copy Markdown View Source

In-fragment SQL mutations for a free-standing dynamic/1,2 call — the expression a user builds ahead of time and splices later:

d = dynamic([p], p.views > 100)
Repo.all(where(query, ^d))

The body is a general SQL expression, including lists, maps, aliases, and windows. It shares SQL value semantics with a hosted where/having, 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 preserved (Mutare.Ecto.BindingReorder handles reordering). Mutare.Ecto.Island supplies pin-interior mutants, delivered here as whole-call rewrites.

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 last argument is a general SQL expression, including a list or map; it is not classified as a filter. 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

mutations(call, context)

@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 last argument is a general SQL expression, including a list or map; it is not classified as a filter. 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.