From b218cd50f2ad3b1ba13c4a0600d27984923fd481 Mon Sep 17 00:00:00 2001 From: Benjamin Sherriff Date: Sun, 22 Dec 2024 16:59:35 -0500 Subject: [PATCH] Fixed query building and dice track --- bruno/oauth/Create API Key.bru | 2 +- src/api/auth/api_key.rs | 11 ++- src/api/dice/mod.rs | 52 +++++++---- src/api/mod.rs | 1 - src/data/condition.rs | 164 +++++++++++++++++++++++++++------ src/data/executable_query.rs | 105 +++++++++++++++++++++ src/data/guilds/model.rs | 1 + src/data/insert.rs | 38 +------- src/data/mod.rs | 3 +- src/data/query.rs | 47 +--------- src/data/update.rs | 40 ++------ 11 files changed, 304 insertions(+), 160 deletions(-) create mode 100644 src/data/executable_query.rs diff --git a/bruno/oauth/Create API Key.bru b/bruno/oauth/Create API Key.bru index 98fc8b7..888c34e 100644 --- a/bruno/oauth/Create API Key.bru +++ b/bruno/oauth/Create API Key.bru @@ -11,5 +11,5 @@ post { } auth:bearer { - token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjI1MDg0MjI2MTIyMTI3NzY5NywibmFtZSI6ImJzaGVycmlmZiIsImlhdCI6MTczNDg0MzA0NywiZXhwIjoxNzM0OTI5NDQ3LCJqdGkiOiIycEowbmN5YmF1TVo4TG1aQ0VwU1B2OWgzMXFzU1FwaCJ9.gYf6oAm2POBXOHUnG4dTy5maKxTjUk8WxawOrIafjEE + token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjI1MDg0MjI2MTIyMTI3NzY5NywibmFtZSI6ImJzaGVycmlmZiIsImlhdCI6MTczNDkwMjgzOSwiZXhwIjoxNzM0OTg5MjM5LCJqdGkiOiJWTlFjeXpBN25sZEt1SWtzcDFzc1pRNHNacUZ2dWZPZCJ9.JnO-Rklv9YZKWjRvehR4-tfP1dlO5vIEWpSh_W4xZWY } diff --git a/src/api/auth/api_key.rs b/src/api/auth/api_key.rs index 1a477db..2b7d217 100644 --- a/src/api/auth/api_key.rs +++ b/src/api/auth/api_key.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::api::auth::{csprng, AuthCredential}; use crate::api::auth::AuthorizationMiddleware; use crate::AppState; +use crate::data::ExecutableQuery; use crate::data::condition::Condition; use crate::data::insert::InsertBuilder; use crate::data::query::QueryBuilder; @@ -52,7 +53,8 @@ impl ApiKey { .column("access_mask", Value::Int(self.access_mask)) .column("created_at", Value::DateTime(self.created_at)) .column("last_used_at", Value::OptionalDateTime(self.last_used_at)) - .execute().await?; + .execute() + .await?; Ok(()) } @@ -64,7 +66,9 @@ impl ApiKey { .column("created_at", Value::DateTime(self.created_at)) .column("last_used_at", Value::OptionalDateTime(self.last_used_at)) .where_condition(Condition::is_equal("key", Value::Text(self.key.clone()))) - .execute().await { + .execute() + .await + { Ok(_) => Ok(()), Err(err) => { log::error!("error: {}", err); @@ -76,7 +80,8 @@ impl ApiKey { pub async fn find_by_key(key: &str) -> Option { QueryBuilder::new(TABLE_NAME) .where_condition(Condition::is_equal("key", Value::Text(key.to_string()))) - .fetch_optional().await + .fetch_optional() + .await } pub async fn delete_by_id(key: &str) -> SirenResult<()> { diff --git a/src/api/dice/mod.rs b/src/api/dice/mod.rs index e0dff08..f5639e2 100644 --- a/src/api/dice/mod.rs +++ b/src/api/dice/mod.rs @@ -10,6 +10,8 @@ use uuid::Uuid; use crate::api::auth::{AuthCredential, AuthorizationMiddleware}; use crate::AppState; use crate::bot::commands::fun::roll::{format_roll, parse_dice}; +use crate::data::condition::Condition; +use crate::data::{ExecutableQuery, Value}; use crate::data::query::QueryBuilder; use crate::error::{Error, SirenResult}; @@ -95,22 +97,26 @@ struct QueryDiceTrack { } impl QueryDiceTrack { - pub async fn find() -> SirenResult> { - let pool = crate::data::pool(); - let query = QueryBuilder::new(TABLE_NAME) - // .where_condition( - // Condition::and( - // Condition::is_equal("guild_id", "$1"), - // Condition::and( - // Condition::is_equal("owner_id", "$2"), - // - // ) - // ) - // ) - .build(); - let items: Vec = sqlx::query_as(&query.0).fetch_all(pool).await?; - - Ok(items) + pub async fn find(dice: &InsertDiceTrack) -> Option { + QueryBuilder::new(TABLE_NAME) + .where_condition(Condition::and( + Condition::is_equal("guild_id", Value::BigInt(dice.guild_id)), + Condition::and( + Condition::is_equal("owner_id", Value::BigInt(dice.owner_id)), + Condition::and( + Condition::is_equal("dice", Value::Text(dice.dice.clone())), + Condition::and( + Condition::is_equal("user_id", Value::OptionalBigInt(dice.user_id)), + Condition::and( + Condition::is_equal("value", Value::OptionalInt(dice.value)), + Condition::is_equal("operator", Value::OptionalText(dice.operator.clone())), + ), + ), + ), + ), + )) + .fetch_optional() + .await } } @@ -168,7 +174,7 @@ pub async fn add_track_dice( let dice = parse_dice(&payload.dice)?; - let dice = InsertDiceTrack { + let insert_dice = InsertDiceTrack { guild_id: guild_id.get() as i64, owner_id: owner_id.get() as i64, dice: format_roll(dice.0, dice.1, dice.2), @@ -180,6 +186,14 @@ pub async fn add_track_dice( }, }; - let dice_track = dice.insert().await?; - Ok(Json(dice_track)) + // Check for existing dice tracks + let results = QueryDiceTrack::find(&insert_dice).await; + + match results { + Some(dice_track) => Ok(Json(dice_track)), + None => { + let dice_track = insert_dice.insert().await?; + Ok(Json(dice_track)) + } + } } diff --git a/src/api/mod.rs b/src/api/mod.rs index e4ea122..5f499f2 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2,7 +2,6 @@ pub use app::App; use std::sync::Arc; use axum::Router; -use serde::Deserialize; use crate::AppState; mod app; diff --git a/src/data/condition.rs b/src/data/condition.rs index 9b50bfb..0d1c295 100644 --- a/src/data/condition.rs +++ b/src/data/condition.rs @@ -24,12 +24,20 @@ impl Condition { Condition::Group(Box::new(self)) } - pub fn is_equal(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} = ?", left), vec![right.into()]) + pub fn is_equal(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} = ?", left), vec![right.into()]), + } } - pub fn not_equal(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} != ?", left), vec![right.into()]) + pub fn not_equal(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} != ?", left), vec![right.into()]), + } } pub fn is_null(value: &str) -> Self { @@ -41,6 +49,11 @@ impl Condition { } pub fn is_in(left: &str, right: Vec) -> Self { + // Use helper function to handle special cases + if let Some(condition) = Self::handle_empty_or_all_none(left, &right, true) { + return condition; + } + let right_list = right .iter() .map(|v| "'?'".to_string()) @@ -50,6 +63,11 @@ impl Condition { } pub fn not_in(left: &str, right: Vec) -> Self { + // Use helper function to handle special cases + if let Some(condition) = Self::handle_empty_or_all_none(left, &right, true) { + return condition; + } + let right_list = right .iter() .map(|v| "'?'".to_string()) @@ -58,36 +76,122 @@ impl Condition { Condition::Simple(format!("{} NOT IN ({})", left, right_list), right) } - pub fn like(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} LIKE '?'", left), vec![right.into()]) + pub fn like(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} LIKE '?'", left), vec![right.into()]), + } } - pub fn not_like(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} NOT LIKE '?'", left), vec![right.into()]) + pub fn not_like(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} NOT LIKE '?'", left), vec![right.into()]), + } } - pub fn i_like(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} ILIKE '?'", left), vec![right.into()]) + pub fn i_like(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} ILIKE '?'", left), vec![right.into()]), + } } - pub fn not_i_like(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} NOT ILIKE '?'", left), vec![right.into()]) + pub fn not_i_like(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} NOT ILIKE '?'", left), vec![right.into()]), + } } - pub fn gt(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} > ?", left), vec![right.into()]) + pub fn gt(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} > ?", left), vec![right.into()]), + } } - pub fn gte(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} >= ?", left), vec![right.into()]) + pub fn gte(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} >= ?", left), vec![right.into()]), + } } - pub fn lt(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} < ?", left), vec![right.into()]) + pub fn lt(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} < ?", left), vec![right.into()]), + } } - pub fn lte(left: &str, right: impl Into) -> Self { - Condition::Simple(format!("{} <= ?", left), vec![right.into()]) + pub fn lte(left: &str, right: impl Into + Clone) -> Self { + let value = right.clone().into(); + match Self::from_optional_value(left, value) { + Some(condition) => condition, + None => Condition::Simple(format!("{} <= ?", left), vec![right.into()]), + } + } + + // Private helper function to handle optional values + fn from_optional_value(left: &str, value: Value) -> Option { + match value { + Value::OptionalInt(None) => Some(Condition::is_null(left)), + Value::OptionalBigInt(None) => Some(Condition::is_null(left)), + Value::OptionalFloat(None) => Some(Condition::is_null(left)), + Value::OptionalDouble(None) => Some(Condition::is_null(left)), + Value::OptionalBool(None) => Some(Condition::is_null(left)), + Value::OptionalText(None) => Some(Condition::is_null(left)), + Value::OptionalDateTime(None) => Some(Condition::is_null(left)), + _ => None, // For non-optional or Some(value), let the primary method handle it + } + } + + // Private helper to handle `empty` or `all-None` lists + fn handle_empty_or_all_none(left: &str, right: &[Value], negate: bool) -> Option { + if right.is_empty() { + // For an empty list, return an always-false condition + // NOT IN with empty list is always TRUE, but we're defaulting to SQL-SAFE result (FALSE) + return Some(Condition::Simple( + if negate { + "TRUE".to_string() + } else { + "FALSE".to_string() + }, + vec![], + )); + } + + // Check if all elements in the `right` vector are `None` (Optional*) + if right.iter().all(|v| { + matches!( + v, + Value::OptionalInt(None) + | Value::OptionalBigInt(None) + | Value::OptionalFloat(None) + | Value::OptionalDouble(None) + | Value::OptionalBool(None) + | Value::OptionalText(None) + | Value::OptionalDateTime(None) + ) + }) { + // If all values are None, handle as NULL or NOT NULL + return Some(if negate { + Condition::not_null(left) + } else { + Condition::is_null(left) + }); + } + + // Otherwise, this is not an empty or all-none case + None } pub fn to_sql(&self, mut counter: &mut usize) -> (String, Vec) { @@ -96,12 +200,20 @@ impl Condition { match self { Condition::Simple(condition, values) => { - // Replace all instances of '?' with a numbered bind - let mut bind_index = *counter; - let numbered_condition = condition.replace("?", { - bind_index += 1; - &format!("${}", bind_index) - }); + // Replace each instance of '?' with increasing numbered binds + let mut numbered_condition = String::new(); + let mut chars = condition.chars().peekable(); + + while let Some(c) = chars.next() { + if c == '?' { + // Increment the counter and replace `?` with a numbered bind + *counter += 1; + numbered_condition.push_str(&format!("${}", *counter)); + } else { + numbered_condition.push(c); + } + } + sql.push_str(&numbered_condition); binds.extend(values.clone()); } diff --git a/src/data/executable_query.rs b/src/data/executable_query.rs new file mode 100644 index 0000000..1895e39 --- /dev/null +++ b/src/data/executable_query.rs @@ -0,0 +1,105 @@ +use sqlx::{FromRow, Postgres}; +use crate::data::Value; + +pub trait ExecutableQuery { + fn build(&self) -> (String, Vec); + + async fn execute(&self) -> Result { + // Build the SQL query and its values + let (query_string, values) = self.build(); + + // Start constructing the query + let mut query = sqlx::query(&query_string); + + // Bind each value to its respective placeholder + for value in values { + match value { + Value::Int(n) => query = query.bind(n), + Value::OptionalInt(n) => query = query.bind(n), + Value::BigInt(n) => query = query.bind(n), + Value::OptionalBigInt(n) => query = query.bind(n), + Value::Float(n) => query = query.bind(n), + Value::OptionalFloat(n) => query = query.bind(n), + Value::Double(n) => query = query.bind(n), + Value::OptionalDouble(n) => query = query.bind(n), + Value::Bool(n) => query = query.bind(n), + Value::OptionalBool(n) => query = query.bind(n), + Value::Text(n) => query = query.bind(n), + Value::OptionalText(n) => query = query.bind(n), + Value::DateTime(n) => query = query.bind(n), + Value::OptionalDateTime(n) => query = query.bind(n), + } + } + + let pool = crate::data::pool(); + query.execute(pool).await + } + + async fn fetch_optional< + T: Send + Unpin + for<'r> FromRow<'r, ::Row>, + >( + &self, + ) -> Option { + let (query_string, values) = self.build(); + let mut query_as = sqlx::query_as(&query_string); + for value in values { + match value { + Value::Int(n) => query_as = query_as.bind(n), + Value::OptionalInt(n) => query_as = query_as.bind(n), + Value::BigInt(n) => query_as = query_as.bind(n), + Value::OptionalBigInt(n) => query_as = query_as.bind(n), + Value::Float(n) => query_as = query_as.bind(n), + Value::OptionalFloat(n) => query_as = query_as.bind(n), + Value::Double(n) => query_as = query_as.bind(n), + Value::OptionalDouble(n) => query_as = query_as.bind(n), + Value::Bool(n) => query_as = query_as.bind(n), + Value::OptionalBool(n) => query_as = query_as.bind(n), + Value::Text(n) => query_as = query_as.bind(n), + Value::OptionalText(n) => query_as = query_as.bind(n), + Value::DateTime(n) => query_as = query_as.bind(n), + Value::OptionalDateTime(n) => query_as = query_as.bind(n), + } + } + + let pool = crate::data::pool(); + query_as.fetch_optional(pool).await.unwrap_or_else(|err| { + log::error!( + "Unable to fetch optional on query '{}': {}", + query_string, + err + ); + None + }) + } + + async fn fetch_all FromRow<'r, ::Row>>( + &self, + ) -> Vec { + let (query_string, values) = self.build(); + let mut query_as = sqlx::query_as(&query_string); + for value in values { + match value { + Value::Int(n) => query_as = query_as.bind(n), + Value::OptionalInt(n) => query_as = query_as.bind(n), + Value::BigInt(n) => query_as = query_as.bind(n), + Value::OptionalBigInt(n) => query_as = query_as.bind(n), + Value::Float(n) => query_as = query_as.bind(n), + Value::OptionalFloat(n) => query_as = query_as.bind(n), + Value::Double(n) => query_as = query_as.bind(n), + Value::OptionalDouble(n) => query_as = query_as.bind(n), + Value::Bool(n) => query_as = query_as.bind(n), + Value::OptionalBool(n) => query_as = query_as.bind(n), + Value::Text(n) => query_as = query_as.bind(n), + Value::OptionalText(n) => query_as = query_as.bind(n), + Value::DateTime(n) => query_as = query_as.bind(n), + Value::OptionalDateTime(n) => query_as = query_as.bind(n), + } + } + + let pool = crate::data::pool(); + query_as.fetch_all(pool).await.unwrap_or_else(|err| { + log::error!("Unable to fetch all on query '{}': {}", query_string, err); + vec![] + }) + } +} diff --git a/src/data/guilds/model.rs b/src/data/guilds/model.rs index 1752a56..c286701 100644 --- a/src/data/guilds/model.rs +++ b/src/data/guilds/model.rs @@ -1,6 +1,7 @@ use serde::{Serialize, Deserialize}; use sqlx::Database; use crate::data::condition::Condition; +use crate::data::executable_query::ExecutableQuery; use crate::data::insert::InsertBuilder; use crate::data::query::QueryBuilder; use crate::data::update::UpdateBuilder; diff --git a/src/data/insert.rs b/src/data/insert.rs index 816575a..6b5b0d8 100644 --- a/src/data/insert.rs +++ b/src/data/insert.rs @@ -1,3 +1,4 @@ +use crate::data::executable_query::ExecutableQuery; use crate::data::Value; pub struct InsertBuilder { @@ -27,39 +28,10 @@ impl InsertBuilder { self.returning = columns.iter().map(|s| s.to_string()).collect(); self } +} - pub async fn execute(self) -> Result { - // Build the SQL query and its values - let (query_string, values) = self.build(); - - // Start constructing the query - let mut query = sqlx::query(&query_string); - - // Bind each value to its respective placeholder - for value in values { - match value { - Value::Int(n) => query = query.bind(n), - Value::OptionalInt(n) => query = query.bind(n), - Value::BigInt(n) => query = query.bind(n), - Value::OptionalBigInt(n) => query = query.bind(n), - Value::Float(n) => query = query.bind(n), - Value::OptionalFloat(n) => query = query.bind(n), - Value::Double(n) => query = query.bind(n), - Value::OptionalDouble(n) => query = query.bind(n), - Value::Bool(n) => query = query.bind(n), - Value::OptionalBool(n) => query = query.bind(n), - Value::Text(n) => query = query.bind(n), - Value::OptionalText(n) => query = query.bind(n), - Value::DateTime(n) => query = query.bind(n), - Value::OptionalDateTime(n) => query = query.bind(n), - } - } - - let pool = crate::data::pool(); - query.execute(pool).await - } - - fn build(self) -> (String, Vec) { +impl ExecutableQuery for InsertBuilder { + fn build(&self) -> (String, Vec) { if self.columns.is_empty() || self.values.is_empty() { panic!("Cannot build insert query without columns and values"); } @@ -84,6 +56,6 @@ impl InsertBuilder { query.push_str(&format!(" RETURNING {}", self.returning.join(", "))); } - (query, self.values) + (query, self.values.clone()) } } diff --git a/src/data/mod.rs b/src/data/mod.rs index c3b1da3..0008ba3 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -7,12 +7,13 @@ use crate::error::SirenResult; pub mod condition; pub mod events; +mod executable_query; pub mod guilds; pub mod insert; pub mod messages; pub mod query; pub mod update; -mod executable_query; +pub use executable_query::ExecutableQuery; static POOL: OnceLock> = OnceLock::new(); static REDIS: OnceLock = OnceLock::new(); diff --git a/src/data/query.rs b/src/data/query.rs index a0afe76..9278812 100644 --- a/src/data/query.rs +++ b/src/data/query.rs @@ -1,7 +1,5 @@ -use std::fmt; -use std::fmt::Display; -use sqlx::{FromRow, Postgres}; use crate::data::condition::Condition; +use crate::data::executable_query::ExecutableQuery; use crate::data::Value; pub struct QueryBuilder { @@ -44,45 +42,10 @@ impl QueryBuilder { self.limit = Some(limit); self } +} - pub async fn fetch_optional< - T: Send + Unpin + for<'r> FromRow<'r, ::Row>, - >( - self, - ) -> Option { - let (query_string, values) = self.build(); - let mut query_as = sqlx::query_as(&query_string); - for value in values { - match value { - Value::Int(n) => query_as = query_as.bind(n), - Value::OptionalInt(n) => query_as = query_as.bind(n), - Value::BigInt(n) => query_as = query_as.bind(n), - Value::OptionalBigInt(n) => query_as = query_as.bind(n), - Value::Float(n) => query_as = query_as.bind(n), - Value::OptionalFloat(n) => query_as = query_as.bind(n), - Value::Double(n) => query_as = query_as.bind(n), - Value::OptionalDouble(n) => query_as = query_as.bind(n), - Value::Bool(n) => query_as = query_as.bind(n), - Value::OptionalBool(n) => query_as = query_as.bind(n), - Value::Text(n) => query_as = query_as.bind(n), - Value::OptionalText(n) => query_as = query_as.bind(n), - Value::DateTime(n) => query_as = query_as.bind(n), - Value::OptionalDateTime(n) => query_as = query_as.bind(n), - } - } - - let pool = crate::data::pool(); - query_as.fetch_optional(pool).await.unwrap_or_else(|err| { - log::error!( - "Unable to fetch optional on query '{}': {}", - query_string, - err - ); - None - }) - } - - pub fn build(self) -> (String, Vec) { +impl ExecutableQuery for QueryBuilder { + fn build(&self) -> (String, Vec) { let columns = if self.columns.is_empty() { "*".to_string() } else { @@ -92,7 +55,7 @@ impl QueryBuilder { let mut query = format!("SELECT {} FROM {}", columns, self.table); let mut values: Vec = Vec::new(); - if let Some(condition) = self.condition { + if let Some(condition) = &self.condition { let where_condition = condition.to_sql(&mut 0); query.push_str(&format!(" WHERE {}", where_condition.0)); values = where_condition.1; diff --git a/src/data/update.rs b/src/data/update.rs index 2061ee4..4374f16 100644 --- a/src/data/update.rs +++ b/src/data/update.rs @@ -1,4 +1,5 @@ use crate::data::condition::Condition; +use crate::data::executable_query::ExecutableQuery; use crate::data::Value; pub struct UpdateBuilder { @@ -28,39 +29,10 @@ impl UpdateBuilder { self.condition = Some(condition); self } +} - pub async fn execute(self) -> Result { - // Build the SQL query and its values - let (query_string, values) = self.build(); - - // Start constructing the query - let mut query = sqlx::query(&query_string); - - // Bind each value to its respective placeholder - for value in values { - match value { - Value::Int(n) => query = query.bind(n), - Value::OptionalInt(n) => query = query.bind(n), - Value::BigInt(n) => query = query.bind(n), - Value::OptionalBigInt(n) => query = query.bind(n), - Value::Float(n) => query = query.bind(n), - Value::OptionalFloat(n) => query = query.bind(n), - Value::Double(n) => query = query.bind(n), - Value::OptionalDouble(n) => query = query.bind(n), - Value::Bool(n) => query = query.bind(n), - Value::OptionalBool(n) => query = query.bind(n), - Value::Text(n) => query = query.bind(n), - Value::OptionalText(n) => query = query.bind(n), - Value::DateTime(n) => query = query.bind(n), - Value::OptionalDateTime(n) => query = query.bind(n), - } - } - - let pool = crate::data::pool(); - query.execute(pool).await - } - - fn build(self) -> (String, Vec) { +impl ExecutableQuery for UpdateBuilder { + fn build(&self) -> (String, Vec) { if self.columns.is_empty() { panic!("Cannot build update query without columns to set"); } @@ -76,10 +48,10 @@ impl UpdateBuilder { let mut query = format!("UPDATE {} SET {}", self.table, set_clause); let mut counter = self.values.len(); - let mut values: Vec = self.values; + let mut values: Vec = self.values.clone(); // Build where clause - if let Some(condition) = self.condition { + if let Some(condition) = &self.condition { let where_condition = condition.to_sql(&mut counter); query.push_str(&format!(" WHERE {}", where_condition.0)); values.extend(where_condition.1);