1use crate::entities::{Entity, EntityRegistry};
7use crate::{Error, Result};
8use serde_json::Value;
9use std::collections::HashMap;
10use std::path::Path;
11
12pub type SeedData = HashMap<String, Vec<HashMap<String, Value>>>;
16
17pub async fn seed_entity(
25 database: &dyn crate::database::VirtualDatabase,
26 registry: &EntityRegistry,
27 entity_name: &str,
28 records: &[HashMap<String, Value>],
29) -> Result<usize> {
30 let entity = registry
31 .get(entity_name)
32 .ok_or_else(|| Error::generic(format!("Entity '{}' not found", entity_name)))?;
33
34 let table_name = entity.table_name();
35 let mut inserted_count = 0;
36
37 for record in records {
38 validate_foreign_keys(registry, entity, record)?;
40
41 let fields: Vec<String> = record.keys().cloned().collect();
43 let placeholders: Vec<String> = (0..fields.len()).map(|_| "?".to_string()).collect();
44
45 let query = format!(
46 "INSERT INTO {} ({}) VALUES ({})",
47 table_name,
48 fields.join(", "),
49 placeholders.join(", ")
50 );
51
52 let values: Vec<Value> =
54 fields.iter().map(|f| record.get(f).cloned().unwrap_or(Value::Null)).collect();
55
56 database.execute(&query, &values).await?;
57 inserted_count += 1;
58 }
59
60 Ok(inserted_count)
61}
62
63pub async fn seed_all(
73 database: &dyn crate::database::VirtualDatabase,
74 registry: &EntityRegistry,
75 seed_data: &SeedData,
76) -> Result<HashMap<String, usize>> {
77 let order = topological_sort(registry, seed_data)?;
79
80 let mut results = HashMap::new();
81
82 for entity_name in order {
84 if let Some(records) = seed_data.get(&entity_name) {
85 let count = seed_entity(database, registry, &entity_name, records).await?;
86 results.insert(entity_name.clone(), count);
87 }
88 }
89
90 Ok(results)
91}
92
93pub async fn load_seed_file_json<P: AsRef<Path>>(path: P) -> Result<SeedData> {
95 let content = tokio::fs::read_to_string(path.as_ref())
96 .await
97 .map_err(|e| Error::generic(format!("Failed to read seed file: {}", e)))?;
98
99 let json: Value = serde_json::from_str(&content)
100 .map_err(|e| Error::generic(format!("Failed to parse JSON: {}", e)))?;
101
102 parse_seed_data(json)
103}
104
105pub async fn load_seed_file_yaml<P: AsRef<Path>>(path: P) -> Result<SeedData> {
107 let content = tokio::fs::read_to_string(path.as_ref())
108 .await
109 .map_err(|e| Error::generic(format!("Failed to read seed file: {}", e)))?;
110
111 let yaml: Value = serde_yaml::from_str(&content)
112 .map_err(|e| Error::generic(format!("Failed to parse YAML: {}", e)))?;
113
114 parse_seed_data(yaml)
115}
116
117pub async fn load_seed_file<P: AsRef<Path>>(path: P) -> Result<SeedData> {
119 let path_ref = path.as_ref();
120 let ext = path_ref.extension().and_then(|e| e.to_str()).unwrap_or("").to_lowercase();
121
122 match ext.as_str() {
123 "json" => load_seed_file_json(path_ref).await,
124 "yaml" | "yml" => load_seed_file_yaml(path_ref).await,
125 _ => {
126 match load_seed_file_json(path_ref).await {
128 Ok(data) => Ok(data),
129 Err(_) => load_seed_file_yaml(path_ref).await,
130 }
131 }
132 }
133}
134
135fn parse_seed_data(value: Value) -> Result<SeedData> {
137 let obj = value
138 .as_object()
139 .ok_or_else(|| Error::generic("Seed data must be an object".to_string()))?;
140
141 let mut seed_data = HashMap::new();
142
143 for (entity_name, records_value) in obj {
144 let records = records_value
145 .as_array()
146 .ok_or_else(|| {
147 Error::generic(format!("Entity '{}' seed data must be an array", entity_name))
148 })?
149 .iter()
150 .map(|v| {
151 v.as_object()
152 .ok_or_else(|| {
153 Error::generic(format!(
154 "Record in entity '{}' must be an object",
155 entity_name
156 ))
157 })
158 .map(|obj| {
159 obj.iter()
160 .map(|(k, v)| (k.clone(), v.clone()))
161 .collect::<HashMap<String, Value>>()
162 })
163 })
164 .collect::<Result<Vec<_>>>()?;
165
166 seed_data.insert(entity_name.clone(), records);
167 }
168
169 Ok(seed_data)
170}
171
172fn validate_foreign_keys(
174 registry: &EntityRegistry,
175 entity: &Entity,
176 record: &HashMap<String, Value>,
177) -> Result<()> {
178 for fk in &entity.schema.foreign_keys {
179 if let Some(fk_value) = record.get(&fk.field) {
180 let target_entity = registry.get(&fk.target_entity).ok_or_else(|| {
182 Error::generic(format!(
183 "Target entity '{}' not found for foreign key '{}'",
184 fk.target_entity, fk.field
185 ))
186 })?;
187
188 let target_table = target_entity.table_name();
189
190 if fk_value.is_null()
193 && !entity
194 .schema
195 .base
196 .fields
197 .iter()
198 .find(|f| f.name == fk.field)
199 .map(|f| !f.required)
200 .unwrap_or(false)
201 {
202 return Err(Error::generic(format!("Foreign key '{}' cannot be null", fk.field)));
203 }
204 }
205 }
206
207 Ok(())
208}
209
210fn topological_sort(registry: &EntityRegistry, seed_data: &SeedData) -> Result<Vec<String>> {
214 let mut graph: HashMap<String, Vec<String>> = HashMap::new();
216 let mut in_degree: HashMap<String, usize> = HashMap::new();
217
218 for entity_name in seed_data.keys() {
220 graph.insert(entity_name.clone(), Vec::new());
221 in_degree.insert(entity_name.clone(), 0);
222 }
223
224 for entity_name in seed_data.keys() {
226 if let Some(entity) = registry.get(entity_name) {
227 for fk in &entity.schema.foreign_keys {
228 if seed_data.contains_key(&fk.target_entity) {
229 graph.entry(fk.target_entity.clone()).or_default().push(entity_name.clone());
231 *in_degree.entry(entity_name.clone()).or_insert(0) += 1;
232 }
233 }
234 }
235 }
236
237 let mut queue: Vec<String> = in_degree
239 .iter()
240 .filter(|(_, °ree)| degree == 0)
241 .map(|(name, _)| name.clone())
242 .collect();
243
244 let mut result = Vec::new();
245
246 while let Some(node) = queue.pop() {
247 result.push(node.clone());
248
249 if let Some(neighbors) = graph.get(&node) {
250 for neighbor in neighbors {
251 let degree = in_degree.get_mut(neighbor).unwrap();
252 *degree -= 1;
253 if *degree == 0 {
254 queue.push(neighbor.clone());
255 }
256 }
257 }
258 }
259
260 if result.len() != seed_data.len() {
262 return Err(Error::generic(
263 "Circular dependency detected in foreign key relationships".to_string(),
264 ));
265 }
266
267 Ok(result)
268}
269
270pub async fn clear_entity(
272 database: &dyn crate::database::VirtualDatabase,
273 registry: &EntityRegistry,
274 entity_name: &str,
275) -> Result<()> {
276 let entity = registry
277 .get(entity_name)
278 .ok_or_else(|| Error::generic(format!("Entity '{}' not found", entity_name)))?;
279
280 let table_name = entity.table_name();
281 let query = format!("DELETE FROM {}", table_name);
282
283 database.execute(&query, &[]).await?;
284
285 Ok(())
286}
287
288pub async fn clear_all(
290 database: &dyn crate::database::VirtualDatabase,
291 registry: &EntityRegistry,
292) -> Result<()> {
293 let entities: Vec<String> = registry.list();
295
296 for entity_name in entities {
299 if let Err(e) = clear_entity(database, registry, &entity_name).await {
300 tracing::warn!("Failed to clear entity '{}': {}", entity_name, e);
302 }
303 }
304
305 Ok(())
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_parse_seed_data() {
314 let json = serde_json::json!({
315 "users": [
316 {"id": "user1", "name": "Alice"},
317 {"id": "user2", "name": "Bob"}
318 ],
319 "orders": [
320 {"id": "order1", "user_id": "user1", "total": 100.0}
321 ]
322 });
323
324 let seed_data = parse_seed_data(json).unwrap();
325 assert_eq!(seed_data.len(), 2);
326 assert_eq!(seed_data.get("users").unwrap().len(), 2);
327 assert_eq!(seed_data.get("orders").unwrap().len(), 1);
328 }
329
330 #[test]
331 fn test_topological_sort() {
332 }
334}