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}