1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::schema::{
5 ReferenceAction,
6 names::{ColumnName, TableName},
7};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
10#[serde(rename_all = "snake_case", tag = "type")]
11pub enum TableConstraint {
12 PrimaryKey {
13 #[serde(default)]
14 auto_increment: bool,
15 columns: Vec<ColumnName>,
16 },
17 Unique {
18 #[serde(skip_serializing_if = "Option::is_none")]
19 name: Option<String>,
20 columns: Vec<ColumnName>,
21 },
22 ForeignKey {
23 #[serde(skip_serializing_if = "Option::is_none")]
24 name: Option<String>,
25 columns: Vec<ColumnName>,
26 ref_table: TableName,
27 ref_columns: Vec<ColumnName>,
28 on_delete: Option<ReferenceAction>,
29 on_update: Option<ReferenceAction>,
30 },
31 Check {
32 name: String,
33 expr: String,
34 },
35 Index {
36 #[serde(skip_serializing_if = "Option::is_none")]
37 name: Option<String>,
38 columns: Vec<ColumnName>,
39 },
40}
41
42impl TableConstraint {
43 pub fn columns(&self) -> &[ColumnName] {
46 match self {
47 TableConstraint::PrimaryKey { columns, .. } => columns,
48 TableConstraint::Unique { columns, .. } => columns,
49 TableConstraint::ForeignKey { columns, .. } => columns,
50 TableConstraint::Index { columns, .. } => columns,
51 TableConstraint::Check { .. } => &[],
52 }
53 }
54
55 pub fn with_prefix(self, prefix: &str) -> Self {
58 if prefix.is_empty() {
59 return self;
60 }
61 match self {
62 TableConstraint::ForeignKey {
63 name,
64 columns,
65 ref_table,
66 ref_columns,
67 on_delete,
68 on_update,
69 } => TableConstraint::ForeignKey {
70 name,
71 columns,
72 ref_table: format!("{}{}", prefix, ref_table),
73 ref_columns,
74 on_delete,
75 on_update,
76 },
77 other => other,
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_columns_primary_key() {
89 let pk = TableConstraint::PrimaryKey {
90 auto_increment: false,
91 columns: vec!["id".into(), "tenant_id".into()],
92 };
93 assert_eq!(pk.columns().len(), 2);
94 assert_eq!(pk.columns()[0], "id");
95 assert_eq!(pk.columns()[1], "tenant_id");
96 }
97
98 #[test]
99 fn test_columns_unique() {
100 let unique = TableConstraint::Unique {
101 name: Some("uq_email".into()),
102 columns: vec!["email".into()],
103 };
104 assert_eq!(unique.columns().len(), 1);
105 assert_eq!(unique.columns()[0], "email");
106 }
107
108 #[test]
109 fn test_columns_foreign_key() {
110 let fk = TableConstraint::ForeignKey {
111 name: Some("fk_user".into()),
112 columns: vec!["user_id".into()],
113 ref_table: "users".into(),
114 ref_columns: vec!["id".into()],
115 on_delete: None,
116 on_update: None,
117 };
118 assert_eq!(fk.columns().len(), 1);
119 assert_eq!(fk.columns()[0], "user_id");
120 }
121
122 #[test]
123 fn test_columns_index() {
124 let idx = TableConstraint::Index {
125 name: Some("ix_created_at".into()),
126 columns: vec!["created_at".into()],
127 };
128 assert_eq!(idx.columns().len(), 1);
129 assert_eq!(idx.columns()[0], "created_at");
130 }
131
132 #[test]
133 fn test_columns_check_returns_empty() {
134 let check = TableConstraint::Check {
135 name: "check_positive".into(),
136 expr: "amount > 0".into(),
137 };
138 assert!(check.columns().is_empty());
139 }
140
141 #[test]
142 fn test_with_prefix_foreign_key() {
143 let fk = TableConstraint::ForeignKey {
144 name: Some("fk_user".into()),
145 columns: vec!["user_id".into()],
146 ref_table: "users".into(),
147 ref_columns: vec!["id".into()],
148 on_delete: None,
149 on_update: None,
150 };
151 let prefixed = fk.with_prefix("myapp_");
152 if let TableConstraint::ForeignKey { ref_table, .. } = prefixed {
153 assert_eq!(ref_table.as_str(), "myapp_users");
154 } else {
155 panic!("Expected ForeignKey");
156 }
157 }
158
159 #[test]
160 fn test_with_prefix_non_fk_unchanged() {
161 let pk = TableConstraint::PrimaryKey {
162 auto_increment: false,
163 columns: vec!["id".into()],
164 };
165 let prefixed = pk.clone().with_prefix("myapp_");
166 assert_eq!(pk, prefixed);
167
168 let unique = TableConstraint::Unique {
169 name: Some("uq_email".into()),
170 columns: vec!["email".into()],
171 };
172 let prefixed = unique.clone().with_prefix("myapp_");
173 assert_eq!(unique, prefixed);
174
175 let idx = TableConstraint::Index {
176 name: Some("ix_created_at".into()),
177 columns: vec!["created_at".into()],
178 };
179 let prefixed = idx.clone().with_prefix("myapp_");
180 assert_eq!(idx, prefixed);
181
182 let check = TableConstraint::Check {
183 name: "check_positive".into(),
184 expr: "amount > 0".into(),
185 };
186 let prefixed = check.clone().with_prefix("myapp_");
187 assert_eq!(check, prefixed);
188 }
189
190 #[test]
191 fn test_with_prefix_empty_prefix() {
192 let fk = TableConstraint::ForeignKey {
193 name: Some("fk_user".into()),
194 columns: vec!["user_id".into()],
195 ref_table: "users".into(),
196 ref_columns: vec!["id".into()],
197 on_delete: None,
198 on_update: None,
199 };
200 let prefixed = fk.clone().with_prefix("");
201 assert_eq!(fk, prefixed);
202 }
203}