runlight_sqlx/
postgres.rs1use std::sync::Arc;
5use std::time::Instant;
6
7use runlight::BoxFuture;
8use runlight::store::{
9 Db, DbError, Dialect, Hold, Param, Row, TablesBusyError, fill_placeholders, lock_wait, postgres_literal,
10};
11use sqlx::pool::PoolConnection;
12use sqlx::{AssertSqlSafe, PgPool, Postgres};
13use tokio::sync::Mutex;
14
15use crate::rows::{db_error, pg_row};
16
17const MIGRATION_LOCK: i64 = 7_331_906;
19
20#[derive(Clone)]
23pub struct PostgresDb {
24 pool: PgPool,
25}
26
27impl PostgresDb {
28 pub fn new(pool: PgPool) -> PostgresDb {
30 PostgresDb { pool }
31 }
32}
33
34fn text(sql: &str, params: &[Param]) -> Result<AssertSqlSafe<String>, DbError> {
35 Ok(AssertSqlSafe(fill_placeholders(sql, params, false, postgres_literal)?))
36}
37
38impl Db for PostgresDb {
39 fn dialect(&self) -> Dialect {
40 Dialect::Postgres
41 }
42
43 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
44 Box::pin(async move {
45 let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&self.pool).await.map_err(db_error)?;
46 rows.iter().map(pg_row).collect()
47 })
48 }
49
50 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
51 Box::pin(async move {
52 Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&self.pool).await.map_err(db_error)?.rows_affected())
53 })
54 }
55
56 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
57 Box::pin(async move {
58 let mut slot = Slot { conn: Some(self.pool.acquire().await.map_err(db_error)?), nested: false };
61 let conn = slot.conn.as_mut().ok_or_else(ended)?;
62 match kind {
63 Hold::Transaction => {
64 sqlx::raw_sql(AssertSqlSafe("BEGIN".to_string())).execute(&mut **conn).await.map_err(db_error)?;
65 }
66 Hold::Exclusive => {
67 let wait = lock_wait();
69 let until = Instant::now() + wait;
70 loop {
71 let rows = sqlx::raw_sql(AssertSqlSafe(format!(
75 "SELECT pg_try_advisory_lock({MIGRATION_LOCK}) AS ok"
76 )))
77 .fetch_all(&mut **conn)
78 .await
79 .map_err(db_error)?;
80 let ok = rows.first().map(pg_row).transpose()?.is_some_and(|r| r.num("ok") == 1.0);
81 if ok {
82 break;
83 }
84 if Instant::now() >= until {
85 return Err(TablesBusyError { waited: wait }.into());
86 }
87 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
88 }
89 }
90 }
91 Ok(Arc::new(Held { conn: Arc::new(Mutex::new(slot)), kind, role: Role::Owner }) as Arc<dyn Db>)
92 })
93 }
94}
95
96struct Slot {
99 conn: Option<PoolConnection<Postgres>>,
100 nested: bool,
102}
103
104impl Slot {
105 fn close(&mut self) {
106 if let Some(conn) = self.conn.take() {
107 drop(conn.detach());
108 }
109 }
110}
111
112impl Drop for Slot {
113 fn drop(&mut self) {
114 self.close();
115 }
116}
117
118struct Held {
120 conn: Arc<Mutex<Slot>>,
121 kind: Hold,
122 role: Role,
123}
124
125impl Drop for Held {
126 fn drop(&mut self) {
127 if self.role == Role::Owner
129 && let Ok(mut slot) = self.conn.try_lock()
130 {
131 slot.close();
132 }
133 }
134}
135
136#[derive(Clone, Copy, PartialEq, Eq)]
138enum Role {
139 Owner,
141 Nested,
143 Joined,
145}
146
147fn ended() -> DbError {
148 DbError("Runlight: the held connection has been let go".into())
149}
150
151impl Db for Held {
152 fn dialect(&self) -> Dialect {
153 Dialect::Postgres
154 }
155
156 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
157 Box::pin(async move {
158 let mut slot = self.conn.lock().await;
159 let conn = slot.conn.as_mut().ok_or_else(ended)?;
160 let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&mut **conn).await.map_err(db_error)?;
161 rows.iter().map(pg_row).collect()
162 })
163 }
164
165 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
166 Box::pin(async move {
167 let mut slot = self.conn.lock().await;
168 let conn = slot.conn.as_mut().ok_or_else(ended)?;
169 Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&mut **conn).await.map_err(db_error)?.rows_affected())
170 })
171 }
172
173 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
174 Box::pin(async move {
175 if kind == Hold::Transaction && self.kind == Hold::Exclusive {
177 let mut slot = self.conn.lock().await;
178 let slot = &mut *slot;
179 let conn = slot.conn.as_mut().ok_or_else(ended)?;
180 slot.nested = true;
182 sqlx::raw_sql(AssertSqlSafe("BEGIN".to_string())).execute(&mut **conn).await.map_err(db_error)?;
183 return Ok(Arc::new(Held { conn: self.conn.clone(), kind, role: Role::Nested }) as Arc<dyn Db>);
184 }
185 Ok(Arc::new(Held { conn: self.conn.clone(), kind: self.kind, role: Role::Joined }) as Arc<dyn Db>)
186 })
187 }
188
189 fn finish(&self, ok: bool) -> BoxFuture<'_, Result<(), DbError>> {
190 Box::pin(async move {
191 let mut slot = self.conn.lock().await;
192 if self.role == Role::Joined {
193 return Ok(());
194 }
195 if self.role == Role::Nested {
196 let slot = &mut *slot;
197 let Some(conn) = slot.conn.as_mut() else { return Ok(()) };
198 let end = if ok { "COMMIT" } else { "ROLLBACK" };
199 sqlx::raw_sql(AssertSqlSafe(end.to_string())).execute(&mut **conn).await.map_err(db_error)?;
200 slot.nested = false;
201 return Ok(());
202 }
203 if slot.nested {
206 slot.close();
207 return Ok(());
208 }
209 let Some(mut conn) = slot.conn.take() else { return Ok(()) };
210 let result = match self.kind {
211 Hold::Transaction => {
212 let end = if ok { "COMMIT" } else { "ROLLBACK" };
213 sqlx::raw_sql(AssertSqlSafe(end.to_string()))
214 .execute(&mut *conn)
215 .await
216 .map(|_| ())
217 .map_err(db_error)
218 }
219 Hold::Exclusive => sqlx::raw_sql(AssertSqlSafe(format!("SELECT pg_advisory_unlock({MIGRATION_LOCK})")))
222 .execute(&mut *conn)
223 .await
224 .map(|_| ())
225 .map_err(db_error),
226 };
227 if result.is_err() {
228 drop(conn.detach());
230 }
231 if self.kind == Hold::Exclusive { Ok(()) } else { result }
232 })
233 }
234}