Skip to main content

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