diff options
Diffstat (limited to 'server/src/db_utils.rs')
| -rw-r--r-- | server/src/db_utils.rs | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/server/src/db_utils.rs b/server/src/db_utils.rs new file mode 100644 index 0000000..e74fd68 --- /dev/null +++ b/server/src/db_utils.rs @@ -0,0 +1,138 @@ +#![allow(dead_code)] + +use sqlx::database::HasArguments; +use sqlx::encode::Encode; +use sqlx::types::Type; +use sqlx::{Arguments, Database}; +use std::fmt::Display; + +pub struct UpdateBuilder<'args, DB> +where + DB: Database, +{ + table: Option<String>, + names: Option<Vec<(String, String)>>, + where_: Option<Vec<(String, String, String)>>, + values: Option<<DB as HasArguments<'args>>::Arguments>, +} + +impl<'args, DB: Database> UpdateBuilder<'args, DB> +where + DB: Database, +{ + pub fn new() -> Self + where + <DB as HasArguments<'args>>::Arguments: Default, + { + UpdateBuilder { + table: None, + names: Some(Vec::new()), + where_: Some(Vec::new()), + values: Some(Default::default()), + } + } + + #[inline] + fn sanity_check(&self) { + assert!( + self.values.is_some(), + "UpdateBuilder must be reset before reuse after `.build()`" + ); + } + + pub fn table(&mut self, table: impl Display) -> &mut Self { + self.sanity_check(); + + self.table = Some(format!("{table}")); + + self + } + + pub fn and_where<T>(&mut self, name: impl Display, op: impl Display, value: T) -> &mut Self + where + T: 'args + Encode<'args, DB> + Send + Type<DB>, + { + self.sanity_check(); + + let values = self.values.as_mut().expect("BUG: Values taken already"); + values.add(value); + + let mut placeholder = String::new(); + values + .format_placeholder(&mut placeholder) + .expect("error in format_placeholder"); + + let where_ = self.where_.as_mut().expect("BUG: Where taken already"); + where_.push((format!("{name}"), format!("{op}"), placeholder)); + + self + } + + pub fn set<T>(&mut self, name: impl Display, value: T) -> &mut Self + where + T: 'args + Encode<'args, DB> + Send + Type<DB>, + { + self.sanity_check(); + + assert!( + self.where_.as_ref().unwrap().is_empty(), + "set must not be called after add_where" + ); + + let names = self.names.as_mut().expect("BUG: Names taken already"); + let values = self.values.as_mut().expect("BUG: Values taken already"); + values.add(value); + + let mut placeholder = String::new(); + values + .format_placeholder(&mut placeholder) + .expect("error in format_placeholder"); + names.push((format!("{name}"), placeholder)); + + self + } + + pub fn build(&mut self) -> (String, <DB as HasArguments<'args>>::Arguments) { + self.sanity_check(); + + let table = self.table.take().unwrap(); + let mut query = format!("UPDATE {table} SET"); + let mut first = true; + for (name, placeholder) in self.names.take().unwrap() { + if first { + first = false; + } else { + query.push(','); + } + query.push_str(&format!(" {name}={placeholder}")); + } + + let where_ = self.where_.take().unwrap(); + if !where_.is_empty() { + query.push_str(" WHERE"); + + first = true; + for (name, op, placeholder) in where_ { + if first { + first = false; + } else { + query.push_str(" AND"); + } + query.push_str(&format!(" {name}{op}{placeholder}")); + } + } + + // TODO: This method should return a Query, constructed by QueryBuilder, but I can't + // figure out how to create QueryBuilder::with_arguments in this generic method, + // where DB isn't "known". + (query, self.values.take().unwrap()) + } + + pub fn reset(&mut self) -> &mut Self { + self.names = Some(Vec::new()); + self.where_ = Some(Vec::new()); + self.values = Some(Default::default()); + + self + } +} |
