1use 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}