Skip to main content

things3_core/database/
core.rs

1#![allow(deprecated)]
2
3use crate::{
4    database::pool::{apply_sqlite_optimizations, DatabasePoolConfig},
5    error::{Result as ThingsResult, ThingsError},
6};
7use sqlx::{pool::PoolOptions, SqlitePool};
8use std::path::Path;
9use tracing::{info, instrument};
10
11/// SQLx-based database implementation for Things 3 data
12/// This provides async, Send + Sync compatible database access
13#[derive(Debug, Clone)]
14pub struct ThingsDatabase {
15    pub(crate) pool: SqlitePool,
16    pub(crate) config: DatabasePoolConfig,
17}
18
19impl ThingsDatabase {
20    /// Create a new database connection pool with default configuration
21    ///
22    /// # Examples
23    ///
24    /// ```no_run
25    /// use things3_core::{ThingsDatabase, ThingsError};
26    /// use std::path::Path;
27    ///
28    /// # async fn example() -> Result<(), ThingsError> {
29    /// // Connect to Things 3 database
30    /// let db = ThingsDatabase::new(Path::new("/path/to/things.db")).await?;
31    ///
32    /// // Get inbox tasks
33    /// let tasks = db.get_inbox(None).await?;
34    /// println!("Found {} tasks in inbox", tasks.len());
35    /// # Ok(())
36    /// # }
37    /// ```
38    ///
39    /// # Errors
40    ///
41    /// Returns an error if the database connection fails or if `SQLite` configuration fails
42    #[instrument]
43    pub async fn new(database_path: &Path) -> ThingsResult<Self> {
44        Self::new_with_config(database_path, DatabasePoolConfig::default()).await
45    }
46
47    /// Create a new database connection pool with custom configuration
48    ///
49    /// # Examples
50    ///
51    /// ```no_run
52    /// use things3_core::{ThingsDatabase, DatabasePoolConfig, ThingsError};
53    /// use std::path::Path;
54    /// use std::time::Duration;
55    ///
56    /// # async fn example() -> Result<(), ThingsError> {
57    /// // Create custom pool configuration
58    /// let config = DatabasePoolConfig {
59    ///     max_connections: 10,
60    ///     min_connections: 2,
61    ///     connect_timeout: Duration::from_secs(5),
62    ///     idle_timeout: Duration::from_secs(300),
63    ///     max_lifetime: Duration::from_secs(3600),
64    ///     test_before_acquire: true,
65    ///     sqlite_optimizations: Default::default(),
66    /// };
67    ///
68    /// // Connect with custom configuration
69    /// let db = ThingsDatabase::new_with_config(
70    ///     Path::new("/path/to/things.db"),
71    ///     config,
72    /// ).await?;
73    /// # Ok(())
74    /// # }
75    /// ```
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if the database connection fails or if `SQLite` configuration fails
80    #[instrument]
81    pub async fn new_with_config(
82        database_path: &Path,
83        config: DatabasePoolConfig,
84    ) -> ThingsResult<Self> {
85        let database_url = format!("sqlite:{}", database_path.display());
86
87        info!(
88            "Connecting to SQLite database at: {} with optimized pool",
89            database_url
90        );
91
92        let pool = PoolOptions::new()
93            .max_connections(config.max_connections)
94            .min_connections(config.min_connections)
95            .acquire_timeout(config.connect_timeout)
96            .idle_timeout(Some(config.idle_timeout))
97            .max_lifetime(Some(config.max_lifetime))
98            .test_before_acquire(config.test_before_acquire)
99            .connect(&database_url)
100            .await
101            .map_err(|e| ThingsError::unknown(format!("Failed to connect to database: {e}")))?;
102
103        apply_sqlite_optimizations(&pool, &config.sqlite_optimizations).await?;
104
105        info!(
106            "Database connection pool established successfully with {} max connections",
107            config.max_connections
108        );
109
110        Ok(Self { pool, config })
111    }
112
113    /// Create a new database connection pool from a connection string with default configuration
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if the database connection fails or if `SQLite` configuration fails
118    #[instrument]
119    pub async fn from_connection_string(database_url: &str) -> ThingsResult<Self> {
120        Self::from_connection_string_with_config(database_url, DatabasePoolConfig::default()).await
121    }
122
123    /// Create a new database connection pool from a connection string with custom configuration
124    ///
125    /// # Errors
126    ///
127    /// Returns an error if the database connection fails or if `SQLite` configuration fails
128    #[instrument]
129    pub async fn from_connection_string_with_config(
130        database_url: &str,
131        config: DatabasePoolConfig,
132    ) -> ThingsResult<Self> {
133        info!(
134            "Connecting to SQLite database: {} with optimized pool",
135            database_url
136        );
137
138        let pool = PoolOptions::new()
139            .max_connections(config.max_connections)
140            .min_connections(config.min_connections)
141            .acquire_timeout(config.connect_timeout)
142            .idle_timeout(Some(config.idle_timeout))
143            .max_lifetime(Some(config.max_lifetime))
144            .test_before_acquire(config.test_before_acquire)
145            .connect(database_url)
146            .await
147            .map_err(|e| ThingsError::unknown(format!("Failed to connect to database: {e}")))?;
148
149        apply_sqlite_optimizations(&pool, &config.sqlite_optimizations).await?;
150
151        info!(
152            "Database connection pool established successfully with {} max connections",
153            config.max_connections
154        );
155
156        Ok(Self { pool, config })
157    }
158
159    /// Get the underlying connection pool
160    #[must_use]
161    pub fn pool(&self) -> &SqlitePool {
162        &self.pool
163    }
164}