use log::{debug};
use crate::error::{Error, Result as SdkResult};
use crate::types::{DamengValue};
pub struct QueryBuilder {
schema: Option<String>,
table: Option<String>,
select_columns: Vec<String>,
where_clause: Option<String>,
order_by: Vec<String>,
limit: Option<usize>,
offset: Option<usize>,
join_clauses: Vec<String>,
group_by: Vec<String>,
having_clause: Option<String>,
}
impl QueryBuilder {
pub fn new() -> Self {
Self {
schema: None,
table: None,
select_columns: Vec::new(),
where_clause: None,
order_by: Vec::new(),
limit: None,
offset: None,
join_clauses: Vec::new(),
group_by: Vec::new(),
having_clause: None,
}
}
pub fn schema(mut self, schema: impl Into<String>) -> Self {
self.schema = Some(schema.into());
self
}
pub fn table(mut self, table: impl Into<String>) -> Self {
self.table = Some(table.into());
self
}
pub fn select(mut self, columns: &[&str]) -> Self {
self.select_columns.extend(columns.iter().map(|s| s.to_string()));
self
}
pub fn select_column(mut self, column: impl Into<String>) -> Self {
self.select_columns.push(column.into());
self
}
pub fn where_clause(mut self, clause: impl Into<String>) -> Self {
self.where_clause = Some(clause.into());
self
}
pub fn order_by(mut self, column: impl Into<String>, ascending: bool) -> Self {
let dir = if ascending { "ASC" } else { "DESC" };
self.order_by.push(format!("{} {}", column.into(), dir));
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn join(mut self, join_type: JoinType, table: impl Into<String>, on: impl Into<String>) -> Self {
let type_str = match join_type {
JoinType::Inner => "INNER JOIN",
JoinType::Left => "LEFT JOIN",
JoinType::Right => "RIGHT JOIN",
JoinType::Full => "FULL JOIN",
};
self.join_clauses.push(format!("{} {} ON {}", type_str, table.into(), on.into()));
self
}
pub fn group_by(mut self, columns: &[&str]) -> Self {
self.group_by.extend(columns.iter().map(|s| s.to_string()));
self
}
pub fn having(mut self, clause: impl Into<String>) -> Self {
self.having_clause = Some(clause.into());
self
}
pub fn build(&self) -> SdkResult<String> {
if self.table.is_none() {
return Err(Error::query("Table name is required"));
}
let schema_prefix = self.schema.as_ref().map(|s| format!("{}.", s)).unwrap_or_default();
let table_name = format!("{}{}", schema_prefix, self.table.as_ref().unwrap());
let mut sql = String::new();
if self.select_columns.is_empty() {
sql.push_str("SELECT *");
} else {
sql.push_str(&format!("SELECT {}", self.select_columns.join(", ")));
}
sql.push_str(&format!(" FROM {}", table_name));
for join in &self.join_clauses {
sql.push(' ');
sql.push_str(join);
}
if let Some(where_clause) = &self.where_clause {
sql.push_str(&format!(" WHERE {}", where_clause));
}
if !self.group_by.is_empty() {
sql.push_str(&format!(" GROUP BY {}", self.group_by.join(", ")));
}
if let Some(having) = &self.having_clause {
sql.push_str(&format!(" HAVING {}", having));
}
if !self.order_by.is_empty() {
sql.push_str(&format!(" ORDER BY {}", self.order_by.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));
}
debug!("Built query: {}", sql);
Ok(sql)
}
}
impl Default for QueryBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JoinType {
Inner,
Left,
Right,
Full,
}
pub struct BatchInsertBuilder {
table: Option<String>,
schema: Option<String>,
columns: Vec<String>,
values: Vec<Vec<DamengValue>>,
}
impl BatchInsertBuilder {
pub fn new() -> Self {
Self {
table: None,
schema: None,
columns: Vec::new(),
values: Vec::new(),
}
}
pub fn table(mut self, table: impl Into<String>) -> Self {
self.table = Some(table.into());
self
}
pub fn schema(mut self, schema: impl Into<String>) -> Self {
self.schema = Some(schema.into());
self
}
pub fn columns(mut self, columns: &[&str]) -> Self {
self.columns = columns.iter().map(|s| s.to_string()).collect();
self
}
pub fn add_row(mut self, values: Vec<DamengValue>) -> Self {
self.values.push(values);
self
}
pub fn build(&self) -> SdkResult<String> {
let table = self.table.as_ref().ok_or_else(|| Error::query("Table name is required"))?;
let schema_prefix = self.schema.as_ref().map(|s| format!("{}.", s)).unwrap_or_default();
if self.columns.is_empty() {
return Err(Error::query("Columns are required"));
}
if self.values.is_empty() {
return Err(Error::query("Values are required"));
}
let mut sql = String::new();
sql.push_str(&format!("INSERT INTO {}{} (", schema_prefix, table));
sql.push_str(&self.columns.join(", "));
sql.push_str(") VALUES ");
let placeholder_str = vec!["?"; self.columns.len()].join(", ");
for (i, _row) in self.values.iter().enumerate() {
if i > 0 {
sql.push_str(", ");
}
sql.push_str(&format!("({})", placeholder_str));
}
debug!("Built batch insert query: {}", sql);
Ok(sql)
}
pub fn values(&self) -> &[Vec<DamengValue>] {
&self.values
}
}
impl Default for BatchInsertBuilder {
fn default() -> Self {
Self::new()
}
}