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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum SQLConstraintKind {
6    PrimaryKey,
7    ForeignKey,
8    Unique,
9    Check,
10}
11
12/// Typed (non-dyn) constraint metadata.
13pub trait SQLConstraint {
14    type Table;
15    type Kind;
16    type Columns;
17}
18
19pub struct NoConstraint;
20
21impl SQLConstraint for NoConstraint {
22    type Table = ();
23    type Kind = ();
24    type Columns = ();
25}
26
27pub struct PrimaryKeyK;
28pub struct ForeignKeyK;
29pub struct UniqueK;
30pub struct CheckK;
31
32#[diagnostic::on_unimplemented(
33    message = "table `{Self}` does not have a primary key",
34    label = "add `#[column(primary)]` to a column in this table",
35    note = "tables used in this context must have a primary key defined"
36)]
37pub trait HasPrimaryKey {}
38
39#[diagnostic::on_unimplemented(
40    message = "table `{Self}` does not have a `{Kind}` constraint",
41    label = "this table is missing the required constraint"
42)]
43pub trait HasConstraint<Kind> {}
44
45#[diagnostic::on_unimplemented(
46    message = "foreign key type mismatch: `{Self}` cannot reference column of type `{T}`",
47    label = "change this column's type to `{T}` to match the referenced column",
48    note = "the foreign key column type must exactly match the referenced column type"
49)]
50pub trait TypeEq<T> {}
51impl<T> TypeEq<T> for T {}
52
53#[diagnostic::on_unimplemented(
54    message = "column `{Self}` does not belong to table `{Table}`",
55    label = "this column is not defined on the target table",
56    note = "constraint columns must be defined on the table they reference"
57)]
58pub trait ColumnOf<Table> {}
59
60impl<T, Table> ColumnOf<Table> for &T where T: ColumnOf<Table> {}
61
62#[diagnostic::on_unimplemented(
63    message = "column `{Self}` is nullable and cannot be used here",
64    label = "this column must be NOT NULL",
65    note = "primary key columns cannot be nullable"
66)]
67pub trait ColumnNotNull {}
68
69pub trait ColumnValueType {
70    type ValueType;
71}
72
73#[diagnostic::on_unimplemented(
74    message = "one or more columns do not belong to table `{Table}`",
75    label = "these columns are not all defined on the same table",
76    note = "all constraint columns must belong to the target table"
77)]
78pub trait ColumnsBelongTo<Table, Cols> {}
79impl<Table> ColumnsBelongTo<Table, ()> for () {}
80
81macro_rules! impl_columns_belong_to_cb {
82    ($($C:ident),+) => {
83        impl<Table, $($C),+> ColumnsBelongTo<Table, ($($C,)+)> for ()
84        where $($C: ColumnOf<Table>,)+ {}
85    };
86}
87with_tuple_sizes!(impl_columns_belong_to_cb);
88
89#[diagnostic::on_unimplemented(
90    message = "constraint requires at least one column",
91    label = "provide one or more columns for this constraint"
92)]
93pub trait NonEmptyColSet<Cols> {}
94
95macro_rules! impl_non_empty_col_set_cb {
96    ($($C:ident),+) => {
97        impl<$($C),+> NonEmptyColSet<($($C,)+)> for () {}
98    };
99}
100with_tuple_sizes!(impl_non_empty_col_set_cb);
101
102#[diagnostic::on_unimplemented(
103    message = "constraint columns contain duplicates",
104    label = "each column must appear only once in a constraint"
105)]
106pub trait NoDuplicateColSet<Cols> {}
107impl NoDuplicateColSet<()> for () {}
108
109#[diagnostic::on_unimplemented(
110    message = "primary key columns must all be NOT NULL",
111    label = "one or more primary key columns are nullable — remove `Option<>` from them",
112    note = "wrap the column type directly (e.g., `pub id: i64`) instead of `Option<i64>`"
113)]
114pub trait PkNotNull<Cols> {}
115impl PkNotNull<()> for () {}
116
117macro_rules! impl_pk_not_null_cb {
118    ($($C:ident),+) => {
119        impl<$($C),+> PkNotNull<($($C,)+)> for ()
120        where $($C: ColumnNotNull,)+ {}
121    };
122}
123with_tuple_sizes!(impl_pk_not_null_cb);
124
125#[diagnostic::on_unimplemented(
126    message = "foreign key column count mismatch between source and target",
127    label = "the number of FK columns must match the number of referenced columns",
128    note = "e.g., a composite FK with 2 source columns must reference exactly 2 target columns"
129)]
130pub trait FkArityMatch<SrcCols, DstCols> {}
131
132macro_rules! impl_fk_arity_match_cb {
133    ($($S:ident),+; $($D:ident),+) => {
134        impl<$($S,)+ $($D,)+> FkArityMatch<($($S,)+), ($($D,)+)> for () {}
135    };
136}
137with_dual_tuple_sizes!(impl_fk_arity_match_cb);
138
139#[diagnostic::on_unimplemented(
140    message = "foreign key column types do not match the referenced columns",
141    label = "each FK column's type must match the corresponding referenced column's type"
142)]
143pub trait FkTypeMatch<SrcCols, DstCols> {}
144impl FkTypeMatch<(), ()> for () {}
145
146macro_rules! impl_fk_type_match_cb {
147    ($($S:ident),+; $($D:ident),+) => {
148        impl<$($S,)+ $($D,)+> FkTypeMatch<($($S,)+), ($($D,)+)> for ()
149        where
150            $(
151                $S: ColumnValueType,
152                $D: ColumnValueType,
153                <$S as ColumnValueType>::ValueType: TypeEq<<$D as ColumnValueType>::ValueType>,
154            )+
155        {}
156    };
157}
158with_dual_tuple_sizes!(impl_fk_type_match_cb);
159
160#[diagnostic::on_unimplemented(
161    message = "foreign key references a table not present in this schema",
162    label = "add the referenced table to your schema definition",
163    note = "all tables referenced by foreign keys must be included in the schema"
164)]
165pub trait ForeignKeysInSchema<S> {}
166impl<S> ForeignKeysInSchema<S> for () {}
167
168macro_rules! impl_foreign_keys_in_schema_cb {
169    ($($Fk:ident),+) => {
170        impl<S, $($Fk),+> ForeignKeysInSchema<S> for ($($Fk,)+)
171        where
172            $(
173                $Fk: SQLForeignKey,
174                S: SchemaHasTable<<$Fk as SQLForeignKey>::TargetTable>,
175            )+
176        {}
177    };
178}
179with_tuple_sizes!(impl_foreign_keys_in_schema_cb);
180
181#[diagnostic::on_unimplemented(
182    message = "schema contains tables with foreign keys referencing tables not in the schema",
183    label = "ensure all FK target tables are included in this schema"
184)]
185pub trait ValidateTableSetForeignKeys<S> {}
186impl<S> ValidateTableSetForeignKeys<S> for Nil {}
187
188pub trait SQLTableMeta {
189    type ForeignKeys;
190    type PrimaryKey;
191    type Constraints;
192}
193
194impl<S, Head, Tail> ValidateTableSetForeignKeys<S> for Cons<Head, Tail>
195where
196    Head: SQLTableMeta,
197    <Head as SQLTableMeta>::ForeignKeys: ForeignKeysInSchema<S>,
198    Tail: ValidateTableSetForeignKeys<S>,
199{
200}
201
202pub trait ValidateSchemaItemForeignKeys<S> {}
203
204impl<S, Item> ValidateSchemaItemForeignKeys<S> for Item
205where
206    Item: crate::traits::SchemaItemTables,
207    <Item as crate::traits::SchemaItemTables>::Tables: ValidateTableSetForeignKeys<S>,
208{
209}
210
211/// Marker trait for valid ON CONFLICT column targets.
212/// Generated for PK columns, unique columns, PK ZSTs, unique constraint ZSTs,
213/// and unique index structs.
214#[diagnostic::on_unimplemented(
215    message = "`{Self}` is not a valid conflict target for table `{Table}`",
216    label = "use a primary key, unique column, or unique index/constraint from this table",
217    note = "ON CONFLICT targets must be primary key columns, unique columns, or unique indexes"
218)]
219pub trait ConflictTarget<Table>: Copy {
220    fn conflict_columns(&self) -> &'static [&'static str];
221
222    /// Predicate required to infer a matching partial unique index.
223    fn conflict_where_clause(&self) -> Option<&'static str> {
224        None
225    }
226}
227
228/// Marker trait for named constraints usable with ON CONFLICT ON CONSTRAINT (PG-only).
229/// Generated for unique columns and unique constraint ZSTs that have a name.
230#[diagnostic::on_unimplemented(
231    message = "`{Self}` is not a named constraint on table `{Table}`",
232    label = "ON CONSTRAINT requires a named unique or exclusion constraint"
233)]
234pub trait NamedConstraint<Table>: Copy {
235    fn constraint_name(&self) -> &'static str;
236}