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