Mutare.Ecto.Dynamic (mutare_ecto v0.1.1)

Copy Markdown View Source

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 owns, so the same catalog walks it (Mutare.Ecto.Fragment, scalar and aggregate swaps folded in — sumavg for a dynamic destined for a having). This closes the loop the where(q, ^d) splice site leaves open: a top-level ^d is hosted there like any other condition, but the catalog stops at the pin and the island sub-contract finds only a bare variable inside, which core mutates nowhere (Mutare.Ecto.Island) — the splice contributes nothing of its own, so the dynamic is mutated where it is built. This module is that build site.

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 an inert leaf nobody is offered), 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 (its positional reorder is Mutare.Ecto.BindingReorder's). A ^ pin's interior — nested (p.x > ^(min + 1)) or the whole body (dynamic([p], ^(if params.sort, do: a, else: b))) — is never this catalog's: it is sub-contracted to core through the same seam 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 sub-contracted island mutants as producer-attributed Mutare.Mutator.Mutations — or [] when there is nothing to mutate. The condition is located by Mutare.Ecto.Host.Condition.locate/1, exactly as for a standalone where.

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 sub-contracted island mutants as producer-attributed Mutare.Mutator.Mutations — or [] when there is nothing to mutate. The condition is located by Mutare.Ecto.Host.Condition.locate/1, exactly as for a standalone where.