reinhardt_db/migrations/operations/fields.rs
1//! Field operations for migrations
2//!
3//! This module provides operations for adding, removing, altering, and renaming fields,
4//! inspired by Django's `django/db/migrations/operations/fields.py`.
5//!
6//! # Example
7//!
8//! ```rust
9//! use reinhardt_db::migrations::operations::fields::{AddField, RemoveField};
10//! use reinhardt_db::migrations::operations::FieldDefinition;
11//! use reinhardt_db::migrations::operations::models::CreateModel;
12//! use reinhardt_db::migrations::{ProjectState, FieldType};
13//!
14//! let mut state = ProjectState::new();
15//!
16//! // Create a model first
17//! let create = CreateModel::new(
18//! "User",
19//! vec![FieldDefinition::new("id", FieldType::Integer, true, false, Option::<&str>::None)],
20//! );
21//! create.state_forwards("myapp", &mut state);
22//!
23//! // Add a field
24//! let add = AddField::new("User", FieldDefinition::new("email", FieldType::VarChar(255), false, false, Option::<&str>::None));
25//! add.state_forwards("myapp", &mut state);
26//! assert_eq!(state.get_model("myapp", "User").unwrap().fields.len(), 2);
27//!
28//! // Remove a field
29//! let remove = RemoveField::new("User", "email");
30//! remove.state_forwards("myapp", &mut state);
31//! assert_eq!(state.get_model("myapp", "User").unwrap().fields.len(), 1);
32//! ```
33
34use super::{FieldState, ProjectState};
35use crate::backends::schema::BaseDatabaseSchemaEditor;
36use serde::{Deserialize, Serialize};
37
38pub use super::models::FieldDefinition;
39
40/// Add a field to an existing model
41///
42/// # Example
43///
44/// ```rust
45/// use reinhardt_db::migrations::operations::fields::AddField;
46/// use reinhardt_db::migrations::operations::FieldDefinition;
47/// use reinhardt_db::migrations::operations::models::CreateModel;
48/// use reinhardt_db::migrations::{ProjectState, FieldType};
49///
50/// let mut state = ProjectState::new();
51///
52/// // Create a model first
53/// let create = CreateModel::new(
54/// "User",
55/// vec![FieldDefinition::new("id", FieldType::Integer, true, false, Option::<&str>::None)],
56/// );
57/// create.state_forwards("myapp", &mut state);
58///
59/// // Add a field
60/// let add = AddField::new("User", FieldDefinition::new("email", FieldType::VarChar(255), false, false, Option::<&str>::None));
61/// add.state_forwards("myapp", &mut state);
62///
63/// let model = state.get_model("myapp", "User").unwrap();
64/// assert_eq!(model.fields.len(), 2);
65/// assert!(model.fields.contains_key("email"));
66/// ```
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct AddField {
69 /// The model name.
70 pub model_name: String,
71 /// The field.
72 pub field: FieldDefinition,
73 /// The preserve default.
74 pub preserve_default: bool,
75}
76
77impl AddField {
78 /// Create a new AddField operation
79 pub fn new(model_name: impl Into<String>, field: FieldDefinition) -> Self {
80 Self {
81 model_name: model_name.into(),
82 field,
83 preserve_default: true,
84 }
85 }
86
87 /// Set whether to preserve the default value after adding
88 pub fn with_preserve_default(mut self, preserve: bool) -> Self {
89 self.preserve_default = preserve;
90 self
91 }
92
93 /// Apply to project state (forward)
94 pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
95 if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
96 let field = FieldState::new(
97 self.field.name.clone(),
98 self.field.field_type.clone(),
99 self.field.primary_key,
100 );
101 model.add_field(field);
102 }
103 }
104
105 /// Generate SQL using schema editor
106 ///
107 /// # Example
108 ///
109 /// ```rust,no_run
110 /// use reinhardt_db::migrations::operations::fields::AddField;
111 /// use reinhardt_db::migrations::operations::FieldDefinition;
112 /// use reinhardt_db::migrations::FieldType;
113 /// use reinhardt_db::backends::schema::factory::{SchemaEditorFactory, DatabaseType};
114 ///
115 /// let add = AddField::new("users", FieldDefinition::new("email", FieldType::VarChar(255), false, false, Option::<&str>::None));
116 /// let factory = SchemaEditorFactory::new();
117 /// let editor = factory.create_for_database(DatabaseType::PostgreSQL);
118 ///
119 /// let sql = add.database_forwards(editor.as_ref());
120 /// assert_eq!(sql.len(), 1);
121 /// assert!(sql[0].contains("ALTER TABLE"));
122 /// assert!(sql[0].contains("ADD COLUMN"));
123 /// assert!(sql[0].contains("\"email\""));
124 /// ```
125 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
126 let definition = self.field.to_sql_definition();
127 let stmt =
128 schema_editor.add_column_statement(&self.model_name, &self.field.name, &definition);
129 vec![schema_editor.build_alter_table_sql(&stmt)]
130 }
131}
132
133/// Remove a field from a model
134///
135/// # Example
136///
137/// ```rust
138/// use reinhardt_db::migrations::operations::fields::RemoveField;
139/// use reinhardt_db::migrations::operations::FieldDefinition;
140/// use reinhardt_db::migrations::operations::models::CreateModel;
141/// use reinhardt_db::migrations::{ProjectState, FieldType};
142///
143/// let mut state = ProjectState::new();
144///
145/// // Create a model with fields
146/// let create = CreateModel::new(
147/// "User",
148/// vec![
149/// FieldDefinition::new("id", FieldType::Integer, true, false, Option::<&str>::None),
150/// FieldDefinition::new("email", FieldType::VarChar(255), false, false, Option::<&str>::None),
151/// ],
152/// );
153/// create.state_forwards("myapp", &mut state);
154///
155/// // Remove a field
156/// let remove = RemoveField::new("User", "email");
157/// remove.state_forwards("myapp", &mut state);
158///
159/// let model = state.get_model("myapp", "User").unwrap();
160/// assert_eq!(model.fields.len(), 1);
161/// assert!(!model.fields.contains_key("email"));
162/// ```
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct RemoveField {
165 /// The model name.
166 pub model_name: String,
167 /// The field name.
168 pub field_name: String,
169}
170
171impl RemoveField {
172 /// Create a new RemoveField operation
173 pub fn new(model_name: impl Into<String>, field_name: impl Into<String>) -> Self {
174 Self {
175 model_name: model_name.into(),
176 field_name: field_name.into(),
177 }
178 }
179
180 /// Apply to project state (forward)
181 pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
182 if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
183 model.remove_field(&self.field_name);
184 }
185 }
186
187 /// Generate SQL using schema editor
188 ///
189 /// # Example
190 ///
191 /// ```rust,no_run
192 /// use reinhardt_db::migrations::operations::fields::RemoveField;
193 /// use reinhardt_db::backends::schema::factory::{SchemaEditorFactory, DatabaseType};
194 ///
195 /// let remove = RemoveField::new("users", "email");
196 /// let factory = SchemaEditorFactory::new();
197 /// let editor = factory.create_for_database(DatabaseType::PostgreSQL);
198 ///
199 /// let sql = remove.database_forwards(editor.as_ref());
200 /// assert_eq!(sql.len(), 1);
201 /// assert!(sql[0].contains("ALTER TABLE"));
202 /// assert!(sql[0].contains("DROP COLUMN"));
203 /// assert!(sql[0].contains("\"email\""));
204 /// ```
205 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
206 let stmt = schema_editor.drop_column_statement(&self.model_name, &self.field_name);
207 vec![schema_editor.build_alter_table_sql(&stmt)]
208 }
209}
210
211/// Alter a field's definition
212///
213/// # Example
214///
215/// ```rust
216/// use reinhardt_db::migrations::operations::fields::AlterField;
217/// use reinhardt_db::migrations::operations::FieldDefinition;
218/// use reinhardt_db::migrations::operations::models::CreateModel;
219/// use reinhardt_db::migrations::{ProjectState, FieldType};
220///
221/// let mut state = ProjectState::new();
222///
223/// // Create a model with a field
224/// let create = CreateModel::new(
225/// "User",
226/// vec![
227/// FieldDefinition::new("id", FieldType::Integer, true, false, Option::<&str>::None),
228/// FieldDefinition::new("email", FieldType::VarChar(100), false, false, Option::<&str>::None),
229/// ],
230/// );
231/// create.state_forwards("myapp", &mut state);
232///
233/// // Alter the field to make it longer
234/// let alter = AlterField::new("User", FieldDefinition::new("email", FieldType::VarChar(255), false, false, Option::<&str>::None));
235/// alter.state_forwards("myapp", &mut state);
236///
237/// let model = state.get_model("myapp", "User").unwrap();
238/// let field = model.fields.get("email").unwrap();
239/// assert_eq!(field.field_type, FieldType::VarChar(255));
240/// ```
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct AlterField {
243 /// The model name.
244 pub model_name: String,
245 /// The field.
246 pub field: FieldDefinition,
247}
248
249impl AlterField {
250 /// Create a new AlterField operation
251 pub fn new(model_name: impl Into<String>, field: FieldDefinition) -> Self {
252 Self {
253 model_name: model_name.into(),
254 field,
255 }
256 }
257
258 /// Apply to project state (forward)
259 pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
260 if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
261 let field = FieldState::new(
262 self.field.name.clone(),
263 self.field.field_type.clone(),
264 self.field.primary_key,
265 );
266 model.alter_field(&self.field.name, field);
267 }
268 }
269
270 /// Generate SQL using schema editor
271 ///
272 /// Note: Altering columns is database-specific and complex.
273 /// This is a simplified version that may need enhancement.
274 ///
275 /// # Example
276 ///
277 /// ```rust,no_run
278 /// use reinhardt_db::migrations::operations::fields::AlterField;
279 /// use reinhardt_db::migrations::operations::FieldDefinition;
280 /// use reinhardt_db::migrations::FieldType;
281 /// use reinhardt_db::backends::schema::factory::{SchemaEditorFactory, DatabaseType};
282 ///
283 /// let alter = AlterField::new("users", FieldDefinition::new("email", FieldType::VarChar(500), false, false, Option::<&str>::None));
284 /// let factory = SchemaEditorFactory::new();
285 /// let editor = factory.create_for_database(DatabaseType::PostgreSQL);
286 ///
287 /// let sql = alter.database_forwards(editor.as_ref());
288 /// assert!(!sql.is_empty());
289 /// ```
290 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
291 // Use database-specific ALTER COLUMN statement from schema editor
292 // Each database backend (PostgreSQL, MySQL, SQLite, CockroachDB) provides
293 // its own implementation via the alter_column_statement() method
294 vec![schema_editor.alter_column_statement(
295 &self.model_name,
296 &self.field.name,
297 &self.field.field_type.to_sql_string(),
298 )]
299 }
300}
301
302/// Rename a field
303///
304/// # Example
305///
306/// ```rust
307/// use reinhardt_db::migrations::operations::fields::RenameField;
308/// use reinhardt_db::migrations::operations::FieldDefinition;
309/// use reinhardt_db::migrations::operations::models::CreateModel;
310/// use reinhardt_db::migrations::{ProjectState, FieldType};
311///
312/// let mut state = ProjectState::new();
313///
314/// // Create a model with a field
315/// let create = CreateModel::new(
316/// "User",
317/// vec![
318/// FieldDefinition::new("id", FieldType::Integer, true, false, Option::<&str>::None),
319/// FieldDefinition::new("email", FieldType::VarChar(255), false, false, Option::<&str>::None),
320/// ],
321/// );
322/// create.state_forwards("myapp", &mut state);
323///
324/// // Rename the field
325/// let rename = RenameField::new("User", "email", "email_address");
326/// rename.state_forwards("myapp", &mut state);
327///
328/// let model = state.get_model("myapp", "User").unwrap();
329/// assert!(!model.fields.contains_key("email"));
330/// assert!(model.fields.contains_key("email_address"));
331/// ```
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct RenameField {
334 /// The model name.
335 pub model_name: String,
336 /// The old name.
337 pub old_name: String,
338 /// The new name.
339 pub new_name: String,
340}
341
342impl RenameField {
343 /// Create a new RenameField operation
344 pub fn new(
345 model_name: impl Into<String>,
346 old_name: impl Into<String>,
347 new_name: impl Into<String>,
348 ) -> Self {
349 Self {
350 model_name: model_name.into(),
351 old_name: old_name.into(),
352 new_name: new_name.into(),
353 }
354 }
355
356 /// Apply to project state (forward)
357 pub fn state_forwards(&self, app_label: &str, state: &mut ProjectState) {
358 if let Some(model) = state.get_model_mut(app_label, &self.model_name) {
359 model.rename_field(&self.old_name, self.new_name.clone());
360 }
361 }
362
363 /// Generate SQL using schema editor
364 ///
365 /// # Example
366 ///
367 /// ```rust,no_run
368 /// use reinhardt_db::migrations::operations::fields::RenameField;
369 /// use reinhardt_db::backends::schema::factory::{SchemaEditorFactory, DatabaseType};
370 ///
371 /// let rename = RenameField::new("users", "email", "email_address");
372 /// let factory = SchemaEditorFactory::new();
373 /// let editor = factory.create_for_database(DatabaseType::PostgreSQL);
374 ///
375 /// let sql = rename.database_forwards(editor.as_ref());
376 /// assert_eq!(sql.len(), 1);
377 /// assert!(sql[0].contains("ALTER TABLE"));
378 /// assert!(sql[0].contains("RENAME COLUMN"));
379 /// assert!(sql[0].contains("\"email\""));
380 /// assert!(sql[0].contains("\"email_address\""));
381 /// ```
382 pub fn database_forwards(&self, schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
383 vec![schema_editor.rename_column_statement(
384 &self.model_name,
385 &self.old_name,
386 &self.new_name,
387 )]
388 }
389}
390
391// MigrationOperation trait implementation for Django-style naming
392use crate::migrations::operation_trait::MigrationOperation;
393
394impl MigrationOperation for AddField {
395 fn migration_name_fragment(&self) -> Option<String> {
396 Some(format!(
397 "{}_{}",
398 self.model_name.to_lowercase(),
399 self.field.name.to_lowercase()
400 ))
401 }
402
403 fn describe(&self) -> String {
404 format!("Add field {} to {}", self.field.name, self.model_name)
405 }
406}
407
408impl MigrationOperation for RemoveField {
409 fn migration_name_fragment(&self) -> Option<String> {
410 Some(format!(
411 "remove_{}_{}",
412 self.model_name.to_lowercase(),
413 self.field_name.to_lowercase()
414 ))
415 }
416
417 fn describe(&self) -> String {
418 format!("Remove field {} from {}", self.field_name, self.model_name)
419 }
420}
421
422impl MigrationOperation for AlterField {
423 fn migration_name_fragment(&self) -> Option<String> {
424 Some(format!(
425 "alter_{}_{}",
426 self.model_name.to_lowercase(),
427 self.field.name.to_lowercase()
428 ))
429 }
430
431 fn describe(&self) -> String {
432 format!("Alter field {} on {}", self.field.name, self.model_name)
433 }
434}
435
436impl MigrationOperation for RenameField {
437 fn migration_name_fragment(&self) -> Option<String> {
438 Some(format!(
439 "rename_{}_{}",
440 self.model_name.to_lowercase(),
441 self.new_name.to_lowercase()
442 ))
443 }
444
445 fn describe(&self) -> String {
446 format!(
447 "Rename field {} to {} on {}",
448 self.old_name, self.new_name, self.model_name
449 )
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use crate::migrations::FieldType;
457 use crate::migrations::operations::models::CreateModel;
458
459 #[test]
460 fn test_add_field_state_forwards() {
461 let mut state = ProjectState::new();
462
463 // Create a model first
464 let create = CreateModel::new(
465 "User",
466 vec![FieldDefinition::new(
467 "id",
468 FieldType::Integer,
469 true,
470 false,
471 None::<String>,
472 )],
473 );
474 create.state_forwards("myapp", &mut state);
475
476 // Add a field
477 let add = AddField::new(
478 "User",
479 FieldDefinition::new(
480 "email",
481 FieldType::VarChar(255),
482 false,
483 false,
484 None::<String>,
485 ),
486 );
487 add.state_forwards("myapp", &mut state);
488
489 let model = state.get_model("myapp", "User").unwrap();
490 assert_eq!(model.fields.len(), 2);
491 assert!(model.fields.contains_key("email"));
492 }
493
494 #[test]
495 fn test_remove_field_state_forwards() {
496 let mut state = ProjectState::new();
497
498 // Create a model with fields
499 let create = CreateModel::new(
500 "User",
501 vec![
502 FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
503 FieldDefinition::new(
504 "email",
505 FieldType::VarChar(255),
506 false,
507 false,
508 None::<String>,
509 ),
510 ],
511 );
512 create.state_forwards("myapp", &mut state);
513
514 // Remove a field
515 let remove = RemoveField::new("User", "email");
516 remove.state_forwards("myapp", &mut state);
517
518 let model = state.get_model("myapp", "User").unwrap();
519 assert_eq!(model.fields.len(), 1);
520 assert!(!model.fields.contains_key("email"));
521 }
522
523 #[test]
524 fn test_alter_field_state_forwards() {
525 let mut state = ProjectState::new();
526
527 // Create a model with a field
528 let create = CreateModel::new(
529 "User",
530 vec![
531 FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
532 FieldDefinition::new(
533 "email",
534 FieldType::VarChar(100),
535 false,
536 false,
537 None::<String>,
538 ),
539 ],
540 );
541 create.state_forwards("myapp", &mut state);
542
543 // Alter the field
544 let alter = AlterField::new(
545 "User",
546 FieldDefinition::new(
547 "email",
548 FieldType::VarChar(255),
549 false,
550 false,
551 None::<String>,
552 ),
553 );
554 alter.state_forwards("myapp", &mut state);
555
556 let model = state.get_model("myapp", "User").unwrap();
557 let field = model.fields.get("email").unwrap();
558 assert_eq!(field.field_type, FieldType::VarChar(255));
559 }
560
561 #[test]
562 fn test_rename_field_state_forwards() {
563 let mut state = ProjectState::new();
564
565 // Create a model with a field
566 let create = CreateModel::new(
567 "User",
568 vec![
569 FieldDefinition::new("id", FieldType::Integer, true, false, None::<String>),
570 FieldDefinition::new(
571 "email",
572 FieldType::VarChar(255),
573 false,
574 false,
575 None::<String>,
576 ),
577 ],
578 );
579 create.state_forwards("myapp", &mut state);
580
581 // Rename the field
582 let rename = RenameField::new("User", "email", "email_address");
583 rename.state_forwards("myapp", &mut state);
584
585 let model = state.get_model("myapp", "User").unwrap();
586 assert!(!model.fields.contains_key("email"));
587 assert!(model.fields.contains_key("email_address"));
588 }
589
590 #[cfg(feature = "postgres")]
591 #[test]
592 fn test_add_field_database_forwards() {
593 use crate::backends::schema::test_utils::MockSchemaEditor;
594
595 let add = AddField::new(
596 "users",
597 FieldDefinition::new(
598 "email",
599 FieldType::VarChar(255),
600 false,
601 false,
602 None::<String>,
603 ),
604 );
605 let editor = MockSchemaEditor::new();
606
607 let sql = add.database_forwards(&editor);
608 assert_eq!(sql.len(), 1);
609 assert!(sql[0].contains("ALTER TABLE"));
610 assert!(sql[0].contains("ADD COLUMN"));
611 assert!(sql[0].contains("\"email\""));
612 }
613
614 #[cfg(feature = "postgres")]
615 #[test]
616 fn test_remove_field_database_forwards() {
617 use crate::backends::schema::test_utils::MockSchemaEditor;
618
619 let remove = RemoveField::new("users", "email");
620 let editor = MockSchemaEditor::new();
621
622 let sql = remove.database_forwards(&editor);
623 assert_eq!(sql.len(), 1);
624 assert!(sql[0].contains("ALTER TABLE"));
625 assert!(sql[0].contains("DROP COLUMN"));
626 assert!(sql[0].contains("\"email\""));
627 }
628
629 #[cfg(feature = "postgres")]
630 #[test]
631 fn test_rename_field_database_forwards() {
632 use crate::backends::schema::test_utils::MockSchemaEditor;
633
634 let rename = RenameField::new("users", "email", "email_address");
635 let editor = MockSchemaEditor::new();
636
637 let sql = rename.database_forwards(&editor);
638 assert_eq!(sql.len(), 1);
639 assert!(sql[0].contains("ALTER TABLE"));
640 assert!(sql[0].contains("RENAME COLUMN"));
641 assert!(sql[0].contains("\"email\""));
642 assert!(sql[0].contains("\"email_address\""));
643 }
644}