Skip to main content

rbatis/
crud.rs

1///PySql: gen insert/insert_batch, select_by_map, update_by_map, delete_by_map methods
2///```rust
3/// use rbs::value;
4/// use rbatis::{Error, RBatis, rbdc::db::ExecResult};
5///
6/// #[derive(serde::Serialize, serde::Deserialize)]
7/// pub struct MockTable{
8///    pub id: Option<String>
9/// }
10/// rbatis::crud!(MockTable{}); //or crud!(MockTable{},"mock_table");
11///
12/// //use
13/// async fn test_use(rb:&RBatis) -> Result<(),Error>{
14///  let table = MockTable{id: Some("1".to_string())};
15///  let result:ExecResult = MockTable::insert(rb, &table).await?;
16///  let result:ExecResult = MockTable::insert_batch(rb, std::slice::from_ref(&table),10).await?;
17///
18///  let tables:Vec<MockTable> = MockTable::select_by_map(rb,value!{"id":"1"}).await?;
19///  let tables:Vec<MockTable> = MockTable::select_by_map(rb,value!{"id":["1","2","3"]}).await?;
20///  let tables:Vec<MockTable> = MockTable::select_by_map(rb,value!{"id":"1", "column": ["id", "name"]}).await?; 
21///
22///  let result:ExecResult = MockTable::update_by_map(rb, &table, value!{"id":"1"}).await?;
23///  let result:ExecResult = MockTable::delete_by_map(rb, value!{"id":"1"}).await?;
24///  Ok(())
25/// }
26///
27///
28/// ```
29#[macro_export]
30macro_rules! crud {
31    ($table:ty{}) => {
32        $crate::crud!($table {}, "");
33    };
34    ($table:ty{},$table_name:expr) => {
35        // insert
36        impl $table {
37            /// batch insert records
38            ///
39            /// sql: `INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...), (value1, value2, ...), ...`
40            pub async fn insert_batch(
41                executor: &dyn $crate::executor::Executor,
42                tables: &[$table],
43                batch_size: u64,
44            ) -> std::result::Result<$crate::rbdc::db::ExecResult, $crate::rbdc::Error> {
45                use $crate::crud_traits::ColumnSet;
46                #[$crate::py_sql(
47                    "`insert into ${table_name} `
48                    trim ',':
49                     bind columns = tables.column_sets():
50                     for idx,table in tables:
51                      if idx == 0:
52                         `(`
53                         trim ',':
54                           for _,v in columns:
55                              ${v},
56                         `) VALUES `
57                      (
58                      trim ',':
59                       for _,v in columns:
60                         #{table[v]},
61                      ),
62                    "
63                )]
64                async fn insert_batch(
65                    executor: &dyn $crate::executor::Executor,
66                    tables: &[$table],
67                    table_name: &str,
68                ) -> std::result::Result<$crate::rbdc::db::ExecResult, $crate::rbdc::Error>
69                {
70                    impled!()
71                }
72                if tables.is_empty() {
73                    return Err($crate::rbdc::Error::from(
74                        "insert can not insert empty array tables!",
75                    ));
76                }
77                let mut table_name = $table_name.to_string();
78                if table_name.is_empty(){
79                         #[$crate::snake_name($table)]
80                         fn snake_name(){}
81                         table_name = snake_name();
82                }
83                let mut result = $crate::rbdc::db::ExecResult {
84                    rows_affected: 0,
85                    last_insert_id: rbs::Value::Null,
86                };
87                let ranges = $crate::plugin::Page::<()>::make_ranges(tables.len() as u64, batch_size);
88                for (offset, limit) in ranges {
89                    let exec_result = insert_batch(
90                        executor,
91                        &tables[offset as usize..limit as usize],
92                        table_name.as_str(),
93                    )
94                    .await?;
95                    result.rows_affected += exec_result.rows_affected;
96                    result.last_insert_id = exec_result.last_insert_id;
97                }
98                Ok(result)
99            }
100
101            /// insert a single record
102            ///
103            /// sql: `INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...)`
104            pub async fn insert(
105                executor: &dyn $crate::executor::Executor,
106                table: &$table,
107            ) -> std::result::Result<$crate::rbdc::db::ExecResult, $crate::rbdc::Error> {
108                <$table>::insert_batch(executor, std::slice::from_ref(table), 1).await
109            }
110        }
111        // select
112        impl $table {
113            /// select records by condition map.
114            /// supports "column" key in condition to select specific columns, e.g. `value!{"col1":"val1", "column": ["col1", "col2"]}`
115            ///
116            /// sql: `SELECT col1, col2, ... FROM table_name WHERE col1 = ? and col2 in (?, ?, ...)`
117            ///
118            /// condition map -> where sql:
119            /// - `value!{"col1": "val1"}`                       -> `WHERE col1 = 'val1'`
120            /// - `value!{"col1": 1, "col2": "val2"}`           -> `WHERE col1 = 1 and col2 = 'val2'`
121            /// - `value!{"col1": ["v1", "v2", "v3"]}`          -> `WHERE col1 in ('v1', 'v2', 'v3')`
122            /// - `value!{"col1": "val1", "col2": ["a", "b"]}`  -> `WHERE col1 = 'val1' and col2 in ('a', 'b')`
123            /// - null values are skipped
124            pub async fn select_by_map(executor: &dyn $crate::executor::Executor, mut condition: rbs::Value) -> std::result::Result<Vec<$table>, $crate::rbdc::Error> {
125                use rbatis::crud_traits::ValueOperatorSql;
126                // Extract column specification and remove it from condition
127                let table_column = {
128                    let mut columns = String::new();
129                    let mut clean_map = rbs::value::map::ValueMap::with_capacity(condition.len());
130                    for (k, v) in condition {
131                            match k.as_str() {
132                                Some("column") => {
133                                    columns = match v {
134                                        rbs::Value::String(s) => s.clone(),
135                                        rbs::Value::Array(arr) => {
136                                            let cols: Vec<&str> = arr.iter()
137                                                .filter_map(|v| v.as_str())
138                                                .collect();
139                                            if cols.is_empty() { "*".to_string() } else { cols.join(", ") }
140                                        }
141                                        _ => "*".to_string(),
142                                    };
143                                }
144                                _ => { clean_map.insert(k.clone(), v.clone()); }
145                            }
146                    }
147                    if columns.is_empty() { columns = "*".to_string(); }
148                    condition = rbs::Value::Map(clean_map);
149                    columns
150                };
151
152                #[$crate::py_sql(
153          "`select ${table_column} from ${table_name}`
154           trim end=' where ':
155             ` where `
156             trim ' and ': for key,item in condition:
157                          if item == null:
158                             continue:
159                          if !item.is_array():
160                            ` and ${key.operator_sql()}#{item}`
161                          if item.is_array():
162                            ` and ${key} in (`
163                               trim ',': for _,item_array in item:
164                                    #{item_array},
165                            `)`
166        ")]
167                async fn select_by_map(
168                    executor: &dyn $crate::executor::Executor,
169                    table_name: String,
170                    table_column: &str,
171                    condition: &rbs::Value
172                ) -> std::result::Result<Vec<$table>, $crate::rbdc::Error> {
173                    for (_,v) in condition {
174                        if v.is_array() && v.is_empty(){
175                           return Ok(vec![]);
176                        }
177                    }
178                    impled!()
179                }
180                let mut table_name = $table_name.to_string();
181                if table_name.is_empty(){
182                         #[$crate::snake_name($table)]
183                         fn snake_name(){}
184                         table_name = snake_name();
185                }
186                select_by_map(executor, table_name, &table_column, &condition).await
187            }
188        }
189        // update
190        impl $table {
191            /// update records by condition map.
192            /// supports "column" key in condition to update specific columns, e.g. `value!{"col1":"val1", "column": ["col2", "col3"]}`
193            ///
194            /// sql: `UPDATE table_name SET col1 = ?, col2 = ?, ... WHERE col1 = ? and col2 in (?, ?, ...)`
195            /// note: skips null fields by default, skips 'id' field always
196            ///
197            /// condition map -> where sql:
198            /// - `value!{"col1": "val1"}`                       -> `WHERE col1 = 'val1'`
199            /// - `value!{"col1": 1, "col2": "val2"}`           -> `WHERE col1 = 1 and col2 = 'val2'`
200            /// - `value!{"col1": ["v1", "v2", "v3"]}`          -> `WHERE col1 in ('v1', 'v2', 'v3')`
201            /// - `value!{"col1": "val1", "col2": ["a", "b"]}`  -> `WHERE col1 = 'val1' and col2 in ('a', 'b')`
202            /// - null values are skipped
203            pub async fn update_by_map(
204                executor: &dyn $crate::executor::Executor,
205                table: &$table,
206                mut condition: rbs::Value
207            ) -> std::result::Result<$crate::rbdc::db::ExecResult, $crate::rbdc::Error> {
208                use rbatis::crud_traits::{ValueOperatorSql, FilterByColumns};
209
210                // Extract column list for selective updates - implements GitHub issue #591
211                let set_columns = {
212                    let mut columns = rbs::Value::Null;
213                    let mut clean_map = rbs::value::map::ValueMap::with_capacity(condition.len());
214                    for (k, v) in condition {
215                            match k.as_str() {
216                                Some("column") => {
217                                    columns = match v {
218                                        rbs::Value::String(s) => {
219                                            rbs::Value::Array(vec![rbs::Value::String(s.clone())])
220                                        }
221                                        rbs::Value::Array(arr) => {
222                                            let filtered_array: Vec<rbs::Value> = arr.iter()
223                                                .filter(|v| v.as_str().is_some())
224                                                .cloned()
225                                                .collect();
226                                            if filtered_array.is_empty() {
227                                                rbs::Value::Null
228                                            } else {
229                                                rbs::Value::Array(filtered_array)
230                                            }
231                                        }
232                                        _ => rbs::Value::Null,
233                                    };
234                                }
235                                _ => { clean_map.insert(k.clone(), v.clone()); }
236                            }
237                    }
238                    condition = rbs::Value::Map(clean_map);
239                    columns
240                };
241                #[$crate::py_sql(
242                    "`update ${table_name}
243                      if skip_null == false:
244                        set collection='table',skips='id',skip_null=false:
245                      if skip_null == true:
246                        set collection='table',skips='id':
247                      trim end=' where ':
248                       ` where `
249                       trim ' and ': for key,item in condition:
250                            if item == null:
251                               continue:
252                            if !item.is_array():
253                              ` and ${key.operator_sql()}#{item}`
254                            if item.is_array():
255                              ` and ${key} in (`
256                                 trim ',': for _,item_array in item:
257                                      #{item_array},
258                              `)`
259                    "
260                )]
261                  async fn update_by_map_internal(
262                      executor: &dyn $crate::executor::Executor,
263                      table_name: String,
264                      table: &rbs::Value,
265                      condition: &rbs::Value,
266                      skip_null: bool,
267                  ) -> std::result::Result<$crate::rbdc::db::ExecResult, $crate::rbdc::Error> {
268                      for (_,v) in condition {
269                        if v.is_array() && v.is_empty(){
270                           return Ok($crate::rbdc::db::ExecResult::default());
271                        }
272                      }
273                      impled!()
274                  }
275                  let mut table_name = $table_name.to_string();
276                  if table_name.is_empty(){
277                         #[$crate::snake_name($table)]
278                         fn snake_name(){}
279                         table_name = snake_name();
280                  }
281                  let table_value = rbs::value!(table);
282                  let mut skip_null = true;
283                  let table = if set_columns != rbs::Value::Null {
284                    skip_null = false;
285                    table_value.filter_by_columns(&set_columns)
286                  } else {
287                    table_value
288                  };
289                  update_by_map_internal(executor, table_name, &table, &condition, skip_null).await
290            }
291        }
292        // delete
293        impl $table {
294            /// delete records by condition map
295            ///
296            /// sql: `DELETE FROM table_name WHERE col1 = ? and col2 in (?, ?, ...)`
297            ///
298            /// condition map -> where sql:
299            /// - `value!{"col1": "val1"}`                       -> `WHERE col1 = 'val1'`
300            /// - `value!{"col1": 1, "col2": "val2"}`           -> `WHERE col1 = 1 and col2 = 'val2'`
301            /// - `value!{"col1": ["v1", "v2", "v3"]}`          -> `WHERE col1 in ('v1', 'v2', 'v3')`
302            /// - `value!{"col1": "val1", "col2": ["a", "b"]}`  -> `WHERE col1 = 'val1' and col2 in ('a', 'b')`
303            /// - null values are skipped
304            pub async fn delete_by_map(executor: &dyn $crate::executor::Executor, condition: rbs::Value) -> std::result::Result<$crate::rbdc::db::ExecResult, $crate::rbdc::Error> {
305                use rbatis::crud_traits::ValueOperatorSql;
306                #[$crate::py_sql(
307         "`delete from ${table_name}`
308           trim end=' where ':
309             ` where `
310             trim ' and ': for key,item in condition:
311                          if item == null:
312                             continue:
313                          if !item.is_array():
314                            ` and ${key.operator_sql()}#{item}`
315                          if item.is_array():
316                            ` and ${key} in (`
317                               trim ',': for _,item_array in item:
318                                    #{item_array},
319                            `)`
320        ")]
321                async fn delete_by_map(
322                    executor: &dyn $crate::executor::Executor,
323                    table_name: String,
324                    condition: &rbs::Value
325                ) -> std::result::Result<$crate::rbdc::db::ExecResult, $crate::rbdc::Error> {
326                    for (_,v) in condition {
327                        if v.is_array() && v.is_empty(){
328                           return Ok($crate::rbdc::db::ExecResult::default());
329                        }
330                    }
331                    impled!()
332                }
333                let mut table_name = $table_name.to_string();
334                if table_name.is_empty(){
335                         #[$crate::snake_name($table)]
336                         fn snake_name(){}
337                         table_name = snake_name();
338                }
339                delete_by_map(executor, table_name, &condition).await
340            }
341        }
342    };
343}
344
345/// impl html_sql select page.
346///
347/// you must deal with 3 param:
348/// (do_count:bool,page_no:u64,page_size:u64)
349///
350/// you must deal with sql:
351/// return Vec<Record>(if param do_count = false)
352/// return u64(if param do_count = true)
353///
354/// you can see ${page_no} = (page_no -1) * page_size;
355/// you can see ${page_size} = page_size;
356///
357/// just like this example:
358/// ```html
359/// <select id="select_page_data">
360///         `select `
361///         <if test="do_count == true">
362///             `count(1) from table`
363///         </if>
364///         <if test="do_count == false">
365///             `* from table limit ${page_no},${page_size}`
366///         </if>
367///   </select>
368/// ```
369/// ```
370/// #[derive(serde::Serialize, serde::Deserialize)]
371/// pub struct MockTable{}
372/// //rbatis::htmlsql_select_page!(select_page_data(name: &str) -> MockTable => "example.html");
373/// rbatis::htmlsql_select_page!(select_page_data(name: &str) -> MockTable => r#"
374/// <select id="select_page_data">
375///  `select * from table  where id > 1  limit ${page_no},${page_size} `
376/// </select>"#);
377///
378/// rbatis::pysql_select_page!(pysql_select_page(name:&str) -> MockTable =>
379///     r#"`select * from activity where delete_flag = 0`
380///         if name != '':
381///            ` and name=#{name}`
382///       ` limit ${page_no},${page_size}`
383/// "#);
384/// ```
385#[macro_export]
386macro_rules! htmlsql_select_page {
387    ($fn_name:ident($($param_key:ident:$param_type:ty$(,)?)*) -> $table:ty => $($html_file:expr$(,)?)*) => {
388            pub async fn $fn_name(executor: &dyn $crate::executor::Executor, page_request: &dyn $crate::plugin::IPageRequest, $($param_key:$param_type,)*) -> std::result::Result<$crate::plugin::Page<$table>, $crate::rbdc::Error> {
389             #[$crate::html_sql($($html_file,)*)]
390             pub async fn $fn_name(executor: &dyn $crate::executor::Executor,do_count:bool,page_no:u64,page_size:u64,$($param_key: &$param_type,)*) -> std::result::Result<rbs::Value, $crate::rbdc::Error>{
391                 $crate::impled!()
392             }
393              let mut executor = executor;
394              let mut conn = None;
395              if executor.name().eq($crate::executor::Executor::name(executor.rb_ref())){
396                  conn = Some(executor.rb_ref().acquire().await?);
397                  match &conn {
398                      Some(c) => {
399                          executor = c;
400                      }
401                      None => {}
402                  }
403             }
404             let mut total = 0;
405             let mut intercept = executor.rb_ref().get_intercept::<$crate::plugin::intercept_page::PageIntercept>().ok_or_else(|| $crate::rbdc::Error::from("PageIntercept not found"))?;
406             if page_request.do_count() {
407                intercept.count_ids.insert(executor.id(),$crate::plugin::PageRequest::new(page_request.page_no(), page_request.page_size()));
408                let total_value = $fn_name(executor, true, page_request.offset(), page_request.page_size(), $(&$param_key,)*).await?;
409                total = $crate::decode(total_value).unwrap_or(0);
410             }
411             intercept.select_ids.insert(executor.id(),$crate::plugin::PageRequest::new(page_request.page_no(), page_request.page_size()));
412             let mut page = $crate::plugin::Page::<$table>::new(page_request.page_no(), page_request.page_size(), total,vec![]);
413             let records_value = $fn_name(executor, false, page_request.offset(), page_request.page_size(), $(&$param_key,)*).await?;
414             page.records = rbs::from_value(records_value)?;
415             Ok(page)
416         }
417    }
418}
419
420/// impl py_sql select page.
421///
422/// you must deal with 3 param:
423/// (do_count:bool,page_no:u64,page_size:u64)
424///
425/// you must deal with sql:
426/// return Vec<Record>(if param do_count = false)
427/// return u64(if param do_count = true)·
428///
429/// you can see ${page_no} = (page_no -1) * page_size;
430/// you can see ${page_size} = page_size;
431///
432/// just like this example:
433/// ```py
434/// `select * from activity where delete_flag = 0`
435///                   if name != '':
436///                     ` and name=#{name}`
437///                   if !ids.is_empty():
438///                     ` and id in `
439///                     ${ids.sql()}
440/// ```
441/// ```
442/// #[derive(serde::Serialize, serde::Deserialize)]
443/// pub struct MockTable{}
444/// rbatis::pysql_select_page!(pysql_select_page(name:&str) -> MockTable =>
445///     r#"`select * from activity where delete_flag = 0`
446///         if name != '':
447///            ` and name=#{name}`
448///       ` limit ${page_no},${page_size}`
449/// "#);
450/// ```
451#[macro_export]
452macro_rules! pysql_select_page {
453    ($fn_name:ident($($param_key:ident:$param_type:ty$(,)?)*) -> $table:ty => $($py_file:expr$(,)?)*) => {
454            pub async fn $fn_name(executor: &dyn $crate::executor::Executor, page_request: &dyn $crate::plugin::IPageRequest, $($param_key:$param_type,)*) -> std::result::Result<$crate::plugin::Page<$table>, $crate::rbdc::Error> {
455              #[$crate::py_sql($($py_file,)*)]
456              pub async fn $fn_name(executor: &dyn $crate::executor::Executor,do_count:bool,page_no:u64,page_size:u64,$($param_key: &$param_type,)*) -> std::result::Result<rbs::Value, $crate::rbdc::Error>{
457                 $crate::impled!()
458              }
459              let mut executor = executor;
460              let mut conn = None;
461              if executor.name().eq($crate::executor::Executor::name(executor.rb_ref())){
462                  conn = Some(executor.rb_ref().acquire().await?);
463                  match &conn {
464                      Some(c) => {
465                          executor = c;
466                      }
467                      None => {}
468                  }
469              }
470              let mut intercept = executor.rb_ref().get_intercept::<$crate::plugin::intercept_page::PageIntercept>().ok_or_else(|| $crate::rbdc::Error::from("PageIntercept not found"))?;
471              let mut total = 0;
472              if page_request.do_count() {
473                 intercept.count_ids.insert(executor.id(),$crate::plugin::PageRequest::new(page_request.page_no(), page_request.page_size()));
474                 let total_value = $fn_name(executor, true, page_request.offset(), page_request.page_size(), $(&$param_key,)*).await?;
475                 total = $crate::decode(total_value).unwrap_or(0);
476              }
477              intercept.select_ids.insert(executor.id(),$crate::plugin::PageRequest::new(page_request.page_no(), page_request.page_size()));
478              let mut page = $crate::plugin::Page::<$table>::new(page_request.page_no(), page_request.page_size(), total,vec![]);
479              let records_value = $fn_name(executor, false, page_request.offset(), page_request.page_size(), $(&$param_key,)*).await?;
480              page.records = rbs::from_value(records_value)?;
481              Ok(page)
482         }
483    }
484}
485
486/// use macro wrapper #[sql]
487/// for example:
488/// ```rust
489/// use rbatis::executor::Executor;
490/// rbatis::raw_sql!(test_same_id(rb: &dyn Executor, id: &u64)  -> Result<rbs::Value, rbatis::Error> =>
491/// "select * from table where id = ?"
492/// );
493/// ```
494#[macro_export]
495macro_rules! raw_sql {
496    ($fn_name:ident($($param_key:ident:$param_type:ty$(,)?)*) -> $return_type:ty => $sql_file:expr) => {
497       #[$crate::sql($sql_file)]
498       pub async fn $fn_name($($param_key: $param_type,)*) -> $return_type{
499           impled!()
500       }
501    }
502}
503
504/// use macro wrapper #[py_sql]
505/// for query example:
506/// ```rust
507/// use rbatis::executor::Executor;
508/// rbatis::pysql!(test_same_id(rb: &dyn Executor, id: &u64)  -> Result<rbs::Value, rbatis::Error> =>
509/// "select * from table where ${id} = 1
510///  if id != 0:
511///    `id = #{id}`"
512/// );
513/// ```
514/// for exec example:
515/// ```rust
516/// use rbatis::executor::Executor;
517/// use rbdc::db::ExecResult;
518/// rbatis::pysql!(test_same_id(rb: &dyn Executor, id: &u64)  -> Result<ExecResult, rbatis::Error> =>
519/// "`update activity set name = '1' where id = #{id}`"
520/// );
521/// ```
522#[macro_export]
523macro_rules! pysql {
524    ($fn_name:ident($($param_key:ident:$param_type:ty$(,)?)*) -> $return_type:ty => $py_file:expr) => {
525       #[$crate::py_sql($py_file)]
526       pub async fn $fn_name($($param_key: $param_type,)*) -> $return_type{
527          impled!()
528       }
529    }
530}
531
532/// use macro wrapper #[html_sql]
533/// for example query rbs::Value:
534/// ```rust
535/// use rbatis::executor::Executor;
536/// rbatis::htmlsql!(test_select(rb: &dyn Executor, id: &u64)  -> Result<rbs::Value, rbatis::Error> => r#"
537///             <mapper>
538///             <select id="test_same_id">
539///               `select ${id} from my_table`
540///             </select>
541///             </mapper>"#);
542/// ```
543/// exec (from file)
544/// ```rust
545/// use rbatis::executor::Executor;
546/// use rbdc::db::ExecResult;
547/// rbatis::htmlsql!(update_by_id(rb: &dyn Executor, id: &u64)  -> Result<ExecResult, rbatis::Error> => "example/example.html");
548/// ```
549/// query
550/// ```rust
551/// use rbatis::executor::Executor;
552/// #[derive(serde::Serialize, serde::Deserialize)]
553/// pub struct MyTable{
554///      pub id:Option<u64>,
555///      pub name:Option<String>,
556/// }
557/// rbatis::htmlsql!(test_select_table(rb: &dyn Executor, id: &u64)  -> Result<Vec<MyTable>, rbatis::Error> => r#"
558///             <mapper>
559///               <select id="test_same_id">
560///                 `select * from my_table`
561///               </select>
562///             </mapper>"#);
563/// ```
564#[macro_export]
565macro_rules! htmlsql {
566    ($fn_name:ident($($param_key:ident:$param_type:ty$(,)?)*) -> $return_type:ty => $html_file:expr) => {
567        #[$crate::html_sql($html_file)]
568        pub async fn $fn_name($($param_key: $param_type,)*) -> $return_type{
569              impled!()
570        }
571    }
572}