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(), self.driver, 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 log::info!("{}", 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
658/// Implementation of Connection for the main Database struct.
659///
660/// Uses the internal connection pool to execute queries.
661impl Connection for Database {
662 type Exec<'c> = &'c sqlx::Pool<sqlx::Any>;
663
664 fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
665 &self.pool
666 }
667}
668
669/// Implementation of Connection for a mutable reference to Database.
670impl<'a> Connection for &'a mut Database {
671 type Exec<'c> = &'c sqlx::Pool<sqlx::Any>
672 where
673 Self: 'c;
674
675 fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
676 &self.pool
677 }
678}
679
680/// Implementation of Connection for a mutable reference to sqlx::Transaction.
681impl<'a> Connection for &mut sqlx::Transaction<'a, sqlx::Any> {
682 type Exec<'c> = &'c mut sqlx::AnyConnection
683 where
684 Self: 'c;
685
686 fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
687 &mut **self
688 }
689}