Skip to main content

waypoint_core/
lib.rs

1//! Lightweight, Flyway-compatible SQL migration library.
2//!
3//! Targets PostgreSQL (default `postgres` feature) and MySQL 8.0+ (opt-in
4//! `mysql` feature). Build with both features for mixed-engine multi-database
5//! configurations.
6//!
7//! # Quick Start
8//!
9//! ```rust,no_run
10//! use waypoint_core::config::{CliOverrides, WaypointConfig};
11//! use waypoint_core::Waypoint;
12//!
13//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
14//! // `None` reads ./waypoint.toml; pass Some(path) for a different file.
15//! let config = WaypointConfig::load(None, &CliOverrides::default())?;
16//! let wp = Waypoint::new(config).await?;
17//! let report = wp.migrate(None).await?;
18//! println!("Applied {} migrations", report.migrations_applied);
19//! # Ok(())
20//! # }
21//! ```
22//!
23//! # Architecture
24//!
25//! - [`config`] — Configuration loading (TOML, env vars, CLI overrides)
26//! - [`dialect`] — Engine-specific dialect (Postgres / MySQL) abstraction
27//! - [`migration`] — Migration file parsing and scanning
28//! - [`db`] — Database connections, TLS, advisory locks
29//! - [`history`] — Schema history table operations
30//! - [`commands`] — Individual command implementations
31//! - [`checksum`] — CRC32 checksums (Flyway-compatible)
32//! - [`placeholder`] — `${key}` placeholder replacement in SQL
33//! - [`hooks`] — SQL callback hooks (before/after migrate)
34//! - [`directive`] — `-- waypoint:*` comment directive parsing
35//! - [`guard`] — Guard expression parser and evaluator for pre/post conditions
36//! - [`sql_parser`] — Regex-based DDL extraction
37//! - [`safety`] — Migration safety analysis (lock levels, impact, verdicts)
38//! - [`schema`] — Schema introspection + diff
39//! - [`dependency`] — Migration dependency graph
40//! - [`preflight`] — Pre-migration health checks
41//! - [`multi`] — Multi-database orchestration
42//! - [`error`] — Error types
43
44pub mod advisor;
45pub mod checksum;
46pub mod commands;
47pub mod config;
48pub mod db;
49pub mod dependency;
50pub mod dialect;
51pub mod directive;
52pub mod engines;
53pub mod error;
54pub mod guard;
55pub mod history;
56pub mod hooks;
57pub mod migration;
58pub mod multi;
59pub mod placeholder;
60pub mod preflight;
61pub mod reversal;
62pub mod safety;
63pub mod schema;
64pub mod sql_parser;
65pub mod tls;
66
67use std::path::PathBuf;
68
69use config::WaypointConfig;
70use db::DbClient;
71use error::Result;
72
73#[cfg(feature = "postgres")]
74use tokio_postgres::Client;
75
76pub use advisor::AdvisorReport;
77pub use commands::changelog::ChangelogReport;
78pub use commands::check_conflicts::ConflictReport;
79pub use commands::diff::DiffReport;
80pub use commands::drift::DriftReport;
81pub use commands::explain::ExplainReport;
82pub use commands::info::{MigrationInfo, MigrationState};
83pub use commands::lint::LintReport;
84pub use commands::migrate::MigrateReport;
85pub use commands::repair::RepairReport;
86pub use commands::safety::SafetyCommandReport;
87pub use commands::simulate::SimulationReport;
88pub use commands::snapshot::{RestoreReport, SnapshotReport};
89pub use commands::undo::{UndoReport, UndoTarget};
90pub use commands::validate::ValidateReport;
91pub use config::CliOverrides;
92pub use dialect::{DatabaseDialect, DialectKind};
93pub use multi::MultiWaypoint;
94pub use preflight::PreflightReport;
95pub use safety::SafetyReport;
96
97/// Main entry point for the Waypoint library.
98///
99/// Create a `Waypoint` instance with a config and use its methods to
100/// run migration commands programmatically.
101pub struct Waypoint {
102    pub config: WaypointConfig,
103    client: DbClient,
104}
105
106impl Waypoint {
107    /// Create a new Waypoint instance, connecting to the database.
108    ///
109    /// Engine is auto-detected from the configured connection URL scheme
110    /// (`postgres://` / `postgresql://` → PostgreSQL, `mysql://` → MySQL).
111    /// If `connect_retries` is configured, retries with exponential backoff.
112    pub async fn new(config: WaypointConfig) -> Result<Self> {
113        let conn_string = config.connection_string()?;
114        let client = db::connect_for_url(&conn_string, &config).await?;
115        Ok(Self { config, client })
116    }
117
118    /// Create a new Waypoint instance with an existing PostgreSQL client.
119    ///
120    /// Convenience constructor preserved for backwards compatibility. For new
121    /// code or for MySQL connections, use [`Self::with_db_client`].
122    #[cfg(feature = "postgres")]
123    pub fn with_client(config: WaypointConfig, client: Client) -> Self {
124        Self {
125            config,
126            client: DbClient::with_postgres(client),
127        }
128    }
129
130    /// Create a new Waypoint instance with an already-constructed [`DbClient`].
131    pub fn with_db_client(config: WaypointConfig, client: DbClient) -> Self {
132        Self { config, client }
133    }
134
135    /// Get a reference to the underlying database client.
136    pub fn client(&self) -> &DbClient {
137        &self.client
138    }
139
140    /// Get a reference to the underlying PostgreSQL client.
141    ///
142    /// Returns an error if this `Waypoint` was constructed for a non-PostgreSQL
143    /// engine. Most legacy callers can keep using this; new code should prefer
144    /// [`Self::client`] which returns a backend-agnostic [`DbClient`].
145    #[cfg(feature = "postgres")]
146    pub fn postgres_client(&self) -> Result<&Client> {
147        self.client.as_postgres()
148    }
149
150    /// Apply pending migrations.
151    pub async fn migrate(&self, target_version: Option<&str>) -> Result<MigrateReport> {
152        self.migrate_with_options(target_version, false).await
153    }
154
155    /// Apply pending migrations with the additional `force` flag for
156    /// overriding DANGER safety verdicts (PostgreSQL only; MySQL safety
157    /// analysis does not currently gate migrations).
158    pub async fn migrate_with_options(
159        &self,
160        target_version: Option<&str>,
161        force: bool,
162    ) -> Result<MigrateReport> {
163        match self.client.dialect_kind() {
164            #[cfg(feature = "postgres")]
165            DialectKind::Postgres => {
166                commands::migrate::execute_with_options(
167                    self.client.as_postgres()?,
168                    &self.config,
169                    target_version,
170                    force,
171                )
172                .await
173            }
174            #[cfg(not(feature = "postgres"))]
175            DialectKind::Postgres => Err(error::WaypointError::ConfigError(
176                "PostgreSQL support is not compiled in (enable the `postgres` feature)".into(),
177            )),
178            #[cfg(feature = "mysql")]
179            DialectKind::Mysql => {
180                commands::migrate::execute_mysql_with_options(
181                    &self.client,
182                    &self.config,
183                    target_version,
184                    force,
185                )
186                .await
187            }
188            #[cfg(not(feature = "mysql"))]
189            DialectKind::Mysql => Err(error::WaypointError::ConfigError(
190                "MySQL support is not compiled in (enable the `mysql` feature)".into(),
191            )),
192        }
193    }
194
195    /// Show migration status information.
196    pub async fn info(&self) -> Result<Vec<MigrationInfo>> {
197        commands::info::execute_db(&self.client, &self.config).await
198    }
199
200    /// Validate applied migrations against local files.
201    pub async fn validate(&self) -> Result<ValidateReport> {
202        commands::validate::execute_db(&self.client, &self.config).await
203    }
204
205    /// Repair the schema history table.
206    pub async fn repair(&self) -> Result<RepairReport> {
207        self.repair_with(false).await
208    }
209
210    /// Repair the schema history table, optionally as a preview.
211    ///
212    /// With `dry_run = true` nothing is written: the returned report describes
213    /// the work a real [`Waypoint::repair`] would perform.
214    pub async fn repair_with(&self, dry_run: bool) -> Result<RepairReport> {
215        commands::repair::execute_db_with(&self.client, &self.config, dry_run).await
216    }
217
218    /// Baseline an existing database.
219    pub async fn baseline(&self, version: Option<&str>, description: Option<&str>) -> Result<()> {
220        commands::baseline::execute_db(&self.client, &self.config, version, description).await
221    }
222
223    /// Undo applied migrations.
224    pub async fn undo(&self, target: UndoTarget) -> Result<UndoReport> {
225        commands::undo::execute_db(&self.client, &self.config, target).await
226    }
227
228    /// Drop all objects in managed schemas.
229    pub async fn clean(&self, allow_clean: bool) -> Result<Vec<String>> {
230        commands::clean::execute_db(&self.client, &self.config, allow_clean).await
231    }
232
233    /// Run lint on migration files (no DB required).
234    pub fn lint(locations: &[PathBuf], disabled_rules: &[String]) -> Result<LintReport> {
235        commands::lint::execute(locations, disabled_rules)
236    }
237
238    /// Generate changelog from migration files (no DB required).
239    pub fn changelog(
240        locations: &[PathBuf],
241        from: Option<&str>,
242        to: Option<&str>,
243    ) -> Result<ChangelogReport> {
244        commands::changelog::execute(locations, from, to)
245    }
246
247    /// Compare database schema against a target.
248    pub async fn diff(&self, target: commands::diff::DiffTarget) -> Result<DiffReport> {
249        commands::diff::execute_db(&self.client, &self.config, target).await
250    }
251
252    /// Detect schema drift.
253    pub async fn drift(&self) -> Result<DriftReport> {
254        commands::drift::execute_db(&self.client, &self.config).await
255    }
256
257    /// Take a schema snapshot.
258    pub async fn snapshot(
259        &self,
260        snapshot_config: &commands::snapshot::SnapshotConfig,
261    ) -> Result<SnapshotReport> {
262        commands::snapshot::execute_snapshot_db(&self.client, &self.config, snapshot_config).await
263    }
264
265    /// Restore from a schema snapshot.
266    pub async fn restore(
267        &self,
268        snapshot_config: &commands::snapshot::SnapshotConfig,
269        snapshot_id: &str,
270    ) -> Result<RestoreReport> {
271        commands::snapshot::execute_restore_db(
272            &self.client,
273            &self.config,
274            snapshot_config,
275            snapshot_id,
276        )
277        .await
278    }
279
280    /// Run enhanced dry-run with EXPLAIN.
281    pub async fn explain(&self) -> Result<ExplainReport> {
282        commands::explain::execute_db(&self.client, &self.config).await
283    }
284
285    /// Run pre-flight health checks.
286    pub async fn preflight(&self) -> Result<PreflightReport> {
287        preflight::run_preflight_db(&self.client, &self.config.preflight).await
288    }
289
290    /// Check for branch conflicts (no DB required).
291    pub fn check_conflicts(locations: &[PathBuf], base_branch: &str) -> Result<ConflictReport> {
292        commands::check_conflicts::execute(locations, base_branch)
293    }
294
295    /// Analyze pending migrations for safety (lock analysis, impact estimation).
296    pub async fn safety(&self) -> Result<SafetyCommandReport> {
297        commands::safety::execute_db(&self.client, &self.config).await
298    }
299
300    /// Run schema advisor to suggest improvements.
301    pub async fn advise(&self) -> Result<AdvisorReport> {
302        commands::advisor::execute_db(&self.client, &self.config).await
303    }
304
305    /// Simulate pending migrations in a throwaway schema.
306    pub async fn simulate(&self) -> Result<SimulationReport> {
307        commands::simulate::execute_db(&self.client, &self.config).await
308    }
309}