Mutare.Ecto.StaticCondition (mutare_ecto v0.3.0)

Copy Markdown View Source

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

delivery()

@type delivery() :: {:woven, bindings :: [Macro.t()]} | :rebuilt

How one hosted condition's mutants are delivered — see delivery/4.

Functions

delivery(clause, condition, kind, bindings)

@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.

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 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.

weavable?(clause, condition)

@spec weavable?(atom(), Macro.t()) :: boolean()

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.