1use sqlmodel_core::{FieldInfo, InheritanceStrategy, Model, quote_ident};
4use std::marker::PhantomData;
5
6#[derive(Debug)]
8pub struct CreateTable<M: Model> {
9 if_not_exists: bool,
10 _marker: PhantomData<M>,
11}
12
13impl<M: Model> CreateTable<M> {
14 pub fn new() -> Self {
16 Self {
17 if_not_exists: false,
18 _marker: PhantomData,
19 }
20 }
21
22 pub fn if_not_exists(mut self) -> Self {
24 self.if_not_exists = true;
25 self
26 }
27
28 pub fn build(&self) -> String {
36 let inheritance = M::inheritance();
37
38 if inheritance.strategy == InheritanceStrategy::None
41 && inheritance.parent.is_some()
42 && inheritance.discriminator_value.is_some()
43 {
44 return String::new();
47 }
48
49 let mut sql = String::from("CREATE TABLE ");
50
51 if self.if_not_exists {
52 sql.push_str("IF NOT EXISTS ");
53 }
54
55 sql.push_str("e_ident(M::TABLE_NAME));
56 sql.push_str(" (\n");
57
58 let fields = M::fields();
59 let mut column_defs = Vec::new();
60 let mut constraints = Vec::new();
61
62 for field in fields {
63 column_defs.push(self.column_definition(field));
64
65 if field.unique && !field.primary_key {
67 let constraint_name = format!("uk_{}", field.column_name);
68 let constraint = format!(
69 "CONSTRAINT {} UNIQUE ({})",
70 quote_ident(&constraint_name),
71 quote_ident(field.column_name)
72 );
73 constraints.push(constraint);
74 }
75
76 if let Some(fk) = field.foreign_key {
77 let parts: Vec<&str> = fk.split('.').collect();
78 if parts.len() == 2 {
79 let constraint_name = format!("fk_{}_{}", M::TABLE_NAME, field.column_name);
80 let mut fk_sql = format!(
81 "CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {}({})",
82 quote_ident(&constraint_name),
83 quote_ident(field.column_name),
84 quote_ident(parts[0]),
85 quote_ident(parts[1])
86 );
87
88 if let Some(on_delete) = field.on_delete {
90 fk_sql.push_str(" ON DELETE ");
91 fk_sql.push_str(on_delete.as_sql());
92 }
93
94 if let Some(on_update) = field.on_update {
96 fk_sql.push_str(" ON UPDATE ");
97 fk_sql.push_str(on_update.as_sql());
98 }
99
100 constraints.push(fk_sql);
101 }
102 }
103 }
104
105 if inheritance.strategy == InheritanceStrategy::Joined {
107 if let Some(parent_table) = inheritance.parent {
108 let pk_cols = M::PRIMARY_KEY;
110 if !pk_cols.is_empty() {
111 let pk_col = pk_cols[0];
114 let constraint_name = format!("fk_{}_parent", M::TABLE_NAME);
115 let fk_sql = format!(
116 "CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {}({}) ON DELETE CASCADE",
117 quote_ident(&constraint_name),
118 quote_ident(pk_col),
119 quote_ident(parent_table),
120 quote_ident(pk_col)
121 );
122 constraints.push(fk_sql);
123 }
124 }
125 }
126
127 let pk_cols = M::PRIMARY_KEY;
129 if !pk_cols.is_empty() {
130 let quoted_pk: Vec<String> = pk_cols.iter().map(|c| quote_ident(c)).collect();
131 let mut constraint = String::new();
132 constraint.push_str("PRIMARY KEY (");
133 constraint.push_str("ed_pk.join(", "));
134 constraint.push(')');
135 constraints.insert(0, constraint);
136 }
137
138 let all_parts: Vec<_> = column_defs.into_iter().chain(constraints).collect();
140
141 sql.push_str(&all_parts.join(",\n "));
142 sql.push_str("\n)");
143
144 sql
145 }
146
147 pub fn should_skip_table_creation() -> bool {
152 let inheritance = M::inheritance();
153 inheritance.strategy == InheritanceStrategy::None
155 && inheritance.parent.is_some()
156 && inheritance.discriminator_value.is_some()
157 }
158
159 fn column_definition(&self, field: &FieldInfo) -> String {
160 let sql_type = field.effective_sql_type();
161 let mut def = String::from(" ");
162 def.push_str("e_ident(field.column_name));
163 def.push(' ');
164 def.push_str(&sql_type);
165
166 if !field.nullable && !field.auto_increment {
167 def.push_str(" NOT NULL");
168 }
169
170 if field.auto_increment {
171 def.push_str(" AUTOINCREMENT");
174 }
175
176 if let Some(default) = field.default {
177 def.push_str(" DEFAULT ");
178 def.push_str(default);
179 }
180
181 def
182 }
183}
184
185impl<M: Model> Default for CreateTable<M> {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use sqlmodel_core::{FieldInfo, Row, SqlType, Value};
195
196 struct TestHero;
198
199 impl Model for TestHero {
200 const TABLE_NAME: &'static str = "heroes";
201 const PRIMARY_KEY: &'static [&'static str] = &["id"];
202
203 fn fields() -> &'static [FieldInfo] {
204 static FIELDS: &[FieldInfo] = &[
205 FieldInfo::new("id", "id", SqlType::BigInt)
206 .nullable(true)
207 .primary_key(true)
208 .auto_increment(true),
209 FieldInfo::new("name", "name", SqlType::Text).unique(true),
210 FieldInfo::new("age", "age", SqlType::Integer).nullable(true),
211 FieldInfo::new("team_id", "team_id", SqlType::BigInt)
212 .nullable(true)
213 .foreign_key("teams.id"),
214 ];
215 FIELDS
216 }
217
218 fn to_row(&self) -> Vec<(&'static str, Value)> {
219 vec![]
220 }
221
222 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
223 Ok(TestHero)
224 }
225
226 fn primary_key_value(&self) -> Vec<Value> {
227 vec![]
228 }
229
230 fn is_new(&self) -> bool {
231 true
232 }
233 }
234
235 #[test]
236 fn test_create_table_basic() {
237 let sql = CreateTable::<TestHero>::new().build();
238 assert!(sql.starts_with("CREATE TABLE \"heroes\""));
239 assert!(sql.contains("\"id\" BIGINT"));
240 assert!(sql.contains("\"name\" TEXT NOT NULL"));
241 assert!(sql.contains("\"age\" INTEGER"));
242 assert!(sql.contains("\"team_id\" BIGINT"));
243 }
244
245 #[test]
246 fn test_create_table_if_not_exists() {
247 let sql = CreateTable::<TestHero>::new().if_not_exists().build();
248 assert!(sql.starts_with("CREATE TABLE IF NOT EXISTS \"heroes\""));
249 }
250
251 #[test]
252 fn test_create_table_primary_key() {
253 let sql = CreateTable::<TestHero>::new().build();
254 assert!(sql.contains("PRIMARY KEY (\"id\")"));
255 }
256
257 #[test]
258 fn test_create_table_unique_constraint() {
259 let sql = CreateTable::<TestHero>::new().build();
260 assert!(sql.contains("CONSTRAINT \"uk_name\" UNIQUE (\"name\")"));
261 }
262
263 #[test]
264 fn test_create_table_foreign_key() {
265 let sql = CreateTable::<TestHero>::new().build();
266 assert!(sql.contains("FOREIGN KEY (\"team_id\") REFERENCES \"teams\"(\"id\")"));
267 }
268
269 #[test]
270 fn test_create_table_auto_increment() {
271 let sql = CreateTable::<TestHero>::new().build();
272 assert!(sql.contains("AUTOINCREMENT"));
273 }
274
275 #[test]
276 fn test_schema_builder_single_table() {
277 let statements = SchemaBuilder::new().create_table::<TestHero>().build();
278 assert_eq!(statements.len(), 1);
279 assert!(statements[0].contains("CREATE TABLE IF NOT EXISTS \"heroes\""));
280 }
281
282 #[test]
283 fn test_schema_builder_with_index() {
284 let statements = SchemaBuilder::new()
285 .create_table::<TestHero>()
286 .create_index("idx_hero_name", "heroes", &["name"], false)
287 .build();
288 assert_eq!(statements.len(), 2);
289 assert!(
290 statements[1]
291 .contains("CREATE INDEX IF NOT EXISTS \"idx_hero_name\" ON \"heroes\" (\"name\")")
292 );
293 }
294
295 #[test]
296 fn test_schema_builder_unique_index() {
297 let statements = SchemaBuilder::new()
298 .create_index("idx_hero_email", "heroes", &["email"], true)
299 .build();
300 assert!(statements[0].contains("CREATE UNIQUE INDEX"));
301 }
302
303 #[test]
304 fn test_schema_builder_raw_sql() {
305 let statements = SchemaBuilder::new()
306 .raw("ALTER TABLE heroes ADD COLUMN power TEXT")
307 .build();
308 assert_eq!(statements.len(), 1);
309 assert_eq!(statements[0], "ALTER TABLE heroes ADD COLUMN power TEXT");
310 }
311
312 #[test]
313 fn test_schema_builder_multi_column_index() {
314 let statements = SchemaBuilder::new()
315 .create_index("idx_hero_name_age", "heroes", &["name", "age"], false)
316 .build();
317 assert!(statements[0].contains("ON \"heroes\" (\"name\", \"age\")"));
318 }
319
320 struct TestWithDefault;
322
323 impl Model for TestWithDefault {
324 const TABLE_NAME: &'static str = "settings";
325 const PRIMARY_KEY: &'static [&'static str] = &["id"];
326
327 fn fields() -> &'static [FieldInfo] {
328 static FIELDS: &[FieldInfo] = &[
329 FieldInfo::new("id", "id", SqlType::Integer).primary_key(true),
330 FieldInfo::new("is_active", "is_active", SqlType::Boolean).default("true"),
331 ];
332 FIELDS
333 }
334
335 fn to_row(&self) -> Vec<(&'static str, Value)> {
336 vec![]
337 }
338
339 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
340 Ok(TestWithDefault)
341 }
342
343 fn primary_key_value(&self) -> Vec<Value> {
344 vec![]
345 }
346
347 fn is_new(&self) -> bool {
348 true
349 }
350 }
351
352 #[test]
353 fn test_create_table_default_value() {
354 let sql = CreateTable::<TestWithDefault>::new().build();
355 assert!(sql.contains("\"is_active\" BOOLEAN NOT NULL DEFAULT true"));
356 }
357
358 struct TestWithOnDelete;
360
361 impl Model for TestWithOnDelete {
362 const TABLE_NAME: &'static str = "comments";
363 const PRIMARY_KEY: &'static [&'static str] = &["id"];
364
365 fn fields() -> &'static [FieldInfo] {
366 use sqlmodel_core::ReferentialAction;
367 static FIELDS: &[FieldInfo] = &[
368 FieldInfo::new("id", "id", SqlType::BigInt)
369 .nullable(true)
370 .primary_key(true)
371 .auto_increment(true),
372 FieldInfo::new("post_id", "post_id", SqlType::BigInt)
373 .foreign_key("posts.id")
374 .on_delete(ReferentialAction::Cascade)
375 .on_update(ReferentialAction::NoAction),
376 ];
377 FIELDS
378 }
379
380 fn to_row(&self) -> Vec<(&'static str, Value)> {
381 vec![]
382 }
383
384 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
385 Ok(TestWithOnDelete)
386 }
387
388 fn primary_key_value(&self) -> Vec<Value> {
389 vec![]
390 }
391
392 fn is_new(&self) -> bool {
393 true
394 }
395 }
396
397 #[test]
398 fn test_create_table_on_delete_cascade() {
399 let sql = CreateTable::<TestWithOnDelete>::new().build();
400 assert!(sql.contains("FOREIGN KEY (\"post_id\") REFERENCES \"posts\"(\"id\") ON DELETE CASCADE ON UPDATE NO ACTION"));
401 }
402
403 #[test]
404 fn test_referential_action_as_sql() {
405 use sqlmodel_core::ReferentialAction;
406 assert_eq!(ReferentialAction::NoAction.as_sql(), "NO ACTION");
407 assert_eq!(ReferentialAction::Restrict.as_sql(), "RESTRICT");
408 assert_eq!(ReferentialAction::Cascade.as_sql(), "CASCADE");
409 assert_eq!(ReferentialAction::SetNull.as_sql(), "SET NULL");
410 assert_eq!(ReferentialAction::SetDefault.as_sql(), "SET DEFAULT");
411 }
412
413 #[test]
414 fn test_referential_action_from_str() {
415 use sqlmodel_core::ReferentialAction;
416 assert_eq!(
417 ReferentialAction::from_str("CASCADE"),
418 Some(ReferentialAction::Cascade)
419 );
420 assert_eq!(
421 ReferentialAction::from_str("cascade"),
422 Some(ReferentialAction::Cascade)
423 );
424 assert_eq!(
425 ReferentialAction::from_str("SET NULL"),
426 Some(ReferentialAction::SetNull)
427 );
428 assert_eq!(
429 ReferentialAction::from_str("SETNULL"),
430 Some(ReferentialAction::SetNull)
431 );
432 assert_eq!(ReferentialAction::from_str("invalid"), None);
433 }
434
435 #[derive(sqlmodel_macros::Model)]
436 struct TestDerivedSqlTypeOverride {
437 #[sqlmodel(primary_key)]
438 id: i64,
439
440 #[sqlmodel(sql_type = "TIMESTAMP WITH TIME ZONE")]
441 created_at: String,
442 }
443
444 #[test]
445 fn test_create_table_sql_type_attribute_preserves_raw_string() {
446 let sql = CreateTable::<TestDerivedSqlTypeOverride>::new().build();
447 assert!(sql.contains("\"created_at\" TIMESTAMP WITH TIME ZONE NOT NULL"));
448 }
449
450 struct TestWithSqlTypeOverride;
452
453 impl Model for TestWithSqlTypeOverride {
454 const TABLE_NAME: &'static str = "products";
455 const PRIMARY_KEY: &'static [&'static str] = &["id"];
456
457 fn fields() -> &'static [FieldInfo] {
458 static FIELDS: &[FieldInfo] = &[
459 FieldInfo::new("id", "id", SqlType::BigInt)
460 .nullable(true)
461 .primary_key(true)
462 .auto_increment(true),
463 FieldInfo::new("price", "price", SqlType::Real).sql_type_override("DECIMAL(10,2)"),
464 FieldInfo::new("sku", "sku", SqlType::Text)
465 .sql_type_override("VARCHAR(50)")
466 .unique(true),
467 ];
468 FIELDS
469 }
470
471 fn to_row(&self) -> Vec<(&'static str, Value)> {
472 vec![]
473 }
474
475 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
476 Ok(TestWithSqlTypeOverride)
477 }
478
479 fn primary_key_value(&self) -> Vec<Value> {
480 vec![]
481 }
482
483 fn is_new(&self) -> bool {
484 true
485 }
486 }
487
488 #[test]
489 fn test_create_table_sql_type_override() {
490 let sql = CreateTable::<TestWithSqlTypeOverride>::new().build();
491 assert!(sql.contains("\"price\" DECIMAL(10,2) NOT NULL"));
493 assert!(sql.contains("\"sku\" VARCHAR(50) NOT NULL"));
494 assert!(sql.contains("\"id\" BIGINT"));
496 }
497
498 #[test]
499 fn test_field_info_effective_sql_type() {
500 let field_no_override = FieldInfo::new("col", "col", SqlType::Integer);
501 assert_eq!(field_no_override.effective_sql_type(), "INTEGER");
502
503 let field_with_override =
504 FieldInfo::new("col", "col", SqlType::Text).sql_type_override("VARCHAR(255)");
505 assert_eq!(field_with_override.effective_sql_type(), "VARCHAR(255)");
506 }
507
508 #[test]
509 fn test_quote_ident_escapes_embedded_quotes() {
510 assert_eq!(quote_ident("simple"), "\"simple\"");
512
513 assert_eq!(quote_ident("with\"quote"), "\"with\"\"quote\"");
515
516 assert_eq!(quote_ident("a\"b\"c"), "\"a\"\"b\"\"c\"");
518
519 assert_eq!(quote_ident("test\"\"name"), "\"test\"\"\"\"name\"");
521 }
522
523 #[test]
524 fn test_schema_builder_index_with_special_chars() {
525 let statements = SchemaBuilder::new()
526 .create_index("idx\"test", "my\"table", &["col\"name"], false)
527 .build();
528 assert!(statements[0].contains("\"idx\"\"test\""));
530 assert!(statements[0].contains("\"my\"\"table\""));
531 assert!(statements[0].contains("\"col\"\"name\""));
532 }
533
534 struct TestOrderTable;
540
541 impl Model for TestOrderTable {
542 const TABLE_NAME: &'static str = "order"; const PRIMARY_KEY: &'static [&'static str] = &["id"];
544
545 fn fields() -> &'static [FieldInfo] {
546 static FIELDS: &[FieldInfo] = &[
547 FieldInfo::new("id", "id", SqlType::BigInt)
548 .nullable(true)
549 .primary_key(true),
550 FieldInfo::new("select", "select", SqlType::Text), FieldInfo::new("from", "from", SqlType::Text), ];
553 FIELDS
554 }
555
556 fn to_row(&self) -> Vec<(&'static str, Value)> {
557 vec![]
558 }
559
560 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
561 Ok(TestOrderTable)
562 }
563
564 fn primary_key_value(&self) -> Vec<Value> {
565 vec![]
566 }
567
568 fn is_new(&self) -> bool {
569 true
570 }
571 }
572
573 #[test]
574 fn test_create_table_with_keyword_table_name() {
575 let sql = CreateTable::<TestOrderTable>::new().build();
576 assert!(sql.contains("CREATE TABLE \"order\""));
578 assert!(sql.contains("\"select\" TEXT NOT NULL"));
580 assert!(sql.contains("\"from\" TEXT NOT NULL"));
581 assert!(sql.contains("\"id\" BIGINT"));
582 assert!(sql.contains("PRIMARY KEY (\"id\")"));
583 }
584
585 #[test]
586 fn test_schema_builder_with_keyword_table_name() {
587 let statements = SchemaBuilder::new()
588 .create_table::<TestOrderTable>()
589 .build();
590 assert!(statements[0].contains("CREATE TABLE IF NOT EXISTS \"order\""));
591 assert!(statements[0].contains("\"select\" TEXT NOT NULL"));
592 }
593
594 #[test]
595 fn test_create_index_with_keyword_names() {
596 let statements = SchemaBuilder::new()
597 .create_index("idx_order_select", "order", &["select", "from"], false)
598 .build();
599 assert!(statements[0].contains("\"idx_order_select\""));
601 assert!(statements[0].contains("ON \"order\""));
602 assert!(statements[0].contains("(\"select\", \"from\")"));
603 }
604
605 struct TestQuotedNames;
607
608 impl Model for TestQuotedNames {
609 const TABLE_NAME: &'static str = "my\"table"; const PRIMARY_KEY: &'static [&'static str] = &["pk"];
611
612 fn fields() -> &'static [FieldInfo] {
613 static FIELDS: &[FieldInfo] = &[
614 FieldInfo::new("pk", "pk", SqlType::BigInt).primary_key(true),
615 FieldInfo::new("data\"col", "data\"col", SqlType::Text), ];
617 FIELDS
618 }
619
620 fn to_row(&self) -> Vec<(&'static str, Value)> {
621 vec![]
622 }
623
624 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
625 Ok(TestQuotedNames)
626 }
627
628 fn primary_key_value(&self) -> Vec<Value> {
629 vec![]
630 }
631
632 fn is_new(&self) -> bool {
633 true
634 }
635 }
636
637 #[test]
638 fn test_create_table_with_embedded_quotes() {
639 let sql = CreateTable::<TestQuotedNames>::new().build();
640 assert!(sql.contains("CREATE TABLE \"my\"\"table\""));
642 assert!(sql.contains("\"data\"\"col\" TEXT NOT NULL"));
643 assert!(sql.contains("PRIMARY KEY (\"pk\")"));
645 }
646
647 struct TestUnicodeTable;
649
650 impl Model for TestUnicodeTable {
651 const TABLE_NAME: &'static str = "用户表"; const PRIMARY_KEY: &'static [&'static str] = &["id"];
653
654 fn fields() -> &'static [FieldInfo] {
655 static FIELDS: &[FieldInfo] = &[
656 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
657 FieldInfo::new("名前", "名前", SqlType::Text), FieldInfo::new("émoji_🦀", "émoji_🦀", SqlType::Text), ];
660 FIELDS
661 }
662
663 fn to_row(&self) -> Vec<(&'static str, Value)> {
664 vec![]
665 }
666
667 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
668 Ok(TestUnicodeTable)
669 }
670
671 fn primary_key_value(&self) -> Vec<Value> {
672 vec![]
673 }
674
675 fn is_new(&self) -> bool {
676 true
677 }
678 }
679
680 #[test]
681 fn test_create_table_with_unicode_names() {
682 let sql = CreateTable::<TestUnicodeTable>::new().build();
683 assert!(sql.contains("CREATE TABLE \"用户表\""));
685 assert!(sql.contains("\"名前\" TEXT NOT NULL"));
686 assert!(sql.contains("\"émoji_🦀\" TEXT NOT NULL"));
687 }
688
689 struct TestSpacedNames;
691
692 impl Model for TestSpacedNames {
693 const TABLE_NAME: &'static str = "my table";
694 const PRIMARY_KEY: &'static [&'static str] = &["my id"];
695
696 fn fields() -> &'static [FieldInfo] {
697 static FIELDS: &[FieldInfo] = &[
698 FieldInfo::new("my id", "my id", SqlType::BigInt).primary_key(true),
699 FieldInfo::new("full name", "full name", SqlType::Text),
700 ];
701 FIELDS
702 }
703
704 fn to_row(&self) -> Vec<(&'static str, Value)> {
705 vec![]
706 }
707
708 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
709 Ok(TestSpacedNames)
710 }
711
712 fn primary_key_value(&self) -> Vec<Value> {
713 vec![]
714 }
715
716 fn is_new(&self) -> bool {
717 true
718 }
719 }
720
721 #[test]
722 fn test_create_table_with_spaces_in_names() {
723 let sql = CreateTable::<TestSpacedNames>::new().build();
724 assert!(sql.contains("CREATE TABLE \"my table\""));
726 assert!(sql.contains("\"my id\" BIGINT"));
727 assert!(sql.contains("\"full name\" TEXT NOT NULL"));
728 assert!(sql.contains("PRIMARY KEY (\"my id\")"));
729 }
730
731 struct TestFkToKeyword;
733
734 impl Model for TestFkToKeyword {
735 const TABLE_NAME: &'static str = "user_orders";
736 const PRIMARY_KEY: &'static [&'static str] = &["id"];
737
738 fn fields() -> &'static [FieldInfo] {
739 static FIELDS: &[FieldInfo] = &[
740 FieldInfo::new("id", "id", SqlType::BigInt)
741 .nullable(true)
742 .primary_key(true),
743 FieldInfo::new("order_id", "order_id", SqlType::BigInt)
744 .nullable(true)
745 .foreign_key("order.id"), ];
747 FIELDS
748 }
749
750 fn to_row(&self) -> Vec<(&'static str, Value)> {
751 vec![]
752 }
753
754 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
755 Ok(TestFkToKeyword)
756 }
757
758 fn primary_key_value(&self) -> Vec<Value> {
759 vec![]
760 }
761
762 fn is_new(&self) -> bool {
763 true
764 }
765 }
766
767 #[test]
768 fn test_foreign_key_to_keyword_table() {
769 let sql = CreateTable::<TestFkToKeyword>::new().build();
770 assert!(sql.contains("FOREIGN KEY (\"order_id\") REFERENCES \"order\"(\"id\")"));
772 }
773
774 struct TestUniqueKeyword;
776
777 impl Model for TestUniqueKeyword {
778 const TABLE_NAME: &'static str = "items";
779 const PRIMARY_KEY: &'static [&'static str] = &["id"];
780
781 fn fields() -> &'static [FieldInfo] {
782 static FIELDS: &[FieldInfo] = &[
783 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
784 FieldInfo::new("index", "index", SqlType::Integer).unique(true), ];
786 FIELDS
787 }
788
789 fn to_row(&self) -> Vec<(&'static str, Value)> {
790 vec![]
791 }
792
793 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
794 Ok(TestUniqueKeyword)
795 }
796
797 fn primary_key_value(&self) -> Vec<Value> {
798 vec![]
799 }
800
801 fn is_new(&self) -> bool {
802 true
803 }
804 }
805
806 #[test]
807 fn test_unique_constraint_with_keyword_column() {
808 let sql = CreateTable::<TestUniqueKeyword>::new().build();
809 assert!(sql.contains("CONSTRAINT \"uk_index\" UNIQUE (\"index\")"));
811 assert!(sql.contains("\"index\" INTEGER NOT NULL"));
812 }
813
814 #[test]
816 fn test_quote_ident_edge_cases() {
817 assert_eq!(quote_ident(""), "\"\"");
819
820 assert_eq!(quote_ident("x"), "\"x\"");
822
823 assert_eq!(quote_ident("\""), "\"\"\"\"");
825
826 assert_eq!(quote_ident("back\\slash"), "\"back\\slash\"");
828
829 assert_eq!(quote_ident("\"\"\""), "\"\"\"\"\"\"\"\"");
831
832 assert_eq!(quote_ident("a\"b\"c\"d"), "\"a\"\"b\"\"c\"\"d\"");
834 }
835
836 #[test]
838 fn test_various_sql_keywords_as_identifiers() {
839 let keywords = [
841 "select",
842 "from",
843 "where",
844 "order",
845 "group",
846 "by",
847 "having",
848 "insert",
849 "update",
850 "delete",
851 "create",
852 "drop",
853 "table",
854 "index",
855 "primary",
856 "foreign",
857 "key",
858 "references",
859 "constraint",
860 "unique",
861 "not",
862 "null",
863 "default",
864 "and",
865 "or",
866 "in",
867 "between",
868 "like",
869 "is",
870 "as",
871 "join",
872 "inner",
873 "outer",
874 "left",
875 "right",
876 "on",
877 "into",
878 "values",
879 "set",
880 "limit",
881 "offset",
882 "asc",
883 "desc",
884 "user",
885 "database",
886 ];
887
888 for keyword in keywords {
889 let quoted = quote_ident(keyword);
890 assert!(
892 quoted.starts_with('"') && quoted.ends_with('"'),
893 "Keyword '{}' not properly quoted: {}",
894 keyword,
895 quoted
896 );
897 assert_eq!(
899 "ed[1..quoted.len() - 1],
900 keyword,
901 "Keyword '{}' mangled in quoting",
902 keyword
903 );
904 }
905 }
906
907 #[test]
909 fn test_schema_builder_create_index_with_keywords() {
910 let stmts = SchemaBuilder::new()
911 .create_index("idx_user_select", "user", &["select"], true)
912 .build();
913 assert!(stmts[0].contains("CREATE UNIQUE INDEX IF NOT EXISTS \"idx_user_select\""));
914 assert!(stmts[0].contains("ON \"user\" (\"select\")"));
915 }
916
917 #[test]
918 fn test_schema_builder_multi_column_index_with_quotes() {
919 let stmts = SchemaBuilder::new()
920 .create_index("idx\"multi", "tbl\"name", &["col\"a", "col\"b"], false)
921 .build();
922 assert!(stmts[0].contains("\"idx\"\"multi\""));
923 assert!(stmts[0].contains("ON \"tbl\"\"name\""));
924 assert!(stmts[0].contains("(\"col\"\"a\", \"col\"\"b\")"));
925 }
926
927 use sqlmodel_core::InheritanceInfo;
932
933 struct SingleTableBase;
935
936 impl Model for SingleTableBase {
937 const TABLE_NAME: &'static str = "employees";
938 const PRIMARY_KEY: &'static [&'static str] = &["id"];
939
940 fn fields() -> &'static [FieldInfo] {
941 static FIELDS: &[FieldInfo] = &[
942 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
943 FieldInfo::new("name", "name", SqlType::Text),
944 FieldInfo::new("type_", "type_", SqlType::Text), ];
946 FIELDS
947 }
948
949 fn to_row(&self) -> Vec<(&'static str, Value)> {
950 vec![]
951 }
952
953 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
954 Ok(SingleTableBase)
955 }
956
957 fn primary_key_value(&self) -> Vec<Value> {
958 vec![]
959 }
960
961 fn is_new(&self) -> bool {
962 true
963 }
964
965 fn inheritance() -> InheritanceInfo {
966 InheritanceInfo {
967 strategy: sqlmodel_core::InheritanceStrategy::Single,
968 parent: None,
969 discriminator_column: Some("type_"),
970 discriminator_value: None,
971 }
972 }
973 }
974
975 struct SingleTableChild;
977
978 impl Model for SingleTableChild {
979 const TABLE_NAME: &'static str = "managers"; const PRIMARY_KEY: &'static [&'static str] = &["id"];
981
982 fn fields() -> &'static [FieldInfo] {
983 static FIELDS: &[FieldInfo] = &[
984 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
985 FieldInfo::new("department", "department", SqlType::Text),
986 ];
987 FIELDS
988 }
989
990 fn to_row(&self) -> Vec<(&'static str, Value)> {
991 vec![]
992 }
993
994 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
995 Ok(SingleTableChild)
996 }
997
998 fn primary_key_value(&self) -> Vec<Value> {
999 vec![]
1000 }
1001
1002 fn is_new(&self) -> bool {
1003 true
1004 }
1005
1006 fn inheritance() -> InheritanceInfo {
1007 InheritanceInfo {
1010 strategy: sqlmodel_core::InheritanceStrategy::None,
1011 parent: Some("Employee"),
1012 discriminator_column: None,
1013 discriminator_value: Some("manager"),
1014 }
1015 }
1016 }
1017
1018 struct JoinedTableBase;
1020
1021 impl Model for JoinedTableBase {
1022 const TABLE_NAME: &'static str = "persons";
1023 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1024
1025 fn fields() -> &'static [FieldInfo] {
1026 static FIELDS: &[FieldInfo] = &[
1027 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
1028 FieldInfo::new("name", "name", SqlType::Text),
1029 ];
1030 FIELDS
1031 }
1032
1033 fn to_row(&self) -> Vec<(&'static str, Value)> {
1034 vec![]
1035 }
1036
1037 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
1038 Ok(JoinedTableBase)
1039 }
1040
1041 fn primary_key_value(&self) -> Vec<Value> {
1042 vec![]
1043 }
1044
1045 fn is_new(&self) -> bool {
1046 true
1047 }
1048
1049 fn inheritance() -> InheritanceInfo {
1050 InheritanceInfo {
1051 strategy: sqlmodel_core::InheritanceStrategy::Joined,
1052 parent: None,
1053 discriminator_column: None,
1054 discriminator_value: None,
1055 }
1056 }
1057 }
1058
1059 struct JoinedTableChild;
1061
1062 impl Model for JoinedTableChild {
1063 const TABLE_NAME: &'static str = "students";
1064 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1065
1066 fn fields() -> &'static [FieldInfo] {
1067 static FIELDS: &[FieldInfo] = &[
1068 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
1069 FieldInfo::new("grade", "grade", SqlType::Text),
1070 ];
1071 FIELDS
1072 }
1073
1074 fn to_row(&self) -> Vec<(&'static str, Value)> {
1075 vec![]
1076 }
1077
1078 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
1079 Ok(JoinedTableChild)
1080 }
1081
1082 fn primary_key_value(&self) -> Vec<Value> {
1083 vec![]
1084 }
1085
1086 fn is_new(&self) -> bool {
1087 true
1088 }
1089
1090 fn inheritance() -> InheritanceInfo {
1091 InheritanceInfo {
1092 strategy: sqlmodel_core::InheritanceStrategy::Joined,
1093 parent: Some("persons"),
1094 discriminator_column: None,
1095 discriminator_value: None,
1096 }
1097 }
1098 }
1099
1100 #[test]
1101 fn test_single_table_inheritance_base_creates_table() {
1102 let sql = CreateTable::<SingleTableBase>::new().build();
1103 assert!(sql.contains("CREATE TABLE \"employees\""));
1104 assert!(sql.contains("\"type_\" TEXT NOT NULL")); }
1106
1107 #[test]
1108 fn test_single_table_inheritance_child_skips_table_creation() {
1109 let sql = CreateTable::<SingleTableChild>::new().build();
1111 assert!(
1112 sql.is_empty(),
1113 "Single table inheritance child should not create a table"
1114 );
1115 }
1116
1117 #[test]
1118 fn test_single_table_inheritance_child_should_skip() {
1119 assert!(
1120 CreateTable::<SingleTableChild>::should_skip_table_creation(),
1121 "should_skip_table_creation should return true for STI child"
1122 );
1123 assert!(
1124 !CreateTable::<SingleTableBase>::should_skip_table_creation(),
1125 "should_skip_table_creation should return false for STI base"
1126 );
1127 }
1128
1129 #[test]
1130 fn test_joined_table_inheritance_base_creates_table() {
1131 let sql = CreateTable::<JoinedTableBase>::new().build();
1132 assert!(sql.contains("CREATE TABLE \"persons\""));
1133 assert!(sql.contains("\"id\" BIGINT"));
1134 assert!(sql.contains("\"name\" TEXT NOT NULL"));
1135 }
1136
1137 #[test]
1138 fn test_joined_table_inheritance_child_creates_table_with_fk() {
1139 let sql = CreateTable::<JoinedTableChild>::new().build();
1140 assert!(sql.contains("CREATE TABLE \"students\""));
1141 assert!(sql.contains("\"id\" BIGINT"));
1142 assert!(sql.contains("\"grade\" TEXT NOT NULL"));
1143 assert!(
1145 sql.contains("FOREIGN KEY (\"id\") REFERENCES \"persons\"(\"id\") ON DELETE CASCADE"),
1146 "Joined table child should have FK to parent: {}",
1147 sql
1148 );
1149 }
1150
1151 #[test]
1152 fn test_schema_builder_skips_sti_child() {
1153 let statements = SchemaBuilder::new()
1154 .create_table::<SingleTableBase>()
1155 .create_table::<SingleTableChild>() .build();
1157
1158 assert_eq!(
1160 statements.len(),
1161 1,
1162 "STI child should be skipped by SchemaBuilder"
1163 );
1164 assert!(statements[0].contains("CREATE TABLE IF NOT EXISTS \"employees\""));
1165 }
1166
1167 #[test]
1168 fn test_schema_builder_creates_both_joined_tables() {
1169 let statements = SchemaBuilder::new()
1170 .create_table::<JoinedTableBase>()
1171 .create_table::<JoinedTableChild>()
1172 .build();
1173
1174 assert_eq!(
1176 statements.len(),
1177 2,
1178 "Both joined inheritance tables should be created"
1179 );
1180 assert!(statements[0].contains("CREATE TABLE IF NOT EXISTS \"persons\""));
1181 assert!(statements[1].contains("CREATE TABLE IF NOT EXISTS \"students\""));
1182 assert!(statements[1].contains("FOREIGN KEY"));
1183 }
1184}
1185
1186#[derive(Debug, Default)]
1188pub struct SchemaBuilder {
1189 statements: Vec<String>,
1190}
1191
1192impl SchemaBuilder {
1193 pub fn new() -> Self {
1195 Self::default()
1196 }
1197
1198 pub fn create_table<M: Model>(mut self) -> Self {
1203 if CreateTable::<M>::should_skip_table_creation() {
1205 return self;
1206 }
1207 self.statements
1208 .push(CreateTable::<M>::new().if_not_exists().build());
1209 self
1210 }
1211
1212 pub fn raw(mut self, sql: impl Into<String>) -> Self {
1214 self.statements.push(sql.into());
1215 self
1216 }
1217
1218 pub fn create_index(mut self, name: &str, table: &str, columns: &[&str], unique: bool) -> Self {
1220 let unique_str = if unique { "UNIQUE " } else { "" };
1221 let quoted_cols: Vec<String> = columns.iter().map(|c| quote_ident(c)).collect();
1222 let stmt = format!(
1223 "CREATE {}INDEX IF NOT EXISTS {} ON {} ({})",
1224 unique_str,
1225 quote_ident(name),
1226 quote_ident(table),
1227 quoted_cols.join(", ")
1228 );
1229 self.statements.push(stmt);
1230 self
1231 }
1232
1233 pub fn build(self) -> Vec<String> {
1235 self.statements
1236 }
1237}