runlight_sqlx/
postgres.rs1use std::sync::Arc;
5
6use runlight::BoxFuture;
7use runlight::store::{Db, DbError, Dialect, Hold, Param, Row, fill_placeholders, postgres_literal};
8use sqlx::pool::PoolConnection;
9use sqlx::{AssertSqlSafe, PgPool, Postgres};
10use tokio::sync::Mutex;
11
12use crate::rows::{db_error, pg_row};
13
14const MIGRATION_LOCK: i64 = 7_331_906;
16
17#[derive(Clone)]
20pub struct PostgresDb {
21 pool: PgPool,
22}
23
24impl PostgresDb {
25 pub fn new(pool: PgPool) -> PostgresDb {
27 PostgresDb { pool }
28 }
29}
30
31fn text(sql: &str, params: &[Param]) -> Result<AssertSqlSafe<String>, DbError> {
32 Ok(AssertSqlSafe(fill_placeholders(sql, params, false, postgres_literal)?))
33}
34
35impl Db for PostgresDb {
36 fn dialect(&self) -> Dialect {
37 Dialect::Postgres
38 }
39
40 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
41 Box::pin(async move {
42 let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&self.pool).await.map_err(db_error)?;
43 rows.iter().map(pg_row).collect()
44 })
45 }
46
47 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
48 Box::pin(async move {
49 Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&self.pool).await.map_err(db_error)?.rows_affected())
50 })
51 }
52
53 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
54 Box::pin(async move {
55 let mut conn = self.pool.acquire().await.map_err(db_error)?;
56 match kind {
57 Hold::Transaction => {
58 sqlx::raw_sql(AssertSqlSafe("BEGIN".to_string())).execute(&mut *conn).await.map_err(db_error)?;
59 }
60 Hold::Exclusive => loop {
61 let rows =
65 sqlx::raw_sql(AssertSqlSafe(format!("SELECT pg_try_advisory_lock({MIGRATION_LOCK}) AS ok")))
66 .fetch_all(&mut *conn)
67 .await
68 .map_err(db_error)?;
69 let ok = rows.first().map(pg_row).transpose()?.is_some_and(|r| r.num("ok") == 1.0);
70 if ok {
71 break;
72 }
73 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
74 },
75 }
76 Ok(Arc::new(Held { conn: Arc::new(Mutex::new(Some(conn))), kind, role: Role::Owner }) as Arc<dyn Db>)
77 })
78 }
79}
80
81struct Held {
83 conn: Arc<Mutex<Option<PoolConnection<Postgres>>>>,
84 kind: Hold,
85 role: Role,
86}
87
88#[derive(Clone, Copy, PartialEq, Eq)]
90enum Role {
91 Owner,
93 Nested,
95 Joined,
97}
98
99fn ended() -> DbError {
100 DbError("Runlight: the held connection has been let go".into())
101}
102
103impl Db for Held {
104 fn dialect(&self) -> Dialect {
105 Dialect::Postgres
106 }
107
108 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
109 Box::pin(async move {
110 let mut guard = self.conn.lock().await;
111 let conn = guard.as_mut().ok_or_else(ended)?;
112 let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&mut **conn).await.map_err(db_error)?;
113 rows.iter().map(pg_row).collect()
114 })
115 }
116
117 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
118 Box::pin(async move {
119 let mut guard = self.conn.lock().await;
120 let conn = guard.as_mut().ok_or_else(ended)?;
121 Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&mut **conn).await.map_err(db_error)?.rows_affected())
122 })
123 }
124
125 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
126 Box::pin(async move {
127 if kind == Hold::Transaction && self.kind == Hold::Exclusive {
129 let mut guard = self.conn.lock().await;
130 let conn = guard.as_mut().ok_or_else(ended)?;
131 sqlx::raw_sql(AssertSqlSafe("BEGIN".to_string())).execute(&mut **conn).await.map_err(db_error)?;
132 return Ok(Arc::new(Held { conn: self.conn.clone(), kind, role: Role::Nested }) as Arc<dyn Db>);
133 }
134 Ok(Arc::new(Held { conn: self.conn.clone(), kind: self.kind, role: Role::Joined }) as Arc<dyn Db>)
135 })
136 }
137
138 fn finish(&self, ok: bool) -> BoxFuture<'_, Result<(), DbError>> {
139 Box::pin(async move {
140 let mut guard = self.conn.lock().await;
141 if self.role == Role::Joined {
142 return Ok(());
143 }
144 if self.role == Role::Nested {
145 let Some(conn) = guard.as_mut() else { return Ok(()) };
146 let end = if ok { "COMMIT" } else { "ROLLBACK" };
147 sqlx::raw_sql(AssertSqlSafe(end.to_string())).execute(&mut **conn).await.map_err(db_error)?;
148 return Ok(());
149 }
150 let Some(mut conn) = guard.take() else { return Ok(()) };
151 let result = match self.kind {
152 Hold::Transaction => {
153 let end = if ok { "COMMIT" } else { "ROLLBACK" };
154 sqlx::raw_sql(AssertSqlSafe(end.to_string()))
155 .execute(&mut *conn)
156 .await
157 .map(|_| ())
158 .map_err(db_error)
159 }
160 Hold::Exclusive => {
162 let _ = sqlx::raw_sql(AssertSqlSafe(format!("SELECT pg_advisory_unlock({MIGRATION_LOCK})")))
163 .execute(&mut *conn)
164 .await;
165 Ok(())
166 }
167 };
168 if result.is_err() {
169 conn.detach();
171 }
172 result
173 })
174 }
175}