bottle_orm/
database.rs

1//! # Database Module
2//!
3//! This module provides the core database connection and management functionality for Bottle ORM.
4//! It handles connection pooling, driver detection, table creation, and foreign key management
5//! across multiple database backends.
6//!
7//! ## Supported Database Drivers
8//!
9//! - **PostgreSQL**: Full support with advanced features like UUID, JSONB, arrays
10//! - **MySQL**: Complete support for standard MySQL/MariaDB features
11//! - **SQLite**: In-memory and file-based SQLite databases
12//!
13//! ## Features
14//!
15//! - **Connection Pooling**: Automatic connection pool management via sqlx
16//! - **Driver Detection**: Automatic database driver detection from connection URL
17//! - **Schema Management**: Table creation with indexes, constraints, and foreign keys
18//! - **Type Safety**: Type-safe operations across different database backends
19//!
20//! ## Example Usage
21//!
22//! ```rust,ignore
23//! use bottle_orm::Database;
24//!
25//! // Connect to PostgreSQL
26//! let db = Database::connect("postgres://user:pass@localhost/mydb").await?;
27//!
28//! // Connect to SQLite
29//! let db = Database::connect("sqlite::memory:").await?;
30//!
31//! // Connect to MySQL
32//! let db = Database::connect("mysql://user:pass@localhost/mydb").await?;
33//!
34//! // Create table for a model
35//! db.create_table::<User>().await?;
36//!
37//! // Assign foreign keys
38//! db.assign_foreign_keys::<Post>().await?;
39//!
40//! // Start building queries
41//! let users = db.model::<User>().scan().await?;
42//! ```
43
44// ============================================================================
45// External Crate Imports
46// ============================================================================
47
48use heck::ToSnakeCase;
49use sqlx::{any::AnyPoolOptions, AnyPool, Error, Row};
50
51// ============================================================================
52// Internal Crate Imports
53// ============================================================================
54
55use crate::{migration::Migrator, model::Model, query_builder::QueryBuilder, Transaction};
56
57// ============================================================================
58// Database Driver Enumeration
59// ============================================================================
60
61/// Supported database driver types.
62///
63/// This enum represents the different database backends that Bottle ORM can work with.
64/// The driver type is automatically detected from the connection URL and used to
65/// generate appropriate SQL syntax for each database system.
66///
67/// # Variants
68///
69/// * `Postgres` - PostgreSQL database (9.5+)
70/// * `SQLite` - SQLite database (3.x)
71/// * `MySQL` - MySQL or MariaDB database (5.7+/10.2+)
72///
73/// # SQL Dialect Differences
74///
75/// Different drivers use different SQL syntax:
76///
77/// - **Placeholders**:
78///   - PostgreSQL: `$1, $2, $3` (numbered)
79///   - SQLite/MySQL: `?, ?, ?` (positional)
80///
81/// - **Type Casting**:
82///   - PostgreSQL: `$1::UUID`, `$2::TIMESTAMPTZ`
83///   - SQLite/MySQL: Automatic type inference
84///
85/// - **Schema Queries**:
86///   - PostgreSQL: `information_schema` tables
87///   - SQLite: `sqlite_master` system table
88///   - MySQL: `information_schema` tables
89///
90/// # Example
91///
92/// ```rust,ignore
93/// match db.driver {
94///     Drivers::Postgres => println!("Using PostgreSQL"),
95///     Drivers::SQLite => println!("Using SQLite"),
96///     Drivers::MySQL => println!("Using MySQL"),
97/// }
98/// ```
99#[derive(Clone, Debug, Copy)]
100pub enum Drivers {
101    /// PostgreSQL driver.
102    ///
103    /// Used for PostgreSQL databases. Supports advanced features like:
104    /// - UUID native type
105    /// - JSONB for JSON data
106    /// - Array types
107    /// - Full-text search
108    /// - Advanced indexing (GiST, GIN, etc.)
109    Postgres,
110
111    /// SQLite driver.
112    ///
113    /// Used for SQLite databases (both in-memory and file-based). Characteristics:
114    /// - Lightweight and embedded
115    /// - Single-file database
116    /// - Limited concurrent write support
117    /// - Good for development and small applications
118    SQLite,
119
120    /// MySQL driver.
121    ///
122    /// Used for MySQL and MariaDB databases. Features:
123    /// - Wide compatibility
124    /// - Good performance for read-heavy workloads
125    /// - Mature ecosystem
126    /// - ACID compliance (with InnoDB)
127    MySQL,
128}
129
130// ============================================================================
131// Database Connection and Management
132// ============================================================================
133
134/// The main entry point for database connection and management.
135///
136/// `Database` handles connection pooling, driver detection, and provides methods
137/// for schema operations and query building. It uses sqlx's `AnyPool` to support
138/// multiple database backends with a unified interface.
139///
140/// # Fields
141///
142/// * `pool` - The sqlx connection pool for executing queries
143/// * `driver` - The detected database driver type
144///
145/// # Thread Safety
146///
147/// `Database` implements `Clone` and can be safely shared across threads.
148/// The underlying connection pool is thread-safe and handles connection
149/// distribution automatically.
150///
151/// # Example
152///
153/// ```rust,ignore
154/// use bottle_orm::Database;
155///
156/// #[tokio::main]
157/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
158///     // Connect to database
159///     let db = Database::connect("postgres://localhost/mydb").await?;
160///
161///     // Create migrator
162///     let migrator = db.migrator();
163///
164///     // Build queries
165///     let query = db.model::<User>();
166///
167///     // Database can be cloned and shared
168///     let db_clone = db.clone();
169///     tokio::spawn(async move {
170///         let users = db_clone.model::<User>().scan().await;
171///     });
172///
173///     Ok(())
174/// }
175/// ```
176#[derive(Clone)]
177pub struct Database {
178    /// The sqlx connection pool for executing database queries.
179    ///
180    /// This pool manages a set of database connections that can be reused
181    /// across multiple queries, improving performance by avoiding the overhead
182    /// of creating new connections for each operation.
183    pub(crate) pool: AnyPool,
184
185    /// The detected database driver type.
186    ///
187    /// Used to generate driver-specific SQL syntax (e.g., placeholders,
188    /// type casting, schema queries).
189    pub(crate) driver: Drivers,
190}
191
192// ============================================================================
193// Database Implementation
194// ============================================================================
195
196impl Database {
197    // ========================================================================
198    // Connection Management
199    // ========================================================================
200
201    /// Connects to the database using a connection string (Database URL).
202    ///
203    /// This method establishes a connection pool to the specified database and
204    /// automatically detects the driver type based on the URL scheme. The connection
205    /// pool is configured with a default maximum of 5 connections.
206    ///
207    /// # Arguments
208    ///
209    /// * `url` - The database connection string with the format:
210    ///   `<scheme>://<user>:<password>@<host>:<port>/<database>`
211    ///
212    /// # Supported URL Schemes
213    ///
214    /// - **PostgreSQL**: `postgres://` or `postgresql://`
215    /// - **MySQL**: `mysql://`
216    /// - **SQLite**: `sqlite://` or `sqlite::memory:` (for in-memory databases)
217    ///
218    /// # Connection Pool Configuration
219    ///
220    /// - Maximum connections: 5
221    /// - Connection timeout: Default (30 seconds)
222    /// - Idle timeout: Default (10 minutes)
223    ///
224    /// # Returns
225    ///
226    /// * `Ok(Database)` - Successfully connected database instance
227    /// * `Err(sqlx::Error)` - Connection error (invalid URL, authentication failure, etc.)
228    ///
229    /// # Examples
230    ///
231    /// ```rust,ignore
232    /// // PostgreSQL connection
233    /// let db = Database::connect("postgres://user:password@localhost:5432/mydb").await?;
234    ///
235    /// // PostgreSQL with SSL
236    /// let db = Database::connect("postgres://user:password@localhost/mydb?sslmode=require").await?;
237    ///
238    /// // SQLite in-memory database (great for testing)
239    /// let db = Database::connect("sqlite::memory:").await?;
240    ///
241    /// // SQLite file-based database
242    /// let db = Database::connect("sqlite://./database.db").await?;
243    ///
244    /// // MySQL connection
245    /// let db = Database::connect("mysql://user:password@localhost:3306/mydb").await?;
246    /// ```
247    ///
248    /// # Error Handling
249    ///
250    /// ```rust,ignore
251    /// match Database::connect("postgres://localhost/mydb").await {
252    ///     Ok(db) => println!("Connected successfully"),
253    ///     Err(e) => eprintln!("Connection failed: {}", e),
254    /// }
255    /// ```
256    pub async fn connect(url: &str) -> Result<Self, Error> {
257        // Install default drivers for sqlx::Any
258        sqlx::any::install_default_drivers();
259
260        // Create connection pool with maximum 5 connections
261        let pool = AnyPoolOptions::new().max_connections(5).connect(url).await?;
262
263        // Detect driver type from URL scheme
264        let (driver_str, _) = url.split_once(':').unwrap_or(("sqlite", ""));
265        let driver = match driver_str {
266            "postgresql" | "postgres" => Drivers::Postgres,
267            "mysql" => Drivers::MySQL,
268            _ => Drivers::SQLite,
269        };
270
271        Ok(Self { pool, driver })
272    }
273
274    // ========================================================================
275    // Schema Management
276    // ========================================================================
277
278    /// Creates a `Migrator` instance to manage schema migrations.
279    ///
280    /// The migrator allows you to register multiple models and execute
281    /// all necessary schema changes (table creation, foreign keys) in the
282    /// correct order.
283    ///
284    /// # Returns
285    ///
286    /// A new `Migrator` instance associated with this database connection
287    ///
288    /// # Example
289    ///
290    /// ```rust,ignore
291    /// let db = Database::connect("sqlite::memory:").await?;
292    ///
293    /// db.migrator()
294    ///     .register::<User>()
295    ///     .register::<Post>()
296    ///     .register::<Comment>()
297    ///     .run()
298    ///     .await?;
299    /// ```
300    ///
301    /// # See Also
302    ///
303    /// * [`Migrator`] - For detailed migration documentation
304    /// * [`Migrator::register()`] - For registering models
305    /// * [`Migrator::run()`] - For executing migrations
306    pub fn migrator(&self) -> Migrator<'_> {
307        Migrator::new(self)
308    }
309
310    // ========================================================================
311    // Query Building
312    // ========================================================================
313
314    /// Starts building a query for a specific Model.
315    ///
316    /// This method creates a new `QueryBuilder` instance configured for the
317    /// specified model type. The query builder provides a fluent interface
318    /// for constructing SELECT and INSERT queries.
319    ///
320    /// # Type Parameters
321    ///
322    /// * `T` - The Model type to query. Must implement `Model + Send + Sync + Unpin`
323    ///
324    /// # Returns
325    ///
326    /// A new `QueryBuilder` instance ready for query construction
327    ///
328    /// # Example
329    ///
330    /// ```rust,ignore
331    /// // Simple query
332    /// let users: Vec<User> = db.model::<User>().scan().await?;
333    ///
334    /// // Filtered query
335    /// let adults: Vec<User> = db.model::<User>()
336    ///     .filter("age", ">=", 18)
337    ///     .scan()
338    ///     .await?;
339    ///
340    /// // Insert operation
341    /// let new_user = User { /* ... */ };
342    /// db.model::<User>().insert(&new_user).await?;
343    /// ```
344    ///
345    /// # See Also
346    ///
347    /// * [`QueryBuilder`] - For detailed query building documentation
348    /// * [`QueryBuilder::filter()`] - For adding WHERE clauses
349    /// * [`QueryBuilder::scan()`] - For executing SELECT queries
350    /// * [`QueryBuilder::insert()`] - For INSERT operations
351    pub fn model<T: Model + Send + Sync + Unpin>(&self) -> QueryBuilder<'_, T, Self> {
352        // Get active column names from the model
353        let active_columns = T::active_columns();
354        let mut columns: Vec<String> = Vec::with_capacity(active_columns.capacity());
355
356        // Convert column names to snake_case and strip 'r#' prefix if present
357        for col in active_columns {
358            columns.push(col.strip_prefix("r#").unwrap_or(col).to_snake_case());
359        }
360
361        // Create and return the query builder
362        QueryBuilder::new(self.clone(), T::table_name(), T::columns(), columns)
363    }
364
365    // ========================================================================
366    // Table Creation
367    // ========================================================================
368
369    /// Creates the table for model `T` if it does not exist.
370    ///
371    /// This method generates and executes SQL to create a table based on the
372    /// model's structure. It handles column definitions, primary keys, unique
373    /// constraints, default values, and indexes.
374    ///
375    /// # Type Parameters
376    ///
377    /// * `T` - The Model type representing the table
378    ///
379    /// # Returns
380    ///
381    /// * `Ok(&Self)` - Reference to self for method chaining
382    /// * `Err(sqlx::Error)` - Database error during table creation
383    ///
384    /// # Generated SQL Features
385    ///
386    /// - **Primary Keys**: Automatically marked with `PRIMARY KEY`
387    /// - **NOT NULL**: Non-nullable fields get `NOT NULL` constraint
388    /// - **UNIQUE**: Fields marked with `#[orm(unique)]` get `UNIQUE` constraint
389    /// - **DEFAULT**: Fields marked with `#[orm(create_time)]` get `DEFAULT CURRENT_TIMESTAMP`
390    /// - **Indexes**: Fields marked with `#[orm(index)]` get database indexes
391    ///
392    /// # Example
393    ///
394    /// ```rust,ignore
395    /// use bottle_orm::Model;
396    /// use uuid::Uuid;
397    /// use chrono::{DateTime, Utc};
398    ///
399    /// #[derive(Model)]
400    /// struct User {
401    ///     #[orm(primary_key)]
402    ///     id: Uuid,
403    ///     #[orm(size = 50, unique)]
404    ///     username: String,
405    ///     #[orm(index)]
406    ///     email: String,
407    ///     age: i32,
408    ///     #[orm(create_time)]
409    ///     created_at: DateTime<Utc>,
410    /// }
411    ///
412    /// // Creates table with:
413    /// // - UUID primary key
414    /// // - Unique username constraint
415    /// // - Index on email
416    /// // - created_at with DEFAULT CURRENT_TIMESTAMP
417    /// db.create_table::<User>().await?;
418    /// ```
419    ///
420    /// # Generated SQL Example (PostgreSQL)
421    ///
422    /// ```sql
423    /// CREATE TABLE IF NOT EXISTS "user" (
424    ///     "id" UUID PRIMARY KEY,
425    ///     "username" VARCHAR(50) NOT NULL UNIQUE,
426    ///     "email" TEXT NOT NULL,
427    ///     "age" INTEGER NOT NULL,
428    ///     "created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
429    /// );
430    /// CREATE INDEX IF NOT EXISTS "idx_user_email" ON "user" ("email");
431    /// ```
432    pub async fn create_table<T: Model>(&self) -> Result<&Self, Error> {
433        // Get table name in snake_case format
434        let table_name = T::table_name().to_snake_case();
435        let columns = T::columns();
436
437        let mut column_defs = Vec::new();
438        let mut index_statements = Vec::new();
439
440        // Build column definitions
441        for col in &columns {
442            // Strip 'r#' prefix if present (for Rust keywords used as column names)
443            let col_name = col.name.strip_prefix("r#").unwrap_or(col.name).to_snake_case();
444            let mut def = format!("\"{}\" {}", col_name, col.sql_type);
445
446            // Add PRIMARY KEY constraint
447            if col.is_primary_key {
448                def.push_str(" PRIMARY KEY");
449            }
450
451            // Add NOT NULL constraint (except for primary keys, which are implicitly NOT NULL)
452            if !col.is_nullable && !col.is_primary_key {
453                def.push_str(" NOT NULL");
454            }
455
456            // Add DEFAULT CURRENT_TIMESTAMP for create_time fields
457            if col.create_time {
458                def.push_str(" DEFAULT CURRENT_TIMESTAMP");
459            }
460
461            // Add UNIQUE constraint
462            if col.unique {
463                def.push_str(" UNIQUE");
464            }
465
466            column_defs.push(def);
467
468            // Generate index creation statement if needed
469            if col.index {
470                let index_type = if col.unique { "UNIQUE INDEX" } else { "INDEX" };
471                let index_name = format!("idx_{}_{}", table_name, col_name);
472
473                let index_query = format!(
474                    "CREATE {} IF NOT EXISTS \"{}\" ON \"{}\" (\"{}\")",
475                    index_type, index_name, table_name, col_name,
476                );
477
478                index_statements.push(index_query);
479            }
480        }
481
482        // Build and execute CREATE TABLE statement
483        let create_table_query =
484            format!("CREATE TABLE IF NOT EXISTS \"{}\" ({})", table_name.to_snake_case(), column_defs.join(", "));
485        println!("{}", create_table_query);
486
487        sqlx::query(&create_table_query).execute(&self.pool).await?;
488
489        // Create indexes
490        for idx_stmt in index_statements {
491            sqlx::query(&idx_stmt).execute(&self.pool).await?;
492        }
493
494        Ok(self)
495    }
496
497    /// Starts a new database transaction.
498    ///
499    /// Returns a `Transaction` wrapper that can be used to execute multiple
500    /// queries atomically. The transaction must be explicitly committed
501    /// using `commit()`, otherwise it will be rolled back when dropped.
502    ///
503    /// # Returns
504    ///
505    /// * `Ok(Transaction)` - A new transaction instance
506    /// * `Err(sqlx::Error)` - Database error starting transaction
507    ///
508    /// # Example
509    ///
510    /// ```rust,ignore
511    /// let mut tx = db.begin().await?;
512    /// // ... perform operations ...
513    /// tx.commit().await?;
514    /// ```
515    pub async fn begin<'a>(&self) -> Result<Transaction<'a>, sqlx::Error> {
516        let tx = self.pool.begin().await?;
517        Ok(Transaction { tx, driver: self.driver })
518    }
519
520    // ========================================================================
521    // Foreign Key Management
522    // ========================================================================
523
524    /// Checks for and assigns Foreign Keys for model `T`.
525    ///
526    /// This method examines all columns marked with `#[orm(foreign_key = "Table::Column")]`
527    /// and creates the appropriate foreign key constraints. It verifies that constraints
528    /// don't already exist before attempting to create them, preventing duplication errors.
529    ///
530    /// # Type Parameters
531    ///
532    /// * `T` - The Model type to process for foreign keys
533    ///
534    /// # Returns
535    ///
536    /// * `Ok(&Self)` - Reference to self for method chaining
537    /// * `Err(sqlx::Error)` - Database error during foreign key creation
538    ///
539    /// # Constraint Naming
540    ///
541    /// Foreign key constraints are named using the pattern:
542    /// `fk_{table_name}_{column_name}`
543    ///
544    /// # Example
545    ///
546    /// ```rust,ignore
547    /// use bottle_orm::Model;
548    /// use uuid::Uuid;
549    ///
550    /// #[derive(Model)]
551    /// struct User {
552    ///     #[orm(primary_key)]
553    ///     id: Uuid,
554    ///     username: String,
555    /// }
556    ///
557    /// #[derive(Model)]
558    /// struct Post {
559    ///     #[orm(primary_key)]
560    ///     id: Uuid,
561    ///     #[orm(foreign_key = "User::id")]
562    ///     user_id: Uuid,
563    ///     title: String,
564    /// }
565    ///
566    /// // Create tables first
567    /// db.create_table::<User>().await?;
568    /// db.create_table::<Post>().await?;
569    ///
570    /// // Then assign foreign keys
571    /// db.assign_foreign_keys::<Post>().await?;
572    /// ```
573    ///
574    /// # Generated SQL Example
575    ///
576    /// ```sql
577    /// ALTER TABLE "post"
578    /// ADD CONSTRAINT "fk_post_user_id"
579    /// FOREIGN KEY ("user_id")
580    /// REFERENCES "user" ("id");
581    /// ```
582    ///
583    /// # Important Notes
584    ///
585    /// - Foreign key assignment should be done **after** all tables are created
586    /// - The referenced table and column must exist before creating the foreign key
587    /// - Use the `Migrator` to handle the correct order automatically
588    /// - Currently optimized for PostgreSQL (uses `information_schema`)
589    ///
590    /// # See Also
591    ///
592    /// * [`Migrator`] - For automatic migration order management
593    pub async fn assign_foreign_keys<T: Model>(&self) -> Result<&Self, Error> {
594        // Get table name in snake_case format
595        let table_name = T::table_name().to_snake_case();
596        let columns = T::columns();
597
598        // Process each column that has a foreign key definition
599        for col in columns {
600            if let (Some(f_table), Some(f_key)) = (col.foreign_table, col.foreign_key) {
601                // Clean up column and reference names
602                let col_name = col.name.strip_prefix("r#").unwrap_or(col.name).to_snake_case();
603                let f_table_clean = f_table.to_snake_case();
604                let f_key_clean = f_key.to_snake_case();
605
606                // Generate constraint name
607                let constraint_name = format!("fk_{}_{}", table_name, col_name);
608
609                // Check if constraint already exists (PostgreSQL-specific)
610                // TODO: Add support for MySQL and SQLite constraint checking
611                let check_query =
612                    "SELECT count(*) FROM information_schema.table_constraints WHERE constraint_name = $1";
613                let row = sqlx::query(check_query).bind(&constraint_name).fetch_one(&self.pool).await?;
614
615                let count: i64 = row.try_get(0).unwrap_or(0);
616
617                // Skip if constraint already exists
618                if count > 0 {
619                    continue;
620                }
621
622                // Create foreign key constraint
623                let alter_query = format!(
624                    "ALTER TABLE \"{}\" ADD CONSTRAINT \"{}\" FOREIGN KEY (\"{}\") REFERENCES \"{}\" (\"{}\")",
625                    table_name, constraint_name, col_name, f_table_clean, f_key_clean
626                );
627
628                sqlx::query(&alter_query).execute(&self.pool).await?;
629            }
630        }
631
632        Ok(self)
633    }
634}
635
636/// A trait representing a database connection or transaction.
637///
638/// This trait abstracts over `Database` (pool) and `Transaction` types, allowing
639/// the `QueryBuilder` to work seamlessly with both. It uses Generic Associated Types (GATs)
640/// to handle the lifetimes of the executor references correctly.
641pub trait Connection {
642    /// The type of the executor returned by this connection.
643    ///
644    /// This uses GATs to bind the lifetime of the executor (`'c`) to the lifetime
645    /// of the borrow of the connection (`&'c mut self`).
646    type Exec<'c>: sqlx::Executor<'c, Database = sqlx::Any>
647    where
648        Self: 'c;
649
650    /// Returns a mutable reference to the SQLx executor.
651    ///
652    /// # Returns
653    ///
654    /// An executor capable of running SQL queries (either a Pool or a Transaction).
655    fn executor<'c>(&'c mut self) -> Self::Exec<'c>;
656
657    /// Returns the driver type associated with this connection.
658    fn driver(&self) -> Drivers;
659}
660
661/// Implementation of Connection for the main Database struct.
662///
663/// Uses the internal connection pool to execute queries.
664impl Connection for Database {
665    type Exec<'c> = &'c sqlx::Pool<sqlx::Any>;
666
667    fn driver(&self) -> Drivers {
668        self.driver.clone()
669    }
670
671    fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
672        &self.pool
673    }
674}