use sqlx::{PgPool, FromRow, Postgres};
use super::raw_query::{JoinType, JoinClause};
#[derive(Debug, Clone)]
pub enum QueryValue {
Text(String),
Integer(i64),
Float(f64),
Boolean(bool),
Uuid(uuid::Uuid),
Timestamp(chrono::NaiveDateTime),
Null,
}
pub struct QueryBuilder {
table: String,
fields: Vec<String>,
joins: Vec<JoinClause>,
conditions: Vec<(String, QueryValue)>,
order_by: Vec<String>,
limit: Option<u32>,
offset: Option<u32>,
next_param_id: usize,
}
impl QueryBuilder {
pub fn new(table: &str) -> Self {
Self {
table: table.to_string(),
fields: vec!["*".to_string()],
joins: Vec::new(),
conditions: Vec::new(),
order_by: Vec::new(),
limit: None,
offset: None,
next_param_id: 1,
}
}
pub fn select(mut self, fields: &[&str]) -> Self {
self.fields = fields.iter().map(|f| f.to_string()).collect();
self
}
pub fn where_eq(mut self, field: &str, value: QueryValue) -> Self {
self.conditions.push((format!("{} = ${}", field, self.next_param_id), value));
self.next_param_id += 1;
self
}
pub fn where_ne(mut self, field: &str, value: QueryValue) -> Self {
self.conditions.push((format!("{} != ${}", field, self.next_param_id), value));
self.next_param_id += 1;
self
}
pub fn where_gt(mut self, field: &str, value: QueryValue) -> Self {
self.conditions.push((format!("{} > ${}", field, self.next_param_id), value));
self.next_param_id += 1;
self
}
pub fn where_lt(mut self, field: &str, value: QueryValue) -> Self {
self.conditions.push((format!("{} < ${}", field, self.next_param_id), value));
self.next_param_id += 1;
self
}
pub fn where_like(mut self, field: &str, value: QueryValue) -> Self {
self.conditions.push((format!("{} LIKE ${}", field, self.next_param_id), value));
self.next_param_id += 1;
self
}
pub fn where_in(mut self, field: &str, values: Vec<QueryValue>) -> Self {
if values.is_empty() {
return self;
}
let placeholders: Vec<String> = (0..values.len())
.map(|i| format!("${}", self.next_param_id + i))
.collect();
self.conditions.push((
format!("{} IN ({})", field, placeholders.join(", ")),
values[0].clone(), ));
self.next_param_id += values.len();
self
}
pub fn join(mut self, join_type: JoinType, table: &str, on_condition: &str) -> Self {
self.joins.push(JoinClause {
join_type,
table: table.to_string(),
on_condition: on_condition.to_string(),
alias: None,
});
self
}
pub fn join_alias(mut self, join_type: JoinType, table: &str, alias: &str, on_condition: &str) -> Self {
self.joins.push(JoinClause {
join_type,
table: table.to_string(),
on_condition: on_condition.to_string(),
alias: Some(alias.to_string()),
});
self
}
pub fn where_raw(mut self, condition: &str, param: QueryValue) -> Self {
self.conditions.push((condition.to_string(), param));
self.next_param_id += 1;
self
}
pub fn order_by(mut self, field: &str, direction: &str) -> Self {
let direction = direction.to_uppercase();
if direction == "ASC" || direction == "DESC" {
self.order_by.push(format!("{} {}", field, direction));
} else {
self.order_by.push(format!("{} ASC", field));
}
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.offset = Some(offset);
self
}
pub fn build_sql(&self) -> String {
let mut query = format!("SELECT {} FROM {}", self.fields.join(", "), self.table);
for join in &self.joins {
let join_str = match join.join_type {
JoinType::Inner => "INNER JOIN",
JoinType::Left => "LEFT JOIN",
JoinType::Right => "RIGHT JOIN",
JoinType::Full => "FULL JOIN",
JoinType::Cross => "CROSS JOIN",
};
query.push(' ');
query.push_str(join_str);
query.push(' ');
query.push_str(&join.table);
if let Some(alias) = &join.alias {
query.push_str(" AS ");
query.push_str(alias);
}
query.push_str(" ON ");
query.push_str(&join.on_condition);
}
if !self.conditions.is_empty() {
let condition_strings: Vec<String> = self.conditions.iter()
.map(|(condition, _)| condition.clone())
.collect();
query.push_str(" WHERE ");
query.push_str(&condition_strings.join(" AND "));
}
if !self.order_by.is_empty() {
query.push_str(" ORDER BY ");
query.push_str(&self.order_by.join(", "));
}
if let Some(limit) = self.limit {
query.push_str(&format!(" LIMIT {}", limit));
}
if let Some(offset) = self.offset {
query.push_str(&format!(" OFFSET {}", offset));
}
query
}
pub fn build_query(&self) -> (String, Vec<QueryValue>) {
let sql = self.build_sql();
let params: Vec<QueryValue> = self.conditions.iter()
.map(|(_, value)| value.clone())
.collect();
(sql, params)
}
pub async fn execute<T>(&self, pool: &PgPool) -> anyhow::Result<Vec<T>>
where
T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
{
let (sql, params) = self.build_query();
let mut query = sqlx::query_as::<Postgres, T>(&sql);
for param in params {
match param {
QueryValue::Text(val) => query = query.bind(val),
QueryValue::Integer(val) => query = query.bind(val),
QueryValue::Float(val) => query = query.bind(val),
QueryValue::Boolean(val) => query = query.bind(val),
QueryValue::Uuid(val) => query = query.bind(val),
QueryValue::Timestamp(val) => query = query.bind(val),
QueryValue::Null => query = query.bind::<Option<String>>(None),
}
}
let results = query.fetch_all(pool).await?;
Ok(results)
}
pub async fn execute_first<T>(&self, pool: &PgPool) -> anyhow::Result<Option<T>>
where
T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
{
let (sql, params) = self.build_query();
let mut query = sqlx::query_as::<Postgres, T>(&sql);
for param in params {
match param {
QueryValue::Text(val) => query = query.bind(val),
QueryValue::Integer(val) => query = query.bind(val),
QueryValue::Float(val) => query = query.bind(val),
QueryValue::Boolean(val) => query = query.bind(val),
QueryValue::Uuid(val) => query = query.bind(val),
QueryValue::Timestamp(val) => query = query.bind(val),
QueryValue::Null => query = query.bind::<Option<String>>(None),
}
}
let result = query.fetch_optional(pool).await?;
Ok(result)
}
}
impl QueryValue {
pub fn text<T: Into<String>>(value: T) -> Self {
QueryValue::Text(value.into())
}
pub fn integer(value: i64) -> Self {
QueryValue::Integer(value)
}
pub fn float(value: f64) -> Self {
QueryValue::Float(value)
}
pub fn boolean(value: bool) -> Self {
QueryValue::Boolean(value)
}
pub fn uuid(value: uuid::Uuid) -> Self {
QueryValue::Uuid(value)
}
pub fn timestamp(value: chrono::NaiveDateTime) -> Self {
QueryValue::Timestamp(value)
}
pub fn null() -> Self {
QueryValue::Null
}
}