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