1use std::sync::Arc;
4use std::time::Duration;
5
6use runlight::BoxFuture;
7use runlight::store::{Db, DbError, Dialect, Hold, Param, Row};
8use sqlx::pool::PoolConnection;
9use sqlx::sqlite::{Sqlite, SqliteArguments};
10use sqlx::{AssertSqlSafe, SqlitePool};
11use tokio::sync::{Mutex, OwnedMutexGuard};
12
13use crate::rows::{db_error, sqlite_row};
14
15const BUSY_RETRY: Duration = Duration::from_secs(2);
17
18type Slot = Option<PoolConnection<Sqlite>>;
19
20#[derive(Clone)]
24pub struct SqliteDb {
25 pool: SqlitePool,
26 conn: Arc<Mutex<Slot>>,
27}
28
29impl SqliteDb {
30 pub fn new(pool: SqlitePool) -> SqliteDb {
32 SqliteDb { pool, conn: Arc::new(Mutex::new(None)) }
33 }
34}
35
36async fn open(pool: &SqlitePool, guard: &mut Slot) -> Result<(), DbError> {
38 if guard.is_none() {
39 let mut conn = pool.acquire().await.map_err(db_error)?;
40 wal(&mut conn).await.map_err(db_error)?;
43 exec(&mut conn, "PRAGMA synchronous = NORMAL").await.map_err(db_error)?;
44 exec(&mut conn, "PRAGMA busy_timeout = 5000").await.map_err(db_error)?;
45 *guard = Some(conn);
46 }
47 Ok(())
48}
49
50fn arguments(params: Vec<Param>) -> Result<SqliteArguments, DbError> {
51 use sqlx::Arguments;
52 let mut args = SqliteArguments::default();
53 for p in params {
54 match p {
55 Param::Null => args.add(Option::<i64>::None),
56 Param::Int(n) => args.add(n),
57 Param::Float(f) => args.add(f),
58 Param::Text(s) => args.add(s),
59 }
60 .map_err(db_error)?;
61 }
62 Ok(args)
63}
64
65fn broken(err: &sqlx::Error) -> bool {
66 matches!(err, sqlx::Error::Io(_) | sqlx::Error::PoolClosed | sqlx::Error::WorkerCrashed)
67}
68
69async fn all_on(pool: &SqlitePool, slot: &mut Slot, sql: &str, params: Vec<Param>) -> Result<Vec<Row>, DbError> {
70 open(pool, slot).await?;
71 let conn = slot.as_mut().expect("an open connection");
72 let result = sqlx::query_with(AssertSqlSafe(sql.to_string()), arguments(params)?).fetch_all(&mut **conn).await;
73 if let Err(e) = &result
74 && broken(e)
75 {
76 *slot = None;
77 }
78 result.map_err(db_error)?.iter().map(sqlite_row).collect()
79}
80
81async fn run_on(pool: &SqlitePool, slot: &mut Slot, sql: &str, params: Vec<Param>) -> Result<u64, DbError> {
82 open(pool, slot).await?;
83 let conn = slot.as_mut().expect("an open connection");
84 let result = sqlx::query_with(AssertSqlSafe(sql.to_string()), arguments(params)?).execute(&mut **conn).await;
85 if let Err(e) = &result
86 && broken(e)
87 {
88 *slot = None;
89 }
90 Ok(result.map_err(db_error)?.rows_affected())
91}
92
93impl Db for SqliteDb {
94 fn dialect(&self) -> Dialect {
95 Dialect::Sqlite
96 }
97
98 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
99 Box::pin(async move {
100 let mut slot = self.conn.lock().await;
101 all_on(&self.pool, &mut slot, sql, params).await
102 })
103 }
104
105 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
106 Box::pin(async move {
107 let mut slot = self.conn.lock().await;
108 run_on(&self.pool, &mut slot, sql, params).await
109 })
110 }
111
112 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
113 Box::pin(async move {
114 if kind == Hold::Exclusive {
116 return Ok(Arc::new(self.clone()) as Arc<dyn Db>);
117 }
118 let mut guard = self.conn.clone().lock_owned().await;
119 run_on(&self.pool, &mut guard, "BEGIN", vec![]).await?;
120 Ok(Arc::new(Held { pool: self.pool.clone(), guard: Arc::new(Mutex::new(Some(guard))), owner: true })
121 as Arc<dyn Db>)
122 })
123 }
124}
125
126struct Held {
129 pool: SqlitePool,
130 guard: Arc<Mutex<Option<OwnedMutexGuard<Slot>>>>,
131 owner: bool,
132}
133
134fn ended() -> DbError {
135 DbError("Runlight: the transaction has ended".into())
136}
137
138impl Db for Held {
139 fn dialect(&self) -> Dialect {
140 Dialect::Sqlite
141 }
142
143 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
144 Box::pin(async move {
145 let mut held = self.guard.lock().await;
146 let slot = held.as_mut().ok_or_else(ended)?;
147 all_on(&self.pool, slot, sql, params).await
148 })
149 }
150
151 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
152 Box::pin(async move {
153 let mut held = self.guard.lock().await;
154 let slot = held.as_mut().ok_or_else(ended)?;
155 run_on(&self.pool, slot, sql, params).await
156 })
157 }
158
159 fn hold(&self, _kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
160 Box::pin(async move {
162 Ok(Arc::new(Held { pool: self.pool.clone(), guard: self.guard.clone(), owner: false }) as Arc<dyn Db>)
163 })
164 }
165
166 fn finish(&self, ok: bool) -> BoxFuture<'_, Result<(), DbError>> {
167 Box::pin(async move {
168 if !self.owner {
169 return Ok(());
170 }
171 let mut held = self.guard.lock().await;
172 let Some(mut guard) = held.take() else { return Ok(()) };
173 let result = run_on(&self.pool, &mut guard, if ok { "COMMIT" } else { "ROLLBACK" }, vec![]).await;
174 if result.is_err() {
175 *guard = None;
177 }
178 result.map(|_| ())
179 })
180 }
181}
182
183async fn exec(conn: &mut PoolConnection<Sqlite>, text: &'static str) -> Result<(), sqlx::Error> {
185 sqlx::query(text).fetch_all(&mut **conn).await.map(|_| ())
186}
187
188fn is_busy(err: &sqlx::Error) -> bool {
189 let text = err.to_string();
190 let code = match err {
191 sqlx::Error::Database(db) => db.code().map(|c| c.to_string()).unwrap_or_default(),
192 _ => String::new(),
193 };
194 let busy_code = code.parse::<i64>().is_ok_and(|c| c & 0xff == 5 || c & 0xff == 6);
196 busy_code || text.contains("SQLITE_BUSY") || text.contains("database is locked")
197}
198
199async fn wal(conn: &mut PoolConnection<Sqlite>) -> Result<(), sqlx::Error> {
201 let mut waited = Duration::ZERO;
202 let mut attempt = 0u32;
203 loop {
204 match exec(conn, "PRAGMA journal_mode = WAL").await {
205 Err(err) if is_busy(&err) && waited < BUSY_RETRY => {
206 let pause = Duration::from_millis(10u64 << attempt.min(10))
207 .min(Duration::from_millis(200))
208 .min(BUSY_RETRY - waited);
209 tokio::time::sleep(pause).await;
210 waited += pause;
211 attempt += 1;
212 }
213 other => return other,
214 }
215 }
216}