#[non_exhaustive]pub enum ConflictTarget {
PrimaryKey,
Columns(&'static [&'static str]),
ColumnsWithPredicate(&'static [&'static str], &'static str),
PrimaryKeyWithPredicate(&'static str),
}Expand description
Conflict target for an upsert. Defaults to the model’s primary key
(matching the previous PK-only behavior). Self::Columns /
Self::columns let callers upsert on an arbitrary unique tuple —
most commonly a natural key that’s distinct from the PK (e.g.
(owner_id, provider) on a per-owner-and-provider settings row, or
(pairing_id, slot) on a per-slot envelope).
The named columns MUST correspond to a UNIQUE constraint or
UNIQUE index on the target table — the database engine enforces
this and will surface a clear error if not. The upsert builder
additionally requires the input to carry a value for every column
in the target tuple, so the conflict probe (SELECT … FOR UPDATE)
has something to filter on. A column that IS present but whose
value is one of the SqlValue::Null* variants satisfies this
requirement — the probe then filters on column = NULL, which
never matches any row (three-valued SQL logic), so an upsert keyed
on a NULL natural-key column always takes the insert branch. That
is deliberate, not merely convenient: it is the same rule a WHERE col IS NOT NULL partial index encodes, so a NULL key naturally
falls outside such an index’s uniqueness domain both in Postgres’s
own ON CONFLICT inference and in this crate’s conflict probe.
Composite-constraint-by-name (ON CONFLICT ON CONSTRAINT my_unique_idx_v2) is not yet exposed; pass the matching column
tuple via Self::Columns instead.
§Partial unique indexes (cratestack#741)
Self::where_index attaches an index predicate so an upsert can
target a partial unique index (CREATE UNIQUE INDEX ... WHERE <predicate>) — Postgres will not infer a partial index from an
unpredicated ON CONFLICT (<cols>), so without this the statement
fails at runtime with “there is no unique or exclusion constraint
matching the ON CONFLICT specification”. The predicate is kept a
&'static str, exactly like the column names: a compile-time
constant from the schema/call site, passed through to the database
verbatim, with no runtime-value path into the rendered SQL (the
same precedent @@index’s using/opclass already set).
The predicate is not just appended to the emitted ON CONFLICT (…) WHERE … clause — every conflict probe this crate’s runtimes issue
to decide Inserted vs. Existing/DO UPDATE also applies it.
Skipping that half would let the probe match a row the partial
index does not cover, handing the caller a wrong verdict even
though the emitted SQL looks correct.
Declaring a partial index in the schema DDL (@@unique([...], where: "...")) is a separate concern (cratestack#742): this type
only lets an upsert target a partial index that already exists.
§Why an enum with four variants, not two plus a predicate field
An earlier draft of this ticket’s fix collapsed this type into a
{ kind, predicate } struct, which deleted the public PrimaryKey
and Columns(&'static [&'static str]) variants direct construction
/ pattern-matching relied on. A repo-wide grep showed every in-repo
call site only ever constructs a ConflictTarget (never pattern-
matches one), so that break bought nothing — the maintainer ruled
this be reworked additively instead (cratestack#741 finding 3):
Self::PrimaryKey and Self::Columns are restored exactly as
they were pre-#741, and the predicate rides along on two new,
purely additive variants (Self::ColumnsWithPredicate,
Self::PrimaryKeyWithPredicate) reached through
Self::where_index rather than constructed directly. The invalid
PrimaryKey + predicate combination deliberately stays
representable (via Self::PrimaryKeyWithPredicate) rather than
being ruled out at the type level, so Self::validate can reject
it at runtime with a clear CratestackError::Validation instead
of the type system silently preventing the chain
PrimaryKey.where_index(..) from being written at all.
§#[non_exhaustive] (cratestack#741 finding 4 follow-up, maintainer-ruled)
This release already breaks any external crate that pattern-matches
ConflictTarget exhaustively without a wildcard arm — the variant
count just grew from two to four (see above), and that alone forces
such a match to stop compiling. #[non_exhaustive] costs nothing
additional on top of a break those callers must already absorb
this release, and it means every future variant addition is
non-breaking for anyone who updates their match now — deferring it
would mean paying a second, separate break later for no extra
benefit, so this is the cheapest moment it will ever be. It affects
matching only: every existing variant, including the two additive
predicate-carrying ones, stays constructible from outside this
crate exactly as before — #[non_exhaustive] on an enum blocks
exhaustive matches and enum-level struct-update syntax in other
crates, not construction of variants that already exist. (Putting
#[non_exhaustive] on an individual variant instead would be the
opposite mistake — that blocks construction — and is deliberately
not done here.)
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
PrimaryKey
The model’s @id primary key, unpredicated. Default.
Columns(&'static [&'static str])
A caller-supplied tuple of columns forming a unique key on the
target table, unpredicated. Chain Self::where_index to
target a partial unique index instead of a plain one.
ColumnsWithPredicate(&'static [&'static str], &'static str)
Additive (cratestack#741): Self::Columns with an attached
partial-unique-index predicate. Reached via
ConflictTarget::columns(&[...]).where_index(<predicate>); not
normally constructed directly.
PrimaryKeyWithPredicate(&'static str)
Additive (cratestack#741): Self::PrimaryKey with an
attached predicate. This combination can never correspond to a
real index — the primary key index is never partial — and
Self::validate always rejects it. It stays representable
(rather than prevented at the type level) so that rejection is
a normal runtime CratestackError, not a call that can’t
even be written. Reached via
ConflictTarget::PRIMARY_KEY.where_index(<predicate>); not
normally constructed directly.
Implementations§
Source§impl ConflictTarget
impl ConflictTarget
Sourcepub const PRIMARY_KEY: ConflictTarget = Self::PrimaryKey
pub const PRIMARY_KEY: ConflictTarget = Self::PrimaryKey
ConflictTarget::PrimaryKey as an associated const, matching
the naming convention Self::columns sets for Columns.
Kept alongside the PrimaryKey variant itself (both spellings
are used across this codebase’s call sites and tests).
Sourcepub const fn columns(cols: &'static [&'static str]) -> ConflictTarget
pub const fn columns(cols: &'static [&'static str]) -> ConflictTarget
Sugar for ConflictTarget::Columns(&[...]).
Sourcepub const fn where_index(self, predicate: &'static str) -> ConflictTarget
pub const fn where_index(self, predicate: &'static str) -> ConflictTarget
Attach a partial-unique-index predicate, e.g.
ConflictTarget::columns(&["k"]).where_index("status = 'active'")
for an index declared as UNIQUE (k) WHERE status = 'active'.
Only valid when chained onto Self::Columns/Self::columns
— the primary key index is never partial, so chaining this onto
Self::PrimaryKey/Self::PRIMARY_KEY is rejected by
Self::validate rather than silently dropped. This method
itself stays infallible (const fn, so it can be used in a
const builder chain) — the rejection happens where the target
is actually consumed, before any SQL is built.
Sourcepub const fn predicate(&self) -> Option<&'static str>
pub const fn predicate(&self) -> Option<&'static str>
The attached partial-index predicate, if any.
Sourcepub const fn is_primary_key(&self) -> bool
pub const fn is_primary_key(&self) -> bool
true when this target is the model’s primary key.
Sourcepub const fn as_columns(&self) -> Option<&'static [&'static str]>
pub const fn as_columns(&self) -> Option<&'static [&'static str]>
The column tuple, if this target is Self::Columns /
Self::columns (predicated or not); None for
Self::PrimaryKey/Self::PRIMARY_KEY.
Sourcepub fn validate(&self) -> Result<(), CratestackError>
pub fn validate(&self) -> Result<(), CratestackError>
Reject a predicate paired with the primary key target — the PK
index is never partial, so that combination can never
correspond to a real index. Every runtime entry point that
consumes a ConflictTarget calls this before doing any SQL
work, so the rejection is a clear
CratestackError::Validation, not a silently dropped
predicate or a confusing database-side error.
Trait Implementations§
Source§impl Clone for ConflictTarget
impl Clone for ConflictTarget
Source§fn clone(&self) -> ConflictTarget
fn clone(&self) -> ConflictTarget
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for ConflictTarget
Source§impl Debug for ConflictTarget
impl Debug for ConflictTarget
Source§impl Default for ConflictTarget
impl Default for ConflictTarget
Source§fn default() -> ConflictTarget
fn default() -> ConflictTarget
impl Eq for ConflictTarget
Source§impl PartialEq for ConflictTarget
impl PartialEq for ConflictTarget
impl StructuralPartialEq for ConflictTarget
Auto Trait Implementations§
impl Freeze for ConflictTarget
impl RefUnwindSafe for ConflictTarget
impl Send for ConflictTarget
impl Sync for ConflictTarget
impl Unpin for ConflictTarget
impl UnsafeUnpin for ConflictTarget
impl UnwindSafe for ConflictTarget
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more