Fixed query building and dice track

This commit is contained in:
2024-12-22 16:59:35 -05:00
parent bd132d0c6b
commit b218cd50f2
11 changed files with 304 additions and 160 deletions

View File

@@ -11,5 +11,5 @@ post {
} }
auth:bearer { auth:bearer {
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjI1MDg0MjI2MTIyMTI3NzY5NywibmFtZSI6ImJzaGVycmlmZiIsImlhdCI6MTczNDg0MzA0NywiZXhwIjoxNzM0OTI5NDQ3LCJqdGkiOiIycEowbmN5YmF1TVo4TG1aQ0VwU1B2OWgzMXFzU1FwaCJ9.gYf6oAm2POBXOHUnG4dTy5maKxTjUk8WxawOrIafjEE token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjI1MDg0MjI2MTIyMTI3NzY5NywibmFtZSI6ImJzaGVycmlmZiIsImlhdCI6MTczNDkwMjgzOSwiZXhwIjoxNzM0OTg5MjM5LCJqdGkiOiJWTlFjeXpBN25sZEt1SWtzcDFzc1pRNHNacUZ2dWZPZCJ9.JnO-Rklv9YZKWjRvehR4-tfP1dlO5vIEWpSh_W4xZWY
} }

View File

@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::api::auth::{csprng, AuthCredential}; use crate::api::auth::{csprng, AuthCredential};
use crate::api::auth::AuthorizationMiddleware; use crate::api::auth::AuthorizationMiddleware;
use crate::AppState; use crate::AppState;
use crate::data::ExecutableQuery;
use crate::data::condition::Condition; use crate::data::condition::Condition;
use crate::data::insert::InsertBuilder; use crate::data::insert::InsertBuilder;
use crate::data::query::QueryBuilder; use crate::data::query::QueryBuilder;
@@ -52,7 +53,8 @@ impl ApiKey {
.column("access_mask", Value::Int(self.access_mask)) .column("access_mask", Value::Int(self.access_mask))
.column("created_at", Value::DateTime(self.created_at)) .column("created_at", Value::DateTime(self.created_at))
.column("last_used_at", Value::OptionalDateTime(self.last_used_at)) .column("last_used_at", Value::OptionalDateTime(self.last_used_at))
.execute().await?; .execute()
.await?;
Ok(()) Ok(())
} }
@@ -64,7 +66,9 @@ impl ApiKey {
.column("created_at", Value::DateTime(self.created_at)) .column("created_at", Value::DateTime(self.created_at))
.column("last_used_at", Value::OptionalDateTime(self.last_used_at)) .column("last_used_at", Value::OptionalDateTime(self.last_used_at))
.where_condition(Condition::is_equal("key", Value::Text(self.key.clone()))) .where_condition(Condition::is_equal("key", Value::Text(self.key.clone())))
.execute().await { .execute()
.await
{
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
log::error!("error: {}", err); log::error!("error: {}", err);
@@ -76,7 +80,8 @@ impl ApiKey {
pub async fn find_by_key(key: &str) -> Option<Self> { pub async fn find_by_key(key: &str) -> Option<Self> {
QueryBuilder::new(TABLE_NAME) QueryBuilder::new(TABLE_NAME)
.where_condition(Condition::is_equal("key", Value::Text(key.to_string()))) .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<()> { pub async fn delete_by_id(key: &str) -> SirenResult<()> {

View File

@@ -10,6 +10,8 @@ use uuid::Uuid;
use crate::api::auth::{AuthCredential, AuthorizationMiddleware}; use crate::api::auth::{AuthCredential, AuthorizationMiddleware};
use crate::AppState; use crate::AppState;
use crate::bot::commands::fun::roll::{format_roll, parse_dice}; 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::data::query::QueryBuilder;
use crate::error::{Error, SirenResult}; use crate::error::{Error, SirenResult};
@@ -95,22 +97,26 @@ struct QueryDiceTrack {
} }
impl QueryDiceTrack { impl QueryDiceTrack {
pub async fn find() -> SirenResult<Vec<Self>> { pub async fn find(dice: &InsertDiceTrack) -> Option<Self> {
let pool = crate::data::pool(); QueryBuilder::new(TABLE_NAME)
let query = QueryBuilder::new(TABLE_NAME) .where_condition(Condition::and(
// .where_condition( Condition::is_equal("guild_id", Value::BigInt(dice.guild_id)),
// Condition::and( Condition::and(
// Condition::is_equal("guild_id", "$1"), Condition::is_equal("owner_id", Value::BigInt(dice.owner_id)),
// Condition::and( Condition::and(
// Condition::is_equal("owner_id", "$2"), 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)),
.build(); Condition::is_equal("operator", Value::OptionalText(dice.operator.clone())),
let items: Vec<QueryDiceTrack> = sqlx::query_as(&query.0).fetch_all(pool).await?; ),
),
Ok(items) ),
),
))
.fetch_optional()
.await
} }
} }
@@ -168,7 +174,7 @@ pub async fn add_track_dice(
let dice = parse_dice(&payload.dice)?; let dice = parse_dice(&payload.dice)?;
let dice = InsertDiceTrack { let insert_dice = InsertDiceTrack {
guild_id: guild_id.get() as i64, guild_id: guild_id.get() as i64,
owner_id: owner_id.get() as i64, owner_id: owner_id.get() as i64,
dice: format_roll(dice.0, dice.1, dice.2), 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?; // 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)) Ok(Json(dice_track))
}
}
} }

View File

@@ -2,7 +2,6 @@ pub use app::App;
use std::sync::Arc; use std::sync::Arc;
use axum::Router; use axum::Router;
use serde::Deserialize;
use crate::AppState; use crate::AppState;
mod app; mod app;

View File

@@ -24,12 +24,20 @@ impl Condition {
Condition::Group(Box::new(self)) Condition::Group(Box::new(self))
} }
pub fn is_equal(left: &str, right: impl Into<Value>) -> Self { pub fn is_equal(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} = ?", left), vec![right.into()]) 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<Value>) -> Self { pub fn not_equal(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} != ?", left), vec![right.into()]) 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 { pub fn is_null(value: &str) -> Self {
@@ -41,6 +49,11 @@ impl Condition {
} }
pub fn is_in(left: &str, right: Vec<Value>) -> Self { pub fn is_in(left: &str, right: Vec<Value>) -> 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 let right_list = right
.iter() .iter()
.map(|v| "'?'".to_string()) .map(|v| "'?'".to_string())
@@ -50,6 +63,11 @@ impl Condition {
} }
pub fn not_in(left: &str, right: Vec<Value>) -> Self { pub fn not_in(left: &str, right: Vec<Value>) -> 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 let right_list = right
.iter() .iter()
.map(|v| "'?'".to_string()) .map(|v| "'?'".to_string())
@@ -58,36 +76,122 @@ impl Condition {
Condition::Simple(format!("{} NOT IN ({})", left, right_list), right) Condition::Simple(format!("{} NOT IN ({})", left, right_list), right)
} }
pub fn like(left: &str, right: impl Into<Value>) -> Self { pub fn like(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} LIKE '?'", left), vec![right.into()]) 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<Value>) -> Self { pub fn not_like(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} NOT LIKE '?'", left), vec![right.into()]) 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<Value>) -> Self { pub fn i_like(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} ILIKE '?'", left), vec![right.into()]) 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<Value>) -> Self { pub fn not_i_like(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} NOT ILIKE '?'", left), vec![right.into()]) 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<Value>) -> Self { pub fn gt(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} > ?", left), vec![right.into()]) 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<Value>) -> Self { pub fn gte(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} >= ?", left), vec![right.into()]) 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<Value>) -> Self { pub fn lt(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} < ?", left), vec![right.into()]) 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<Value>) -> Self { pub fn lte(left: &str, right: impl Into<Value> + Clone) -> Self {
Condition::Simple(format!("{} <= ?", left), vec![right.into()]) 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<Self> {
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<Self> {
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<Value>) { pub fn to_sql(&self, mut counter: &mut usize) -> (String, Vec<Value>) {
@@ -96,12 +200,20 @@ impl Condition {
match self { match self {
Condition::Simple(condition, values) => { Condition::Simple(condition, values) => {
// Replace all instances of '?' with a numbered bind // Replace each instance of '?' with increasing numbered binds
let mut bind_index = *counter; let mut numbered_condition = String::new();
let numbered_condition = condition.replace("?", { let mut chars = condition.chars().peekable();
bind_index += 1;
&format!("${}", bind_index) 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); sql.push_str(&numbered_condition);
binds.extend(values.clone()); binds.extend(values.clone());
} }

View File

@@ -0,0 +1,105 @@
use sqlx::{FromRow, Postgres};
use crate::data::Value;
pub trait ExecutableQuery {
fn build(&self) -> (String, Vec<Value>);
async fn execute(&self) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> {
// 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, <Postgres as sqlx::Database>::Row>,
>(
&self,
) -> Option<T> {
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<T: Send + Unpin + for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row>>(
&self,
) -> Vec<T> {
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![]
})
}
}

View File

@@ -1,6 +1,7 @@
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use sqlx::Database; use sqlx::Database;
use crate::data::condition::Condition; use crate::data::condition::Condition;
use crate::data::executable_query::ExecutableQuery;
use crate::data::insert::InsertBuilder; use crate::data::insert::InsertBuilder;
use crate::data::query::QueryBuilder; use crate::data::query::QueryBuilder;
use crate::data::update::UpdateBuilder; use crate::data::update::UpdateBuilder;

View File

@@ -1,3 +1,4 @@
use crate::data::executable_query::ExecutableQuery;
use crate::data::Value; use crate::data::Value;
pub struct InsertBuilder { pub struct InsertBuilder {
@@ -27,39 +28,10 @@ impl InsertBuilder {
self.returning = columns.iter().map(|s| s.to_string()).collect(); self.returning = columns.iter().map(|s| s.to_string()).collect();
self self
} }
}
pub async fn execute(self) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> { impl ExecutableQuery for InsertBuilder {
// Build the SQL query and its values fn build(&self) -> (String, Vec<Value>) {
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<Value>) {
if self.columns.is_empty() || self.values.is_empty() { if self.columns.is_empty() || self.values.is_empty() {
panic!("Cannot build insert query without columns and values"); panic!("Cannot build insert query without columns and values");
} }
@@ -84,6 +56,6 @@ impl InsertBuilder {
query.push_str(&format!(" RETURNING {}", self.returning.join(", "))); query.push_str(&format!(" RETURNING {}", self.returning.join(", ")));
} }
(query, self.values) (query, self.values.clone())
} }
} }

View File

@@ -7,12 +7,13 @@ use crate::error::SirenResult;
pub mod condition; pub mod condition;
pub mod events; pub mod events;
mod executable_query;
pub mod guilds; pub mod guilds;
pub mod insert; pub mod insert;
pub mod messages; pub mod messages;
pub mod query; pub mod query;
pub mod update; pub mod update;
mod executable_query; pub use executable_query::ExecutableQuery;
static POOL: OnceLock<Pool<Postgres>> = OnceLock::new(); static POOL: OnceLock<Pool<Postgres>> = OnceLock::new();
static REDIS: OnceLock<RedisClient> = OnceLock::new(); static REDIS: OnceLock<RedisClient> = OnceLock::new();

View File

@@ -1,7 +1,5 @@
use std::fmt;
use std::fmt::Display;
use sqlx::{FromRow, Postgres};
use crate::data::condition::Condition; use crate::data::condition::Condition;
use crate::data::executable_query::ExecutableQuery;
use crate::data::Value; use crate::data::Value;
pub struct QueryBuilder { pub struct QueryBuilder {
@@ -44,45 +42,10 @@ impl QueryBuilder {
self.limit = Some(limit); self.limit = Some(limit);
self self
} }
}
pub async fn fetch_optional< impl ExecutableQuery for QueryBuilder {
T: Send + Unpin + for<'r> FromRow<'r, <Postgres as sqlx::Database>::Row>, fn build(&self) -> (String, Vec<Value>) {
>(
self,
) -> Option<T> {
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<Value>) {
let columns = if self.columns.is_empty() { let columns = if self.columns.is_empty() {
"*".to_string() "*".to_string()
} else { } else {
@@ -92,7 +55,7 @@ impl QueryBuilder {
let mut query = format!("SELECT {} FROM {}", columns, self.table); let mut query = format!("SELECT {} FROM {}", columns, self.table);
let mut values: Vec<Value> = Vec::new(); let mut values: Vec<Value> = Vec::new();
if let Some(condition) = self.condition { if let Some(condition) = &self.condition {
let where_condition = condition.to_sql(&mut 0); let where_condition = condition.to_sql(&mut 0);
query.push_str(&format!(" WHERE {}", where_condition.0)); query.push_str(&format!(" WHERE {}", where_condition.0));
values = where_condition.1; values = where_condition.1;

View File

@@ -1,4 +1,5 @@
use crate::data::condition::Condition; use crate::data::condition::Condition;
use crate::data::executable_query::ExecutableQuery;
use crate::data::Value; use crate::data::Value;
pub struct UpdateBuilder { pub struct UpdateBuilder {
@@ -28,39 +29,10 @@ impl UpdateBuilder {
self.condition = Some(condition); self.condition = Some(condition);
self self
} }
}
pub async fn execute(self) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> { impl ExecutableQuery for UpdateBuilder {
// Build the SQL query and its values fn build(&self) -> (String, Vec<Value>) {
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<Value>) {
if self.columns.is_empty() { if self.columns.is_empty() {
panic!("Cannot build update query without columns to set"); 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 query = format!("UPDATE {} SET {}", self.table, set_clause);
let mut counter = self.values.len(); let mut counter = self.values.len();
let mut values: Vec<Value> = self.values; let mut values: Vec<Value> = self.values.clone();
// Build where clause // Build where clause
if let Some(condition) = self.condition { if let Some(condition) = &self.condition {
let where_condition = condition.to_sql(&mut counter); let where_condition = condition.to_sql(&mut counter);
query.push_str(&format!(" WHERE {}", where_condition.0)); query.push_str(&format!(" WHERE {}", where_condition.0));
values.extend(where_condition.1); values.extend(where_condition.1);