diff --git a/Cargo.toml b/Cargo.toml index 838d75e..1f50d84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,4 @@ axum = "0.7.7" lazy_static = "1.5.0" futures = "0.3.31" axum-login = "0.16.0" +sqlx-postgres = "0.8.2" diff --git a/src/data/guilds/model.rs b/src/data/guilds/model.rs index 54f5cde..b3b03cd 100644 --- a/src/data/guilds/model.rs +++ b/src/data/guilds/model.rs @@ -1,4 +1,5 @@ use serde::{Serialize, Deserialize}; +use crate::data::query::{Condition, QueryBuilder}; use crate::error::SirenResult; const TABLE_NAME: &str = "guilds"; @@ -36,7 +37,10 @@ impl GuildCache { pub async fn get_by_id(id: i64) -> SirenResult> { let pool = crate::data::pool(); - let item = sqlx::query_as::<_, Self>(&format!("SELECT * FROM {} WHERE id = $1", TABLE_NAME)) + let query = QueryBuilder::new(TABLE_NAME) + .where_condition(Condition::is_equal("id", "$1")) // Use a placeholder + .build(); + let item = sqlx::query_as(&query) .bind(id) .fetch_optional(pool) .await?; diff --git a/src/data/mod.rs b/src/data/mod.rs index d8c1b20..c90e036 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -7,6 +7,7 @@ use crate::error::SirenResult; pub mod events; pub mod guilds; pub mod messages; +mod query; static POOL: OnceLock> = OnceLock::new(); static REDIS: OnceLock = OnceLock::new(); diff --git a/src/data/query.rs b/src/data/query.rs new file mode 100644 index 0000000..37bd1a0 --- /dev/null +++ b/src/data/query.rs @@ -0,0 +1,163 @@ +pub struct QueryBuilder { + table: String, + columns: Vec, + condition: Option, + order_by: Vec, + limit: Option, +} + +impl QueryBuilder { + pub fn new(table: &str) -> Self { + QueryBuilder { + table: table.to_string(), + columns: Vec::new(), + condition: None, + order_by: Vec::new(), + limit: None, + } + } + + pub fn select(mut self, columns: &[&str]) -> Self { + self.columns = columns.iter().map(|s| s.to_string()).collect(); + self + } + + pub fn where_condition(mut self, condition: Condition) -> Self { + self.condition = Some(condition); + self + } + + pub fn order_by(mut self, column: &str, direction: &str) -> Self { + self.order_by.push(format!("{} {}", column, direction)); + self + } + + pub fn limit(mut self, limit: usize) -> Self { + self.limit = Some(limit); + self + } + + pub fn build(self) -> String { + let columns = if self.columns.is_empty() { + "*".to_string() + } else { + self.columns.join(",") + }; + + let mut query = format!("SELECT {} FROM {}", columns, self.table); + + if let Some(condition) = self.condition { + query.push_str(&format!(" WHERE {}", condition.to_sql())); + } + + if !self.order_by.is_empty() { + query.push_str(&format!(" ORDER BY {}", self.order_by.join(" ORDER BY"))); + } + + if let Some(limit) = self.limit { + query.push_str(&format!(" LIMIT {}", limit)); + } + + query + } +} + +pub enum Condition { + Simple(String), + And(Box, Box), + Or(Box, Box), + Group(Box), +} + +impl Condition { + pub fn new(condition: &str) -> Self { + Condition::Simple(condition.to_string()) + } + + pub fn and(self, other: Self) -> Self { + Condition::And(Box::new(self), Box::new(other)) + } + + pub fn or(self, other: Self) -> Self { + Condition::Or(Box::new(self), Box::new(other)) + } + + pub fn group(self) -> Self { + Condition::Group(Box::new(self)) + } + + pub fn is_equal(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} = {}", left, right)) + } + + pub fn not_equal(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} != {}", left, right)) + } + + pub fn is_null(value: &str) -> Self { + Condition::Simple(format!("{} IS NULL", value)) + } + + pub fn not_null(value: &str) -> Self { + Condition::Simple(format!("{} IS NOT NULL", value)) + } + + pub fn is_in(left: &str, right: &[&str]) -> Self { + let right_list = right + .iter() + .map(|v| format!("'{}'", v)) + .collect::>() + .join(", "); + Condition::Simple(format!("{} IN ({})", left, right_list)) + } + + pub fn not_in(left: &str, right: &[&str]) -> Self { + let right_list = right + .iter() + .map(|v| format!("'{}'", v)) + .collect::>() + .join(", "); + Condition::Simple(format!("{} NOT IN ({})", left, right_list)) + } + + pub fn like(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} LIKE '{}'", left, right)) + } + + pub fn not_like(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} NOT LIKE '{}'", left, right)) + } + + pub fn i_like(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} ILIKE '{}'", left, right)) + } + + pub fn not_i_like(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} NOT ILIKE '{}'", left, right)) + } + + pub fn gt(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} > {}", left, right)) + } + + pub fn gte(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} >= {}", left, right)) + } + + pub fn lt(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} < {}", left, right)) + } + + pub fn lte(left: &str, right: &str) -> Self { + Condition::Simple(format!("{} <= {}", left, right)) + } + + fn to_sql(&self) -> String { + match self { + Condition::Simple(s) => s.to_string(), + Condition::And(a, b) => format!("{} AND {}", a.to_sql(), b.to_sql()), + Condition::Or(a, b) => format!("{} OR {}", a.to_sql(), b.to_sql()), + Condition::Group(a) => format!("({})", a.to_sql()), + } + } +} \ No newline at end of file