use std::fmt::Write;
use crate::conditional::{BuildCondition, Condition};
#[cfg(feature = "mysql")]
use crate::db_specific::mysql;
#[cfg(feature = "postgres")]
use crate::db_specific::postgres;
#[cfg(feature = "sqlite")]
use crate::db_specific::sqlite;
use crate::error::Error;
use crate::value::NullType;
use crate::{DBImpl, OnConflict, Value};
pub trait Update<'until_build, 'post_build> {
fn rollback_transaction(self) -> Self;
fn where_clause(self, condition: &'until_build Condition<'post_build>) -> Self;
fn add_update(self, column_name: &'until_build str, column_value: Value<'post_build>) -> Self;
fn build(self) -> Result<(String, Vec<Value<'post_build>>), Error>;
}
#[derive(Debug)]
pub struct UpdateData<'until_build, 'post_build> {
pub(crate) model: &'until_build str,
pub(crate) on_conflict: OnConflict,
pub(crate) updates: Vec<(&'until_build str, Value<'post_build>)>,
pub(crate) where_clause: Option<&'until_build Condition<'post_build>>,
pub(crate) lookup: Vec<Value<'post_build>>,
}
#[derive(Debug)]
pub enum UpdateImpl<'until_build, 'post_build> {
#[cfg(feature = "sqlite")]
SQLite(UpdateData<'until_build, 'post_build>),
#[cfg(feature = "mysql")]
MySQL(UpdateData<'until_build, 'post_build>),
#[cfg(feature = "postgres")]
Postgres(UpdateData<'until_build, 'post_build>),
}
impl<'until_build, 'post_build> Update<'until_build, 'post_build>
for UpdateImpl<'until_build, 'post_build>
{
fn rollback_transaction(mut self) -> Self {
match self {
#[cfg(feature = "sqlite")]
UpdateImpl::SQLite(ref mut d) => d.on_conflict = OnConflict::ROLLBACK,
#[cfg(feature = "mysql")]
UpdateImpl::MySQL(ref mut d) => d.on_conflict = OnConflict::ROLLBACK,
#[cfg(feature = "postgres")]
UpdateImpl::Postgres(ref mut d) => d.on_conflict = OnConflict::ROLLBACK,
};
self
}
fn where_clause(mut self, condition: &'until_build Condition<'post_build>) -> Self {
match self {
#[cfg(feature = "sqlite")]
UpdateImpl::SQLite(ref mut d) => d.where_clause = Some(condition),
#[cfg(feature = "mysql")]
UpdateImpl::MySQL(ref mut d) => d.where_clause = Some(condition),
#[cfg(feature = "postgres")]
UpdateImpl::Postgres(ref mut d) => d.where_clause = Some(condition),
};
self
}
fn add_update(
mut self,
column_name: &'until_build str,
column_value: Value<'post_build>,
) -> Self {
match self {
#[cfg(feature = "sqlite")]
UpdateImpl::SQLite(ref mut d) => d.updates.push((column_name, column_value)),
#[cfg(feature = "mysql")]
UpdateImpl::MySQL(ref mut d) => d.updates.push((column_name, column_value)),
#[cfg(feature = "postgres")]
UpdateImpl::Postgres(ref mut d) => d.updates.push((column_name, column_value)),
};
self
}
fn build(self) -> Result<(String, Vec<Value<'post_build>>), Error> {
match self {
#[cfg(feature = "sqlite")]
UpdateImpl::SQLite(mut d) => {
if d.updates.is_empty() {
return Err(Error::SQLBuildError(String::from(
"There must be at least one update in an UPDATE statement",
)));
}
let mut s = format!(
"UPDATE {}{} SET ",
match d.on_conflict {
OnConflict::ABORT => "OR ABORT ",
OnConflict::ROLLBACK => "OR ROLLBACK ",
},
d.model,
);
let update_index = d.updates.len() - 1;
for (idx, (name, value)) in d.updates.into_iter().enumerate() {
if let Value::Choice(c) = value {
write!(s, "\"{name}\" = {}", sqlite::fmt(c)).unwrap();
} else if let Value::Null(NullType::Choice) = value {
write!(s, "\"{name}\" = NULL").unwrap();
} else {
write!(s, "\"{name}\" = ?").unwrap();
d.lookup.push(value);
}
if idx != update_index {
write!(s, ", ").unwrap();
}
}
if let Some(condition) = d.where_clause {
write!(
s,
" WHERE {}",
condition.build(DBImpl::SQLite, &mut d.lookup)
)
.unwrap();
}
write!(s, ";").unwrap();
Ok((s, d.lookup))
}
#[cfg(feature = "mysql")]
UpdateImpl::MySQL(mut d) => {
if d.updates.is_empty() {
return Err(Error::SQLBuildError(String::from(
"There must be at least one update in an UPDATE statement",
)));
}
let mut s = format!(
"UPDATE {}{} SET ",
match d.on_conflict {
OnConflict::ABORT => "OR ABORT ",
OnConflict::ROLLBACK => "OR ROLLBACK ",
},
d.model,
);
let update_index = d.updates.len() - 1;
for (idx, (name, value)) in d.updates.into_iter().enumerate() {
if let Value::Choice(c) = value {
write!(s, "`{name}` = {}", mysql::fmt(c)).unwrap();
} else if let Value::Null(NullType::Choice) = value {
write!(s, "`{name}` = NULL").unwrap();
} else {
write!(s, "`{name}` = ?").unwrap();
d.lookup.push(value);
}
if idx != update_index {
write!(s, ", ").unwrap();
}
}
if let Some(condition) = d.where_clause {
write!(
s,
" WHERE {}",
condition.build(DBImpl::MySQL, &mut d.lookup)
)
.unwrap();
}
write!(s, ";").unwrap();
Ok((s, d.lookup))
}
#[cfg(feature = "postgres")]
UpdateImpl::Postgres(mut d) => {
if d.updates.is_empty() {
return Err(Error::SQLBuildError(String::from(
"There must be at least one update in an UPDATE statement",
)));
}
let mut s = format!("UPDATE \"{}\" SET ", d.model);
let update_index = d.updates.len() - 1;
for (idx, (name, value)) in d.updates.into_iter().enumerate() {
if let Value::Choice(c) = value {
write!(s, "\"{name}\" = {}", postgres::fmt(c)).unwrap();
} else if let Value::Null(NullType::Choice) = value {
write!(s, "\"{name}\" = NULL").unwrap();
} else {
d.lookup.push(value);
write!(s, "\"{name}\" = ${}", d.lookup.len()).unwrap();
}
if idx != update_index {
write!(s, ", ").unwrap();
}
}
if let Some(condition) = d.where_clause {
write!(
s,
" WHERE {}",
condition.build(DBImpl::Postgres, &mut d.lookup)
)
.unwrap();
}
write!(s, ";").unwrap();
Ok((s, d.lookup))
}
}
}
}