1use crate::{Error, Result};
7use serde_json::Value;
8use std::collections::HashMap;
9
10pub struct ConstraintValidator;
12
13impl ConstraintValidator {
14 pub async fn validate_foreign_key(
16 &self,
17 database: &dyn crate::database::VirtualDatabase,
18 table_name: &str,
19 field: &str,
20 value: &Value,
21 target_table: &str,
22 target_field: &str,
23 ) -> Result<()> {
24 let query = format!("SELECT COUNT(*) FROM {} WHERE {} = ?", target_table, target_field);
26
27 let params = vec![value.clone()];
28 let results = database.query(&query, ¶ms).await?;
29
30 if results.is_empty() || results[0].get("COUNT(*)") == Some(&Value::Number(0.into())) {
31 return Err(Error::generic(format!(
32 "Foreign key constraint violation: {} = {:?} does not exist in {}.{}",
33 field, value, target_table, target_field
34 )));
35 }
36
37 Ok(())
38 }
39
40 pub async fn validate_unique(
42 &self,
43 database: &dyn crate::database::VirtualDatabase,
44 table_name: &str,
45 fields: &[String],
46 values: &HashMap<String, Value>,
47 exclude_id: Option<&Value>,
48 ) -> Result<()> {
49 let mut conditions = Vec::new();
50 let mut params = Vec::new();
51
52 for field in fields {
53 if let Some(value) = values.get(field) {
54 conditions.push(format!("{} = ?", field));
55 params.push(value.clone());
56 }
57 }
58
59 if conditions.is_empty() {
60 return Ok(()); }
62
63 let mut query =
64 format!("SELECT COUNT(*) FROM {} WHERE {}", table_name, conditions.join(" AND "));
65
66 if let Some(id) = exclude_id {
67 query.push_str(" AND id != ?");
68 params.push(id.clone());
69 }
70
71 let results = database.query(&query, ¶ms).await?;
72
73 if !results.is_empty() {
74 if let Some(count) = results[0].get("COUNT(*)") {
75 if count.as_u64().unwrap_or(0) > 0 {
76 return Err(Error::generic(format!(
77 "Unique constraint violation: combination of {:?} already exists",
78 fields
79 )));
80 }
81 }
82 }
83
84 Ok(())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::database::{InMemoryDatabase, VirtualDatabase};
92
93 async fn setup_test_db() -> InMemoryDatabase {
94 let mut db = InMemoryDatabase::new().await.unwrap();
95 db.initialize().await.unwrap();
96
97 db.create_table("CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT)")
99 .await
100 .unwrap();
101
102 db.create_table(
104 "CREATE TABLE IF NOT EXISTS orders (id TEXT PRIMARY KEY, user_id TEXT, total REAL)",
105 )
106 .await
107 .unwrap();
108
109 db
110 }
111
112 #[tokio::test]
113 async fn test_validate_foreign_key_success() {
114 let db = setup_test_db().await;
115
116 db.execute(
118 "INSERT INTO users (id, name) VALUES (?, ?)",
119 &[
120 Value::String("user-1".to_string()),
121 Value::String("John".to_string()),
122 ],
123 )
124 .await
125 .unwrap();
126
127 let validator = ConstraintValidator;
128 let result = validator
129 .validate_foreign_key(
130 &db,
131 "orders",
132 "user_id",
133 &Value::String("user-1".to_string()),
134 "users",
135 "id",
136 )
137 .await;
138
139 assert!(result.is_ok());
140 }
141
142 #[tokio::test]
143 async fn test_validate_foreign_key_failure() {
144 let db = setup_test_db().await;
145
146 let validator = ConstraintValidator;
147 let result = validator
148 .validate_foreign_key(
149 &db,
150 "orders",
151 "user_id",
152 &Value::String("nonexistent-user".to_string()),
153 "users",
154 "id",
155 )
156 .await;
157
158 assert!(result.is_err());
159 let err = result.unwrap_err();
160 assert!(err.to_string().contains("Foreign key constraint violation"));
161 }
162
163 #[tokio::test]
164 async fn test_validate_unique_no_duplicates() {
165 let db = setup_test_db().await;
166
167 db.execute(
169 "INSERT INTO users (id, name) VALUES (?, ?)",
170 &[
171 Value::String("user-1".to_string()),
172 Value::String("John".to_string()),
173 ],
174 )
175 .await
176 .unwrap();
177
178 let validator = ConstraintValidator;
179 let mut values = HashMap::new();
180 values.insert("name".to_string(), Value::String("Jane".to_string()));
181
182 let result = validator
183 .validate_unique(&db, "users", &["name".to_string()], &values, None)
184 .await;
185
186 assert!(result.is_ok());
187 }
188
189 #[tokio::test]
190 async fn test_validate_unique_with_duplicate() {
191 let db = setup_test_db().await;
192
193 db.execute(
195 "INSERT INTO users (id, name) VALUES (?, ?)",
196 &[
197 Value::String("user-1".to_string()),
198 Value::String("John".to_string()),
199 ],
200 )
201 .await
202 .unwrap();
203
204 let validator = ConstraintValidator;
205 let mut values = HashMap::new();
206 values.insert("name".to_string(), Value::String("John".to_string()));
207
208 let result = validator
209 .validate_unique(&db, "users", &["name".to_string()], &values, None)
210 .await;
211
212 assert!(result.is_err());
213 let err = result.unwrap_err();
214 assert!(err.to_string().contains("Unique constraint violation"));
215 }
216
217 #[tokio::test]
218 async fn test_validate_unique_with_exclude_id() {
219 let db = setup_test_db().await;
220
221 db.execute(
223 "INSERT INTO users (id, name) VALUES (?, ?)",
224 &[
225 Value::String("user-1".to_string()),
226 Value::String("John".to_string()),
227 ],
228 )
229 .await
230 .unwrap();
231
232 let validator = ConstraintValidator;
233 let mut values = HashMap::new();
234 values.insert("name".to_string(), Value::String("John".to_string()));
235
236 let result = validator
238 .validate_unique(
239 &db,
240 "users",
241 &["name".to_string()],
242 &values,
243 Some(&Value::String("user-1".to_string())),
244 )
245 .await;
246
247 assert!(result.is_ok());
248 }
249
250 #[tokio::test]
251 async fn test_validate_unique_empty_fields() {
252 let db = setup_test_db().await;
253
254 let validator = ConstraintValidator;
255 let values = HashMap::new();
256
257 let result = validator.validate_unique(&db, "users", &[], &values, None).await;
258
259 assert!(result.is_ok());
260 }
261
262 #[tokio::test]
263 async fn test_validate_unique_missing_value() {
264 let db = setup_test_db().await;
265
266 let validator = ConstraintValidator;
267 let values = HashMap::new(); let result = validator
270 .validate_unique(&db, "users", &["name".to_string()], &values, None)
271 .await;
272
273 assert!(result.is_ok());
275 }
276
277 #[tokio::test]
278 async fn test_validate_unique_multiple_fields() {
279 let db = setup_test_db().await;
280
281 db.create_table(
283 "CREATE TABLE IF NOT EXISTS products (id TEXT PRIMARY KEY, category TEXT, sku TEXT)",
284 )
285 .await
286 .unwrap();
287
288 db.execute(
290 "INSERT INTO products (id, category, sku) VALUES (?, ?, ?)",
291 &[
292 Value::String("prod-1".to_string()),
293 Value::String("electronics".to_string()),
294 Value::String("SKU-001".to_string()),
295 ],
296 )
297 .await
298 .unwrap();
299
300 let validator = ConstraintValidator;
301 let mut values = HashMap::new();
302 values.insert("category".to_string(), Value::String("electronics".to_string()));
303 values.insert("sku".to_string(), Value::String("SKU-002".to_string())); let result = validator
306 .validate_unique(
307 &db,
308 "products",
309 &["category".to_string(), "sku".to_string()],
310 &values,
311 None,
312 )
313 .await;
314
315 assert!(result.is_ok());
316 }
317}