mybatis/
mybatis.rs

1use mybatis_core::db::DBConnectOption;
2use once_cell::sync::OnceCell;
3use serde::de::DeserializeOwned;
4use serde::ser::Serialize;
5use std::borrow::BorrowMut;
6use std::cell::Cell;
7use std::collections::HashMap;
8use std::sync::Arc;
9use std::time::Duration;
10use uuid::Uuid;
11
12use crate::executor::{MyBatisConnExecutor, MyBatisExecutor, MyBatisTxExecutor};
13use crate::intercept::SqlIntercept;
14use crate::log::{LogPlugin, MyBatisLogPlugin};
15use crate::logic_delete::{LogicDelete, MyBatisLogicDeletePlugin};
16use crate::page::{IPage, IPageRequest, MyBatisPagePlugin, Page, PagePlugin};
17use crate::plus::MybatisPlus;
18use crate::snowflake::new_snowflake_id;
19use crate::wrapper::Wrapper;
20use mybatis_core::db::{
21    DBExecResult, DBPool, DBPoolConn, DBPoolOptions, DBQuery, DBTx, DriverType,
22};
23use mybatis_core::Error;
24use mybatis_sql::PageLimit;
25use mybatis_util::error_util::ToResult;
26use std::fmt::{Debug, Formatter};
27
28/// mybatis engine
29// #[derive(Debug)]
30pub struct Mybatis {
31    // the connection pool,use OnceCell init this
32    pub pool: OnceCell<DBPool>,
33    // page plugin
34    pub page_plugin: Box<dyn PagePlugin>,
35    // sql intercept vec chain
36    pub sql_intercepts: Vec<Box<dyn SqlIntercept>>,
37    // log plugin
38    pub log_plugin: Arc<Box<dyn LogPlugin>>,
39    // logic delete plugin
40    pub logic_plugin: Option<Box<dyn LogicDelete>>,
41    // sql param binder
42    pub encoder: fn(q: &mut DBQuery, arg: rbson::Bson) -> crate::Result<()>,
43}
44
45impl Debug for Mybatis {
46    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47        f.debug_struct("Mybatis")
48            .field("pool", &self.pool)
49            .field("page_plugin", &self.page_plugin)
50            .field("sql_intercepts", &self.sql_intercepts)
51            .field("logic_plugin", &self.logic_plugin)
52            .finish()
53    }
54}
55
56impl Default for Mybatis {
57    fn default() -> Mybatis {
58        Mybatis::new()
59    }
60}
61
62///Mybatis Options
63#[derive(Debug)]
64pub struct MybatisOption {
65    /// page plugin
66    pub page_plugin: Box<dyn PagePlugin>,
67    /// sql intercept vec chain
68    pub sql_intercepts: Vec<Box<dyn SqlIntercept>>,
69    /// log plugin
70    pub log_plugin: Arc<Box<dyn LogPlugin>>,
71    /// logic delete plugin
72    pub logic_plugin: Option<Box<dyn LogicDelete>>,
73}
74
75impl Default for MybatisOption {
76    fn default() -> Self {
77        Self {
78            page_plugin: Box::new(MyBatisPagePlugin::new()),
79            sql_intercepts: vec![],
80            logic_plugin: None,
81            log_plugin: Arc::new(Box::new(MyBatisLogPlugin::default()) as Box<dyn LogPlugin>),
82        }
83    }
84}
85
86impl Mybatis {
87    ///create an Mybatis
88    pub fn new() -> Self {
89        return Self::new_with_opt(MybatisOption::default());
90    }
91
92    ///new MyBatis from Option
93    pub fn new_with_opt(option: MybatisOption) -> Self {
94        return Self {
95            pool: OnceCell::new(),
96            page_plugin: option.page_plugin,
97            sql_intercepts: option.sql_intercepts,
98            logic_plugin: option.logic_plugin,
99            log_plugin: option.log_plugin,
100            encoder: |q, arg| {
101                q.bind_value(arg)?;
102                Ok(())
103            },
104        };
105    }
106
107    /// try return an new wrapper,if not call the link() method,it will be panic!
108    pub fn new_wrapper(&self) -> Wrapper {
109        let driver = self.driver_type();
110        if driver.as_ref().unwrap().eq(&DriverType::None) {
111            panic!("[mybatis] .new_wrapper() method must be call .link(url) to init first!");
112        }
113        Wrapper::new(&driver.unwrap_or_else(|_| {
114            panic!("[mybatis] .new_wrapper() method must be call .link(url) to init first!");
115        }))
116    }
117
118    /// try return an new wrapper and set table formats,if not call the link() method,it will be panic!
119    pub fn new_wrapper_table<T>(&self) -> Wrapper
120    where
121        T: MybatisPlus,
122    {
123        let mut w = self.new_wrapper();
124        let formats = T::formats(self.driver_type().unwrap());
125        w = w.set_formats(formats);
126        return w;
127    }
128
129    /// link pool
130    pub async fn link(&self, driver_url: &str) -> Result<(), Error> {
131        return Ok(self.link_opt(driver_url, DBPoolOptions::default()).await?);
132    }
133
134    /// link pool by DBPoolOptions
135    /// for example:
136    ///          let mut opt = PoolOptions::new();
137    ///          opt.max_size = 20;
138    ///          rb.link_opt("mysql://root:123456@localhost:3306/test", opt).await.unwrap();
139    pub async fn link_opt(
140        &self,
141        driver_url: &str,
142        pool_options: DBPoolOptions,
143    ) -> Result<(), Error> {
144        if driver_url.is_empty() {
145            return Err(Error::from("[mybatis] link url is empty!"));
146        }
147        let pool = DBPool::new_opt_str(driver_url, pool_options).await?;
148        self.pool.set(pool);
149        return Ok(());
150    }
151
152    /// link pool by DBConnectOption and DBPoolOptions
153    /// for example:
154    ///         let db_cfg=DBConnectOption::from("mysql://root:123456@localhost:3306/test")?;
155    ///         rb.link_cfg(&db_cfg,PoolOptions::new());
156    pub async fn link_cfg(
157        &self,
158        connect_option: &DBConnectOption,
159        pool_options: DBPoolOptions,
160    ) -> Result<(), Error> {
161        let pool = DBPool::new_opt(connect_option, pool_options).await?;
162        self.pool.set(pool);
163        return Ok(());
164    }
165
166    pub fn set_log_plugin(&mut self, arg: impl LogPlugin + 'static) {
167        self.log_plugin = Arc::new(Box::new(arg));
168    }
169
170    pub fn set_logic_plugin(&mut self, arg: impl LogicDelete + 'static) {
171        self.logic_plugin = Some(Box::new(arg));
172    }
173
174    pub fn set_page_plugin(&mut self, arg: impl PagePlugin + 'static) {
175        self.page_plugin = Box::new(arg);
176    }
177
178    pub fn add_sql_intercept(&mut self, arg: impl SqlIntercept + 'static) {
179        self.sql_intercepts.push(Box::new(arg));
180    }
181
182    pub fn set_sql_intercepts(&mut self, arg: Vec<Box<dyn SqlIntercept>>) {
183        self.sql_intercepts = arg;
184    }
185
186    /// get conn pool
187    pub fn get_pool(&self) -> Result<&DBPool, Error> {
188        let p = self.pool.get();
189        if p.is_none() {
190            return Err(Error::from("[mybatis] mybatis pool not inited!"));
191        }
192        return Ok(p.unwrap());
193    }
194
195    /// get driver type
196    pub fn driver_type(&self) -> Result<DriverType, Error> {
197        let pool = self.get_pool()?;
198        Ok(pool.driver_type())
199    }
200
201    /// get an DataBase Connection used for the next step
202    pub async fn acquire(&self) -> Result<MyBatisConnExecutor<'_>, Error> {
203        let pool = self.get_pool()?;
204        let conn = pool.acquire().await?;
205        return Ok(MyBatisConnExecutor {
206            conn: conn,
207            rb: &self,
208        });
209    }
210
211    /// get an DataBase Connection,and call begin method,used for the next step
212    pub async fn acquire_begin(&self) -> Result<MyBatisTxExecutor<'_>, Error> {
213        let pool = self.get_pool()?;
214        let conn = pool.begin().await?;
215        return Ok(MyBatisTxExecutor {
216            tx_id: new_snowflake_id(),
217            conn: conn,
218            rb: &self,
219        });
220    }
221
222    /// is debug mode
223    pub fn is_debug_mode(&self) -> bool {
224        if cfg!(feature = "debug_mode") {
225            return true;
226        }
227        return false;
228    }
229
230    /// change ref to executor
231    pub fn as_executor(&self) -> MyBatisExecutor {
232        self.into()
233    }
234}
235
236pub trait AsSqlTag {
237    fn sql_tag(&self) -> char;
238    fn do_replace_tag(&self, sql: &mut String);
239}
240
241impl AsSqlTag for DriverType {
242    #[inline]
243    fn sql_tag(&self) -> char {
244        match self {
245            DriverType::None => '?',
246            DriverType::Mysql => '?',
247            DriverType::Sqlite => '?',
248            DriverType::Postgres => '$',
249            //mssql is '@p',so use '$' to '@p'
250            DriverType::Mssql => '$',
251        }
252    }
253    #[inline]
254    fn do_replace_tag(&self, sql: &mut String) {
255        if self.eq(&DriverType::Mssql) {
256            *sql = sql.replace("$", "@p");
257        }
258    }
259}