lode/on_conflict

Upsert policy for inserts — Ecto’s :on_conflict / :conflict_target options as plain data, rendered to Postgres INSERT ... ON CONFLICT.

Column references are typed Field(row) values (not bare strings), so the target/action columns are owner-checked against the schema being upserted — repo.upsert(schema: person_schema(), on_conflict: ...) unifies row, so a column of another schema won’t type-check. repo erases the policy to its runtime form (just names) before handing it to the adapter, which is why the adapter sees OnConflict(Nil).

repo.upsert(r, s, cs, on_conflict: on_conflict.Update( target: on_conflict.Columns([person_email()]), action: on_conflict.Replace([person_name()]), ))

Types

Which uniqueness violation triggers the conflict branch (Ecto’s :conflict_target). Column references name the database columns (a field’s source, where they differ).

pub type ConflictTarget(row) {
  AnyConstraint
  Columns(List(field.Field(row)))
  Constraint(String)
}

Constructors

  • AnyConstraint

    No explicit target — any unique constraint counts. Postgres allows this only for DO NOTHING; DO UPDATE must name its target.

  • Columns(List(field.Field(row)))

    ON CONFLICT (col, ...) — the columns of a unique index.

  • Constraint(String)

    ON CONFLICT ON CONSTRAINT name — a named unique constraint.

The upsert policy (Ecto’s :on_conflict option).

pub type OnConflict(row) {
  Raise
  Nothing(target: ConflictTarget(row))
  Update(target: ConflictTarget(row), action: UpdateAction(row))
}

Constructors

  • Raise

    No ON CONFLICT clause: a uniqueness violation surfaces as a ConstraintError (Ecto’s default, :raise — minus the raising).

  • Nothing(target: ConflictTarget(row))

    ON CONFLICT [target] DO NOTHING: silently skip conflicting rows.

  • Update(target: ConflictTarget(row), action: UpdateAction(row))

    ON CONFLICT target DO UPDATE SET ...: update the existing row instead.

What DO UPDATE sets on the existing row.

pub type UpdateAction(row) {
  ReplaceAll
  ReplaceAllExcept(List(field.Field(row)))
  Replace(List(field.Field(row)))
  Set(List(#(field.Field(row), value.Value)))
}

Constructors

  • ReplaceAll

    Set every inserted column to the incoming (EXCLUDED) value. The insert payload omits autogenerated and virtual columns, so those are never replaced — but a natural (non-autogenerated) primary key in the payload is, matching Ecto’s :replace_all; use ReplaceAllExcept to keep it.

  • ReplaceAllExcept(List(field.Field(row)))

    ReplaceAll minus the listed columns.

  • Replace(List(field.Field(row)))

    Set only the listed columns to their incoming (EXCLUDED) values (Ecto’s {:replace, fields}).

  • Set(List(#(field.Field(row), value.Value)))

    Set the listed columns to explicit values (Ecto’s on_conflict: [set: ...]).

Values

pub fn erase(policy: OnConflict(a)) -> OnConflict(b)

Re-type a policy’s phantom row (a no-op at runtime — the phantom has no runtime representation). repo calls this to hand the schema-checked policy to the adapter as OnConflict(Nil).

pub fn set_columns(
  action action: UpdateAction(row),
  insert_columns columns: List(String),
) -> List(String)

Resolve an action to the concrete column names it sets, given the columns the INSERT provides. Used by adapters to expand ReplaceAll/ReplaceAllExcept and by repo to reject actions that would set nothing.

Search Document