dmsc/database/
mod.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Database Module
19//!
20//! This module provides a comprehensive database abstraction layer for DMSC,
21//! supporting multiple database backends with a unified interface.
22//!
23//! ## Key Components
24//!
25//! - **DMSCDatabase**: Trait defining the database interface for all supported databases
26//! - **DMSCDatabasePool**: Connection pool management with automatic reuse and health checks
27//! - **DMSCDatabaseConfig**: Builder-style configuration for database connections
28//! - **DMSCDatabaseMigrator**: Database schema migration management
29//! - **DMSCDBRow**: Row representation with type-safe column access
30//! - **DMSCDBResult**: Query result set with iterator support
31//! - **DMSCDatabaseTransaction**: ACID transaction support for data integrity
32//!
33//! ## Supported Databases
34//!
35//! - **PostgreSQL** - Enterprise-grade relational database with full feature support
36//! - **MySQL** - Popular open-source database with high performance
37//! - **SQLite** - Embedded database for lightweight scenarios
38//!
39//! ## Features
40//!
41//! - **Connection Pooling**: Efficient connection reuse with configurable pool size
42//! - **Async/Await**: Full asynchronous API using Tokio runtime
43//! - **Parameter Binding**: Safe SQL parameter binding to prevent injection attacks
44//! - **Batch Operations**: Efficient bulk insert/update operations
45//! - **Transactions**: ACID-compliant transaction support
46//! - **Migrations**: Version-controlled schema migrations
47//! - **Type Conversion**: Automatic conversion between database and JSON types
48//!
49//! ## Usage Example
50//!
51//! ```rust,ignore
52//! use dmsc::database::{DMSCDatabase, DMSCDatabaseConfig, PooledDatabase};
53//!
54//! #[tokio::main]
55//! async fn main() -> DMSCResult<()> {
56//!     let config = DMSCDatabaseConfig::postgres()
57//!         .host("localhost")
58//!         .port(5432)
59//!         .database("mydb")
60//!         .user("user")
61//!         .password("pass")
62//!         .max_connections(10)
63//!         .min_idle_connections(2)
64//!         .connection_timeout_secs(30)
65//!         .build();
66//!
67//!     let pool = DMSCDatabasePool::new(config).await?;
68//!     let db = pool.get().await?;
69//!
70//!     // Execute a query with parameter binding
71//!     use serde_json::json;
72//!     let rows = db.query("SELECT * FROM users WHERE id = $1", &[&json!(1)]).await?;
73//!     for row in rows {
74//!         let id: i64 = row.get("id");
75//!         let name: String = row.get("name");
76//!         println!("User: {} - {}", id, name);
77//!     }
78//!
79//!     Ok(())
80//! }
81//! ```
82//!
83//! ## Batch Operations Example
84//!
85//! ```rust,ignore
86//! use dmsc::database::{DMSCDatabasePool, DMSCDatabaseConfig};
87//!
88//! #[tokio::main]
89//! async fn main() -> DMSCResult<()> {
90//!     let config = DMSCDatabaseConfig::postgres()
91//!         .host("localhost")
92//!         .database("mydb")
93//!         .build();
94//!
95//!     let pool = DMSCDatabasePool::new(config).await?;
96//!     let db = pool.get().await?;
97//!
98//!     // Batch insert users
99//!     let users = vec![
100//!         vec![serde_json::json!("alice"), serde_json::json!("alice@example.com")],
101//!         vec![serde_json::json!("bob"), serde_json::json!("bob@example.com")],
102//!         vec![serde_json::json!("charlie"), serde_json::json!("charlie@example.com")],
103//!     ];
104//!
105//!     let results = db.batch_execute(
106//!         "INSERT INTO users (name, email) VALUES ($1, $2)",
107//!         &users
108//!     ).await?;
109//!
110//!     println!("Inserted {} rows", results.len());
111//!     Ok(())
112//! }
113//! ```
114//!
115//! ## Transaction Example
116//!
117//! ```rust,ignore
118//! use dmsc::database::{DMSCDatabasePool, DMSCDatabaseConfig};
119//!
120//! #[tokio::main]
121//! async fn main() -> DMSCResult<()> {
122//!     let config = DMSCDatabaseConfig::postgres()
123//!         .host("localhost")
124//!         .database("mydb")
125//!         .build();
126//!
127//!     let pool = DMSCDatabasePool::new(config).await?;
128//!     let db = pool.get().await?;
129//!
130//!     // Start transaction
131//!     let mut tx = db.transaction().await?;
132//!
133//!     // Execute operations within transaction
134//!     tx.execute("UPDATE accounts SET balance = balance - 100 WHERE id = $1", &[&1]).await?;
135//!     tx.execute("UPDATE accounts SET balance = balance + 100 WHERE id = $1", &[&2]).await?;
136//!
137//!     // Commit transaction
138//!     tx.commit().await?;
139//!
140//!     Ok(())
141//! }
142//! ```
143
144mod config;
145mod pool;
146mod row;
147mod result;
148mod migration;
149pub mod orm;
150
151#[cfg(feature = "postgres")]
152pub mod postgres;
153#[cfg(feature = "mysql")]
154pub mod mysql;
155#[cfg(feature = "sqlite")]
156pub mod sqlite;
157
158pub use config::{DMSCDatabaseConfig, DatabaseType};
159pub use pool::{DMSCDatabasePool, PooledDatabase, DatabaseMetrics};
160pub use row::DMSCDBRow;
161pub use result::DMSCDBResult;
162pub use migration::{DMSCDatabaseMigration, DMSCMigrationHistory, DMSCDatabaseMigrator};
163pub use orm::{QueryBuilder, Criteria, SortOrder, Pagination, ComparisonOperator, 
164    TableDefinition, ColumnDefinition, IndexDefinition, ForeignKeyDefinition,
165    DMSCORMSimpleRepository, DMSCORMCrudRepository, DMSCORMRepository};
166
167use crate::core::{DMSCResult, DMSCError};
168use async_trait::async_trait;
169use thiserror::Error as ThisError;
170use serde::{Serialize, Deserialize};
171
172#[derive(Debug, ThisError, Clone, Serialize, Deserialize)]
173pub enum DMSCDatabaseTransactionError {
174    #[error("Transaction commit failed: {message}")]
175    CommitFailed { message: String },
176    #[error("Transaction rollback failed: {message}")]
177    RollbackFailed { message: String },
178    #[error("Transaction already completed")]
179    AlreadyCompleted,
180    #[error("Transaction operation failed: {message}")]
181    OperationFailed { message: String },
182}
183
184impl From<DMSCDatabaseTransactionError> for DMSCError {
185    fn from(e: DMSCDatabaseTransactionError) -> Self {
186        DMSCError::Database(e.to_string())
187    }
188}
189
190impl From<DMSCDatabaseTransactionError> for DMSCResult<()> {
191    fn from(e: DMSCDatabaseTransactionError) -> Self {
192        Err(e.into())
193    }
194}
195
196/// Trait defining the database interface for all supported databases.
197///
198/// This trait provides a unified API for database operations across different
199/// database backends. Implementations handle backend-specific details while
200/// presenting a consistent interface to callers.
201///
202/// ## Supported Operations
203///
204/// - **Execution**: Execute SQL statements without returning results
205/// - **Querying**: Execute SELECT statements and iterate over results
206/// - **Parameter Binding**: Safe SQL parameter binding to prevent injection
207/// - **Batch Operations**: Efficient bulk execution and querying
208/// - **Transactions**: ACID-compliant transaction support
209///
210/// ## Example
211///
212/// ```rust,ignore
213/// use dmsc::database::{DMSCDatabase, DMSCDatabasePool, DMSCDatabaseConfig};
214///
215/// #[tokio::main]
216/// async fn main() -> DMSCResult<()> {
217///     let config = DMSCDatabaseConfig::postgres()
218///         .host("localhost")
219///         .database("mydb")
220///         .build();
221///
222///     let pool = DMSCDatabasePool::new(config).await?;
223///     let db = pool.get().await?;
224///
225///     // Execute a statement
226///     db.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)").await?;
227///
228///     // Query with parameters
229///     let rows = db.query_with_params(
230///         "SELECT * FROM users WHERE name = $1",
231///         &[serde_json::json!("alice")]
232///     ).await?;
233///
234///     Ok(())
235/// }
236/// ```
237#[async_trait]
238pub trait DMSCDatabase: Send + Sync {
239    /// Returns the type of database this instance connects to.
240    ///
241    /// This can be used to identify which database backend is in use
242    /// and potentially branch behavior based on database type.
243    fn database_type(&self) -> DatabaseType;
244
245    /// Executes a SQL statement without returning results.
246    ///
247    /// This method is suitable for INSERT, UPDATE, DELETE, and DDL statements.
248    /// Returns the number of rows affected.
249    ///
250    /// ## Parameters
251    ///
252    /// - `sql`: The SQL statement to execute
253    ///
254    /// ## Returns
255    ///
256    /// The number of rows affected, or an error if execution fails.
257    async fn execute(&self, sql: &str) -> DMSCResult<u64>;
258
259    /// Executes a SQL query and returns all matching rows.
260    ///
261    /// This method is suitable for SELECT statements. The returned result
262    /// can be iterated over to access individual rows.
263    ///
264    /// ## Parameters
265    ///
266    /// - `sql`: The SQL query to execute
267    ///
268    /// ## Returns
269    ///
270    /// A result set containing all matching rows, or an error if the query fails.
271    async fn query(&self, sql: &str) -> DMSCResult<DMSCDBResult>;
272
273    /// Executes a SQL query and returns at most one row.
274    ///
275    /// This method is equivalent to adding "LIMIT 1" to the query.
276    /// Returns `None` if no rows match.
277    ///
278    /// ## Parameters
279    ///
280    /// - `sql`: The SQL query to execute
281    ///
282    /// ## Returns
283    ///
284    /// `Some(row)` if a row was found, `None` if no rows match, or an error.
285    async fn query_one(&self, sql: &str) -> DMSCResult<Option<DMSCDBRow>>;
286
287    /// Checks if the database connection is alive.
288    ///
289    /// This method can be used to verify that the connection is still
290    /// responsive before executing important operations.
291    ///
292    /// ## Returns
293    ///
294    /// `true` if the connection is healthy, `false` otherwise.
295    async fn ping(&self) -> DMSCResult<bool>;
296
297    /// Checks if the database connection is currently connected.
298    ///
299    /// Unlike `ping()`, this method does not perform any network operation.
300    ///
301    /// ## Returns
302    ///
303    /// `true` if connected, `false` otherwise.
304    fn is_connected(&self) -> bool;
305
306    /// Closes the database connection.
307    ///
308    /// This method should be called when the connection is no longer needed
309    /// to release resources. After calling this method, the connection
310    /// should not be used.
311    async fn close(&self) -> DMSCResult<()>;
312
313    /// Executes the same SQL statement multiple times with different parameters.
314    ///
315    /// This is more efficient than executing statements individually when
316    /// performing bulk operations.
317    ///
318    /// ## Parameters
319    ///
320    /// - `sql`: The SQL statement with placeholders ($1, $2, etc.)
321    /// - `params`: A slice of parameter sets, one for each execution
322    ///
323    /// ## Returns
324    ///
325    /// A vector of affected row counts, one for each execution.
326    async fn batch_execute(&self, sql: &str, params: &[Vec<serde_json::Value>]) -> DMSCResult<Vec<u64>> {
327        let mut results = Vec::with_capacity(params.len());
328        for param_set in params {
329            let result = self.execute_with_params(sql, param_set).await?;
330            results.push(result);
331        }
332        Ok(results)
333    }
334
335    /// Executes the same query multiple times with different parameters.
336    ///
337    /// This is more efficient than executing queries individually when
338    /// performing bulk reads.
339    ///
340    /// ## Parameters
341    ///
342    /// - `sql`: The SQL query with placeholders ($1, $2, etc.)
343    /// - `params`: A slice of parameter sets, one for each query
344    ///
345    /// ## Returns
346    ///
347    /// A vector of result sets, one for each query.
348    async fn batch_query(&self, sql: &str, params: &[Vec<serde_json::Value>]) -> DMSCResult<Vec<DMSCDBResult>> {
349        let mut results = Vec::with_capacity(params.len());
350        for param_set in params {
351            let result = self.query_with_params(sql, param_set).await?;
352            results.push(result);
353        }
354        Ok(results)
355    }
356
357    /// Executes a SQL statement with parameters.
358    ///
359    /// Parameters are bound using placeholder syntax ($1, $2, etc.) to
360    /// prevent SQL injection attacks.
361    ///
362    /// ## Parameters
363    ///
364    /// - `sql`: The SQL statement with placeholders
365    /// - `params`: The parameter values to bind
366    ///
367    /// ## Returns
368    ///
369    /// The number of rows affected.
370    async fn execute_with_params(&self, sql: &str, params: &[serde_json::Value]) -> DMSCResult<u64>;
371
372    /// Executes a SQL query with parameters.
373    ///
374    /// Parameters are bound using placeholder syntax ($1, $2, etc.) to
375    /// prevent SQL injection attacks.
376    ///
377    /// ## Parameters
378    ///
379    /// - `sql`: The SQL query with placeholders
380    /// - `params`: The parameter values to bind
381    ///
382    /// ## Returns
383    ///
384    /// A result set containing all matching rows.
385    async fn query_with_params(&self, sql: &str, params: &[serde_json::Value]) -> DMSCResult<DMSCDBResult>;
386
387    /// Starts a new database transaction.
388    ///
389    /// Transactions allow multiple operations to be grouped together
390    /// with atomic commit/rollback semantics.
391    ///
392    /// ## Returns
393    ///
394    /// A transaction handle that can be used to execute operations
395    /// within the transaction.
396    async fn transaction(&self) -> DMSCResult<Box<dyn DMSCDatabaseTransaction>>;
397}
398
399/// Trait representing a database transaction.
400///
401/// Transactions provide atomic operations where multiple SQL statements
402/// can be executed together and either committed or rolled back as a unit.
403/// This ensures data integrity even when operations fail partway through.
404///
405/// ## Transaction Lifecycle
406///
407/// 1. **Begin**: Transaction starts with `DMSCDatabase::transaction()`
408/// 2. **Execute**: Run SQL statements within the transaction
409/// 3. **Commit**: Save all changes with `commit()`
410///    OR **Rollback**: Discard all changes with `rollback()`
411///
412/// ## Example
413///
414/// ```rust,ignore
415/// use dmsc::database::{DMSCDatabasePool, DMSCDatabaseConfig};
416///
417/// #[tokio::main]
418/// async fn main() -> DMSCResult<()> {
419///     let config = DMSCDatabaseConfig::postgres()
420///         .host("localhost")
421///         .database("mydb")
422///         .build();
423///
424///     let pool = DMSCDatabasePool::new(config).await?;
425///     let db = pool.get().await?;
426///
427///     let mut tx = db.transaction().await?;
428///
429///     // Transfer funds between accounts
430///     tx.execute("UPDATE accounts SET balance = balance - 100 WHERE id = $1", &[&1]).await?;
431///     tx.execute("UPDATE accounts SET balance = balance + 100 WHERE id = $1", &[&2]).await?;
432///
433///     tx.commit().await?;
434///     Ok(())
435/// }
436/// ```
437///
438/// ## Auto-Rollback
439///
440/// If a transaction is dropped without explicitly calling `commit()` or
441/// `rollback()`, it will automatically be rolled back to ensure no
442/// partial changes are persisted.
443#[async_trait]
444pub trait DMSCDatabaseTransaction: Send + Sync {
445    /// Executes a SQL statement within the transaction.
446    ///
447    /// See [`DMSCDatabase::execute()`] for more details.
448    async fn execute(&self, sql: &str) -> DMSCResult<u64>;
449
450    /// Executes a SQL query within the transaction.
451    ///
452    /// See [`DMSCDatabase::query()`] for more details.
453    async fn query(&self, sql: &str) -> DMSCResult<DMSCDBResult>;
454
455    /// Commits all changes made within the transaction.
456    ///
457    /// After calling this method, the transaction is complete and
458    /// all changes are permanent.
459    ///
460    /// ## Errors
461    ///
462    /// Returns an error if the commit fails. In this case, the
463    /// transaction should be considered failed and no further
464    /// operations should be attempted.
465    async fn commit(&self) -> DMSCResult<()>;
466
467    /// Rolls back all changes made within the transaction.
468    ///
469    /// This discards all changes made since the transaction began.
470    /// The transaction is then complete and cannot be used further.
471    ///
472    /// ## Errors
473    ///
474    /// Returns an error if the rollback fails. In this case, the
475    /// transaction state is uncertain and the database should be
476    /// checked for consistency.
477    async fn rollback(&self) -> DMSCResult<()>;
478
479    /// Closes the transaction without committing.
480    ///
481    /// If the transaction has not been committed, this will implicitly
482    /// roll back any changes. This method is primarily for cleanup
483    /// and should not be used as a substitute for explicit rollback.
484    async fn close(&self) -> DMSCResult<()>;
485}