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
12pub 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
60#[diagnostic::on_unimplemented(
61 message = "column `{Self}` is nullable and cannot be used here",
62 label = "this column must be NOT NULL",
63 note = "primary key columns cannot be nullable"
64)]
65pub trait ColumnNotNull {}
66
67pub trait ColumnValueType {
68 type ValueType;
69}
70
71#[diagnostic::on_unimplemented(
72 message = "one or more columns do not belong to table `{Table}`",
73 label = "these columns are not all defined on the same table",
74 note = "all constraint columns must belong to the target table"
75)]
76pub trait ColumnsBelongTo<Table, Cols> {}
77impl<Table> ColumnsBelongTo<Table, ()> for () {}
78
79macro_rules! impl_columns_belong_to_cb {
80 ($($C:ident),+) => {
81 impl<Table, $($C),+> ColumnsBelongTo<Table, ($($C,)+)> for ()
82 where $($C: ColumnOf<Table>,)+ {}
83 };
84}
85with_tuple_sizes!(impl_columns_belong_to_cb);
86
87#[diagnostic::on_unimplemented(
88 message = "constraint requires at least one column",
89 label = "provide one or more columns for this constraint"
90)]
91pub trait NonEmptyColSet<Cols> {}
92
93macro_rules! impl_non_empty_col_set_cb {
94 ($($C:ident),+) => {
95 impl<$($C),+> NonEmptyColSet<($($C,)+)> for () {}
96 };
97}
98with_tuple_sizes!(impl_non_empty_col_set_cb);
99
100#[diagnostic::on_unimplemented(
101 message = "constraint columns contain duplicates",
102 label = "each column must appear only once in a constraint"
103)]
104pub trait NoDuplicateColSet<Cols> {}
105impl NoDuplicateColSet<()> for () {}
106
107#[diagnostic::on_unimplemented(
108 message = "primary key columns must all be NOT NULL",
109 label = "one or more primary key columns are nullable — remove `Option<>` from them",
110 note = "wrap the column type directly (e.g., `pub id: i64`) instead of `Option<i64>`"
111)]
112pub trait PkNotNull<Cols> {}
113impl PkNotNull<()> for () {}
114
115macro_rules! impl_pk_not_null_cb {
116 ($($C:ident),+) => {
117 impl<$($C),+> PkNotNull<($($C,)+)> for ()
118 where $($C: ColumnNotNull,)+ {}
119 };
120}
121with_tuple_sizes!(impl_pk_not_null_cb);
122
123#[diagnostic::on_unimplemented(
124 message = "foreign key column count mismatch between source and target",
125 label = "the number of FK columns must match the number of referenced columns",
126 note = "e.g., a composite FK with 2 source columns must reference exactly 2 target columns"
127)]
128pub trait FkArityMatch<SrcCols, DstCols> {}
129
130macro_rules! impl_fk_arity_match_cb {
131 ($($S:ident),+; $($D:ident),+) => {
132 impl<$($S,)+ $($D,)+> FkArityMatch<($($S,)+), ($($D,)+)> for () {}
133 };
134}
135with_dual_tuple_sizes!(impl_fk_arity_match_cb);
136
137#[diagnostic::on_unimplemented(
138 message = "foreign key column types do not match the referenced columns",
139 label = "each FK column's type must match the corresponding referenced column's type"
140)]
141pub trait FkTypeMatch<SrcCols, DstCols> {}
142impl FkTypeMatch<(), ()> for () {}
143
144macro_rules! impl_fk_type_match_cb {
145 ($($S:ident),+; $($D:ident),+) => {
146 impl<$($S,)+ $($D,)+> FkTypeMatch<($($S,)+), ($($D,)+)> for ()
147 where
148 $(
149 $S: ColumnValueType,
150 $D: ColumnValueType,
151 <$S as ColumnValueType>::ValueType: TypeEq<<$D as ColumnValueType>::ValueType>,
152 )+
153 {}
154 };
155}
156with_dual_tuple_sizes!(impl_fk_type_match_cb);
157
158#[diagnostic::on_unimplemented(
159 message = "foreign key references a table not present in this schema",
160 label = "add the referenced table to your schema definition",
161 note = "all tables referenced by foreign keys must be included in the schema"
162)]
163pub trait ForeignKeysInSchema<S> {}
164impl<S> ForeignKeysInSchema<S> for () {}
165
166macro_rules! impl_foreign_keys_in_schema_cb {
167 ($($Fk:ident),+) => {
168 impl<S, $($Fk),+> ForeignKeysInSchema<S> for ($($Fk,)+)
169 where
170 $(
171 $Fk: SQLForeignKey,
172 S: SchemaHasTable<<$Fk as SQLForeignKey>::TargetTable>,
173 )+
174 {}
175 };
176}
177with_tuple_sizes!(impl_foreign_keys_in_schema_cb);
178
179#[diagnostic::on_unimplemented(
180 message = "schema contains tables with foreign keys referencing tables not in the schema",
181 label = "ensure all FK target tables are included in this schema"
182)]
183pub trait ValidateTableSetForeignKeys<S> {}
184impl<S> ValidateTableSetForeignKeys<S> for Nil {}
185
186pub trait SQLTableMeta {
187 type ForeignKeys;
188 type PrimaryKey;
189 type Constraints;
190}
191
192impl<S, Head, Tail> ValidateTableSetForeignKeys<S> for Cons<Head, Tail>
193where
194 Head: SQLTableMeta,
195 <Head as SQLTableMeta>::ForeignKeys: ForeignKeysInSchema<S>,
196 Tail: ValidateTableSetForeignKeys<S>,
197{
198}
199
200pub trait ValidateSchemaItemForeignKeys<S> {}
201
202impl<S, Item> ValidateSchemaItemForeignKeys<S> for Item
203where
204 Item: crate::traits::SchemaItemTables,
205 <Item as crate::traits::SchemaItemTables>::Tables: ValidateTableSetForeignKeys<S>,
206{
207}
208
209#[diagnostic::on_unimplemented(
213 message = "`{Self}` is not a valid conflict target for table `{Table}`",
214 label = "use a primary key, unique column, or unique index/constraint from this table",
215 note = "ON CONFLICT targets must be primary key columns, unique columns, or unique indexes"
216)]
217pub trait ConflictTarget<Table>: Copy {
218 fn conflict_columns(&self) -> &'static [&'static str];
219}
220
221#[diagnostic::on_unimplemented(
224 message = "`{Self}` is not a named constraint on table `{Table}`",
225 label = "ON CONSTRAINT requires a named unique index or unique constraint"
226)]
227pub trait NamedConstraint<Table>: Copy {
228 fn constraint_name(&self) -> &'static str;
229}