1use sea_query::{Alias, ForeignKey, Index, Table, TableCreateStatement};
2
3use vespertide_core::{ColumnDef, ColumnType, ComplexColumnType, TableConstraint};
4
5use super::helpers::{
6 build_create_enum_type_sql, build_schema_statement, build_sea_column_def_with_table,
7 collect_sqlite_enum_check_clauses, to_sea_fk_action,
8};
9use super::types::{BuiltQuery, DatabaseBackend, RawSql};
10use crate::error::QueryError;
11
12pub(crate) fn build_create_table_for_backend(
13 backend: DatabaseBackend,
14 table: &str,
15 columns: &[ColumnDef],
16 constraints: &[TableConstraint],
17) -> TableCreateStatement {
18 let mut stmt = Table::create().table(Alias::new(table)).to_owned();
19 add_create_table_columns(&mut stmt, backend, table, columns, constraints);
20 add_create_table_constraints(&mut stmt, backend, table, columns, constraints);
21 stmt
22}
23
24fn add_create_table_columns(
25 stmt: &mut TableCreateStatement,
26 backend: DatabaseBackend,
27 table: &str,
28 columns: &[ColumnDef],
29 constraints: &[TableConstraint],
30) {
31 let has_table_primary_key = constraints
32 .iter()
33 .any(|c| matches!(c, TableConstraint::PrimaryKey { .. }));
34 let auto_increment_columns = collect_auto_increment_columns(constraints);
35
36 for column in columns {
37 let mut col = build_sea_column_def_with_table(backend, table, column);
38 if column.primary_key.is_some() && !has_table_primary_key {
39 col.primary_key();
40 }
41 if auto_increment_columns.contains(column.name.as_str())
42 && column.r#type.supports_auto_increment()
43 {
44 if matches!(backend, DatabaseBackend::Sqlite) {
45 col.primary_key();
46 }
47 col.auto_increment();
48 }
49 *stmt = stmt.col(col).to_owned();
50 }
51}
52
53fn collect_auto_increment_columns(
54 constraints: &[TableConstraint],
55) -> std::collections::HashSet<&str> {
56 constraints
57 .iter()
58 .filter_map(|c| {
59 if let TableConstraint::PrimaryKey {
60 columns: pk_cols,
61 auto_increment: true,
62 ..
63 } = c
64 {
65 Some(pk_cols.iter().map(AsRef::as_ref).collect::<Vec<_>>())
66 } else {
67 None
68 }
69 })
70 .flatten()
71 .collect()
72}
73
74fn add_create_table_constraints(
75 stmt: &mut TableCreateStatement,
76 backend: DatabaseBackend,
77 table: &str,
78 columns: &[ColumnDef],
79 constraints: &[TableConstraint],
80) {
81 for constraint in constraints {
82 match constraint {
83 TableConstraint::PrimaryKey {
84 columns: pk_cols,
85 auto_increment,
86 ..
87 } => add_primary_key_constraint(stmt, backend, columns, pk_cols, *auto_increment),
88 TableConstraint::Unique {
89 name,
90 columns: unique_cols,
91 ..
92 } => add_mysql_unique_constraint(stmt, backend, table, unique_cols, name.as_deref()),
93 TableConstraint::ForeignKey { .. } => {
94 add_foreign_key_constraint(stmt, table, constraint);
95 }
96 TableConstraint::Check { name, expr, .. } => {
97 let _ = (name, expr);
98 }
99 TableConstraint::Index { .. } => {}
100 _ => {
101 unreachable!("TableConstraint is #[non_exhaustive]; all variants are matched above")
102 }
103 }
104 }
105}
106
107fn add_primary_key_constraint(
108 stmt: &mut TableCreateStatement,
109 backend: DatabaseBackend,
110 columns: &[ColumnDef],
111 pk_cols: &[impl AsRef<str>],
112 auto_increment: bool,
113) {
114 if should_skip_sqlite_auto_increment_pk(backend, columns, pk_cols, auto_increment) {
115 return;
116 }
117
118 let mut pk_idx = Index::create();
119 for c in pk_cols {
120 pk_idx.col(Alias::new(c.as_ref()));
121 }
122 *stmt = stmt.primary_key(&mut pk_idx).to_owned();
123}
124
125fn should_skip_sqlite_auto_increment_pk(
126 backend: DatabaseBackend,
127 columns: &[ColumnDef],
128 pk_cols: &[impl AsRef<str>],
129 auto_increment: bool,
130) -> bool {
131 matches!(backend, DatabaseBackend::Sqlite)
132 && auto_increment
133 && pk_cols.iter().all(|col_name| {
134 columns
135 .iter()
136 .find(|c| c.name == col_name.as_ref())
137 .is_some_and(|c| c.r#type.supports_auto_increment())
138 })
139}
140
141fn add_mysql_unique_constraint(
142 stmt: &mut TableCreateStatement,
143 backend: DatabaseBackend,
144 table: &str,
145 unique_cols: &[impl AsRef<str>],
146 name: Option<&str>,
147) {
148 if !matches!(backend, DatabaseBackend::MySql) {
149 return;
150 }
151
152 let index_name = super::helpers::build_unique_constraint_name(table, unique_cols, name);
153 let mut idx = Index::create().name(&index_name).unique().to_owned();
154 for col in unique_cols {
155 idx.col(Alias::new(col.as_ref()));
156 }
157 *stmt = stmt.index(&mut idx).to_owned();
158}
159
160fn add_foreign_key_constraint(
161 stmt: &mut TableCreateStatement,
162 table: &str,
163 constraint: &TableConstraint,
164) {
165 if let TableConstraint::ForeignKey {
166 name,
167 columns: fk_cols,
168 ref_table,
169 ref_columns,
170 on_delete,
171 on_update,
172 ..
173 } = constraint
174 {
175 let fk_name = super::helpers::build_foreign_key_name(table, fk_cols, name.as_deref());
176 let mut fk = ForeignKey::create().name(&fk_name).to_owned();
177 fk.from_tbl(Alias::new(table));
178 for col in fk_cols {
179 fk.from_col(Alias::new(col));
180 }
181 fk.to_tbl(Alias::new(ref_table));
182 for col in ref_columns {
183 fk.to_col(Alias::new(col));
184 }
185 if let Some(action) = on_delete {
186 fk.on_delete(to_sea_fk_action(action));
187 }
188 if let Some(action) = on_update {
189 fk.on_update(to_sea_fk_action(action));
190 }
191 *stmt = stmt.foreign_key(&mut fk).to_owned();
192 }
193}
194
195pub fn build_create_table(
196 backend: DatabaseBackend,
197 table: &str,
198 columns: &[ColumnDef],
199 constraints: &[TableConstraint],
200) -> Result<Vec<BuiltQuery>, QueryError> {
201 let table_def = vespertide_core::TableDef {
204 description: None,
205 name: table.into(),
206 columns: columns.to_vec(),
207 constraints: constraints.to_vec(),
208 };
209 let normalized = table_def.normalize().map_err(|e| {
210 QueryError::SchemaError(format!("Failed to normalize table '{table}': {e}"))
211 })?;
212
213 let columns = &normalized.columns;
215 let constraints = &normalized.constraints;
216
217 let mut queries = Vec::new();
218
219 let mut created_enums = std::collections::HashSet::new();
222 for column in columns {
223 if let ColumnType::Complex(ComplexColumnType::Enum { name, .. }) = &column.r#type
224 && created_enums.insert(name.clone())
225 && let Some(create_type_sql) = build_create_enum_type_sql(table, &column.r#type)
226 {
227 queries.push(BuiltQuery::Raw(create_type_sql));
228 }
229 }
230
231 let (table_constraints, unique_constraints): (Vec<&TableConstraint>, Vec<&TableConstraint>) =
234 constraints
235 .iter()
236 .partition(|c| !matches!(c, TableConstraint::Unique { .. }));
237
238 let create_table_stmt = if matches!(backend, DatabaseBackend::MySql) {
242 build_create_table_for_backend(backend, table, columns, constraints)
243 } else {
244 let table_constraints_owned: Vec<TableConstraint> =
246 table_constraints.iter().copied().cloned().collect();
247 build_create_table_for_backend(backend, table, columns, &table_constraints_owned)
248 };
249
250 if matches!(backend, DatabaseBackend::Sqlite) {
252 let enum_check_clauses = collect_sqlite_enum_check_clauses(table, columns);
253 if enum_check_clauses.is_empty() {
254 queries.push(BuiltQuery::CreateTable(Box::new(create_table_stmt)));
255 } else {
256 let base_sql = build_schema_statement(&create_table_stmt, backend);
258 let mut modified_sql = base_sql;
259 if let Some(pos) = modified_sql.rfind(')') {
260 let check_sql = enum_check_clauses.join(", ");
261 modified_sql.insert_str(pos, &format!(", {check_sql}"));
262 }
263 queries.push(BuiltQuery::Raw(RawSql::per_backend(
264 modified_sql.clone(),
265 modified_sql.clone(),
266 modified_sql,
267 )));
268 }
269 } else {
270 queries.push(BuiltQuery::CreateTable(Box::new(create_table_stmt)));
271 }
272
273 if matches!(backend, DatabaseBackend::Postgres | DatabaseBackend::Sqlite) {
275 for constraint in unique_constraints {
276 if let TableConstraint::Unique {
277 name,
278 columns: unique_cols,
279 ..
280 } = constraint
281 {
282 let index_name = super::helpers::build_unique_constraint_name(
284 table,
285 unique_cols,
286 name.as_deref(),
287 );
288 let mut idx = Index::create()
289 .table(Alias::new(table))
290 .name(&index_name)
291 .unique()
292 .to_owned();
293 for col in unique_cols {
294 idx.col(Alias::new(col));
295 }
296 queries.push(BuiltQuery::CreateIndex(Box::new(idx)));
297 }
298 }
299 }
300
301 for constraint in constraints {
303 if let TableConstraint::Index {
304 name,
305 columns: index_cols,
306 } = constraint
307 {
308 let index_name = super::helpers::build_index_name(table, index_cols, name.as_deref());
310 let mut idx = Index::create()
311 .table(Alias::new(table))
312 .name(&index_name)
313 .to_owned();
314 for col in index_cols {
315 idx.col(Alias::new(col));
316 }
317 queries.push(BuiltQuery::CreateIndex(Box::new(idx)));
318 }
319 }
320
321 Ok(queries)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::test_support::col;
328 use insta::{assert_snapshot, with_settings};
329 use rstest::rstest;
330 use vespertide_core::{ColumnType, EnumValues, SimpleColumnType};
331
332 fn join_queries(queries: &[BuiltQuery], backend: DatabaseBackend, separator: &str) -> String {
333 let mut sql = String::new();
334 for (index, query) in queries.iter().enumerate() {
335 if index > 0 {
336 sql.push_str(separator);
337 }
338 sql.push_str(&query.build(backend));
339 }
340 sql
341 }
342
343 #[rstest]
344 #[case::create_table_postgres(
345 "create_table_postgres",
346 DatabaseBackend::Postgres,
347 &["CREATE TABLE \"users\" ( \"id\" integer )"]
348 )]
349 #[case::create_table_mysql(
350 "create_table_mysql",
351 DatabaseBackend::MySql,
352 &["CREATE TABLE `users` ( `id` int )"]
353 )]
354 #[case::create_table_sqlite(
355 "create_table_sqlite",
356 DatabaseBackend::Sqlite,
357 &["CREATE TABLE \"users\" ( \"id\" integer )"]
358 )]
359 fn test_create_table(
360 #[case] title: &str,
361 #[case] backend: DatabaseBackend,
362 #[case] expected: &[&str],
363 ) {
364 let result = build_create_table(
365 backend,
366 "users",
367 &[col("id", ColumnType::Simple(SimpleColumnType::Integer))],
368 &[],
369 )
370 .unwrap();
371 let sql = join_queries(&result, backend, "\n");
372 for exp in expected {
373 assert!(
374 sql.contains(exp),
375 "Expected SQL to contain '{exp}', got: {sql}"
376 );
377 }
378
379 with_settings!({ snapshot_suffix => format!("create_table_{}", title) }, {
380 assert_snapshot!(sql);
381 });
382 }
383
384 #[rstest]
385 #[case::inline_unique_postgres(DatabaseBackend::Postgres)]
386 #[case::inline_unique_mysql(DatabaseBackend::MySql)]
387 #[case::inline_unique_sqlite(DatabaseBackend::Sqlite)]
388 fn test_create_table_with_inline_unique(#[case] backend: DatabaseBackend) {
389 use vespertide_core::schema::str_or_bool::StrOrBoolOrArray;
392
393 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
394 email_col.unique = Some(StrOrBoolOrArray::Bool(true));
395
396 let result = build_create_table(
397 backend,
398 "users",
399 &[
400 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
401 email_col,
402 ],
403 &[],
405 )
406 .unwrap();
407 let sql = join_queries(&result, backend, "\n");
408
409 assert!(
411 sql.contains("UNIQUE") || sql.to_uppercase().contains("UNIQUE"),
412 "Normalized unique constraint should be in SQL, but not found: {sql}"
413 );
414 with_settings!({ snapshot_suffix => format!("create_table_with_inline_unique_{:?}", backend) }, {
415 assert_snapshot!(sql);
416 });
417 }
418
419 #[rstest]
420 #[case::table_level_unique_postgres(DatabaseBackend::Postgres)]
421 #[case::table_level_unique_mysql(DatabaseBackend::MySql)]
422 #[case::table_level_unique_sqlite(DatabaseBackend::Sqlite)]
423 fn test_create_table_with_table_level_unique(#[case] backend: DatabaseBackend) {
424 let result = build_create_table(
426 backend,
427 "users",
428 &[
429 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
430 col("email", ColumnType::Simple(SimpleColumnType::Text)),
431 ],
432 &[TableConstraint::Unique {
433 name: Some("uq_email".into()),
434 columns: vec!["email".into()],
435 strategy: vespertide_core::UniqueConstraintStrategy::DeleteDuplicates {
436 keep: vespertide_core::KeepPolicy::First,
437 },
438 }],
439 )
440 .unwrap();
441 let sql = join_queries(&result, backend, "\n");
442 assert!(sql.contains("CREATE TABLE"));
443 match backend {
445 DatabaseBackend::MySql => {
446 assert!(
447 sql.contains("UNIQUE"),
448 "MySQL should have UNIQUE in CREATE TABLE: {sql}"
449 );
450 }
451 _ => {
452 assert!(
454 sql.contains("CREATE UNIQUE INDEX"),
455 "Postgres/SQLite should have CREATE UNIQUE INDEX: {sql}"
456 );
457 }
458 }
459 with_settings!({ snapshot_suffix => format!("create_table_with_table_level_unique_{:?}", backend) }, {
460 assert_snapshot!(sql);
461 });
462 }
463
464 #[rstest]
465 #[case::table_level_unique_no_name_postgres(DatabaseBackend::Postgres)]
466 #[case::table_level_unique_no_name_mysql(DatabaseBackend::MySql)]
467 #[case::table_level_unique_no_name_sqlite(DatabaseBackend::Sqlite)]
468 fn test_create_table_with_table_level_unique_no_name(#[case] backend: DatabaseBackend) {
469 let result = build_create_table(
471 backend,
472 "users",
473 &[
474 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
475 col("email", ColumnType::Simple(SimpleColumnType::Text)),
476 ],
477 &[TableConstraint::Unique {
478 name: None,
479 columns: vec!["email".into()],
480 strategy: vespertide_core::UniqueConstraintStrategy::DeleteDuplicates {
481 keep: vespertide_core::KeepPolicy::First,
482 },
483 }],
484 )
485 .unwrap();
486 let sql = join_queries(&result, backend, "\n");
487 assert!(sql.contains("CREATE TABLE"));
488 match backend {
490 DatabaseBackend::MySql => {
491 assert!(
492 sql.contains("UNIQUE"),
493 "MySQL should have UNIQUE in CREATE TABLE: {sql}"
494 );
495 }
496 _ => {
497 assert!(
499 sql.contains("CREATE UNIQUE INDEX"),
500 "Postgres/SQLite should have CREATE UNIQUE INDEX: {sql}"
501 );
502 }
503 }
504 with_settings!({ snapshot_suffix => format!("create_table_with_table_level_unique_no_name_{:?}", backend) }, {
505 assert_snapshot!(sql);
506 });
507 }
508
509 #[rstest]
510 #[case::postgres(DatabaseBackend::Postgres)]
511 #[case::mysql(DatabaseBackend::MySql)]
512 #[case::sqlite(DatabaseBackend::Sqlite)]
513 fn test_create_table_with_enum_column(#[case] backend: DatabaseBackend) {
514 let columns = vec![
516 ColumnDef {
517 name: "id".into(),
518 r#type: ColumnType::Simple(SimpleColumnType::Integer),
519 nullable: false,
520 default: None,
521 comment: None,
522 primary_key: None,
523 unique: None,
524 index: None,
525 foreign_key: None,
526 },
527 ColumnDef {
528 name: "status".into(),
529 r#type: ColumnType::Complex(ComplexColumnType::Enum {
530 name: "user_status".into(),
531 values: EnumValues::String(vec![
532 "active".into(),
533 "inactive".into(),
534 "pending".into(),
535 ]),
536 }),
537 nullable: false,
538 default: Some("'active'".into()),
539 comment: None,
540 primary_key: None,
541 unique: None,
542 index: None,
543 foreign_key: None,
544 },
545 ];
546 let constraints = vec![TableConstraint::PrimaryKey {
547 auto_increment: false,
548 columns: vec!["id".into()],
549 strategy: vespertide_core::PrimaryKeyAdditionStrategy::default(),
550 }];
551
552 let result = build_create_table(backend, "users", &columns, &constraints);
553 assert!(result.is_ok());
554 let queries = result.unwrap();
555 let sql = join_queries(&queries, backend, ";\n");
556
557 with_settings!({ snapshot_suffix => format!("create_table_with_enum_column_{:?}", backend) }, {
558 assert_snapshot!(sql);
559 });
560 }
561
562 #[rstest]
563 #[case::auto_increment_postgres(DatabaseBackend::Postgres)]
564 #[case::auto_increment_mysql(DatabaseBackend::MySql)]
565 #[case::auto_increment_sqlite(DatabaseBackend::Sqlite)]
566 fn test_create_table_with_auto_increment_primary_key(#[case] backend: DatabaseBackend) {
567 let columns = vec![ColumnDef {
569 name: "id".into(),
570 r#type: ColumnType::Simple(SimpleColumnType::Integer),
571 nullable: false,
572 default: None,
573 comment: None,
574 primary_key: None,
575 unique: None,
576 index: None,
577 foreign_key: None,
578 }];
579 let constraints = vec![TableConstraint::PrimaryKey {
580 auto_increment: true,
581 columns: vec!["id".into()],
582 strategy: vespertide_core::PrimaryKeyAdditionStrategy::default(),
583 }];
584
585 let result = build_create_table(backend, "users", &columns, &constraints);
586 assert!(result.is_ok());
587 let queries = result.unwrap();
588 let sql = join_queries(&queries, backend, ";\n");
589
590 match backend {
592 DatabaseBackend::Postgres => {
593 assert!(
598 sql.contains("SERIAL")
599 || sql.contains("serial")
600 || sql.contains("IDENTITY")
601 || sql.contains("identity"),
602 "PostgreSQL should use SERIAL or IDENTITY for auto_increment, got: {sql}"
603 );
604 }
605 DatabaseBackend::MySql => {
606 assert!(
607 sql.contains("AUTO_INCREMENT") || sql.contains("auto_increment"),
608 "MySQL should use AUTO_INCREMENT for auto_increment, got: {sql}"
609 );
610 }
611 DatabaseBackend::Sqlite => {
612 assert!(
613 sql.contains("AUTOINCREMENT") || sql.contains("autoincrement"),
614 "SQLite should use AUTOINCREMENT for auto_increment, got: {sql}"
615 );
616 }
617 }
618
619 with_settings!({ snapshot_suffix => format!("create_table_with_auto_increment_{:?}", backend) }, {
620 assert_snapshot!(sql);
621 });
622 }
623
624 #[rstest]
625 #[case::inline_auto_increment_postgres(DatabaseBackend::Postgres)]
626 #[case::inline_auto_increment_mysql(DatabaseBackend::MySql)]
627 #[case::inline_auto_increment_sqlite(DatabaseBackend::Sqlite)]
628 fn test_create_table_with_inline_auto_increment_primary_key(#[case] backend: DatabaseBackend) {
629 use vespertide_core::schema::primary_key::{PrimaryKeyDef, PrimaryKeySyntax};
631
632 let columns = vec![ColumnDef {
633 name: "id".into(),
634 r#type: ColumnType::Simple(SimpleColumnType::Integer),
635 nullable: false,
636 default: None,
637 comment: None,
638 primary_key: Some(PrimaryKeySyntax::Object(PrimaryKeyDef {
639 auto_increment: true,
640 })),
641 unique: None,
642 index: None,
643 foreign_key: None,
644 }];
645
646 let result = build_create_table(backend, "users", &columns, &[]);
647 assert!(result.is_ok());
648 let queries = result.unwrap();
649 let sql = join_queries(&queries, backend, ";\n");
650
651 match backend {
653 DatabaseBackend::Postgres => {
654 assert!(
659 sql.contains("SERIAL")
660 || sql.contains("serial")
661 || sql.contains("IDENTITY")
662 || sql.contains("identity"),
663 "PostgreSQL should use SERIAL or IDENTITY for auto_increment, got: {sql}"
664 );
665 }
666 DatabaseBackend::MySql => {
667 assert!(
668 sql.contains("AUTO_INCREMENT") || sql.contains("auto_increment"),
669 "MySQL should use AUTO_INCREMENT for auto_increment, got: {sql}"
670 );
671 }
672 DatabaseBackend::Sqlite => {
673 assert!(
674 sql.contains("AUTOINCREMENT") || sql.contains("autoincrement"),
675 "SQLite should use AUTOINCREMENT for auto_increment, got: {sql}"
676 );
677 }
678 }
679
680 with_settings!({ snapshot_suffix => format!("create_table_with_inline_auto_increment_{:?}", backend) }, {
681 assert_snapshot!(sql);
682 });
683 }
684
685 #[rstest]
688 #[case::timestamp_now_default_postgres(DatabaseBackend::Postgres)]
689 #[case::timestamp_now_default_mysql(DatabaseBackend::MySql)]
690 #[case::timestamp_now_default_sqlite(DatabaseBackend::Sqlite)]
691 #[rstest]
697 #[case::postgres(DatabaseBackend::Postgres)]
698 #[case::mysql(DatabaseBackend::MySql)]
699 #[case::sqlite(DatabaseBackend::Sqlite)]
700 fn test_create_table_check_and_index_constraints_are_skipped(#[case] backend: DatabaseBackend) {
701 let constraints = vec![
704 TableConstraint::PrimaryKey {
705 columns: vec!["id".into()],
706 auto_increment: false,
707 strategy: vespertide_core::PrimaryKeyAdditionStrategy::default(),
708 },
709 TableConstraint::Check {
710 name: "chk_age".into(),
711 expr: "age > 0".into(),
712 strategy: vespertide_core::CheckViolationStrategy::default(),
713 },
714 TableConstraint::Index {
715 name: Some("idx_age".into()),
716 columns: vec!["age".into()],
717 },
718 ];
719 let columns = vec![
720 ColumnDef {
721 name: "id".into(),
722 r#type: ColumnType::Simple(SimpleColumnType::Integer),
723 nullable: false,
724 default: None,
725 comment: None,
726 primary_key: None,
727 unique: None,
728 index: None,
729 foreign_key: None,
730 },
731 ColumnDef {
732 name: "age".into(),
733 r#type: ColumnType::Simple(SimpleColumnType::Integer),
734 nullable: false,
735 default: None,
736 comment: None,
737 primary_key: None,
738 unique: None,
739 index: None,
740 foreign_key: None,
741 },
742 ];
743 let queries = build_create_table(backend, "users", &columns, &constraints).unwrap();
744 let sql = queries
745 .iter()
746 .map(|q| q.build(backend))
747 .collect::<Vec<_>>()
748 .join("\n");
749 assert!(sql.contains("PRIMARY KEY"));
754 assert!(!sql.contains("CHECK (age > 0)"));
755 assert!(sql.contains("CREATE INDEX") || sql.contains("idx_age"));
758 }
759
760 #[rstest]
761 #[case::postgres(DatabaseBackend::Postgres)]
762 #[case::mysql(DatabaseBackend::MySql)]
763 #[case::sqlite(DatabaseBackend::Sqlite)]
764 fn test_create_table_with_timestamp_now_default(#[case] backend: DatabaseBackend) {
765 let columns = vec![
766 ColumnDef {
767 name: "id".into(),
768 r#type: ColumnType::Simple(SimpleColumnType::BigInt),
769 nullable: false,
770 default: None,
771 comment: None,
772 primary_key: None,
773 unique: None,
774 index: None,
775 foreign_key: None,
776 },
777 ColumnDef {
778 name: "created_at".into(),
779 r#type: ColumnType::Simple(SimpleColumnType::Timestamptz),
780 nullable: false,
781 default: Some("NOW()".into()), comment: None,
783 primary_key: None,
784 unique: None,
785 index: None,
786 foreign_key: None,
787 },
788 ];
789
790 let result = build_create_table(backend, "events", &columns, &[]);
791 assert!(result.is_ok(), "build_create_table failed: {result:?}");
792 let queries = result.unwrap();
793 let sql = join_queries(&queries, backend, "\n");
794
795 if matches!(backend, DatabaseBackend::Sqlite) {
797 assert!(
798 !sql.contains("NOW()"),
799 "SQLite should not contain NOW(), got: {sql}"
800 );
801 assert!(
802 sql.contains("CURRENT_TIMESTAMP"),
803 "SQLite should use CURRENT_TIMESTAMP, got: {sql}"
804 );
805 }
806
807 with_settings!({ snapshot_suffix => format!("create_table_with_timestamp_now_default_{:?}", backend) }, {
808 assert_snapshot!(sql);
809 });
810 }
811}