Skip to main content

oxilean_codegen/sql_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{
6    SQLAlterTableBuilder, SQLAnalyzeBuilder, SQLBackend, SQLColumn, SQLColumnDef, SQLColumnInfo,
7    SQLCommonTableExpression, SQLCreateTableBuilder, SQLDeleteBuilder, SQLDialect, SQLExpr,
8    SQLIndexBuilder, SQLInsertBuilder, SQLIsolationLevel, SQLJoin, SQLMigration,
9    SQLMigrationRunner, SQLQueryFormatter, SQLQueryOptimizer, SQLQueryPlan, SQLQueryPlanNode,
10    SQLSchemaInspector, SQLSelectBuilder, SQLSequenceBuilder, SQLStmt, SQLStoredProcedure,
11    SQLTable, SQLTableInfo, SQLTransactionBuilder, SQLTrigger, SQLType, SQLTypeMapper,
12    SQLUpdateBuilder, SQLViewBuilder, SQLWindowFunction, SQLWithQuery,
13};
14
15#[cfg(test)]
16mod tests {
17    use super::*;
18    pub(super) fn sqlite() -> SQLBackend {
19        SQLBackend::new(SQLDialect::SQLite)
20    }
21    pub(super) fn pg() -> SQLBackend {
22        SQLBackend::new(SQLDialect::PostgreSQL)
23    }
24    pub(super) fn mysql() -> SQLBackend {
25        SQLBackend::new(SQLDialect::MySQL)
26    }
27    pub(super) fn mssql() -> SQLBackend {
28        SQLBackend::new(SQLDialect::MSSQL)
29    }
30    #[test]
31    pub(super) fn test_emit_type_sqlite() {
32        let b = sqlite();
33        assert_eq!(b.emit_type(&SQLType::Integer), "INTEGER");
34        assert_eq!(b.emit_type(&SQLType::Real), "REAL");
35        assert_eq!(b.emit_type(&SQLType::Text), "TEXT");
36        assert_eq!(b.emit_type(&SQLType::Blob), "BLOB");
37        assert_eq!(b.emit_type(&SQLType::Boolean), "INTEGER");
38        assert_eq!(b.emit_type(&SQLType::Timestamp), "TEXT");
39    }
40    #[test]
41    pub(super) fn test_emit_type_postgresql() {
42        let b = pg();
43        assert_eq!(b.emit_type(&SQLType::Real), "DOUBLE PRECISION");
44        assert_eq!(b.emit_type(&SQLType::Blob), "BYTEA");
45        assert_eq!(b.emit_type(&SQLType::Boolean), "BOOLEAN");
46        assert_eq!(b.emit_type(&SQLType::Timestamp), "TIMESTAMP");
47    }
48    #[test]
49    pub(super) fn test_emit_type_mysql() {
50        let b = mysql();
51        assert_eq!(b.emit_type(&SQLType::Integer), "INT");
52        assert_eq!(b.emit_type(&SQLType::Real), "DOUBLE");
53        assert_eq!(b.emit_type(&SQLType::Boolean), "TINYINT(1)");
54        assert_eq!(b.emit_type(&SQLType::Timestamp), "DATETIME");
55    }
56    #[test]
57    pub(super) fn test_emit_type_mssql() {
58        let b = mssql();
59        assert_eq!(b.emit_type(&SQLType::Integer), "INT");
60        assert_eq!(b.emit_type(&SQLType::Real), "FLOAT");
61        assert_eq!(b.emit_type(&SQLType::Text), "NVARCHAR(MAX)");
62        assert_eq!(b.emit_type(&SQLType::Blob), "VARBINARY(MAX)");
63        assert_eq!(b.emit_type(&SQLType::Boolean), "BIT");
64        assert_eq!(b.emit_type(&SQLType::Timestamp), "DATETIME2");
65    }
66    #[test]
67    pub(super) fn test_select_all() {
68        let b = sqlite();
69        assert_eq!(b.select_all("users"), "SELECT * FROM users;");
70    }
71    #[test]
72    pub(super) fn test_select_with_limit_sqlite() {
73        let b = sqlite();
74        assert_eq!(b.select_limit("users", 10), "SELECT * FROM users LIMIT 10;");
75    }
76    #[test]
77    pub(super) fn test_select_with_limit_mssql() {
78        let b = mssql();
79        assert_eq!(b.select_limit("users", 5), "SELECT TOP 5 * FROM users;");
80    }
81    #[test]
82    pub(super) fn test_select_with_where() {
83        let b = pg();
84        let stmt = SQLStmt::Select {
85            cols: vec!["id".to_string(), "name".to_string()],
86            from: "users".to_string(),
87            where_: Some(SQLExpr::BinOp(
88                Box::new(SQLExpr::Column("id".to_string())),
89                "=".to_string(),
90                Box::new(SQLExpr::Literal("42".to_string())),
91            )),
92            limit: None,
93        };
94        let out = b.emit_stmt(&stmt);
95        assert!(out.contains("WHERE (id = 42)"));
96        assert!(out.contains("SELECT id, name FROM users"));
97    }
98    #[test]
99    pub(super) fn test_insert_stmt() {
100        let b = sqlite();
101        let stmt = SQLStmt::Insert {
102            table: "users".to_string(),
103            values: vec![
104                SQLExpr::Literal("1".to_string()),
105                SQLExpr::Literal("'Alice'".to_string()),
106            ],
107        };
108        assert_eq!(b.emit_stmt(&stmt), "INSERT INTO users VALUES (1, 'Alice');");
109    }
110    #[test]
111    pub(super) fn test_update_stmt() {
112        let b = mysql();
113        let stmt = SQLStmt::Update {
114            table: "users".to_string(),
115            set_col: "name".to_string(),
116            set_val: SQLExpr::Literal("'Bob'".to_string()),
117            where_: Some(SQLExpr::BinOp(
118                Box::new(SQLExpr::Column("id".to_string())),
119                "=".to_string(),
120                Box::new(SQLExpr::Literal("1".to_string())),
121            )),
122        };
123        let out = b.emit_stmt(&stmt);
124        assert!(out.contains("UPDATE users SET name = 'Bob'"));
125        assert!(out.contains("WHERE (id = 1)"));
126    }
127    #[test]
128    pub(super) fn test_delete_stmt() {
129        let b = sqlite();
130        let stmt = SQLStmt::Delete {
131            table: "logs".to_string(),
132            where_: Some(SQLExpr::BinOp(
133                Box::new(SQLExpr::Column("age".to_string())),
134                ">".to_string(),
135                Box::new(SQLExpr::Literal("30".to_string())),
136            )),
137        };
138        let out = b.emit_stmt(&stmt);
139        assert_eq!(out, "DELETE FROM logs WHERE (age > 30);");
140    }
141    #[test]
142    pub(super) fn test_drop_table() {
143        let b = pg();
144        assert_eq!(
145            b.emit_stmt(&SQLStmt::DropTable("old_table".to_string())),
146            "DROP TABLE IF EXISTS old_table;"
147        );
148    }
149    #[test]
150    pub(super) fn test_create_table_stmt() {
151        let b = sqlite();
152        let table = SQLTable {
153            name: "items".to_string(),
154            columns: vec![
155                SQLColumn {
156                    name: "id".to_string(),
157                    ty: SQLType::Integer,
158                    not_null: true,
159                    primary_key: true,
160                },
161                SQLColumn {
162                    name: "label".to_string(),
163                    ty: SQLType::Text,
164                    not_null: true,
165                    primary_key: false,
166                },
167            ],
168        };
169        let out = b.create_table_stmt(&table);
170        assert!(out.contains("CREATE TABLE IF NOT EXISTS items"));
171        assert!(out.contains("id INTEGER PRIMARY KEY NOT NULL"));
172        assert!(out.contains("label TEXT NOT NULL"));
173    }
174    #[test]
175    pub(super) fn test_schema_for_type() {
176        let b = pg();
177        let schema = b.schema_for_type("User");
178        assert_eq!(schema.name, "user");
179        assert_eq!(schema.columns.len(), 3);
180        assert_eq!(schema.columns[0].name, "id");
181        assert!(schema.columns[0].primary_key);
182    }
183    #[test]
184    pub(super) fn test_func_call_expr() {
185        let b = sqlite();
186        let expr = SQLExpr::FuncCall("COUNT".to_string(), vec![SQLExpr::Column("*".to_string())]);
187        assert_eq!(b.emit_expr(&expr), "COUNT(*)");
188    }
189    #[test]
190    pub(super) fn test_insert_placeholders_sqlite() {
191        let b = sqlite();
192        let out = b.insert_placeholders("events", 3);
193        assert_eq!(out, "INSERT INTO events VALUES (?, ?, ?);");
194    }
195    #[test]
196    pub(super) fn test_insert_placeholders_postgresql() {
197        let b = pg();
198        let out = b.insert_placeholders("events", 3);
199        assert_eq!(out, "INSERT INTO events VALUES ($1, $2, $3);");
200    }
201    #[test]
202    pub(super) fn test_nested_binop_expr() {
203        let b = sqlite();
204        let expr = SQLExpr::BinOp(
205            Box::new(SQLExpr::BinOp(
206                Box::new(SQLExpr::Column("a".to_string())),
207                "+".to_string(),
208                Box::new(SQLExpr::Literal("1".to_string())),
209            )),
210            "*".to_string(),
211            Box::new(SQLExpr::Literal("2".to_string())),
212        );
213        assert_eq!(b.emit_expr(&expr), "((a + 1) * 2)");
214    }
215}
216#[cfg(test)]
217mod sql_extended_tests {
218    use super::*;
219    #[test]
220    pub(super) fn test_select_builder() {
221        let sql = SQLSelectBuilder::new()
222            .column("id")
223            .column("name")
224            .from_table("users")
225            .where_cond("age > 18")
226            .order_asc("name")
227            .limit(10)
228            .build();
229        assert!(sql.contains("SELECT id, name FROM users"));
230        assert!(sql.contains("WHERE age > 18"));
231        assert!(sql.contains("ORDER BY name ASC"));
232        assert!(sql.contains("LIMIT 10"));
233    }
234    #[test]
235    pub(super) fn test_insert_builder() {
236        let sql = SQLInsertBuilder::new("users")
237            .column("name")
238            .column("email")
239            .values(vec![
240                "'Alice'".to_string(),
241                "'alice@example.com'".to_string(),
242            ])
243            .build();
244        assert!(sql.contains("INSERT INTO users"));
245        assert!(sql.contains("(name, email)"));
246        assert!(sql.contains("VALUES"));
247    }
248    #[test]
249    pub(super) fn test_update_builder() {
250        let sql = SQLUpdateBuilder::new("users")
251            .set("email", "'new@example.com'")
252            .where_cond("id = 1")
253            .build();
254        assert!(sql.contains("UPDATE users SET"));
255        assert!(sql.contains("email = 'new@example.com'"));
256        assert!(sql.contains("WHERE id = 1"));
257    }
258    #[test]
259    pub(super) fn test_delete_builder() {
260        let sql = SQLDeleteBuilder::new("sessions")
261            .where_cond("expires_at < NOW()")
262            .build();
263        assert!(sql.contains("DELETE FROM sessions"));
264        assert!(sql.contains("WHERE expires_at < NOW()"));
265    }
266    #[test]
267    pub(super) fn test_create_table() {
268        let sql = SQLCreateTableBuilder::new("orders")
269            .if_not_exists()
270            .column(
271                SQLColumnDef::new("id", SQLType::Integer)
272                    .primary_key()
273                    .auto_increment(),
274            )
275            .column(SQLColumnDef::new("user_id", SQLType::Integer).not_null())
276            .column(SQLColumnDef::new("total", SQLType::Real).not_null())
277            .build(&SQLDialect::SQLite);
278        assert!(sql.contains("CREATE TABLE IF NOT EXISTS orders"));
279        assert!(sql.contains("id INTEGER PRIMARY KEY"));
280        assert!(sql.contains("user_id INTEGER NOT NULL"));
281    }
282    #[test]
283    pub(super) fn test_index_builder() {
284        let sql = SQLIndexBuilder::new("idx_email", "users")
285            .on_column("email")
286            .unique()
287            .build();
288        assert!(sql.contains("CREATE UNIQUE INDEX idx_email ON users (email)"));
289    }
290    #[test]
291    pub(super) fn test_migration() {
292        let m = SQLMigration::new(1, "Add users table")
293            .up("CREATE TABLE users (id INTEGER PRIMARY KEY)")
294            .down("DROP TABLE users");
295        assert!(m.emit_up().contains("-- Migration v1"));
296        assert!(m.emit_down().contains("-- Rollback v1"));
297        assert!(m.emit_down().contains("DROP TABLE users"));
298    }
299    #[test]
300    pub(super) fn test_migration_runner() {
301        let mut runner = SQLMigrationRunner::new();
302        runner.add_migration(SQLMigration::new(1, "Init").up("CREATE TABLE t1 (id INT)"));
303        runner.add_migration(
304            SQLMigration::new(2, "Add col").up("ALTER TABLE t1 ADD COLUMN name TEXT"),
305        );
306        assert_eq!(runner.pending_migrations().len(), 2);
307        let sql = runner.emit_pending_sql();
308        assert!(sql.contains("Migration v1"));
309        assert!(sql.contains("Migration v2"));
310    }
311    #[test]
312    pub(super) fn test_view_builder() {
313        let select = SQLSelectBuilder::new().column("*").from_table("users");
314        let view = SQLViewBuilder::new("active_users", select)
315            .or_replace()
316            .build();
317        assert!(view.contains("CREATE OR REPLACE VIEW active_users AS SELECT"));
318    }
319    #[test]
320    pub(super) fn test_window_function() {
321        let wf = SQLWindowFunction::new("ROW_NUMBER()")
322            .partition_by("department")
323            .order_asc("salary");
324        let s = wf.emit();
325        assert!(s.contains("ROW_NUMBER()"));
326        assert!(s.contains("PARTITION BY department"));
327        assert!(s.contains("ORDER BY salary ASC"));
328    }
329    #[test]
330    pub(super) fn test_with_query() {
331        let inner = SQLSelectBuilder::new().column("id").from_table("employees");
332        let cte = SQLCommonTableExpression::new("emp_cte", inner);
333        let outer = SQLSelectBuilder::new().column("*").from_table("emp_cte");
334        let with_q = SQLWithQuery::new(outer).with(cte).build();
335        assert!(with_q.contains("WITH emp_cte AS"));
336        assert!(with_q.contains("FROM emp_cte"));
337    }
338    #[test]
339    pub(super) fn test_transaction() {
340        let tx = SQLTransactionBuilder::new()
341            .isolation(SQLIsolationLevel::Serializable)
342            .add_statement("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
343            .add_statement("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
344            .build();
345        assert!(tx.contains("SERIALIZABLE"));
346        assert!(tx.contains("BEGIN;"));
347        assert!(tx.contains("COMMIT;"));
348    }
349    #[test]
350    pub(super) fn test_join_emit() {
351        let j = SQLJoin::inner("orders").alias("o").on("u.id = o.user_id");
352        let s = j.emit();
353        assert!(s.contains("INNER JOIN orders AS o ON u.id = o.user_id"));
354    }
355    #[test]
356    pub(super) fn test_analyze_builder() {
357        let sql = SQLAnalyzeBuilder::new("users")
358            .verbose()
359            .column("email")
360            .build();
361        assert!(sql.contains("ANALYZE VERBOSE users"));
362        assert!(sql.contains("(email)"));
363    }
364}
365#[cfg(test)]
366mod sql_schema_tests {
367    use super::*;
368    #[test]
369    pub(super) fn test_table_info() {
370        let mut info = SQLTableInfo::new("users");
371        info.columns.push(SQLColumnInfo {
372            name: "id".to_string(),
373            ordinal_position: 1,
374            data_type: "INTEGER".to_string(),
375            is_nullable: false,
376            column_default: None,
377            is_primary_key: true,
378            is_unique: true,
379        });
380        info.columns.push(SQLColumnInfo {
381            name: "email".to_string(),
382            ordinal_position: 2,
383            data_type: "TEXT".to_string(),
384            is_nullable: true,
385            column_default: None,
386            is_primary_key: false,
387            is_unique: true,
388        });
389        assert_eq!(info.primary_key_columns().len(), 1);
390        assert_eq!(info.nullable_columns().len(), 1);
391        let q = info.emit_describe_query();
392        assert!(q.contains("users"));
393    }
394    #[test]
395    pub(super) fn test_schema_inspector() {
396        let inspector = SQLSchemaInspector::new(SQLDialect::SQLite);
397        let q = inspector.list_tables_query();
398        assert!(q.contains("sqlite_master"));
399        let col_q = inspector.column_info_query("users");
400        assert!(col_q.contains("PRAGMA"));
401        let idx_q = inspector.index_info_query("users");
402        assert!(idx_q.contains("PRAGMA"));
403    }
404    #[test]
405    pub(super) fn test_query_plan() {
406        let mut plan = SQLQueryPlan::new();
407        plan.add_node(SQLQueryPlanNode::SeqScan {
408            table: "users".to_string(),
409            cost: 100.0,
410            rows: 1000,
411        });
412        plan.add_node(SQLQueryPlanNode::IndexScan {
413            table: "orders".to_string(),
414            index: "idx_user_id".to_string(),
415            cost: 10.0,
416            rows: 10,
417        });
418        assert!(plan.has_seq_scan());
419        assert!(plan.has_index_scan());
420        assert!((plan.total_cost - 110.0).abs() < 0.01);
421        let desc = plan.describe();
422        assert!(desc.contains("Seq Scan"));
423        assert!(desc.contains("Index Scan"));
424    }
425    #[test]
426    pub(super) fn test_query_optimizer() {
427        let mut opt = SQLQueryOptimizer::new();
428        opt.add_table_stats("users", 100000);
429        opt.add_table_stats("orders", 5000000);
430        let cost = opt.estimate_join_cost("users", "orders");
431        assert!(cost > 0.0);
432        let select = SQLSelectBuilder::new()
433            .column("*")
434            .from_table("users")
435            .where_cond("id = 1");
436        let suggestions = opt.suggest_indexes(&select);
437        assert!(suggestions.iter().any(|s| s.contains("idx_users_id")));
438    }
439    #[test]
440    pub(super) fn test_query_formatter() {
441        let formatter = SQLQueryFormatter::new();
442        let sql = "SELECT * FROM users WHERE id = 1 ORDER BY name";
443        let formatted = formatter.format(sql);
444        assert!(!formatted.is_empty());
445    }
446}
447#[cfg(test)]
448mod sql_proc_tests {
449    use super::*;
450    #[test]
451    pub(super) fn test_stored_function() {
452        let func = SQLStoredProcedure::function("get_user_count", "INTEGER")
453            .param("dept_id", "INTEGER")
454            .body("BEGIN RETURN (SELECT COUNT(*) FROM users WHERE department_id = dept_id); END;")
455            .emit();
456        assert!(func.contains("CREATE OR REPLACE FUNCTION get_user_count"));
457        assert!(func.contains("RETURNS INTEGER"));
458        assert!(func.contains("dept_id INTEGER"));
459    }
460    #[test]
461    pub(super) fn test_trigger() {
462        let trigger = SQLTrigger::new("audit_users", "users", "log_changes")
463            .after()
464            .on_insert()
465            .on_update()
466            .emit();
467        assert!(trigger.contains("CREATE OR REPLACE TRIGGER audit_users"));
468        assert!(trigger.contains("AFTER INSERT OR UPDATE ON users"));
469        assert!(trigger.contains("EXECUTE FUNCTION log_changes()"));
470    }
471    #[test]
472    pub(super) fn test_sequence() {
473        let seq = SQLSequenceBuilder::new("user_id_seq")
474            .start_with(1000)
475            .increment_by(1)
476            .max(i64::MAX)
477            .build();
478        assert!(seq.contains("CREATE SEQUENCE user_id_seq"));
479        assert!(seq.contains("START WITH 1000"));
480    }
481    #[test]
482    pub(super) fn test_procedure() {
483        let proc = SQLStoredProcedure::procedure("cleanup_sessions")
484            .body("BEGIN DELETE FROM sessions WHERE expires_at < NOW(); END;")
485            .emit();
486        assert!(proc.contains("CREATE OR REPLACE PROCEDURE cleanup_sessions"));
487        assert!(!proc.contains("RETURNS"));
488    }
489}
490#[allow(dead_code)]
491pub fn sql_escape_string(s: &str) -> String {
492    format!("'{}'", s.replace('\'', "''"))
493}
494#[allow(dead_code)]
495pub fn sql_is_reserved(word: &str) -> bool {
496    const RESERVED: &[&str] = &[
497        "SELECT", "FROM", "WHERE", "TABLE", "INDEX", "CREATE", "DROP", "INSERT", "UPDATE",
498        "DELETE", "JOIN", "GROUP", "ORDER", "BY", "HAVING", "LIMIT", "OFFSET", "ON", "AS", "AND",
499        "OR", "NOT", "NULL", "TRUE", "FALSE",
500    ];
501    RESERVED.iter().any(|r| r.eq_ignore_ascii_case(word))
502}
503#[allow(dead_code)]
504pub fn sql_quote_if_needed(ident: &str) -> String {
505    if sql_is_reserved(ident) || ident.contains(' ') || ident.contains('-') {
506        format!(r#""{}""#, ident)
507    } else {
508        ident.to_string()
509    }
510}
511#[allow(dead_code)]
512pub fn sql_dialect_name(dialect: &SQLDialect) -> &'static str {
513    match dialect {
514        SQLDialect::PostgreSQL => "PostgreSQL",
515        SQLDialect::MySQL => "MySQL",
516        SQLDialect::SQLite => "SQLite",
517        SQLDialect::MSSQL => "Microsoft SQL Server",
518    }
519}
520#[allow(dead_code)]
521pub fn sql_supports_returning(dialect: &SQLDialect) -> bool {
522    matches!(dialect, SQLDialect::PostgreSQL | SQLDialect::SQLite)
523}
524#[allow(dead_code)]
525pub fn sql_auto_increment_syntax(dialect: &SQLDialect) -> &'static str {
526    match dialect {
527        SQLDialect::PostgreSQL => "SERIAL",
528        SQLDialect::MySQL => "AUTO_INCREMENT",
529        SQLDialect::SQLite => "AUTOINCREMENT",
530        SQLDialect::MSSQL => "IDENTITY(1,1)",
531    }
532}
533#[allow(dead_code)]
534pub fn sql_current_timestamp(dialect: &SQLDialect) -> &'static str {
535    match dialect {
536        SQLDialect::MySQL => "NOW()",
537        SQLDialect::SQLite => "datetime('now')",
538        _ => "CURRENT_TIMESTAMP",
539    }
540}
541#[allow(dead_code)]
542pub fn sql_string_concat(dialect: &SQLDialect, a: &str, b: &str) -> String {
543    match dialect {
544        SQLDialect::MySQL => format!("CONCAT({}, {})", a, b),
545        _ => format!("{} || {}", a, b),
546    }
547}
548#[allow(dead_code)]
549pub fn sql_limit_offset(dialect: &SQLDialect, limit: u64, offset: u64) -> String {
550    match dialect {
551        SQLDialect::MSSQL | SQLDialect::SQLite => {
552            format!("OFFSET {} ROWS FETCH NEXT {} ROWS ONLY", offset, limit)
553        }
554        _ => format!("LIMIT {} OFFSET {}", limit, offset),
555    }
556}
557#[cfg(test)]
558mod sql_util_tests {
559    use super::*;
560    #[test]
561    pub(super) fn test_sql_escape_string() {
562        let s = sql_escape_string("it's a test");
563        assert_eq!(s, "'it''s a test'");
564    }
565    #[test]
566    pub(super) fn test_sql_is_reserved() {
567        assert!(sql_is_reserved("SELECT"));
568        assert!(sql_is_reserved("select"));
569        assert!(!sql_is_reserved("username"));
570    }
571    #[test]
572    pub(super) fn test_sql_quote_if_needed() {
573        assert_eq!(sql_quote_if_needed("username"), "username");
574        assert!(sql_quote_if_needed("SELECT").starts_with('"'));
575    }
576    #[test]
577    pub(super) fn test_sql_dialect_name() {
578        assert_eq!(sql_dialect_name(&SQLDialect::PostgreSQL), "PostgreSQL");
579        assert_eq!(sql_dialect_name(&SQLDialect::SQLite), "SQLite");
580    }
581    #[test]
582    pub(super) fn test_sql_supports_returning() {
583        assert!(sql_supports_returning(&SQLDialect::PostgreSQL));
584        assert!(sql_supports_returning(&SQLDialect::SQLite));
585        assert!(!sql_supports_returning(&SQLDialect::MySQL));
586    }
587    #[test]
588    pub(super) fn test_sql_limit_offset() {
589        let pg = sql_limit_offset(&SQLDialect::PostgreSQL, 10, 20);
590        assert_eq!(pg, "LIMIT 10 OFFSET 20");
591        let ms = sql_limit_offset(&SQLDialect::MSSQL, 10, 20);
592        assert!(ms.contains("FETCH NEXT 10 ROWS"));
593    }
594    #[test]
595    pub(super) fn test_sql_concat() {
596        let pg = sql_string_concat(&SQLDialect::PostgreSQL, "first_name", "last_name");
597        assert!(pg.contains("||"));
598        let mysql = sql_string_concat(&SQLDialect::MySQL, "first_name", "last_name");
599        assert!(mysql.starts_with("CONCAT"));
600    }
601}
602#[allow(dead_code)]
603pub const SQL_BACKEND_VERSION: &str = "1.0.0";
604#[allow(dead_code)]
605pub fn sql_version() -> &'static str {
606    SQL_BACKEND_VERSION
607}
608#[cfg(test)]
609mod sql_alter_tests {
610    use super::*;
611    #[test]
612    pub(super) fn test_type_mapper() {
613        let mapper = SQLTypeMapper::new(SQLDialect::PostgreSQL, SQLDialect::MySQL);
614        assert_eq!(mapper.map_integer(), "INT");
615        assert_eq!(mapper.map_boolean(), "TINYINT(1)");
616        assert_eq!(mapper.map_json(), "JSON");
617    }
618    #[test]
619    pub(super) fn test_alter_table() {
620        let stmts = SQLAlterTableBuilder::new("users")
621            .add_column(SQLColumnDef::new("phone", SQLType::Text).not_null())
622            .drop_column("legacy_field")
623            .rename_column("email", "email_address")
624            .build(&SQLDialect::PostgreSQL);
625        assert_eq!(stmts.len(), 3);
626        assert!(stmts[0].contains("ADD COLUMN phone"));
627        assert!(stmts[1].contains("DROP COLUMN legacy_field"));
628        assert!(stmts[2].contains("RENAME COLUMN email TO email_address"));
629    }
630}