use std::collections::{BTreeMap, HashMap, HashSet, LinkedList, VecDeque};
use std::fmt::Display;
use std::hash::Hash;
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::core::convert::{ResultCodec, StmtConvert};
use crate::core::db::DBExecResult;
use crate::core::db::DriverType;
use crate::core::Error;
use crate::core::Result;
use crate::plugin::page::{IPageRequest, Page};
use crate::plugin::version_lock::VersionLockPlugin;
use crate::rbatis::Rbatis;
use crate::sql::rule::SqlRule;
use crate::utils::string_util::to_snake_name;
use crate::wrapper::Wrapper;
pub trait CRUDTable: Send + Sync + Serialize + DeserializeOwned {
type IdType: Send + Sync + Clone + Serialize + Display + Eq + PartialEq;
fn id_name() -> String {
"id".to_string()
}
fn get_id(&self) -> Option<&Self::IdType>;
fn table_name() -> String {
let type_name = std::any::type_name::<Self>();
let mut name = type_name.to_string();
let names: Vec<&str> = name.split("::").collect();
name = names.get(names.len() - 1).unwrap_or(&"").to_string();
return to_snake_name(&name);
}
fn table_columns() -> String {
let bean: serde_json::Result<Self> = serde_json::from_str("{}");
if bean.is_err() {
return " * ".to_string();
}
let v = json!(&bean.unwrap());
if !v.is_object() {
return " * ".to_string();
}
let m = v.as_object().unwrap();
let mut fields = String::new();
for (k, _) in m {
fields.push_str(k);
fields.push_str(",");
}
fields.pop();
return format!("{}", fields);
}
fn make_column_value_map(
&self,
db_type: &DriverType,
) -> Result<serde_json::Map<String, Value>> {
let json = json!(self);
if json.eq(&serde_json::Value::Null) {
return Err(Error::from("[rbaits] to_value_map() fail!"));
}
if !json.is_object() {
return Err(Error::from(
"[rbaits] to_value_map() fail,data is not an object!",
));
}
return Ok(json.as_object().unwrap().to_owned());
}
fn do_format_column(driver_type: &DriverType, column: &str, data: String) -> String {
let m = Self::formats(driver_type);
let source = m.get(column);
match source {
Some(s) => s(&data),
_ => {
return data.to_string();
}
}
}
fn make_value_sql_arg(
&self,
db_type: &DriverType,
index: &mut usize,
) -> Result<(String, String, Vec<serde_json::Value>)> {
let mut value_sql = String::new();
let mut arr = vec![];
let cols = Self::table_columns();
let columns: Vec<&str> = cols.split(",").collect();
let map = self.make_column_value_map(db_type)?;
let mut column_sql = String::new();
for column in &columns {
let column = crate::utils::string_util::un_packing_string(column);
let v = map.get(column).unwrap_or(&serde_json::Value::Null);
if Self::id_name().eq(column) && v.eq(&serde_json::Value::Null) {
continue;
}
column_sql = column_sql + column + ",";
value_sql = value_sql
+ Self::do_format_column(db_type, &column, db_type.stmt_convert(*index)).as_str()
+ ",";
arr.push(v.to_owned());
*index += 1;
}
column_sql.pop();
value_sql.pop();
return Ok((column_sql, value_sql, arr));
}
fn formats(
driver_type: &crate::core::db::DriverType,
) -> HashMap<String, fn(arg: &str) -> String> {
return HashMap::new();
}
}
impl<T> CRUDTable for Option<T>
where
T: CRUDTable,
{
type IdType = T::IdType;
fn id_name() -> String {
T::id_name()
}
fn get_id(&self) -> Option<&Self::IdType> {
match self {
Some(s) => {
return s.get_id();
}
None => {
return None;
}
}
}
fn table_name() -> String {
T::table_name()
}
fn table_columns() -> String {
T::table_columns()
}
fn formats(driver_type: &DriverType) -> HashMap<String, fn(arg: &str) -> String> {
T::formats(driver_type)
}
fn make_column_value_map(
&self,
db_type: &DriverType,
) -> Result<serde_json::Map<String, Value>> {
if self.is_none() {
return Err(crate::core::Error::from(
"[rbatis] can not make_column_value_map() for None value!",
));
}
T::make_column_value_map(self.as_ref().unwrap(), db_type)
}
fn make_value_sql_arg(
&self,
db_type: &DriverType,
index: &mut usize,
) -> Result<(String, String, Vec<serde_json::Value>)> {
if self.is_none() {
return Err(crate::core::Error::from(
"[rbatis] can not make_sql_arg() for None value!",
));
}
T::make_value_sql_arg(self.as_ref().unwrap(), db_type, index)
}
}
pub trait Ids<C>
where
C: CRUDTable,
{
fn to_ids(&self) -> Vec<C::IdType>;
}
impl<C> Ids<C> for [C]
where
C: CRUDTable,
{
fn to_ids(&self) -> Vec<C::IdType> {
let mut vec = vec![];
for item in self {
let id = item.get_id();
if id.is_some() {
match id {
Some(id) => {
vec.push(id.clone());
}
_ => {}
}
}
}
vec
}
}
impl<C> Ids<C> for HashSet<C>
where
C: CRUDTable,
{
fn to_ids(&self) -> Vec<C::IdType> {
let mut vec = vec![];
for item in self {
let id = item.get_id();
if id.is_some() {
match id {
Some(id) => {
vec.push(id.clone());
}
_ => {}
}
}
}
vec
}
}
impl<C> Ids<C> for VecDeque<C>
where
C: CRUDTable,
{
fn to_ids(&self) -> Vec<C::IdType> {
let mut vec = vec![];
for item in self {
let id = item.get_id();
if id.is_some() {
match id {
Some(id) => {
vec.push(id.clone());
}
_ => {}
}
}
}
vec
}
}
impl<C> Ids<C> for LinkedList<C>
where
C: CRUDTable,
{
fn to_ids(&self) -> Vec<C::IdType> {
let mut vec = vec![];
for item in self {
let id = item.get_id();
if id.is_some() {
match id {
Some(id) => {
vec.push(id.clone());
}
_ => {}
}
}
}
vec
}
}
impl<K, C> Ids<C> for HashMap<K, C>
where
C: CRUDTable,
{
fn to_ids(&self) -> Vec<C::IdType> {
let mut vec = vec![];
for (_, item) in self {
let id = item.get_id();
if id.is_some() {
match id {
Some(id) => {
vec.push(id.clone());
}
_ => {}
}
}
}
vec
}
}
impl<K, C> Ids<C> for BTreeMap<K, C>
where
C: CRUDTable,
{
fn to_ids(&self) -> Vec<C::IdType> {
let mut vec = vec![];
for (_, item) in self {
let id = item.get_id();
if id.is_some() {
match id {
Some(id) => {
vec.push(id.clone());
}
_ => {}
}
}
}
vec
}
}
#[async_trait]
pub trait CRUD {
async fn save_by_wrapper<T>(
&self,
context_id: &str,
entity: &T,
w: &Wrapper,
) -> Result<DBExecResult>
where
T: CRUDTable;
async fn save<T>(&self, context_id: &str, entity: &T) -> Result<DBExecResult>
where
T: CRUDTable;
async fn save_batch<T>(&self, context_id: &str, entity: &[T]) -> Result<DBExecResult>
where
T: CRUDTable;
async fn save_batch_slice<T>(&self, context_id: &str, entity: &[T], slice_len: usize) -> Result<DBExecResult>
where
T: CRUDTable;
async fn remove_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<u64>
where
T: CRUDTable;
async fn remove_by_id<T>(&self, context_id: &str, id: &T::IdType) -> Result<u64>
where
T: CRUDTable;
async fn remove_batch_by_id<T>(&self, context_id: &str, ids: &[T::IdType]) -> Result<u64>
where
T: CRUDTable;
async fn update_by_wrapper<T>(
&self,
context_id: &str,
arg: &mut T,
w: &Wrapper,
update_null_value: bool,
) -> Result<u64>
where
T: CRUDTable;
async fn update_by_id<T>(&self, context_id: &str, arg: &mut T) -> Result<u64>
where
T: CRUDTable;
async fn update_batch_by_id<T>(&self, context_id: &str, ids: &mut [T]) -> Result<u64>
where
T: CRUDTable;
async fn fetch_by_id<T>(&self, context_id: &str, id: &T::IdType) -> Result<T>
where
T: CRUDTable;
async fn fetch_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<T>
where
T: CRUDTable;
async fn fetch_count_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<u64>
where
T: CRUDTable;
async fn fetch_page_by_wrapper<T>(
&self,
context_id: &str,
w: &Wrapper,
page: &dyn IPageRequest,
) -> Result<Page<T>>
where
T: CRUDTable;
async fn fetch_list<T>(&self, context_id: &str) -> Result<Vec<T>>
where
T: CRUDTable;
async fn fetch_list_by_ids<T>(&self, context_id: &str, ids: &[T::IdType]) -> Result<Vec<T>>
where
T: CRUDTable;
async fn fetch_list_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<Vec<T>>
where
T: CRUDTable;
}
#[async_trait]
impl CRUD for Rbatis {
async fn save_by_wrapper<T>(
&self,
context_id: &str,
entity: &T,
w: &Wrapper,
) -> Result<DBExecResult>
where
T: CRUDTable,
{
if w.sql.starts_with(crate::sql::TEMPLATE.insert_into.value) {
return self.exec_prepare(context_id, &w.sql, &w.args).await;
} else {
let mut w = w.clone();
let mut index = 0;
let (columns, values, args) =
entity.make_value_sql_arg(&self.driver_type()?, &mut index)?;
let table_name = choose_dyn_table_name::<T>(&w);
w = w.insert_into(&table_name, &columns, &values);
for x in args {
w.args.push(x);
}
return self.exec_prepare(context_id, &w.sql, &w.args).await;
}
}
async fn save<T>(&self, context_id: &str, entity: &T) -> Result<DBExecResult>
where
T: CRUDTable,
{
let mut index = 0;
let (columns, values, args) =
entity.make_value_sql_arg(&self.driver_type()?, &mut index)?;
let sql = format!(
"{} {} ({}) {} ({})",
crate::sql::TEMPLATE.insert_into.value,
T::table_name(),
columns,
crate::sql::TEMPLATE.values.value,
values
);
return self.exec_prepare(context_id, sql.as_str(), &args).await;
}
async fn save_batch<T>(&self, context_id: &str, args: &[T]) -> Result<DBExecResult>
where
T: CRUDTable,
{
if args.is_empty() {
return Ok(DBExecResult {
rows_affected: 0,
last_insert_id: None,
});
}
let mut value_arr = String::new();
let mut arg_arr = vec![];
let mut column_sql = String::new();
let mut field_index = 0;
for x in args {
let (columns, values, args) =
x.make_value_sql_arg(&self.driver_type()?, &mut field_index)?;
if column_sql.is_empty() {
column_sql = columns;
}
value_arr = value_arr + format!("({}),", values).as_str();
for x in args {
arg_arr.push(x);
}
}
value_arr.pop();
let sql = format!(
"{} {} ({}) {} {}",
crate::sql::TEMPLATE.insert_into.value,
T::table_name(),
column_sql,
crate::sql::TEMPLATE.values.value,
value_arr
);
return self.exec_prepare(context_id, sql.as_str(), &arg_arr).await;
}
async fn save_batch_slice<T>(&self, context_id: &str, args: &[T], slice_len: usize) -> Result<DBExecResult>
where
T: CRUDTable,
{
if slice_len == 0 || args.len() <= slice_len {
return self.save_batch(context_id, args).await;
} else {
let mut temp_result = DBExecResult { rows_affected: 0, last_insert_id: None };
let total = args.len();
let mut pages = args.len() / slice_len;
if total % slice_len != 0 {
pages = pages + 1;
}
for page in 0..pages {
let mut temp_len = slice_len * (1 + page);
if temp_len > total {
temp_len = total;
}
let temp = &args[page * slice_len..temp_len];
let result = self.save_batch(context_id, temp).await?;
temp_result.last_insert_id = result.last_insert_id;
temp_result.rows_affected = result.rows_affected + temp_result.rows_affected;
}
return Ok(temp_result);
}
}
async fn remove_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<u64>
where
T: CRUDTable,
{
let table_name = choose_dyn_table_name::<T>(w);
let where_sql = self.driver_type()?.make_where(&w.sql);
let mut sql = String::new();
if self.logic_plugin.is_some() {
sql = self.logic_plugin.as_ref().unwrap().create_remove_sql(
context_id,
&self.driver_type()?,
&table_name,
&T::table_columns(),
&where_sql,
)?;
} else {
sql = format!("{} {} {}", crate::sql::TEMPLATE.delete_from.value, table_name, &where_sql);
}
return Ok(self
.exec_prepare(context_id, sql.as_str(), &w.args)
.await?
.rows_affected);
}
async fn remove_by_id<T>(&self, context_id: &str, id: &T::IdType) -> Result<u64>
where
T: CRUDTable,
{
let mut sql = String::new();
let driver_type = &self.driver_type()?;
let id_str = T::do_format_column(&driver_type, &T::id_name(), driver_type.stmt_convert(0));
if self.logic_plugin.is_some() {
sql = self.logic_plugin.as_ref().unwrap().create_remove_sql(
context_id,
&driver_type,
T::table_name().as_str(),
&T::table_columns(),
format!("{} {} = {}", crate::sql::TEMPLATE.r#where.value, T::id_name(), id_str).as_str(),
)?;
} else {
sql = format!(
"{} {} {} {} = {}",
crate::sql::TEMPLATE.delete_from.value,
T::table_name(),
crate::sql::TEMPLATE.r#where.value,
T::id_name(),
id_str
);
}
return Ok(self
.exec_prepare(context_id, sql.as_str(), &vec![json!(id)])
.await?
.rows_affected);
}
async fn remove_batch_by_id<T>(&self, context_id: &str, ids: &[T::IdType]) -> Result<u64>
where
T: CRUDTable,
{
if ids.is_empty() {
return Ok(0);
}
let w = self
.new_wrapper_table::<T>()
.and()
.in_array(&T::id_name(), &ids);
return self.remove_by_wrapper::<T>(context_id, &w).await;
}
async fn update_by_wrapper<T>(
&self,
context_id: &str,
arg: &mut T,
w: &Wrapper,
update_null_value: bool,
) -> Result<u64>
where
T: CRUDTable,
{
let table_name = choose_dyn_table_name::<T>(w);
let mut args = vec![];
let mut old_version = serde_json::Value::Null;
let driver_type = &self.driver_type()?;
let mut map = arg.make_column_value_map(&driver_type)?;
let mut sets = String::new();
for (column, v) in &mut map {
if column.eq(&T::id_name()) {
continue;
}
if !update_null_value && v.is_null() {
continue;
}
sets.push_str(
format!(
" {} = {},",
column,
T::do_format_column(
&driver_type,
&column,
driver_type.stmt_convert(args.len()),
)
)
.as_str(),
);
match &self.version_lock_plugin {
Some(version_lock_plugin) => {
old_version = v.clone();
*v = version_lock_plugin.try_add_one(context_id, &old_version, column);
}
_ => {}
}
args.push(v.clone());
}
sets.pop();
let mut wrapper = self.new_wrapper_table::<T>();
wrapper.sql = format!("{} {} {} {} ", crate::sql::TEMPLATE.update.value, table_name, crate::sql::TEMPLATE.set.value, sets);
wrapper.args = args;
match self.version_lock_plugin.as_ref() {
Some(version_lock_plugin) => {
let version_sql = version_lock_plugin
.as_ref()
.try_make_where_sql(context_id, &old_version);
if !version_sql.is_empty() {
if !wrapper.sql.contains(crate::sql::TEMPLATE.r#where.left_right_space) {
wrapper.sql.push_str(crate::sql::TEMPLATE.r#where.left_right_space);
}
wrapper.sql.push_str(
&version_sql,
);
}
}
_ => {}
}
if !w.sql.is_empty() {
if !wrapper.sql.contains(crate::sql::TEMPLATE.r#where.left_right_space) {
wrapper.sql.push_str(crate::sql::TEMPLATE.r#where.left_right_space);
}
wrapper = wrapper.and();
wrapper = wrapper.push_wrapper(&w);
}
let rows_affected = self
.exec_prepare(context_id, wrapper.sql.as_str(), &wrapper.args)
.await?
.rows_affected;
if rows_affected > 0 {
*arg = serde_json::from_value(serde_json::Value::Object(map)).into_result()?;
}
return Ok(rows_affected);
}
async fn update_by_id<T>(&self, context_id: &str, arg: &mut T) -> Result<u64>
where
T: CRUDTable,
{
let id = arg.get_id();
if id.is_none() {
let id_name = T::id_name();
return Err(crate::core::Error::from(format!(
"[rbatis] update_by_id() arg.{} can no be none!",
id_name
)));
}
self.update_by_wrapper(
context_id,
arg,
&self
.new_wrapper_table::<T>()
.eq(&T::id_name(), arg.get_id()),
false,
)
.await
}
async fn update_batch_by_id<T>(&self, context_id: &str, args: &mut [T]) -> Result<u64>
where
T: CRUDTable,
{
let mut updates = 0;
for x in args {
updates += self.update_by_id(context_id, x).await?
}
Ok(updates)
}
async fn fetch_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<T>
where
T: CRUDTable,
{
let sql = make_select_sql::<T>(context_id, &self, &T::table_columns(), &w)?;
return self.fetch_prepare(context_id, sql.as_str(), &w.args).await;
}
async fn fetch_count_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<u64>
where
T: CRUDTable,
{
let sql = make_select_sql::<T>(context_id, &self, "count(1)", &w)?;
return self.fetch_prepare(context_id, sql.as_str(), &w.args).await;
}
async fn fetch_by_id<T>(&self, context_id: &str, id: &T::IdType) -> Result<T>
where
T: CRUDTable,
{
let w = self.new_wrapper_table::<T>().eq(&T::id_name(), id);
return self.fetch_by_wrapper(context_id, &w).await;
}
async fn fetch_list_by_wrapper<T>(&self, context_id: &str, w: &Wrapper) -> Result<Vec<T>>
where
T: CRUDTable,
{
let sql = make_select_sql::<T>(context_id, &self, &T::table_columns(), &w)?;
return self.fetch_prepare(context_id, sql.as_str(), &w.args).await;
}
async fn fetch_list<T>(&self, context_id: &str) -> Result<Vec<T>>
where
T: CRUDTable,
{
return self
.fetch_list_by_wrapper(context_id, &self.new_wrapper_table::<T>())
.await;
}
async fn fetch_list_by_ids<T>(&self, context_id: &str, ids: &[T::IdType]) -> Result<Vec<T>>
where
T: CRUDTable,
{
let w = self.new_wrapper_table::<T>().in_array(&T::id_name(), ids);
return self.fetch_list_by_wrapper(context_id, &w).await;
}
async fn fetch_page_by_wrapper<T>(
&self,
context_id: &str,
w: &Wrapper,
page: &dyn IPageRequest,
) -> Result<Page<T>>
where
T: CRUDTable,
{
let sql = make_select_sql::<T>(context_id, &self, &T::table_columns(), &w)?;
self.fetch_page(context_id, sql.as_str(), &w.args, page)
.await
}
}
fn choose_dyn_table_name<T>(w: &Wrapper) -> String
where
T: CRUDTable,
{
let mut table_name = T::table_name();
let table_name_format = w.formats.get("table_name");
if table_name_format.is_some() {
match table_name_format {
Some(table_name_format) => {
table_name = table_name_format(&table_name);
}
_ => {}
}
}
return table_name;
}
fn make_select_sql<T>(context_id: &str, rb: &Rbatis, column: &str, w: &Wrapper) -> Result<String>
where
T: CRUDTable,
{
let driver_type = rb.driver_type()?;
let table_name = choose_dyn_table_name::<T>(w);
if rb.logic_plugin.is_some() {
let logic_ref = rb.logic_plugin.as_ref().unwrap();
return logic_ref.create_select_sql(
context_id,
&driver_type,
&table_name,
column,
&T::table_columns(),
&w.sql,
);
}
Ok(format!(
"{} {} {} {} {}",
crate::sql::TEMPLATE.select.value,
column,
crate::sql::TEMPLATE.from.value,
table_name,
driver_type.make_where(&w.sql)
))
}