use super::{FieldState, ProjectState};
use crate::backends::schema::BaseDatabaseSchemaEditor;
use serde::{Deserialize, Serialize};
pub use super::models::FieldDefinition;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddField {
pub model_name: String,
pub field: FieldDefinition,
pub preserve_default: bool,
}
impl AddField {
pub fn new(model_name: impl Into<String>, field: FieldDefinition) -> Self {
Self {
model_name: model_name.into(),
field,
preserve_default: true,
}
}
pub fn with_preserve_default(mut self, preserve: bool) -> Self {
self.preserve_default = preserve;
self
}
pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
let field = FieldState::new(
self.field.name.clone(),
self.field.field_type.clone(),
self.field.primary_key,
);
model.add_field(field);
}
}
pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
let definition = self.field.to_sql_definition();
let stmt =
schema_editor.add_column_statement(&self.model_name, &self.field.name, &definition);
vec![schema_editor.build_alter_table_sql(&stmt)]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoveField {
pub model_name: String,
pub field_name: String,
}
impl RemoveField {
pub fn new(model_name: impl Into<String>, field_name: impl Into<String>) -> Self {
Self {
model_name: model_name.into(),
field_name: field_name.into(),
}
}
pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
model.remove_field(&self.field_name);
}
}
pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
let stmt = schema_editor.drop_column_statement(&self.model_name, &self.field_name);
vec![schema_editor.build_alter_table_sql(&stmt)]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlterField {
pub model_name: String,
pub field: FieldDefinition,
}
impl AlterField {
pub fn new(model_name: impl Into<String>, field: FieldDefinition) -> Self {
Self {
model_name: model_name.into(),
field,
}
}
pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
let field = FieldState::new(
self.field.name.clone(),
self.field.field_type.clone(),
self.field.primary_key,
);
model.alter_field(&self.field.name, field);
}
}
pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
vec![schema_editor.alter_column_statement(
&self.model_name,
&self.field.name,
&self.field.field_type.to_sql_string(),
)]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RenameField {
pub model_name: String,
pub old_name: String,
pub new_name: String,
}
impl RenameField {
pub fn new(
model_name: impl Into<String>,
old_name: impl Into<String>,
new_name: impl Into<String>,
) -> Self {
Self {
model_name: model_name.into(),
old_name: old_name.into(),
new_name: new_name.into(),
}
}
pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
model.rename_field(&self.old_name, self.new_name.clone());
}
}
pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
vec![schema_editor.rename_column_statement(
&self.model_name,
&self.old_name,
&self.new_name,
)]
}
}
use crate::migrations::operation_trait::MigrationOperation;
impl MigrationOperation for AddField {
fn migration_name_fragment(&self) -> Option<String> {
Some(format!(
"{}_{}",
self.model_name.to_lowercase(),
self.field.name.to_lowercase()
))
}
fn describe(&self) -> String {
format!("Add field {} to {}", self.field.name, self.model_name)
}
}
impl MigrationOperation for RemoveField {
fn migration_name_fragment(&self) -> Option<String> {
Some(format!(
"remove_{}_{}",
self.model_name.to_lowercase(),
self.field_name.to_lowercase()
))
}
fn describe(&self) -> String {
format!("Remove field {} from {}", self.field_name, self.model_name)
}
}
impl MigrationOperation for AlterField {
fn migration_name_fragment(&self) -> Option<String> {
Some(format!(
"alter_{}_{}",
self.model_name.to_lowercase(),
self.field.name.to_lowercase()
))
}
fn describe(&self) -> String {
format!("Alter field {} on {}", self.field.name, self.model_name)
}
}
impl MigrationOperation for RenameField {
fn migration_name_fragment(&self) -> Option<String> {
Some(format!(
"rename_{}_{}",
self.model_name.to_lowercase(),
self.new_name.to_lowercase()
))
}
fn describe(&self) -> String {
format!(
"Rename field {} to {} on {}",
self.old_name, self.new_name, self.model_name
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::migrations::FieldType;
use crate::migrations::operations::models::CreateModel;
#[test]
fn test_add_field_state_forwards() {
let mut state = ProjectState::new();
let create = CreateModel::new(
"User",
vec![FieldDefinition::new(
"id",
FieldType::Integer,
true,
false,
None::<String>,
)],
);
create.state_forwards("myapp", &mut state);
let add = AddField::new(
"User",
FieldDefinition::new(
"email",
FieldType::VarChar(255),
false,
false,
None::<String>,
),
);
add.state_forwards("myapp", &mut state);
let model = state.get_model("myapp", "User").unwrap();
assert_eq!(model.fields.len(), 2);
assert!(model.fields.contains_key("email"));
}
#[test]
fn test_remove_field_state_forwards() {
let mut state = ProjectState::new();
let create = CreateModel::new(
"User",
vec![
FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
FieldDefinition::new(
"email",
FieldType::VarChar(255),
false,
false,
None::<String>,
),
],
);
create.state_forwards("myapp", &mut state);
let remove = RemoveField::new("User", "email");
remove.state_forwards("myapp", &mut state);
let model = state.get_model("myapp", "User").unwrap();
assert_eq!(model.fields.len(), 1);
assert!(!model.fields.contains_key("email"));
}
#[test]
fn test_alter_field_state_forwards() {
let mut state = ProjectState::new();
let create = CreateModel::new(
"User",
vec![
FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
FieldDefinition::new(
"email",
FieldType::VarChar(100),
false,
false,
None::<String>,
),
],
);
create.state_forwards("myapp", &mut state);
let alter = AlterField::new(
"User",
FieldDefinition::new(
"email",
FieldType::VarChar(255),
false,
false,
None::<String>,
),
);
alter.state_forwards("myapp", &mut state);
let model = state.get_model("myapp", "User").unwrap();
let field = model.fields.get("email").unwrap();
assert_eq!(field.field_type, FieldType::VarChar(255));
}
#[test]
fn test_rename_field_state_forwards() {
let mut state = ProjectState::new();
let create = CreateModel::new(
"User",
vec![
FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
FieldDefinition::new(
"email",
FieldType::VarChar(255),
false,
false,
None::<String>,
),
],
);
create.state_forwards("myapp", &mut state);
let rename = RenameField::new("User", "email", "email_address");
rename.state_forwards("myapp", &mut state);
let model = state.get_model("myapp", "User").unwrap();
assert!(!model.fields.contains_key("email"));
assert!(model.fields.contains_key("email_address"));
}
#[cfg(feature = "postgres")]
#[test]
fn test_add_field_database_forwards() {
use crate::backends::schema::test_utils::MockSchemaEditor;
let add = AddField::new(
"users",
FieldDefinition::new(
"email",
FieldType::VarChar(255),
false,
false,
None::<String>,
),
);
let editor = MockSchemaEditor::new();
let sql = add.database_forwards(&editor);
assert_eq!(sql.len(), 1);
assert!(sql[0].contains("ALTER TABLE"));
assert!(sql[0].contains("ADD COLUMN"));
assert!(sql[0].contains("\"email\""));
}
#[cfg(feature = "postgres")]
#[test]
fn test_remove_field_database_forwards() {
use crate::backends::schema::test_utils::MockSchemaEditor;
let remove = RemoveField::new("users", "email");
let editor = MockSchemaEditor::new();
let sql = remove.database_forwards(&editor);
assert_eq!(sql.len(), 1);
assert!(sql[0].contains("ALTER TABLE"));
assert!(sql[0].contains("DROP COLUMN"));
assert!(sql[0].contains("\"email\""));
}
#[cfg(feature = "postgres")]
#[test]
fn test_rename_field_database_forwards() {
use crate::backends::schema::test_utils::MockSchemaEditor;
let rename = RenameField::new("users", "email", "email_address");
let editor = MockSchemaEditor::new();
let sql = rename.database_forwards(&editor);
assert_eq!(sql.len(), 1);
assert!(sql[0].contains("ALTER TABLE"));
assert!(sql[0].contains("RENAME COLUMN"));
assert!(sql[0].contains("\"email\""));
assert!(sql[0].contains("\"email_address\""));
}
}