use crate::entities::{Entity, EntityRegistry};
use crate::{Error, Result};
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
pub type SeedData = HashMap<String, Vec<HashMap<String, Value>>>;
pub async fn seed_entity(
database: &dyn crate::database::VirtualDatabase,
registry: &EntityRegistry,
entity_name: &str,
records: &[HashMap<String, Value>],
) -> Result<usize> {
let entity = registry
.get(entity_name)
.ok_or_else(|| Error::internal(format!("Entity '{}' not found", entity_name)))?;
let table_name = entity.table_name();
let mut inserted_count = 0;
for record in records {
validate_foreign_keys(registry, entity, record)?;
let fields: Vec<String> = record.keys().cloned().collect();
let placeholders: Vec<String> = (0..fields.len()).map(|_| "?".to_string()).collect();
let query = format!(
"INSERT INTO {} ({}) VALUES ({})",
table_name,
fields.join(", "),
placeholders.join(", ")
);
let values: Vec<Value> =
fields.iter().map(|f| record.get(f).cloned().unwrap_or(Value::Null)).collect();
database.execute(&query, &values).await?;
inserted_count += 1;
}
Ok(inserted_count)
}
pub async fn seed_all(
database: &dyn crate::database::VirtualDatabase,
registry: &EntityRegistry,
seed_data: &SeedData,
) -> Result<HashMap<String, usize>> {
let order = topological_sort(registry, seed_data)?;
let mut results = HashMap::new();
for entity_name in order {
if let Some(records) = seed_data.get(&entity_name) {
let count = seed_entity(database, registry, &entity_name, records).await?;
results.insert(entity_name.clone(), count);
}
}
Ok(results)
}
pub async fn load_seed_file_json<P: AsRef<Path>>(path: P) -> Result<SeedData> {
let content = tokio::fs::read_to_string(path.as_ref())
.await
.map_err(|e| Error::internal(format!("Failed to read seed file: {}", e)))?;
let json: Value = serde_json::from_str(&content)
.map_err(|e| Error::internal(format!("Failed to parse JSON: {}", e)))?;
parse_seed_data(json)
}
pub async fn load_seed_file_yaml<P: AsRef<Path>>(path: P) -> Result<SeedData> {
let content = tokio::fs::read_to_string(path.as_ref())
.await
.map_err(|e| Error::internal(format!("Failed to read seed file: {}", e)))?;
let yaml: Value = serde_yaml::from_str(&content)
.map_err(|e| Error::internal(format!("Failed to parse YAML: {}", e)))?;
parse_seed_data(yaml)
}
pub async fn load_seed_file<P: AsRef<Path>>(path: P) -> Result<SeedData> {
let path_ref = path.as_ref();
let ext = path_ref.extension().and_then(|e| e.to_str()).unwrap_or("").to_lowercase();
match ext.as_str() {
"json" => load_seed_file_json(path_ref).await,
"yaml" | "yml" => load_seed_file_yaml(path_ref).await,
_ => {
match load_seed_file_json(path_ref).await {
Ok(data) => Ok(data),
Err(_) => load_seed_file_yaml(path_ref).await,
}
}
}
}
fn parse_seed_data(value: Value) -> Result<SeedData> {
let obj = value
.as_object()
.ok_or_else(|| Error::internal("Seed data must be an object".to_string()))?;
let mut seed_data = HashMap::new();
for (entity_name, records_value) in obj {
let records = records_value
.as_array()
.ok_or_else(|| {
Error::internal(format!("Entity '{}' seed data must be an array", entity_name))
})?
.iter()
.map(|v| {
v.as_object()
.ok_or_else(|| {
Error::internal(format!(
"Record in entity '{}' must be an object",
entity_name
))
})
.map(|obj| {
obj.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<String, Value>>()
})
})
.collect::<Result<Vec<_>>>()?;
seed_data.insert(entity_name.clone(), records);
}
Ok(seed_data)
}
fn validate_foreign_keys(
registry: &EntityRegistry,
entity: &Entity,
record: &HashMap<String, Value>,
) -> Result<()> {
for fk in &entity.schema.foreign_keys {
if let Some(fk_value) = record.get(&fk.field) {
let target_entity = registry.get(&fk.target_entity).ok_or_else(|| {
Error::internal(format!(
"Target entity '{}' not found for foreign key '{}'",
fk.target_entity, fk.field
))
})?;
let _target_table = target_entity.table_name();
if fk_value.is_null()
&& !entity
.schema
.base
.fields
.iter()
.find(|f| f.name == fk.field)
.map(|f| !f.required)
.unwrap_or(false)
{
return Err(Error::internal(format!("Foreign key '{}' cannot be null", fk.field)));
}
}
}
Ok(())
}
fn topological_sort(registry: &EntityRegistry, seed_data: &SeedData) -> Result<Vec<String>> {
let mut graph: HashMap<String, Vec<String>> = HashMap::new();
let mut in_degree: HashMap<String, usize> = HashMap::new();
for entity_name in seed_data.keys() {
graph.insert(entity_name.clone(), Vec::new());
in_degree.insert(entity_name.clone(), 0);
}
for entity_name in seed_data.keys() {
if let Some(entity) = registry.get(entity_name) {
for fk in &entity.schema.foreign_keys {
if seed_data.contains_key(&fk.target_entity) {
graph.entry(fk.target_entity.clone()).or_default().push(entity_name.clone());
*in_degree.entry(entity_name.clone()).or_insert(0) += 1;
}
}
}
}
let mut queue: Vec<String> = in_degree
.iter()
.filter(|(_, °ree)| degree == 0)
.map(|(name, _)| name.clone())
.collect();
let mut result = Vec::new();
while let Some(node) = queue.pop() {
result.push(node.clone());
if let Some(neighbors) = graph.get(&node) {
for neighbor in neighbors {
let degree = in_degree.get_mut(neighbor).unwrap();
*degree -= 1;
if *degree == 0 {
queue.push(neighbor.clone());
}
}
}
}
if result.len() != seed_data.len() {
return Err(Error::internal(
"Circular dependency detected in foreign key relationships".to_string(),
));
}
Ok(result)
}
pub async fn clear_entity(
database: &dyn crate::database::VirtualDatabase,
registry: &EntityRegistry,
entity_name: &str,
) -> Result<()> {
let entity = registry
.get(entity_name)
.ok_or_else(|| Error::internal(format!("Entity '{}' not found", entity_name)))?;
let table_name = entity.table_name();
let query = format!("DELETE FROM {}", table_name);
database.execute(&query, &[]).await?;
Ok(())
}
pub async fn clear_all(
database: &dyn crate::database::VirtualDatabase,
registry: &EntityRegistry,
) -> Result<()> {
let entities: Vec<String> = registry.list();
for entity_name in entities {
if let Err(e) = clear_entity(database, registry, &entity_name).await {
tracing::warn!("Failed to clear entity '{}': {}", entity_name, e);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_seed_data() {
let json = serde_json::json!({
"users": [
{"id": "user1", "name": "Alice"},
{"id": "user2", "name": "Bob"}
],
"orders": [
{"id": "order1", "user_id": "user1", "total": 100.0}
]
});
let seed_data = parse_seed_data(json).unwrap();
assert_eq!(seed_data.len(), 2);
assert_eq!(seed_data.get("users").unwrap().len(), 2);
assert_eq!(seed_data.get("orders").unwrap().len(), 1);
}
#[test]
fn test_topological_sort() {
}
}