1use std::sync::Arc;
5
6use runlight::BoxFuture;
7use runlight::store::{Db, DbError, Dialect, Hold, Param, Row, fill_placeholders, mysql_literal};
8use sqlx::pool::PoolConnection;
9use sqlx::{AssertSqlSafe, MySql, MySqlPool};
10use tokio::sync::Mutex;
11
12use crate::rows::{db_error, mysql_row};
13
14const LOCK: &str = "CONCAT('runlight:', LEFT(SHA2(COALESCE(DATABASE(), ''), 256), 48))";
17
18#[derive(Clone)]
22pub struct MysqlDb {
23 pool: MySqlPool,
24}
25
26impl MysqlDb {
27 pub fn new(pool: MySqlPool) -> MysqlDb {
29 MysqlDb { pool }
30 }
31}
32
33const NO_BACKSLASH_ESCAPES_OFF: &str = "SET SESSION sql_mode = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', @@SESSION.sql_mode, ','), ',NO_BACKSLASH_ESCAPES,', ','))";
35
36fn text(sql: &str, params: &[Param]) -> Result<AssertSqlSafe<String>, DbError> {
40 Ok(AssertSqlSafe(format!("{NO_BACKSLASH_ESCAPES_OFF}; {}", fill_placeholders(sql, params, true, mysql_literal)?)))
41}
42
43async fn exec(conn: &mut PoolConnection<MySql>, sql: &str) -> Result<Vec<Row>, DbError> {
44 let rows = sqlx::raw_sql(AssertSqlSafe(sql.to_string())).fetch_all(&mut **conn).await.map_err(db_error)?;
45 rows.iter().map(mysql_row).collect()
46}
47
48impl Db for MysqlDb {
49 fn dialect(&self) -> Dialect {
50 Dialect::Mysql
51 }
52
53 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
54 Box::pin(async move {
55 let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&self.pool).await.map_err(db_error)?;
56 rows.iter().map(mysql_row).collect()
57 })
58 }
59
60 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
61 Box::pin(async move {
62 Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&self.pool).await.map_err(db_error)?.rows_affected())
63 })
64 }
65
66 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
67 Box::pin(async move {
68 let mut conn = self.pool.acquire().await.map_err(db_error)?;
69 match kind {
70 Hold::Transaction => begin(&mut conn).await?,
71 Hold::Exclusive => loop {
72 let rows = exec(&mut conn, &format!("SELECT GET_LOCK({LOCK}, 5) AS ok")).await?;
73 let row = rows.into_iter().next().unwrap_or_default();
74 if row.is_null("ok") {
75 return Err(DbError("Runlight: MySQL refused the lock for creating tables".into()));
76 }
77 if row.num("ok") == 1.0 {
78 break;
79 }
80 },
82 }
83 Ok(Arc::new(Held { conn: Arc::new(Mutex::new(Some(conn))), kind, role: Role::Owner }) as Arc<dyn Db>)
84 })
85 }
86}
87
88async fn begin(conn: &mut PoolConnection<MySql>) -> Result<(), DbError> {
91 exec(conn, "SET TRANSACTION ISOLATION LEVEL READ COMMITTED").await?;
92 exec(conn, "START TRANSACTION").await?;
93 Ok(())
94}
95
96struct Held {
98 conn: Arc<Mutex<Option<PoolConnection<MySql>>>>,
99 kind: Hold,
100 role: Role,
101}
102
103#[derive(Clone, Copy, PartialEq, Eq)]
105enum Role {
106 Owner,
108 Nested,
110 Joined,
112}
113
114fn ended() -> DbError {
115 DbError("Runlight: the held connection has been let go".into())
116}
117
118impl Db for Held {
119 fn dialect(&self) -> Dialect {
120 Dialect::Mysql
121 }
122
123 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>> {
124 Box::pin(async move {
125 let mut guard = self.conn.lock().await;
126 let conn = guard.as_mut().ok_or_else(ended)?;
127 let rows = sqlx::raw_sql(text(sql, ¶ms)?).fetch_all(&mut **conn).await.map_err(db_error)?;
128 rows.iter().map(mysql_row).collect()
129 })
130 }
131
132 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>> {
133 Box::pin(async move {
134 let mut guard = self.conn.lock().await;
135 let conn = guard.as_mut().ok_or_else(ended)?;
136 Ok(sqlx::raw_sql(text(sql, ¶ms)?).execute(&mut **conn).await.map_err(db_error)?.rows_affected())
137 })
138 }
139
140 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>> {
141 Box::pin(async move {
142 if kind == Hold::Transaction && self.kind == Hold::Exclusive {
143 let mut guard = self.conn.lock().await;
144 begin(guard.as_mut().ok_or_else(ended)?).await?;
145 return Ok(Arc::new(Held { conn: self.conn.clone(), kind, role: Role::Nested }) as Arc<dyn Db>);
146 }
147 Ok(Arc::new(Held { conn: self.conn.clone(), kind: self.kind, role: Role::Joined }) as Arc<dyn Db>)
148 })
149 }
150
151 fn finish(&self, ok: bool) -> BoxFuture<'_, Result<(), DbError>> {
152 Box::pin(async move {
153 let mut guard = self.conn.lock().await;
154 match self.role {
155 Role::Joined => Ok(()),
156 Role::Nested => {
157 let Some(conn) = guard.as_mut() else { return Ok(()) };
158 exec(conn, if ok { "COMMIT" } else { "ROLLBACK" }).await.map(|_| ())
159 }
160 Role::Owner => {
161 let Some(mut conn) = guard.take() else { return Ok(()) };
162 let result = match self.kind {
163 Hold::Transaction => exec(&mut conn, if ok { "COMMIT" } else { "ROLLBACK" }).await.map(|_| ()),
164 Hold::Exclusive => {
166 let _ = exec(&mut conn, &format!("DO RELEASE_LOCK({LOCK})")).await;
167 Ok(())
168 }
169 };
170 if result.is_err() {
171 conn.detach();
173 }
174 result
175 }
176 }
177 })
178 }
179}