use std::future::Future;
use super::{DateTime, Db, DbValue, Executor, ModelKey, Query, ToDbValue, now, quote, sql};
use crate::{Error, Result};
use anyhow::anyhow;
pub trait Model: super::FromRow + Sized + Send + Sync + Unpin + 'static {
const TABLE: &'static str;
const COLUMNS: &'static [&'static str];
const SOFT_DELETES: bool = false;
const SELECT_ALL: bool = false;
const SEARCHABLE: &'static [&'static str] = &[];
const SEARCH_LANGUAGE: &'static str = "english";
type Key: ModelKey;
fn id(&self) -> Self::Key;
#[doc(hidden)]
fn set_id(&mut self, id: Self::Key);
#[doc(hidden)]
fn values(&self) -> Vec<DbValue>;
#[doc(hidden)]
fn touch(&mut self, _now: DateTime, _creating: bool) {}
#[doc(hidden)]
fn set_deleted_at(&mut self, _at: Option<DateTime>) {}
#[doc(hidden)]
fn forget_timestamps(&mut self) {}
fn replicate(&self) -> Self
where
Self: Clone,
{
let mut copy = self.clone();
copy.set_id(Self::Key::default());
copy.set_deleted_at(None);
copy.forget_timestamps();
copy
}
fn default_scope(query: Query<Self>) -> Query<Self> {
query
}
fn saving(&mut self, _creating: bool) -> Result {
Ok(())
}
fn saved(&self, _created: bool) -> impl Future<Output = Result> + Send {
async { Ok(()) }
}
fn deleting(&self) -> Result {
Ok(())
}
fn deleted(&self) -> impl Future<Output = Result> + Send {
async { Ok(()) }
}
fn query() -> Query<Self> {
Self::default_scope(Query::new())
}
fn unscoped() -> Query<Self> {
Query::new()
}
fn refresh(&mut self, db: &Db) -> impl Future<Output = Result<()>> + Send {
async move {
let id = self.id();
*self = Self::unscoped()
.with_trashed()
.where_eq("id", id)
.first_or_404(db)
.await?;
Ok(())
}
}
fn search(words: &str) -> Query<Self> {
Self::query().search(words)
}
fn where_eq(column: &str, value: impl ToDbValue) -> Query<Self> {
Self::query().where_eq(column, value)
}
fn all<'c, E: Executor<'c>>(db: E) -> impl Future<Output = Result<Vec<Self>>> + Send {
Self::query().order_by("id").get(db)
}
fn find<'c, E: Executor<'c>>(
db: E,
id: Self::Key,
) -> impl Future<Output = Result<Option<Self>>> + Send {
Self::query().where_eq("id", id).first(db)
}
fn find_many<'c, E: Executor<'c>>(
db: E,
ids: impl IntoIterator<Item = Self::Key>,
) -> impl Future<Output = Result<Vec<Self>>> + Send {
let ids: Vec<Self::Key> = ids.into_iter().collect();
Self::query().where_in("id", ids).order_by("id").get(db)
}
fn insert_many<'c, E: Executor<'c>>(
db: E,
models: Vec<Self>,
) -> impl Future<Output = Result<u64>> + Send {
async move { write_many::<Self>(db.into_conn(), models, None).await }
}
fn upsert<'c, E: Executor<'c>>(
db: E,
models: Vec<Self>,
unique_by: &[&str],
update: &[&str],
) -> impl Future<Output = Result<u64>> + Send {
let unique_by: Vec<String> = unique_by.iter().map(|c| (*c).to_owned()).collect();
let update: Vec<String> = update.iter().map(|c| (*c).to_owned()).collect();
async move {
let key_target = !<Self::Key as ModelKey>::AUTO_INCREMENT;
for column in unique_by.iter().chain(&update) {
let id_allowed =
key_target && unique_by.contains(column) && !update.contains(column);
if !Self::COLUMNS.contains(&column.as_str()) || (column == "id" && !id_allowed) {
return Err(
anyhow!("`{}` has no column `{column}` to upsert", Self::TABLE).into(),
);
}
}
if unique_by.is_empty() {
return Err(anyhow!("upsert needs at least one `unique_by` column").into());
}
write_many::<Self>(db.into_conn(), models, Some((unique_by, update))).await
}
}
fn find_or_404<'c, E: Executor<'c>>(
db: E,
id: Self::Key,
) -> impl Future<Output = Result<Self>> + Send {
let found = Self::find(db, id);
async move { found.await?.ok_or(Error::NotFound) }
}
fn create<'c, E: Executor<'c>>(
db: E,
mut model: Self,
) -> impl Future<Output = Result<Self>> + Send {
async move {
model.insert(db).await?;
Ok(model)
}
}
fn insert<'c, E: Executor<'c>>(&mut self, db: E) -> impl Future<Output = Result> + Send {
async move {
self.saving(true)?;
self.touch(now(), true);
if self.id().is_unsaved()
&& let Some(key) = Self::Key::generate()
{
self.set_id(key);
}
let key = self.id();
let mut columns: Vec<String> = Self::COLUMNS
.iter()
.filter(|c| **c != "id")
.map(|c| quote(c))
.collect();
let mut values = self.values();
let table = quote(Self::TABLE);
if key.is_unsaved() {
if !<Self::Key as ModelKey>::AUTO_INCREMENT {
return Err(anyhow!(
"set the `id` of a new {} row before inserting it",
Self::TABLE
)
.into());
}
let sql_text = if columns.is_empty() {
format!("INSERT INTO {table} DEFAULT VALUES RETURNING id")
} else {
let marks = vec!["?"; columns.len()].join(", ");
format!(
"INSERT INTO {table} ({}) VALUES ({marks}) RETURNING id",
columns.join(", ")
)
};
let id: Self::Key = sql(sql_text).bind_all(values).scalar(db).await?;
self.set_id(id);
} else {
columns.insert(0, quote("id"));
values.insert(0, key.to_db_value());
let marks = vec!["?"; columns.len()].join(", ");
sql(format!(
"INSERT INTO {table} ({}) VALUES ({marks})",
columns.join(", ")
))
.bind_all(values)
.execute(db)
.await?;
}
self.saved(true).await
}
}
fn save<'c, E: Executor<'c>>(&mut self, db: E) -> impl Future<Output = Result> + Send {
async move {
if self.id().is_unsaved() {
return self.insert(db).await;
}
self.saving(false)?;
self.touch(now(), false);
let columns: Vec<String> = Self::COLUMNS
.iter()
.filter(|c| **c != "id")
.map(|c| quote(c))
.collect();
let values = self.values();
let table = quote(Self::TABLE);
if columns.is_empty() {
return self.saved(false).await;
}
let sets: Vec<String> = columns.iter().map(|c| format!("{c} = ?")).collect();
let changed = sql(format!(
"UPDATE {table} SET {} WHERE id = ?",
sets.join(", ")
))
.bind_all(values)
.bind(self.id())
.execute(db)
.await?;
if changed == 0 {
return Err(Error::NotFound);
}
self.saved(false).await
}
}
fn save_only<'c, E: Executor<'c>>(
&mut self,
db: E,
columns: &[&str],
) -> impl Future<Output = Result> + Send {
let columns: Vec<String> = columns.iter().map(|c| (*c).to_owned()).collect();
async move {
self.saving(false)?;
update_columns(self, db, columns).await
}
}
fn save_changes<'c, E: Executor<'c>>(
&mut self,
db: E,
original: &Self,
) -> impl Future<Output = Result<bool>> + Send {
let hooked = self.saving(false);
let changed: Vec<String> = Self::COLUMNS
.iter()
.filter(|c| **c != "id")
.zip(self.values().into_iter().zip(original.values()))
.filter(|(_, (now, before))| now != before)
.map(|(column, _)| (*column).to_owned())
.collect();
async move {
hooked?;
if changed.is_empty() {
return Ok(false);
}
update_columns(self, db, changed).await?;
Ok(true)
}
}
fn delete<'c, E: Executor<'c>>(&mut self, db: E) -> impl Future<Output = Result> + Send {
async move {
if !Self::SOFT_DELETES {
return self.force_delete(db).await;
}
self.deleting()?;
let at = now();
sql(format!(
"UPDATE {} SET deleted_at = ? WHERE id = ?",
quote(Self::TABLE)
))
.bind(at)
.bind(self.id())
.execute(db)
.await?;
self.set_deleted_at(Some(at));
self.deleted().await
}
}
fn force_delete<'c, E: Executor<'c>>(&self, db: E) -> impl Future<Output = Result> + Send {
async move {
self.deleting()?;
sql(format!("DELETE FROM {} WHERE id = ?", quote(Self::TABLE)))
.bind(self.id())
.execute(db)
.await?;
self.deleted().await
}
}
fn restore<'c, E: Executor<'c>>(&mut self, db: E) -> impl Future<Output = Result> + Send {
async move {
if !Self::SOFT_DELETES {
return Err(anyhow!("{} does not use soft deletes", Self::TABLE).into());
}
sql(format!(
"UPDATE {} SET deleted_at = NULL WHERE id = ?",
quote(Self::TABLE)
))
.bind(self.id())
.execute(db)
.await?;
self.set_deleted_at(None);
Ok(())
}
}
}
async fn update_columns<'c, M: Model, E: Executor<'c>>(
model: &mut M,
db: E,
columns: Vec<String>,
) -> Result {
if model.id().is_unsaved() {
return Err(anyhow!("save_only on an unsaved {} row", M::TABLE).into());
}
for column in &columns {
if column == "id" || !M::COLUMNS.contains(&column.as_str()) {
return Err(anyhow!("{} has no column `{column}` to save", M::TABLE).into());
}
}
model.touch(now(), false);
let mut sets = Vec::new();
let mut binds = Vec::new();
for (column, value) in M::COLUMNS
.iter()
.filter(|c| **c != "id")
.zip(model.values())
{
if columns.iter().any(|c| c == column) || *column == "updated_at" {
sets.push(format!("{} = ?", quote(column)));
binds.push(value);
}
}
if !sets.is_empty() {
let changed = sql(format!(
"UPDATE {} SET {} WHERE id = ?",
quote(M::TABLE),
sets.join(", ")
))
.bind_all(binds)
.bind(model.id())
.execute(db)
.await?;
if changed == 0 {
return Err(Error::NotFound);
}
}
model.saved(false).await
}
pub trait ModelHooks {
fn saving(&mut self, _creating: bool) -> Result {
Ok(())
}
fn saved(&self, _created: bool) -> impl Future<Output = Result> + Send {
async { Ok(()) }
}
fn deleting(&self) -> Result {
Ok(())
}
fn deleted(&self) -> impl Future<Output = Result> + Send {
async { Ok(()) }
}
}
const MAX_BINDS: usize = 30_000;
async fn write_many<M: Model>(
mut conn: super::Conn<'_>,
mut models: Vec<M>,
upsert: Option<(Vec<String>, Vec<String>)>,
) -> Result<u64> {
let at = now();
let with_keys = !<M::Key as super::ModelKey>::AUTO_INCREMENT;
for model in &mut models {
model.touch(at, true);
if with_keys && model.id().is_unsaved() {
match <M::Key as super::ModelKey>::generate() {
Some(key) => model.set_id(key),
None => {
return Err(anyhow!(
"set the `id` of every new {} row before inserting them",
M::TABLE
)
.into());
}
}
}
}
let mut columns: Vec<&str> = M::COLUMNS.iter().copied().filter(|c| *c != "id").collect();
if with_keys {
columns.insert(0, "id");
}
if columns.is_empty() || models.is_empty() {
return Ok(0);
}
let quoted: Vec<String> = columns.iter().map(|c| quote(c)).collect();
let row_marks = format!("({})", vec!["?"; columns.len()].join(", "));
let conflict = upsert.map(|(unique_by, update)| {
let targets: Vec<String> = unique_by.iter().map(|c| quote(c)).collect();
let mut sets: Vec<String> = update
.iter()
.map(|c| format!("{0} = excluded.{0}", quote(c)))
.collect();
if columns.contains(&"updated_at") && !update.iter().any(|c| c == "updated_at") {
sets.push(format!("{0} = excluded.{0}", quote("updated_at")));
}
if sets.is_empty() {
format!(" ON CONFLICT ({}) DO NOTHING", targets.join(", "))
} else {
format!(
" ON CONFLICT ({}) DO UPDATE SET {}",
targets.join(", "),
sets.join(", ")
)
}
});
let per_statement = (MAX_BINDS / columns.len()).max(1);
let mut written = 0;
for chunk in models.chunks(per_statement) {
let marks = vec![row_marks.as_str(); chunk.len()].join(", ");
let statement = format!(
"INSERT INTO {} ({}) VALUES {marks}{}",
quote(M::TABLE),
quoted.join(", "),
conflict.as_deref().unwrap_or_default()
);
let values = chunk.iter().flat_map(|model| {
let key = with_keys.then(|| model.id().to_db_value());
key.into_iter().chain(model.values())
});
written += sql(statement)
.bind_all(values)
.execute(conn.reborrow())
.await?;
}
Ok(written)
}