1use crate::connection_init::ConnectionInit;
68use crate::shared::{ConnSlot, TxSlot, affected_result, map_error, with_conn, with_tx};
69use crate::sql::{MySql, compile, compile_write};
70use async_trait::async_trait;
71use siderite_orm::{
72 Backend, BackendCapabilities, BackendError, ExecResult, Executor, IsolationLevel, OrmError,
73 QueryPlan, QueryResult, Transaction, Value, WritePlan,
74};
75use sqlx::Executor as _;
76use sqlx::mysql::{MySqlPool, MySqlPoolOptions, MySqlQueryResult};
77use std::sync::Arc;
78
79mod io;
80mod metadata;
81#[cfg(feature = "mysql-native")]
82pub mod native;
83mod returning;
84mod sqlx_io;
85
86use metadata::TableCache;
87use returning::{prepare, synthesize, synthesized_result, table_of, write_returning};
88use sqlx_io::{execute_rows, fetch_rows, run};
89
90const SESSION_SETUP: [&str; 3] = [
92 "SET time_zone = '+00:00'",
93 "SET SESSION sql_mode = REPLACE(@@sql_mode, 'NO_BACKSLASH_ESCAPES', '')",
94 "SET SESSION group_concat_max_len = 4294967295",
95];
96
97#[derive(Debug, Clone)]
99pub struct MySqlBackend {
100 pool: MySqlPool,
101 tables: Arc<TableCache>,
102}
103
104impl MySqlBackend {
105 pub async fn connect(url: &str) -> Result<Self, BackendError> {
119 let options = MySqlPoolOptions::new()
120 .max_connections(10)
121 .test_before_acquire(false);
122 Self::connect_with(url, options).await
123 }
124
125 pub async fn connect_with(url: &str, options: MySqlPoolOptions) -> Result<Self, BackendError> {
141 let init: ConnectionInit<sqlx::MySql> =
142 std::sync::Arc::new(|_, _| Box::pin(async { Ok(()) }));
143 Self::connect_with_init(url, options, init).await
144 }
145
146 pub async fn connect_with_init(
159 url: &str,
160 options: MySqlPoolOptions,
161 init: ConnectionInit<sqlx::MySql>,
162 ) -> Result<Self, BackendError> {
163 let pool = options
164 .after_connect(move |conn, meta| {
165 let init = init.clone();
166 Box::pin(async move {
167 init(conn, meta).await?;
168 for statement in SESSION_SETUP {
169 conn.execute(statement).await?;
170 }
171 Ok(())
172 })
173 })
174 .connect(url)
175 .await
176 .map_err(|e| BackendError::Connection(e.to_string()))?;
177 Ok(Self::from_pool(pool))
178 }
179
180 pub fn from_pool(pool: MySqlPool) -> Self {
183 Self {
184 pool,
185 tables: Arc::default(),
186 }
187 }
188}
189
190#[async_trait]
191impl Executor for MySqlBackend {
192 fn capabilities(&self) -> BackendCapabilities {
193 BackendCapabilities::mysql()
194 }
195
196 async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
197 let compiled = compile(plan, &MySql)?;
198 fetch_rows(&self.pool, &compiled.sql, compiled.params).await
199 }
200
201 async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
202 if plan.returning().is_empty() {
203 let compiled = compile_write(plan, &MySql)?;
204 let affected = execute_rows(&self.pool, &compiled.sql, compiled.params).await?;
205 return Ok(affected_result(affected));
206 }
207 let prepared = prepare(plan)?;
208 let info = self.tables.table(&mut &self.pool, table_of(plan)).await?;
209 if let WritePlan::Insert(p) = plan
210 && let Some(row) = synthesize(&info, p)
211 {
212 let write = prepared.write;
214 let done = run(&self.pool, &write.sql, write.params).await?;
215 return synthesized_result(&info, row, &done);
216 }
217 let mut tx = self.pool.begin().await.map_err(db_error)?;
222 let result = write_returning(&mut *tx, &info, plan, prepared).await?;
223 tx.commit().await.map_err(db_error)?;
224 Ok(result)
225 }
226
227 async fn fetch_raw(&self, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError> {
228 fetch_rows(&self.pool, sql, params).await
229 }
230
231 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, OrmError> {
232 execute_rows(&self.pool, sql, params).await
233 }
234
235 async fn execute_script(&self, sql: &str) -> Result<(), OrmError> {
236 let done = sqlx::Executor::execute(&self.pool, sql).await;
237 self.tables.clear();
239 script_result(done)
240 }
241}
242
243#[async_trait]
244impl Backend for MySqlBackend {
245 fn read_parameter_count(&self, plan: &QueryPlan) -> Result<Option<usize>, OrmError> {
246 Ok(Some(compile(plan, &MySql)?.params.len()))
247 }
248
249 async fn begin(
250 &self,
251 isolation: Option<IsolationLevel>,
252 ) -> Result<Box<dyn Transaction>, OrmError> {
253 let tx = match isolation {
254 None => self.pool.begin().await,
255 Some(level) => {
256 self.capabilities()
257 .require(siderite_orm::Feature::Isolation(level))?;
258 let statement = format!(
261 "SET TRANSACTION ISOLATION LEVEL {}; START TRANSACTION",
262 isolation_sql(level)
263 );
264 self.pool.begin_with(statement).await
265 }
266 }
267 .map_err(db_error)?;
268 Ok(Box::new(MySqlTransaction {
269 slot: TxSlot::new(tx),
270 tables: Arc::clone(&self.tables),
271 }))
272 }
273
274 async fn begin_schema(&self, transactional: bool) -> Result<Box<dyn Transaction>, OrmError> {
275 if transactional {
276 return self.begin(None).await;
277 }
278 let conn = self.pool.acquire().await.map_err(db_error)?;
279 Ok(Box::new(MySqlHeld {
280 slot: ConnSlot::new(conn),
281 tables: Arc::clone(&self.tables),
282 }))
283 }
284}
285
286fn isolation_sql(level: IsolationLevel) -> &'static str {
287 match level {
288 IsolationLevel::ReadCommitted => "READ COMMITTED",
289 IsolationLevel::RepeatableRead => "REPEATABLE READ",
290 IsolationLevel::Serializable => "SERIALIZABLE",
291 }
292}
293
294struct MySqlTransaction {
296 slot: TxSlot<sqlx::MySql>,
297 tables: Arc<TableCache>,
298}
299
300#[async_trait]
301impl Executor for MySqlTransaction {
302 fn capabilities(&self) -> BackendCapabilities {
303 BackendCapabilities::mysql()
304 }
305
306 async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
307 let compiled = compile(plan, &MySql)?;
308 with_tx!(self.slot, conn => fetch_rows(conn, &compiled.sql, compiled.params).await)
309 }
310
311 async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
312 if plan.returning().is_empty() {
313 let compiled = compile_write(plan, &MySql)?;
314 let affected = with_tx!(self.slot, conn => execute_rows(conn, &compiled.sql, compiled.params).await)?;
315 return Ok(affected_result(affected));
316 }
317 let prepared = prepare(plan)?;
318 with_tx!(self.slot, conn => {
319 let info = self.tables.table(&mut *conn, table_of(plan)).await?;
320 write_returning(conn, &info, plan, prepared).await
321 })
322 }
323
324 async fn fetch_raw(&self, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError> {
325 with_tx!(self.slot, conn => fetch_rows(conn, sql, params).await)
326 }
327
328 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, OrmError> {
329 with_tx!(self.slot, conn => execute_rows(conn, sql, params).await)
330 }
331
332 async fn execute_script(&self, sql: &str) -> Result<(), OrmError> {
333 let result =
334 with_tx!(self.slot, conn => script_result(sqlx::Executor::execute(conn, sql).await));
335 self.tables.clear();
336 result
337 }
338}
339
340#[async_trait]
341impl Transaction for MySqlTransaction {
342 async fn commit(&self) -> Result<(), OrmError> {
343 self.slot.commit().await
344 }
345
346 async fn rollback(&self) -> Result<(), OrmError> {
347 self.slot.rollback().await
348 }
349}
350
351struct MySqlHeld {
357 slot: ConnSlot<sqlx::MySql>,
358 tables: Arc<TableCache>,
359}
360
361#[async_trait]
362impl Executor for MySqlHeld {
363 fn capabilities(&self) -> BackendCapabilities {
364 BackendCapabilities::mysql()
365 }
366
367 async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
368 let compiled = compile(plan, &MySql)?;
369 with_conn!(self.slot, conn => fetch_rows(conn, &compiled.sql, compiled.params).await)
370 }
371
372 async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
373 if plan.returning().is_empty() {
374 let compiled = compile_write(plan, &MySql)?;
375 let affected = with_conn!(self.slot, conn => execute_rows(conn, &compiled.sql, compiled.params).await)?;
376 return Ok(affected_result(affected));
377 }
378 let prepared = prepare(plan)?;
379 with_conn!(self.slot, conn => {
380 let info = self.tables.table(&mut *conn, table_of(plan)).await?;
381 write_returning(conn, &info, plan, prepared).await
382 })
383 }
384
385 async fn fetch_raw(&self, sql: &str, params: Vec<Value>) -> Result<QueryResult, OrmError> {
386 with_conn!(self.slot, conn => fetch_rows(conn, sql, params).await)
387 }
388
389 async fn execute_raw(&self, sql: &str, params: Vec<Value>) -> Result<u64, OrmError> {
390 with_conn!(self.slot, conn => execute_rows(conn, sql, params).await)
391 }
392
393 async fn execute_script(&self, sql: &str) -> Result<(), OrmError> {
394 let result =
395 with_conn!(self.slot, conn => script_result(sqlx::Executor::execute(conn, sql).await));
396 self.tables.clear();
397 result
398 }
399}
400
401#[async_trait]
402impl Transaction for MySqlHeld {
403 async fn commit(&self) -> Result<(), OrmError> {
404 let mut conn = self.slot.take().await?;
405 conn.close_on_drop();
406 drop(conn);
407 Ok(())
408 }
409
410 async fn rollback(&self) -> Result<(), OrmError> {
411 let mut conn = self.slot.take().await?;
412 conn.close_on_drop();
413 drop(conn);
414 Ok(())
415 }
416}
417
418fn db_error(e: sqlx::Error) -> OrmError {
419 map_error(e).into()
420}
421
422fn script_result(done: Result<MySqlQueryResult, sqlx::Error>) -> Result<(), OrmError> {
423 done.map(|_| ()).map_err(db_error)
424}
425
426