Builds selector-host targets for localized Ecto query conditions.
The companion Mutare.Ecto.Host.Routing identifies hosted argument positions. This module then
coordinates four focused components: Host.Condition locates the condition argument a
where/having call contains, Host.Bindings interprets Ecto binding declarations, Host.Catalog
produces the logical mutants — the plugin's own SQL catalog plus the pin interiors
Mutare.Ecto.Island passes to core (which is why context is passed down to the
catalog) — and Host.Target constructs the dynamic/2 wrap and selector splice consumed by
Mutare core.
The host builds a condition target for a predicate only. Core offers the whole call as
soon as any one position routed :hosted, and does not confine the returned targets to those
positions — so a from's hosted limit: brings a sibling where: [score: 5] here too. That
sibling is a keyword filter, which the classifier already gave to core pair by pair, and
which dynamic/2 would refuse. The host therefore reads every condition value through the
same classification the classifier used (Mutare.Ecto.Host.Condition.shape/1), wherever the
value is written: a from clause, a condition macro's argument, a join's on: option —
Host.Condition locates each of the three for a predicate only. Neither the predicate catalog
nor the pin sub-contract is offered a keyword filter, so hosting a sibling never changes
what happens to it.
A condition that is itself a ^ pin (where: ^filters, where(q, ^cond), a join's on: ^cond —
a predicate of kind :root_pin, by the same classification) is woven pin-only, over its bare
interior: Ecto treats such a root interpolation according to its runtime value — a keyword list is
a field filter, a boolean a literal condition, a dynamic is expanded — and a dynamic/2 wrap
would turn the first two into plain parameters. Pinning the selector alone hands Ecto the same
kind of value the written pin did, so the instrumented query behaves as the original.
Besides conditions, the host also weaves the :bound ±1 bump of a literal limit/offset
value, as a pin-only target with no dynamic/2 wrap and no bindings — see
Mutare.Ecto.Bound, the bump catalog and its literal guard. In a from, only the
effective occurrence of a repeated bound weaves (Mutare.Ecto.AST.FromCall.effective_clause?/2).
A condition the host cannot weave — a subquery in a having, which Ecto accepts only
statically built, or a condition under a binding declaration the plugin cannot re-declare — is
delivered as a whole-call rebuild instead, by Mutare.Ecto.StaticCondition — whose
delivery/4 decides, for the host and the rebuild alike, which conditions those are.
Summary
Functions
Mutare.Mutator.MacroHost.host/2: the selector-host targets for a resolved Ecto.Query macro
call. The call's node is re-read through Mutare.Ecto.AST.QueryCall so the splice transforms
rebuild the author's written form. Core's context is unpacked once here, into the plugin's
%Mutare.Ecto.Context{} — this is the hosted path's core boundary, as
Mutare.Ecto.Dispatcher.mutations/2 is the mutate/2 path's.
Functions
@spec host(Mutare.CallRouting.Call.t(), Mutare.Mutator.context()) :: [ Mutare.Ecto.Host.Target.t() ]
Mutare.Mutator.MacroHost.host/2: the selector-host targets for a resolved Ecto.Query macro
call. The call's node is re-read through Mutare.Ecto.AST.QueryCall so the splice transforms
rebuild the author's written form. Core's context is unpacked once here, into the plugin's
%Mutare.Ecto.Context{} — this is the hosted path's core boundary, as
Mutare.Ecto.Dispatcher.mutations/2 is the mutate/2 path's.