# `Mutare.Ecto.Fragment`
[🔗](https://github.com/foxbenjaminfox/mutare_ecto/blob/v0.4.2/lib/mutare/ecto/fragment.ex#L1)

The plugin's **own** SQL-semantics mutation catalog for a query fragment — the boolean
condition of a `where`/`having` clause or of a free-standing `dynamic/1,2`. Given a condition's
AST, `mutants/1` returns every *single-point* variant: the same condition with **exactly one**
operator/predicate swapped, one variant per mutatable position. Each variant is a full,
compile-safe alternative the SQL engine will actually run; the host (`Mutare.Ecto.Host`) weaves
them behind a `^`/`dynamic` selector so one of them bakes into the query per run, while
`Mutare.Ecto.Dynamic` rebuilds a free-standing `dynamic` call whole (an ordinary expression
position, needing no weave).

## SQL semantics throughout the catalog

The catalog reuses **none** of Mutare's built-in mutators: the operators look like Elixir's
but they evaluate under SQL's semantics — three-valued boolean logic for the connectives,
NULL handling for the predicates, boundary behaviour for the comparisons, the engine's
division for arithmetic — so an Elixir-semantics mutator would silently drop
genuinely killable mutants (or emit provably equivalent ones). The in-fragment literal arms are
implemented here for the same reason: a literal written into a condition is part of the SQL
the query runs, not interpolated Elixir, so core never traverses it (the clause is raw/`:hosted`). They follow
core's value conventions — the numeric arms take their off-by-one/zero table and
`# mutare:ignore` labels from `Mutare.AST.numeric_alternatives/3`, so the two can't drift —
but the mutations are selected here under SQL semantics. Each is named after the *type* it
mutates (`integer_literal`, …), never after `fragment(...)`.

A `^` pin's interior is ordinary Elixir evaluated at runtime, so this catalog never mutates it
(applying the SQL mutation `^(min * 2)` → `^(min / 2)` would mutate the parameter's Elixir value).
The walk treats a pin as a leaf, and `islands/2` returns each interior for the calling module
to pass to core — see `Mutare.Ecto.Island` — together with the pin's **role** (`t:role/0`): the
pin moves a value out of the SQL, not out of the position it fills, so a pin at a structural
position (below) still carries structure. Field references (`u.age`) are likewise never mutated.

## The families

  * **Comparison** — `>`↔`>=`, `<`↔`<=`, `==`↔`!=`. The `==`/`!=` swap is never "equivalent":
    in SQL both forms exclude `NULL` rows and differ on every concrete value.
  * **Connective** — `and`↔`or`. Genuinely three-valued (a `NULL` operand is neither true nor
    false), so its equivalences differ from Elixir's.
  * **NullPredicate** — `is_nil(x)`↔`not is_nil(x)`, treated as one unit so `not is_nil(x)`
    flips back rather than double-negating. Its argument is descended like any other, but
    `is_nil` observes only *whether* the argument is NULL, so a mutant there is pruned when —
    and only when — it is **known** to be NULL on exactly the original's rows (see
    "What `is_nil` observes" below).
  * **Membership** — `x in ^list`↔`x not in ^list` and `exists(subquery)`↔`not exists(subquery)`
    (unit polarity flips, no double negation), one **element drop** per *distinct* entry of a
    *written* in-list, removing every occurrence (`x in [1, 2, 3]` → `x in [2, 3]`/…; `IN` is
    set membership, so `[1, 1, 2]` shrinks to `[2]`/`[1, 1]`, never to the equivalent
    `[1, 2]`), and `like`↔`ilike` — **dialect-gated** on
    `:postgres` (`ilike` is Postgres-specific; the rest is portable). The `in` operands are
    descended (a swap on the left or a literal in the written list changes which rows match).
    A subquery argument is not descended *as a condition*; its interior is recursed by
    `Mutare.Ecto.Subquery`, each mutant rebuilt into this condition.
  * **Arithmetic** — `+`↔`-`, `*`↔`/`, the shared `Mutare.Ecto.Scalar` catalog (also delivered
    in `select`/`order_by` values); binary forms only.
  * **Coalesce** — `coalesce(x, default)` → `x` (also `Mutare.Ecto.Scalar`): differs exactly
    on the rows where `x` is NULL. Beneath `is_nil` too: `is_nil(coalesce(x, d))` →
    `is_nil(x)` differs on every row where `x` is NULL and `d` is not.
  * **Aggregate** — `sum`↔`avg`, `min`↔`max` (the shared `Mutare.Ecto.Aggregate`), for a
    `having: sum(p.x) > n`. Applied per node by this walk, so a condition is walked **once**
    for every family.
  * **Temporal** — `ago(n, unit)`↔`from_now(n, unit)`: symmetric around *now*, so a comparison
    against them differs only for rows inside that window. The `unit` is structural (below);
    the count keeps its literal mutants.
  * **IntegerLiteral** / **FloatLiteral** — a *non-pinned* numeric literal (`u.age > 18` →
    `19`/`17`/`0`; `2.5` → `3.5`/`1.5`/`0.0`): `n±1` plus the zero sentinel, deduped and never
    equal to the original.
  * **StringLiteral** — `u.name == "ok"` → `""`/`"mutare"`, dropping whichever equals the original.
  * **BooleanLiteral** — `true`↔`false`: a boolean in a fragment is worth flipping exactly when
    it is worth using.
  * **AtomLiteral** — any *other* literal atom → the `:mutare` sentinel (dropped when already
    the sentinel); `nil` is excluded (NULL/absence, no clean swap).

The string/atom/boolean arms are opt-in (off under the default `families:`) — see the
`Mutare.Ecto` configuration docs.

A literal arm is also suppressed at a **structural position** of a known Ecto DSL form, where the
literal shapes the SQL the builder emits rather than carrying data (a mutant would be a broken
query, not a live one): the `fragment` template, an interval unit, a cast type, a column,
binding, or alias name, a map key — `structural_position?/1` is the registry, consulted off the
`{parent_form, arity, index}` the walk threads down. Data literals at every *other* position
of those forms are still mutated. A **tuple** is classified by position in the same way: at a
structural position it is a compound cast spec (`{:array, :string}`), skipped whole; anywhere else it is
Ecto's tuple comparison (`{p.views, p.id} > {1, 2}`), walked like a written list — each element
a data position of the comparison (`children/2`).

The same registry gives a **pin** at such a position its role. Ecto accepts an interpolation
at most of them — `field(p, ^name)`, `type(^v, ^type)`, `ago(^n, ^unit)`, `as(^binding)`,
`selected_as(^name)`, a fragment's `identifier(^name)` — and the value the pin computes is the
same column, cast type, unit or name the written literal would have been. `islands/2` reports
such a pin as `:structural`, never as a plain `:value`, and so it does a pin nested in a
compound cast spec (`type(x, {:array, ^inner})`), which the walk does not otherwise enter.

## What `is_nil` observes

`is_nil(x)` reads one bit of `x` per row — whether it is NULL — so a mutant inside `x` that is
NULL on exactly the original's rows cannot change the predicate: it is equivalent, and pruned.
That property is claimed only where it is **known**, from one table of per-form NULL rules
(`nullness/1`):

  * a non-`nil` literal (a written negative number included) is never NULL;
  * `a + b`, `a - b` and `a * b` are NULL exactly when an operand is;
  * `coalesce(a, b)` is NULL exactly when both operands are;
  * `sum`/`avg` of `x` is NULL exactly when no input row has a non-NULL `x`.

`min`/`max` follow that rule too, but on SQLite they also choose the row that a bare
(ungrouped, unaggregated) column is read from, so a swap that keeps their NULL-ness can still
change the result; they are unknown.

The arithmetic rules assume the arithmetic stays finite. A float that overflows breaks them:
on SQLite, `Inf - Inf` is NaN, which it returns as NULL, and Postgres raises on the overflow.
A mutant that only such values can tell apart (`(x + x) - (x + x)` → `(x + x) + (x + x)` at
`x = 1.0e308`) is pruned as equivalent anyway: no test suite is expected to kill it.

Two decisions read that table. A mutant is pruned when it and the node it replaces are both
never NULL (a literal bump) or follow the same rule over the same operands (`+`→`-`,
`sum`→`avg`). And the NULL-ness-only observation passes down through a form only while that
form has a rule: each rule makes the form's NULL-ness a function of its operands' NULL-ness,
never of their values. Beneath any other form an operand's *value* may decide whether the
whole is NULL, so the full catalog applies there again.

A window, `over(function, options)`, is split. Its function is evaluated over the rows the
options select, so the function takes the window's own observation. The options' values
*select* those rows. Beneath `is_nil`, a partition key `p.a + p.b` → `p.a - p.b` keeps every
key's NULL-ness but can regroup the rows and leave one partition with only NULL inputs. That
makes its `sum` NULL, so the options are always observed by value.

Every other form is **unknown**, and an unknown is never pruned:

  * `a / b` — a zero divisor yields NULL on SQLite and MySQL and raises on Postgres, so
    `is_nil(a * b)` → `is_nil(a / b)` is live, and so is a literal inside a divisor;
  * `and`/`or` — three-valued (`NULL and false` is false where `NULL or false` is NULL);
  * `in` and a tuple comparison — NULL or not depending on which values match;
  * a JSON path (`is_nil(p.meta["k"])` — the key picks the element), a `fragment(...)`
    (`is_nil(fragment("NULLIF(?, ?)", p.score, 0))` — the `0` decides which scores read as
    NULL), a subquery, a nested author macro;
  * a `^` pin — ordinary Elixir, free to compute `nil` or a value by any route
    (`^(opts[:min] || default)`), so an island beneath `is_nil` is passed to core like any other.

A form missing from the table costs an equivalent mutant (`is_nil(p.a > 1)` → `>=`: a scalar
comparison is NULL exactly when an operand is, but it has no rule here), never a live one.

## Traversal

The traversal is the plugin's one shared walk (`Mutare.Ecto.Walk`, whose author-macro rule
specifies which nested-call arguments are entered): this module supplies its descent rule
(`children/2` — the unit predicates, and each child's context: its
`{parent_form, arity, index}` position and what the enclosing predicate observes of it) and
two per-node readers over the positions traversed — the catalog (`local/3`, behind
`mutants/2`) and the island collector (`local_islands/2`, behind `islands/2`) — so the two
can never traverse different sets of nodes in a condition.

