#![forbid(unsafe_code)]
pub mod queries;
pub use queries::create_table::{CreateTable, T, TableBuilder};
pub use queries::delete::{D, Delete, DeleteBuilder};
pub use queries::drop_table::DropTable;
pub use queries::insert::{I, Insert, InsertBuilder, InsertSource, OnConflict};
pub use queries::select::{Columns, Select, SelectExpression};
pub use queries::update::{U, Update, UpdateBuilder};
pub trait Sql {
fn sql(&self) -> String;
}
pub trait Build {
fn build(&self) -> Self;
}
pub trait Parameterized {
fn param(&mut self) -> String;
}
#[derive(Clone)]
pub enum Distinct<'a> {
All,
On(Vec<&'a str>),
}
#[derive(Clone)]
pub enum Op<'a> {
And,
Or,
Equals,
NotEquals,
GreaterThan,
LessThan,
GreaterOrEqual,
LessOrEqual,
Like,
In,
Exists,
NotExists,
Any,
All,
O(&'a str),
}
impl<'a> Sql for Op<'a> {
fn sql(&self) -> String {
match &self {
Op::And => "AND",
Op::Or => "OR",
Op::Equals => "=",
Op::NotEquals => "!=",
Op::GreaterThan => ">",
Op::LessThan => "<",
Op::GreaterOrEqual => ">=",
Op::LessOrEqual => "<=",
Op::Like => "LIKE",
Op::In => "IN",
Op::Exists => "EXISTS",
Op::NotExists => "NOT EXISTS",
Op::Any => "ANY",
Op::All => "ALL",
Op::O(s) => s,
}
.to_string()
}
}
#[derive(Clone)]
pub enum Term<'a> {
Atom(&'a str),
Condition(Box<Term<'a>>, Op<'a>, Box<Term<'a>>),
Parens(Box<Term<'a>>),
Null,
Subquery(Box<Query<'a>>),
Not(Box<Term<'a>>),
Cast(Box<Term<'a>>, &'a str),
PgCast(Box<Term<'a>>, &'a str),
Case(CaseExpression<'a>),
Coalesce(Vec<Term<'a>>),
NullIf(Box<Term<'a>>, Box<Term<'a>>),
Concat(Vec<Term<'a>>),
Substring(Box<Term<'a>>, Option<Box<Term<'a>>>, Option<Box<Term<'a>>>),
Upper(Box<Term<'a>>),
Lower(Box<Term<'a>>),
Now,
CurrentDate,
Interval(&'a str),
DateAdd(Box<Term<'a>>, Box<Term<'a>>),
DateSub(Box<Term<'a>>, Box<Term<'a>>),
}
impl<'a> Sql for CaseExpression<'a> {
fn sql(&self) -> String {
let mut s = "CASE".to_string();
for wt in &self.when_thens {
s.push_str(&format!(" WHEN {} THEN {}", wt.when.sql(), wt.then.sql()));
}
if let Some(et) = &self.else_term {
s.push_str(&format!(" ELSE {}", et.sql()));
}
s.push_str(" END");
s
}
}
#[derive(Clone)]
pub struct WhenThen<'a> {
pub when: Term<'a>,
pub then: Term<'a>,
}
#[derive(Clone)]
pub struct CaseExpression<'a> {
pub when_thens: Vec<WhenThen<'a>>,
pub else_term: Option<Box<Term<'a>>>,
}
impl<'a> Sql for Term<'a> {
fn sql(&self) -> String {
match &self {
Term::Atom(s) => s.to_string(),
Term::Condition(t1, op, t2) => format!("{} {} {}", t1.sql(), op.sql(), t2.sql()),
Term::Null => "".to_string(),
Term::Parens(t) => format!("({})", t.sql()),
Term::Subquery(q) => format!("({})", q.sql()),
Term::Not(t) => format!("NOT {}", t.sql()),
Term::Cast(t, ty) => format!("CAST({} AS {})", t.sql(), ty),
Term::PgCast(t, ty) => format!("{}::{}", t.sql(), ty),
Term::Case(c) => c.sql(),
Term::Coalesce(terms) => {
let terms_sql: Vec<String> = terms.iter().map(|t| t.sql()).collect();
format!("COALESCE({})", terms_sql.join(", "))
}
Term::NullIf(t1, t2) => format!("NULLIF({}, {})", t1.sql(), t2.sql()),
Term::Concat(terms) => {
let terms_sql: Vec<String> = terms.iter().map(|t| t.sql()).collect();
format!("CONCAT({})", terms_sql.join(", "))
}
Term::Substring(t, from, for_) => {
let mut s = format!("SUBSTRING({}", t.sql());
if let Some(f) = from {
s.push_str(&format!(" FROM {}", f.sql()));
}
if let Some(f) = for_ {
s.push_str(&format!(" FOR {}", f.sql()));
}
s.push(')');
s
}
Term::Upper(t) => format!("UPPER({})", t.sql()),
Term::Lower(t) => format!("LOWER({})", t.sql()),
Term::Now => "NOW()".to_string(),
Term::CurrentDate => "CURRENT_DATE".to_string(),
Term::Interval(s) => format!("INTERVAL '{}'", s),
Term::DateAdd(t1, t2) => format!("{} + {}", t1.sql(), t2.sql()),
Term::DateSub(t1, t2) => format!("{} - {}", t1.sql(), t2.sql()),
}
}
}
pub fn eq<'a>(left: &'a str, right: &'a str) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(left)),
Op::Equals,
Box::new(Term::Atom(right)),
)
}
pub fn ne<'a>(left: &'a str, right: &'a str) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(left)),
Op::NotEquals,
Box::new(Term::Atom(right)),
)
}
pub fn gt<'a>(left: &'a str, right: &'a str) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(left)),
Op::GreaterThan,
Box::new(Term::Atom(right)),
)
}
pub fn lt<'a>(left: &'a str, right: &'a str) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(left)),
Op::LessThan,
Box::new(Term::Atom(right)),
)
}
pub fn gte<'a>(left: &'a str, right: &'a str) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(left)),
Op::GreaterOrEqual,
Box::new(Term::Atom(right)),
)
}
pub fn lte<'a>(left: &'a str, right: &'a str) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(left)),
Op::LessOrEqual,
Box::new(Term::Atom(right)),
)
}
pub fn like<'a>(left: &'a str, right: &'a str) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(left)),
Op::Like,
Box::new(Term::Atom(right)),
)
}
pub fn and<'a>(left: Term<'a>, right: Term<'a>) -> Term<'a> {
Term::Condition(Box::new(left), Op::And, Box::new(right))
}
pub fn or<'a>(left: Term<'a>, right: Term<'a>) -> Term<'a> {
Term::Condition(Box::new(left), Op::Or, Box::new(right))
}
pub fn not<'a>(term: Term<'a>) -> Term<'a> {
Term::Not(Box::new(term))
}
pub fn cast<'a>(term: Term<'a>, type_name: &'a str) -> Term<'a> {
Term::Cast(Box::new(term), type_name)
}
pub fn pg_cast<'a>(term: Term<'a>, type_name: &'a str) -> Term<'a> {
Term::PgCast(Box::new(term), type_name)
}
pub fn case<'a>(when_thens: Vec<WhenThen<'a>>, else_term: Option<Term<'a>>) -> Term<'a> {
Term::Case(CaseExpression {
when_thens,
else_term: else_term.map(Box::new),
})
}
pub fn coalesce<'a>(terms: Vec<Term<'a>>) -> Term<'a> {
Term::Coalesce(terms)
}
pub fn nullif<'a>(left: Term<'a>, right: Term<'a>) -> Term<'a> {
Term::NullIf(Box::new(left), Box::new(right))
}
pub fn concat<'a>(terms: Vec<Term<'a>>) -> Term<'a> {
Term::Concat(terms)
}
pub fn substring<'a>(term: Term<'a>, from: Option<Term<'a>>, for_: Option<Term<'a>>) -> Term<'a> {
Term::Substring(Box::new(term), from.map(Box::new), for_.map(Box::new))
}
pub fn upper<'a>(term: Term<'a>) -> Term<'a> {
Term::Upper(Box::new(term))
}
pub fn lower<'a>(term: Term<'a>) -> Term<'a> {
Term::Lower(Box::new(term))
}
pub fn now<'a>() -> Term<'a> {
Term::Now
}
pub fn current_date<'a>() -> Term<'a> {
Term::CurrentDate
}
pub fn interval<'a>(s: &'a str) -> Term<'a> {
Term::Interval(s)
}
pub fn date_add<'a>(left: Term<'a>, right: Term<'a>) -> Term<'a> {
Term::DateAdd(Box::new(left), Box::new(right))
}
pub fn date_sub<'a>(left: Term<'a>, right: Term<'a>) -> Term<'a> {
Term::DateSub(Box::new(left), Box::new(right))
}
pub fn parens<'a>(term: Term<'a>) -> Term<'a> {
Term::Parens(Box::new(term))
}
pub fn in_<'a>(column: &'a str, values: Vec<&'a str>) -> Term<'a> {
let values_str = values.join(", ");
Term::Atom(Box::leak(
format!("{} IN ({})", column, values_str).into_boxed_str(),
))
}
pub fn between<'a>(column: &'a str, low: &'a str, high: &'a str) -> Term<'a> {
Term::Atom(Box::leak(
format!("{} BETWEEN {} AND {}", column, low, high).into_boxed_str(),
))
}
pub fn is_null<'a>(column: &'a str) -> Term<'a> {
Term::Atom(Box::leak(format!("{} IS NULL", column).into_boxed_str()))
}
pub fn is_not_null<'a>(column: &'a str) -> Term<'a> {
Term::Atom(Box::leak(
format!("{} IS NOT NULL", column).into_boxed_str(),
))
}
pub fn exists<'a>(subquery: Query<'a>) -> Term<'a> {
let sql = format!("EXISTS ({})", subquery.sql());
Term::Atom(Box::leak(sql.into_boxed_str()))
}
pub fn not_exists<'a>(subquery: Query<'a>) -> Term<'a> {
let sql = format!("NOT EXISTS ({})", subquery.sql());
Term::Atom(Box::leak(sql.into_boxed_str()))
}
pub fn in_subquery<'a>(column: &'a str, subquery: Query<'a>) -> Term<'a> {
Term::Condition(
Box::new(Term::Atom(column)),
Op::In,
Box::new(Term::Subquery(Box::new(subquery))),
)
}
pub fn any<'a>(column: &'a str, op: Op<'a>, subquery: Query<'a>) -> Term<'a> {
let sql = format!("{} {} ANY ({})", column, op.sql(), subquery.sql());
Term::Atom(Box::leak(sql.into_boxed_str()))
}
pub fn all<'a>(column: &'a str, op: Op<'a>, subquery: Query<'a>) -> Term<'a> {
let sql = format!("{} {} ALL ({})", column, op.sql(), subquery.sql());
Term::Atom(Box::leak(sql.into_boxed_str()))
}
pub fn p(n: usize) -> String {
format!("${}", n)
}
pub struct PgParams {
count: usize,
}
impl PgParams {
pub fn new() -> Self {
PgParams { count: 0 }
}
pub fn seq(&mut self) -> String {
self.count += 1;
format!("${}", self.count)
}
}
impl Default for PgParams {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct Having<'a> {
pub term: Term<'a>,
}
impl<'a> Having<'a> {
pub fn new(t: Term<'a>) -> Having<'a> {
Having { term: t }
}
}
impl<'a> Sql for Having<'a> {
fn sql(&self) -> String {
self.term.sql().to_string()
}
}
#[derive(Clone)]
pub enum OrderedColumn<'a> {
Asc(&'a str),
Desc(&'a str),
}
#[derive(Clone)]
pub struct OrderBy<'a> {
pub columns: Vec<OrderedColumn<'a>>,
}
impl<'a> Sql for OrderBy<'a> {
fn sql(&self) -> String {
let mut result = "ORDER BY ".to_string();
let mut first = true;
for c in &self.columns {
if !first {
result.push_str(", ");
}
first = false;
match c {
OrderedColumn::Asc(s) => result.push_str(&format!("{} ASC", s)),
OrderedColumn::Desc(s) => result.push_str(&format!("{} DESC", s)),
}
}
result
}
}
#[derive(Clone)]
pub enum FromSource<'a> {
Table(&'a str),
Subquery(Box<Query<'a>>, &'a str),
}
impl<'a> Sql for FromSource<'a> {
fn sql(&self) -> String {
match self {
FromSource::Table(table) => table.to_string(),
FromSource::Subquery(query, alias) => format!("({}) AS {}", query.sql(), alias),
}
}
}
#[derive(Clone)]
pub enum JoinType {
Inner,
Left,
Right,
Full,
Cross,
}
impl Sql for JoinType {
fn sql(&self) -> String {
match self {
JoinType::Inner => "INNER JOIN",
JoinType::Left => "LEFT JOIN",
JoinType::Right => "RIGHT JOIN",
JoinType::Full => "FULL JOIN",
JoinType::Cross => "CROSS JOIN",
}
.to_string()
}
}
#[derive(Clone)]
pub struct Join<'a> {
pub join_type: JoinType,
pub source: FromSource<'a>,
pub on: Option<Term<'a>>,
}
impl<'a> Sql for Join<'a> {
fn sql(&self) -> String {
let mut result = format!("{} {}", self.join_type.sql(), self.source.sql());
if let Some(condition) = &self.on {
result.push_str(&format!(" ON {}", condition.sql()));
}
result
}
}
#[derive(Clone)]
pub struct Cte<'a> {
pub name: &'a str,
pub query: Box<Query<'a>>,
}
impl<'a> Sql for Cte<'a> {
fn sql(&self) -> String {
format!("{} AS ({})", self.name, self.query.sql())
}
}
#[derive(Clone)]
pub struct Query<'a> {
pub with_clause: Option<Vec<Cte<'a>>>,
pub select: Option<Select<'a>>,
pub from: Option<FromSource<'a>>,
pub joins: Vec<Join<'a>>,
pub where_clause: Option<Term<'a>>,
pub group_by: Option<Vec<&'a str>>,
pub having: Option<Having<'a>>,
pub order_by: Option<OrderBy<'a>>,
pub limit: Option<u64>,
pub offset: Option<u64>,
pub for_update: bool,
}
pub struct QueryBuilder<'a> {
pub with_clause: Option<Vec<Cte<'a>>>,
pub select: Option<Select<'a>>,
pub from: Option<FromSource<'a>>,
pub joins: Vec<Join<'a>>,
pub where_clause: Option<Term<'a>>,
pub group_by: Option<Vec<&'a str>>,
pub having: Option<Having<'a>>,
pub order_by: Option<OrderBy<'a>>,
pub limit: Option<u64>,
pub offset: Option<u64>,
pub for_update: bool,
pub params: PgParams,
}
#[allow(non_snake_case)]
pub fn Q<'a>() -> QueryBuilder<'a> {
QueryBuilder {
with_clause: None,
select: None,
from: None,
joins: Vec::new(),
where_clause: None,
group_by: None,
having: None,
order_by: None,
limit: None,
offset: None,
for_update: false,
params: PgParams::new(),
}
}
impl<'a> QueryBuilder<'a> {
pub fn build(&self) -> Query<'a> {
Query {
with_clause: self.with_clause.clone(),
select: self.select.clone(),
from: self.from.clone(),
joins: self.joins.clone(),
where_clause: self.where_clause.clone(),
group_by: self.group_by.clone(),
having: self.having.clone(),
order_by: self.order_by.clone(),
limit: self.limit,
offset: self.offset,
for_update: self.for_update,
}
}
pub fn with(&'a mut self, name: &'a str, query: Query<'a>) -> &'a mut QueryBuilder<'a> {
let cte = Cte {
name,
query: Box::new(query),
};
match &mut self.with_clause {
None => self.with_clause = Some(vec![cte]),
Some(ctes) => ctes.push(cte),
}
self
}
pub fn select(&'a mut self, cols: Vec<&'a str>) -> &'a mut QueryBuilder<'a> {
self.select = Some(Select::new(Columns::Selected(cols), None));
self
}
pub fn select_expressions(
&'a mut self,
exprs: Vec<SelectExpression<'a>>,
) -> &'a mut QueryBuilder<'a> {
self.select = Some(Select::new(Columns::Expressions(exprs), None));
self
}
pub fn distinct(&'a mut self) -> &'a mut QueryBuilder<'a> {
if let Some(s) = &mut self.select {
s.distinct = Some(Distinct::All);
}
self
}
pub fn distinct_on(&'a mut self, cols: Vec<&'a str>) -> &'a mut QueryBuilder<'a> {
if let Some(s) = &mut self.select {
s.distinct = Some(Distinct::On(cols));
}
self
}
pub fn from(&'a mut self, table: &'a str) -> &'a mut QueryBuilder<'a> {
self.from = Some(FromSource::Table(table));
self
}
pub fn from_subquery(
&'a mut self,
subquery: Query<'a>,
alias: &'a str,
) -> &'a mut QueryBuilder<'a> {
self.from = Some(FromSource::Subquery(Box::new(subquery), alias));
self
}
pub fn inner_join(&'a mut self, table: &'a str, on: Term<'a>) -> &'a mut QueryBuilder<'a> {
self.joins.push(Join {
join_type: JoinType::Inner,
source: FromSource::Table(table),
on: Some(on),
});
self
}
pub fn left_join(&'a mut self, table: &'a str, on: Term<'a>) -> &'a mut QueryBuilder<'a> {
self.joins.push(Join {
join_type: JoinType::Left,
source: FromSource::Table(table),
on: Some(on),
});
self
}
pub fn right_join(&'a mut self, table: &'a str, on: Term<'a>) -> &'a mut QueryBuilder<'a> {
self.joins.push(Join {
join_type: JoinType::Right,
source: FromSource::Table(table),
on: Some(on),
});
self
}
pub fn full_join(&'a mut self, table: &'a str, on: Term<'a>) -> &'a mut QueryBuilder<'a> {
self.joins.push(Join {
join_type: JoinType::Full,
source: FromSource::Table(table),
on: Some(on),
});
self
}
pub fn cross_join(&'a mut self, table: &'a str) -> &'a mut QueryBuilder<'a> {
self.joins.push(Join {
join_type: JoinType::Cross,
source: FromSource::Table(table),
on: None,
});
self
}
pub fn join_subquery(
&'a mut self,
join_type: JoinType,
subquery: Query<'a>,
alias: &'a str,
on: Term<'a>,
) -> &'a mut QueryBuilder<'a> {
self.joins.push(Join {
join_type,
source: FromSource::Subquery(Box::new(subquery), alias),
on: Some(on),
});
self
}
pub fn where_(&'a mut self, term: Term<'a>) -> &'a mut QueryBuilder<'a> {
self.where_clause = Some(term);
self
}
pub fn where_opt(&'a mut self, term: Option<Term<'a>>) -> &'a mut QueryBuilder<'a> {
if let Some(t) = term {
self.where_clause = Some(t);
}
self
}
pub fn and_where(&'a mut self, term: Term<'a>) -> &'a mut QueryBuilder<'a> {
match &self.where_clause {
None => self.where_clause = Some(term),
Some(existing) => {
self.where_clause = Some(Term::Condition(
Box::new(existing.clone()),
Op::And,
Box::new(term),
));
}
}
self
}
pub fn group_by(&'a mut self, cols: Vec<&'a str>) -> &'a mut QueryBuilder<'a> {
self.group_by = Some(cols);
self
}
pub fn having(&'a mut self, term: Term<'a>) -> &'a mut QueryBuilder<'a> {
self.having = Some(Having::new(term));
self
}
pub fn order_by(&'a mut self, cols: Vec<OrderedColumn<'a>>) -> &'a mut QueryBuilder<'a> {
self.order_by = Some(OrderBy { columns: cols });
self
}
pub fn limit(&'a mut self, limit: u64) -> &'a mut QueryBuilder<'a> {
self.limit = Some(limit);
self
}
pub fn offset(&'a mut self, offset: u64) -> &'a mut QueryBuilder<'a> {
self.offset = Some(offset);
self
}
pub fn for_update(&'a mut self) -> &'a mut QueryBuilder<'a> {
self.for_update = true;
self
}
}
impl<'a> Parameterized for QueryBuilder<'a> {
fn param(&mut self) -> String {
self.params.seq()
}
}
impl<'a> Sql for Query<'a> {
fn sql(&self) -> String {
let mut result = String::new();
if let Some(ctes) = &self.with_clause {
result.push_str("WITH ");
let mut first = true;
for cte in ctes {
if !first {
result.push_str(", ");
}
first = false;
result.push_str(&cte.sql());
}
result.push(' ');
}
if let Some(select) = &self.select {
result.push_str(&format!("SELECT {}", select.sql()));
}
if let Some(from) = &self.from {
result.push_str(&format!(" FROM {}", from.sql()));
}
for join in &self.joins {
result.push_str(&format!(" {}", join.sql()));
}
if let Some(conditions) = &self.where_clause {
result.push_str(&format!(" WHERE {}", conditions.sql()));
}
if let Some(group_by) = &self.group_by {
result.push_str(&format!(" GROUP BY {}", group_by.join(", ")));
}
if let Some(having) = &self.having {
result.push_str(&format!(" HAVING {}", having.sql()));
}
if let Some(order_by) = &self.order_by {
result.push_str(&format!(" {}", order_by.sql()));
}
if let Some(limit) = &self.limit {
result.push_str(&format!(" LIMIT {}", limit));
}
if let Some(offset) = &self.offset {
result.push_str(&format!(" OFFSET {}", offset));
}
if self.for_update {
result.push_str(" FOR UPDATE");
}
result
}
}