Skip to main content

drizzle_core/traits/
constraint.rs

1use crate::relation::SchemaHasTable;
2use crate::traits::{SQLForeignKey, type_set::Cons, type_set::Nil};
3
4/// The kind of a table constraint, at runtime.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum SQLConstraintKind {
7    /// `PRIMARY KEY`.
8    PrimaryKey,
9    /// `FOREIGN KEY`.
10    ForeignKey,
11    /// `UNIQUE`.
12    Unique,
13    /// `CHECK`.
14    Check,
15}
16
17/// A table constraint described by types, generated by the table macros.
18pub trait SQLConstraint {
19    /// The table the constraint belongs to.
20    type Table;
21    /// The kind: [`PrimaryKeyK`], [`ForeignKeyK`], [`UniqueK`] or [`CheckK`].
22    type Kind;
23    /// The constrained columns, as a tuple.
24    type Columns;
25}
26
27/// Placeholder [`SQLConstraint`] for a table without one.
28pub struct NoConstraint;
29
30impl SQLConstraint for NoConstraint {
31    type Table = ();
32    type Kind = ();
33    type Columns = ();
34}
35
36/// Constraint kind: primary key (`#[column(primary)]` or a composite key).
37pub struct PrimaryKeyK;
38/// Constraint kind: foreign key (`#[column(references = ...)]`).
39pub struct ForeignKeyK;
40/// Constraint kind: unique (`#[column(unique)]` or a unique constraint).
41pub struct UniqueK;
42/// Constraint kind: check constraint.
43pub struct CheckK;
44
45/// Table `Self` declares a constraint of kind `Kind` ([`PrimaryKeyK`],
46/// [`ForeignKeyK`], [`UniqueK`], [`CheckK`]).
47///
48/// Generated by the table macros. Use it as a bound to require, say, a
49/// primary key.
50///
51/// # Examples
52///
53/// ```
54/// use drizzle_core::{HasConstraint, PrimaryKeyK};
55///
56/// struct Users;
57/// impl HasConstraint<PrimaryKeyK> for Users {}
58///
59/// fn needs_primary_key<T: HasConstraint<PrimaryKeyK>>() {}
60/// needs_primary_key::<Users>();
61/// ```
62#[diagnostic::on_unimplemented(
63    message = "table `{Self}` does not have a `{Kind}` constraint",
64    label = "this table is missing the required constraint",
65    note = "declare it on the table, e.g. `#[column(primary)]` for `PrimaryKeyK`"
66)]
67pub trait HasConstraint<Kind> {}
68
69/// `Self` and `T` are the same type. Used to give type-equality checks a
70/// clear error message.
71#[diagnostic::on_unimplemented(
72    message = "foreign key type mismatch: `{Self}` cannot reference column of type `{T}`",
73    label = "change this column's type to `{T}` to match the referenced column",
74    note = "the foreign key column type must exactly match the referenced column type"
75)]
76pub trait TypeEq<T> {}
77impl<T> TypeEq<T> for T {}
78
79/// The column `Self` belongs to `Table`. Generated by the table macros.
80#[diagnostic::on_unimplemented(
81    message = "column `{Self}` does not belong to table `{Table}`",
82    label = "this column is not defined on the target table",
83    note = "constraint columns must be defined on the table they reference"
84)]
85pub trait ColumnOf<Table> {}
86
87impl<T, Table> ColumnOf<Table> for &T where T: ColumnOf<Table> {}
88
89/// The column `Self` is `NOT NULL`. Generated by the table macros.
90#[diagnostic::on_unimplemented(
91    message = "column `{Self}` is nullable and cannot be used here",
92    label = "this column must be NOT NULL",
93    note = "primary key columns cannot be nullable"
94)]
95pub trait ColumnNotNull {}
96
97/// The Rust value type of a column, as declared on the table struct.
98pub trait ColumnValueType {
99    /// The field's Rust type.
100    type ValueType;
101}
102
103/// Every column in the tuple `Cols` belongs to `Table`.
104#[diagnostic::on_unimplemented(
105    message = "one or more columns do not belong to table `{Table}`",
106    label = "these columns are not all defined on the same table",
107    note = "all constraint columns must belong to the target table"
108)]
109pub trait ColumnsBelongTo<Table, Cols> {}
110impl<Table> ColumnsBelongTo<Table, ()> for () {}
111
112macro_rules! impl_columns_belong_to_cb {
113    ($($C:ident),+) => {
114        impl<Table, $($C),+> ColumnsBelongTo<Table, ($($C,)+)> for ()
115        where $($C: ColumnOf<Table>,)+ {}
116    };
117}
118with_tuple_sizes!(impl_columns_belong_to_cb);
119
120/// The column tuple `Cols` is not empty.
121#[diagnostic::on_unimplemented(
122    message = "constraint requires at least one column",
123    label = "provide one or more columns for this constraint"
124)]
125pub trait NonEmptyColSet<Cols> {}
126
127macro_rules! impl_non_empty_col_set_cb {
128    ($($C:ident),+) => {
129        impl<$($C),+> NonEmptyColSet<($($C,)+)> for () {}
130    };
131}
132with_tuple_sizes!(impl_non_empty_col_set_cb);
133
134/// The column tuple `Cols` has no duplicates.
135#[diagnostic::on_unimplemented(
136    message = "constraint columns contain duplicates",
137    label = "each column must appear only once in a constraint"
138)]
139pub trait NoDuplicateColSet<Cols> {}
140impl NoDuplicateColSet<()> for () {}
141
142/// Every primary key column in `Cols` is `NOT NULL`.
143#[diagnostic::on_unimplemented(
144    message = "primary key columns must all be NOT NULL",
145    label = "one or more primary key columns are nullable — remove `Option<>` from them",
146    note = "wrap the column type directly (e.g., `pub id: i64`) instead of `Option<i64>`"
147)]
148pub trait PkNotNull<Cols> {}
149impl PkNotNull<()> for () {}
150
151macro_rules! impl_pk_not_null_cb {
152    ($($C:ident),+) => {
153        impl<$($C),+> PkNotNull<($($C,)+)> for ()
154        where $($C: ColumnNotNull,)+ {}
155    };
156}
157with_tuple_sizes!(impl_pk_not_null_cb);
158
159/// A foreign key has as many source columns as target columns.
160#[diagnostic::on_unimplemented(
161    message = "foreign key column count mismatch between source and target",
162    label = "the number of FK columns must match the number of referenced columns",
163    note = "e.g., a composite FK with 2 source columns must reference exactly 2 target columns"
164)]
165pub trait FkArityMatch<SrcCols, DstCols> {}
166
167macro_rules! impl_fk_arity_match_cb {
168    ($($S:ident),+; $($D:ident),+) => {
169        impl<$($S,)+ $($D,)+> FkArityMatch<($($S,)+), ($($D,)+)> for () {}
170    };
171}
172with_dual_tuple_sizes!(impl_fk_arity_match_cb);
173
174/// Each foreign key column has the same Rust type as the column it
175/// references.
176#[diagnostic::on_unimplemented(
177    message = "foreign key column types do not match the referenced columns",
178    label = "each FK column's type must match the corresponding referenced column's type"
179)]
180pub trait FkTypeMatch<SrcCols, DstCols> {}
181impl FkTypeMatch<(), ()> for () {}
182
183macro_rules! impl_fk_type_match_cb {
184    ($($S:ident),+; $($D:ident),+) => {
185        impl<$($S,)+ $($D,)+> FkTypeMatch<($($S,)+), ($($D,)+)> for ()
186        where
187            $(
188                $S: ColumnValueType,
189                $D: ColumnValueType,
190                <$S as ColumnValueType>::ValueType: TypeEq<<$D as ColumnValueType>::ValueType>,
191            )+
192        {}
193    };
194}
195with_dual_tuple_sizes!(impl_fk_type_match_cb);
196
197/// Every foreign key in the tuple `Self` targets a table in schema `S`.
198#[diagnostic::on_unimplemented(
199    message = "foreign key references a table not present in this schema",
200    label = "add the referenced table to your schema definition",
201    note = "all tables referenced by foreign keys must be included in the schema"
202)]
203pub trait ForeignKeysInSchema<S> {}
204impl<S> ForeignKeysInSchema<S> for () {}
205
206macro_rules! impl_foreign_keys_in_schema_cb {
207    ($($Fk:ident),+) => {
208        impl<S, $($Fk),+> ForeignKeysInSchema<S> for ($($Fk,)+)
209        where
210            $(
211                $Fk: SQLForeignKey,
212                S: SchemaHasTable<<$Fk as SQLForeignKey>::TargetTable>,
213            )+
214        {}
215    };
216}
217with_tuple_sizes!(impl_foreign_keys_in_schema_cb);
218
219/// Every foreign key of every table in the list `Self` targets a table in
220/// schema `S`.
221#[diagnostic::on_unimplemented(
222    message = "schema contains tables with foreign keys referencing tables not in the schema",
223    label = "ensure all FK target tables are included in this schema"
224)]
225pub trait ValidateTableSetForeignKeys<S> {}
226impl<S> ValidateTableSetForeignKeys<S> for Nil {}
227
228/// A table's keys and constraints as types, generated by the table macros.
229pub trait SQLTableMeta {
230    /// The table's foreign keys, as a tuple.
231    type ForeignKeys;
232    /// The table's primary key.
233    type PrimaryKey;
234    /// The table's other constraints, as a tuple.
235    type Constraints;
236}
237
238impl<S, Head, Tail> ValidateTableSetForeignKeys<S> for Cons<Head, Tail>
239where
240    Head: SQLTableMeta,
241    <Head as SQLTableMeta>::ForeignKeys: ForeignKeysInSchema<S>,
242    Tail: ValidateTableSetForeignKeys<S>,
243{
244}
245
246/// The foreign keys of a schema item's tables all target tables in schema
247/// `S`. Checked by the schema macros.
248pub trait ValidateSchemaItemForeignKeys<S> {}
249
250impl<S, Item> ValidateSchemaItemForeignKeys<S> for Item
251where
252    Item: crate::traits::SchemaItemTables,
253    <Item as crate::traits::SchemaItemTables>::Tables: ValidateTableSetForeignKeys<S>,
254{
255}
256
257/// Something that can be the target of `ON CONFLICT (...)` for `Table`.
258///
259/// Generated for primary-key and unique columns, the primary key itself,
260/// unique constraints, and unique indexes. Passing anything else to
261/// `.on_conflict(...)` is a compile error.
262#[diagnostic::on_unimplemented(
263    message = "`{Self}` is not a valid conflict target for table `{Table}`",
264    label = "use a primary key, unique column, or unique index/constraint from this table",
265    note = "ON CONFLICT targets must be primary key columns, unique columns, or unique indexes"
266)]
267pub trait ConflictTarget<Table>: Copy {
268    /// The column names inside `ON CONFLICT (...)`.
269    fn conflict_columns(&self) -> &'static [&'static str];
270
271    /// The `WHERE` predicate of a partial unique index, which the conflict
272    /// target must repeat for the database to pick that index.
273    fn conflict_where_clause(&self) -> Option<&'static str> {
274        None
275    }
276}
277
278/// A named constraint usable as `ON CONFLICT ON CONSTRAINT "name"`
279/// (PostgreSQL only).
280///
281/// Generated for unique columns and unique constraints that have a name.
282#[diagnostic::on_unimplemented(
283    message = "`{Self}` is not a named constraint on table `{Table}`",
284    label = "ON CONSTRAINT requires a named unique or exclusion constraint"
285)]
286pub trait NamedConstraint<Table>: Copy {
287    /// The constraint's name.
288    fn constraint_name(&self) -> &'static str;
289}