mockforge_vbr/
migration.rs

1//! Database migration system
2//!
3//! This module handles generating SQLite schema from entity definitions,
4//! creating tables, indexes, foreign keys, and managing schema migrations.
5
6use crate::entities::{Entity, EntityRegistry};
7use crate::schema::{CascadeAction, ManyToManyDefinition, VbrSchemaDefinition};
8use crate::{Error, Result};
9
10/// Migration manager for database schema evolution
11pub struct MigrationManager {
12    /// Current migration version
13    version: u64,
14}
15
16impl MigrationManager {
17    /// Create a new migration manager
18    pub fn new() -> Self {
19        Self { version: 0 }
20    }
21
22    /// Generate CREATE TABLE statement from an entity
23    pub fn generate_create_table(&self, entity: &Entity) -> Result<String> {
24        let schema = &entity.schema;
25        let table_name = entity.table_name();
26
27        let mut sql = format!("CREATE TABLE IF NOT EXISTS {} (\n", table_name);
28        let mut columns = Vec::new();
29
30        // Add columns from schema fields
31        for field in &schema.base.fields {
32            let column_def = self.field_to_column_definition(field, schema)?;
33            columns.push(column_def);
34        }
35
36        // Add primary key constraint
37        if !schema.primary_key.is_empty() {
38            let pk_fields = schema.primary_key.join(", ");
39            columns.push(format!("PRIMARY KEY ({})", pk_fields));
40        }
41
42        sql.push_str(&columns.join(",\n    "));
43        sql.push_str("\n)");
44
45        Ok(sql)
46    }
47
48    /// Generate foreign key constraints
49    pub fn generate_foreign_keys(&self, entity: &Entity) -> Vec<String> {
50        let mut fk_statements = Vec::new();
51        let table_name = entity.table_name();
52
53        for fk in &entity.schema.foreign_keys {
54            let on_delete = cascade_action_to_sql(fk.on_delete);
55            let on_update = cascade_action_to_sql(fk.on_update);
56
57            let fk_name = format!("fk_{}_{}", table_name, fk.field);
58            let statement = format!(
59                "ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {}({}) ON DELETE {} ON UPDATE {}",
60                table_name, fk_name, fk.field, fk.target_entity.to_lowercase() + "s", fk.target_field, on_delete, on_update
61            );
62            fk_statements.push(statement);
63        }
64
65        fk_statements
66    }
67
68    /// Generate index creation statements
69    pub fn generate_indexes(&self, entity: &Entity) -> Vec<String> {
70        let mut index_statements = Vec::new();
71        let table_name = entity.table_name();
72
73        for index in &entity.schema.indexes {
74            let unique = if index.unique { "UNIQUE " } else { "" };
75            let fields = index.fields.join(", ");
76            let statement = format!(
77                "CREATE {}INDEX IF NOT EXISTS {} ON {} ({})",
78                unique, index.name, table_name, fields
79            );
80            index_statements.push(statement);
81        }
82
83        index_statements
84    }
85
86    /// Generate junction table creation statement for a many-to-many relationship
87    pub fn generate_junction_table(&self, m2m: &ManyToManyDefinition) -> Result<String> {
88        let junction_table = m2m
89            .junction_table
90            .as_ref()
91            .ok_or_else(|| Error::generic("Junction table name is required".to_string()))?;
92
93        // Get table names for entities (assuming pluralization)
94        let table_a = m2m.entity_a.to_lowercase() + "s";
95        let table_b = m2m.entity_b.to_lowercase() + "s";
96
97        let on_delete_a = cascade_action_to_sql(m2m.on_delete_a);
98        let on_delete_b = cascade_action_to_sql(m2m.on_delete_b);
99
100        // Create junction table with composite primary key
101        let sql = format!(
102            "CREATE TABLE IF NOT EXISTS {} (
103    {} TEXT NOT NULL,
104    {} TEXT NOT NULL,
105    PRIMARY KEY ({}, {}),
106    FOREIGN KEY ({}) REFERENCES {}(id) ON DELETE {},
107    FOREIGN KEY ({}) REFERENCES {}(id) ON DELETE {}
108)",
109            junction_table,
110            m2m.entity_a_field,
111            m2m.entity_b_field,
112            m2m.entity_a_field,
113            m2m.entity_b_field,
114            m2m.entity_a_field,
115            table_a,
116            on_delete_a,
117            m2m.entity_b_field,
118            table_b,
119            on_delete_b
120        );
121
122        Ok(sql)
123    }
124
125    /// Generate all junction tables for many-to-many relationships in the registry
126    pub fn generate_all_junction_tables(
127        &self,
128        registry: &EntityRegistry,
129    ) -> Result<Vec<(String, String)>> {
130        let mut junction_tables = Vec::new();
131        let mut processed = std::collections::HashSet::new();
132
133        // Collect all many-to-many relationships
134        for entity in registry.list() {
135            if let Some(entity_def) = registry.get(&entity) {
136                for m2m in &entity_def.schema.many_to_many {
137                    // Create a canonical key to avoid duplicates
138                    let (a, b) = if m2m.entity_a < m2m.entity_b {
139                        (m2m.entity_a.clone(), m2m.entity_b.clone())
140                    } else {
141                        (m2m.entity_b.clone(), m2m.entity_a.clone())
142                    };
143                    let key = format!("{}:{}", a, b);
144
145                    if !processed.contains(&key) {
146                        processed.insert(key);
147                        let table_name = m2m
148                            .junction_table
149                            .as_ref()
150                            .ok_or_else(|| {
151                                Error::generic("Junction table name is required".to_string())
152                            })?
153                            .clone();
154                        let create_sql = self.generate_junction_table(m2m)?;
155                        junction_tables.push((table_name, create_sql));
156                    }
157                }
158            }
159        }
160
161        Ok(junction_tables)
162    }
163
164    /// Convert a field definition to SQL column definition
165    fn field_to_column_definition(
166        &self,
167        field: &mockforge_data::FieldDefinition,
168        schema: &VbrSchemaDefinition,
169    ) -> Result<String> {
170        let name = &field.name;
171        let sql_type = rust_type_to_sql_type(&field.field_type)?;
172        let mut parts = vec![format!("{} {}", name, sql_type)];
173
174        // Add NOT NULL if required
175        if field.required {
176            parts.push("NOT NULL".to_string());
177        }
178
179        // Add default value if specified
180        if let Some(default) = &field.default {
181            let default_value = value_to_sql_default(default)?;
182            parts.push(format!("DEFAULT {}", default_value));
183        }
184
185        // Check for auto-generation rules
186        if let Some(rule) = schema.auto_generation.get(name) {
187            match rule {
188                crate::schema::AutoGenerationRule::AutoIncrement => {
189                    parts.push("AUTOINCREMENT".to_string());
190                }
191                crate::schema::AutoGenerationRule::Uuid => {
192                    // UUID will be generated in application code
193                }
194                crate::schema::AutoGenerationRule::Timestamp => {
195                    parts.push("DEFAULT CURRENT_TIMESTAMP".to_string());
196                }
197                crate::schema::AutoGenerationRule::Date => {
198                    parts.push("DEFAULT (date('now'))".to_string());
199                }
200                crate::schema::AutoGenerationRule::Custom(expr) => {
201                    parts.push(format!("DEFAULT ({})", expr));
202                }
203                crate::schema::AutoGenerationRule::Pattern(_) => {
204                    // Pattern-based IDs are generated in application code
205                }
206                crate::schema::AutoGenerationRule::Realistic { .. } => {
207                    // Realistic IDs are generated in application code
208                }
209            }
210        }
211
212        Ok(parts.join(" "))
213    }
214
215    /// Migrate database schema for all entities
216    pub async fn migrate(
217        &self,
218        entities: &[Entity],
219        database: &mut dyn crate::database::VirtualDatabase,
220    ) -> Result<()> {
221        // Generate and execute CREATE TABLE statements
222        for entity in entities {
223            let create_table = self.generate_create_table(entity)?;
224            database.create_table(&create_table).await?;
225
226            // Create indexes
227            for index_sql in self.generate_indexes(entity) {
228                database.execute(&index_sql, &[]).await?;
229            }
230
231            // Note: Foreign keys are added after all tables are created
232            // This will be handled in a separate pass
233        }
234
235        // Second pass: add foreign key constraints
236        for entity in entities {
237            for fk_sql in self.generate_foreign_keys(entity) {
238                // Foreign keys might fail if tables don't exist yet, so we continue on error
239                let _ = database.execute(&fk_sql, &[]).await;
240            }
241        }
242
243        Ok(())
244    }
245}
246
247impl Default for MigrationManager {
248    fn default() -> Self {
249        Self::new()
250    }
251}
252
253/// Create a database table for an entity
254///
255/// This is a convenience function that creates a table, indexes, and foreign keys
256/// for a single entity.
257///
258/// # Arguments
259/// * `database` - The virtual database instance
260/// * `entity` - The entity to create a table for
261pub async fn create_table_for_entity(
262    database: &dyn crate::database::VirtualDatabase,
263    entity: &Entity,
264) -> Result<()> {
265    let manager = MigrationManager::new();
266
267    // Create the table
268    let create_table = manager.generate_create_table(entity)?;
269    database.create_table(&create_table).await?;
270
271    // Create indexes
272    for index_sql in manager.generate_indexes(entity) {
273        database.execute(&index_sql, &[]).await?;
274    }
275
276    // Create foreign keys (if target tables exist)
277    for fk_sql in manager.generate_foreign_keys(entity) {
278        // Foreign keys might fail if target tables don't exist yet
279        // This is okay - they'll be created when the target entity is processed
280        let _ = database.execute(&fk_sql, &[]).await;
281    }
282
283    Ok(())
284}
285
286/// Create all junction tables for many-to-many relationships
287///
288/// # Arguments
289/// * `database` - The virtual database instance
290/// * `registry` - The entity registry containing all entities
291pub async fn create_junction_tables(
292    database: &dyn crate::database::VirtualDatabase,
293    registry: &EntityRegistry,
294) -> Result<()> {
295    let manager = MigrationManager::new();
296    let junction_tables = manager.generate_all_junction_tables(registry)?;
297
298    for (_table_name, create_sql) in junction_tables {
299        database.create_table(&create_sql).await?;
300    }
301
302    Ok(())
303}
304
305/// Convert Rust type to SQL type
306fn rust_type_to_sql_type(rust_type: &str) -> Result<&str> {
307    match rust_type.to_lowercase().as_str() {
308        "string" | "str" | "text" | "uuid" | "email" | "url" => Ok("TEXT"),
309        "integer" | "int" | "i32" | "i64" => Ok("INTEGER"),
310        "float" | "double" | "f32" | "f64" | "number" => Ok("REAL"),
311        "boolean" | "bool" => Ok("INTEGER"), // SQLite uses INTEGER for booleans
312        "date" | "datetime" | "timestamp" => Ok("TEXT"), // SQLite stores dates as TEXT
313        _ => Ok("TEXT"),                     // Default to TEXT for unknown types
314    }
315}
316
317/// Convert cascade action to SQL
318fn cascade_action_to_sql(action: CascadeAction) -> &'static str {
319    match action {
320        CascadeAction::NoAction => "NO ACTION",
321        CascadeAction::Cascade => "CASCADE",
322        CascadeAction::SetNull => "SET NULL",
323        CascadeAction::SetDefault => "SET DEFAULT",
324        CascadeAction::Restrict => "RESTRICT",
325    }
326}
327
328/// Convert serde_json::Value to SQL default value string
329fn value_to_sql_default(value: &serde_json::Value) -> Result<String> {
330    match value {
331        serde_json::Value::String(s) => Ok(format!("'{}'", s.replace("'", "''"))),
332        serde_json::Value::Number(n) => Ok(n.to_string()),
333        serde_json::Value::Bool(b) => Ok(if *b { "1" } else { "0" }.to_string()),
334        serde_json::Value::Null => Ok("NULL".to_string()),
335        _ => Err(Error::generic("Unsupported default value type".to_string())),
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342    use crate::database::{InMemoryDatabase, VirtualDatabase};
343    use crate::schema::{
344        AutoGenerationRule, CascadeAction, ForeignKeyDefinition, IndexDefinition,
345        ManyToManyDefinition,
346    };
347    use mockforge_data::{FieldDefinition, SchemaDefinition};
348
349    fn create_test_entity(name: &str) -> Entity {
350        let base_schema = SchemaDefinition::new(name.to_string())
351            .with_field(FieldDefinition::new("id".to_string(), "string".to_string()))
352            .with_field(FieldDefinition::new("name".to_string(), "string".to_string()));
353
354        let vbr_schema = VbrSchemaDefinition::new(base_schema);
355        Entity::new(name.to_string(), vbr_schema)
356    }
357
358    // MigrationManager tests
359    #[test]
360    fn test_migration_manager_new() {
361        let manager = MigrationManager::new();
362        assert_eq!(manager.version, 0);
363    }
364
365    #[test]
366    fn test_migration_manager_default() {
367        let manager = MigrationManager::default();
368        assert_eq!(manager.version, 0);
369    }
370
371    #[test]
372    fn test_generate_create_table() {
373        let manager = MigrationManager::new();
374        let entity = create_test_entity("User");
375
376        let result = manager.generate_create_table(&entity);
377        assert!(result.is_ok());
378
379        let sql = result.unwrap();
380        assert!(sql.contains("CREATE TABLE IF NOT EXISTS users"));
381        assert!(sql.contains("id TEXT"));
382        assert!(sql.contains("name TEXT"));
383        assert!(sql.contains("PRIMARY KEY (id)"));
384    }
385
386    #[test]
387    fn test_generate_create_table_with_multiple_fields() {
388        let manager = MigrationManager::new();
389
390        let base_schema = SchemaDefinition::new("Product".to_string())
391            .with_field(FieldDefinition::new("id".to_string(), "string".to_string()))
392            .with_field(FieldDefinition::new("name".to_string(), "string".to_string()))
393            .with_field(FieldDefinition::new("price".to_string(), "number".to_string()))
394            .with_field(FieldDefinition::new("in_stock".to_string(), "boolean".to_string()));
395
396        let vbr_schema = VbrSchemaDefinition::new(base_schema);
397        let entity = Entity::new("Product".to_string(), vbr_schema);
398
399        let sql = manager.generate_create_table(&entity).unwrap();
400        assert!(sql.contains("name TEXT"));
401        assert!(sql.contains("price REAL"));
402        assert!(sql.contains("in_stock INTEGER"));
403    }
404
405    #[test]
406    fn test_generate_foreign_keys() {
407        let manager = MigrationManager::new();
408
409        let base_schema = SchemaDefinition::new("Order".to_string())
410            .with_field(FieldDefinition::new("id".to_string(), "string".to_string()));
411
412        let mut vbr_schema = VbrSchemaDefinition::new(base_schema);
413        vbr_schema.foreign_keys.push(ForeignKeyDefinition {
414            field: "user_id".to_string(),
415            target_entity: "User".to_string(),
416            target_field: "id".to_string(),
417            on_delete: CascadeAction::Cascade,
418            on_update: CascadeAction::NoAction,
419        });
420
421        let entity = Entity::new("Order".to_string(), vbr_schema);
422
423        let fk_statements = manager.generate_foreign_keys(&entity);
424        assert_eq!(fk_statements.len(), 1);
425        assert!(fk_statements[0].contains("FOREIGN KEY"));
426        assert!(fk_statements[0].contains("user_id"));
427        assert!(fk_statements[0].contains("CASCADE"));
428    }
429
430    #[test]
431    fn test_generate_indexes() {
432        let manager = MigrationManager::new();
433
434        let base_schema = SchemaDefinition::new("User".to_string());
435        let mut vbr_schema = VbrSchemaDefinition::new(base_schema);
436
437        vbr_schema.indexes.push(IndexDefinition {
438            name: "idx_email".to_string(),
439            fields: vec!["email".to_string()],
440            unique: true,
441        });
442
443        let entity = Entity::new("User".to_string(), vbr_schema);
444
445        let index_statements = manager.generate_indexes(&entity);
446        assert_eq!(index_statements.len(), 1);
447        assert!(index_statements[0].contains("CREATE UNIQUE INDEX"));
448        assert!(index_statements[0].contains("idx_email"));
449        assert!(index_statements[0].contains("email"));
450    }
451
452    #[test]
453    fn test_generate_indexes_non_unique() {
454        let manager = MigrationManager::new();
455
456        let base_schema = SchemaDefinition::new("Product".to_string());
457        let mut vbr_schema = VbrSchemaDefinition::new(base_schema);
458
459        vbr_schema.indexes.push(IndexDefinition {
460            name: "idx_category".to_string(),
461            fields: vec!["category".to_string()],
462            unique: false,
463        });
464
465        let entity = Entity::new("Product".to_string(), vbr_schema);
466
467        let index_statements = manager.generate_indexes(&entity);
468        assert_eq!(index_statements.len(), 1);
469        assert!(index_statements[0].contains("CREATE INDEX"));
470        assert!(!index_statements[0].contains("UNIQUE"));
471    }
472
473    #[test]
474    fn test_generate_junction_table() {
475        let manager = MigrationManager::new();
476
477        let m2m = ManyToManyDefinition::new("User".to_string(), "Role".to_string());
478
479        let result = manager.generate_junction_table(&m2m);
480        assert!(result.is_ok());
481
482        let sql = result.unwrap();
483        assert!(sql.contains("CREATE TABLE IF NOT EXISTS"));
484        assert!(sql.contains("user_id"));
485        assert!(sql.contains("role_id"));
486        assert!(sql.contains("PRIMARY KEY"));
487        assert!(sql.contains("FOREIGN KEY"));
488    }
489
490    #[test]
491    fn test_generate_junction_table_custom() {
492        let manager = MigrationManager::new();
493
494        let m2m = ManyToManyDefinition::new("User".to_string(), "Role".to_string())
495            .with_junction_table("user_role_mapping".to_string())
496            .with_fields("usr_id".to_string(), "rol_id".to_string());
497
498        let sql = manager.generate_junction_table(&m2m).unwrap();
499        assert!(sql.contains("user_role_mapping"));
500        assert!(sql.contains("usr_id"));
501        assert!(sql.contains("rol_id"));
502    }
503
504    #[test]
505    fn test_generate_all_junction_tables() {
506        let manager = MigrationManager::new();
507        let mut registry = EntityRegistry::new();
508
509        // Create entities with M2M relationships
510        let base_schema1 = SchemaDefinition::new("User".to_string());
511        let mut vbr_schema1 = VbrSchemaDefinition::new(base_schema1);
512        vbr_schema1
513            .many_to_many
514            .push(ManyToManyDefinition::new("User".to_string(), "Role".to_string()));
515        let entity1 = Entity::new("User".to_string(), vbr_schema1);
516        registry.register(entity1).unwrap();
517
518        let base_schema2 = SchemaDefinition::new("Role".to_string());
519        let vbr_schema2 = VbrSchemaDefinition::new(base_schema2);
520        let entity2 = Entity::new("Role".to_string(), vbr_schema2);
521        registry.register(entity2).unwrap();
522
523        let result = manager.generate_all_junction_tables(&registry);
524        assert!(result.is_ok());
525
526        let junction_tables = result.unwrap();
527        assert_eq!(junction_tables.len(), 1);
528    }
529
530    #[test]
531    fn test_generate_all_junction_tables_no_duplicates() {
532        let manager = MigrationManager::new();
533        let mut registry = EntityRegistry::new();
534
535        // Both entities define the same M2M relationship
536        let base_schema1 = SchemaDefinition::new("User".to_string());
537        let mut vbr_schema1 = VbrSchemaDefinition::new(base_schema1);
538        vbr_schema1
539            .many_to_many
540            .push(ManyToManyDefinition::new("User".to_string(), "Role".to_string()));
541        let entity1 = Entity::new("User".to_string(), vbr_schema1);
542        registry.register(entity1).unwrap();
543
544        let base_schema2 = SchemaDefinition::new("Role".to_string());
545        let mut vbr_schema2 = VbrSchemaDefinition::new(base_schema2);
546        vbr_schema2
547            .many_to_many
548            .push(ManyToManyDefinition::new("Role".to_string(), "User".to_string()));
549        let entity2 = Entity::new("Role".to_string(), vbr_schema2);
550        registry.register(entity2).unwrap();
551
552        let junction_tables = manager.generate_all_junction_tables(&registry).unwrap();
553        // Should only create one junction table, not two
554        assert_eq!(junction_tables.len(), 1);
555    }
556
557    #[tokio::test]
558    async fn test_migrate() {
559        let manager = MigrationManager::new();
560        let mut database = InMemoryDatabase::new().await.unwrap();
561        database.initialize().await.unwrap();
562
563        let entity = create_test_entity("User");
564        let entities = vec![entity];
565
566        let result = manager.migrate(&entities, &mut database).await;
567        assert!(result.is_ok());
568
569        // Verify table was created
570        assert!(database.table_exists("users").await.unwrap());
571    }
572
573    #[tokio::test]
574    async fn test_create_table_for_entity() {
575        let mut database = InMemoryDatabase::new().await.unwrap();
576        database.initialize().await.unwrap();
577
578        let entity = create_test_entity("Product");
579
580        let result = create_table_for_entity(&database, &entity).await;
581        assert!(result.is_ok());
582
583        // Verify table was created
584        assert!(database.table_exists("products").await.unwrap());
585    }
586
587    #[tokio::test]
588    async fn test_create_junction_tables() {
589        let mut database = InMemoryDatabase::new().await.unwrap();
590        database.initialize().await.unwrap();
591
592        let mut registry = EntityRegistry::new();
593
594        // Create entities with M2M relationship
595        let base_schema1 = SchemaDefinition::new("User".to_string());
596        let mut vbr_schema1 = VbrSchemaDefinition::new(base_schema1);
597        vbr_schema1
598            .many_to_many
599            .push(ManyToManyDefinition::new("User".to_string(), "Group".to_string()));
600        let entity1 = Entity::new("User".to_string(), vbr_schema1);
601        registry.register(entity1).unwrap();
602
603        let result = create_junction_tables(&database, &registry).await;
604        assert!(result.is_ok());
605    }
606
607    // Helper function tests
608    #[test]
609    fn test_rust_type_to_sql_type() {
610        assert_eq!(rust_type_to_sql_type("string").unwrap(), "TEXT");
611        assert_eq!(rust_type_to_sql_type("String").unwrap(), "TEXT");
612        assert_eq!(rust_type_to_sql_type("integer").unwrap(), "INTEGER");
613        assert_eq!(rust_type_to_sql_type("int").unwrap(), "INTEGER");
614        assert_eq!(rust_type_to_sql_type("i32").unwrap(), "INTEGER");
615        assert_eq!(rust_type_to_sql_type("float").unwrap(), "REAL");
616        assert_eq!(rust_type_to_sql_type("f64").unwrap(), "REAL");
617        assert_eq!(rust_type_to_sql_type("boolean").unwrap(), "INTEGER");
618        assert_eq!(rust_type_to_sql_type("bool").unwrap(), "INTEGER");
619        assert_eq!(rust_type_to_sql_type("date").unwrap(), "TEXT");
620        assert_eq!(rust_type_to_sql_type("datetime").unwrap(), "TEXT");
621        assert_eq!(rust_type_to_sql_type("unknown_type").unwrap(), "TEXT");
622    }
623
624    #[test]
625    fn test_cascade_action_to_sql() {
626        assert_eq!(cascade_action_to_sql(CascadeAction::NoAction), "NO ACTION");
627        assert_eq!(cascade_action_to_sql(CascadeAction::Cascade), "CASCADE");
628        assert_eq!(cascade_action_to_sql(CascadeAction::SetNull), "SET NULL");
629        assert_eq!(cascade_action_to_sql(CascadeAction::SetDefault), "SET DEFAULT");
630        assert_eq!(cascade_action_to_sql(CascadeAction::Restrict), "RESTRICT");
631    }
632
633    #[test]
634    fn test_value_to_sql_default() {
635        // String
636        let result = value_to_sql_default(&serde_json::json!("test")).unwrap();
637        assert_eq!(result, "'test'");
638
639        // String with quotes
640        let result = value_to_sql_default(&serde_json::json!("it's")).unwrap();
641        assert_eq!(result, "'it''s'");
642
643        // Number
644        let result = value_to_sql_default(&serde_json::json!(42)).unwrap();
645        assert_eq!(result, "42");
646
647        // Boolean true
648        let result = value_to_sql_default(&serde_json::json!(true)).unwrap();
649        assert_eq!(result, "1");
650
651        // Boolean false
652        let result = value_to_sql_default(&serde_json::json!(false)).unwrap();
653        assert_eq!(result, "0");
654
655        // Null
656        let result = value_to_sql_default(&serde_json::json!(null)).unwrap();
657        assert_eq!(result, "NULL");
658
659        // Unsupported type (array)
660        let result = value_to_sql_default(&serde_json::json!([]));
661        assert!(result.is_err());
662    }
663
664    #[test]
665    fn test_field_to_column_definition_with_auto_generation() {
666        let manager = MigrationManager::new();
667
668        let base_schema = SchemaDefinition::new("User".to_string())
669            .with_field(FieldDefinition::new("id".to_string(), "string".to_string()));
670
671        let mut vbr_schema = VbrSchemaDefinition::new(base_schema);
672        vbr_schema.auto_generation.insert("id".to_string(), AutoGenerationRule::Uuid);
673
674        // Access field from base schema
675        let field = &vbr_schema.base.fields[0];
676        let result = manager.field_to_column_definition(field, &vbr_schema);
677        assert!(result.is_ok());
678
679        let column_def = result.unwrap();
680        assert!(column_def.contains("id"));
681        assert!(column_def.contains("TEXT"));
682    }
683
684    #[test]
685    fn test_field_to_column_definition_with_default() {
686        let manager = MigrationManager::new();
687
688        let mut field = FieldDefinition::new("status".to_string(), "string".to_string());
689        field.default = Some(serde_json::json!("active"));
690        let base_schema = SchemaDefinition::new("User".to_string()).with_field(field);
691
692        let vbr_schema = VbrSchemaDefinition::new(base_schema);
693        let field_ref = &vbr_schema.base.fields[0];
694
695        let column_def = manager.field_to_column_definition(field_ref, &vbr_schema).unwrap();
696        assert!(column_def.contains("DEFAULT 'active'"));
697    }
698
699    #[test]
700    fn test_field_to_column_definition_required() {
701        let manager = MigrationManager::new();
702
703        let base_schema = SchemaDefinition::new("User".to_string())
704            .with_field(FieldDefinition::new("email".to_string(), "string".to_string()));
705
706        let vbr_schema = VbrSchemaDefinition::new(base_schema);
707        let field = &vbr_schema.base.fields[0];
708
709        let column_def = manager.field_to_column_definition(field, &vbr_schema).unwrap();
710        assert!(column_def.contains("NOT NULL"));
711    }
712
713    #[test]
714    fn test_generate_create_table_with_composite_primary_key() {
715        let manager = MigrationManager::new();
716
717        let base_schema = SchemaDefinition::new("UserRole".to_string());
718        let vbr_schema = VbrSchemaDefinition::new(base_schema)
719            .with_primary_key(vec!["user_id".to_string(), "role_id".to_string()]);
720
721        let entity = Entity::new("UserRole".to_string(), vbr_schema);
722
723        let sql = manager.generate_create_table(&entity).unwrap();
724        assert!(sql.contains("PRIMARY KEY (user_id, role_id)"));
725    }
726
727    #[test]
728    fn test_generate_create_table_with_auto_increment() {
729        let manager = MigrationManager::new();
730
731        let base_schema = SchemaDefinition::new("Counter".to_string())
732            .with_field(FieldDefinition::new("id".to_string(), "integer".to_string()));
733
734        let mut vbr_schema = VbrSchemaDefinition::new(base_schema);
735        vbr_schema
736            .auto_generation
737            .insert("id".to_string(), AutoGenerationRule::AutoIncrement);
738
739        let entity = Entity::new("Counter".to_string(), vbr_schema);
740
741        let sql = manager.generate_create_table(&entity).unwrap();
742        assert!(sql.contains("AUTOINCREMENT"));
743    }
744}