1use super::{FieldState, ModelState, ProjectState};
33use crate::backends::schema::BaseDatabaseSchemaEditor;
34use crate::backends::types::DatabaseType;
35use serde::{Deserialize, Serialize};
36use std::collections::HashMap;
37use std::fmt;
38
39#[non_exhaustive]
41#[derive(Debug, Clone, PartialEq)]
42pub enum ValidationError {
43 EmptyCompositePrimaryKey {
45 table_name: String,
47 },
48 NonExistentField {
50 field_name: String,
52 table_name: String,
54 available_fields: Vec<String>,
56 },
57}
58
59impl fmt::Display for ValidationError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 ValidationError::EmptyCompositePrimaryKey { table_name } => {
63 write!(
64 f,
65 "Composite primary key for table '{}' cannot be empty",
66 table_name
67 )
68 }
69 ValidationError::NonExistentField {
70 field_name,
71 table_name,
72 available_fields,
73 } => {
74 write!(
75 f,
76 "Field '{}' does not exist in table '{}'. Available fields: [{}]",
77 field_name,
78 table_name,
79 available_fields.join(", ")
80 )
81 }
82 }
83 }
84}
85
86impl std::error::Error for ValidationError {}
87
88pub type ValidationResult<T> = Result<T, ValidationError>;
90
91pub fn quote_identifier(identifier: &str, database_type: DatabaseType) -> String {
117 match database_type {
118 DatabaseType::Postgres | DatabaseType::Sqlite => {
119 format!("\"{}\"", identifier.replace('"', "\"\""))
122 }
123 DatabaseType::Mysql => {
124 format!("`{}`", identifier.replace('`', "``"))
127 }
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
147pub struct FieldDefinition {
148 pub name: String,
150 pub field_type: crate::migrations::FieldType,
152 pub primary_key: bool,
154 pub unique: bool,
156 pub default: Option<String>,
158 pub null: bool,
160
161 pub generated: Option<String>,
164 pub generated_stored: Option<bool>,
166 #[cfg(any(feature = "db-mysql", feature = "db-sqlite"))]
168 pub generated_virtual: Option<bool>,
169
170 #[cfg(feature = "db-postgres")]
173 pub identity_always: Option<bool>,
174 #[cfg(feature = "db-postgres")]
176 pub identity_by_default: Option<bool>,
177 #[cfg(feature = "db-mysql")]
179 pub auto_increment: Option<bool>,
180 #[cfg(feature = "db-sqlite")]
182 pub autoincrement: Option<bool>,
183
184 pub collate: Option<String>,
187 #[cfg(feature = "db-mysql")]
189 pub character_set: Option<String>,
190
191 #[cfg(any(feature = "db-postgres", feature = "db-mysql"))]
194 pub comment: Option<String>,
195
196 #[cfg(feature = "db-postgres")]
199 pub storage: Option<String>,
200 #[cfg(feature = "db-postgres")]
202 pub compression: Option<String>,
203
204 #[cfg(feature = "db-mysql")]
207 pub on_update_current_timestamp: Option<bool>,
208
209 #[cfg(feature = "db-mysql")]
212 pub invisible: Option<bool>,
213
214 #[cfg(any(feature = "db-postgres", feature = "db-mysql"))]
217 pub fulltext: Option<bool>,
218
219 #[cfg(feature = "db-mysql")]
222 pub unsigned: Option<bool>,
223 #[cfg(feature = "db-mysql")]
225 pub zerofill: Option<bool>,
226}
227
228impl FieldDefinition {
229 pub fn new(
242 name: impl Into<String>,
243 field_type: crate::migrations::FieldType,
244 primary_key: bool,
245 unique: bool,
246 default: Option<impl Into<String>>,
247 ) -> Self {
248 Self {
249 name: name.into(),
250 field_type,
251 primary_key,
252 unique,
253 default: default.map(|d| d.into()),
254 null: false,
255 generated: None,
257 generated_stored: None,
258 #[cfg(any(feature = "db-mysql", feature = "db-sqlite"))]
259 generated_virtual: None,
260 #[cfg(feature = "db-postgres")]
262 identity_always: None,
263 #[cfg(feature = "db-postgres")]
264 identity_by_default: None,
265 #[cfg(feature = "db-mysql")]
266 auto_increment: None,
267 #[cfg(feature = "db-sqlite")]
268 autoincrement: None,
269 collate: None,
271 #[cfg(feature = "db-mysql")]
272 character_set: None,
273 #[cfg(any(feature = "db-postgres", feature = "db-mysql"))]
275 comment: None,
276 #[cfg(feature = "db-postgres")]
278 storage: None,
279 #[cfg(feature = "db-postgres")]
280 compression: None,
281 #[cfg(feature = "db-mysql")]
283 on_update_current_timestamp: None,
284 #[cfg(feature = "db-mysql")]
286 invisible: None,
287 #[cfg(any(feature = "db-postgres", feature = "db-mysql"))]
289 fulltext: None,
290 #[cfg(feature = "db-mysql")]
292 unsigned: None,
293 #[cfg(feature = "db-mysql")]
294 zerofill: None,
295 }
296 }
297
298 pub fn nullable(mut self, null: bool) -> Self {
300 self.null = null;
301 self
302 }
303
304 pub fn to_sql_definition(&self) -> String {
319 let mut parts = vec![self.field_type.to_sql_string()];
320
321 if let Some(ref generated_expr) = self.generated {
323 parts.push(format!("GENERATED ALWAYS AS ({})", generated_expr));
324
325 let is_stored = self.generated_stored.unwrap_or(false);
327
328 #[cfg(any(feature = "db-mysql", feature = "db-sqlite"))]
329 let is_virtual = self.generated_virtual.unwrap_or(false);
330 #[cfg(not(any(feature = "db-mysql", feature = "db-sqlite")))]
331 let is_virtual = false;
332
333 if is_stored {
334 parts.push("STORED".to_string());
335 } else if is_virtual {
336 parts.push("VIRTUAL".to_string());
337 }
338 }
339
340 #[cfg(feature = "db-postgres")]
342 if self.identity_always.unwrap_or(false) {
343 parts.push("GENERATED ALWAYS AS IDENTITY".to_string());
344 }
345 #[cfg(feature = "db-postgres")]
346 if self.identity_by_default.unwrap_or(false) {
347 parts.push("GENERATED BY DEFAULT AS IDENTITY".to_string());
348 }
349 #[cfg(feature = "db-mysql")]
350 if self.auto_increment.unwrap_or(false) {
351 parts.push("AUTO_INCREMENT".to_string());
352 }
353 #[cfg(feature = "db-sqlite")]
354 if self.autoincrement.unwrap_or(false) {
355 parts.push("AUTOINCREMENT".to_string());
356 }
357
358 if self.primary_key {
359 parts.push("PRIMARY KEY".to_string());
360 }
361
362 if self.unique && !self.primary_key {
363 parts.push("UNIQUE".to_string());
364 }
365
366 if !self.null && !self.primary_key {
367 parts.push("NOT NULL".to_string());
368 }
369
370 if let Some(ref default) = self.default {
371 parts.push(format!("DEFAULT {}", default));
372 }
373
374 #[cfg(feature = "db-mysql")]
376 if let Some(ref character_set) = self.character_set {
377 parts.push(format!("CHARACTER SET {}", character_set));
378 }
379
380 if let Some(ref collate) = self.collate {
381 parts.push(format!("COLLATE {}", collate));
382 }
383
384 #[cfg(feature = "db-mysql")]
386 if let Some(ref comment) = self.comment {
387 parts.push(format!("COMMENT '{}'", comment.replace('\'', "''")));
388 }
389
390 #[cfg(feature = "db-postgres")]
392 if let Some(ref storage) = self.storage {
393 parts.push(format!("STORAGE {}", storage.to_uppercase()));
394 }
395 #[cfg(feature = "db-postgres")]
396 if let Some(ref compression) = self.compression {
397 parts.push(format!("COMPRESSION {}", compression));
398 }
399
400 #[cfg(feature = "db-mysql")]
402 if self.on_update_current_timestamp.unwrap_or(false) {
403 parts.push("ON UPDATE CURRENT_TIMESTAMP".to_string());
404 }
405
406 #[cfg(feature = "db-mysql")]
408 if self.invisible.unwrap_or(false) {
409 parts.push("INVISIBLE".to_string());
410 }
411
412 #[cfg(feature = "db-mysql")]
420 if self.unsigned.unwrap_or(false) {
421 parts.push("UNSIGNED".to_string());
422 }
423 #[cfg(feature = "db-mysql")]
424 if self.zerofill.unwrap_or(false) {
425 parts.push("ZEROFILL".to_string());
426 }
427
428 parts.join(" ")
429 }
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct CreateModel {
456 pub name: String,
458 pub fields: Vec<FieldDefinition>,
460 pub options: HashMap<String, String>,
462 pub bases: Vec<String>,
464 pub composite_primary_key: Option<Vec<String>>,
466}
467
468impl CreateModel {
469 pub fn new(name: impl Into<String>, fields: Vec<FieldDefinition>) -> Self {
471 Self {
472 name: name.into(),
473 fields,
474 options: HashMap::new(),
475 bases: vec![],
476 composite_primary_key: None,
477 }
478 }
479
480 pub fn with_options(mut self, options: HashMap<String, String>) -> Self {
482 self.options = options;
483 self
484 }
485
486 pub fn with_bases(mut self, bases: Vec<String>) -> Self {
488 self.bases = bases;
489 self
490 }
491
492 pub fn with_composite_primary_key(mut self, fields: Vec<String>) -> ValidationResult<Self> {
519 if fields.is_empty() {
521 return Err(ValidationError::EmptyCompositePrimaryKey {
522 table_name: self.name.clone(),
523 });
524 }
525
526 let available_field_names: Vec<String> =
528 self.fields.iter().map(|f| f.name.clone()).collect();
529
530 for field_name in &fields {
531 if !available_field_names.contains(field_name) {
532 return Err(ValidationError::NonExistentField {
533 field_name: field_name.clone(),
534 table_name: self.name.clone(),
535 available_fields: available_field_names.clone(),
536 });
537 }
538 }
539
540 self.composite_primary_key = Some(fields);
541 Ok(self)
542 }
543
544 pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
546 let mut model = ModelState::new(app_label, &self.name);
547
548 for field_def in &self.fields {
549 let field = FieldState::new(
550 field_def.name.clone(),
551 field_def.field_type.clone(),
552 field_def.primary_key,
553 );
554 model.add_field(field);
555 }
556
557 state.add_model(model);
558 }
559
560 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
588 let mut sql_statements = Vec::new();
589
590 let has_composite_pk = self.composite_primary_key.is_some();
592
593 let column_defs: Vec<String> = self
595 .fields
596 .iter()
597 .map(|f| {
598 if has_composite_pk && f.primary_key {
600 let mut parts = vec![f.field_type.to_sql_string()];
602
603 parts.push("NOT NULL".to_string());
605
606 if f.unique {
607 parts.push("UNIQUE".to_string());
608 }
609 if let Some(ref default) = f.default {
610 parts.push(format!("DEFAULT {}", default));
611 }
612 parts.join(" ")
613 } else {
614 f.to_sql_definition()
615 }
616 })
617 .collect();
618
619 let columns: Vec<(&str, &str)> = self
621 .fields
622 .iter()
623 .zip(column_defs.iter())
624 .map(|(f, def)| (f.name.as_str(), def.as_str()))
625 .collect();
626
627 let stmt = schema_editor.create_table_statement(&self.name, &columns);
629 let mut create_sql = schema_editor.build_create_table_sql(&stmt);
630
631 if let Some(ref pk_fields) = self.composite_primary_key {
633 let db_type = schema_editor.database_type();
634 let pk_name = format!("{}_pkey", self.name);
635 let quoted_pk_name = quote_identifier(&pk_name, db_type);
636 let quoted_pk_fields = pk_fields
637 .iter()
638 .map(|f| quote_identifier(f, db_type))
639 .collect::<Vec<_>>()
640 .join(", ");
641 let constraint_sql = format!(
642 "CONSTRAINT {} PRIMARY KEY ({})",
643 quoted_pk_name, quoted_pk_fields
644 );
645
646 if create_sql.ends_with(");") {
650 let insert_pos = create_sql.len() - 2; create_sql.insert_str(insert_pos, &format!(", {}", constraint_sql));
652 } else if create_sql.ends_with(")") {
653 let insert_pos = create_sql.len() - 1; create_sql.insert_str(insert_pos, &format!(", {}", constraint_sql));
655 }
656 }
657
658 #[cfg(feature = "db-sqlite")]
661 {
662 let mut table_options = Vec::new();
663
664 if let Some(strict_val) = self.options.get("strict")
665 && strict_val == "true"
666 {
667 table_options.push("STRICT");
668 }
669
670 if let Some(without_rowid_val) = self.options.get("without_rowid")
671 && without_rowid_val == "true"
672 {
673 table_options.push("WITHOUT ROWID");
674 }
675
676 if !table_options.is_empty() {
677 if create_sql.ends_with(';') {
679 create_sql.pop();
680 }
681 create_sql.push(' ');
683 create_sql.push_str(&table_options.join(" "));
684 create_sql.push(';');
685 }
686 }
687
688 sql_statements.push(create_sql);
689 sql_statements
690 }
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
718pub struct DeleteModel {
719 pub name: String,
721}
722
723impl DeleteModel {
724 pub fn new(name: impl Into<String>) -> Self {
726 Self { name: name.into() }
727 }
728
729 pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
731 state.remove_model(app_label, &self.name);
732 }
733
734 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
752 let stmt = schema_editor.drop_table_statement(&self.name, false);
753 vec![schema_editor.build_drop_table_sql(&stmt)]
754 }
755}
756
757#[derive(Debug, Clone, Serialize, Deserialize)]
783pub struct RenameModel {
784 pub old_name: String,
786 pub new_name: String,
788}
789
790impl RenameModel {
791 pub fn new(old_name: impl Into<String>, new_name: impl Into<String>) -> Self {
793 Self {
794 old_name: old_name.into(),
795 new_name: new_name.into(),
796 }
797 }
798
799 pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
801 state.rename_model(app_label, &self.old_name, self.new_name.clone());
802 }
803
804 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
823 let db_type = schema_editor.database_type();
825 let old_name = quote_identifier(&self.old_name, db_type);
826 let new_name = quote_identifier(&self.new_name, db_type);
827
828 vec![format!("ALTER TABLE {} RENAME TO {}", old_name, new_name)]
829 }
830}
831
832#[derive(Debug, Clone, Serialize, Deserialize)]
862pub struct MoveModel {
863 pub model_name: String,
865 pub from_app: String,
867 pub to_app: String,
869 pub rename_table: bool,
871 pub old_table_name: Option<String>,
873 pub new_table_name: Option<String>,
875}
876
877impl MoveModel {
878 pub fn new(
892 model_name: impl Into<String>,
893 from_app: impl Into<String>,
894 to_app: impl Into<String>,
895 ) -> Self {
896 Self {
897 model_name: model_name.into(),
898 from_app: from_app.into(),
899 to_app: to_app.into(),
900 rename_table: false,
901 old_table_name: None,
902 new_table_name: None,
903 }
904 }
905
906 pub fn with_table_rename(
921 mut self,
922 old_table: impl Into<String>,
923 new_table: impl Into<String>,
924 ) -> Self {
925 self.rename_table = true;
926 self.old_table_name = Some(old_table.into());
927 self.new_table_name = Some(new_table.into());
928 self
929 }
930
931 pub fn state_forwards(&self, _app_label: &str, state: &mut ProjectState) {
935 if let Some(model) = state
937 .models
938 .remove(&(self.from_app.clone(), self.model_name.clone()))
939 {
940 let mut new_model = model;
942 new_model.app_label = self.to_app.clone();
943
944 state
946 .models
947 .insert((self.to_app.clone(), self.model_name.clone()), new_model);
948 }
949 }
950
951 pub fn state_backwards(&self, _app_label: &str, state: &mut ProjectState) {
955 if let Some(model) = state
957 .models
958 .remove(&(self.to_app.clone(), self.model_name.clone()))
959 {
960 let mut original_model = model;
962 original_model.app_label = self.from_app.clone();
963
964 state.models.insert(
966 (self.from_app.clone(), self.model_name.clone()),
967 original_model,
968 );
969 }
970 }
971
972 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
999 if self.rename_table {
1000 if let (Some(old_table), Some(new_table)) = (&self.old_table_name, &self.new_table_name)
1001 {
1002 let db_type = schema_editor.database_type();
1004 let old_name = quote_identifier(old_table, db_type);
1005 let new_name = quote_identifier(new_table, db_type);
1006
1007 vec![format!("ALTER TABLE {} RENAME TO {}", old_name, new_name)]
1008 } else {
1009 vec![]
1010 }
1011 } else {
1012 vec![]
1014 }
1015 }
1016
1017 pub fn database_backwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
1019 if self.rename_table {
1020 if let (Some(old_table), Some(new_table)) = (&self.old_table_name, &self.new_table_name)
1021 {
1022 let db_type = schema_editor.database_type();
1025 let old_name = quote_identifier(old_table, db_type);
1026 let new_name = quote_identifier(new_table, db_type);
1027
1028 vec![format!("ALTER TABLE {} RENAME TO {}", new_name, old_name)]
1029 } else {
1030 vec![]
1031 }
1032 } else {
1033 vec![]
1034 }
1035 }
1036}
1037
1038use crate::migrations::operation_trait::MigrationOperation;
1040
1041impl MigrationOperation for CreateModel {
1042 fn migration_name_fragment(&self) -> Option<String> {
1043 Some(self.name.to_lowercase())
1044 }
1045
1046 fn describe(&self) -> String {
1047 format!("Create model {}", self.name)
1048 }
1049}
1050
1051impl MigrationOperation for DeleteModel {
1052 fn migration_name_fragment(&self) -> Option<String> {
1053 Some(format!("delete_{}", self.name.to_lowercase()))
1054 }
1055
1056 fn describe(&self) -> String {
1057 format!("Delete model {}", self.name)
1058 }
1059}
1060
1061impl MigrationOperation for RenameModel {
1062 fn migration_name_fragment(&self) -> Option<String> {
1063 Some(format!(
1064 "rename_{}_to_{}",
1065 self.old_name.to_lowercase(),
1066 self.new_name.to_lowercase()
1067 ))
1068 }
1069
1070 fn describe(&self) -> String {
1071 format!("Rename model {} to {}", self.old_name, self.new_name)
1072 }
1073}
1074
1075impl MigrationOperation for MoveModel {
1076 fn migration_name_fragment(&self) -> Option<String> {
1077 Some(format!(
1078 "move_{}_to_{}",
1079 self.model_name.to_lowercase(),
1080 self.to_app.to_lowercase()
1081 ))
1082 }
1083
1084 fn describe(&self) -> String {
1085 format!(
1086 "Move model {} from {} to {}",
1087 self.model_name, self.from_app, self.to_app
1088 )
1089 }
1090}
1091
1092#[cfg(test)]
1093mod tests {
1094 use super::*;
1095 use crate::migrations::FieldType;
1096
1097 #[test]
1098 fn test_field_definition_to_sql() {
1099 let field = FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>);
1100 let sql = field.to_sql_definition();
1101 assert_eq!(sql, "INTEGER PRIMARY KEY");
1102
1103 let field2 =
1104 FieldDefinition::new("email", FieldType::VarChar(255), false, true, Some("''"));
1105 let sql2 = field2.to_sql_definition();
1106 assert_eq!(sql2, "VARCHAR(255) UNIQUE NOT NULL DEFAULT ''");
1107 }
1108
1109 #[test]
1110 fn test_create_model_state_forwards() {
1111 let mut state = ProjectState::new();
1112 let create = CreateModel::new(
1113 "User",
1114 vec![
1115 FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
1116 FieldDefinition::new(
1117 "name",
1118 FieldType::VarChar(100),
1119 false,
1120 false,
1121 None::<String>,
1122 ),
1123 ],
1124 );
1125
1126 create.state_forwards("myapp", &mut state);
1127
1128 let model = state.get_model("myapp", "User").unwrap();
1129 assert_eq!(model.name, "User");
1130 assert_eq!(model.fields.len(), 2);
1131 assert_eq!(model.fields.get("id").unwrap().name, "id");
1132 assert_eq!(model.fields.get("name").unwrap().name, "name");
1133 }
1134
1135 #[test]
1136 fn test_delete_model_state_forwards() {
1137 let mut state = ProjectState::new();
1138
1139 let create = CreateModel::new(
1141 "User",
1142 vec![FieldDefinition::new(
1143 "id",
1144 FieldType::Integer,
1145 true,
1146 false,
1147 None::<String>,
1148 )],
1149 );
1150 create.state_forwards("myapp", &mut state);
1151 assert!(state.get_model("myapp", "User").is_some());
1152
1153 let delete = DeleteModel::new("User");
1155 delete.state_forwards("myapp", &mut state);
1156 assert!(state.get_model("myapp", "User").is_none());
1157 }
1158
1159 #[test]
1160 fn test_rename_model_state_forwards() {
1161 let mut state = ProjectState::new();
1162
1163 let create = CreateModel::new(
1165 "User",
1166 vec![FieldDefinition::new(
1167 "id",
1168 FieldType::Integer,
1169 true,
1170 false,
1171 None::<String>,
1172 )],
1173 );
1174 create.state_forwards("myapp", &mut state);
1175
1176 let rename = RenameModel::new("User", "Customer");
1178 rename.state_forwards("myapp", &mut state);
1179
1180 assert!(state.get_model("myapp", "User").is_none());
1181 let model = state.get_model("myapp", "Customer").unwrap();
1182 assert_eq!(model.name, "Customer");
1183 }
1184
1185 #[cfg(feature = "db-postgres")]
1186 #[test]
1187 fn test_delete_model_database_forwards() {
1188 use crate::backends::schema::test_utils::MockSchemaEditor;
1189
1190 let delete = DeleteModel::new("users");
1191 let editor = MockSchemaEditor::new();
1192
1193 let sql = delete.database_forwards(&editor);
1194 assert_eq!(sql.len(), 1);
1195 assert_eq!(sql[0], "DROP TABLE IF EXISTS \"users\"");
1196 }
1197
1198 #[cfg(feature = "db-postgres")]
1199 #[test]
1200 fn test_rename_model_database_forwards() {
1201 use crate::backends::schema::test_utils::MockSchemaEditor;
1202
1203 let rename = RenameModel::new("users", "customers");
1204 let editor = MockSchemaEditor::new();
1205
1206 let sql = rename.database_forwards(&editor);
1207 assert_eq!(sql.len(), 1);
1208 assert_eq!(sql[0], "ALTER TABLE \"users\" RENAME TO \"customers\"");
1209 }
1210
1211 #[test]
1212 fn test_field_definition_nullable() {
1213 let field = FieldDefinition::new(
1214 "email",
1215 FieldType::VarChar(255),
1216 false,
1217 false,
1218 None::<String>,
1219 )
1220 .nullable(true);
1221
1222 assert!(field.null);
1223 let sql = field.to_sql_definition();
1224 assert_eq!(sql, "VARCHAR(255)");
1225 }
1226
1227 #[test]
1228 fn test_create_model_with_options() {
1229 let mut options = HashMap::new();
1230 options.insert("db_table".to_string(), "custom_users".to_string());
1231
1232 let create = CreateModel::new(
1233 "User",
1234 vec![FieldDefinition::new(
1235 "id",
1236 FieldType::Integer,
1237 true,
1238 false,
1239 None::<String>,
1240 )],
1241 )
1242 .with_options(options.clone());
1243
1244 assert_eq!(create.options, options);
1245 assert_eq!(
1246 create.options.get("db_table"),
1247 Some(&"custom_users".to_string())
1248 );
1249 }
1250
1251 #[test]
1252 fn test_create_model_with_bases() {
1253 let bases = vec!["BaseModel".to_string(), "Timestamped".to_string()];
1254
1255 let create = CreateModel::new(
1256 "User",
1257 vec![FieldDefinition::new(
1258 "id",
1259 FieldType::Integer,
1260 true,
1261 false,
1262 None::<String>,
1263 )],
1264 )
1265 .with_bases(bases.clone());
1266
1267 assert_eq!(create.bases, bases);
1268 assert_eq!(create.bases.len(), 2);
1269 }
1270
1271 #[test]
1272 fn test_create_model_multiple_fields() {
1273 let mut state = ProjectState::new();
1274
1275 let create = CreateModel::new(
1276 "User",
1277 vec![
1278 FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
1279 FieldDefinition::new(
1280 "username",
1281 FieldType::VarChar(50),
1282 false,
1283 true,
1284 None::<String>,
1285 ),
1286 FieldDefinition::new(
1287 "email",
1288 FieldType::VarChar(255),
1289 false,
1290 true,
1291 None::<String>,
1292 ),
1293 FieldDefinition::new("is_active", FieldType::Boolean, false, false, Some("true")),
1294 ],
1295 );
1296
1297 create.state_forwards("myapp", &mut state);
1298
1299 let model = state.get_model("myapp", "User").unwrap();
1300 assert_eq!(model.fields.len(), 4);
1301 assert_eq!(model.fields.get("id").unwrap().name, "id");
1302 assert_eq!(model.fields.get("username").unwrap().name, "username");
1303 assert_eq!(model.fields.get("email").unwrap().name, "email");
1304 assert_eq!(model.fields.get("is_active").unwrap().name, "is_active");
1305 }
1306
1307 #[test]
1308 fn test_field_definition_with_default() {
1309 let field = FieldDefinition::new(
1310 "status",
1311 FieldType::VarChar(20),
1312 false,
1313 false,
1314 Some("'pending'"),
1315 );
1316
1317 assert_eq!(field.default, Some("'pending'".to_string()));
1318
1319 let sql = field.to_sql_definition();
1320 assert_eq!(sql, "VARCHAR(20) NOT NULL DEFAULT 'pending'");
1321 }
1322
1323 #[test]
1324 fn test_delete_model_removes_from_state() {
1325 let mut state = ProjectState::new();
1326
1327 let create1 = CreateModel::new(
1329 "User",
1330 vec![FieldDefinition::new(
1331 "id",
1332 FieldType::Integer,
1333 true,
1334 false,
1335 None::<String>,
1336 )],
1337 );
1338 let create2 = CreateModel::new(
1339 "Post",
1340 vec![FieldDefinition::new(
1341 "id",
1342 FieldType::Integer,
1343 true,
1344 false,
1345 None::<String>,
1346 )],
1347 );
1348
1349 create1.state_forwards("myapp", &mut state);
1350 create2.state_forwards("myapp", &mut state);
1351
1352 assert!(state.get_model("myapp", "User").is_some());
1353 assert!(state.get_model("myapp", "Post").is_some());
1354
1355 let delete = DeleteModel::new("User");
1357 delete.state_forwards("myapp", &mut state);
1358
1359 assert!(state.get_model("myapp", "User").is_none());
1360 assert!(state.get_model("myapp", "Post").is_some());
1361 }
1362
1363 #[test]
1364 fn test_rename_model_preserves_fields() {
1365 let mut state = ProjectState::new();
1366
1367 let create = CreateModel::new(
1369 "User",
1370 vec![
1371 FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
1372 FieldDefinition::new(
1373 "name",
1374 FieldType::VarChar(100),
1375 false,
1376 false,
1377 None::<String>,
1378 ),
1379 ],
1380 );
1381 create.state_forwards("myapp", &mut state);
1382
1383 let rename = RenameModel::new("User", "Account");
1385 rename.state_forwards("myapp", &mut state);
1386
1387 let model = state.get_model("myapp", "Account").unwrap();
1389 assert_eq!(model.fields.len(), 2);
1390 assert_eq!(model.fields.get("id").unwrap().name, "id");
1391 assert_eq!(model.fields.get("name").unwrap().name, "name");
1392 }
1393
1394 #[test]
1395 fn test_move_model_basic() {
1396 let mut state = ProjectState::new();
1397
1398 let create = CreateModel::new(
1400 "User",
1401 vec![FieldDefinition::new(
1402 "id",
1403 FieldType::Integer,
1404 true,
1405 false,
1406 None::<String>,
1407 )],
1408 );
1409 create.state_forwards("myapp", &mut state);
1410 assert!(state.get_model("myapp", "User").is_some());
1411
1412 let move_op = MoveModel::new("User", "myapp", "auth");
1414 move_op.state_forwards("auth", &mut state);
1415
1416 assert!(state.get_model("myapp", "User").is_none());
1418 assert!(state.get_model("auth", "User").is_some());
1419
1420 let model = state.get_model("auth", "User").unwrap();
1422 assert_eq!(model.app_label, "auth");
1423 }
1424
1425 #[test]
1426 fn test_move_model_preserves_fields() {
1427 let mut state = ProjectState::new();
1428
1429 let create = CreateModel::new(
1431 "User",
1432 vec![
1433 FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
1434 FieldDefinition::new(
1435 "email",
1436 FieldType::VarChar(255),
1437 false,
1438 false,
1439 None::<String>,
1440 ),
1441 FieldDefinition::new(
1442 "name",
1443 FieldType::VarChar(100),
1444 false,
1445 false,
1446 None::<String>,
1447 ),
1448 ],
1449 );
1450 create.state_forwards("myapp", &mut state);
1451
1452 let move_op = MoveModel::new("User", "myapp", "auth");
1454 move_op.state_forwards("auth", &mut state);
1455
1456 let model = state.get_model("auth", "User").unwrap();
1458 assert_eq!(model.fields.len(), 3);
1459 assert_eq!(model.fields.get("id").unwrap().name, "id");
1460 assert_eq!(model.fields.get("email").unwrap().name, "email");
1461 assert_eq!(model.fields.get("name").unwrap().name, "name");
1462 }
1463
1464 #[test]
1465 fn test_move_model_backwards() {
1466 let mut state = ProjectState::new();
1467
1468 let create = CreateModel::new(
1470 "User",
1471 vec![FieldDefinition::new(
1472 "id",
1473 FieldType::Integer,
1474 true,
1475 false,
1476 None::<String>,
1477 )],
1478 );
1479 create.state_forwards("myapp", &mut state);
1480
1481 let move_op = MoveModel::new("User", "myapp", "auth");
1482 move_op.state_forwards("auth", &mut state);
1483 assert!(state.get_model("auth", "User").is_some());
1484
1485 move_op.state_backwards("myapp", &mut state);
1487
1488 assert!(state.get_model("auth", "User").is_none());
1490 assert!(state.get_model("myapp", "User").is_some());
1491
1492 let model = state.get_model("myapp", "User").unwrap();
1493 assert_eq!(model.app_label, "myapp");
1494 }
1495
1496 #[cfg(feature = "db-postgres")]
1497 #[test]
1498 fn test_move_model_without_table_rename() {
1499 use crate::backends::schema::test_utils::MockSchemaEditor;
1500
1501 let move_op = MoveModel::new("User", "myapp", "auth");
1502 let editor = MockSchemaEditor::new();
1503
1504 let sql = move_op.database_forwards(&editor);
1505 assert_eq!(sql.len(), 0);
1507 }
1508
1509 #[cfg(feature = "db-postgres")]
1510 #[test]
1511 fn test_move_model_with_table_rename() {
1512 use crate::backends::schema::test_utils::MockSchemaEditor;
1513
1514 let move_op =
1515 MoveModel::new("User", "myapp", "auth").with_table_rename("myapp_user", "auth_user");
1516
1517 let editor = MockSchemaEditor::new();
1518
1519 let sql = move_op.database_forwards(&editor);
1520 assert_eq!(sql.len(), 1);
1521 assert_eq!(sql[0], "ALTER TABLE \"myapp_user\" RENAME TO \"auth_user\"");
1522 }
1523
1524 #[cfg(feature = "db-postgres")]
1525 #[test]
1526 fn test_move_model_backward_sql() {
1527 use crate::backends::schema::test_utils::MockSchemaEditor;
1528
1529 let move_op =
1530 MoveModel::new("User", "myapp", "auth").with_table_rename("myapp_user", "auth_user");
1531
1532 let editor = MockSchemaEditor::new();
1533
1534 let sql = move_op.database_backwards(&editor);
1535 assert_eq!(sql.len(), 1);
1536 assert_eq!(sql[0], "ALTER TABLE \"auth_user\" RENAME TO \"myapp_user\"");
1538 }
1539}