pub mod arg;
pub mod bind;
pub mod error;
pub mod name;
pub mod prelude;
pub use crate::error::SqlBuilderError;
pub use crate::name::SqlName;
use anyhow::Result;
#[derive(Clone)]
pub struct SqlBuilder {
statement: Statement,
table: String,
join_natural: bool,
join_operator: JoinOperator,
joins: Vec<String>,
distinct: bool,
fields: Vec<String>,
sets: Vec<String>,
values: Values,
returning: Option<String>,
group_by: Vec<String>,
having: Option<String>,
unions: String,
wheres: Vec<String>,
order_by: Vec<String>,
limit: Option<String>,
offset: Option<String>,
}
#[derive(Clone)]
enum Statement {
SelectFrom,
SelectValues,
UpdateTable,
InsertInto,
DeleteFrom,
}
#[derive(Clone)]
enum JoinOperator {
Join,
LeftJoin,
LeftOuterJoin,
RightJoin,
RightOuterJoin,
InnerJoin,
CrossJoin,
}
#[derive(Clone)]
enum Values {
Empty,
List(Vec<String>),
Select(String),
}
impl SqlBuilder {
fn default() -> Self {
Self {
statement: Statement::SelectFrom,
table: String::new(),
join_natural: false,
join_operator: JoinOperator::Join,
joins: Vec::new(),
distinct: false,
fields: Vec::new(),
sets: Vec::new(),
values: Values::Empty,
returning: None,
group_by: Vec::new(),
having: None,
unions: String::new(),
wheres: Vec::new(),
order_by: Vec::new(),
limit: None,
offset: None,
}
}
pub fn select_from<S: ToString>(table: S) -> Self {
Self {
table: table.to_string(),
..Self::default()
}
}
pub fn and_table<S: ToString>(&mut self, table: S) -> &mut Self {
self.table = format!("{}, {}", self.table, table.to_string());
self
}
pub fn select_values<S: ToString>(values: &[S]) -> Self {
let mut sel = Self {
statement: Statement::SelectValues,
..Self::default()
};
sel.fields(values);
sel
}
pub fn insert_into<S: ToString>(table: S) -> Self {
Self {
statement: Statement::InsertInto,
table: table.to_string(),
..Self::default()
}
}
pub fn update_table<S: ToString>(table: S) -> Self {
Self {
statement: Statement::UpdateTable,
table: table.to_string(),
..Self::default()
}
}
pub fn delete_from<S: ToString>(table: S) -> Self {
Self {
statement: Statement::DeleteFrom,
table: table.to_string(),
..Self::default()
}
}
pub fn natural(&mut self) -> &mut Self {
self.join_natural = true;
self
}
pub fn left(&mut self) -> &mut Self {
self.join_operator = JoinOperator::LeftJoin;
self
}
pub fn left_outer(&mut self) -> &mut Self {
self.join_operator = JoinOperator::LeftOuterJoin;
self
}
pub fn right(&mut self) -> &mut Self {
self.join_operator = JoinOperator::RightJoin;
self
}
pub fn right_outer(&mut self) -> &mut Self {
self.join_operator = JoinOperator::RightOuterJoin;
self
}
pub fn inner(&mut self) -> &mut Self {
self.join_operator = JoinOperator::InnerJoin;
self
}
pub fn cross(&mut self) -> &mut Self {
self.join_operator = JoinOperator::CrossJoin;
self
}
pub fn join<S: ToString>(&mut self, table: S) -> &mut Self {
let mut text = match &self.join_operator {
JoinOperator::Join if self.join_natural => "NATURAL JOIN ",
JoinOperator::Join => "JOIN ",
JoinOperator::LeftJoin if self.join_natural => "NATURAL LEFT JOIN ",
JoinOperator::LeftJoin => "LEFT JOIN ",
JoinOperator::LeftOuterJoin if self.join_natural => "NATURAL LEFT OUTER JOIN ",
JoinOperator::LeftOuterJoin => "LEFT OUTER JOIN ",
JoinOperator::RightJoin if self.join_natural => "NATURAL RIGHT JOIN ",
JoinOperator::RightJoin => "RIGHT JOIN ",
JoinOperator::RightOuterJoin if self.join_natural => "NATURAL RIGHT OUTER JOIN ",
JoinOperator::RightOuterJoin => "RIGHT OUTER JOIN ",
JoinOperator::InnerJoin if self.join_natural => "NATURAL INNER JOIN ",
JoinOperator::InnerJoin => "INNER JOIN ",
JoinOperator::CrossJoin if self.join_natural => "NATURAL CROSS JOIN ",
JoinOperator::CrossJoin => "CROSS JOIN ",
}
.to_string();
self.join_natural = false;
text.push_str(&table.to_string());
self.joins.push(text);
self
}
pub fn on<S: ToString>(&mut self, constraint: S) -> &mut Self {
if let Some(last) = self.joins.last_mut() {
last.push_str(" ON ");
last.push_str(&constraint.to_string());
}
self
}
pub fn on_eq<S: ToString, T: ToString>(&mut self, c1: S, c2: T) -> &mut Self {
if let Some(last) = self.joins.last_mut() {
last.push_str(" ON ");
last.push_str(&c1.to_string());
last.push_str(" = ");
last.push_str(&c2.to_string());
}
self
}
pub fn distinct(&mut self) -> &mut Self {
self.distinct = true;
self
}
pub fn fields<S: ToString>(&mut self, fields: &[S]) -> &mut Self {
let mut fields = fields
.iter()
.map(|f| (*f).to_string())
.collect::<Vec<String>>();
self.fields.append(&mut fields);
self
}
pub fn set_fields<S: ToString>(&mut self, fields: &[S]) -> &mut Self {
let fields = fields
.iter()
.map(|f| (*f).to_string())
.collect::<Vec<String>>();
self.fields = fields;
self
}
pub fn field<S: ToString>(&mut self, field: S) -> &mut Self {
self.fields.push(field.to_string());
self
}
pub fn set_field<S: ToString>(&mut self, field: S) -> &mut Self {
self.fields = vec![field.to_string()];
self
}
pub fn count<S: ToString>(&mut self, field: S) -> &mut Self {
self.fields.push(format!("COUNT({})", field.to_string()));
self
}
pub fn count_as<S, T>(&mut self, field: S, name: T) -> &mut Self
where
S: ToString,
T: ToString,
{
self.fields.push(format!(
"COUNT({}) AS {}",
field.to_string(),
name.to_string()
));
self
}
pub fn set<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let expr = format!("{} = {}", &field.to_string(), &value.to_string());
self.sets.push(expr);
self
}
pub fn set_str<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let expr = format!("{} = '{}'", &field.to_string(), &esc(&value.to_string()));
self.sets.push(expr);
self
}
pub fn values<S: ToString>(&mut self, values: &[S]) -> &mut Self {
let values: Vec<String> = values
.iter()
.map(|v| (*v).to_string())
.collect::<Vec<String>>();
let values = format!("({})", values.join(", "));
match &mut self.values {
Values::Empty => self.values = Values::List(vec![values]),
Values::Select(_) => self.values = Values::List(vec![values]),
Values::List(v) => v.push(values),
};
self
}
pub fn select<S: ToString>(&mut self, query: S) -> &mut Self {
self.values = Values::Select(query.to_string());
self
}
pub fn returning<S: ToString>(&mut self, field: S) -> &mut Self {
self.returning = Some(field.to_string());
self
}
pub fn returning_id(&mut self) -> &mut Self {
self.returning("id")
}
pub fn group_by<S: ToString>(&mut self, field: S) -> &mut Self {
self.group_by.push(field.to_string());
self
}
pub fn having<S: ToString>(&mut self, cond: S) -> &mut Self {
self.having = Some(cond.to_string());
self
}
pub fn and_where<S: ToString>(&mut self, cond: S) -> &mut Self {
self.wheres.push(cond.to_string());
self
}
pub fn and_where_eq<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" = ");
cond.push_str(&value.to_string());
self.and_where(&cond)
}
pub fn and_where_ne<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" <> ");
cond.push_str(&value.to_string());
self.and_where(&cond)
}
pub fn and_where_gt<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" > ");
cond.push_str(&value.to_string());
self.and_where(&cond)
}
pub fn and_where_ge<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" >= ");
cond.push_str(&value.to_string());
self.and_where(&cond)
}
pub fn and_where_lt<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" < ");
cond.push_str(&value.to_string());
self.and_where(&cond)
}
pub fn and_where_le<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" <= ");
cond.push_str(&value.to_string());
self.and_where(&cond)
}
pub fn and_where_like<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.and_where(&cond)
}
pub fn and_where_like_right<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.and_where(&cond)
}
pub fn and_where_like_left<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.and_where(&cond)
}
pub fn and_where_like_any<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.and_where(&cond)
}
pub fn and_where_not_like<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.and_where(&cond)
}
pub fn and_where_not_like_right<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.and_where(&cond)
}
pub fn and_where_not_like_left<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.and_where(&cond)
}
pub fn and_where_not_like_any<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.and_where(&cond)
}
pub fn and_where_is_null<S: ToString>(&mut self, field: S) -> &mut Self {
let mut cond = field.to_string();
cond.push_str(" IS NULL");
self.and_where(&cond)
}
pub fn and_where_is_not_null<S: ToString>(&mut self, field: S) -> &mut Self {
let mut cond = field.to_string();
cond.push_str(" IS NOT NULL");
self.and_where(&cond)
}
pub fn and_where_in<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| (*v).to_string())
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" IN (");
cond.push_str(&list);
cond.push(')');
self.and_where(&cond)
}
pub fn and_where_in_quoted<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| quote((*v).to_string()))
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" IN (");
cond.push_str(&list);
cond.push(')');
self.and_where(&cond)
}
pub fn and_where_not_in<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| (*v).to_string())
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" NOT IN (");
cond.push_str(&list);
cond.push(')');
self.and_where(&cond)
}
pub fn and_where_not_in_quoted<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| quote((*v).to_string()))
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" NOT IN (");
cond.push_str(&list);
cond.push(')');
self.and_where(&cond)
}
pub fn and_where_in_query<S, T>(&mut self, field: S, query: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" IN (");
cond.push_str(&query.to_string());
cond.push(')');
self.and_where(&cond)
}
pub fn and_where_not_in_query<S, T>(&mut self, field: S, query: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT IN (");
cond.push_str(&query.to_string());
cond.push(')');
self.and_where(&cond)
}
pub fn and_where_between<S, T, U>(&mut self, field: S, min: T, max: U) -> &mut Self
where
S: ToString,
T: ToString,
U: ToString,
{
let mut cond = field.to_string();
cond.push_str(" BETWEEN ");
cond.push_str(&min.to_string());
cond.push_str(" AND ");
cond.push_str(&max.to_string());
self.and_where(&cond)
}
pub fn and_where_not_between<S, T, U>(&mut self, field: S, min: T, max: U) -> &mut Self
where
S: ToString,
T: ToString,
U: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT BETWEEN ");
cond.push_str(&min.to_string());
cond.push_str(" AND ");
cond.push_str(&max.to_string());
self.and_where(&cond)
}
pub fn or_where<S: ToString>(&mut self, cond: S) -> &mut Self {
if self.wheres.is_empty() {
self.wheres.push(cond.to_string());
} else if let Some(last) = self.wheres.last_mut() {
last.push_str(" OR ");
last.push_str(&cond.to_string());
}
self
}
pub fn or_where_eq<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" = ");
cond.push_str(&value.to_string());
self.or_where(&cond)
}
pub fn or_where_ne<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" <> ");
cond.push_str(&value.to_string());
self.or_where(&cond)
}
pub fn or_where_gt<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" > ");
cond.push_str(&value.to_string());
self.or_where(&cond)
}
pub fn or_where_ge<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" >= ");
cond.push_str(&value.to_string());
self.or_where(&cond)
}
pub fn or_where_lt<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" < ");
cond.push_str(&value.to_string());
self.or_where(&cond)
}
pub fn or_where_le<S, T>(&mut self, field: S, value: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" <= ");
cond.push_str(&value.to_string());
self.or_where(&cond)
}
pub fn or_where_like<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.or_where(&cond)
}
pub fn or_where_like_right<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.or_where(&cond)
}
pub fn or_where_like_left<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.or_where(&cond)
}
pub fn or_where_like_any<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.or_where(&cond)
}
pub fn or_where_not_like<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.or_where(&cond)
}
pub fn or_where_not_like_right<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push('\'');
self.or_where(&cond)
}
pub fn or_where_not_like_left<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.or_where(&cond)
}
pub fn or_where_not_like_any<S, T>(&mut self, field: S, mask: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT LIKE '%");
cond.push_str(&esc(&mask.to_string()));
cond.push_str("%'");
self.or_where(&cond)
}
pub fn or_where_is_null<S: ToString>(&mut self, field: S) -> &mut Self {
let mut cond = field.to_string();
cond.push_str(" IS NULL");
self.or_where(&cond)
}
pub fn or_where_is_not_null<S: ToString>(&mut self, field: S) -> &mut Self {
let mut cond = field.to_string();
cond.push_str(" IS NOT NULL");
self.or_where(&cond)
}
pub fn or_where_in<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| (*v).to_string())
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" IN (");
cond.push_str(&list);
cond.push(')');
self.or_where(&cond)
}
pub fn or_where_in_quoted<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| quote((*v).to_string()))
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" IN (");
cond.push_str(&list);
cond.push(')');
self.or_where(&cond)
}
pub fn or_where_not_in<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| (*v).to_string())
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" NOT IN (");
cond.push_str(&list);
cond.push(')');
self.or_where(&cond)
}
pub fn or_where_not_in_quoted<S, T>(&mut self, field: S, list: &[T]) -> &mut Self
where
S: ToString,
T: ToString,
{
let list: Vec<String> = list
.iter()
.map(|v| quote((*v).to_string()))
.collect::<Vec<String>>();
let list = list.join(", ");
let mut cond = field.to_string();
cond.push_str(" NOT IN (");
cond.push_str(&list);
cond.push(')');
self.or_where(&cond)
}
pub fn or_where_in_query<S, T>(&mut self, field: S, query: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" IN (");
cond.push_str(&query.to_string());
cond.push(')');
self.or_where(&cond)
}
pub fn or_where_not_in_query<S, T>(&mut self, field: S, query: T) -> &mut Self
where
S: ToString,
T: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT IN (");
cond.push_str(&query.to_string());
cond.push(')');
self.or_where(&cond)
}
pub fn or_where_between<S, T, U>(&mut self, field: S, min: T, max: U) -> &mut Self
where
S: ToString,
T: ToString,
U: ToString,
{
let mut cond = field.to_string();
cond.push_str(" BETWEEN ");
cond.push_str(&min.to_string());
cond.push_str(" AND ");
cond.push_str(&max.to_string());
self.or_where(&cond)
}
pub fn or_where_not_between<S, T, U>(&mut self, field: S, min: T, max: U) -> &mut Self
where
S: ToString,
T: ToString,
U: ToString,
{
let mut cond = field.to_string();
cond.push_str(" NOT BETWEEN ");
cond.push_str(&min.to_string());
cond.push_str(" AND ");
cond.push_str(&max.to_string());
self.or_where(&cond)
}
pub fn union<S: ToString>(&mut self, query: S) -> &mut Self {
let append = format!(" UNION {}", &query.to_string());
self.unions.push_str(&append);
self
}
pub fn union_all<S: ToString>(&mut self, query: S) -> &mut Self {
self.unions.push_str(" UNION ALL ");
self.unions.push_str(&query.to_string());
self
}
pub fn order_by<S: ToString>(&mut self, field: S, desc: bool) -> &mut Self {
let order = if desc {
format!("{} DESC", &field.to_string())
} else {
field.to_string()
};
self.order_by.push(order);
self
}
pub fn order_asc<S: ToString>(&mut self, field: S) -> &mut Self {
self.order_by(&field.to_string(), false)
}
pub fn order_desc<S: ToString>(&mut self, field: S) -> &mut Self {
self.order_by(&field.to_string(), true)
}
pub fn limit<S: ToString>(&mut self, limit: S) -> &mut Self {
self.limit = Some(limit.to_string());
self
}
pub fn offset<S: ToString>(&mut self, offset: S) -> &mut Self {
self.offset = Some(offset.to_string());
self
}
pub fn sql(&self) -> Result<String> {
match self.statement {
Statement::SelectFrom => self.sql_select(),
Statement::SelectValues => self.sql_select_values(),
Statement::UpdateTable => self.sql_update(),
Statement::InsertInto => self.sql_insert(),
Statement::DeleteFrom => self.sql_delete(),
}
}
fn sql_select(&self) -> Result<String> {
if self.table.is_empty() {
return Err(SqlBuilderError::NoTableName.into());
}
let mut text = self.query()?;
text.push(';');
Ok(text)
}
fn sql_select_values(&self) -> Result<String> {
if self.fields.is_empty() {
return Err(SqlBuilderError::NoValues.into());
}
let mut text = self.query_values()?;
text.push(';');
Ok(text)
}
pub fn subquery(&self) -> Result<String> {
let text = self.query()?;
let text = format!("({})", &text);
Ok(text)
}
pub fn subquery_as<S: ToString>(&self, name: S) -> Result<String> {
let mut text = "(".to_string();
text.push_str(&self.query()?);
text.push_str(") AS ");
text.push_str(&name.to_string());
Ok(text)
}
pub fn query(&self) -> Result<String> {
let distinct = if self.distinct { " DISTINCT" } else { "" };
let fields = if self.fields.is_empty() {
"*".to_string()
} else {
self.fields.join(", ")
};
let joins = if self.joins.is_empty() {
String::new()
} else {
format!(" {}", self.joins.join(" "))
};
let group_by = if self.group_by.is_empty() {
String::new()
} else {
let having = if let Some(having) = &self.having {
format!(" HAVING {}", having)
} else {
String::new()
};
format!(" GROUP BY {}{}", self.group_by.join(", "), having)
};
let wheres = SqlBuilder::make_wheres(&self.wheres);
let order_by = if self.order_by.is_empty() || !self.unions.is_empty() {
String::new()
} else {
format!(" ORDER BY {}", self.order_by.join(", "))
};
let limit = match &self.limit {
Some(limit) => format!(" LIMIT {}", limit),
None => String::new(),
};
let offset = match &self.offset {
Some(offset) => format!(" OFFSET {}", offset),
None => String::new(),
};
let sql = format!("SELECT{distinct} {fields} FROM {table}{joins}{wheres}{group_by}{unions}{order_by}{limit}{offset}",
distinct = distinct,
fields = fields,
table = &self.table,
joins = joins,
group_by = group_by,
wheres = wheres,
unions = &self.unions,
order_by = order_by,
limit = limit,
offset = offset,
);
Ok(sql)
}
pub fn query_values(&self) -> Result<String> {
let fields = self.fields.join(", ");
let sql = format!("SELECT {fields}", fields = fields);
Ok(sql)
}
fn sql_insert(&self) -> Result<String> {
if self.table.is_empty() {
return Err(SqlBuilderError::NoTableName.into());
}
let fields = self.fields.join(", ");
let sql = match &self.values {
Values::Empty => return Err(SqlBuilderError::NoValues.into()),
Values::List(values) => {
if values.is_empty() {
return Err(SqlBuilderError::NoValues.into());
}
let values = values.join(", ");
let returning = if let Some(ret) = &self.returning {
format!(" RETURNING {}", ret)
} else {
"".to_string()
};
format!(
"INSERT INTO {table} ({fields}) VALUES {values}{returning};",
table = &self.table,
fields = fields,
values = values,
returning = returning,
)
}
Values::Select(query) => {
format!(
"INSERT INTO {table} ({fields}) {query};",
table = &self.table,
fields = fields,
query = query,
)
}
};
Ok(sql)
}
fn sql_update(&self) -> Result<String> {
if self.table.is_empty() {
return Err(SqlBuilderError::NoTableName.into());
}
if self.sets.is_empty() {
return Err(SqlBuilderError::NoSetFields.into());
}
let sets = self.sets.join(", ");
let wheres = SqlBuilder::make_wheres(&self.wheres);
let returning = if let Some(ret) = &self.returning {
format!(" RETURNING {}", ret)
} else {
"".to_string()
};
let sql = format!(
"UPDATE {table} SET {sets}{wheres}{returning};",
table = &self.table,
sets = sets,
wheres = wheres,
returning = returning,
);
Ok(sql)
}
fn sql_delete(&self) -> Result<String> {
if self.table.is_empty() {
return Err(SqlBuilderError::NoTableName.into());
}
let wheres = SqlBuilder::make_wheres(&self.wheres);
let sql = format!(
"DELETE FROM {table}{wheres};",
table = &self.table,
wheres = wheres,
);
Ok(sql)
}
fn make_wheres(wheres: &[String]) -> String {
match wheres.len() {
0 => String::new(),
1 => {
let wheres = wheres[0].to_string();
format!(" WHERE {}", wheres)
}
_ => {
let wheres: Vec<String> = wheres.iter().map(|w| format!("({})", w)).collect();
format!(" WHERE {}", wheres.join(" AND "))
}
}
}
}
pub fn esc<S: ToString>(src: S) -> String {
src.to_string().replace("'", "''")
}
pub fn quote<S: ToString>(src: S) -> String {
format!("'{}'", esc(src.to_string()))
}
pub fn baquote<S: ToString>(src: S) -> String {
format!("`{}`", src.to_string().replace("`", "\\`"))
}
pub fn brquote<S: ToString>(src: S) -> String {
format!("[{}]", src.to_string().replace("]", "]]"))
}
pub fn dquote<S: ToString>(src: S) -> String {
format!("\"{}\"", src.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_esc() -> Result<()> {
let sql = esc("Hello, 'World'");
assert_eq!(&sql, "Hello, ''World''");
Ok(())
}
#[test]
fn test_quote() -> Result<()> {
let sql = quote("Hello, 'World'");
assert_eq!(&sql, "'Hello, ''World'''");
let sql = baquote("Hello, 'World'");
assert_eq!(&sql, "`Hello, 'World'`");
let sql = dquote("Hello, 'World'");
assert_eq!(&sql, "\"Hello, 'World'\"");
Ok(())
}
#[test]
fn test_select_only_values() -> Result<()> {
let values = SqlBuilder::select_values(&["10", "e("100")]).sql()?;
assert_eq!("SELECT 10, '100';", &values);
Ok(())
}
#[test]
fn test_select_all_books() -> Result<()> {
let sql = SqlBuilder::select_from("books").sql()?;
assert_eq!(&sql, "SELECT * FROM books;");
Ok(())
}
#[test]
fn test_show_all_prices() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.distinct()
.field("price")
.sql()?;
assert_eq!(&sql, "SELECT DISTINCT price FROM books;");
Ok(())
}
#[test]
fn test_select_title_and_price() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.fields(&["title", "price"])
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books;");
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books;");
Ok(())
}
#[test]
fn test_select_expensive_books() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where("price > 100")
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books WHERE price > 100;");
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_gt("price", 200)
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books WHERE price > 200;");
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_ge("price", 300)
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books WHERE price >= 300;");
Ok(())
}
#[test]
fn test_select_price_for_harry_potter_and_phil_stone() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("price")
.and_where_eq("title", quote("Harry Potter and the Philosopher's Stone"))
.sql()?;
assert_eq!(
&sql,
"SELECT price FROM books WHERE title = 'Harry Potter and the Philosopher''s Stone';"
);
Ok(())
}
#[test]
fn test_select_price_not_for_harry_potter_and_phil_stone() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("price")
.and_where_ne("title", quote("Harry Potter and the Philosopher's Stone"))
.sql()?;
assert_eq!(
&sql,
"SELECT price FROM books WHERE title <> 'Harry Potter and the Philosopher''s Stone';"
);
Ok(())
}
#[test]
fn test_select_expensive_harry_potter() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where("price > 100")
.and_where_like_left("title", "Harry Potter")
.sql()?;
assert_eq!(
&sql,
"SELECT title, price FROM books WHERE (price > 100) AND (title LIKE 'Harry Potter%');"
);
Ok(())
}
#[test]
fn test_select_strange_books() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where("price < 2")
.or_where("price > 1000")
.or_where_eq("title", quote("Harry Potter and the Philosopher's Stone"))
.or_where_ne("price", 100)
.or_where_like("title", "Alice's")
.or_where_not_like_any("LOWER(title)", " the ")
.or_where_is_null("title")
.or_where_is_not_null("price")
.sql()?;
assert_eq!(
&sql,
"SELECT title, price FROM books WHERE price < 2 OR price > 1000 OR title = 'Harry Potter and the Philosopher''s Stone' OR price <> 100 OR title LIKE 'Alice''s' OR LOWER(title) NOT LIKE '% the %' OR title IS NULL OR price IS NOT NULL;"
);
Ok(())
}
#[test]
fn test_order_harry_potter_by_price() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_by("price", false)
.sql()?;
assert_eq!(
&sql,
"SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' ORDER BY price;"
);
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_desc("price")
.sql()?;
assert_eq!(
&sql,
"SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' ORDER BY price DESC;"
);
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_desc("price")
.order_asc("title")
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' ORDER BY price DESC, title;");
Ok(())
}
#[test]
fn test_find_cheap_or_harry_potter() -> Result<()> {
let append = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where("price < 100")
.order_asc("title")
.query()?;
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_desc("price")
.union(&append)
.sql()?;
assert_eq!(
"SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' UNION SELECT title, price FROM books WHERE price < 100 ORDER BY title;",
&sql
);
let append = SqlBuilder::select_values(&["'The Great Gatsby'", "124"]).query_values()?;
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_desc("price")
.union_all(&append)
.sql()?;
assert_eq!(
"SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' UNION ALL SELECT 'The Great Gatsby', 124;",
&sql
);
Ok(())
}
#[test]
fn test_select_first_3_harry_potter_books() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_asc("title")
.limit(3)
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' ORDER BY title LIMIT 3;");
Ok(())
}
#[test]
fn test_select_harry_potter_from_second_book() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_asc("title")
.offset(2)
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' ORDER BY title OFFSET 2;");
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.and_where_like_left("title", "Harry Potter")
.order_asc("title")
.limit(3)
.offset(2)
.sql()?;
assert_eq!(&sql, "SELECT title, price FROM books WHERE title LIKE 'Harry Potter%' ORDER BY title LIMIT 3 OFFSET 2;");
Ok(())
}
#[test]
fn test_find_books_not_about_alice() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.and_where_not_like_any("title", "Alice's")
.sql()?;
assert_eq!(
"SELECT title FROM books WHERE title NOT LIKE '%Alice''s%';",
&sql
);
Ok(())
}
#[test]
fn test_books_without_price() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("title")
.and_where_is_null("price")
.sql()?;
assert_eq!(&sql, "SELECT title FROM books WHERE price IS NULL;");
let sql = SqlBuilder::select_from("books")
.field("title")
.and_where_is_not_null("price")
.sql()?;
assert_eq!(&sql, "SELECT title FROM books WHERE price IS NOT NULL;");
Ok(())
}
#[test]
fn test_group_books_by_price() -> Result<()> {
let sql = SqlBuilder::select_from("books")
.field("price")
.field("COUNT(price) AS cnt")
.group_by("price")
.order_desc("cnt")
.sql()?;
assert_eq!(
&sql,
"SELECT price, COUNT(price) AS cnt FROM books GROUP BY price ORDER BY cnt DESC;"
);
let sql = SqlBuilder::select_from("books")
.field("price")
.field("COUNT(price) AS cnt")
.group_by("price")
.having("price > 100")
.order_desc("cnt")
.sql()?;
assert_eq!(&sql, "SELECT price, COUNT(price) AS cnt FROM books GROUP BY price HAVING price > 100 ORDER BY cnt DESC;");
let sql = SqlBuilder::select_from("books")
.field("price")
.field("COUNT(price) AS cnt")
.group_by("price")
.and_where("price > 100")
.order_desc("cnt")
.sql()?;
assert_eq!(&sql, "SELECT price, COUNT(price) AS cnt FROM books WHERE price > 100 GROUP BY price ORDER BY cnt DESC;");
Ok(())
}
#[test]
fn test_group_books_by_price_category() -> Result<()> {
let cat = SqlBuilder::select_from("books")
.field("CASE WHEN price < 100 THEN 'cheap' ELSE 'expensive' END AS category")
.subquery()?;
assert_eq!("(SELECT CASE WHEN price < 100 THEN 'cheap' ELSE 'expensive' END AS category FROM books)", &cat);
let sql = SqlBuilder::select_from(&cat)
.field("category")
.field("COUNT(category) AS cnt")
.group_by("category")
.order_desc("cnt")
.order_asc("category")
.sql()?;
assert_eq!("SELECT category, COUNT(category) AS cnt FROM (SELECT CASE WHEN price < 100 THEN 'cheap' ELSE 'expensive' END AS category FROM books) GROUP BY category ORDER BY cnt DESC, category;", &sql);
let cat = SqlBuilder::select_from("books")
.field("CASE WHEN price < 100 THEN 'cheap' ELSE 'expensive' END")
.subquery_as("category")?;
assert_eq!("(SELECT CASE WHEN price < 100 THEN 'cheap' ELSE 'expensive' END FROM books) AS category", &cat);
let sql = SqlBuilder::select_from("books")
.field("title")
.field("price")
.field(&cat)
.sql()?;
assert_eq!("SELECT title, price, (SELECT CASE WHEN price < 100 THEN 'cheap' ELSE 'expensive' END FROM books) AS category FROM books;", &sql);
Ok(())
}
#[test]
fn test_grow_price() -> Result<()> {
let sql = SqlBuilder::update_table("books")
.set("price", "price + 10")
.sql()?;
assert_eq!(&sql, "UPDATE books SET price = price + 10;");
let sql = SqlBuilder::update_table("books")
.set("price", "price * 0.1")
.and_where_like_left("title", "Harry Potter")
.returning_id()
.sql()?;
assert_eq!(
&sql,
"UPDATE books SET price = price * 0.1 WHERE title LIKE 'Harry Potter%' RETURNING id;"
);
Ok(())
}
#[test]
fn test_add_new_books() -> Result<()> {
let sql = SqlBuilder::insert_into("books")
.field("title")
.field("price")
.values(&[quote("In Search of Lost Time"), 150.to_string()])
.values(&["'Don Quixote', 200"])
.sql()?;
assert_eq!(&sql, "INSERT INTO books (title, price) VALUES ('In Search of Lost Time', 150), ('Don Quixote', 200);");
let sql = SqlBuilder::insert_into("books")
.field("title")
.field("price")
.values(&["'Don Quixote', 200"])
.returning_id()
.sql()?;
assert_eq!(
&sql,
"INSERT INTO books (title, price) VALUES ('Don Quixote', 200) RETURNING id;"
);
Ok(())
}
#[test]
fn test_add_books_from_warehouse() -> Result<()> {
let query = SqlBuilder::select_from("warehouse")
.field("title")
.field("preliminary_price * 2")
.query()?;
assert_eq!("SELECT title, preliminary_price * 2 FROM warehouse", &query);
let sql = SqlBuilder::insert_into("books")
.field("title")
.field("price")
.select(&query)
.sql()?;
assert_eq!(
"INSERT INTO books (title, price) SELECT title, preliminary_price * 2 FROM warehouse;",
&sql
);
Ok(())
}
#[test]
fn test_sold_all_harry_potter() -> Result<()> {
let sql = SqlBuilder::update_table("books")
.set("price", 0)
.set("title", "'[SOLD!]' || title")
.and_where_like_left("title", "Harry Potter")
.sql()?;
assert_eq!(&sql, "UPDATE books SET price = 0, title = '[SOLD!]' || title WHERE title LIKE 'Harry Potter%';");
Ok(())
}
#[test]
fn test_mark_as_not_distr() -> Result<()> {
let sql = SqlBuilder::update_table("books")
.set_str("comment", "Don't distribute!")
.and_where_le("price", "100")
.returning("id, comment")
.sql()?;
assert_eq!(
"UPDATE books SET comment = 'Don''t distribute!' WHERE price <= 100 RETURNING id, comment;",
&sql
);
Ok(())
}
#[test]
fn test_remove_all_expensive_books() -> Result<()> {
let sql = SqlBuilder::delete_from("books")
.and_where("price > 100")
.sql()?;
assert_eq!(&sql, "DELETE FROM books WHERE price > 100;");
Ok(())
}
#[test]
fn test_count_books_in_shops() -> Result<()> {
let sql = SqlBuilder::select_from("books AS b")
.field("b.title")
.field("s.total")
.left_outer()
.join("shops AS s")
.on("b.id = s.book")
.sql()?;
assert_eq!(
&sql,
"SELECT b.title, s.total FROM books AS b LEFT OUTER JOIN shops AS s ON b.id = s.book;"
);
let sql = SqlBuilder::select_from("books AS b")
.field("b.title")
.field("s.total")
.left_outer()
.join("shops AS s")
.on_eq("b.id", "s.book")
.sql()?;
assert_eq!(
&sql,
"SELECT b.title, s.total FROM books AS b LEFT OUTER JOIN shops AS s ON b.id = s.book;"
);
Ok(())
}
}