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::{
50 any::{AnyArguments, AnyPoolOptions},
51 AnyPool, Arguments, Error, Row,
52};
53use std::time::Duration;
54
55// ============================================================================
56// Internal Crate Imports
57// ============================================================================
58
59use crate::{migration::Migrator, model::Model, query_builder::QueryBuilder, Transaction};
60
61// ============================================================================
62// Database Driver Enumeration
63// ============================================================================
64
65/// Supported database driver types.
66///
67/// This enum represents the different database backends that Bottle ORM can work with.
68/// The driver type is automatically detected from the connection URL and used to
69/// generate appropriate SQL syntax for each database system.
70///
71/// # Variants
72///
73/// * `Postgres` - PostgreSQL database (9.5+)
74/// * `SQLite` - SQLite database (3.x)
75/// * `MySQL` - MySQL or MariaDB database (5.7+/10.2+)
76///
77/// # SQL Dialect Differences
78///
79/// Different drivers use different SQL syntax:
80///
81/// - **Placeholders**:
82/// - PostgreSQL: `$1, $2, $3` (numbered)
83/// - SQLite/MySQL: `?, ?, ?` (positional)
84///
85/// - **Type Casting**:
86/// - PostgreSQL: `$1::UUID`, `$2::TIMESTAMPTZ`
87/// - SQLite/MySQL: Automatic type inference
88///
89/// - **Schema Queries**:
90/// - PostgreSQL: `information_schema` tables
91/// - SQLite: `sqlite_master` system table
92/// - MySQL: `information_schema` tables
93///
94/// # Example
95///
96/// ```rust,ignore
97/// match db.driver {
98/// Drivers::Postgres => println!("Using PostgreSQL"),
99/// Drivers::SQLite => println!("Using SQLite"),
100/// Drivers::MySQL => println!("Using MySQL"),
101/// }
102/// ```
103#[derive(Clone, Debug, Copy)]
104pub enum Drivers {
105 /// PostgreSQL driver.
106 ///
107 /// Used for PostgreSQL databases. Supports advanced features like:
108 /// - UUID native type
109 /// - JSONB for JSON data
110 /// - Array types
111 /// - Full-text search
112 /// - Advanced indexing (GiST, GIN, etc.)
113 Postgres,
114
115 /// SQLite driver.
116 ///
117 /// Used for SQLite databases (both in-memory and file-based). Characteristics:
118 /// - Lightweight and embedded
119 /// - Single-file database
120 /// - Limited concurrent write support
121 /// - Good for development and small applications
122 SQLite,
123
124 /// MySQL driver.
125 ///
126 /// Used for MySQL and MariaDB databases. Features:
127 /// - Wide compatibility
128 /// - Good performance for read-heavy workloads
129 /// - Mature ecosystem
130 /// - ACID compliance (with InnoDB)
131 MySQL,
132}
133
134// ============================================================================
135// Database Builder
136// ============================================================================
137
138/// A builder for creating a `Database` connection with custom options.
139///
140/// Allows configuration of connection pool settings such as maximum connections,
141/// timeouts, and lifetimes.
142///
143/// # Example
144///
145/// ```rust,ignore
146/// let db = Database::builder()
147/// .max_connections(10)
148/// .min_connections(2)
149/// .acquire_timeout(std::time::Duration::from_secs(5))
150/// .connect("postgres://...")
151/// .await?;
152/// ```
153#[derive(Debug)]
154pub struct DatabaseBuilder {
155 options: AnyPoolOptions,
156}
157
158impl DatabaseBuilder {
159 /// Sets the maximum number of connections that this pool should maintain.
160 pub fn max_connections(mut self, max: u32) -> Self {
161 self.options = self.options.max_connections(max);
162 self
163 }
164
165 /// Sets the minimum number of connections that this pool should maintain.
166 pub fn min_connections(mut self, min: u32) -> Self {
167 self.options = self.options.min_connections(min);
168 self
169 }
170
171 /// Sets the maximum amount of time to spend waiting for a connection.
172 pub fn acquire_timeout(mut self, timeout: Duration) -> Self {
173 self.options = self.options.acquire_timeout(timeout);
174 self
175 }
176
177 /// Sets the maximum amount of time a connection may be idle.
178 pub fn idle_timeout(mut self, timeout: Duration) -> Self {
179 self.options = self.options.idle_timeout(Some(timeout));
180 self
181 }
182
183 /// Sets the maximum lifetime of a connection.
184 pub fn max_lifetime(mut self, lifetime: Duration) -> Self {
185 self.options = self.options.max_lifetime(Some(lifetime));
186 self
187 }
188
189 /// Connects to the database using the configured options.
190 pub async fn connect(self, url: &str) -> Result<Database, Error> {
191 // Install default drivers for sqlx::Any
192 sqlx::any::install_default_drivers();
193
194 let pool = self.options.connect(url).await?;
195
196 // Detect driver type from URL scheme
197 let (driver_str, _) = url.split_once(':').unwrap_or(("sqlite", ""));
198 let driver = match driver_str {
199 "postgresql" | "postgres" => Drivers::Postgres,
200 "mysql" => Drivers::MySQL,
201 _ => Drivers::SQLite,
202 };
203
204 Ok(Database { pool, driver })
205 }
206}
207
208// ============================================================================
209// Database Connection and Management
210// ============================================================================
211
212/// The main entry point for database connection and management.
213///
214/// `Database` handles connection pooling, driver detection, and provides methods
215/// for schema operations and query building. It uses sqlx's `AnyPool` to support
216/// multiple database backends with a unified interface.
217///
218/// # Fields
219///
220/// * `pool` - The sqlx connection pool for executing queries
221/// * `driver` - The detected database driver type
222///
223/// # Thread Safety
224///
225/// `Database` implements `Clone` and can be safely shared across threads.
226/// The underlying connection pool is thread-safe and handles connection
227/// distribution automatically.
228///
229/// # Example
230///
231/// ```rust,ignore
232/// use bottle_orm::Database;
233///
234/// #[tokio::main]
235/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
236/// // Connect to database
237/// let db = Database::connect("postgres://localhost/mydb").await?;
238///
239/// // Create migrator
240/// let migrator = db.migrator();
241///
242/// // Build queries
243/// let query = db.model::<User>();
244///
245/// // Database can be cloned and shared
246/// let db_clone = db.clone();
247/// tokio::spawn(async move {
248/// let users = db_clone.model::<User>().scan().await;
249/// });
250///
251/// Ok(())
252/// }
253/// ```
254#[derive(Debug, Clone)]
255pub struct Database {
256 /// The sqlx connection pool for executing database queries.
257 ///
258 /// This pool manages a set of database connections that can be reused
259 /// across multiple queries, improving performance by avoiding the overhead
260 /// of creating new connections for each operation.
261 pub(crate) pool: AnyPool,
262
263 /// The detected database driver type.
264 ///
265 /// Used to generate driver-specific SQL syntax (e.g., placeholders,
266 /// type casting, schema queries).
267 pub(crate) driver: Drivers,
268}
269
270// ============================================================================
271// Database Implementation
272// ============================================================================
273
274impl Database {
275 // ========================================================================
276 // Connection Management
277 // ========================================================================
278
279 /// Creates a builder to configure the database connection options.
280 ///
281 /// Returns a `DatabaseBuilder` which allows setting pool options like
282 /// `max_connections`, timeouts, etc.
283 ///
284 /// # Example
285 ///
286 /// ```rust,ignore
287 /// let db = Database::builder()
288 /// .max_connections(20)
289 /// .connect("postgres://...")
290 /// .await?;
291 /// ```
292 pub fn builder() -> DatabaseBuilder {
293 DatabaseBuilder { options: AnyPoolOptions::new() }
294 }
295
296 /// Connects to the database using a connection string (Database URL).
297 ///
298 /// This method establishes a connection pool to the specified database and
299 /// automatically detects the driver type based on the URL scheme. The connection
300 /// pool is configured with a default maximum of 5 connections.
301 ///
302 /// # Arguments
303 ///
304 /// * `url` - The database connection string with the format:
305 /// `<scheme>://<user>:<password>@<host>:<port>/<database>`
306 ///
307 /// # Supported URL Schemes
308 ///
309 /// - **PostgreSQL**: `postgres://` or `postgresql://`
310 /// - **MySQL**: `mysql://`
311 /// - **SQLite**: `sqlite://` or `sqlite::memory:` (for in-memory databases)
312 ///
313 /// # Connection Pool Configuration
314 ///
315 /// - Maximum connections: 5
316 /// - Connection timeout: Default (30 seconds)
317 /// - Idle timeout: Default (10 minutes)
318 ///
319 /// # Returns
320 ///
321 /// * `Ok(Database)` - Successfully connected database instance
322 /// * `Err(sqlx::Error)` - Connection error (invalid URL, authentication failure, etc.)
323 ///
324 /// # Examples
325 ///
326 /// ```rust,ignore
327 /// // PostgreSQL connection
328 /// let db = Database::connect("postgres://user:password@localhost:5432/mydb").await?;
329 ///
330 /// // PostgreSQL with SSL
331 /// let db = Database::connect("postgres://user:password@localhost/mydb?sslmode=require").await?;
332 ///
333 /// // SQLite in-memory database (great for testing)
334 /// let db = Database::connect("sqlite::memory:").await?;
335 ///
336 /// // SQLite file-based database
337 /// let db = Database::connect("sqlite://./database.db").await?;
338 ///
339 /// // MySQL connection
340 /// let db = Database::connect("mysql://user:password@localhost:3306/mydb").await?;
341 /// ```
342 ///
343 /// # Error Handling
344 ///
345 /// ```rust,ignore
346 /// match Database::connect("postgres://localhost/mydb").await {
347 /// Ok(db) => println!("Connected successfully"),
348 /// Err(e) => eprintln!("Connection failed: {}", e),
349 /// }
350 /// ```
351 pub async fn connect(url: &str) -> Result<Self, Error> {
352 Self::builder().max_connections(5).connect(url).await
353 }
354
355 // ========================================================================
356 // Schema Management
357 // ========================================================================
358
359 /// Creates a `Migrator` instance to manage schema migrations.
360 ///
361 /// The migrator allows you to register multiple models and execute
362 /// all necessary schema changes (table creation, foreign keys) in the
363 /// correct order.
364 ///
365 /// # Returns
366 ///
367 /// A new `Migrator` instance associated with this database connection
368 ///
369 /// # Example
370 ///
371 /// ```rust,ignore
372 /// let db = Database::connect("sqlite::memory:").await?;
373 ///
374 /// db.migrator()
375 /// .register::<User>()
376 /// .register::<Post>()
377 /// .register::<Comment>()
378 /// .run()
379 /// .await?;
380 /// ```
381 ///
382 /// # See Also
383 ///
384 /// * [`Migrator`] - For detailed migration documentation
385 /// * [`Migrator::register()`] - For registering models
386 /// * [`Migrator::run()`] - For executing migrations
387 pub fn migrator(&self) -> Migrator<'_> {
388 Migrator::new(self)
389 }
390
391 // ========================================================================
392 // Query Building
393 // ========================================================================
394
395 /// Starts building a query for a specific Model.
396 ///
397 /// This method creates a new `QueryBuilder` instance configured for the
398 /// specified model type. The query builder provides a fluent interface
399 /// for constructing SELECT and INSERT queries.
400 ///
401 /// # Type Parameters
402 ///
403 /// * `T` - The Model type to query. Must implement `Model + Send + Sync + Unpin`
404 ///
405 /// # Returns
406 ///
407 /// A new `QueryBuilder` instance ready for query construction
408 ///
409 /// # Example
410 ///
411 /// ```rust,ignore
412 /// // Simple query
413 /// let users: Vec<User> = db.model::<User>().scan().await?;
414 ///
415 /// // Filtered query
416 /// let adults: Vec<User> = db.model::<User>()
417 /// .filter("age", ">=", 18)
418 /// .scan()
419 /// .await?;
420 ///
421 /// // Insert operation
422 /// let new_user = User { /* ... */ };
423 /// db.model::<User>().insert(&new_user).await?;
424 /// ```
425 ///
426 /// # See Also
427 ///
428 /// * [`QueryBuilder`] - For detailed query building documentation
429 /// * [`QueryBuilder::filter()`] - For adding WHERE clauses
430 /// * [`QueryBuilder::scan()`] - For executing SELECT queries
431 /// * [`QueryBuilder::insert()`] - For INSERT operations
432 pub fn model<T: Model + Send + Sync + Unpin>(&self) -> QueryBuilder<'_, T, Self> {
433 // Get active column names from the model
434 let active_columns = T::active_columns();
435 let mut columns: Vec<String> = Vec::with_capacity(active_columns.capacity());
436
437 // Convert column names to snake_case and strip 'r#' prefix if present
438 for col in active_columns {
439 columns.push(col.strip_prefix("r#").unwrap_or(col).to_snake_case());
440 }
441
442 // Create and return the query builder
443 QueryBuilder::new(self.clone(), self.driver, T::table_name(), T::columns(), columns)
444 }
445
446 /// Creates a raw SQL query builder.
447 ///
448 /// This provides a "safety hatch" to execute raw SQL queries when the fluent
449 /// QueryBuilder is not sufficient (e.g., complex joins, CTEs, specific DB features).
450 ///
451 /// # Arguments
452 ///
453 /// * `sql` - The raw SQL query string (use `$1`, `?`, etc. for placeholders based on driver)
454 ///
455 /// # Returns
456 ///
457 /// A `RawQuery` builder that allows binding parameters and executing the query.
458 ///
459 /// # Example
460 ///
461 /// ```rust,ignore
462 /// // Fetching complex data
463 /// let users: Vec<User> = db.raw("SELECT * FROM users WHERE age > $1")
464 /// .bind(18)
465 /// .fetch_all()
466 /// .await?;
467 ///
468 /// // Executing an update
469 /// let affected = db.raw("UPDATE users SET active = $1 WHERE last_login < $2")
470 /// .bind(false)
471 /// .bind(one_year_ago)
472 /// .execute()
473 /// .await?;
474 /// ```
475 pub fn raw<'a>(&'a self, sql: &'a str) -> RawQuery<'a, Self> {
476 RawQuery::new(self.clone(), sql)
477 }
478
479 // ========================================================================
480 // Table Creation
481 // ========================================================================
482
483 /// Creates the table for model `T` if it does not exist.
484 ///
485 /// This method generates and executes SQL to create a table based on the
486 /// model's structure. It handles column definitions, primary keys, unique
487 /// constraints, default values, and indexes.
488 ///
489 /// # Type Parameters
490 ///
491 /// * `T` - The Model type representing the table
492 ///
493 /// # Returns
494 ///
495 /// * `Ok(&Self)` - Reference to self for method chaining
496 /// * `Err(sqlx::Error)` - Database error during table creation
497 ///
498 /// # Generated SQL Features
499 ///
500 /// - **Primary Keys**: Automatically marked with `PRIMARY KEY`
501 /// - **NOT NULL**: Non-nullable fields get `NOT NULL` constraint
502 /// - **UNIQUE**: Fields marked with `#[orm(unique)]` get `UNIQUE` constraint
503 /// - **DEFAULT**: Fields marked with `#[orm(create_time)]` get `DEFAULT CURRENT_TIMESTAMP`
504 /// - **Indexes**: Fields marked with `#[orm(index)]` get database indexes
505 ///
506 /// # Example
507 ///
508 /// ```rust,ignore
509 /// use bottle_orm::Model;
510 /// use uuid::Uuid;
511 /// use chrono::{DateTime, Utc};
512 ///
513 /// #[derive(Model)]
514 /// struct User {
515 /// #[orm(primary_key)]
516 /// id: Uuid,
517 /// #[orm(size = 50, unique)]
518 /// username: String,
519 /// #[orm(index)]
520 /// email: String,
521 /// age: i32,
522 /// #[orm(create_time)]
523 /// created_at: DateTime<Utc>,
524 /// }
525 ///
526 /// // Creates table with:
527 /// // - UUID primary key
528 /// // - Unique username constraint
529 /// // - Index on email
530 /// // - created_at with DEFAULT CURRENT_TIMESTAMP
531 /// db.create_table::<User>().await?;
532 /// ```
533 ///
534 /// # Generated SQL Example (PostgreSQL)
535 ///
536 /// ```sql
537 /// CREATE TABLE IF NOT EXISTS "user" (
538 /// "id" UUID PRIMARY KEY,
539 /// "username" VARCHAR(50) NOT NULL UNIQUE,
540 /// "email" TEXT NOT NULL,
541 /// "age" INTEGER NOT NULL,
542 /// "created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
543 /// );
544 /// CREATE INDEX IF NOT EXISTS "idx_user_email" ON "user" ("email");
545 /// ```
546 pub async fn create_table<T: Model>(&self) -> Result<&Self, Error> {
547 // Get table name in snake_case format
548 let table_name = T::table_name().to_snake_case();
549 let columns = T::columns();
550
551 let mut column_defs = Vec::new();
552 let mut index_statements = Vec::new();
553 let mut primary_keys = Vec::new(); // Track primary key columns to support composite keys
554
555 // Build column definitions
556 for col in &columns {
557 // Strip 'r#' prefix if present (for Rust keywords used as column names)
558 let col_name = col.name.strip_prefix("r#").unwrap_or(col.name).to_snake_case();
559 let mut def = format!("\"{}\" {}", col_name, col.sql_type);
560
561 // Collect primary key columns to be added as a table constraint later
562 if col.is_primary_key {
563 primary_keys.push(col_name.clone());
564 }
565
566 // Ensure non-nullable columns and primary keys are marked as NOT NULL
567 if !col.is_nullable || col.is_primary_key {
568 def.push_str(" NOT NULL");
569 }
570
571 // Add DEFAULT CURRENT_TIMESTAMP for create_time fields
572 if col.create_time {
573 def.push_str(" DEFAULT CURRENT_TIMESTAMP");
574 }
575
576 // Add UNIQUE constraint
577 if col.unique {
578 def.push_str(" UNIQUE");
579 }
580
581 column_defs.push(def);
582
583 // Generate index creation statement if needed
584 if col.index {
585 let index_type = if col.unique { "UNIQUE INDEX" } else { "INDEX" };
586 let index_name = format!("idx_{}_{}", table_name, col_name);
587
588 let index_query = format!(
589 "CREATE {} IF NOT EXISTS \"{}\" ON \"{}\" (\"{}\")",
590 index_type, index_name, table_name, col_name,
591 );
592
593 index_statements.push(index_query);
594 }
595 }
596
597 // Add PRIMARY KEY constraint if any columns were marked as primary keys
598 if !primary_keys.is_empty() {
599 let pk_columns = primary_keys.iter().map(|pk| format!("\"{}\"", pk)).collect::<Vec<_>>().join(", ");
600
601 let pk_constraint = format!("PRIMARY KEY ({})", pk_columns);
602 column_defs.push(pk_constraint);
603 }
604
605 // Add SQLite Foreign Keys inline (SQLite doesn't support ADD CONSTRAINT)
606 if let Drivers::SQLite = self.driver {
607 for col in &columns {
608 if let (Some(f_table), Some(f_key)) = (col.foreign_table, col.foreign_key) {
609 let col_name = col.name.strip_prefix("r#").unwrap_or(col.name).to_snake_case();
610 let f_table_clean = f_table.to_snake_case();
611 let f_key_clean = f_key.to_snake_case();
612
613 let fk_def = format!(
614 "FOREIGN KEY (\"{}\") REFERENCES \"{}\" (\"{}\")",
615 col_name, f_table_clean, f_key_clean
616 );
617 column_defs.push(fk_def);
618 }
619 }
620 }
621
622 // Build and execute CREATE TABLE statement
623 let create_table_query = format!("CREATE TABLE IF NOT EXISTS \"{}\" ({})", table_name, column_defs.join(", "));
624 log::info!("{}", create_table_query);
625
626 sqlx::query(&create_table_query).execute(&self.pool).await?;
627
628 // Create indexes
629 for idx_stmt in index_statements {
630 sqlx::query(&idx_stmt).execute(&self.pool).await?;
631 }
632
633 Ok(self)
634 }
635
636 /// Returns the current columns of a table in the database.
637 pub async fn get_table_columns(&self, table_name: &str) -> Result<Vec<String>, Error> {
638 let table_name_snake = table_name.to_snake_case();
639 let query = match self.driver {
640 Drivers::Postgres => {
641 "SELECT column_name FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public'".to_string()
642 }
643 Drivers::MySQL => {
644 "SELECT column_name FROM information_schema.columns WHERE table_name = ? AND table_schema = DATABASE()".to_string()
645 }
646 Drivers::SQLite => {
647 // SQLite needs a different approach since PRAGMA is not a standard SELECT
648 format!("PRAGMA table_info(\"{}\")", table_name_snake)
649 }
650 };
651
652 let rows = if let Drivers::SQLite = self.driver {
653 sqlx::query(&query).fetch_all(&self.pool).await?
654 } else {
655 sqlx::query(&query).bind(&table_name_snake).fetch_all(&self.pool).await?
656 };
657
658 let mut columns = Vec::new();
659 for row in rows {
660 let col_name: String = if let Drivers::SQLite = self.driver {
661 row.try_get("name")?
662 } else {
663 row.try_get(0)?
664 };
665 columns.push(col_name);
666 }
667
668 Ok(columns)
669 }
670
671 /// Returns the current indexes of a table in the database.
672 pub async fn get_table_indexes(&self, table_name: &str) -> Result<Vec<String>, Error> {
673 let table_name_snake = table_name.to_snake_case();
674 let query = match self.driver {
675 Drivers::Postgres => {
676 "SELECT indexname FROM pg_indexes WHERE tablename = $1 AND schemaname = 'public'".to_string()
677 }
678 Drivers::MySQL => {
679 "SELECT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_NAME = ? AND TABLE_SCHEMA = DATABASE()".to_string()
680 }
681 Drivers::SQLite => {
682 format!("PRAGMA index_list(\"{}\")", table_name_snake)
683 }
684 };
685
686 let rows = if let Drivers::SQLite = self.driver {
687 sqlx::query(&query).fetch_all(&self.pool).await?
688 } else {
689 sqlx::query(&query).bind(&table_name_snake).fetch_all(&self.pool).await?
690 };
691
692 let mut indexes = Vec::new();
693 for row in rows {
694 let idx_name: String = if let Drivers::SQLite = self.driver {
695 row.try_get("name")?
696 } else {
697 row.try_get(0)?
698 };
699 indexes.push(idx_name);
700 }
701
702 Ok(indexes)
703 }
704
705 /// Synchronizes the database table with the model definition (Diffing).
706 ///
707 /// This method compares the current table structure in the database with the
708 /// model's `ColumnInfo`. If the table doesn't exist, it creates it. If it
709 /// exists, it adds any missing columns or indexes.
710 pub async fn sync_table<T: Model>(&self) -> Result<&Self, Error> {
711 let table_name = T::table_name().to_snake_case();
712
713 // Check if table exists
714 let exists: bool = match self.driver {
715 Drivers::Postgres => {
716 let row = sqlx::query("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $1 AND table_schema = 'public')")
717 .bind(&table_name)
718 .fetch_one(&self.pool)
719 .await?;
720 row.try_get(0)?
721 }
722 Drivers::MySQL => {
723 let row = sqlx::query("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = ? AND table_schema = DATABASE())")
724 .bind(&table_name)
725 .fetch_one(&self.pool)
726 .await?;
727 row.try_get(0)?
728 }
729 Drivers::SQLite => {
730 let row = sqlx::query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?")
731 .bind(&table_name)
732 .fetch_one(&self.pool)
733 .await?;
734 let count: i64 = row.try_get(0)?;
735 count > 0
736 }
737 };
738
739 if !exists {
740 // Table doesn't exist, create it from scratch
741 self.create_table::<T>().await?;
742 return Ok(self);
743 }
744
745 // Table exists, perform diffing
746 let current_columns = self.get_table_columns(&table_name).await?;
747 let current_indexes = self.get_table_indexes(&table_name).await?;
748 let model_columns = T::columns();
749
750 for col in model_columns {
751 let col_name = col.name.strip_prefix("r#").unwrap_or(col.name).to_snake_case();
752 let index_name = format!("idx_{}_{}", table_name, col_name);
753 // 1. Check for missing columns
754 if !current_columns.contains(&col_name) {
755 log::info!("Adding missing column '{}' to table '{}'", col_name, table_name);
756
757 let mut alter_query = format!("ALTER TABLE \"{}\" ADD COLUMN \"{}\" {}", table_name, col_name, col.sql_type);
758
759 if !col.is_nullable {
760 if col.create_time {
761 alter_query.push_str(" NOT NULL DEFAULT CURRENT_TIMESTAMP");
762 }
763 // Note: We avoid adding NOT NULL to existing columns without DEFAULT to prevent errors
764 }
765
766 sqlx::query(&alter_query).execute(&self.pool).await?;
767 }
768
769 // 2. Check for missing indexes (even if column existed)
770 if col.index && !current_indexes.contains(&index_name) {
771 log::info!("Creating missing index '{}' on table '{}'", index_name, table_name);
772 let index_type = if col.unique { "UNIQUE INDEX" } else { "INDEX" };
773 let index_query = format!(
774 "CREATE {} IF NOT EXISTS \"{}\" ON \"{}\" (\"{}\")",
775 index_type, index_name, table_name, col_name,
776 );
777 sqlx::query(&index_query).execute(&self.pool).await?;
778 }
779 }
780
781 Ok(self)
782 }
783
784 /// Starts a new database transaction.
785 ///
786 /// Returns a `Transaction` wrapper that can be used to execute multiple
787 /// queries atomically. The transaction must be explicitly committed
788 /// using `commit()`, otherwise it will be rolled back when dropped.
789 ///
790 /// # Returns
791 ///
792 /// * `Ok(Transaction)` - A new transaction instance
793 /// * `Err(sqlx::Error)` - Database error starting transaction
794 ///
795 /// # Example
796 ///
797 /// ```rust,ignore
798 /// let mut tx = db.begin().await?;
799 /// // ... perform operations ...
800 /// tx.commit().await?;
801 /// ```
802 pub async fn begin<'a>(&self) -> Result<Transaction<'a>, sqlx::Error> {
803 let tx = self.pool.begin().await?;
804 Ok(Transaction { tx, driver: self.driver })
805 }
806
807 // ========================================================================
808 // Foreign Key Management
809 // ========================================================================
810
811 /// Checks for and assigns Foreign Keys for model `T`.
812 ///
813 /// This method examines all columns marked with `#[orm(foreign_key = "Table::Column")]`
814 /// and creates the appropriate foreign key constraints. It verifies that constraints
815 /// don't already exist before attempting to create them, preventing duplication errors.
816 ///
817 /// # Type Parameters
818 ///
819 /// * `T` - The Model type to process for foreign keys
820 ///
821 /// # Returns
822 ///
823 /// * `Ok(&Self)` - Reference to self for method chaining
824 /// * `Err(sqlx::Error)` - Database error during foreign key creation
825 ///
826 /// # Constraint Naming
827 ///
828 /// Foreign key constraints are named using the pattern:
829 /// `fk_{table_name}_{column_name}`
830 ///
831 /// # Example
832 ///
833 /// ```rust,ignore
834 /// use bottle_orm::Model;
835 /// use uuid::Uuid;
836 ///
837 /// #[derive(Model)]
838 /// struct User {
839 /// #[orm(primary_key)]
840 /// id: Uuid,
841 /// username: String,
842 /// }
843 ///
844 /// #[derive(Model)]
845 /// struct Post {
846 /// #[orm(primary_key)]
847 /// id: Uuid,
848 /// #[orm(foreign_key = "User::id")]
849 /// user_id: Uuid,
850 /// title: String,
851 /// }
852 ///
853 /// // Create tables first
854 /// db.create_table::<User>().await?;
855 /// db.create_table::<Post>().await?;
856 ///
857 /// // Then assign foreign keys
858 /// db.assign_foreign_keys::<Post>().await?;
859 /// ```
860 ///
861 /// # Generated SQL Example
862 ///
863 /// ```sql
864 /// ALTER TABLE "post"
865 /// ADD CONSTRAINT "fk_post_user_id"
866 /// FOREIGN KEY ("user_id")
867 /// REFERENCES "user" ("id");
868 /// ```
869 ///
870 /// # Important Notes
871 ///
872 /// - Foreign key assignment should be done **after** all tables are created
873 /// - The referenced table and column must exist before creating the foreign key
874 /// - Use the `Migrator` to handle the correct order automatically
875 /// - Currently optimized for PostgreSQL (uses `information_schema`)
876 ///
877 /// # See Also
878 ///
879 /// * [`Migrator`] - For automatic migration order management
880 pub async fn assign_foreign_keys<T: Model>(&self) -> Result<&Self, Error> {
881 // SQLite handles FKs in create_table, so we skip here
882 if let Drivers::SQLite = self.driver {
883 return Ok(self);
884 }
885
886 // Get table name in snake_case format
887 let table_name = T::table_name().to_snake_case();
888 let columns = T::columns();
889
890 // Process each column that has a foreign key definition
891 for col in columns {
892 if let (Some(f_table), Some(f_key)) = (col.foreign_table, col.foreign_key) {
893 // Clean up column and reference names
894 let col_name = col.name.strip_prefix("r#").unwrap_or(col.name).to_snake_case();
895 let f_table_clean = f_table.to_snake_case();
896 let f_key_clean = f_key.to_snake_case();
897
898 // Generate constraint name
899 let constraint_name = format!("fk_{}_{}", table_name, col_name);
900
901 // Check if constraint already exists
902 let count: i64 = match self.driver {
903 Drivers::Postgres => {
904 let check_query =
905 "SELECT count(*) FROM information_schema.table_constraints WHERE constraint_name = $1";
906 let row = sqlx::query(check_query).bind(&constraint_name).fetch_one(&self.pool).await?;
907 row.try_get(0).unwrap_or(0)
908 }
909 Drivers::MySQL => {
910 let check_query = "SELECT count(*) FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = ? AND TABLE_SCHEMA = DATABASE()";
911 let row = sqlx::query(check_query).bind(&constraint_name).fetch_one(&self.pool).await?;
912 row.try_get(0).unwrap_or(0)
913 }
914 Drivers::SQLite => 0, // Unreachable
915 };
916
917 // Skip if constraint already exists
918 if count > 0 {
919 continue;
920 }
921
922 // Create foreign key constraint
923 let alter_query = format!(
924 "ALTER TABLE \"{}\" ADD CONSTRAINT \"{}\" FOREIGN KEY (\"{}\") REFERENCES \"{}\" (\"{}\")",
925 table_name, constraint_name, col_name, f_table_clean, f_key_clean
926 );
927
928 sqlx::query(&alter_query).execute(&self.pool).await?;
929 }
930 }
931
932 Ok(self)
933 }
934}
935
936/// A trait representing a database connection or transaction.
937///
938/// This trait abstracts over `Database` (pool) and `Transaction` types, allowing
939/// the `QueryBuilder` to work seamlessly with both. It uses Generic Associated Types (GATs)
940/// to handle the lifetimes of the executor references correctly.
941pub trait Connection {
942 /// The type of the executor returned by this connection.
943 ///
944 /// This uses GATs to bind the lifetime of the executor (`'c`) to the lifetime
945 /// of the borrow of the connection (`&'c mut self`).
946 type Exec<'c>: sqlx::Executor<'c, Database = sqlx::Any>
947 where
948 Self: 'c;
949
950 /// Returns a mutable reference to the SQLx executor.
951 ///
952 /// # Returns
953 ///
954 /// An executor capable of running SQL queries (either a Pool or a Transaction).
955 fn executor<'c>(&'c mut self) -> Self::Exec<'c>;
956}
957
958/// Implementation of Connection for the main Database struct.
959///
960/// Uses the internal connection pool to execute queries.
961impl Connection for Database {
962 type Exec<'c> = &'c sqlx::Pool<sqlx::Any>;
963
964 fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
965 &self.pool
966 }
967}
968
969/// Implementation of Connection for a mutable reference to Database.
970impl<'a> Connection for &'a mut Database {
971 type Exec<'c>
972 = &'c sqlx::Pool<sqlx::Any>
973 where
974 Self: 'c;
975
976 fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
977 &self.pool
978 }
979}
980
981/// Implementation of Connection for a mutable reference to sqlx::Transaction.
982impl<'a> Connection for &mut sqlx::Transaction<'a, sqlx::Any> {
983 type Exec<'c>
984 = &'c mut sqlx::AnyConnection
985 where
986 Self: 'c;
987
988 fn executor<'c>(&'c mut self) -> Self::Exec<'c> {
989 &mut **self
990 }
991}
992
993// ============================================================================
994// Raw SQL Query Builder
995// ============================================================================
996/// A builder for executing raw SQL queries with parameter binding.
997///
998/// Returned by `Database::raw()` or `Transaction::raw()`. Allows constructing safe, parameterized
999/// SQL queries that can bypass the standard model-based QueryBuilder when
1000/// complex SQL features (CTEs, Window Functions, etc.) are needed.
1001pub struct RawQuery<'a, C> {
1002 conn: C,
1003 sql: &'a str,
1004 args: AnyArguments<'a>,
1005}
1006
1007impl<'a, C> RawQuery<'a, C>
1008where
1009 C: Connection + Send,
1010{
1011 /// Creates a new RawQuery instance.
1012 pub(crate) fn new(conn: C, sql: &'a str) -> Self {
1013 Self { conn, sql, args: AnyArguments::default() }
1014 }
1015
1016 /// Binds a parameter to the query.
1017 ///
1018 /// # Arguments
1019 ///
1020 /// * `value` - The value to bind. Must implement `sqlx::Encode` and `sqlx::Type`.
1021 pub fn bind<T>(mut self, value: T) -> Self
1022 where
1023 T: 'a + sqlx::Encode<'a, sqlx::Any> + sqlx::Type<sqlx::Any> + Send + Sync,
1024 {
1025 let _ = self.args.add(value);
1026 self
1027 }
1028
1029 /// Executes the query and returns all matching rows mapped to type `T`.
1030 pub async fn fetch_all<T>(mut self) -> Result<Vec<T>, Error>
1031 where
1032 T: for<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> + Send + Unpin,
1033 {
1034 sqlx::query_as_with::<_, T, _>(self.sql, self.args).fetch_all(self.conn.executor()).await
1035 }
1036
1037 /// Executes the query and returns the first matching row mapped to type `T`.
1038 pub async fn fetch_one<T>(mut self) -> Result<T, Error>
1039 where
1040 T: for<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> + Send + Unpin,
1041 {
1042 sqlx::query_as_with::<_, T, _>(self.sql, self.args).fetch_one(self.conn.executor()).await
1043 }
1044
1045 /// Executes the query and returns the first matching row, or None if not found.
1046 pub async fn fetch_optional<T>(mut self) -> Result<Option<T>, Error>
1047 where
1048 T: for<'r> sqlx::FromRow<'r, sqlx::any::AnyRow> + Send + Unpin,
1049 {
1050 sqlx::query_as_with::<_, T, _>(self.sql, self.args).fetch_optional(self.conn.executor()).await
1051 }
1052
1053 /// Executes the query and returns a single scalar value.
1054 ///
1055 /// Useful for queries like `SELECT count(*) ...` or `SELECT id ...`.
1056 pub async fn fetch_scalar<O>(mut self) -> Result<O, Error>
1057 where
1058 O: for<'r> sqlx::Decode<'r, sqlx::Any> + sqlx::Type<sqlx::Any> + Send + Unpin,
1059 {
1060 sqlx::query_scalar_with::<_, O, _>(self.sql, self.args).fetch_one(self.conn.executor()).await
1061 }
1062
1063 /// Executes the query and returns a single scalar value, or None if not found.
1064 pub async fn fetch_scalar_optional<O>(mut self) -> Result<Option<O>, Error>
1065 where
1066 O: for<'r> sqlx::Decode<'r, sqlx::Any> + sqlx::Type<sqlx::Any> + Send + Unpin,
1067 {
1068 sqlx::query_scalar_with::<_, O, _>(self.sql, self.args).fetch_optional(self.conn.executor()).await
1069 }
1070
1071 /// Executes the query (INSERT, UPDATE, DELETE) and returns the number of affected rows.
1072 pub async fn execute(mut self) -> Result<u64, Error> {
1073 let result = sqlx::query_with(self.sql, self.args).execute(self.conn.executor()).await?;
1074
1075 Ok(result.rows_affected())
1076 }
1077}