fluxforge/lib.rs
1//! # FluxForge
2//!
3//! A database schema converter and migration engine for MySQL and PostgreSQL.
4//!
5//! FluxForge provides a unified interface for extracting, transforming, and replicating
6//! database schemas and data between MySQL and PostgreSQL databases. It supports:
7//!
8//! - Schema extraction and conversion
9//! - Type mapping and transformation
10//! - Data replication with verification
11//! - Dependency-aware table ordering
12//!
13//! # Examples
14//!
15//! ```no_run
16//! use fluxforge::{drivers, core::ForgeConfig};
17//!
18//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
19//! let config = ForgeConfig::default();
20//! let driver = drivers::create_driver("mysql://user:pass@localhost/db", &config, true).await?;
21//! let schema = driver.fetch_schema(&config).await?;
22//! println!("Extracted {} tables", schema.tables.len());
23//! # Ok(())
24//! # }
25//! ```
26
27pub mod config;
28pub mod core;
29pub mod drivers;
30pub mod ops;
31
32// Re-export for easier access
33pub use crate::core::ForgeUniversalDataTransferPacket;
34pub use crate::core::{ForgeConfig, ForgeError};
35pub use crate::core::{ForgeSchema, ForgeSchemaColumn, ForgeSchemaTable};
36pub use crate::core::{ForgeUniversalDataField, ForgeUniversalDataRow};
37
38use async_trait::async_trait;
39use futures::Stream;
40use indexmap::IndexMap;
41use std::pin::Pin;
42
43/// Database driver trait for unified database operations.
44///
45/// This trait provides a common interface for interacting with different database systems
46/// (MySQL, PostgreSQL). Implementations handle database-specific operations while presenting
47/// a unified API for schema extraction, migration, and data replication.
48///
49/// # Examples
50///
51/// ```no_run
52/// use fluxforge::{DatabaseDriver, drivers, core::ForgeConfig};
53///
54/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
55/// let config = ForgeConfig::default();
56/// let driver = drivers::create_driver("postgres://user:pass@localhost/db", &config, true).await?;
57/// let is_empty = driver.db_is_empty().await?;
58/// println!("Database is empty: {}", is_empty);
59/// # Ok(())
60/// # }
61/// ```
62#[async_trait]
63pub trait DatabaseDriver: Send + Sync {
64 /// Checks if the database is empty (contains no tables).
65 ///
66 /// # Examples
67 ///
68 /// ```no_run
69 /// # use fluxforge::{DatabaseDriver, drivers, core::ForgeConfig};
70 /// # async fn example(driver: &dyn DatabaseDriver) -> Result<(), Box<dyn std::error::Error>> {
71 /// if driver.db_is_empty().await? {
72 /// println!("Database is empty and ready for replication");
73 /// }
74 /// # Ok(())
75 /// # }
76 /// ```
77 ///
78 /// # Errors
79 ///
80 /// Returns an error if the database connection fails or the query cannot be executed.
81 async fn db_is_empty(&self) -> Result<bool, Box<dyn std::error::Error>>;
82
83 /// Fetches the complete database schema including tables, columns, indices, and foreign keys.
84 ///
85 /// # Arguments
86 ///
87 /// * `config` - Configuration for type mappings and transformation rules
88 ///
89 /// # Examples
90 ///
91 /// ```no_run
92 /// # use fluxforge::{DatabaseDriver, core::ForgeConfig};
93 /// # async fn example(driver: &dyn DatabaseDriver) -> Result<(), Box<dyn std::error::Error>> {
94 /// let config = ForgeConfig::default();
95 /// let schema = driver.fetch_schema(&config).await?;
96 /// for table in &schema.tables {
97 /// println!("Table: {} with {} columns", table.name, table.columns.len());
98 /// }
99 /// # Ok(())
100 /// # }
101 /// ```
102 ///
103 /// # Errors
104 ///
105 /// Returns an error if:
106 /// - Database connection fails
107 /// - Schema metadata cannot be queried
108 /// - Type mapping configuration is invalid
109 async fn fetch_schema(
110 &self,
111 config: &ForgeConfig,
112 ) -> Result<ForgeSchema, Box<dyn std::error::Error>>;
113
114 /// Compares source schema with target database and applies necessary changes.
115 ///
116 /// # Arguments
117 ///
118 /// * `schema` - The source schema to apply
119 /// * `config` - Configuration for type mappings and transformation rules
120 /// * `dry_run` - If true, returns SQL statements without executing them
121 /// * `verbose` - Enable verbose output
122 /// * `destructive` - If true, allows dropping tables and columns not in source schema
123 ///
124 /// # Examples
125 ///
126 /// ```no_run
127 /// # use fluxforge::{DatabaseDriver, ForgeSchema, core::ForgeConfig};
128 /// # async fn example(driver: &dyn DatabaseDriver, schema: &ForgeSchema) -> Result<(), Box<dyn std::error::Error>> {
129 /// let config = ForgeConfig::default();
130 /// let statements = driver.diff_and_apply_schema(
131 /// schema,
132 /// &config,
133 /// true, // dry_run
134 /// false, // verbose
135 /// false // destructive
136 /// ).await?;
137 /// for sql in statements {
138 /// println!("Would execute: {}", sql);
139 /// }
140 /// # Ok(())
141 /// # }
142 /// ```
143 ///
144 /// # Errors
145 ///
146 /// Returns an error if:
147 /// - Database connection fails
148 /// - SQL statements cannot be generated or executed
149 /// - Schema conflicts cannot be resolved
150 async fn diff_and_apply_schema(
151 &self,
152 schema: &ForgeSchema,
153 config: &ForgeConfig,
154 dry_run: bool,
155 verbose: bool,
156 destructive: bool,
157 ) -> Result<Vec<String>, Box<dyn std::error::Error>>;
158
159 /// Streams all rows from a table as universal values.
160 ///
161 /// # Arguments
162 ///
163 /// * `table_name` - Name of the table to stream
164 ///
165 /// # Examples
166 ///
167 /// ```no_run
168 /// # use fluxforge::DatabaseDriver;
169 /// # use futures::StreamExt;
170 /// # async fn example(driver: &dyn DatabaseDriver) -> Result<(), Box<dyn std::error::Error>> {
171 /// let mut stream = driver.stream_table_data("users").await?;
172 /// while let Some(row) = stream.next().await {
173 /// let row = row?;
174 /// println!("Row: {:?}", row);
175 /// }
176 /// # Ok(())
177 /// # }
178 /// ```
179 ///
180 /// # Errors
181 ///
182 /// Returns an error if:
183 /// - Table does not exist
184 /// - Database connection fails
185 /// - Row data cannot be decoded
186 async fn stream_table_data(
187 &self,
188 table_name: &str,
189 ) -> Result<
190 Pin<
191 Box<
192 dyn Stream<Item = Result<IndexMap<String, ForgeUniversalDataField>, ForgeError>>
193 + Send
194 + '_,
195 >,
196 >,
197 Box<dyn std::error::Error>,
198 >;
199
200 /// Streams rows from a table ordered by specified columns.
201 ///
202 /// # Arguments
203 ///
204 /// * `table_name` - Name of the table to stream
205 /// * `order_by` - Column names to order by
206 ///
207 /// # Examples
208 ///
209 /// ```no_run
210 /// # use fluxforge::DatabaseDriver;
211 /// # use futures::StreamExt;
212 /// # async fn example(driver: &dyn DatabaseDriver) -> Result<(), Box<dyn std::error::Error>> {
213 /// let mut stream = driver.stream_table_data_ordered(
214 /// "users",
215 /// &["id".to_string()]
216 /// ).await?;
217 /// while let Some(row) = stream.next().await {
218 /// let row = row?;
219 /// println!("Row: {:?}", row);
220 /// }
221 /// # Ok(())
222 /// # }
223 /// ```
224 ///
225 /// # Errors
226 ///
227 /// Returns an error if:
228 /// - Table does not exist
229 /// - Order by columns do not exist
230 /// - Database connection fails
231 async fn stream_table_data_ordered(
232 &self,
233 table_name: &str,
234 order_by: &[String],
235 ) -> Result<
236 Pin<
237 Box<
238 dyn Stream<Item = Result<IndexMap<String, ForgeUniversalDataField>, ForgeError>>
239 + Send
240 + '_,
241 >,
242 >,
243 Box<dyn std::error::Error>,
244 >;
245
246 /// Inserts a batch of rows into a table.
247 ///
248 /// # Arguments
249 ///
250 /// * `table_name` - Name of the target table
251 /// * `dry_run` - If true, prints SQL without executing
252 /// * `halt_on_error` - If true, stops on first error; if false, logs errors and continues
253 /// * `chunk` - Vector of rows to insert
254 ///
255 /// # Examples
256 ///
257 /// ```no_run
258 /// # use fluxforge::{DatabaseDriver, core::ForgeUniversalDataField};
259 /// # use indexmap::IndexMap;
260 /// # async fn example(driver: &dyn DatabaseDriver) -> Result<(), Box<dyn std::error::Error>> {
261 /// let mut row = IndexMap::new();
262 /// row.insert("id".to_string(), ForgeUniversalDataField::Integer(1));
263 /// row.insert("name".to_string(), ForgeUniversalDataField::Text("Alice".to_string()));
264 /// driver.insert_chunk("users", false, true, vec![row]).await?;
265 /// # Ok(())
266 /// # }
267 /// ```
268 ///
269 /// # Errors
270 ///
271 /// Returns an error if:
272 /// - Table does not exist
273 /// - Column types are incompatible
274 /// - Database constraints are violated
275 /// - `halt_on_error` is true and any insert fails
276 async fn insert_chunk(
277 &self,
278 table_name: &str,
279 dry_run: bool,
280 halt_on_error: bool,
281 chunk: Vec<IndexMap<String, ForgeUniversalDataField>>,
282 ) -> Result<(), Box<dyn std::error::Error>>;
283
284 /// Gets the total number of rows in a table.
285 ///
286 /// # Arguments
287 ///
288 /// * `table_name` - Name of the table
289 ///
290 /// # Examples
291 ///
292 /// ```no_run
293 /// # use fluxforge::DatabaseDriver;
294 /// # async fn example(driver: &dyn DatabaseDriver) -> Result<(), Box<dyn std::error::Error>> {
295 /// let count = driver.get_table_row_count("users").await?;
296 /// println!("Table has {} rows", count);
297 /// # Ok(())
298 /// # }
299 /// ```
300 ///
301 /// # Errors
302 ///
303 /// Returns an error if:
304 /// - Table does not exist
305 /// - Database connection fails
306 async fn get_table_row_count(
307 &self,
308 table_name: &str,
309 ) -> Result<u64, Box<dyn std::error::Error>>;
310}