# `island`

```elixir
@type island() :: {Macro.t(), role(), Mutare.Ecto.Walk.rebuild()}
```

One interpolation island: the pin's interior, the pin's role, and the rebuild of the walked
root around a replacement interior (the pin itself kept).

# `role`

```elixir
@type role() :: :value | :condition | :structural
```

What a `^` pin's value is **to the query**, read off where the pin sits — the one fact about a
pin its interior cannot show. It decides how much of that interior is still structure once
the interior is passed to core; the policy per role is `Mutare.Ecto.Island`'s.

  * `:value` — a query parameter, plain application data: a pin at any data position (a
    comparison operand, an in-list element, a JSON path key, a `fragment`, `splice` or
    `constant` argument).
  * `:condition` — the pin *is* a whole condition (a `:root_pin` predicate,
    `Mutare.Ecto.Host.Condition`), which Ecto dispatches on its runtime value: a keyword
    filter, a boolean or a dynamic.
  * `:structural` — the pin fills a structural position (the moduledoc's registry): its value
    is a column, binding or alias name, a cast type, an interval unit or an SQL identifier,
    which the builder writes into the SQL it emits.

# `islands`

```elixir
@spec islands(Macro.t(), role()) :: [island()]
```

Every Elixir **island** in `root`, as `t:island/0` triples — `interior` is
a pin's Elixir expression, `role` what the pin's value is to the query (`t:role/0`), and
`rebuild.(mutated_interior)` the full `root` with exactly that pin's interior replaced (the
pin itself kept). The calling module passes each interior to core, under the policy its role
selects — see `Mutare.Ecto.Island`.

