use crate::core::expr::{Expr, OrderExpr};
use crate::core::types::Value;
#[must_use = "query does nothing until .build() or .fetch_all() is called"]
#[derive(Debug)]
pub struct SelectQuery {
table: String,
columns: Option<Vec<String>>,
wheres: Vec<Expr>,
order_bys: Vec<OrderExpr>,
limit: Option<u64>,
offset: Option<u64>,
for_update: bool,
}
impl SelectQuery {
pub fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
columns: None,
wheres: Vec::new(),
order_bys: Vec::new(),
limit: None,
offset: None,
for_update: false,
}
}
pub fn columns(mut self, cols: Vec<&str>) -> Self {
self.columns = Some(cols.into_iter().map(String::from).collect());
self
}
pub fn where_(mut self, expr: Expr) -> Self {
self.wheres.push(expr);
self
}
pub fn order_by(mut self, order: OrderExpr) -> Self {
self.order_bys.push(order);
self
}
pub fn limit(mut self, n: u64) -> Self {
self.limit = Some(n);
self
}
pub fn offset(mut self, n: u64) -> Self {
self.offset = Some(n);
self
}
pub fn for_update(mut self) -> Self {
self.for_update = true;
self
}
pub fn build(&self) -> (String, Vec<Value>) {
let mut sql = String::new();
let mut binds = Vec::new();
sql.push_str("SELECT ");
match &self.columns {
Some(cols) => {
let qualified: Vec<String> = cols
.iter()
.map(|c| format!("\"{}\".\"{c}\"", self.table))
.collect();
sql.push_str(&qualified.join(", "));
}
None => {
sql.push_str(&format!("\"{}\".*", self.table));
}
}
sql.push_str(&format!(" FROM \"{}\"", self.table));
if !self.wheres.is_empty() {
let combined = self.wheres.iter().cloned().reduce(|a, b| a.and(b)).unwrap();
let bind_start = binds.len() + 1;
sql.push_str(&format!(" WHERE {}", combined.to_sql(bind_start)));
binds.extend(combined.binds());
}
if !self.order_bys.is_empty() {
let orders: Vec<String> = self.order_bys.iter().map(|o| o.to_sql_bare()).collect();
sql.push_str(&format!(" ORDER BY {}", orders.join(", ")));
}
if let Some(limit) = self.limit {
sql.push_str(&format!(" LIMIT {limit}"));
}
if let Some(offset) = self.offset {
sql.push_str(&format!(" OFFSET {offset}"));
}
if self.for_update {
sql.push_str(" FOR UPDATE");
}
(sql, binds)
}
}