1use crate::relation::SchemaHasTable;
2use crate::traits::{SQLForeignKey, type_set::Cons, type_set::Nil};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum SQLConstraintKind {
7 PrimaryKey,
9 ForeignKey,
11 Unique,
13 Check,
15}
16
17pub trait SQLConstraint {
19 type Table;
21 type Kind;
23 type Columns;
25}
26
27pub struct NoConstraint;
29
30impl SQLConstraint for NoConstraint {
31 type Table = ();
32 type Kind = ();
33 type Columns = ();
34}
35
36pub struct PrimaryKeyK;
38pub struct ForeignKeyK;
40pub struct UniqueK;
42pub struct CheckK;
44
45#[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#[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#[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#[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
97pub trait ColumnValueType {
99 type ValueType;
101}
102
103#[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#[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#[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#[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#[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#[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#[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#[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
228pub trait SQLTableMeta {
230 type ForeignKeys;
232 type PrimaryKey;
234 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
246pub 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#[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 fn conflict_columns(&self) -> &'static [&'static str];
270
271 fn conflict_where_clause(&self) -> Option<&'static str> {
274 None
275 }
276}
277
278#[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 fn constraint_name(&self) -> &'static str;
289}