A subquery's computed source is also an island, with role `:value`; its rebuild replaces the
source expression directly. `Mutare.Ecto.Subquery` identifies those query-building positions.

The islands are a second reader of the **same** positions `mutants/2` reads
(`local_islands/2` over `Mutare.Ecto.Walk.positions/3`, under the same `children/2`), so a
caller cannot reach an island the catalog would not have walked past — by construction, not by
a parallel walk kept in step. So a subquery argument surfaces the pins of the clauses
`Mutare.Ecto.Subquery` mutates under that wrapper, and the pin itself is the boundary
(everything beneath it is passed to core for mutation). What the enclosing predicate observes
never narrows this reader: a pin beneath `is_nil` is an island too, because nothing is known
about which Elixir mutants keep a parameter's `nil`-ness (see "What `is_nil` observes").

The role is read off the same walk: a pin's is its position's — `:structural` at a
structural position, `:value` at every other. Only a pin that *is* the root has no position
to read, and what the root is only the caller knows, so the caller states it as `root_role`:
`:condition` for a predicate, and `Mutare.Ecto.Subquery`'s for the other roots it walks.

# `mutants`

```elixir
@spec mutants(Macro.t(), Mutare.Ecto.Config.t(), Mutare.Ecto.Walk.slot()) :: [
  Mutare.Ecto.Tag.t()
]
```

Every single-point mutant of a `where`/`having` condition as self-tagging `Mutare.Ecto.Tag`s, or
`[]` when the condition has nothing the catalog mutates (a bare boolean column, an
interpolation). The condition is a **predicate**. A keyword filter (`where: [score: 5]`) is not
this catalog's syntax — the walk would read a `score: 5` pair as a value tuple, data on both
sides, and rename the column — so every caller classifies a condition-position value first
(`Mutare.Ecto.Host.Condition`). One tag per mutatable position, each the full condition with that one
position swapped, tagged with the SQL family that produced it (so the caller can filter by
`families:`) **and** the finer label naming the operator/kind it swapped (so a qualified
`# mutare:ignore[ecto:<]` can suppress just that one). `config` carries `dialects:` — the
`like`↔`ilike` swap is emitted only under `:postgres`.

The catalog proper is `local/3` — the mutations for one node, at its `{parent_form, arity, index}`
position — applied at every position the shared walk (`Mutare.Ecto.Walk`) traverses under this
catalog's own descent rule (`children/2`), and narrowed beneath an `is_nil` to the mutants not
known to keep the node's NULL-ness (`observable/3`).

`root_slot` is the call argument the walked root fills in the Ecto expression. It is `nil` for
a whole condition. A caller that walks part of a larger expression passes the slot that part
fills (`Mutare.Ecto.Subquery` does this for a keyword-filter pair value). A root of this walk
is not necessarily the root of the Ecto expression.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
