Skip to main content

reinhardt_db/
migrations.rs

1//! # Reinhardt Migrations
2//!
3//! Database migration system for Reinhardt framework.
4//!
5//! ## Features
6//!
7//! - **Auto-detection**: Detects model changes and generates migrations.
8//!   Same-app `CreateTable` operations follow foreign-key order from
9//!   `FieldState.foreign_key` and model constraints, not lexicographic table names.
10//! - **Migration Graph**: Manages dependencies between migrations
11//! - **AST-Based Entry Points**: Generates Rust 2024 Edition-compliant module files
12//! - **State Reconstruction**: Django-style `ProjectState` building from migration history
13//! - **Zero Downtime**: Support for safe schema changes in production
14//!
15//! ## AST-Based Entry Point Generation
16//!
17//! The `makemigrations` command uses Abstract Syntax Tree (AST) parsing to generate
18//! and maintain migration entry point files (`migrations/app_name.rs`). This ensures:
19//!
20//! 1. **Rust 2024 Edition Compliance**: Uses `app_name.rs` instead of deprecated `mod.rs`
21//! 2. **Robust Module Detection**: Structurally identifies existing migration modules
22//! 3. **Consistent Formatting**: Standardized output via `prettyplease`
23//!
24//! ### Generated Entry Point Example
25//!
26//! The migration system automatically generates entry point files:
27//!
28//! ```rust,ignore
29//! // migrations/myapp.rs (auto-generated - example only)
30//! pub mod _0001_initial;
31//! pub mod _0002_add_field;
32//!
33//! pub fn all_migrations() -> Vec<fn() -> Migration> {
34//!     vec![_0001_initial::migration, _0002_add_field::migration]
35//! }
36//! ```
37//!
38//! This file is automatically updated when new migrations are created.
39
40pub mod ast_parser;
41pub mod auto_migration;
42pub mod autodetector;
43pub mod dependency;
44pub mod di_support;
45pub mod executor;
46pub mod fields;
47pub mod graph;
48pub mod introspect;
49pub mod introspection;
50pub mod migration;
51pub mod migration_namer;
52pub mod migration_numbering;
53pub mod model_registry;
54pub mod operation_trait;
55pub mod operations;
56pub mod plan;
57pub mod recorder;
58pub mod registry;
59pub mod repository;
60pub mod schema_diff;
61pub mod schema_editor;
62pub mod service;
63pub mod source;
64#[cfg(feature = "sqlite")]
65pub(crate) mod sqlite_pragma;
66pub mod squash;
67pub mod state_loader;
68pub mod visualization;
69pub mod zero_downtime;
70
71#[cfg(feature = "contenttypes")]
72pub use crate::contenttypes::migration::MigrationRecord;
73pub use autodetector::{
74	// Pattern Learning and Inference
75	ChangeTracker,
76	ConstraintDefinition,
77	DetectedChanges,
78	FieldState,
79	ForeignKeyAction,
80	ForeignKeyConstraintInfo,
81	ForeignKeyInfo,
82	IndexDefinition,
83	InferenceEngine,
84	InferenceRule,
85	InferredIntent,
86	InteractiveAutodetector,
87	MigrationAutodetector,
88	MigrationPrompt,
89	ModelState,
90	OperationRef,
91	PatternMatcher,
92	ProjectState,
93	RuleCondition,
94	SimilarityConfig,
95	to_snake_case,
96};
97pub use dependency::{
98	DependencyCondition, DependencyResolutionContext, DependencyResolver, MigrationDependency,
99	OptionalDependency, SwappableDependency,
100};
101pub use di_support::{MigrationConfig, MigrationService as DIMigrationService};
102pub use executor::{DatabaseMigrationExecutor, ExecutionResult, OperationOptimizer};
103pub use fields::FieldType;
104pub use graph::{MigrationGraph, MigrationKey, MigrationNode};
105pub use migration::Migration;
106pub use migration_namer::MigrationNamer;
107pub use migration_numbering::MigrationNumbering;
108pub use model_registry::{
109	FieldMetadata, ManyToManyMetadata, ModelMetadata, ModelRegistry, RelationshipMetadata,
110	global_registry,
111};
112// Re-export the crate-root M2M naming helpers so callers can continue to
113// import them from `reinhardt_db::migrations::*` or
114// `reinhardt_db::migrations::naming::*`. The actual module lives at the
115// crate root because the `orm` and `migrations` features are independent.
116pub use crate::m2m_naming as naming;
117pub use crate::m2m_naming::{default_m2m_columns, default_through_table};
118pub use operation_trait::MigrationOperation;
119pub use operations::{
120	AddColumn, AlterColumn, AlterTableOptions, BulkLoadFormat, BulkLoadOptions, BulkLoadSource,
121	ColumnDefinition, Constraint, CreateTable, DeferrableOption, DropColumn, IndexType,
122	InterleaveSpec, MySqlAlgorithm, MySqlLock, Operation, PartitionDef, PartitionOptions,
123	PartitionType, PartitionValues, SqlDialect, field_type_string_to_field_type,
124};
125pub use plan::{MigrationPlan, TransactionMode};
126
127// New operations from refactored modules
128pub use auto_migration::{
129	AutoMigrationError, AutoMigrationGenerator, AutoMigrationResult, ValidationResult,
130};
131pub use operations::{
132	AddField, AlterField, CreateCollation, CreateExtension, CreateModel, DeleteModel,
133	DropExtension, FieldDefinition, MoveModel, RemoveField, RenameField, RenameModel, RunCode,
134	RunSQL, StateOperation, special::DataMigration,
135};
136pub use recorder::{DatabaseMigrationRecorder, MigrationRecorder};
137pub use repository::{MigrationRepository, filesystem::FilesystemRepository};
138pub use schema_diff::{
139	ColumnSchema, ConstraintSchema, DatabaseSchema, ForeignKeySchemaInfo, IndexSchema, SchemaDiff,
140	SchemaDiffResult, TableSchema,
141};
142pub use schema_editor::SchemaEditor;
143pub use service::MigrationService;
144pub use source::{
145	MigrationSource, composite::CompositeSource, filesystem::FilesystemSource,
146	registry::RegistrySource,
147};
148pub use squash::{MigrationSquasher, SquashOptions};
149pub use state_loader::{MigrationStateLoader, build_state_from_files};
150pub use visualization::{HistoryEntry, MigrationStats, MigrationVisualizer, OutputFormat};
151pub use zero_downtime::{MigrationPhase, Strategy, ZeroDowntimeMigration};
152
153pub use introspect::{
154	GeneratedFile, GeneratedOutput, GenerationConfig, IntrospectConfig, NamingConvention,
155	OutputConfig, SchemaCodeGenerator, TableFilterConfig, TypeMapper, TypeMappingError,
156	escape_rust_keyword, generate_models, preview_output, sanitize_identifier, to_pascal_case,
157	write_output,
158};
159pub use introspection::{
160	ColumnInfo, DatabaseIntrospector, ForeignKeyInfo as IntrospectionForeignKeyInfo, IndexInfo,
161	TableInfo, UniqueConstraintInfo,
162};
163
164// Re-export types from reinhardt-backends for convenience
165pub use crate::backends::{DatabaseConnection, DatabaseType};
166
167use thiserror::Error;
168
169/// Trait for types that provide migrations.
170///
171/// This trait enables compile-time migration collection, which is necessary
172/// because Rust cannot dynamically load code at runtime like Python's Django.
173///
174/// # Example
175///
176/// Application-side implementation (migration modules would be generated):
177///
178/// ```rust,ignore
179/// use reinhardt_db::migrations::{Migration, MigrationProvider};
180///
181/// // In your application's migrations module
182/// // These modules would be generated by `makemigrations` command:
183/// // pub mod _0001_initial;
184/// // pub mod _0002_add_published;
185///
186/// pub struct PollsMigrations;
187///
188/// impl MigrationProvider for PollsMigrations {
189///     fn migrations() -> Vec<Migration> {
190///         vec![
191///             _0001_initial::migration(),
192///             _0002_add_published::migration(),
193///         ]
194///     }
195/// }
196///
197/// // Usage in tests:
198/// // let (container, db) = postgres_with_migrations_from::<PollsMigrations>().await;
199/// ```
200pub trait MigrationProvider {
201	/// Returns all migrations provided by this type.
202	///
203	/// Migrations should be returned in dependency order (base migrations first).
204	fn migrations() -> Vec<Migration>;
205}
206
207/// Errors that can occur during migration operations.
208#[non_exhaustive]
209#[derive(Debug, Error)]
210pub enum MigrationError {
211	/// The requested migration was not found.
212	#[error("Migration not found: {0}")]
213	NotFound(String),
214
215	/// A migration dependency could not be resolved.
216	#[error("Dependency error: {0}")]
217	DependencyError(String),
218
219	/// An SQL execution error occurred.
220	#[error("SQL error: {0}")]
221	SqlError(#[from] sqlx::Error),
222
223	/// A database backend error occurred.
224	#[error("Database error: {0}")]
225	DatabaseError(#[from] crate::backends::QueryDatabaseError),
226
227	/// The migration definition is invalid.
228	#[error("Invalid migration: {0}")]
229	InvalidMigration(String),
230
231	/// The migration cannot be reversed.
232	#[error("Irreversible migration: {0}")]
233	IrreversibleError(String),
234
235	/// An I/O error occurred during migration.
236	#[error("IO error: {0}")]
237	IoError(#[from] std::io::Error),
238
239	/// A formatting error occurred.
240	#[error("Format error: {0}")]
241	FmtError(#[from] std::fmt::Error),
242
243	/// Circular dependency detected in migration graph.
244	#[error("Circular dependency detected: {cycle}")]
245	CircularDependency {
246		/// Description of the dependency cycle.
247		cycle: String,
248	},
249
250	/// A required migration node was not found.
251	#[error("Node not found: {message} - {node}")]
252	NodeNotFound {
253		/// The error message.
254		message: String,
255		/// The node identifier.
256		node: String,
257	},
258
259	/// An error occurred during database introspection.
260	#[error("Introspection error: {0}")]
261	IntrospectionError(String),
262
263	/// The database type is not supported.
264	#[error("Unsupported database: {0}")]
265	UnsupportedDatabase(String),
266
267	/// Duplicate operations detected
268	///
269	/// This error occurs when a new migration has identical operations
270	/// to an existing migration, which usually indicates a problem with
271	/// from_state construction during makemigrations.
272	#[error("Duplicate operations: {0}")]
273	DuplicateOperations(String),
274
275	/// Foreign key integrity violation during table recreation
276	///
277	/// This error occurs when SQLite table recreation results in orphaned
278	/// foreign key references, indicating data integrity issues that must
279	/// be resolved before the migration can proceed.
280	#[error("Foreign key violation: {0}")]
281	ForeignKeyViolation(String),
282
283	/// Path traversal attempt detected in migration path components
284	///
285	/// This error occurs when an app label or migration name contains
286	/// path traversal sequences (e.g., `..`) that could escape the
287	/// migration root directory.
288	#[error("Path traversal detected: {0}")]
289	PathTraversal(String),
290}
291
292/// Type alias for result.
293pub type Result<T> = std::result::Result<T, MigrationError>;
294
295// Prelude for migrations
296/// Prelude module.
297pub mod prelude {
298	pub use super::fields::prelude::*;
299	pub use super::{ColumnDefinition, Constraint, ForeignKeyAction, Migration, Operation};
300}