use async_trait::async_trait;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct IndexDefinition {
pub name: String,
pub columns: Vec<String>,
pub unique: bool,
}
impl IndexDefinition {
pub fn new(name: impl Into<String>, columns: Vec<String>, unique: bool) -> Self {
Self {
name: name.into(),
columns,
unique,
}
}
pub fn parse(_table_name: &str, input: &str, unique: bool) -> Vec<Self> {
if input.is_empty() {
return vec![];
}
input
.split(';')
.filter(|s| !s.trim().is_empty())
.map(|part| {
let part = part.trim();
let (name, columns) = if let Some((n, cols)) = part.split_once(':') {
(n.trim().to_string(), cols)
} else {
let cols = part;
let prefix = if unique { "uidx" } else { "idx" };
let col_part = cols.replace(',', "_").replace(' ', "");
(format!("{}_{}", prefix, col_part), cols)
};
let columns: Vec<String> = columns
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
IndexDefinition::new(name, columns, unique)
})
.collect()
}
}
use crate::error::{Error, Result};
use crate::query::QueryBuilder;
pub trait ModelMeta: Sized + Send + Sync + Clone + 'static {
type PrimaryKey: Send + Sync + Clone + std::fmt::Debug + std::fmt::Display + 'static;
fn table_name() -> &'static str;
fn primary_key_name() -> &'static str;
fn column_names() -> &'static [&'static str];
fn field_names() -> &'static [&'static str];
fn hidden_attributes() -> Vec<&'static str> {
vec!["deleted_at"]
}
fn default_presenter() -> &'static str {
"default"
}
fn searchable_fields() -> Vec<&'static str> {
vec![]
}
fn default_order() -> Vec<(&'static str, &'static str)> {
vec![("id", "DESC")]
}
fn option_set_label() -> &'static str {
"id"
}
fn option_set_search_fields() -> Vec<&'static str> {
vec![]
}
fn translatable_fields() -> Vec<&'static str> {
vec![]
}
fn allowed_languages() -> Vec<String> {
crate::config::Config::get_languages()
}
fn fallback_language() -> String {
crate::config::Config::get_fallback_language()
}
fn has_translations() -> bool {
!Self::translatable_fields().is_empty()
}
fn has_one_attached_file() -> Vec<&'static str> {
vec![]
}
fn has_many_attached_files() -> Vec<&'static str> {
vec![]
}
fn files_relations() -> Vec<&'static str> {
let mut relations = Self::has_one_attached_file();
relations.extend(Self::has_many_attached_files());
relations
}
fn has_file_attachments() -> bool {
!Self::files_relations().is_empty()
}
fn file_url_generator() -> crate::config::FileUrlGenerator {
crate::config::Config::get_file_url_generator()
}
#[inline]
fn generate_file_url(field_name: &str, file: &crate::attachments::FileAttachment) -> String {
Self::file_url_generator()(field_name, file)
}
fn soft_delete_enabled() -> bool {
false
}
fn has_timestamps() -> bool {
false
}
fn indexes() -> Vec<IndexDefinition> {
vec![]
}
fn unique_indexes() -> Vec<IndexDefinition> {
vec![]
}
fn all_indexes() -> Vec<IndexDefinition> {
let mut all = Self::indexes();
all.extend(Self::unique_indexes());
all
}
fn has_indexes() -> bool {
!Self::indexes().is_empty() || !Self::unique_indexes().is_empty()
}
fn tokenization_enabled() -> bool {
false
}
fn token_encoder() -> Option<crate::tokenization::TokenEncoder> {
None
}
fn token_decoder() -> Option<crate::tokenization::TokenDecoder> {
None
}
}
#[async_trait]
pub trait Model: ModelMeta + crate::internal::InternalModel + serde::Serialize + for<'de> serde::Deserialize<'de> {
fn primary_key(&self) -> Self::PrimaryKey;
fn db() -> crate::error::Result<&'static crate::database::Database> {
crate::database::require_db()
}
fn database(&self) -> crate::error::Result<&'static crate::database::Database> {
crate::database::require_db()
}
async fn all() -> Result<Vec<Self>>
where
Self: Sized,
{
crate::internal::QueryExecutor::find_all::<Self>(crate::database::require_db()?.__internal_connection()).await
}
fn query() -> QueryBuilder<Self>
where
Self: Sized,
{
QueryBuilder::new()
}
async fn count() -> Result<u64>
where
Self: Sized,
{
crate::internal::QueryExecutor::count::<Self>(crate::database::require_db()?.__internal_connection(), None).await
}
async fn exists_any() -> Result<bool>
where
Self: Sized,
{
Ok(Self::count().await? > 0)
}
async fn insert_all(models: Vec<Self>) -> Result<Vec<Self>>
where
Self: Sized,
<<Self as crate::internal::InternalModel>::Entity as crate::internal::EntityTrait>::Model:
crate::internal::IntoActiveModel<<Self as crate::internal::InternalModel>::ActiveModel>,
{
if models.is_empty() {
return Ok(Vec::new());
}
let conn = crate::database::require_db()?.__internal_connection();
crate::internal::QueryExecutor::insert_many::<Self>(conn, models).await
}
async fn insert_many_returning(models: Vec<Self>) -> Result<Vec<Self>>
where
Self: Sized,
<<Self as crate::internal::InternalModel>::Entity as crate::internal::EntityTrait>::Model:
crate::internal::IntoActiveModel<<Self as crate::internal::InternalModel>::ActiveModel>,
{
if models.is_empty() {
return Ok(Vec::new());
}
Self::insert_all(models).await
}
async fn insert_many(models: Vec<Self>) -> Result<Vec<Self>>
where
Self: Sized,
<<Self as crate::internal::InternalModel>::Entity as crate::internal::EntityTrait>::Model:
crate::internal::IntoActiveModel<<Self as crate::internal::InternalModel>::ActiveModel>,
{
if models.is_empty() {
return Ok(Vec::new());
}
Self::insert_all(models).await
}
async fn insert_or_update(
model: Self,
conflict_columns: Vec<&str>,
) -> Result<Self>
where
Self: Sized;
fn on_conflict(conflict_columns: Vec<&str>) -> OnConflictBuilder<Self>
where
Self: Sized,
{
OnConflictBuilder::new(conflict_columns.into_iter().map(|s| s.to_string()).collect())
}
fn update_all() -> BatchUpdateBuilder<Self>
where
Self: Sized,
{
BatchUpdateBuilder::new()
}
async fn transaction<F, T>(f: F) -> Result<T>
where
Self: Sized,
F: for<'c> FnOnce(&'c crate::database::Transaction)
-> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T>> + Send + 'c>> + Send,
T: Send,
{
crate::database::require_db()?.transaction(f).await
}
async fn first() -> Result<Option<Self>>
where
Self: Sized,
{
crate::internal::QueryExecutor::first::<Self>(crate::database::require_db()?.__internal_connection()).await
}
async fn last() -> Result<Option<Self>>
where
Self: Sized,
{
crate::internal::QueryExecutor::last::<Self>(crate::database::require_db()?.__internal_connection()).await
}
async fn paginate(page: u64, per_page: u64) -> Result<Vec<Self>>
where
Self: Sized,
{
let offset = (page.saturating_sub(1)) * per_page;
crate::internal::QueryExecutor::paginate::<Self>(crate::database::require_db()?.__internal_connection(), per_page, offset).await
}
async fn find(id: Self::PrimaryKey) -> Result<Option<Self>>
where
Self: Sized;
async fn find_or_fail(id: Self::PrimaryKey) -> Result<Self>
where
Self: Sized,
{
let id_display = format!("{}", id);
Self::find(id).await?.ok_or_else(|| {
Error::not_found(format!("{} with {} = {} not found", Self::table_name(), Self::primary_key_name(), id_display))
})
}
async fn exists(id: Self::PrimaryKey) -> Result<bool>
where
Self: Sized,
{
Ok(Self::find(id).await?.is_some())
}
async fn create(model: Self) -> Result<Self>
where
Self: Sized;
async fn destroy(id: Self::PrimaryKey) -> Result<u64>
where
Self: Sized;
async fn save(self) -> Result<Self>
where
Self: Sized;
async fn update(self) -> Result<Self>
where
Self: Sized;
async fn delete(self) -> Result<u64>
where
Self: Sized;
#[doc(hidden)]
async fn __insert_with_conflict(model: Self, builder: OnConflictBuilder<Self>) -> Result<Self>
where
Self: Sized;
async fn reload(&self) -> Result<Self>
where
Self: Sized,
{
Self::find_or_fail(self.primary_key()).await
}
fn is_new(&self) -> bool {
if self.primary_key().to_string().is_empty() {
return true;
}
false
}
fn to_json(&self, options: Option<HashMap<String, String>>) -> serde_json::Value
where
Self: serde::Serialize,
{
let opts = options.unwrap_or_default();
let fallback = Self::fallback_language();
let current_language = opts.get("language")
.map(|s| s.as_str())
.unwrap_or(&fallback);
let _current_presenter = opts.get("presenter")
.map(|s| s.as_str())
.unwrap_or(Self::default_presenter());
let hidden = Self::hidden_attributes();
let translatable = Self::translatable_fields();
let file_relations = Self::files_relations();
let mut json = match serde_json::to_value(self) {
Ok(serde_json::Value::Object(map)) => map,
_ => return serde_json::json!({}),
};
for attr in &hidden {
json.remove(*attr);
}
if Self::has_translations() {
if let Some(translations) = json.get("translations").cloned() {
if let Some(trans_obj) = translations.as_object() {
for field in &translatable {
if let Some(field_trans) = trans_obj.get(*field) {
if let Some(field_obj) = field_trans.as_object() {
if let Some(value) = field_obj.get(current_language)
.or_else(|| field_obj.get(&fallback))
{
json.insert(field.to_string(), value.clone());
}
}
}
}
}
json.remove("translations");
}
}
if Self::has_file_attachments() {
let url_generator = Self::file_url_generator();
if let Some(files) = json.remove("files") {
if let Some(files_obj) = files.as_object() {
for relation in &file_relations {
if let Some(file_data) = files_obj.get(*relation) {
let processed = Self::process_file_for_json(relation, file_data, &hidden, url_generator);
json.insert(relation.to_string(), processed);
}
}
}
}
}
serde_json::Value::Object(json)
}
#[inline]
fn process_file_for_json(
field_name: &str,
file_data: &serde_json::Value,
hidden_attrs: &[&str],
url_generator: crate::config::FileUrlGenerator,
) -> serde_json::Value {
match file_data {
serde_json::Value::Object(obj) => {
let mut cleaned = serde_json::Map::new();
for (key, value) in obj {
if !hidden_attrs.contains(&key.as_str()) {
cleaned.insert(key.clone(), value.clone());
}
}
if let Ok(file_attachment) = serde_json::from_value::<crate::attachments::FileAttachment>(serde_json::Value::Object(obj.clone())) {
let url = url_generator(field_name, &file_attachment);
cleaned.insert("url".to_string(), serde_json::Value::String(url));
}
serde_json::Value::Object(cleaned)
}
serde_json::Value::Array(arr) => {
let cleaned: Vec<serde_json::Value> = arr
.iter()
.map(|item| Self::process_file_for_json(field_name, item, hidden_attrs, url_generator))
.collect();
serde_json::Value::Array(cleaned)
}
other => other.clone(),
}
}
fn collection_to_json(models: Vec<Self>, options: Option<HashMap<String, String>>) -> serde_json::Value
where
Self: serde::Serialize,
{
let items: Vec<serde_json::Value> = models
.iter()
.map(|model| model.to_json(options.clone()))
.collect();
serde_json::Value::Array(items)
}
fn to_hash_map(&self) -> HashMap<String, String>
where
Self: serde::Serialize,
{
let json = self.to_json(None);
let mut map = HashMap::new();
if let Some(obj) = json.as_object() {
for (key, value) in obj {
if key != "params" {
let str_val = match value {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "null".to_string(),
_ => value.to_string(),
};
map.insert(key.clone(), str_val);
}
}
}
map
}
fn load_language_translations(&mut self, _language: &str) -> std::result::Result<(), String> {
if !Self::has_translations() {
return Ok(()); }
Ok(())
}
fn load_all_translations(&mut self) -> std::result::Result<(), String> {
if !Self::has_translations() {
return Ok(()); }
Ok(())
}
fn extract_translations(data: &mut HashMap<String, serde_json::Value>) -> std::result::Result<serde_json::Value, String> {
if !Self::has_translations() {
return Ok(serde_json::json!({}));
}
let mut translations = serde_json::Map::new();
let translatable = Self::translatable_fields();
for field in translatable {
if let Some(value) = data.get(field) {
if value.is_object() {
translations.insert(field.to_string(), value.clone());
data.remove(field);
}
}
}
Ok(serde_json::Value::Object(translations))
}
fn get_files_attribute(&self) -> std::result::Result<HashMap<String, serde_json::Value>, String> {
if !Self::has_file_attachments() {
return Ok(HashMap::new());
}
Ok(HashMap::new())
}
fn set_files_attribute(&mut self, _files: HashMap<String, serde_json::Value>) -> std::result::Result<(), String> {
if !Self::has_file_attachments() {
return Ok(());
}
Ok(())
}
fn attach_file(&mut self, relation_type: &str, file_key: &str) -> std::result::Result<(), String> {
if !Self::has_file_attachments() {
return Err("Model does not support file attachments".to_string());
}
let mut files = self.get_files_attribute()?;
let file_metadata = serde_json::json!({
"key": file_key,
"filename": file_key.split('/').next_back().unwrap_or(file_key),
"created_at": chrono::Utc::now().to_rfc3339(),
});
if Self::has_one_attached_file().contains(&relation_type) {
files.insert(relation_type.to_string(), file_metadata);
} else if Self::has_many_attached_files().contains(&relation_type) {
let mut array = files.get(relation_type)
.and_then(|v| v.as_array().cloned())
.unwrap_or_default();
array.push(file_metadata);
files.insert(relation_type.to_string(), serde_json::Value::Array(array));
} else {
return Err(format!("Unknown file relation: {}", relation_type));
}
self.set_files_attribute(files)?;
Ok(())
}
fn attach_files(&mut self, relation_type: &str, file_keys: Vec<&str>) -> std::result::Result<(), String> {
if !Self::has_file_attachments() {
return Err("Model does not support file attachments".to_string());
}
if !Self::has_many_attached_files().contains(&relation_type) {
return Err(format!("Relation '{}' is not a hasMany relation", relation_type));
}
for file_key in file_keys {
self.attach_file(relation_type, file_key)?;
}
Ok(())
}
fn detach_file(&mut self, relation_type: &str, file_key: Option<&str>) -> std::result::Result<(), String> {
if !Self::has_file_attachments() {
return Err("Model does not support file attachments".to_string());
}
let mut files = self.get_files_attribute()?;
if let Some(key) = file_key {
if Self::has_one_attached_file().contains(&relation_type) {
if let Some(current) = files.get(relation_type) {
if current.get("key").and_then(|k| k.as_str()) == Some(key) {
files.insert(relation_type.to_string(), serde_json::Value::Null);
}
}
} else if Self::has_many_attached_files().contains(&relation_type) {
if let Some(array) = files.get(relation_type).and_then(|v| v.as_array()) {
let filtered: Vec<serde_json::Value> = array.iter()
.filter(|item| item.get("key").and_then(|k| k.as_str()) != Some(key))
.cloned()
.collect();
files.insert(relation_type.to_string(), serde_json::Value::Array(filtered));
}
}
} else if Self::has_one_attached_file().contains(&relation_type) {
files.insert(relation_type.to_string(), serde_json::Value::Null);
} else if Self::has_many_attached_files().contains(&relation_type) {
files.insert(relation_type.to_string(), serde_json::Value::Array(vec![]));
}
self.set_files_attribute(files)?;
Ok(())
}
fn sync_files(&mut self, relation_type: &str, file_keys: Vec<&str>) -> std::result::Result<(), String> {
if !Self::has_file_attachments() {
return Err("Model does not support file attachments".to_string());
}
let mut files = self.get_files_attribute()?;
if file_keys.is_empty() {
if Self::has_one_attached_file().contains(&relation_type) {
files.insert(relation_type.to_string(), serde_json::Value::Null);
} else if Self::has_many_attached_files().contains(&relation_type) {
files.insert(relation_type.to_string(), serde_json::Value::Array(vec![]));
}
} else if Self::has_one_attached_file().contains(&relation_type) {
let file_metadata = serde_json::json!({
"key": file_keys[0],
"filename": file_keys[0].split('/').next_back().unwrap_or(file_keys[0]),
"created_at": chrono::Utc::now().to_rfc3339(),
});
files.insert(relation_type.to_string(), file_metadata);
} else if Self::has_many_attached_files().contains(&relation_type) {
let file_array: Vec<serde_json::Value> = file_keys.iter().map(|key| {
serde_json::json!({
"key": key,
"filename": key.split('/').next_back().unwrap_or(key),
"created_at": chrono::Utc::now().to_rfc3339(),
})
}).collect();
files.insert(relation_type.to_string(), serde_json::Value::Array(file_array));
} else {
return Err(format!("Unknown file relation: {}", relation_type));
}
self.set_files_attribute(files)?;
Ok(())
}
fn extract_files(data: &mut HashMap<String, serde_json::Value>) -> std::result::Result<serde_json::Value, String> {
if !Self::has_file_attachments() {
return Ok(serde_json::json!({}));
}
let mut files = serde_json::Map::new();
let file_relations = Self::files_relations();
for relation in file_relations {
if let Some(value) = data.remove(relation) {
files.insert(relation.to_string(), value);
}
}
Ok(serde_json::Value::Object(files))
}
}
pub struct CreateBuilder<M: ModelMeta> {
_marker: std::marker::PhantomData<M>,
values: std::collections::HashMap<String, serde_json::Value>,
}
impl<M: ModelMeta> CreateBuilder<M> {
pub fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
values: std::collections::HashMap::new(),
}
}
pub fn set(mut self, field: &str, value: impl Into<serde_json::Value>) -> Self {
self.values.insert(field.to_string(), value.into());
self
}
}
impl<M: ModelMeta> Default for CreateBuilder<M> {
fn default() -> Self {
Self::new()
}
}
pub struct UpdateBuilder<M: Model> {
model: M,
changes: std::collections::HashMap<String, serde_json::Value>,
}
impl<M: Model> UpdateBuilder<M> {
pub fn new(model: M) -> Self {
Self {
model,
changes: std::collections::HashMap::new(),
}
}
pub fn set(mut self, field: &str, value: impl Into<serde_json::Value>) -> Self {
self.changes.insert(field.to_string(), value.into());
self
}
pub async fn save(self) -> Result<M> {
self.model.update().await
}
}
#[doc(hidden)]
pub struct OnConflictBuilder<M: Model> {
pub _marker: std::marker::PhantomData<M>,
pub conflict_columns: Vec<String>,
pub update_columns: Option<Vec<String>>,
pub exclude_columns: Option<Vec<String>>,
}
impl<M: Model> OnConflictBuilder<M> {
pub fn new(conflict_columns: Vec<String>) -> Self {
Self {
_marker: std::marker::PhantomData,
conflict_columns,
update_columns: None,
exclude_columns: None,
}
}
pub fn update_columns(mut self, columns: Vec<&str>) -> Self {
self.update_columns = Some(columns.into_iter().map(|s| s.to_string()).collect());
self
}
pub fn update_all_except(mut self, columns: Vec<&str>) -> Self {
self.exclude_columns = Some(columns.into_iter().map(|s| s.to_string()).collect());
self
}
pub async fn insert(self, model: M) -> Result<M>
where
M: Sized,
{
M::__insert_with_conflict(model, self).await
}
}
pub struct BatchUpdateBuilder<M: Model> {
_marker: std::marker::PhantomData<M>,
updates: std::collections::HashMap<String, UpdateValue>,
conditions: Vec<crate::query::WhereCondition>,
returning: bool,
limit_value: Option<u64>,
}
#[derive(Debug, Clone)]
pub enum UpdateValue {
Value(serde_json::Value),
Raw(String),
Increment(i64),
Decrement(i64),
Multiply(f64),
Divide(f64),
ArrayAppend(serde_json::Value),
ArrayRemove(serde_json::Value),
JsonSet(String, serde_json::Value),
Coalesce(serde_json::Value),
}
impl<M: Model> BatchUpdateBuilder<M> {
pub fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
updates: std::collections::HashMap::new(),
conditions: Vec::new(),
returning: false,
limit_value: None,
}
}
pub fn set(mut self, field: &str, value: impl Into<serde_json::Value>) -> Self {
self.updates.insert(field.to_string(), UpdateValue::Value(value.into()));
self
}
pub fn set_raw(mut self, field: &str, expression: &str) -> Self {
self.updates.insert(field.to_string(), UpdateValue::Raw(expression.to_string()));
self
}
pub fn set_if<F>(mut self, field: &str, value: impl Into<serde_json::Value>, condition: F) -> Self
where
F: FnOnce(Self) -> Self,
{
self.updates.insert(field.to_string(), UpdateValue::Value(value.into()));
condition(self)
}
pub fn increment(mut self, field: &str, by: i64) -> Self {
self.updates.insert(field.to_string(), UpdateValue::Increment(by));
self
}
pub fn decrement(mut self, field: &str, by: i64) -> Self {
self.updates.insert(field.to_string(), UpdateValue::Decrement(by));
self
}
pub fn multiply(mut self, field: &str, by: f64) -> Self {
self.updates.insert(field.to_string(), UpdateValue::Multiply(by));
self
}
pub fn divide(mut self, field: &str, by: f64) -> Self {
self.updates.insert(field.to_string(), UpdateValue::Divide(by));
self
}
pub fn array_append(mut self, field: &str, value: impl Into<serde_json::Value>) -> Self {
self.updates.insert(field.to_string(), UpdateValue::ArrayAppend(value.into()));
self
}
pub fn array_remove(mut self, field: &str, value: impl Into<serde_json::Value>) -> Self {
self.updates.insert(field.to_string(), UpdateValue::ArrayRemove(value.into()));
self
}
pub fn json_set(mut self, field: &str, path: &str, value: impl Into<serde_json::Value>) -> Self {
self.updates.insert(field.to_string(), UpdateValue::JsonSet(path.to_string(), value.into()));
self
}
pub fn coalesce(mut self, field: &str, default: impl Into<serde_json::Value>) -> Self {
self.updates.insert(field.to_string(), UpdateValue::Coalesce(default.into()));
self
}
pub fn limit(mut self, n: u64) -> Self {
self.limit_value = Some(n);
self
}
pub fn returning(mut self) -> Self {
self.returning = true;
self
}
pub fn where_eq(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::Eq,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn where_not(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::NotEq,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn where_gt(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::Gt,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn where_gte(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::Gte,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn where_lt(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::Lt,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn where_lte(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::Lte,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn where_in<V: Into<serde_json::Value>>(mut self, column: &str, values: Vec<V>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::In,
value: crate::query::ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_not_in<V: Into<serde_json::Value>>(mut self, column: &str, values: Vec<V>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::NotIn,
value: crate::query::ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_null(mut self, column: &str) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::IsNull,
value: crate::query::ConditionValue::None,
});
self
}
pub fn where_not_null(mut self, column: &str) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::IsNotNull,
value: crate::query::ConditionValue::None,
});
self
}
pub fn where_between(mut self, column: &str, min: impl Into<serde_json::Value>, max: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::Between,
value: crate::query::ConditionValue::Range(min.into(), max.into()),
});
self
}
pub fn where_like(mut self, column: &str, pattern: &str) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: column.to_string(),
operator: crate::query::Operator::Like,
value: crate::query::ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
pub fn or_where_eq(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: format!("__OR__{}", column),
operator: crate::query::Operator::Eq,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn or_where_not(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: format!("__OR__{}", column),
operator: crate::query::Operator::NotEq,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn or_where_gt(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: format!("__OR__{}", column),
operator: crate::query::Operator::Gt,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn or_where_lt(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: format!("__OR__{}", column),
operator: crate::query::Operator::Lt,
value: crate::query::ConditionValue::Single(value.into()),
});
self
}
pub fn or_where_in<V: Into<serde_json::Value>>(mut self, column: &str, values: Vec<V>) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: format!("__OR__{}", column),
operator: crate::query::Operator::In,
value: crate::query::ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn or_where_null(mut self, column: &str) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: format!("__OR__{}", column),
operator: crate::query::Operator::IsNull,
value: crate::query::ConditionValue::None,
});
self
}
pub fn or_where_like(mut self, column: &str, pattern: &str) -> Self {
self.conditions.push(crate::query::WhereCondition {
column: format!("__OR__{}", column),
operator: crate::query::Operator::Like,
value: crate::query::ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
fn format_value(v: &serde_json::Value) -> String {
match v {
serde_json::Value::Null => "NULL".to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::String(s) => format!("'{}'", s.replace('\'', "''")),
_ => format!("'{}'", v.to_string().replace('\'', "''")),
}
}
pub async fn execute(self) -> Result<u64> {
if self.updates.is_empty() {
return Ok(0);
}
let db_type = crate::database::require_db()?.backend();
let quote = match db_type {
crate::config::DatabaseType::MySQL | crate::config::DatabaseType::MariaDB => '`',
_ => '"',
};
let set_parts: Vec<String> = self.updates.iter()
.map(|(k, v)| {
let col = format!("{0}{1}{0}", quote, k);
match v {
UpdateValue::Value(val) => {
format!("{} = {}", col, Self::format_value(val))
}
UpdateValue::Raw(expr) => {
format!("{} = {}", col, expr)
}
UpdateValue::Increment(by) => {
format!("{} = {} + {}", col, col, by)
}
UpdateValue::Decrement(by) => {
format!("{} = {} - {}", col, col, by)
}
UpdateValue::Multiply(by) => {
format!("{} = {} * {}", col, col, by)
}
UpdateValue::Divide(by) => {
format!("{} = {} / {}", col, col, by)
}
UpdateValue::ArrayAppend(val) => {
match db_type {
crate::config::DatabaseType::Postgres => {
format!("{} = array_append({}, {})", col, col, Self::format_value(val))
}
crate::config::DatabaseType::MySQL | crate::config::DatabaseType::MariaDB => {
format!("{} = JSON_ARRAY_APPEND({}, '$', {})", col, col, Self::format_value(val))
}
crate::config::DatabaseType::SQLite => {
format!("{} = json_insert({}, '$[#]', {})", col, col, Self::format_value(val))
}
}
}
UpdateValue::ArrayRemove(val) => {
match db_type {
crate::config::DatabaseType::Postgres => {
format!("{} = array_remove({}, {})", col, col, Self::format_value(val))
}
crate::config::DatabaseType::MySQL | crate::config::DatabaseType::MariaDB => {
format!("{} = JSON_REMOVE({}, JSON_UNQUOTE(JSON_SEARCH({}, 'one', {})))", col, col, col, Self::format_value(val))
}
crate::config::DatabaseType::SQLite => {
format!("{} = (SELECT json_group_array(value) FROM json_each({}) WHERE value != {})", col, col, Self::format_value(val))
}
}
}
UpdateValue::JsonSet(path, val) => {
match db_type {
crate::config::DatabaseType::Postgres => {
format!("{} = jsonb_set({}, '{{{}}}', '{}')", col, col, path.trim_start_matches("$."), Self::format_value(val).trim_matches('\''))
}
crate::config::DatabaseType::MySQL | crate::config::DatabaseType::MariaDB => {
format!("{} = JSON_SET({}, '{}', {})", col, col, path, Self::format_value(val))
}
crate::config::DatabaseType::SQLite => {
format!("{} = json_set({}, '{}', {})", col, col, path, Self::format_value(val))
}
}
}
UpdateValue::Coalesce(default) => {
format!("{} = COALESCE({}, {})", col, col, Self::format_value(default))
}
}
})
.collect();
let mut and_parts: Vec<String> = Vec::new();
let mut or_parts: Vec<String> = Vec::new();
for cond in &self.conditions {
let (is_or, actual_column) = if cond.column.starts_with("__OR__") {
(true, cond.column.strip_prefix("__OR__").unwrap_or(&cond.column))
} else {
(false, cond.column.as_str())
};
let col = format!("{0}{1}{0}", quote, actual_column);
let part = match &cond.operator {
crate::query::Operator::Eq => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} = {}", col, Self::format_value(v)))
} else { None }
}
crate::query::Operator::NotEq => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} != {}", col, Self::format_value(v)))
} else { None }
}
crate::query::Operator::Gt => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} > {}", col, Self::format_value(v)))
} else { None }
}
crate::query::Operator::Gte => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} >= {}", col, Self::format_value(v)))
} else { None }
}
crate::query::Operator::Lt => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} < {}", col, Self::format_value(v)))
} else { None }
}
crate::query::Operator::Lte => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} <= {}", col, Self::format_value(v)))
} else { None }
}
crate::query::Operator::In => {
if let crate::query::ConditionValue::List(vals) = &cond.value {
let list = vals.iter().map(Self::format_value).collect::<Vec<_>>().join(", ");
Some(format!("{} IN ({})", col, list))
} else { None }
}
crate::query::Operator::NotIn => {
if let crate::query::ConditionValue::List(vals) = &cond.value {
let list = vals.iter().map(Self::format_value).collect::<Vec<_>>().join(", ");
Some(format!("{} NOT IN ({})", col, list))
} else { None }
}
crate::query::Operator::IsNull => Some(format!("{} IS NULL", col)),
crate::query::Operator::IsNotNull => Some(format!("{} IS NOT NULL", col)),
crate::query::Operator::Between => {
if let crate::query::ConditionValue::Range(min, max) = &cond.value {
Some(format!("{} BETWEEN {} AND {}", col, Self::format_value(min), Self::format_value(max)))
} else { None }
}
crate::query::Operator::Like => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} LIKE {}", col, Self::format_value(v)))
} else { None }
}
_ => None,
};
if let Some(part) = part {
if is_or {
or_parts.push(part);
} else {
and_parts.push(part);
}
}
}
let table = format!("{0}{1}{0}", quote, M::table_name());
let mut sql = format!(
"UPDATE {} SET {}",
table,
set_parts.join(", ")
);
if !and_parts.is_empty() || !or_parts.is_empty() {
sql.push_str(" WHERE ");
let mut where_parts: Vec<String> = Vec::new();
if !and_parts.is_empty() {
where_parts.push(and_parts.join(" AND "));
}
if !or_parts.is_empty() {
let or_clause = if or_parts.len() == 1 {
or_parts[0].clone()
} else {
format!("({})", or_parts.join(" OR "))
};
where_parts.push(or_clause);
}
if and_parts.is_empty() {
sql.push_str(&or_parts.join(" OR "));
} else if or_parts.is_empty() {
sql.push_str(&and_parts.join(" AND "));
} else {
sql.push_str(&format!("{} AND ({})", and_parts.join(" AND "), or_parts.join(" OR ")));
}
}
if let Some(limit) = self.limit_value {
if matches!(db_type, crate::config::DatabaseType::MySQL | crate::config::DatabaseType::MariaDB) {
sql.push_str(&format!(" LIMIT {}", limit));
}
}
crate::Database::execute(&sql).await
}
pub async fn execute_returning(self) -> Result<Vec<M>> {
if self.updates.is_empty() {
return Ok(vec![]);
}
let db_type = crate::database::require_db()?.backend();
if db_type == crate::config::DatabaseType::MySQL {
return Err(Error::query("MySQL does not support RETURNING clause".to_string()));
}
let quote = match db_type {
crate::config::DatabaseType::MySQL | crate::config::DatabaseType::MariaDB => '`',
_ => '"',
};
let set_parts: Vec<String> = self.updates.iter()
.map(|(k, v)| {
let col = format!("{0}{1}{0}", quote, k);
match v {
UpdateValue::Value(val) => format!("{} = {}", col, Self::format_value(val)),
UpdateValue::Raw(expr) => format!("{} = {}", col, expr),
UpdateValue::Increment(by) => format!("{} = {} + {}", col, col, by),
UpdateValue::Decrement(by) => format!("{} = {} - {}", col, col, by),
UpdateValue::Multiply(by) => format!("{} = {} * {}", col, col, by),
UpdateValue::Divide(by) => format!("{} = {} / {}", col, col, by),
_ => format!("{} = {}", col, col), }
})
.collect();
let where_parts: Vec<String> = self.conditions.iter()
.filter_map(|cond| {
let col = format!("{0}{1}{0}", quote, cond.column);
match &cond.operator {
crate::query::Operator::Eq => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} = {}", col, Self::format_value(v)))
} else { None }
}
crate::query::Operator::NotEq => {
if let crate::query::ConditionValue::Single(v) = &cond.value {
Some(format!("{} != {}", col, Self::format_value(v)))
} else { None }
}
_ => None,
}
})
.collect();
let table = format!("{0}{1}{0}", quote, M::table_name());
let mut sql = format!("UPDATE {} SET {}", table, set_parts.join(", "));
if !where_parts.is_empty() {
sql.push_str(" WHERE ");
sql.push_str(&where_parts.join(" AND "));
}
sql.push_str(" RETURNING *");
crate::Database::raw::<M>(&sql).await
}
}
impl<M: Model> Default for BatchUpdateBuilder<M> {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
pub trait NestedSave: Model {
async fn save_with_one<R: Model>(
self,
related: R,
foreign_key: &str,
) -> Result<(Self, R)>
where
Self: Sized,
{
let parent = self.save().await?;
let pk_value = {
let pk = parent.primary_key();
serde_json::Value::String(format!("{}", pk))
};
let mut related_json = serde_json::to_value(&related)
.map_err(|e| Error::conversion(format!("Failed to serialize related model: {}", e)))?;
if let serde_json::Value::Object(ref mut map) = related_json {
let pk_str = pk_value.as_str().unwrap_or_default();
if let Ok(pk_i64) = pk_str.parse::<i64>() {
map.insert(foreign_key.to_string(), serde_json::json!(pk_i64));
} else {
map.insert(foreign_key.to_string(), pk_value);
}
}
let related: R = serde_json::from_value(related_json)
.map_err(|e| Error::conversion(format!("Failed to deserialize related model: {}", e)))?;
let related = related.save().await?;
Ok((parent, related))
}
async fn save_with_many<R: Model>(
self,
related: Vec<R>,
foreign_key: &str,
) -> Result<(Self, Vec<R>)>
where
Self: Sized,
{
let parent = self.save().await?;
let pk_value = {
let pk = parent.primary_key();
serde_json::Value::String(format!("{}", pk))
};
let mut saved_related = Vec::with_capacity(related.len());
for item in related {
let mut item_json = serde_json::to_value(&item)
.map_err(|e| Error::conversion(format!("Failed to serialize related model: {}", e)))?;
if let serde_json::Value::Object(ref mut map) = item_json {
let pk_str = pk_value.as_str().unwrap_or_default();
if let Ok(pk_i64) = pk_str.parse::<i64>() {
map.insert(foreign_key.to_string(), serde_json::json!(pk_i64));
} else {
map.insert(foreign_key.to_string(), pk_value.clone());
}
}
let item: R = serde_json::from_value(item_json)
.map_err(|e| Error::conversion(format!("Failed to deserialize related model: {}", e)))?;
let saved = item.save().await?;
saved_related.push(saved);
}
Ok((parent, saved_related))
}
async fn update_with_one<R: Model>(
self,
related: R,
) -> Result<(Self, R)>
where
Self: Sized,
{
let parent = self.update().await?;
let related = related.update().await?;
Ok((parent, related))
}
async fn update_with_many<R: Model>(
self,
related: Vec<R>,
) -> Result<(Self, Vec<R>)>
where
Self: Sized,
{
let parent = self.update().await?;
let mut updated = Vec::with_capacity(related.len());
for item in related {
updated.push(item.update().await?);
}
Ok((parent, updated))
}
async fn delete_with_many<R: Model>(
self,
related: Vec<R>,
) -> Result<u64>
where
Self: Sized,
{
let mut total = 0u64;
for item in related {
total += item.delete().await?;
}
total += self.delete().await?;
Ok(total)
}
}
impl<M: Model> NestedSave for M {}
pub struct NestedSaveBuilder<M: Model> {
parent: M,
one_relations: Vec<(serde_json::Value, String)>,
many_relations: Vec<(Vec<serde_json::Value>, String)>,
}
impl<M: Model> NestedSaveBuilder<M> {
pub fn new(parent: M) -> Self {
Self {
parent,
one_relations: Vec::new(),
many_relations: Vec::new(),
}
}
pub fn with_one<R: Model>(mut self, related: R, foreign_key: &str) -> Self {
if let Ok(json) = serde_json::to_value(&related) {
self.one_relations.push((json, foreign_key.to_string()));
}
self
}
pub fn with_many<R: Model>(mut self, related: Vec<R>, foreign_key: &str) -> Self {
let json_values: Vec<serde_json::Value> = related.into_iter()
.filter_map(|r| serde_json::to_value(&r).ok())
.collect();
self.many_relations.push((json_values, foreign_key.to_string()));
self
}
pub async fn save(self) -> Result<(M, Vec<serde_json::Value>)> {
let parent = self.parent.save().await?;
let pk_value = {
let pk = parent.primary_key();
serde_json::Value::String(format!("{}", pk))
};
let mut saved_json = Vec::new();
for (mut json, fk) in self.one_relations {
if let serde_json::Value::Object(ref mut map) = json {
let pk_str = pk_value.as_str().unwrap_or_default();
if let Ok(pk_i64) = pk_str.parse::<i64>() {
map.insert(fk, serde_json::json!(pk_i64));
} else {
map.insert(fk, pk_value.clone());
}
}
saved_json.push(json);
}
for (items, fk) in self.many_relations {
for mut json in items {
if let serde_json::Value::Object(ref mut map) = json {
let pk_str = pk_value.as_str().unwrap_or_default();
if let Ok(pk_i64) = pk_str.parse::<i64>() {
map.insert(fk.clone(), serde_json::json!(pk_i64));
} else {
map.insert(fk.clone(), pk_value.clone());
}
}
saved_json.push(json);
}
}
Ok((parent, saved_json))
}
}