1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
use CratestackError;
/// 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 `match`es 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.)