diff --git a/.changepacks/changepack_log_CDjntNpuuGDyO1BES5WxE.json b/.changepacks/changepack_log_CDjntNpuuGDyO1BES5WxE.json new file mode 100644 index 0000000..261b484 --- /dev/null +++ b/.changepacks/changepack_log_CDjntNpuuGDyO1BES5WxE.json @@ -0,0 +1 @@ +{"changes":{"crates/vespertide-macro/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch","crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch","crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch","crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide/Cargo.toml":"Patch"},"note":"Optimize query","date":"2025-12-15T06:52:49.007765800Z"} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7ffdf1c..316aa5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1993,7 +1993,7 @@ dependencies = [ "pgvector", "rust_decimal", "sea-orm-macros", - "sea-query", + "sea-query 1.0.0-rc.22", "sea-query-sqlx", "sea-schema", "serde", @@ -2022,6 +2022,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sea-query" +version = "0.32.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c" +dependencies = [ + "inherent", + "sea-query-derive 0.4.3", +] + [[package]] name = "sea-query" version = "1.0.0-rc.22" @@ -2033,12 +2043,26 @@ dependencies = [ "inherent", "ordered-float", "rust_decimal", - "sea-query-derive", + "sea-query-derive 1.0.0-rc.11", "serde_json", "time", "uuid", ] +[[package]] +name = "sea-query-derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" +dependencies = [ + "darling", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.111", + "thiserror", +] + [[package]] name = "sea-query-derive" version = "1.0.0-rc.11" @@ -2062,7 +2086,7 @@ dependencies = [ "bigdecimal", "chrono", "rust_decimal", - "sea-query", + "sea-query 1.0.0-rc.22", "serde_json", "sqlx", "time", @@ -2076,7 +2100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "845b7ed3e7a4f4458fe7218931b54e92be0dce01fc3c310d996c7b76d9a37ea5" dependencies = [ "async-trait", - "sea-query", + "sea-query 1.0.0-rc.22", "sea-query-sqlx", "sea-schema-derive", "sqlx", @@ -3000,7 +3024,9 @@ dependencies = [ name = "vespertide-query" version = "0.1.7" dependencies = [ + "insta", "rstest", + "sea-query 0.32.7", "thiserror", "vespertide-core", ] diff --git a/crates/vespertide-cli/.gitignore b/crates/vespertide-cli/.gitignore new file mode 100644 index 0000000..aeb421f --- /dev/null +++ b/crates/vespertide-cli/.gitignore @@ -0,0 +1 @@ +src/models \ No newline at end of file diff --git a/crates/vespertide-cli/migrations/0001_test_message.json b/crates/vespertide-cli/migrations/0001_test_message.json new file mode 100644 index 0000000..88a92ad --- /dev/null +++ b/crates/vespertide-cli/migrations/0001_test_message.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/migration.schema.json", + "actions": [ + { + "columns": [], + "constraints": [], + "table": "test_table", + "type": "create_table" + } + ], + "comment": "test message", + "created_at": "2025-12-15T06:12:57Z", + "version": 1 +} \ No newline at end of file diff --git a/crates/vespertide-cli/models/test_table.json b/crates/vespertide-cli/models/test_table.json new file mode 100644 index 0000000..bbb6c56 --- /dev/null +++ b/crates/vespertide-cli/models/test_table.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/model.schema.json", + "columns": [], + "constraints": [], + "indexes": [], + "name": "test_table" +} \ No newline at end of file diff --git a/crates/vespertide-cli/src/commands/diff.rs b/crates/vespertide-cli/src/commands/diff.rs index 324e932..adc4673 100644 --- a/crates/vespertide-cli/src/commands/diff.rs +++ b/crates/vespertide-cli/src/commands/diff.rs @@ -169,11 +169,7 @@ fn format_constraint_type(constraint: &vespertide_core::TableConstraint) -> Stri } } vespertide_core::TableConstraint::Check { name, expr } => { - if let Some(n) = name { - format!("{} CHECK ({})", n, expr) - } else { - format!("CHECK ({})", expr) - } + format!("{} CHECK ({})", name, expr) } } } @@ -345,7 +341,7 @@ mod tests { MigrationAction::AddConstraint { table: "users".into(), constraint: vespertide_core::TableConstraint::Check { - name: Some("check_age".into()), + name: "check_age".into(), expr: "age > 0".into(), }, }, @@ -389,11 +385,17 @@ mod tests { MigrationAction::RemoveConstraint { table: "users".into(), constraint: vespertide_core::TableConstraint::Check { - name: None, + name: "check_age".into(), expr: "age > 0".into(), }, }, - format!("{} {} {} {}", "Remove constraint:".bright_red(), "CHECK (age > 0)".bright_cyan().bold(), "from".bright_white(), "users".bright_cyan()) + format!( + "{} {} {} {}", + "Remove constraint:".bright_red(), + "check_age CHECK (age > 0)".bright_cyan().bold(), + "from".bright_white(), + "users".bright_cyan() + ) )] #[serial] fn format_action_cases(#[case] action: MigrationAction, #[case] expected: String) { diff --git a/crates/vespertide-cli/src/commands/log.rs b/crates/vespertide-cli/src/commands/log.rs index 0e1b1ea..25e8f9f 100644 --- a/crates/vespertide-cli/src/commands/log.rs +++ b/crates/vespertide-cli/src/commands/log.rs @@ -1,6 +1,6 @@ use anyhow::Result; use colored::Colorize; -use vespertide_query::build_plan_queries; +use vespertide_query::{DatabaseBackend, build_plan_queries}; use crate::utils::load_migrations; @@ -54,11 +54,9 @@ pub fn cmd_log() -> Result<()> { println!( " {}. {}", (i + 1).to_string().bright_magenta().bold(), - q.sql.trim().bright_white() + q.build(DatabaseBackend::Postgres).trim().bright_white() ); - if !q.binds.is_empty() { - println!(" {} {:?}", "binds:".bright_cyan(), q.binds); - } + println!(" {} {:?}", "binds:".bright_cyan(), q.binds()); } println!(); } diff --git a/crates/vespertide-cli/src/commands/sql.rs b/crates/vespertide-cli/src/commands/sql.rs index cc4b22a..8d82ed4 100644 --- a/crates/vespertide-cli/src/commands/sql.rs +++ b/crates/vespertide-cli/src/commands/sql.rs @@ -1,7 +1,7 @@ use anyhow::Result; use colored::Colorize; use vespertide_planner::plan_next_migration; -use vespertide_query::build_plan_queries; +use vespertide_query::{DatabaseBackend, build_plan_queries}; use crate::utils::{load_config, load_migrations, load_models}; @@ -60,11 +60,9 @@ fn emit_sql(plan: &vespertide_core::MigrationPlan) -> Result<()> { println!( "{}. {}", (i + 1).to_string().bright_magenta().bold(), - q.sql.trim().bright_white() + q.build(DatabaseBackend::Postgres).trim().bright_white() ); - if !q.binds.is_empty() { - println!(" {} {:?}", "binds:".bright_cyan(), q.binds); - } + println!(" {} {:?}", "binds:".bright_cyan(), q.binds()); } Ok(()) @@ -137,31 +135,28 @@ mod tests { let tmp = tempdir().unwrap(); let _guard = CwdGuard::new(&tmp.path().to_path_buf()); - let cfg = write_config(); + let _cfg = write_config(); write_model("users"); - fs::create_dir_all(cfg.migrations_dir()).unwrap(); + // No migrations yet -> plan will create table let result = cmd_sql(); assert!(result.is_ok()); } #[test] - fn emit_sql_no_actions_early_return() { + #[serial] + fn cmd_sql_no_changes() { + let tmp = tempdir().unwrap(); + let _guard = CwdGuard::new(&tmp.path().to_path_buf()); + + let cfg = write_config(); + write_model("users"); + + // Create initial migration to establish baseline let plan = MigrationPlan { comment: None, created_at: None, version: 1, - actions: vec![], - }; - assert!(emit_sql(&plan).is_ok()); - } - - #[test] - fn emit_sql_with_metadata() { - let plan = MigrationPlan { - comment: Some("init".into()), - created_at: Some("2024-01-01T00:00:00Z".into()), - version: 1, actions: vec![MigrationAction::CreateTable { table: "users".into(), columns: vec![ColumnDef { @@ -181,6 +176,27 @@ mod tests { }], }], }; - assert!(emit_sql(&plan).is_ok()); + fs::create_dir_all(cfg.migrations_dir()).unwrap(); + let path = cfg.migrations_dir().join("0001_init.json"); + fs::write(path, serde_json::to_string_pretty(&plan).unwrap()).unwrap(); + + let result = cmd_sql(); + assert!(result.is_ok()); + } + + #[test] + #[serial] + fn emit_sql_prints_created_at_and_comment() { + let plan = MigrationPlan { + comment: Some("with comment".into()), + created_at: Some("2024-01-02T00:00:00Z".into()), + version: 1, + actions: vec![MigrationAction::RawSql { + sql: "SELECT 1;".into(), + }], + }; + + let result = emit_sql(&plan); + assert!(result.is_ok()); } } diff --git a/crates/vespertide-cli/vespertide.json b/crates/vespertide-cli/vespertide.json new file mode 100644 index 0000000..bae157d --- /dev/null +++ b/crates/vespertide-cli/vespertide.json @@ -0,0 +1,10 @@ +{ + "modelsDir": "models", + "migrationsDir": "migrations", + "tableNamingCase": "snake", + "columnNamingCase": "snake", + "modelFormat": "json", + "migrationFormat": "json", + "migrationFilenamePattern": "%04v_%m", + "modelExportDir": "src/models" +} \ No newline at end of file diff --git a/crates/vespertide-core/src/schema/constraint.rs b/crates/vespertide-core/src/schema/constraint.rs index 7cd0d65..64b77ee 100644 --- a/crates/vespertide-core/src/schema/constraint.rs +++ b/crates/vespertide-core/src/schema/constraint.rs @@ -27,7 +27,7 @@ pub enum TableConstraint { on_update: Option, }, Check { - name: Option, + name: String, expr: String, }, } diff --git a/crates/vespertide-core/src/schema/table.rs b/crates/vespertide-core/src/schema/table.rs index bb4844c..2756bcb 100644 --- a/crates/vespertide-core/src/schema/table.rs +++ b/crates/vespertide-core/src/schema/table.rs @@ -89,12 +89,13 @@ impl TableDef { } } - // Add primary key constraint if any columns have inline pk and no existing pk constraint + // Add primary key constraint if any columns have inline pk and no existing pk constraint. if !pk_columns.is_empty() { - let has_pk = constraints + let has_pk_constraint = constraints .iter() .any(|c| matches!(c, TableConstraint::PrimaryKey { .. })); - if !has_pk { + + if !has_pk_constraint { constraints.push(TableConstraint::PrimaryKey { auto_increment: pk_auto_increment, columns: pk_columns, diff --git a/crates/vespertide-macro/src/lib.rs b/crates/vespertide-macro/src/lib.rs index 27ecd3b..f6d9003 100644 --- a/crates/vespertide-macro/src/lib.rs +++ b/crates/vespertide-macro/src/lib.rs @@ -6,7 +6,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::{Expr, Ident, Token, parse_macro_input}; -use vespertide_query::build_plan_queries; +use vespertide_query::{DatabaseBackend, build_plan_queries}; struct MacroInput { pool: Expr, @@ -85,16 +85,21 @@ pub fn vespertide_migration(input: TokenStream) -> TokenStream { } }; - // Statically embed SQL text and bind parameters (as values) + // Pre-generate SQL for all backends at compile time let sql_statements: Vec<_> = queries .iter() .map(|q| { - let sql = &q.sql; - let binds = &q.binds; - let value_tokens = binds.iter().map(|b| { - quote! { sea_orm::Value::String(Some(#b.to_string())) } - }); - quote! { (#sql, vec![#(#value_tokens),*]) } + let pg_sql = q.build(DatabaseBackend::Postgres); + let mysql_sql = q.build(DatabaseBackend::MySql); + let sqlite_sql = q.build(DatabaseBackend::Sqlite); + quote! { + match backend { + sea_orm::DatabaseBackend::Postgres => #pg_sql, + sea_orm::DatabaseBackend::MySql => #mysql_sql, + sea_orm::DatabaseBackend::Sqlite => #sqlite_sql, + _ => #pg_sql, // Fallback to PostgreSQL syntax for unknown backends + } + } }) .collect(); @@ -109,20 +114,18 @@ pub fn vespertide_migration(input: TokenStream) -> TokenStream { // Execute SQL statements #( { - let (sql, values) = #sql_statements; - let stmt = sea_orm::Statement::from_sql_and_values(backend, sql, values); + let sql: &str = #sql_statements; + let stmt = sea_orm::Statement::from_string(backend, sql); txn.execute_raw(stmt).await.map_err(|e| { - ::vespertide::MigrationError::DatabaseError(format!("Failed to execute SQL: {}", e)) + ::vespertide::MigrationError::DatabaseError(format!("Failed to execute SQL '{}': {}", sql, e)) })?; } )* // Insert version record for this migration - let stmt = sea_orm::Statement::from_sql_and_values( - backend, - &format!("INSERT INTO {} (version) VALUES (?)", version_table), - vec![sea_orm::Value::Int(Some(#version as i32))], - ); + let q = if matches!(backend, sea_orm::DatabaseBackend::MySql) { '`' } else { '"' }; + let insert_sql = format!("INSERT INTO {q}{}{q} (version) VALUES ({})", version_table, #version); + let stmt = sea_orm::Statement::from_string(backend, insert_sql); txn.execute_raw(stmt).await.map_err(|e| { ::vespertide::MigrationError::DatabaseError(format!("Failed to insert version: {}", e)) })?; @@ -147,8 +150,9 @@ pub fn vespertide_migration(input: TokenStream) -> TokenStream { // Create version table if it does not exist // Table structure: version (INTEGER PRIMARY KEY), created_at (timestamp) + let q = if matches!(backend, sea_orm::DatabaseBackend::MySql) { '`' } else { '"' }; let create_table_sql = format!( - "CREATE TABLE IF NOT EXISTS {} (version INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)", + "CREATE TABLE IF NOT EXISTS {q}{}{q} (version INTEGER PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)", version_table ); let stmt = sea_orm::Statement::from_string(backend, create_table_sql); @@ -157,10 +161,8 @@ pub fn vespertide_migration(input: TokenStream) -> TokenStream { })?; // Read current maximum version (latest applied migration) - let stmt = sea_orm::Statement::from_string( - backend, - format!("SELECT MAX(version) as version FROM {}", version_table), - ); + let select_sql = format!("SELECT MAX(version) as version FROM {q}{}{q}", version_table); + let stmt = sea_orm::Statement::from_string(backend, select_sql); let version_result = __pool.query_one_raw(stmt).await.map_err(|e| { ::vespertide::MigrationError::DatabaseError(format!("Failed to read version: {}", e)) })?; diff --git a/crates/vespertide-macro/src/loader.rs b/crates/vespertide-macro/src/loader.rs index 5a8839a..70de3de 100644 --- a/crates/vespertide-macro/src/loader.rs +++ b/crates/vespertide-macro/src/loader.rs @@ -66,7 +66,7 @@ pub fn load_migrations_from_dir( #[cfg(test)] mod tests { use super::*; - use std::fs; + use std::{env, fs}; use tempfile::TempDir; #[test] @@ -217,4 +217,39 @@ actions: let plans = result.unwrap(); assert_eq!(plans.len(), 1); } + + #[test] + fn test_load_migrations_at_compile_time_reads_manifest_dir() { + let temp_dir = TempDir::new().unwrap(); + let root = temp_dir.path(); + + let config_content = r#"{ + "modelsDir": "models", + "migrationsDir": "migrations", + "tableNamingCase": "snake", + "columnNamingCase": "snake" + }"#; + fs::write(root.join("vespertide.json"), config_content).unwrap(); + + let migrations_dir = root.join("migrations"); + fs::create_dir_all(&migrations_dir).unwrap(); + fs::write( + migrations_dir.join("0001_test.json"), + r#"{"version":1,"actions":[]}"#, + ) + .unwrap(); + + unsafe { + env::set_var("CARGO_MANIFEST_DIR", root); + } + let result = load_migrations_at_compile_time(); + unsafe { + env::remove_var("CARGO_MANIFEST_DIR"); + } + + assert!(result.is_ok()); + let plans = result.unwrap(); + assert_eq!(plans.len(), 1); + assert_eq!(plans[0].version, 1); + } } diff --git a/crates/vespertide-planner/src/apply.rs b/crates/vespertide-planner/src/apply.rs index 9b22d57..e636945 100644 --- a/crates/vespertide-planner/src/apply.rs +++ b/crates/vespertide-planner/src/apply.rs @@ -411,7 +411,7 @@ mod tests { on_update: None, }, TableConstraint::Check { - name: None, + name: "ck_old".into(), expr: "old IS NOT NULL".into(), }, ], @@ -455,7 +455,7 @@ mod tests { on_update: None, }, TableConstraint::Check { - name: None, + name: "ck_old".into(), expr: "old IS NOT NULL".into(), }, ], @@ -484,7 +484,7 @@ mod tests { on_update: None, }, TableConstraint::Check { - name: None, + name: "ck_old".into(), expr: "old IS NOT NULL".into(), }, ], @@ -500,7 +500,7 @@ mod tests { vec![ TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }, TableConstraint::Check { - name: None, + name: "ck_old".into(), expr: "old IS NOT NULL".into(), }, ], @@ -624,7 +624,7 @@ mod tests { on_update: None, }, TableConstraint::Check { - name: None, + name: "ck_old".into(), expr: "old > 0".into(), }, ], @@ -646,7 +646,7 @@ mod tests { on_update: None, }, TableConstraint::Check { - name: None, + name: "ck_old".into(), expr: "old > 0".into(), }, ], @@ -656,7 +656,7 @@ mod tests { vec![ TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }, TableConstraint::Check { - name: None, + name: "ck_id".into(), expr: "id > 0".into(), }, ], @@ -666,7 +666,7 @@ mod tests { vec![ TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }, TableConstraint::Check { - name: None, + name: "ck_id".into(), expr: "id > 0".into(), }, ], diff --git a/crates/vespertide-planner/src/validate.rs b/crates/vespertide-planner/src/validate.rs index 72c81ee..6c428d6 100644 --- a/crates/vespertide-planner/src/validate.rs +++ b/crates/vespertide-planner/src/validate.rs @@ -522,7 +522,7 @@ mod tests { "users", vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], vec![TableConstraint::Check { - name: Some("ck".into()), + name: "ck".into(), expr: "id > 0".into(), }], vec![], diff --git a/crates/vespertide-query/Cargo.toml b/crates/vespertide-query/Cargo.toml index 5e6ed15..ab0119b 100644 --- a/crates/vespertide-query/Cargo.toml +++ b/crates/vespertide-query/Cargo.toml @@ -11,6 +11,8 @@ description = "Converts migration actions into SQL statements with bind paramete [dependencies] vespertide-core = { workspace = true } thiserror = "2" +sea-query = "0.32" [dev-dependencies] rstest = "0.26" +insta = "1.44" diff --git a/crates/vespertide-query/src/builder.rs b/crates/vespertide-query/src/builder.rs index d20ce2d..0aa4a4c 100644 --- a/crates/vespertide-query/src/builder.rs +++ b/crates/vespertide-query/src/builder.rs @@ -3,6 +3,9 @@ use vespertide_core::MigrationPlan; use crate::error::QueryError; use crate::sql::{BuiltQuery, build_action_queries}; +#[cfg(test)] +use crate::sql::DatabaseBackend; + pub fn build_plan_queries(plan: &MigrationPlan) -> Result, QueryError> { let mut queries: Vec = Vec::new(); for action in &plan.actions { @@ -41,7 +44,7 @@ mod tests { version: 1, actions: vec![], }, - vec![] + 0 )] #[case::single_action( MigrationPlan { @@ -52,9 +55,7 @@ mod tests { table: "users".into(), }], }, - vec![ - ("DROP TABLE $1;".to_string(), vec!["users".to_string()]) - ] + 1 )] #[case::multiple_actions( MigrationPlan { @@ -72,37 +73,55 @@ mod tests { }, ], }, - vec![ - ( - "CREATE TABLE $1 ($2 INTEGER);".to_string(), - vec!["users".to_string(), "id".to_string()] - ), - ( - "DROP TABLE $1;".to_string(), - vec!["posts".to_string()] - ), - ] + 2 )] - fn test_build_plan_queries( - #[case] plan: MigrationPlan, - #[case] expected: Vec<(String, Vec)>, - ) { + fn test_build_plan_queries(#[case] plan: MigrationPlan, #[case] expected_count: usize) { let result = build_plan_queries(&plan).unwrap(); assert_eq!( result.len(), - expected.len(), + expected_count, "Expected {} queries, got {}", - expected.len(), + expected_count, result.len() ); + } - for (i, (expected_sql, expected_binds)) in expected.iter().enumerate() { - assert_eq!(result[i].sql, *expected_sql, "Query {} sql mismatch", i); - assert_eq!( - result[i].binds, *expected_binds, - "Query {} binds mismatch", - i - ); - } + #[test] + fn test_build_plan_queries_sql_content() { + let plan = MigrationPlan { + comment: None, + created_at: None, + version: 1, + actions: vec![ + MigrationAction::CreateTable { + table: "users".into(), + columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + constraints: vec![], + }, + MigrationAction::DeleteTable { + table: "posts".into(), + }, + ], + }; + + let result = build_plan_queries(&plan).unwrap(); + assert_eq!(result.len(), 2); + + // Test PostgreSQL output + let sql1 = result[0].build(DatabaseBackend::Postgres); + assert!(sql1.contains("CREATE TABLE")); + assert!(sql1.contains("\"users\"")); + assert!(sql1.contains("\"id\"")); + + let sql2 = result[1].build(DatabaseBackend::Postgres); + assert!(sql2.contains("DROP TABLE")); + assert!(sql2.contains("\"posts\"")); + + // Test MySQL output + let sql1_mysql = result[0].build(DatabaseBackend::MySql); + assert!(sql1_mysql.contains("`users`")); + + let sql2_mysql = result[1].build(DatabaseBackend::MySql); + assert!(sql2_mysql.contains("`posts`")); } } diff --git a/crates/vespertide-query/src/lib.rs b/crates/vespertide-query/src/lib.rs index b522cbb..6836c5b 100644 --- a/crates/vespertide-query/src/lib.rs +++ b/crates/vespertide-query/src/lib.rs @@ -4,4 +4,4 @@ pub mod sql; pub use builder::build_plan_queries; pub use error::QueryError; -pub use sql::{BuiltQuery, build_action_queries}; +pub use sql::{BuiltQuery, DatabaseBackend, build_action_queries}; diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_mysql.snap new file mode 100644 index 0000000..63bc66e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE `users` ADD COLUMN `nickname` text diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_postgres.snap new file mode 100644 index 0000000..91a0019 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD COLUMN "nickname" text diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_sqlite.snap new file mode 100644 index 0000000..91a0019 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_simple_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD COLUMN "nickname" text diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_mysql.snap new file mode 100644 index 0000000..68b928a --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_mysql.snap @@ -0,0 +1,7 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE `users` ADD COLUMN `age` int +UPDATE "users" SET "age" = 0 +ALTER TABLE "users" ALTER COLUMN "age" SET NOT NULL diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_postgres.snap new file mode 100644 index 0000000..202b660 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_postgres.snap @@ -0,0 +1,7 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD COLUMN "age" integer +UPDATE "users" SET "age" = 0 +ALTER TABLE "users" ALTER COLUMN "age" SET NOT NULL diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_sqlite.snap new file mode 100644 index 0000000..202b660 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_column_with_backfill_sqlite.snap @@ -0,0 +1,7 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD COLUMN "age" integer +UPDATE "users" SET "age" = 0 +ALTER TABLE "users" ALTER COLUMN "age" SET NOT NULL diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_mysql.snap new file mode 100644 index 0000000..03f180e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CONSTRAINT "chk_age" CHECK (age > 0) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_postgres.snap new file mode 100644 index 0000000..03f180e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CONSTRAINT "chk_age" CHECK (age > 0) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_sqlite.snap new file mode 100644 index 0000000..03f180e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_named_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CONSTRAINT "chk_age" CHECK (age > 0) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_mysql.snap new file mode 100644 index 0000000..5ca5652 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CHECK (age > 0) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_postgres.snap new file mode 100644 index 0000000..5ca5652 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CHECK (age > 0) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_sqlite.snap new file mode 100644 index 0000000..5ca5652 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_check_unnamed_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CHECK (age > 0) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_mysql.snap new file mode 100644 index 0000000..4c3d98c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" ADD CONSTRAINT "fk_user" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE RESTRICT diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_postgres.snap new file mode 100644 index 0000000..4c3d98c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" ADD CONSTRAINT "fk_user" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE RESTRICT diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_sqlite.snap new file mode 100644 index 0000000..4c3d98c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" ADD CONSTRAINT "fk_user" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE RESTRICT diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_mysql.snap new file mode 100644 index 0000000..f50b943 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_postgres.snap new file mode 100644 index 0000000..f50b943 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_sqlite.snap new file mode 100644 index 0000000..f50b943 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_foreign_key_unnamed_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_mysql.snap new file mode 100644 index 0000000..7e617d0 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD PRIMARY KEY ("id") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_postgres.snap new file mode 100644 index 0000000..7e617d0 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD PRIMARY KEY ("id") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_sqlite.snap new file mode 100644 index 0000000..7e617d0 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_primary_key_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD PRIMARY KEY ("id") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_mysql.snap new file mode 100644 index 0000000..a34a67b --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CONSTRAINT "uq_email" UNIQUE ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_postgres.snap new file mode 100644 index 0000000..a34a67b --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CONSTRAINT "uq_email" UNIQUE ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_sqlite.snap new file mode 100644 index 0000000..a34a67b --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_named_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD CONSTRAINT "uq_email" UNIQUE ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_mysql.snap new file mode 100644 index 0000000..b191d9c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD UNIQUE ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_postgres.snap new file mode 100644 index 0000000..b191d9c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD UNIQUE ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_sqlite.snap new file mode 100644 index 0000000..b191d9c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_constraint_unique_unnamed_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ADD UNIQUE ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_mysql.snap new file mode 100644 index 0000000..076a40e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE INDEX `idx_email` ON `users` (`email`) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_postgres.snap new file mode 100644 index 0000000..bd73250 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE INDEX "idx_email" ON "users" ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_sqlite.snap new file mode 100644 index 0000000..bd73250 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_index_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE INDEX "idx_email" ON "users" ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_mysql.snap new file mode 100644 index 0000000..e1aed79 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE UNIQUE INDEX `idx_email` ON `users` (`email`) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_postgres.snap new file mode 100644 index 0000000..8b65029 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE UNIQUE INDEX "idx_email" ON "users" ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_sqlite.snap new file mode 100644 index 0000000..8b65029 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_add_unique_index_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE UNIQUE INDEX "idx_email" ON "users" ("email") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_mysql.snap new file mode 100644 index 0000000..a823b84 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE `users` ( `id` int ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_postgres.snap new file mode 100644 index 0000000..23c5c14 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "id" integer, "name" text ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_sqlite.snap new file mode 100644 index 0000000..bdc3b71 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "id" integer ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_mysql.snap new file mode 100644 index 0000000..577057f --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE `users` ( `status` text DEFAULT 'active' ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_postgres.snap new file mode 100644 index 0000000..1c816d2 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "status" text DEFAULT 'active' ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_sqlite.snap new file mode 100644 index 0000000..1c816d2 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_default_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "status" text DEFAULT 'active' ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_mysql.snap new file mode 100644 index 0000000..551e669 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE `posts` ( `id` int, `user_id` int, CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_postgres.snap new file mode 100644 index 0000000..066b107 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "posts" ( "id" integer, "user_id" integer, CONSTRAINT "fk_user" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE RESTRICT ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_sqlite.snap new file mode 100644 index 0000000..78bbcab --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_fk_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "posts" ( "id" integer, "user_id" integer, FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE RESTRICT ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_mysql.snap new file mode 100644 index 0000000..253dca2 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "id" integer NOT NULL, "email" text UNIQUE, PRIMARY KEY ("id") ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_postgres.snap new file mode 100644 index 0000000..253dca2 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "id" integer NOT NULL, "email" text UNIQUE, PRIMARY KEY ("id") ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_sqlite.snap new file mode 100644 index 0000000..253dca2 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_constraints_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "id" integer NOT NULL, "email" text UNIQUE, PRIMARY KEY ("id") ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_mysql.snap new file mode 100644 index 0000000..d34ae95 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE `users` ( `id` int NOT NULL PRIMARY KEY ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_postgres.snap new file mode 100644 index 0000000..53b85e0 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "id" integer NOT NULL PRIMARY KEY ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_sqlite.snap new file mode 100644 index 0000000..53b85e0 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_create_table_with_inline_primary_key_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +CREATE TABLE "users" ( "id" integer NOT NULL PRIMARY KEY ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_mysql.snap new file mode 100644 index 0000000..53a7e85 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE `users` DROP COLUMN `email` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_postgres.snap new file mode 100644 index 0000000..3b50025 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP COLUMN "email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_sqlite.snap new file mode 100644 index 0000000..3b50025 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_column_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP COLUMN "email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_mysql.snap new file mode 100644 index 0000000..abba7fa --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +DROP TABLE `users` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_postgres.snap new file mode 100644 index 0000000..8623a72 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +DROP TABLE "users" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_sqlite.snap new file mode 100644 index 0000000..8623a72 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_delete_table_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +DROP TABLE "users" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_modify_column_type_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_modify_column_type_mysql.snap new file mode 100644 index 0000000..5838d76 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_modify_column_type_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE `users` MODIFY COLUMN `age` varchar(50) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_modify_column_type_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_modify_column_type_postgres.snap new file mode 100644 index 0000000..a23c0da --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_modify_column_type_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" ALTER COLUMN "age" TYPE varchar(50) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_mysql.snap new file mode 100644 index 0000000..6e2c585 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +SELECT 1 diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_postgres.snap new file mode 100644 index 0000000..6e2c585 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +SELECT 1 diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_sqlite.snap new file mode 100644 index 0000000..6e2c585 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_raw_sql_action_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +SELECT 1 diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_mysql.snap new file mode 100644 index 0000000..3dcfbdf --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "chk_age" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_postgres.snap new file mode 100644 index 0000000..3dcfbdf --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "chk_age" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_sqlite.snap new file mode 100644 index 0000000..3dcfbdf --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_check_named_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "chk_age" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_mysql.snap new file mode 100644 index 0000000..f1479c1 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" DROP CONSTRAINT "fk_user" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_postgres.snap new file mode 100644 index 0000000..f1479c1 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" DROP CONSTRAINT "fk_user" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_sqlite.snap new file mode 100644 index 0000000..f1479c1 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_named_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" DROP CONSTRAINT "fk_user" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_mysql.snap new file mode 100644 index 0000000..7d76242 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" DROP CONSTRAINT "posts_user_id_fkey" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_postgres.snap new file mode 100644 index 0000000..7d76242 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" DROP CONSTRAINT "posts_user_id_fkey" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_sqlite.snap new file mode 100644 index 0000000..7d76242 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_foreign_key_unnamed_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "posts" DROP CONSTRAINT "posts_user_id_fkey" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_mysql.snap new file mode 100644 index 0000000..c6d2a4f --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "users_pkey" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_postgres.snap new file mode 100644 index 0000000..c6d2a4f --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "users_pkey" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_sqlite.snap new file mode 100644 index 0000000..c6d2a4f --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_primary_key_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "users_pkey" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_mysql.snap new file mode 100644 index 0000000..39ad3fa --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "uq_email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_postgres.snap new file mode 100644 index 0000000..39ad3fa --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "uq_email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_sqlite.snap new file mode 100644 index 0000000..39ad3fa --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_named_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "uq_email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_mysql.snap new file mode 100644 index 0000000..e6f3dad --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "users_email_key" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_postgres.snap new file mode 100644 index 0000000..e6f3dad --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "users_email_key" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_sqlite.snap new file mode 100644 index 0000000..e6f3dad --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_constraint_unique_unnamed_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" DROP CONSTRAINT "users_email_key" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_mysql.snap new file mode 100644 index 0000000..8336c55 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +DROP INDEX `idx_email` ON diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_postgres.snap new file mode 100644 index 0000000..c54c79e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +DROP INDEX "idx_email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_sqlite.snap new file mode 100644 index 0000000..c54c79e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_remove_index_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +DROP INDEX "idx_email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_mysql.snap new file mode 100644 index 0000000..927d27a --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE `users` RENAME COLUMN `email` TO `contact_email` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_postgres.snap new file mode 100644 index 0000000..f63b6ff --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" RENAME COLUMN "email" TO "contact_email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_sqlite.snap new file mode 100644 index 0000000..f63b6ff --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_column_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" RENAME COLUMN "email" TO "contact_email" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_mysql.snap new file mode 100644 index 0000000..32bf701 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +RENAME TABLE `users` TO `accounts` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_postgres.snap new file mode 100644 index 0000000..1a2e25c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" RENAME TO "accounts" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_sqlite.snap new file mode 100644 index 0000000..1a2e25c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_migration_action@build_migration_action_rename_table_action_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: "result.iter().map(|q| q.build(backend)).collect::>().join(\"\\n\")" +--- +ALTER TABLE "users" RENAME TO "accounts" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_mysql.snap new file mode 100644 index 0000000..33d6f4f --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE `t` ADD COLUMN `c` int diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_postgres.snap new file mode 100644 index 0000000..c945c0e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE "t" ADD COLUMN "c" integer diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_sqlite.snap new file mode 100644 index 0000000..c945c0e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_alter_table_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE "t" ADD COLUMN "c" integer diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_foreign_key_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_foreign_key_mysql.snap new file mode 100644 index 0000000..475864a --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_foreign_key_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE `a` ADD CONSTRAINT `fk` FOREIGN KEY (`c`) REFERENCES `b` (`id`) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_foreign_key_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_foreign_key_postgres.snap new file mode 100644 index 0000000..f1b4adf --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_foreign_key_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE "a" ADD CONSTRAINT "fk" FOREIGN KEY ("c") REFERENCES "b" ("id") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_mysql.snap new file mode 100644 index 0000000..351cc89 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +CREATE INDEX `idx` ON `t` (`c`) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_postgres.snap new file mode 100644 index 0000000..da47560 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +CREATE INDEX "idx" ON "t" ("c") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_sqlite.snap new file mode 100644 index 0000000..da47560 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_index_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +CREATE INDEX "idx" ON "t" ("c") diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_mysql.snap new file mode 100644 index 0000000..d0c931c --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +CREATE TABLE `t` ( `id` int ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_postgres.snap new file mode 100644 index 0000000..3f554ff --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +CREATE TABLE "t" ( "id" integer ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_sqlite.snap new file mode 100644 index 0000000..3f554ff --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_create_table_query_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +CREATE TABLE "t" ( "id" integer ) diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_foreign_key_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_foreign_key_mysql.snap new file mode 100644 index 0000000..73f640e --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_foreign_key_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE `a` DROP FOREIGN KEY `fk` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_foreign_key_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_foreign_key_postgres.snap new file mode 100644 index 0000000..ad765ac --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_foreign_key_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE "a" DROP CONSTRAINT "fk" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_mysql.snap new file mode 100644 index 0000000..99f2f28 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +DROP INDEX `idx` ON `t` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_postgres.snap new file mode 100644 index 0000000..0724d04 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +DROP INDEX "idx" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_sqlite.snap new file mode 100644 index 0000000..0724d04 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_index_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +DROP INDEX "idx" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_mysql.snap new file mode 100644 index 0000000..7815af8 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +DROP TABLE `t` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_postgres.snap new file mode 100644 index 0000000..984540a --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +DROP TABLE "t" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_sqlite.snap new file mode 100644 index 0000000..984540a --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_drop_table_query_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +DROP TABLE "t" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_mysql.snap new file mode 100644 index 0000000..bede88d --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +SELECT 1 diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_postgres.snap new file mode 100644 index 0000000..bede88d --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +SELECT 1 diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_sqlite.snap new file mode 100644 index 0000000..bede88d --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_raw_query_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +SELECT 1 diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_mysql.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_mysql.snap new file mode 100644 index 0000000..68c0a63 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_mysql.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +RENAME TABLE `a` TO `b` diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_postgres.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_postgres.snap new file mode 100644 index 0000000..b442933 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_postgres.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE "a" RENAME TO "b" diff --git a/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_sqlite.snap b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_sqlite.snap new file mode 100644 index 0000000..b442933 --- /dev/null +++ b/crates/vespertide-query/src/snapshots/vespertide_query__sql__tests__build_query@build_query_rename_table_sqlite.snap @@ -0,0 +1,5 @@ +--- +source: crates/vespertide-query/src/sql.rs +expression: sql +--- +ALTER TABLE "a" RENAME TO "b" diff --git a/crates/vespertide-query/src/sql.rs b/crates/vespertide-query/src/sql.rs index aef1936..8a13198 100644 --- a/crates/vespertide-query/src/sql.rs +++ b/crates/vespertide-query/src/sql.rs @@ -1,18 +1,210 @@ -use std::fmt::Write; - -use vespertide_core::{ColumnDef, MigrationAction, TableConstraint}; +use sea_query::{ + Alias, ColumnDef as SeaColumnDef, ForeignKey, ForeignKeyAction, ForeignKeyCreateStatement, + ForeignKeyDropStatement, Index, IndexCreateStatement, IndexDropStatement, MysqlQueryBuilder, + PostgresQueryBuilder, SqliteQueryBuilder, Table, TableAlterStatement, TableCreateStatement, + TableDropStatement, TableRenameStatement, +}; +use vespertide_core::{ + ColumnDef, ColumnType, ComplexColumnType, MigrationAction, ReferenceAction, SimpleColumnType, + TableConstraint, +}; use crate::error::QueryError; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BuiltQuery { - pub sql: String, - pub binds: Vec, +/// Database backend for SQL generation +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DatabaseBackend { + Postgres, + MySql, + Sqlite, +} + +/// Represents a built query that can be converted to SQL for any database backend +#[derive(Debug, Clone)] +pub enum BuiltQuery { + CreateTable(Box), + DropTable(Box), + AlterTable(Box), + CreateIndex(Box), + DropIndex(Box), + RenameTable(Box), + CreateForeignKey(Box), + DropForeignKey(Box), + Raw(String), +} + +impl BuiltQuery { + /// Build SQL string for the specified database backend + pub fn build(&self, backend: DatabaseBackend) -> String { + match self { + BuiltQuery::CreateTable(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::DropTable(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::AlterTable(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::CreateIndex(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::DropIndex(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::RenameTable(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::CreateForeignKey(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::DropForeignKey(stmt) => match backend { + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + BuiltQuery::Raw(sql) => sql.clone(), + } + } + + /// Backward compatibility: binds are now empty (DDL doesn't use bind parameters) + pub fn binds(&self) -> Vec { + Vec::new() + } +} + +/// Apply vespertide ColumnType to sea_query ColumnDef +fn apply_column_type(col: &mut SeaColumnDef, ty: &ColumnType) { + match ty { + ColumnType::Simple(simple) => match simple { + SimpleColumnType::SmallInt => { + col.small_integer(); + } + SimpleColumnType::Integer => { + col.integer(); + } + SimpleColumnType::BigInt => { + col.big_integer(); + } + SimpleColumnType::Real => { + col.float(); + } + SimpleColumnType::DoublePrecision => { + col.double(); + } + SimpleColumnType::Text => { + col.text(); + } + SimpleColumnType::Boolean => { + col.boolean(); + } + SimpleColumnType::Date => { + col.date(); + } + SimpleColumnType::Time => { + col.time(); + } + SimpleColumnType::Timestamp => { + col.timestamp(); + } + SimpleColumnType::Timestamptz => { + col.timestamp_with_time_zone(); + } + SimpleColumnType::Interval => { + col.interval(None, None); + } + SimpleColumnType::Bytea => { + col.binary(); + } + SimpleColumnType::Uuid => { + col.uuid(); + } + SimpleColumnType::Json => { + col.json(); + } + SimpleColumnType::Jsonb => { + col.json_binary(); + } + SimpleColumnType::Inet => { + col.custom(Alias::new("INET")); + } + SimpleColumnType::Cidr => { + col.custom(Alias::new("CIDR")); + } + SimpleColumnType::Macaddr => { + col.custom(Alias::new("MACADDR")); + } + SimpleColumnType::Xml => { + col.custom(Alias::new("XML")); + } + }, + ColumnType::Complex(complex) => match complex { + ComplexColumnType::Varchar { length } => { + col.string_len(*length); + } + ComplexColumnType::Numeric { precision, scale } => { + col.decimal_len(*precision, *scale); + } + ComplexColumnType::Char { length } => { + col.char_len(*length); + } + ComplexColumnType::Custom { custom_type } => { + col.custom(Alias::new(custom_type)); + } + }, + } +} + +/// Convert vespertide ReferenceAction to sea_query ForeignKeyAction +fn to_sea_fk_action(action: &ReferenceAction) -> ForeignKeyAction { + match action { + ReferenceAction::Cascade => ForeignKeyAction::Cascade, + ReferenceAction::Restrict => ForeignKeyAction::Restrict, + ReferenceAction::SetNull => ForeignKeyAction::SetNull, + ReferenceAction::SetDefault => ForeignKeyAction::SetDefault, + ReferenceAction::NoAction => ForeignKeyAction::NoAction, + } +} + +/// Convert vespertide ReferenceAction to SQL string +fn reference_action_sql(action: &ReferenceAction) -> &'static str { + match action { + ReferenceAction::Cascade => "CASCADE", + ReferenceAction::Restrict => "RESTRICT", + ReferenceAction::SetNull => "SET NULL", + ReferenceAction::SetDefault => "SET DEFAULT", + ReferenceAction::NoAction => "NO ACTION", + } } -pub(crate) fn bind(binds: &mut Vec, value: impl Into) -> String { - binds.push(value.into()); - format!("${}", binds.len()) +/// Build sea_query ColumnDef from vespertide ColumnDef +fn build_sea_column_def(column: &ColumnDef) -> SeaColumnDef { + let mut col = SeaColumnDef::new(Alias::new(&column.name)); + apply_column_type(&mut col, &column.r#type); + + if !column.nullable { + col.not_null(); + } + + if let Some(default) = &column.default { + col.default(sea_query::Expr::cust(default)); + } + + col } pub fn build_action_queries(action: &MigrationAction) -> Result, QueryError> { @@ -21,297 +213,263 @@ pub fn build_action_queries(action: &MigrationAction) -> Result, table, columns, constraints, - } => Ok(vec![create_table_sql(table, columns, constraints)?]), + } => Ok(vec![build_create_table(table, columns, constraints)?]), + MigrationAction::DeleteTable { table } => { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - Ok(vec![BuiltQuery { - sql: format!("DROP TABLE {t};"), - binds, - }]) + let stmt = Table::drop().table(Alias::new(table)).to_owned(); + Ok(vec![BuiltQuery::DropTable(Box::new(stmt))]) } + MigrationAction::AddColumn { table, column, fill_with, - } => { - // If adding NOT NULL without default, optionally backfill then enforce NOT NULL. - let mut stmts: Vec = Vec::new(); - let mut binds_add = Vec::new(); - let t = bind(&mut binds_add, table); - let add_col_sql = if column.nullable || column.default.is_some() || fill_with.is_none() - { - format!( - "ALTER TABLE {t} ADD COLUMN {};", - column_def_sql(column, &mut binds_add) - ) - } else { - // Add as nullable to allow backfill. - let mut c = column.clone(); - c.nullable = true; - format!( - "ALTER TABLE {t} ADD COLUMN {};", - column_def_sql(&c, &mut binds_add) - ) - }; - stmts.push(BuiltQuery { - sql: add_col_sql, - binds: binds_add, - }); - - if let Some(fill) = fill_with { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let col = bind(&mut binds, &column.name); - let val = bind(&mut binds, fill); - stmts.push(BuiltQuery { - sql: format!("UPDATE {t} SET {col} = {val};"), - binds, - }); - } + } => build_add_column(table, column, fill_with.as_deref()), - if !column.nullable && column.default.is_none() && fill_with.is_some() { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let col = bind(&mut binds, &column.name); - stmts.push(BuiltQuery { - sql: format!("ALTER TABLE {t} ALTER COLUMN {col} SET NOT NULL;"), - binds, - }); - } + MigrationAction::RenameColumn { table, from, to } => { + let stmt = Table::alter() + .table(Alias::new(table)) + .rename_column(Alias::new(from), Alias::new(to)) + .to_owned(); + Ok(vec![BuiltQuery::AlterTable(Box::new(stmt))]) + } - Ok(stmts) + MigrationAction::DeleteColumn { table, column } => { + let stmt = Table::alter() + .table(Alias::new(table)) + .drop_column(Alias::new(column)) + .to_owned(); + Ok(vec![BuiltQuery::AlterTable(Box::new(stmt))]) } - MigrationAction::RenameColumn { table, from, to } => Ok(vec![BuiltQuery { - sql: { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let f = bind(&mut binds, from); - let tt = bind(&mut binds, to); - format!("ALTER TABLE {t} RENAME COLUMN {f} TO {tt};") - }, - binds: { - let mut b = Vec::new(); - bind(&mut b, table); - bind(&mut b, from); - bind(&mut b, to); - b - }, - }]), - MigrationAction::DeleteColumn { table, column } => Ok(vec![BuiltQuery { - sql: { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let c = bind(&mut binds, column); - format!("ALTER TABLE {t} DROP COLUMN {c};") - }, - binds: { - let mut b = Vec::new(); - bind(&mut b, table); - bind(&mut b, column); - b - }, - }]), + MigrationAction::ModifyColumnType { table, column, new_type, - } => Ok(vec![BuiltQuery { - sql: { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let c = bind(&mut binds, column); - format!( - "ALTER TABLE {t} ALTER COLUMN {c} TYPE {};", - new_type.to_sql() - ) - }, - binds: { - let mut b = Vec::new(); - bind(&mut b, table); - bind(&mut b, column); - b - }, - }]), - MigrationAction::AddIndex { table, index } => Ok(vec![BuiltQuery { - sql: { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let idx = bind(&mut binds, &index.name); - let cols = index - .columns - .iter() - .map(|c| bind(&mut binds, c)) - .collect::>() - .join(", "); - let unique = if index.unique { "UNIQUE " } else { "" }; - format!("CREATE {unique}INDEX {idx} ON {t} ({cols});") - }, - binds: { - let mut b = Vec::new(); - bind(&mut b, table); - bind(&mut b, &index.name); - for c in &index.columns { - bind(&mut b, c); - } - b - }, - }]), - MigrationAction::RemoveIndex { name, .. } => Ok(vec![BuiltQuery { - sql: { - let mut binds = Vec::new(); - let n = bind(&mut binds, name); - format!("DROP INDEX {n};") - }, - binds: { - let mut b = Vec::new(); - bind(&mut b, name); - b - }, - }]), - MigrationAction::RenameTable { from, to } => Ok(vec![BuiltQuery { - sql: { - let mut binds = Vec::new(); - let f = bind(&mut binds, from); - let t = bind(&mut binds, to); - format!("ALTER TABLE {f} RENAME TO {t};") - }, - binds: { - let mut b = Vec::new(); - bind(&mut b, from); - bind(&mut b, to); - b - }, - }]), - MigrationAction::RawSql { sql } => Ok(vec![BuiltQuery { - sql: sql.to_string(), - binds: Vec::new(), - }]), + } => { + let mut col = SeaColumnDef::new(Alias::new(column)); + apply_column_type(&mut col, new_type); + + let stmt = Table::alter() + .table(Alias::new(table)) + .modify_column(col) + .to_owned(); + Ok(vec![BuiltQuery::AlterTable(Box::new(stmt))]) + } + + MigrationAction::AddIndex { table, index } => { + let mut stmt = Index::create() + .name(&index.name) + .table(Alias::new(table)) + .to_owned(); + + for col in &index.columns { + stmt = stmt.col(Alias::new(col)).to_owned(); + } + + if index.unique { + stmt = stmt.unique().to_owned(); + } + + Ok(vec![BuiltQuery::CreateIndex(Box::new(stmt))]) + } + + MigrationAction::RemoveIndex { name, .. } => { + let stmt = Index::drop().name(name).to_owned(); + Ok(vec![BuiltQuery::DropIndex(Box::new(stmt))]) + } + + MigrationAction::RenameTable { from, to } => { + let stmt = Table::rename() + .table(Alias::new(from), Alias::new(to)) + .to_owned(); + Ok(vec![BuiltQuery::RenameTable(Box::new(stmt))]) + } + + MigrationAction::RawSql { sql } => Ok(vec![BuiltQuery::Raw(sql.clone())]), + MigrationAction::AddConstraint { table, constraint } => { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let constraint_sql = table_constraint_sql(constraint, &mut binds)?; - Ok(vec![BuiltQuery { - sql: format!("ALTER TABLE {t} ADD {constraint_sql};"), - binds, - }]) + build_add_constraint(table, constraint) } + MigrationAction::RemoveConstraint { table, constraint } => { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - // For removing constraints, we need to handle each type differently - let drop_sql = match constraint { - TableConstraint::PrimaryKey { .. } => { - // Syntax for dropping primary key - format!("ALTER TABLE {t} DROP CONSTRAINT {t}_pkey;") - } - TableConstraint::Unique { name, columns } => { - if let Some(n) = name { - let nm = bind(&mut binds, n); - format!("ALTER TABLE {t} DROP CONSTRAINT {nm};") - } else { - // Generate default constraint name for unnamed unique - let cols = columns.join("_"); - let constraint_name = bind(&mut binds, format!("{}_{}_key", table, cols)); - format!("ALTER TABLE {t} DROP CONSTRAINT {constraint_name};") - } - } - TableConstraint::ForeignKey { name, columns, .. } => { - if let Some(n) = name { - let nm = bind(&mut binds, n); - format!("ALTER TABLE {t} DROP CONSTRAINT {nm};") - } else { - // Generate default constraint name for unnamed foreign key - let cols = columns.join("_"); - let constraint_name = bind(&mut binds, format!("{}_{}_fkey", table, cols)); - format!("ALTER TABLE {t} DROP CONSTRAINT {constraint_name};") - } - } - TableConstraint::Check { name, .. } => { - if let Some(n) = name { - let nm = bind(&mut binds, n); - format!("ALTER TABLE {t} DROP CONSTRAINT {nm};") - } else { - // Check constraints without names are problematic to drop - // Return an error or use a placeholder - return Err(QueryError::Other( - "Cannot drop unnamed CHECK constraint".to_string(), - )); - } - } - }; - Ok(vec![BuiltQuery { - sql: drop_sql, - binds, - }]) + build_remove_constraint(table, constraint) } } } -fn create_table_sql( +fn build_create_table( table: &str, columns: &[ColumnDef], constraints: &[TableConstraint], ) -> Result { - let mut binds = Vec::new(); - let t = bind(&mut binds, table); - let mut parts: Vec = columns + let mut stmt = Table::create().table(Alias::new(table)).to_owned(); + + let has_table_primary_key = constraints .iter() - .map(|c| column_def_sql(c, &mut binds)) - .collect(); + .any(|c| matches!(c, TableConstraint::PrimaryKey { .. })); + + // Add columns + for column in columns { + let mut col = build_sea_column_def(column); + + // Check for inline primary key + if column.primary_key.is_some() && !has_table_primary_key { + col.primary_key(); + } + + // Check for inline unique constraint + if column.unique.is_some() { + col.unique_key(); + } + + stmt = stmt.col(col).to_owned(); + } + + // Add table-level constraints for constraint in constraints { - parts.push(table_constraint_sql(constraint, &mut binds)?); + match constraint { + TableConstraint::PrimaryKey { + columns: pk_cols, + auto_increment: _, + } => { + // Build primary key index + let mut pk_idx = Index::create(); + for c in pk_cols { + pk_idx = pk_idx.col(Alias::new(c)).to_owned(); + } + stmt = stmt.primary_key(&mut pk_idx).to_owned(); + } + TableConstraint::Unique { + name, + columns: unique_cols, + } => { + let mut idx = Index::create(); + if let Some(n) = name { + idx = idx.name(n).to_owned(); + } + for col in unique_cols { + idx = idx.col(Alias::new(col)).to_owned(); + } + // Note: sea-query doesn't have a direct way to add named unique constraints + // We'll handle this as a separate index if needed + } + TableConstraint::ForeignKey { + name, + columns: fk_cols, + ref_table, + ref_columns, + on_delete, + on_update, + } => { + let mut fk = ForeignKey::create(); + if let Some(n) = name { + fk = fk.name(n).to_owned(); + } + fk = fk.from_tbl(Alias::new(table)).to_owned(); + for col in fk_cols { + fk = fk.from_col(Alias::new(col)).to_owned(); + } + fk = fk.to_tbl(Alias::new(ref_table)).to_owned(); + for col in ref_columns { + fk = fk.to_col(Alias::new(col)).to_owned(); + } + if let Some(action) = on_delete { + fk = fk.on_delete(to_sea_fk_action(action)).to_owned(); + } + if let Some(action) = on_update { + fk = fk.on_update(to_sea_fk_action(action)).to_owned(); + } + stmt = stmt.foreign_key(&mut fk).to_owned(); + } + TableConstraint::Check { name, expr } => { + // sea-query doesn't have direct CHECK constraint support in TableCreateStatement + // This would need to be handled as raw SQL or post-creation ALTER + let _ = (name, expr); + } + } } - let mut sql = String::new(); - write!(&mut sql, "CREATE TABLE {t} ({});", parts.join(", ")).unwrap(); - Ok(BuiltQuery { sql, binds }) + + Ok(BuiltQuery::CreateTable(Box::new(stmt))) } -fn column_def_sql(column: &ColumnDef, binds: &mut Vec) -> String { - let name = bind(binds, &column.name); - let mut parts = vec![format!("{name} {}", column.r#type.to_sql())]; - if !column.nullable { - parts.push("NOT NULL".into()); - } - if let Some(default) = &column.default { - let p = bind(binds, default); - parts.push(format!("DEFAULT {p}")); +fn build_add_column( + table: &str, + column: &ColumnDef, + fill_with: Option<&str>, +) -> Result, QueryError> { + let mut stmts: Vec = Vec::new(); + + // If adding NOT NULL without default, we need special handling + let needs_backfill = !column.nullable && column.default.is_none() && fill_with.is_some(); + + if needs_backfill { + // Add as nullable first + let mut temp_col = column.clone(); + temp_col.nullable = true; + let col_def = build_sea_column_def(&temp_col); + + let stmt = Table::alter() + .table(Alias::new(table)) + .add_column(col_def) + .to_owned(); + stmts.push(BuiltQuery::AlterTable(Box::new(stmt))); + + // Backfill with provided value + if let Some(fill) = fill_with { + let sql = format!("UPDATE \"{}\" SET \"{}\" = {}", table, column.name, fill); + stmts.push(BuiltQuery::Raw(sql)); + } + + // Set NOT NULL + let sql = format!( + "ALTER TABLE \"{}\" ALTER COLUMN \"{}\" SET NOT NULL", + table, column.name + ); + stmts.push(BuiltQuery::Raw(sql)); + } else { + let col_def = build_sea_column_def(column); + let stmt = Table::alter() + .table(Alias::new(table)) + .add_column(col_def) + .to_owned(); + stmts.push(BuiltQuery::AlterTable(Box::new(stmt))); } - parts.join(" ") + + Ok(stmts) } -fn table_constraint_sql( +fn build_add_constraint( + table: &str, constraint: &TableConstraint, - binds: &mut Vec, -) -> Result { - Ok(match constraint { +) -> Result, QueryError> { + match constraint { TableConstraint::PrimaryKey { columns, .. } => { - let placeholders = columns + // Use raw SQL for adding primary key constraint + let cols = columns .iter() - .map(|c| bind(binds, c)) + .map(|c| format!("\"{}\"", c)) .collect::>() .join(", "); - format!("PRIMARY KEY ({placeholders})") + let sql = format!("ALTER TABLE \"{}\" ADD PRIMARY KEY ({})", table, cols); + Ok(vec![BuiltQuery::Raw(sql)]) + } + TableConstraint::Unique { name, columns } => { + let cols = columns + .iter() + .map(|c| format!("\"{}\"", c)) + .collect::>() + .join(", "); + let sql = if let Some(n) = name { + format!( + "ALTER TABLE \"{}\" ADD CONSTRAINT \"{}\" UNIQUE ({})", + table, n, cols + ) + } else { + format!("ALTER TABLE \"{}\" ADD UNIQUE ({})", table, cols) + }; + Ok(vec![BuiltQuery::Raw(sql)]) } - TableConstraint::Unique { name, columns } => match name { - Some(n) => { - let nm = bind(binds, n); - let placeholders = columns - .iter() - .map(|c| bind(binds, c)) - .collect::>() - .join(", "); - format!("CONSTRAINT {nm} UNIQUE ({placeholders})") - } - None => { - let placeholders = columns - .iter() - .map(|c| bind(binds, c)) - .collect::>() - .join(", "); - format!("UNIQUE ({placeholders})") - } - }, TableConstraint::ForeignKey { name, columns, @@ -320,79 +478,104 @@ fn table_constraint_sql( on_delete, on_update, } => { - let mut sql = String::new(); - if let Some(n) = name { - let nm = bind(binds, n); - write!(&mut sql, "CONSTRAINT {nm} ").unwrap(); - } + // Use Raw SQL for FK creation to avoid SQLite panic from sea-query + // SQLite doesn't support ALTER TABLE ADD CONSTRAINT for FK, but we generate + // the SQL anyway - runtime will need to handle SQLite FK differently (table recreation) let cols = columns .iter() - .map(|c| bind(binds, c)) + .map(|c| format!("\"{}\"", c)) .collect::>() .join(", "); let ref_cols = ref_columns .iter() - .map(|c| bind(binds, c)) + .map(|c| format!("\"{}\"", c)) .collect::>() .join(", "); - let ref_tbl = bind(binds, ref_table); - write!( - &mut sql, - "FOREIGN KEY ({cols}) REFERENCES {ref_tbl} ({ref_cols})" - ) - .unwrap(); - if let Some(action) = on_delete { - write!( - &mut sql, - " ON DELETE {}", - reference_action_sql(action, binds) + + let mut sql = if let Some(n) = name { + format!( + "ALTER TABLE \"{}\" ADD CONSTRAINT \"{}\" FOREIGN KEY ({}) REFERENCES \"{}\" ({})", + table, n, cols, ref_table, ref_cols + ) + } else { + format!( + "ALTER TABLE \"{}\" ADD FOREIGN KEY ({}) REFERENCES \"{}\" ({})", + table, cols, ref_table, ref_cols ) - .unwrap(); + }; + + if let Some(action) = on_delete { + sql.push_str(&format!(" ON DELETE {}", reference_action_sql(action))); } if let Some(action) = on_update { - write!( - &mut sql, - " ON UPDATE {}", - reference_action_sql(action, binds) - ) - .unwrap(); + sql.push_str(&format!(" ON UPDATE {}", reference_action_sql(action))); } - sql + + Ok(vec![BuiltQuery::Raw(sql)]) } - TableConstraint::Check { name, expr } => match name { - Some(n) => { - let nm = bind(binds, n); - let e = bind(binds, expr); - format!("CONSTRAINT {nm} CHECK ({e})") - } - None => { - let e = bind(binds, expr); - format!("CHECK ({e})") - } - }, - }) + TableConstraint::Check { name, expr } => { + let sql = format!( + "ALTER TABLE \"{}\" ADD CONSTRAINT \"{}\" CHECK ({})", + table, name, expr + ); + Ok(vec![BuiltQuery::Raw(sql)]) + } + } } -fn reference_action_sql( - action: &vespertide_core::ReferenceAction, - _binds: &mut Vec, -) -> &'static str { - match action { - vespertide_core::ReferenceAction::Cascade => "CASCADE", - vespertide_core::ReferenceAction::Restrict => "RESTRICT", - vespertide_core::ReferenceAction::SetNull => "SET NULL", - vespertide_core::ReferenceAction::SetDefault => "SET DEFAULT", - vespertide_core::ReferenceAction::NoAction => "NO ACTION", +fn build_remove_constraint( + table: &str, + constraint: &TableConstraint, +) -> Result, QueryError> { + match constraint { + TableConstraint::PrimaryKey { .. } => { + let sql = format!( + "ALTER TABLE \"{}\" DROP CONSTRAINT \"{}_pkey\"", + table, table + ); + Ok(vec![BuiltQuery::Raw(sql)]) + } + TableConstraint::Unique { name, columns } => { + let constraint_name = if let Some(n) = name { + n.clone() + } else { + format!("{}_{}_key", table, columns.join("_")) + }; + let sql = format!( + "ALTER TABLE \"{}\" DROP CONSTRAINT \"{}\"", + table, constraint_name + ); + Ok(vec![BuiltQuery::Raw(sql)]) + } + TableConstraint::ForeignKey { name, columns, .. } => { + // Use Raw SQL to avoid SQLite panic from sea-query + // SQLite doesn't support ALTER TABLE DROP CONSTRAINT for FK + let constraint_name = if let Some(n) = name { + n.clone() + } else { + format!("{}_{}_fkey", table, columns.join("_")) + }; + let sql = format!( + "ALTER TABLE \"{}\" DROP CONSTRAINT \"{}\"", + table, constraint_name + ); + Ok(vec![BuiltQuery::Raw(sql)]) + } + TableConstraint::Check { name, .. } => { + let sql = format!("ALTER TABLE \"{}\" DROP CONSTRAINT \"{}\"", table, name); + Ok(vec![BuiltQuery::Raw(sql)]) + } } } #[cfg(test)] mod tests { use super::*; + use insta::{assert_snapshot, with_settings}; use rstest::rstest; + use vespertide_core::schema::primary_key::PrimaryKeySyntax; use vespertide_core::{ - ColumnDef, ColumnType, ComplexColumnType, IndexDef, MigrationAction, ReferenceAction, - SimpleColumnType, TableConstraint, + ColumnDef, ColumnType, ComplexColumnType, IndexDef, SimpleColumnType, StrOrBoolOrArray, }; fn col(name: &str, ty: ColumnType) -> ColumnDef { @@ -410,272 +593,1159 @@ mod tests { } #[rstest] - #[case( - vec!["test"], - vec!["$1"], - vec!["test".to_string()] - )] - #[case( - vec!["test", "test2"], - vec!["$1", "$2"], - vec!["test".to_string(), "test2".to_string()] - )] - fn test_bind( - #[case] inputs: Vec<&str>, - #[case] expected_placeholders: Vec<&str>, - #[case] expected_binds: Vec, + #[case(ColumnType::Simple(SimpleColumnType::Integer))] + #[case(ColumnType::Simple(SimpleColumnType::BigInt))] + #[case(ColumnType::Simple(SimpleColumnType::Text))] + #[case(ColumnType::Simple(SimpleColumnType::Boolean))] + #[case(ColumnType::Simple(SimpleColumnType::Timestamp))] + #[case(ColumnType::Simple(SimpleColumnType::Uuid))] + #[case(ColumnType::Complex(ComplexColumnType::Varchar { length: 255 }))] + #[case(ColumnType::Complex(ComplexColumnType::Numeric { precision: 10, scale: 2 }))] + fn test_column_type_conversion(#[case] ty: ColumnType) { + // Just ensure no panic - test by creating a column with this type + let mut col = SeaColumnDef::new(Alias::new("test")); + apply_column_type(&mut col, &ty); + } + + #[rstest] + #[case(SimpleColumnType::SmallInt)] + #[case(SimpleColumnType::Integer)] + #[case(SimpleColumnType::BigInt)] + #[case(SimpleColumnType::Real)] + #[case(SimpleColumnType::DoublePrecision)] + #[case(SimpleColumnType::Text)] + #[case(SimpleColumnType::Boolean)] + #[case(SimpleColumnType::Date)] + #[case(SimpleColumnType::Time)] + #[case(SimpleColumnType::Timestamp)] + #[case(SimpleColumnType::Timestamptz)] + #[case(SimpleColumnType::Interval)] + #[case(SimpleColumnType::Bytea)] + #[case(SimpleColumnType::Uuid)] + #[case(SimpleColumnType::Json)] + #[case(SimpleColumnType::Jsonb)] + #[case(SimpleColumnType::Inet)] + #[case(SimpleColumnType::Cidr)] + #[case(SimpleColumnType::Macaddr)] + #[case(SimpleColumnType::Xml)] + fn test_all_simple_types_cover_branches(#[case] ty: SimpleColumnType) { + let mut col = SeaColumnDef::new(Alias::new("t")); + apply_column_type(&mut col, &ColumnType::Simple(ty)); + } + + #[rstest] + #[case(ComplexColumnType::Varchar { length: 42 })] + #[case(ComplexColumnType::Numeric { precision: 8, scale: 3 })] + #[case(ComplexColumnType::Char { length: 3 })] + #[case(ComplexColumnType::Custom { custom_type: "GEOGRAPHY".into() })] + fn test_all_complex_types_cover_branches(#[case] ty: ComplexColumnType) { + let mut col = SeaColumnDef::new(Alias::new("t")); + apply_column_type(&mut col, &ColumnType::Complex(ty)); + } + + #[rstest] + #[case::cascade(ReferenceAction::Cascade, ForeignKeyAction::Cascade)] + #[case::restrict(ReferenceAction::Restrict, ForeignKeyAction::Restrict)] + #[case::set_null(ReferenceAction::SetNull, ForeignKeyAction::SetNull)] + #[case::set_default(ReferenceAction::SetDefault, ForeignKeyAction::SetDefault)] + #[case::no_action(ReferenceAction::NoAction, ForeignKeyAction::NoAction)] + fn test_reference_action_conversion( + #[case] action: ReferenceAction, + #[case] expected: ForeignKeyAction, ) { - let mut binds = Vec::new(); - for (i, input) in inputs.iter().enumerate() { - let placeholder = bind(&mut binds, *input); - assert_eq!(placeholder, expected_placeholders[i]); - } - assert_eq!(binds, expected_binds); + // Just ensure the function doesn't panic and returns valid ForeignKeyAction + let result = to_sea_fk_action(&action); + assert!( + matches!(result, _expected), + "Expected {:?}, got {:?}", + expected, + result + ); + } + + #[rstest] + #[case(ReferenceAction::Cascade, "CASCADE")] + #[case(ReferenceAction::Restrict, "RESTRICT")] + #[case(ReferenceAction::SetNull, "SET NULL")] + #[case(ReferenceAction::SetDefault, "SET DEFAULT")] + #[case(ReferenceAction::NoAction, "NO ACTION")] + fn test_reference_action_sql_all_variants( + #[case] action: ReferenceAction, + #[case] expected: &str, + ) { + assert_eq!(reference_action_sql(&action), expected); + } + + #[test] + #[should_panic] + fn test_sqlite_create_foreign_key_build_panics() { + let fk = ForeignKey::create() + .name("fk") + .from_tbl(Alias::new("a")) + .from_col(Alias::new("c")) + .to_tbl(Alias::new("b")) + .to_col(Alias::new("id")) + .to_owned(); + let q = BuiltQuery::CreateForeignKey(Box::new(fk)); + // sea-query panics when building FK SQL for SQLite; exercise that branch + let _ = q.build(DatabaseBackend::Sqlite); + } + + #[test] + #[should_panic] + fn test_sqlite_drop_foreign_key_build_panics() { + let fk = ForeignKey::drop() + .name("fk") + .table(Alias::new("a")) + .to_owned(); + let q = BuiltQuery::DropForeignKey(Box::new(fk)); + // sea-query panics when building FK SQL for SQLite; exercise that branch + let _ = q.build(DatabaseBackend::Sqlite); + } + + #[test] + fn test_backend_specific_quoting() { + let action = MigrationAction::CreateTable { + table: "users".into(), + columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + constraints: vec![], + }; + let result = build_action_queries(&action).unwrap(); + + // PostgreSQL uses double quotes + let pg_sql = result[0].build(DatabaseBackend::Postgres); + assert!(pg_sql.contains("\"users\"")); + + // MySQL uses backticks + let mysql_sql = result[0].build(DatabaseBackend::MySql); + assert!(mysql_sql.contains("`users`")); + + // SQLite uses double quotes + let sqlite_sql = result[0].build(DatabaseBackend::Sqlite); + assert!(sqlite_sql.contains("\"users\"")); } #[rstest] - #[case::create_table( + #[case::create_table_postgres( + "create_table_postgres", MigrationAction::CreateTable { table: "users".into(), columns: vec![ col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("name", ColumnType::Simple(SimpleColumnType::Text)), ], - constraints: vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }], + constraints: vec![], }, - vec![( - "CREATE TABLE $1 ($2 INTEGER, $3 TEXT, PRIMARY KEY ($4));".to_string(), - vec!["users".to_string(), "id".to_string(), "name".to_string(), "id".to_string()], - )] + DatabaseBackend::Postgres, + &["CREATE TABLE \"users\" ( \"id\" integer, \"name\" text )"] )] - #[case::delete_table( - MigrationAction::DeleteTable { + #[case::create_table_mysql( + "create_table_mysql", + MigrationAction::CreateTable { table: "users".into(), + columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + constraints: vec![], }, - vec![("DROP TABLE $1;".to_string(), vec!["users".to_string()])] + DatabaseBackend::MySql, + &["CREATE TABLE `users` ( `id` int )"] )] - #[case::add_column_nullable( - MigrationAction::AddColumn { + #[case::create_table_sqlite( + "create_table_sqlite", + MigrationAction::CreateTable { table: "users".into(), - column: col("email", ColumnType::Simple(SimpleColumnType::Text)), - fill_with: None, - }, - vec![( - "ALTER TABLE $1 ADD COLUMN $2 TEXT;".to_string(), - vec!["users".to_string(), "email".to_string()], - )] - )] - #[case::add_column_not_null_with_default( - { - let mut c = col("email", ColumnType::Simple(SimpleColumnType::Text)); - c.nullable = false; - c.default = Some("''".to_string()); - MigrationAction::AddColumn { - table: "users".into(), - column: c, - fill_with: None, - } - }, - vec![( - "ALTER TABLE $1 ADD COLUMN $2 TEXT NOT NULL DEFAULT $3;".to_string(), - vec!["users".to_string(), "email".to_string(), "''".to_string()], - )] - )] - #[case::add_column_not_null_with_fill( - { - let mut c = col("email", ColumnType::Simple(SimpleColumnType::Text)); - c.nullable = false; - MigrationAction::AddColumn { - table: "users".into(), - column: c, - fill_with: Some("test@example.com".to_string()), - } - }, - vec![ - ( - "ALTER TABLE $1 ADD COLUMN $2 TEXT;".to_string(), - vec!["users".to_string(), "email".to_string()], - ), - ( - "UPDATE $1 SET $2 = $3;".to_string(), - vec!["users".to_string(), "email".to_string(), "test@example.com".to_string()], - ), - ( - "ALTER TABLE $1 ALTER COLUMN $2 SET NOT NULL;".to_string(), - vec!["users".to_string(), "email".to_string()], - ), - ] - )] - #[case::add_column_not_null_without_default_without_fill( - { - let mut c = col("email", ColumnType::Simple(SimpleColumnType::Text)); - c.nullable = false; - MigrationAction::AddColumn { - table: "users".into(), - column: c, - fill_with: None, - } + columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + constraints: vec![], }, - vec![( - "ALTER TABLE $1 ADD COLUMN $2 TEXT NOT NULL;".to_string(), - vec!["users".to_string(), "email".to_string()], - )] + DatabaseBackend::Sqlite, + &["CREATE TABLE \"users\" ( \"id\" integer )"] )] - #[case::rename_column( - MigrationAction::RenameColumn { + #[case::create_table_with_default_postgres( + "create_table_with_default_postgres", + MigrationAction::CreateTable { table: "users".into(), - from: "old_name".into(), - to: "new_name".into(), + columns: vec![ColumnDef { + name: "status".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: Some("'active'".into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }], + constraints: vec![], }, - vec![( - "ALTER TABLE $1 RENAME COLUMN $2 TO $3;".to_string(), - vec!["users".to_string(), "old_name".to_string(), "new_name".to_string()], - )] + DatabaseBackend::Postgres, + &["DEFAULT", "'active'"] )] - #[case::delete_column( - MigrationAction::DeleteColumn { + #[case::create_table_with_default_mysql( + "create_table_with_default_mysql", + MigrationAction::CreateTable { table: "users".into(), - column: "email".into(), + columns: vec![ColumnDef { + name: "status".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: Some("'active'".into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }], + constraints: vec![], }, - vec![( - "ALTER TABLE $1 DROP COLUMN $2;".to_string(), - vec!["users".to_string(), "email".to_string()], - )] + DatabaseBackend::MySql, + &["DEFAULT 'active'"] )] - #[case::modify_column_type( - MigrationAction::ModifyColumnType { + #[case::create_table_with_default_sqlite( + "create_table_with_default_sqlite", + MigrationAction::CreateTable { table: "users".into(), - column: "age".into(), - new_type: ColumnType::Simple(SimpleColumnType::BigInt), + columns: vec![ColumnDef { + name: "status".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: Some("'active'".into()), + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }], + constraints: vec![], }, - vec![( - "ALTER TABLE $1 ALTER COLUMN $2 TYPE BIGINT;".to_string(), - vec!["users".to_string(), "age".to_string()], - )] + DatabaseBackend::Sqlite, + &["DEFAULT 'active'"] )] - #[case::add_index( - MigrationAction::AddIndex { + #[case::create_table_with_inline_primary_key_postgres( + "create_table_with_inline_primary_key_postgres", + MigrationAction::CreateTable { table: "users".into(), - index: IndexDef { - name: "idx_email".into(), - columns: vec!["email".into()], - unique: false, - }, + columns: vec![ColumnDef { + name: "id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: None, + foreign_key: None, + }], + constraints: vec![], }, - vec![( - "CREATE INDEX $2 ON $1 ($3);".to_string(), - vec!["users".to_string(), "idx_email".to_string(), "email".to_string()], - )] + DatabaseBackend::Postgres, + &["PRIMARY KEY"] )] - #[case::add_unique_index( - MigrationAction::AddIndex { + #[case::create_table_with_inline_primary_key_mysql( + "create_table_with_inline_primary_key_mysql", + MigrationAction::CreateTable { table: "users".into(), - index: IndexDef { - name: "idx_email".into(), - columns: vec!["email".into()], - unique: true, - }, + columns: vec![ColumnDef { + name: "id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: None, + foreign_key: None, + }], + constraints: vec![], }, - vec![( - "CREATE UNIQUE INDEX $2 ON $1 ($3);".to_string(), - vec!["users".to_string(), "idx_email".to_string(), "email".to_string()], - )] + DatabaseBackend::MySql, + &["PRIMARY KEY"] )] - #[case::add_index_multiple_columns( - MigrationAction::AddIndex { + #[case::create_table_with_inline_primary_key_sqlite( + "create_table_with_inline_primary_key_sqlite", + MigrationAction::CreateTable { table: "users".into(), - index: IndexDef { - name: "idx_name_email".into(), - columns: vec!["name".into(), "email".into()], - unique: false, - }, + columns: vec![ColumnDef { + name: "id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: None, + foreign_key: None, + }], + constraints: vec![], }, - vec![( - "CREATE INDEX $2 ON $1 ($3, $4);".to_string(), - vec![ - "users".to_string(), - "idx_name_email".to_string(), - "name".to_string(), - "email".to_string(), + DatabaseBackend::Sqlite, + &["PRIMARY KEY"] + )] + #[case::create_table_with_inline_constraints_postgres( + "create_table_with_inline_constraints_postgres", + MigrationAction::CreateTable { + table: "users".into(), + columns: vec![ + ColumnDef { + name: "id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: None, + foreign_key: None, + }, + ColumnDef { + name: "email".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: None, + comment: None, + primary_key: None, + unique: Some(StrOrBoolOrArray::Bool(true)), + index: None, + foreign_key: None, + }, + ], + constraints: vec![ + TableConstraint::PrimaryKey { + auto_increment: false, + columns: vec!["id".into()], + }, + TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + TableConstraint::Check { + name: "chk_always_true".into(), + expr: "1 = 1".into(), + }, ], - )] + }, + DatabaseBackend::Postgres, + &["PRIMARY KEY", "UNIQUE"] )] - #[case::remove_index( - MigrationAction::RemoveIndex { + #[case::create_table_with_inline_constraints_mysql( + "create_table_with_inline_constraints_mysql", + MigrationAction::CreateTable { table: "users".into(), - name: "idx_email".into(), + columns: vec![ + ColumnDef { + name: "id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: None, + foreign_key: None, + }, + ColumnDef { + name: "email".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: None, + comment: None, + primary_key: None, + unique: Some(StrOrBoolOrArray::Bool(true)), + index: None, + foreign_key: None, + }, + ], + constraints: vec![ + TableConstraint::PrimaryKey { + auto_increment: false, + columns: vec!["id".into()], + }, + TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + TableConstraint::Check { + name: "chk_always_true".into(), + expr: "1 = 1".into(), + }, + ], + }, + DatabaseBackend::Postgres, + &["PRIMARY KEY", "UNIQUE"] + )] + #[case::create_table_with_inline_constraints_sqlite( + "create_table_with_inline_constraints_sqlite", + MigrationAction::CreateTable { + table: "users".into(), + columns: vec![ + ColumnDef { + name: "id".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: Some(PrimaryKeySyntax::Bool(true)), + unique: None, + index: None, + foreign_key: None, + }, + ColumnDef { + name: "email".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: None, + comment: None, + primary_key: None, + unique: Some(StrOrBoolOrArray::Bool(true)), + index: None, + foreign_key: None, + }, + ], + constraints: vec![ + TableConstraint::PrimaryKey { + auto_increment: false, + columns: vec!["id".into()], + }, + TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + TableConstraint::Check { + name: "chk_always_true".into(), + expr: "1 = 1".into(), + }, + ], + }, + DatabaseBackend::Postgres, + &["PRIMARY KEY", "UNIQUE"] + )] + #[case::create_table_with_fk_postgres( + "create_table_with_fk_postgres", + MigrationAction::CreateTable { + table: "posts".into(), + columns: vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + constraints: vec![TableConstraint::ForeignKey { + name: Some("fk_user".into()), + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: Some(ReferenceAction::Restrict), + }], + }, + DatabaseBackend::Postgres, + &["REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"] + )] + #[case::create_table_with_fk_mysql( + "create_table_with_fk_mysql", + MigrationAction::CreateTable { + table: "posts".into(), + columns: vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + constraints: vec![TableConstraint::ForeignKey { + name: Some("fk_user".into()), + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: Some(ReferenceAction::Restrict), + }], + }, + DatabaseBackend::MySql, + &["REFERENCES `users` (`id`)", "ON DELETE CASCADE", "ON UPDATE RESTRICT"] + )] + #[case::create_table_with_fk_sqlite( + "create_table_with_fk_sqlite", + MigrationAction::CreateTable { + table: "posts".into(), + columns: vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + constraints: vec![TableConstraint::ForeignKey { + name: Some("fk_user".into()), + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: Some(ReferenceAction::Restrict), + }], + }, + DatabaseBackend::Sqlite, + &["REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"] + )] + #[case::delete_table_postgres( + "delete_table_postgres", + MigrationAction::DeleteTable { table: "users".into() }, + DatabaseBackend::Postgres, + &["DROP TABLE \"users\""] + )] + #[case::delete_table_mysql( + "delete_table_mysql", + MigrationAction::DeleteTable { table: "users".into() }, + DatabaseBackend::MySql, + &["DROP TABLE `users`"] + )] + #[case::delete_table_sqlite( + "delete_table_sqlite", + MigrationAction::DeleteTable { table: "users".into() }, + DatabaseBackend::Sqlite, + &["DROP TABLE \"users\""] + )] + #[case::rename_column_postgres( + "rename_column_postgres", + MigrationAction::RenameColumn { + table: "users".into(), + from: "email".into(), + to: "contact_email".into(), + }, + DatabaseBackend::Postgres, + &["ALTER TABLE \"users\" RENAME COLUMN \"email\" TO \"contact_email\""] + )] + #[case::rename_column_mysql( + "rename_column_mysql", + MigrationAction::RenameColumn { + table: "users".into(), + from: "email".into(), + to: "contact_email".into(), + }, + DatabaseBackend::MySql, + &["ALTER TABLE `users` RENAME COLUMN `email` TO `contact_email`"] + )] + #[case::rename_column_sqlite( + "rename_column_sqlite", + MigrationAction::RenameColumn { + table: "users".into(), + from: "email".into(), + to: "contact_email".into(), + }, + DatabaseBackend::Sqlite, + &["ALTER TABLE \"users\" RENAME COLUMN \"email\" TO \"contact_email\""] + )] + #[case::add_column_with_backfill_postgres( + "add_column_with_backfill_postgres", + MigrationAction::AddColumn { + table: "users".into(), + column: ColumnDef { + name: "age".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + fill_with: Some("0".into()), + }, + DatabaseBackend::Postgres, + &["ALTER TABLE \"users\" ADD COLUMN \"age\""] + )] + #[case::add_column_with_backfill_mysql( + "add_column_with_backfill_mysql", + MigrationAction::AddColumn { + table: "users".into(), + column: ColumnDef { + name: "age".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + fill_with: Some("0".into()), + }, + DatabaseBackend::MySql, + &["ALTER TABLE `users` ADD COLUMN `age` int"] + )] + #[case::add_column_with_backfill_sqlite( + "add_column_with_backfill_sqlite", + MigrationAction::AddColumn { + table: "users".into(), + column: ColumnDef { + name: "age".into(), + r#type: ColumnType::Simple(SimpleColumnType::Integer), + nullable: false, + default: None, + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + fill_with: Some("0".into()), + }, + DatabaseBackend::Sqlite, + &["ALTER TABLE \"users\" ADD COLUMN \"age\""] + )] + #[case::add_column_simple_postgres( + "add_column_simple_postgres", + MigrationAction::AddColumn { + table: "users".into(), + column: ColumnDef { + name: "nickname".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: None, + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + fill_with: None, + }, + DatabaseBackend::Postgres, + &["ALTER TABLE \"users\" ADD COLUMN \"nickname\""] + )] + #[case::add_column_simple_mysql( + "add_column_simple_mysql", + MigrationAction::AddColumn { + table: "users".into(), + column: ColumnDef { + name: "nickname".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: None, + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + fill_with: None, + }, + DatabaseBackend::MySql, + &["ALTER TABLE `users` ADD COLUMN `nickname` text"] + )] + #[case::add_column_simple_sqlite( + "add_column_simple_sqlite", + MigrationAction::AddColumn { + table: "users".into(), + column: ColumnDef { + name: "nickname".into(), + r#type: ColumnType::Simple(SimpleColumnType::Text), + nullable: true, + default: None, + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + }, + fill_with: None, + }, + DatabaseBackend::Sqlite, + &["ALTER TABLE \"users\" ADD COLUMN \"nickname\""] + )] + #[case::modify_column_type_postgres( + "modify_column_type_postgres", + MigrationAction::ModifyColumnType { + table: "users".into(), + column: "age".into(), + new_type: ColumnType::Complex(ComplexColumnType::Varchar { length: 50 }), + }, + DatabaseBackend::Postgres, + &["ALTER TABLE \"users\"", "\"age\""] + )] + #[case::modify_column_type_mysql( + "modify_column_type_mysql", + MigrationAction::ModifyColumnType { + table: "users".into(), + column: "age".into(), + new_type: ColumnType::Complex(ComplexColumnType::Varchar { length: 50 }), }, - vec![( - "DROP INDEX $1;".to_string(), - vec!["idx_email".to_string()], - )] + DatabaseBackend::MySql, + &["ALTER TABLE `users` MODIFY COLUMN `age` varchar(50)"] )] - #[case::rename_table( + // NOTE: SQLite does not support MODIFY COLUMN; see sea-query sqlite backend. + // #[case::modify_column_type_sqlite( + // "modify_column_type_sqlite", + // MigrationAction::ModifyColumnType { + // table: "users".into(), + // column: "age".into(), + // new_type: ColumnType::Complex(ComplexColumnType::Varchar { length: 50 }), + // }, + // DatabaseBackend::Sqlite, + // &["ALTER TABLE \"users\" MODIFY COLUMN \"age\" VARCHAR(50)"] + // )] + #[case::rename_table_action_postgres( + "rename_table_action_postgres", MigrationAction::RenameTable { - from: "old_users".into(), - to: "new_users".into(), + from: "users".into(), + to: "accounts".into(), + }, + DatabaseBackend::Postgres, + &["ALTER TABLE \"users\" RENAME TO \"accounts\""] + )] + #[case::rename_table_action_mysql( + "rename_table_action_mysql", + MigrationAction::RenameTable { + from: "users".into(), + to: "accounts".into(), + }, + DatabaseBackend::MySql, + &["RENAME TABLE `users` TO `accounts`"] + )] + #[case::rename_table_action_sqlite( + "rename_table_action_sqlite", + MigrationAction::RenameTable { + from: "users".into(), + to: "accounts".into(), + }, + DatabaseBackend::Sqlite, + &["ALTER TABLE \"users\" RENAME TO \"accounts\""] + )] + #[case::raw_sql_action_postgres( + "raw_sql_action_postgres", + MigrationAction::RawSql { + sql: "SELECT 1".into(), + }, + DatabaseBackend::Postgres, + &["SELECT 1"] + )] + #[case::raw_sql_action_mysql( + "raw_sql_action_mysql", + MigrationAction::RawSql { + sql: "SELECT 1".into(), }, - vec![( - "ALTER TABLE $1 RENAME TO $2;".to_string(), - vec!["old_users".to_string(), "new_users".to_string()], - )] + DatabaseBackend::MySql, + &["SELECT 1"] )] - #[case::raw_sql( + #[case::raw_sql_action_sqlite( + "raw_sql_action_sqlite", MigrationAction::RawSql { - sql: "SELECT 1;".to_string(), + sql: "SELECT 1".into(), + }, + DatabaseBackend::Sqlite, + &["SELECT 1"] + )] + #[case::delete_column_postgres( + "delete_column_postgres", + MigrationAction::DeleteColumn { + table: "users".into(), + column: "email".into(), + }, + DatabaseBackend::Postgres, + &["ALTER TABLE \"users\" DROP COLUMN \"email\""] + )] + #[case::delete_column_mysql( + "delete_column_mysql", + MigrationAction::DeleteColumn { + table: "users".into(), + column: "email".into(), + }, + DatabaseBackend::MySql, + &["ALTER TABLE `users` DROP COLUMN `email`"] + )] + #[case::delete_column_sqlite( + "delete_column_sqlite", + MigrationAction::DeleteColumn { + table: "users".into(), + column: "email".into(), + }, + DatabaseBackend::Sqlite, + &["ALTER TABLE \"users\" DROP COLUMN \"email\""] + )] + #[case::add_index_postgres( + "add_index_postgres", + MigrationAction::AddIndex { + table: "users".into(), + index: IndexDef { name: "idx_email".into(), columns: vec!["email".into()], unique: false }, + }, + DatabaseBackend::Postgres, + &["CREATE INDEX \"idx_email\" ON \"users\" (\"email\")"] + )] + #[case::add_index_mysql( + "add_index_mysql", + MigrationAction::AddIndex { + table: "users".into(), + index: IndexDef { name: "idx_email".into(), columns: vec!["email".into()], unique: false }, + }, + DatabaseBackend::MySql, + &["CREATE INDEX `idx_email` ON `users` (`email`)"] + )] + #[case::add_index_sqlite( + "add_index_sqlite", + MigrationAction::AddIndex { + table: "users".into(), + index: IndexDef { name: "idx_email".into(), columns: vec!["email".into()], unique: false }, + }, + DatabaseBackend::Sqlite, + &["CREATE INDEX \"idx_email\" ON \"users\" (\"email\")"] + )] + #[case::add_unique_index_postgres( + "add_unique_index_postgres", + MigrationAction::AddIndex { + table: "users".into(), + index: IndexDef { name: "idx_email".into(), columns: vec!["email".into()], unique: true }, + }, + DatabaseBackend::Postgres, + &["CREATE UNIQUE INDEX \"idx_email\" ON \"users\" (\"email\")"] + )] + #[case::add_unique_index_mysql( + "add_unique_index_mysql", + MigrationAction::AddIndex { + table: "users".into(), + index: IndexDef { name: "idx_email".into(), columns: vec!["email".into()], unique: true }, + }, + DatabaseBackend::MySql, + &["CREATE UNIQUE INDEX `idx_email` ON `users` (`email`)"] + )] + #[case::add_unique_index_sqlite( + "add_unique_index_sqlite", + MigrationAction::AddIndex { + table: "users".into(), + index: IndexDef { name: "idx_email".into(), columns: vec!["email".into()], unique: true }, + }, + DatabaseBackend::Sqlite, + &["CREATE UNIQUE INDEX \"idx_email\" ON \"users\" (\"email\")"] + )] + #[case::add_constraint_primary_key_postgres( + "add_constraint_primary_key_postgres", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::PrimaryKey { + columns: vec!["id".into()], + auto_increment: false, + }, }, - vec![("SELECT 1;".to_string(), vec![])] + DatabaseBackend::Postgres, + &["ALTER TABLE \"users\" ADD PRIMARY KEY (\"id\")"] )] - #[case::add_constraint_primary_key( + #[case::add_constraint_primary_key_mysql( + "add_constraint_primary_key_mysql", MigrationAction::AddConstraint { table: "users".into(), constraint: TableConstraint::PrimaryKey { + columns: vec!["id".into()], auto_increment: false, + }, + }, + DatabaseBackend::MySql, + &["ALTER TABLE \"users\" ADD PRIMARY KEY (\"id\")"] + )] + #[case::add_constraint_primary_key_sqlite( + "add_constraint_primary_key_sqlite", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::PrimaryKey { columns: vec!["id".into()], + auto_increment: false, }, }, - vec![( - "ALTER TABLE $1 ADD PRIMARY KEY ($2);".to_string(), - vec!["users".to_string(), "id".to_string()], - )] + DatabaseBackend::Sqlite, + &["ALTER TABLE \"users\" ADD PRIMARY KEY (\"id\")"] )] - #[case::add_constraint_unique( + #[case::add_constraint_unique_named_postgres( + "add_constraint_unique_named_postgres", MigrationAction::AddConstraint { table: "users".into(), constraint: TableConstraint::Unique { - name: Some("unique_email".into()), + name: Some("uq_email".into()), columns: vec!["email".into()], }, }, - vec![( - "ALTER TABLE $1 ADD CONSTRAINT $2 UNIQUE ($3);".to_string(), - vec!["users".to_string(), "unique_email".to_string(), "email".to_string()], - )] + DatabaseBackend::Postgres, + &["ADD CONSTRAINT \"uq_email\" UNIQUE (\"email\")"] + )] + #[case::add_constraint_unique_named_mysql( + "add_constraint_unique_named_mysql", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + }, + DatabaseBackend::MySql, + &["ADD CONSTRAINT \"uq_email\" UNIQUE (\"email\")"] + )] + #[case::add_constraint_unique_named_sqlite( + "add_constraint_unique_named_sqlite", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + }, + DatabaseBackend::Sqlite, + &["ADD CONSTRAINT \"uq_email\" UNIQUE (\"email\")"] + )] + #[case::add_constraint_unique_unnamed_postgres( + "add_constraint_unique_unnamed_postgres", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: None, + columns: vec!["email".into()], + }, + }, + DatabaseBackend::Postgres, + &["ADD UNIQUE (\"email\")"] + )] + #[case::add_constraint_unique_unnamed_mysql( + "add_constraint_unique_unnamed_mysql", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: None, + columns: vec!["email".into()], + }, + }, + DatabaseBackend::MySql, + &["ADD UNIQUE (\"email\")"] + )] + #[case::add_constraint_unique_unnamed_sqlite( + "add_constraint_unique_unnamed_sqlite", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: None, + columns: vec!["email".into()], + }, + }, + DatabaseBackend::Sqlite, + &["ADD UNIQUE (\"email\")"] + )] + #[case::add_constraint_foreign_key_postgres( + "add_constraint_foreign_key_postgres", + MigrationAction::AddConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: Some("fk_user".into()), + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: Some(ReferenceAction::Restrict), + }, + }, + DatabaseBackend::Postgres, + &["FOREIGN KEY (\"user_id\")", "REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"] + )] + #[case::add_constraint_foreign_key_mysql( + "add_constraint_foreign_key_mysql", + MigrationAction::AddConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: Some("fk_user".into()), + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: Some(ReferenceAction::Restrict), + }, + }, + DatabaseBackend::MySql, + &["FOREIGN KEY (\"user_id\")", "REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"] + )] + #[case::add_constraint_foreign_key_unnamed_postgres( + "add_constraint_foreign_key_unnamed_postgres", + MigrationAction::AddConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: None, + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: None, + on_update: None, + }, + }, + DatabaseBackend::Postgres, + &["ADD FOREIGN KEY (\"user_id\") REFERENCES \"users\" (\"id\")"] + )] + #[case::add_constraint_foreign_key_unnamed_mysql( + "add_constraint_foreign_key_unnamed_mysql", + MigrationAction::AddConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: None, + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: None, + on_update: None, + }, + }, + DatabaseBackend::MySql, + &["ADD FOREIGN KEY (\"user_id\") REFERENCES \"users\" (\"id\")"] + )] + #[case::add_constraint_foreign_key_unnamed_sqlite( + "add_constraint_foreign_key_unnamed_sqlite", + MigrationAction::AddConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: None, + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: None, + on_update: None, + }, + }, + DatabaseBackend::Sqlite, + &["ADD FOREIGN KEY (\"user_id\") REFERENCES \"users\" (\"id\")"] + )] + #[case::add_constraint_foreign_key_sqlite( + "add_constraint_foreign_key_sqlite", + MigrationAction::AddConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: Some("fk_user".into()), + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: Some(ReferenceAction::Cascade), + on_update: Some(ReferenceAction::Restrict), + }, + }, + DatabaseBackend::Sqlite, + &["FOREIGN KEY (\"user_id\")", "REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"] + )] + #[case::add_constraint_check_named_postgres( + "add_constraint_check_named_postgres", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Check { + name: "chk_age".into(), + expr: "age > 0".into(), + }, + }, + DatabaseBackend::Postgres, + &["ADD CONSTRAINT \"chk_age\" CHECK (age > 0)"] + )] + #[case::add_constraint_check_named_mysql( + "add_constraint_check_named_mysql", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Check { + name: "chk_age".into(), + expr: "age > 0".into(), + }, + }, + DatabaseBackend::MySql, + &["ADD CONSTRAINT \"chk_age\" CHECK (age > 0)"] + )] + #[case::add_constraint_check_named_sqlite( + "add_constraint_check_named_sqlite", + MigrationAction::AddConstraint { + table: "users".into(), + constraint: TableConstraint::Check { + name: "chk_age".into(), + expr: "age > 0".into(), + }, + }, + DatabaseBackend::Sqlite, + &["ADD CONSTRAINT \"chk_age\" CHECK (age > 0)"] )] - #[case::remove_constraint_primary_key( + #[case::remove_constraint_primary_key_postgres( + "remove_constraint_primary_key_postgres", MigrationAction::RemoveConstraint { table: "users".into(), constraint: TableConstraint::PrimaryKey { + columns: vec!["id".into()], auto_increment: false, + }, + }, + DatabaseBackend::Postgres, + &["DROP CONSTRAINT \"users_pkey\""] + )] + #[case::remove_constraint_primary_key_mysql( + "remove_constraint_primary_key_mysql", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::PrimaryKey { + columns: vec!["id".into()], + auto_increment: false, + }, + }, + DatabaseBackend::MySql, + &["DROP CONSTRAINT \"users_pkey\""] + )] + #[case::remove_constraint_primary_key_sqlite( + "remove_constraint_primary_key_sqlite", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::PrimaryKey { columns: vec!["id".into()], + auto_increment: false, + }, + }, + DatabaseBackend::Sqlite, + &["DROP CONSTRAINT \"users_pkey\""] + )] + #[case::remove_constraint_unique_named_postgres( + "remove_constraint_unique_named_postgres", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + }, + DatabaseBackend::Postgres, + &["DROP CONSTRAINT \"uq_email\""] + )] + #[case::remove_constraint_unique_named_mysql( + "remove_constraint_unique_named_mysql", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + }, + DatabaseBackend::MySql, + &["DROP CONSTRAINT \"uq_email\""] + )] + #[case::remove_constraint_unique_named_sqlite( + "remove_constraint_unique_named_sqlite", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: Some("uq_email".into()), + columns: vec!["email".into()], + }, + }, + DatabaseBackend::Sqlite, + &["DROP CONSTRAINT \"uq_email\""] + )] + #[case::remove_constraint_unique_unnamed_postgres( + "remove_constraint_unique_unnamed_postgres", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::Unique { + name: None, + columns: vec!["email".into()], }, }, - vec![("ALTER TABLE $1 DROP CONSTRAINT $1_pkey;".to_string(), vec!["users".to_string()])] + DatabaseBackend::Postgres, + &["DROP CONSTRAINT \"users_email_key\""] )] - #[case::remove_constraint_unique_named( + #[case::remove_constraint_unique_unnamed_mysql( + "remove_constraint_unique_unnamed_mysql", MigrationAction::RemoveConstraint { table: "users".into(), constraint: TableConstraint::Unique { - name: Some("unique_email".into()), + name: None, columns: vec!["email".into()], }, }, - vec![( - "ALTER TABLE $1 DROP CONSTRAINT $2;".to_string(), - vec!["users".to_string(), "unique_email".to_string()], - )] + DatabaseBackend::MySql, + &["DROP CONSTRAINT \"users_email_key\""] )] - #[case::remove_constraint_unique_unnamed( + #[case::remove_constraint_unique_unnamed_sqlite( + "remove_constraint_unique_unnamed_sqlite", MigrationAction::RemoveConstraint { table: "users".into(), constraint: TableConstraint::Unique { @@ -683,12 +1753,11 @@ mod tests { columns: vec!["email".into()], }, }, - vec![( - "ALTER TABLE $1 DROP CONSTRAINT $2;".to_string(), - vec!["users".to_string(), "users_email_key".to_string()], - )] + DatabaseBackend::Sqlite, + &["DROP CONSTRAINT \"users_email_key\""] )] - #[case::remove_constraint_foreign_key_named( + #[case::remove_constraint_foreign_key_named_postgres( + "remove_constraint_foreign_key_named_postgres", MigrationAction::RemoveConstraint { table: "posts".into(), constraint: TableConstraint::ForeignKey { @@ -700,16 +1769,15 @@ mod tests { on_update: None, }, }, - vec![( - "ALTER TABLE $1 DROP CONSTRAINT $2;".to_string(), - vec!["posts".to_string(), "fk_user".to_string()], - )] + DatabaseBackend::Postgres, + &["DROP CONSTRAINT \"fk_user\""] )] - #[case::remove_constraint_foreign_key_unnamed( + #[case::remove_constraint_foreign_key_named_mysql( + "remove_constraint_foreign_key_named_mysql", MigrationAction::RemoveConstraint { table: "posts".into(), constraint: TableConstraint::ForeignKey { - name: None, + name: Some("fk_user".into()), columns: vec!["user_id".into()], ref_table: "users".into(), ref_columns: vec!["id".into()], @@ -717,312 +1785,287 @@ mod tests { on_update: None, }, }, - vec![( - "ALTER TABLE $1 DROP CONSTRAINT $2;".to_string(), - vec!["posts".to_string(), "posts_user_id_fkey".to_string()], - )] + DatabaseBackend::MySql, + &["DROP CONSTRAINT \"fk_user\""] )] - #[case::remove_constraint_check_named( + #[case::remove_constraint_foreign_key_named_sqlite( + "remove_constraint_foreign_key_named_sqlite", MigrationAction::RemoveConstraint { - table: "users".into(), - constraint: TableConstraint::Check { - name: Some("check_age".into()), - expr: "age > 0".into(), + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: Some("fk_user".into()), + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: None, + on_update: None, }, }, - vec![( - "ALTER TABLE $1 DROP CONSTRAINT $2;".to_string(), - vec!["users".to_string(), "check_age".to_string()], - )] + DatabaseBackend::Sqlite, + &["DROP CONSTRAINT \"fk_user\""] )] - fn test_build_action_queries( - #[case] action: MigrationAction, - #[case] expected: Vec<(String, Vec)>, - ) { - let result = build_action_queries(&action).unwrap(); - assert_eq!( - result.len(), - expected.len(), - "Expected {} queries, got {}", - expected.len(), - result.len() - ); - - for (i, (expected_sql, expected_binds)) in expected.iter().enumerate() { - assert_eq!(result[i].sql, *expected_sql, "Query {} mismatch sql", i); - assert_eq!( - result[i].binds, *expected_binds, - "Query {} mismatch binds", - i - ); - } - } - - #[rstest] - #[case::simple( - "users", - vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("name", ColumnType::Simple(SimpleColumnType::Text))], - vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }], - ( - "CREATE TABLE $1 ($2 INTEGER, $3 TEXT, PRIMARY KEY ($4));".to_string(), - vec!["users".to_string(), "id".to_string(), "name".to_string(), "id".to_string()], - ) - )] - #[case::multiple_constraints( - "users", - vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("email", ColumnType::Simple(SimpleColumnType::Text))], - vec![ - TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }, - TableConstraint::Unique { - name: Some("unique_email".into()), - columns: vec!["email".into()], + #[case::remove_constraint_foreign_key_unnamed_postgres( + "remove_constraint_foreign_key_unnamed_postgres", + MigrationAction::RemoveConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: None, + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: None, + on_update: None, }, - ], - ( - "CREATE TABLE $1 ($2 INTEGER, $3 TEXT, PRIMARY KEY ($4), CONSTRAINT $5 UNIQUE ($6));".to_string(), - vec![ - "users".to_string(), - "id".to_string(), - "email".to_string(), - "id".to_string(), - "unique_email".to_string(), - "email".to_string(), - ], - ) + }, + DatabaseBackend::Postgres, + &["DROP CONSTRAINT \"posts_user_id_fkey\""] )] - fn test_create_table_sql( - #[case] table: &str, - #[case] columns: Vec, - #[case] constraints: Vec, - #[case] expected: (String, Vec), - ) { - let result = create_table_sql(table, &columns, &constraints).unwrap(); - assert_eq!(result.sql, expected.0); - assert_eq!(result.binds, expected.1); - } - - #[rstest] - #[case::nullable( - col("name", ColumnType::Simple(SimpleColumnType::Text)), - ("$1 TEXT".to_string(), vec!["name".to_string()]) - )] - #[case::not_null( - { - let mut c = col("name", ColumnType::Simple(SimpleColumnType::Text)); - c.nullable = false; - c - }, - ("$1 TEXT NOT NULL".to_string(), vec!["name".to_string()]) - )] - #[case::with_default( - { - let mut c = col("name", ColumnType::Simple(SimpleColumnType::Text)); - c.default = Some("'default'".to_string()); - c - }, - ( - "$1 TEXT DEFAULT $2".to_string(), - vec!["name".to_string(), "'default'".to_string()], - ) - )] - fn test_column_def_sql(#[case] column: ColumnDef, #[case] expected: (String, Vec)) { - let mut binds = Vec::new(); - let result = column_def_sql(&column, &mut binds); - assert_eq!(result, expected.0); - assert_eq!(binds, expected.1); - } - - #[rstest] - #[case(ColumnType::Simple(SimpleColumnType::Integer), "INTEGER")] - #[case(ColumnType::Simple(SimpleColumnType::BigInt), "BIGINT")] - #[case(ColumnType::Simple(SimpleColumnType::Text), "TEXT")] - #[case(ColumnType::Simple(SimpleColumnType::Boolean), "BOOLEAN")] - #[case(ColumnType::Simple(SimpleColumnType::Timestamp), "TIMESTAMP")] - #[case(ColumnType::Simple(SimpleColumnType::Uuid), "UUID")] - #[case(ColumnType::Simple(SimpleColumnType::Interval), "INTERVAL")] - #[case(ColumnType::Simple(SimpleColumnType::Xml), "XML")] - #[case(ColumnType::Complex(ComplexColumnType::Numeric { precision: 10, scale: 2 }), "NUMERIC(10, 2)")] - #[case(ColumnType::Complex(ComplexColumnType::Char { length: 10 }), "CHAR(10)")] - #[case(ColumnType::Complex(ComplexColumnType::Custom { custom_type: "VARCHAR(255)".to_string() }), "VARCHAR(255)")] - fn test_column_type_sql(#[case] ty: ColumnType, #[case] expected: &str) { - assert_eq!(ty.to_sql(), expected); - } - - #[rstest] - #[case::primary_key_single( - TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }, - ("PRIMARY KEY ($1)".to_string(), vec!["id".to_string()]) + #[case::remove_constraint_foreign_key_unnamed_mysql( + "remove_constraint_foreign_key_unnamed_mysql", + MigrationAction::RemoveConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: None, + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: None, + on_update: None, + }, + }, + DatabaseBackend::MySql, + &["DROP CONSTRAINT \"posts_user_id_fkey\""] )] - #[case::primary_key_multiple( - TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into(), "version".into()] }, - ("PRIMARY KEY ($1, $2)".to_string(), vec!["id".to_string(), "version".to_string()]) + #[case::remove_constraint_foreign_key_unnamed_sqlite( + "remove_constraint_foreign_key_unnamed_sqlite", + MigrationAction::RemoveConstraint { + table: "posts".into(), + constraint: TableConstraint::ForeignKey { + name: None, + columns: vec!["user_id".into()], + ref_table: "users".into(), + ref_columns: vec!["id".into()], + on_delete: None, + on_update: None, + }, + }, + DatabaseBackend::Sqlite, + &["DROP CONSTRAINT \"posts_user_id_fkey\""] )] - #[case::unique_without_name( - TableConstraint::Unique { - name: None, - columns: vec!["email".into()], + #[case::remove_constraint_check_named_postgres( + "remove_constraint_check_named_postgres", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::Check { + name: "chk_age".into(), + expr: "age > 0".into(), + }, }, - ("UNIQUE ($1)".to_string(), vec!["email".to_string()]) + DatabaseBackend::Postgres, + &["DROP CONSTRAINT \"chk_age\""] )] - #[case::unique_with_name( - TableConstraint::Unique { - name: Some("unique_email".into()), - columns: vec!["email".into()], + #[case::remove_constraint_check_named_mysql( + "remove_constraint_check_named_mysql", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::Check { + name: "chk_age".into(), + expr: "age > 0".into(), + }, }, - ( - "CONSTRAINT $1 UNIQUE ($2)".to_string(), - vec!["unique_email".to_string(), "email".to_string()], - ) + DatabaseBackend::MySql, + &["DROP CONSTRAINT \"chk_age\""] )] - #[case::foreign_key_without_name( - TableConstraint::ForeignKey { - name: None, - columns: vec!["user_id".into()], - ref_table: "users".into(), - ref_columns: vec!["id".into()], - on_delete: None, - on_update: None, - }, - ( - "FOREIGN KEY ($1) REFERENCES $3 ($2)".to_string(), - vec!["user_id".to_string(), "id".to_string(), "users".to_string()], - ) - )] - #[case::foreign_key_with_name( - TableConstraint::ForeignKey { - name: Some("fk_user".into()), - columns: vec!["user_id".into()], - ref_table: "users".into(), - ref_columns: vec!["id".into()], - on_delete: None, - on_update: None, - }, - ( - "CONSTRAINT $1 FOREIGN KEY ($2) REFERENCES $4 ($3)".to_string(), - vec![ - "fk_user".to_string(), - "user_id".to_string(), - "id".to_string(), - "users".to_string(), - ], - ) + #[case::remove_constraint_check_named_sqlite( + "remove_constraint_check_named_sqlite", + MigrationAction::RemoveConstraint { + table: "users".into(), + constraint: TableConstraint::Check { + name: "chk_age".into(), + expr: "age > 0".into(), + }, + }, + DatabaseBackend::Sqlite, + &["DROP CONSTRAINT \"chk_age\""] )] - #[case::foreign_key_with_on_delete( - TableConstraint::ForeignKey { - name: None, - columns: vec!["user_id".into()], - ref_table: "users".into(), - ref_columns: vec!["id".into()], - on_delete: Some(ReferenceAction::Cascade), - on_update: None, - }, - ( - "FOREIGN KEY ($1) REFERENCES $3 ($2) ON DELETE CASCADE".to_string(), - vec!["user_id".to_string(), "id".to_string(), "users".to_string()], - ) - )] - #[case::foreign_key_with_on_update( - TableConstraint::ForeignKey { - name: None, - columns: vec!["user_id".into()], - ref_table: "users".into(), - ref_columns: vec!["id".into()], - on_delete: None, - on_update: Some(ReferenceAction::Restrict), - }, - ( - "FOREIGN KEY ($1) REFERENCES $3 ($2) ON UPDATE RESTRICT".to_string(), - vec!["user_id".to_string(), "id".to_string(), "users".to_string()], - ) - )] - #[case::foreign_key_with_both_actions( - TableConstraint::ForeignKey { - name: None, - columns: vec!["user_id".into()], - ref_table: "users".into(), - ref_columns: vec!["id".into()], - on_delete: Some(ReferenceAction::SetNull), - on_update: Some(ReferenceAction::SetDefault), - }, - ( - "FOREIGN KEY ($1) REFERENCES $3 ($2) ON DELETE SET NULL ON UPDATE SET DEFAULT".to_string(), - vec!["user_id".to_string(), "id".to_string(), "users".to_string()], - ) - )] - #[case::foreign_key_multiple_columns( - TableConstraint::ForeignKey { - name: None, - columns: vec!["user_id".into(), "tenant_id".into()], - ref_table: "user_tenants".into(), - ref_columns: vec!["user_id".into(), "tenant_id".into()], - on_delete: None, - on_update: None, - }, - ( - "FOREIGN KEY ($1, $2) REFERENCES $5 ($3, $4)".to_string(), - vec![ - "user_id".to_string(), - "tenant_id".to_string(), - "user_id".to_string(), - "tenant_id".to_string(), - "user_tenants".to_string(), - ], - ) + #[case::remove_index_postgres( + "remove_index_postgres", + MigrationAction::RemoveIndex { + table: "users".into(), + name: "idx_email".into(), + }, + DatabaseBackend::Postgres, + &["DROP INDEX \"idx_email\""] )] - #[case::check_without_name( - TableConstraint::Check { - name: None, - expr: "age > 0".to_string(), + #[case::remove_index_mysql( + "remove_index_mysql", + MigrationAction::RemoveIndex { + table: "users".into(), + name: "idx_email".into(), }, - ("CHECK ($1)".to_string(), vec!["age > 0".to_string()]) + DatabaseBackend::MySql, + &["DROP INDEX `idx_email`"] )] - #[case::check_with_name( - TableConstraint::Check { - name: Some("check_age".into()), - expr: "age > 0".to_string(), + #[case::remove_index_sqlite( + "remove_index_sqlite", + MigrationAction::RemoveIndex { + table: "users".into(), + name: "idx_email".into(), }, - ( - "CONSTRAINT $1 CHECK ($2)".to_string(), - vec!["check_age".to_string(), "age > 0".to_string()], - ) + DatabaseBackend::Sqlite, + &["DROP INDEX \"idx_email\""] )] - fn test_table_constraint_sql( - #[case] constraint: TableConstraint, - #[case] expected: (String, Vec), + fn test_build_migration_action( + #[case] title: &str, + #[case] action: MigrationAction, + #[case] backend: DatabaseBackend, + #[case] expected: &[&str], ) { - let mut binds = Vec::new(); - let result = table_constraint_sql(&constraint, &mut binds).unwrap(); - assert_eq!(result, expected.0); - assert_eq!(binds, expected.1); + let result = build_action_queries(&action).unwrap(); + let sql = result[0].build(backend); + for exp in expected { + assert!( + sql.contains(exp), + "Expected SQL to contain '{}', got: {}", + exp, + sql + ); + } + + with_settings!({ snapshot_suffix => format!("build_migration_action_{}", title) }, { + assert_snapshot!(result.iter().map(|q| q.build(backend)).collect::>().join("\n")); + }); } #[rstest] - #[case(ReferenceAction::Cascade, "CASCADE")] - #[case(ReferenceAction::Restrict, "RESTRICT")] - #[case(ReferenceAction::SetNull, "SET NULL")] - #[case(ReferenceAction::SetDefault, "SET DEFAULT")] - #[case(ReferenceAction::NoAction, "NO ACTION")] - fn test_reference_action_sql(#[case] action: ReferenceAction, #[case] expected: &str) { - let mut binds = Vec::new(); - assert_eq!(reference_action_sql(&action, &mut binds), expected); - } + #[case::create_table_query_postgres( + "create_table_query_postgres", + BuiltQuery::CreateTable(Box::new( + Table::create() + .table(Alias::new("t")) + .col(SeaColumnDef::new(Alias::new("id")).integer().to_owned()) + .to_owned(), + )), + DatabaseBackend::Postgres, + &["CREATE TABLE \"t\""] + )] + #[case::create_table_query_mysql("create_table_query_mysql", BuiltQuery::CreateTable(Box::new(Table::create() + .table(Alias::new("t")) + .col(SeaColumnDef::new(Alias::new("id")).integer().to_owned()) + .to_owned())),DatabaseBackend::MySql, &["CREATE TABLE `t`"])] + #[case::create_table_query_sqlite("create_table_query_sqlite", BuiltQuery::CreateTable(Box::new(Table::create() + .table(Alias::new("t")) + .col(SeaColumnDef::new(Alias::new("id")).integer().to_owned()) + .to_owned())),DatabaseBackend::Sqlite, &["CREATE TABLE \"t\""])] + #[case::drop_table_query_postgres( + "drop_table_query_postgres", + BuiltQuery::DropTable(Box::new( + Table::drop().table(Alias::new("t")).to_owned(), + )), + DatabaseBackend::Postgres, + &["DROP TABLE \"t\""] + )] + #[case::drop_table_query_mysql("drop_table_query_mysql", BuiltQuery::DropTable(Box::new(Table::drop().table(Alias::new("t")).to_owned())), DatabaseBackend::MySql, &["DROP TABLE `t`"])] + #[case::drop_table_query_sqlite("drop_table_query_sqlite", BuiltQuery::DropTable(Box::new(Table::drop().table(Alias::new("t")).to_owned())), DatabaseBackend::Sqlite, &["DROP TABLE \"t\""])] + #[case::raw_query_postgres( + "raw_query_postgres", + BuiltQuery::Raw("SELECT 1".into()), + DatabaseBackend::Postgres, + &["SELECT 1"] + )] + #[case::raw_query_mysql("raw_query_mysql", BuiltQuery::Raw("SELECT 1".into()), DatabaseBackend::MySql, &["SELECT 1"])] + #[case::raw_query_sqlite("raw_query_sqlite", BuiltQuery::Raw("SELECT 1".into()), DatabaseBackend::Sqlite, &["SELECT 1"])] + #[case::alter_table_postgres("alter_table_postgres", BuiltQuery::AlterTable(Box::new(Table::alter() + .table(Alias::new("t")) + .add_column(SeaColumnDef::new(Alias::new("c")).integer().to_owned()) + .to_owned())),DatabaseBackend::Postgres, &["ALTER TABLE \"t\" ADD COLUMN \"c\" integer"])] + #[case::alter_table_mysql("alter_table_mysql", BuiltQuery::AlterTable(Box::new(Table::alter() + .table(Alias::new("t")) + .add_column(SeaColumnDef::new(Alias::new("c")).integer().to_owned()) + .to_owned())),DatabaseBackend::MySql, &["ALTER TABLE `t` ADD COLUMN `c` int"])] + #[case::alter_table_sqlite("alter_table_sqlite", BuiltQuery::AlterTable(Box::new(Table::alter() + .table(Alias::new("t")) + .add_column(SeaColumnDef::new(Alias::new("c")).integer().to_owned()) + .to_owned())),DatabaseBackend::Sqlite, &["ALTER TABLE \"t\" ADD COLUMN \"c\" integer"])] + #[case::create_index_postgres("create_index_postgres", BuiltQuery::CreateIndex(Box::new(Index::create() + .name("idx") + .table(Alias::new("t")) + .col(Alias::new("c")) + .to_owned())),DatabaseBackend::Postgres, &["CREATE INDEX \"idx\" ON \"t\" (\"c\")"])] + #[case::create_index_mysql("create_index_mysql", BuiltQuery::CreateIndex(Box::new(Index::create() + .name("idx") + .table(Alias::new("t")) + .col(Alias::new("c")) + .to_owned())),DatabaseBackend::MySql, &["CREATE INDEX `idx` ON `t` (`c`"])] + #[case::create_index_sqlite("create_index_sqlite", BuiltQuery::CreateIndex(Box::new(Index::create() + .name("idx") + .table(Alias::new("t")) + .col(Alias::new("c")) + .to_owned())),DatabaseBackend::Sqlite, &["CREATE INDEX \"idx\" ON \"t\" (\"c\")"])] + #[case::drop_index_postgres("drop_index_postgres", BuiltQuery::DropIndex(Box::new(Index::drop().name("idx").table(Alias::new("t")).to_owned())),DatabaseBackend::Postgres, &["DROP INDEX \"idx\""])] + #[case::drop_index_mysql("drop_index_mysql", BuiltQuery::DropIndex(Box::new(Index::drop().name("idx").table(Alias::new("t")).to_owned())),DatabaseBackend::MySql, &["`idx`"])] + #[case::drop_index_sqlite("drop_index_sqlite", BuiltQuery::DropIndex(Box::new(Index::drop().name("idx").table(Alias::new("t")).to_owned())),DatabaseBackend::Sqlite, &["\"idx\""])] + #[case::rename_table_postgres("rename_table_postgres", BuiltQuery::RenameTable(Box::new(Table::rename() + .table(Alias::new("a"), Alias::new("b")) + .to_owned())),DatabaseBackend::Postgres, &["ALTER TABLE \"a\" RENAME TO \"b\""])] + #[case::rename_table_mysql("rename_table_mysql", BuiltQuery::RenameTable(Box::new(Table::rename() + .table(Alias::new("a"), Alias::new("b")) + .to_owned())),DatabaseBackend::MySql, &["RENAME TABLE `a` TO `b`"])] + #[case::rename_table_sqlite("rename_table_sqlite", BuiltQuery::RenameTable(Box::new(Table::rename() + .table(Alias::new("a"), Alias::new("b")) + .to_owned())),DatabaseBackend::Sqlite, &["ALTER TABLE \"a\" RENAME TO \"b\""])] + #[case::create_foreign_key_postgres("create_foreign_key_postgres", BuiltQuery::CreateForeignKey(Box::new(ForeignKey::create() + .name("fk") + .from_tbl(Alias::new("a")) + .from_col(Alias::new("c")) + .to_tbl(Alias::new("b")) + .to_col(Alias::new("id")) + .to_owned())),DatabaseBackend::Postgres, &["ALTER TABLE \"a\" ADD CONSTRAINT \"fk\" FOREIGN KEY (\"c\") REFERENCES \"b\" (\"id\")"])] + #[case::create_foreign_key_mysql("create_foreign_key_mysql", BuiltQuery::CreateForeignKey(Box::new(ForeignKey::create() + .name("fk") + .from_tbl(Alias::new("a")) + .from_col(Alias::new("c")) + .to_tbl(Alias::new("b")) + .to_col(Alias::new("id")) + .to_owned())),DatabaseBackend::MySql, &["ALTER TABLE `a` ADD CONSTRAINT `fk` FOREIGN KEY (`c`) REFERENCES `b` (`id`)"])] + // NOTE: SQLite does not support modifying FK constraints on existing tables; sea-query panics for these. + // #[case::create_foreign_key_sqlite("create_foreign_key_sqlite", BuiltQuery::CreateForeignKey(Box::new(ForeignKey::create() + // .name("fk") + // .from_tbl(Alias::new("a")) + // .from_col(Alias::new("c")) + // .to_tbl(Alias::new("b")) + // .to_col(Alias::new("id")) + // .to_owned())),DatabaseBackend::Sqlite, &["ALTER TABLE \"a\" ADD CONSTRAINT \"fk\" FOREIGN KEY (\"c\") REFERENCES \"b\" (\"id\")"])] + #[case::drop_foreign_key_postgres("drop_foreign_key_postgres", BuiltQuery::DropForeignKey(Box::new(ForeignKey::drop() + .name("fk") + .table(Alias::new("a")) + .to_owned())),DatabaseBackend::Postgres, &["ALTER TABLE \"a\" DROP CONSTRAINT \"fk\""])] + #[case::drop_foreign_key_mysql("drop_foreign_key_mysql", BuiltQuery::DropForeignKey(Box::new(ForeignKey::drop() + .name("fk") + .table(Alias::new("a")) + .to_owned())),DatabaseBackend::MySql, &["ALTER TABLE `a` DROP FOREIGN KEY `fk`"])] + // #[case::drop_foreign_key_sqlite("drop_foreign_key_sqlite", BuiltQuery::DropForeignKey(Box::new(ForeignKey::drop() + // .name("fk") + // .table(Alias::new("a")) + // .to_owned())),DatabaseBackend::Sqlite, &["ALTER TABLE \"a\" DROP CONSTRAINT \"fk\""])] + fn test_build_query( + #[case] title: &str, + #[case] q: BuiltQuery, + #[case] backend: DatabaseBackend, + #[case] expected: &[&str], + ) { + let sql = q.build(backend); + for exp in expected { + assert!( + sql.contains(exp), + "Expected SQL to contain '{}', got: {}", + exp, + sql + ); + } - #[test] - fn test_remove_constraint_check_unnamed_error() { - let action = MigrationAction::RemoveConstraint { - table: "users".into(), - constraint: TableConstraint::Check { - name: None, - expr: "age > 0".into(), - }, - }; - let result = build_action_queries(&action); - assert!(result.is_err()); - assert!( - result - .unwrap_err() - .to_string() - .contains("Cannot drop unnamed CHECK constraint") - ); + with_settings!({ snapshot_suffix => format!("build_query_{}", title) }, { + assert_snapshot!(sql); + }); } } diff --git a/examples/app/Cargo.toml b/examples/app/Cargo.toml index 0a501f8..4e45d9f 100644 --- a/examples/app/Cargo.toml +++ b/examples/app/Cargo.toml @@ -7,5 +7,5 @@ publish = false [dependencies] vespertide = { path = "../../crates/vespertide" } tokio = { version = "1", features = ["full"] } -sea-orm = { version = "2.0.0-rc.21", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"] } +sea-orm = { version = "2.0.0-rc.21", features = ["sqlx-sqlite", "sqlx-postgres", "runtime-tokio-native-tls", "macros"] } anyhow = "1" diff --git a/examples/app/migrations/0001_create_user.json b/examples/app/migrations/0001_create_user.json index 5a6192c..11f4322 100644 --- a/examples/app/migrations/0001_create_user.json +++ b/examples/app/migrations/0001_create_user.json @@ -1,20 +1,63 @@ { - "comment": "Create user", - "created_at": "2025-12-09T15:06:17Z", - "version": 1, + "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/migration.schema.json", "actions": [ { - "type": "create_table", - "table": "user", "columns": [ { - "name": "aa", + "comment": null, + "default": null, + "foreign_key": null, + "index": null, + "name": "id", + "nullable": false, + "primary_key": true, "type": "integer", + "unique": null + }, + { + "comment": null, + "default": null, + "foreign_key": null, + "index": null, + "name": "name", + "nullable": false, + "primary_key": null, + "type": "text", + "unique": null + }, + { + "comment": null, + "default": null, + "foreign_key": null, + "index": null, + "name": "email", "nullable": false, - "default": null + "primary_key": null, + "type": "text", + "unique": true } ], - "constraints": [] + "constraints": [ + { + "auto_increment": false, + "columns": [ + "id" + ], + "type": "primary_key" + }, + { + "columns": [ + "email" + ], + "name": null, + "type": "unique" + } + ], + "table": "user", + "type": "create_table" } - ] + ], + "comment": "Create user", + "created_at": "2025-12-15T06:19:36Z", + "version": 1 } \ No newline at end of file diff --git a/examples/app/migrations/0002_create_post.json b/examples/app/migrations/0002_create_post.json new file mode 100644 index 0000000..4b219fb --- /dev/null +++ b/examples/app/migrations/0002_create_post.json @@ -0,0 +1,139 @@ +{ + "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/migration.schema.json", + "actions": [ + { + "columns": [ + { + "comment": null, + "default": null, + "foreign_key": null, + "index": null, + "name": "id", + "nullable": false, + "primary_key": true, + "type": "integer", + "unique": null + }, + { + "comment": null, + "default": null, + "foreign_key": null, + "index": null, + "name": "title", + "nullable": false, + "primary_key": null, + "type": "text", + "unique": null + }, + { + "comment": null, + "default": null, + "foreign_key": null, + "index": null, + "name": "content", + "nullable": false, + "primary_key": null, + "type": "text", + "unique": null + }, + { + "comment": null, + "default": null, + "foreign_key": null, + "index": null, + "name": "created_at", + "nullable": false, + "primary_key": null, + "type": "timestamp", + "unique": null + }, + { + "comment": null, + "default": null, + "foreign_key": null, + "index": [ + "tuple", + "tuple2" + ], + "name": "updated_at", + "nullable": true, + "primary_key": null, + "type": "timestamp", + "unique": null + }, + { + "comment": null, + "default": null, + "foreign_key": { + "on_delete": null, + "on_update": null, + "ref_columns": [ + "id" + ], + "ref_table": "user" + }, + "index": [ + "tuple", + "tuple2" + ], + "name": "user_id", + "nullable": false, + "primary_key": null, + "type": "integer", + "unique": null + } + ], + "constraints": [ + { + "auto_increment": false, + "columns": [ + "id" + ], + "type": "primary_key" + }, + { + "columns": [ + "user_id" + ], + "name": null, + "on_delete": null, + "on_update": null, + "ref_columns": [ + "id" + ], + "ref_table": "user", + "type": "foreign_key" + } + ], + "table": "post", + "type": "create_table" + }, + { + "index": { + "columns": [ + "updated_at", + "user_id" + ], + "name": "tuple", + "unique": false + }, + "table": "post", + "type": "add_index" + }, + { + "index": { + "columns": [ + "updated_at", + "user_id" + ], + "name": "tuple2", + "unique": false + }, + "table": "post", + "type": "add_index" + } + ], + "comment": "Create post", + "created_at": "2025-12-15T06:36:34Z", + "version": 2 +} \ No newline at end of file diff --git a/examples/app/models/user copy.json b/examples/app/models/user copy.json deleted file mode 100644 index 7221fb1..0000000 --- a/examples/app/models/user copy.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/model.schema.json", - "name": "user", - "columns": [ - { - "name": "id", - "type": "uuid", - "nullable": false, - "default": "gen_random_uuid()", - "primary_key": true - }, - { - "name": "email", - "type": { "kind": "varchar", "length": 255 }, - "nullable": false, - "unique": true, - "index": true - }, - { - "name": "password", - "type": { "kind": "varchar", "length": 255 }, - "nullable": false - }, - { - "name": "name", - "type": { "kind": "varchar", "length": 100 }, - "nullable": false - }, - { - "name": "profile_image", - "type": "text", - "nullable": true - }, - { - "name": "created_at", - "type": "timestamptz", - "nullable": false, - "default": "now()" - }, - { - "name": "updated_at", - "type": "timestamptz", - "nullable": true - } - ], - "constraints": [], - "indexes": [] -} diff --git a/examples/app/src/main.rs b/examples/app/src/main.rs index 4e769a2..449abe9 100644 --- a/examples/app/src/main.rs +++ b/examples/app/src/main.rs @@ -6,8 +6,9 @@ use std::time::Duration; async fn main() -> Result<()> { println!("Hello, world!"); - // Configure SQLite connection - let mut opt = ConnectOptions::new("sqlite://./local.db?mode=rwc"); + let mut opt = ConnectOptions::new("postgres://postgres:password@localhost:5432/postgres"); + // // Configure SQLite connection + // let mut opt = ConnectOptions::new("sqlite://./local.db?mode=rwc"); opt.max_connections(100) .min_connections(5) .connect_timeout(Duration::from_secs(8)) diff --git a/examples/app/src/models/post/post.rs b/examples/app/src/models/post/post.rs index 52aef45..d7ea39d 100644 --- a/examples/app/src/models/post/post.rs +++ b/examples/app/src/models/post/post.rs @@ -11,6 +11,12 @@ pub struct Model { pub created_at: DateTime, pub updated_at: Option, pub user_id: i32, + #[sea_orm(belongs_to, from = "user_id", to = "id")] + pub user: HasOne, } + +// Index definitions (SeaORM uses Statement builders externally) +// tuple on [updated_at, user_id] unique=false +// tuple2 on [updated_at, user_id] unique=false impl ActiveModelBehavior for ActiveModel {}