pub(crate) mod engine;
pub mod plan;
use crate::expressions::*;
use crate::generator::{Generator, GeneratorConfig, NotInStyle};
use crate::parser::Parser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClauseOptions {
pub append: bool,
}
impl Default for ClauseOptions {
fn default() -> Self {
Self { append: true }
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LateralViewOptions {
pub outer: bool,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CtasOptions {
pub replace: bool,
pub temporary: bool,
}
fn generate_builder_sql(expression: &Expression) -> String {
let mut generator = Generator::with_config(GeneratorConfig {
not_in_style: NotInStyle::Infix,
..Default::default()
});
generator.generate(expression).unwrap_or_default()
}
fn is_safe_identifier_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
let mut chars = name.chars();
let Some(first) = chars.next() else {
return false;
};
if !(first == '_' || first.is_ascii_alphabetic()) {
return false;
}
chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}
fn builder_identifier(name: &str) -> Identifier {
if name == "*" || is_safe_identifier_name(name) {
Identifier::new(name)
} else {
Identifier::quoted(name)
}
}
fn builder_table_ref(name: &str) -> TableRef {
let parts: Vec<&str> = name.split('.').collect();
match parts.len() {
3 => {
let mut t = TableRef::new(parts[2]);
t.name = builder_identifier(parts[2]);
t.schema = Some(builder_identifier(parts[1]));
t.catalog = Some(builder_identifier(parts[0]));
t
}
2 => {
let mut t = TableRef::new(parts[1]);
t.name = builder_identifier(parts[1]);
t.schema = Some(builder_identifier(parts[0]));
t
}
_ => {
let first = parts.first().copied().unwrap_or("");
let mut t = TableRef::new(first);
t.name = builder_identifier(first);
t
}
}
}
pub fn col(name: &str) -> Expr {
let parts: Vec<&str> = name.split('.').collect();
if parts.len() >= 3 && parts.iter().all(|part| !part.is_empty()) {
let mut expr = Expression::boxed_column(Column {
name: builder_identifier(parts[1]),
table: Some(builder_identifier(parts[0])),
join_mark: false,
trailing_comments: Vec::new(),
span: None,
inferred_type: None,
});
for field in &parts[2..] {
expr = Expression::Dot(Box::new(DotAccess {
this: expr,
field: builder_identifier(field),
}));
}
return Expr(expr);
}
if let Some((table, column)) = name.rsplit_once('.') {
Expr(Expression::boxed_column(Column {
name: builder_identifier(column),
table: Some(builder_identifier(table)),
join_mark: false,
trailing_comments: Vec::new(),
span: None,
inferred_type: None,
}))
} else {
Expr(Expression::boxed_column(Column {
name: builder_identifier(name),
table: None,
join_mark: false,
trailing_comments: Vec::new(),
span: None,
inferred_type: None,
}))
}
}
pub fn lit<V: IntoLiteral>(value: V) -> Expr {
value.into_literal()
}
pub fn star() -> Expr {
Expr(Expression::star())
}
pub fn null() -> Expr {
Expr(Expression::Null(Null))
}
pub fn boolean(value: bool) -> Expr {
Expr(Expression::Boolean(BooleanLiteral { value }))
}
pub fn table(name: &str) -> Expr {
Expr(Expression::Table(Box::new(builder_table_ref(name))))
}
pub fn func(name: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
Expr(Expression::Function(Box::new(Function {
name: name.to_string(),
args: args.into_iter().map(|a| a.0).collect(),
..Function::default()
})))
}
pub fn cast(expr: Expr, to: &str) -> Expr {
let data_type = parse_simple_data_type(to);
Expr(Expression::Cast(Box::new(Cast {
this: expr.0,
to: data_type,
trailing_comments: Vec::new(),
double_colon_syntax: false,
format: None,
default: None,
inferred_type: None,
})))
}
pub fn not(expr: Expr) -> Expr {
Expr(Expression::Not(Box::new(UnaryOp::new(expr.0))))
}
pub fn and(left: Expr, right: Expr) -> Expr {
left.and(right)
}
pub fn or(left: Expr, right: Expr) -> Expr {
left.or(right)
}
pub fn alias(expr: Expr, name: &str) -> Expr {
Expr(Expression::Alias(Box::new(Alias {
this: expr.0,
alias: builder_identifier(name),
column_aliases: Vec::new(),
alias_explicit_as: false,
alias_keyword: None,
pre_alias_comments: Vec::new(),
trailing_comments: Vec::new(),
inferred_type: None,
})))
}
pub fn sql_expr(sql: &str) -> Expr {
let wrapped = format!("SELECT {}", sql);
let ast = Parser::parse_sql(&wrapped).expect("sql_expr: failed to parse SQL expression");
if let Expression::Select(s) = &ast[0] {
if let Some(first) = s.expressions.first() {
return Expr(first.clone());
}
}
panic!("sql_expr: failed to extract expression from parsed SQL");
}
pub fn condition(sql: &str) -> Expr {
sql_expr(sql)
}
pub fn count(expr: Expr) -> Expr {
Expr(Expression::Count(Box::new(CountFunc {
this: Some(expr.0),
star: false,
distinct: false,
filter: None,
ignore_nulls: None,
original_name: None,
inferred_type: None,
})))
}
pub fn count_star() -> Expr {
Expr(Expression::Count(Box::new(CountFunc {
this: None,
star: true,
distinct: false,
filter: None,
ignore_nulls: None,
original_name: None,
inferred_type: None,
})))
}
pub fn count_distinct(expr: Expr) -> Expr {
Expr(Expression::Count(Box::new(CountFunc {
this: Some(expr.0),
star: false,
distinct: true,
filter: None,
ignore_nulls: None,
original_name: None,
inferred_type: None,
})))
}
pub fn sum(expr: Expr) -> Expr {
Expr(Expression::Sum(Box::new(AggFunc {
this: expr.0,
distinct: false,
filter: None,
order_by: vec![],
name: None,
ignore_nulls: None,
having_max: None,
limit: None,
inferred_type: None,
})))
}
pub fn avg(expr: Expr) -> Expr {
Expr(Expression::Avg(Box::new(AggFunc {
this: expr.0,
distinct: false,
filter: None,
order_by: vec![],
name: None,
ignore_nulls: None,
having_max: None,
limit: None,
inferred_type: None,
})))
}
pub fn min_(expr: Expr) -> Expr {
Expr(Expression::Min(Box::new(AggFunc {
this: expr.0,
distinct: false,
filter: None,
order_by: vec![],
name: None,
ignore_nulls: None,
having_max: None,
limit: None,
inferred_type: None,
})))
}
pub fn max_(expr: Expr) -> Expr {
Expr(Expression::Max(Box::new(AggFunc {
this: expr.0,
distinct: false,
filter: None,
order_by: vec![],
name: None,
ignore_nulls: None,
having_max: None,
limit: None,
inferred_type: None,
})))
}
pub fn approx_distinct(expr: Expr) -> Expr {
Expr(Expression::ApproxDistinct(Box::new(AggFunc {
this: expr.0,
distinct: false,
filter: None,
order_by: vec![],
name: None,
ignore_nulls: None,
having_max: None,
limit: None,
inferred_type: None,
})))
}
pub fn upper(expr: Expr) -> Expr {
Expr(Expression::Upper(Box::new(UnaryFunc::new(expr.0))))
}
pub fn lower(expr: Expr) -> Expr {
Expr(Expression::Lower(Box::new(UnaryFunc::new(expr.0))))
}
pub fn length(expr: Expr) -> Expr {
Expr(Expression::Length(Box::new(UnaryFunc::new(expr.0))))
}
pub fn trim(expr: Expr) -> Expr {
Expr(Expression::Trim(Box::new(TrimFunc {
this: expr.0,
characters: None,
position: TrimPosition::Both,
sql_standard_syntax: false,
position_explicit: false,
})))
}
pub fn ltrim(expr: Expr) -> Expr {
Expr(Expression::LTrim(Box::new(UnaryFunc::new(expr.0))))
}
pub fn rtrim(expr: Expr) -> Expr {
Expr(Expression::RTrim(Box::new(UnaryFunc::new(expr.0))))
}
pub fn reverse(expr: Expr) -> Expr {
Expr(Expression::Reverse(Box::new(UnaryFunc::new(expr.0))))
}
pub fn initcap(expr: Expr) -> Expr {
Expr(Expression::Initcap(Box::new(UnaryFunc::new(expr.0))))
}
pub fn substring(expr: Expr, start: Expr, len: Option<Expr>) -> Expr {
Expr(Expression::Substring(Box::new(SubstringFunc {
this: expr.0,
start: start.0,
length: len.map(|l| l.0),
from_for_syntax: false,
})))
}
pub fn replace_(expr: Expr, old: Expr, new: Expr) -> Expr {
Expr(Expression::Replace(Box::new(ReplaceFunc {
this: expr.0,
old: old.0,
new: new.0,
})))
}
pub fn concat_ws(separator: Expr, exprs: impl IntoIterator<Item = Expr>) -> Expr {
Expr(Expression::ConcatWs(Box::new(ConcatWs {
separator: separator.0,
expressions: exprs.into_iter().map(|e| e.0).collect(),
})))
}
pub fn coalesce(exprs: impl IntoIterator<Item = Expr>) -> Expr {
Expr(Expression::Coalesce(Box::new(VarArgFunc {
expressions: exprs.into_iter().map(|e| e.0).collect(),
original_name: None,
inferred_type: None,
})))
}
pub fn null_if(expr1: Expr, expr2: Expr) -> Expr {
Expr(Expression::NullIf(Box::new(BinaryFunc {
this: expr1.0,
expression: expr2.0,
original_name: None,
inferred_type: None,
})))
}
pub fn if_null(expr: Expr, fallback: Expr) -> Expr {
Expr(Expression::IfNull(Box::new(BinaryFunc {
this: expr.0,
expression: fallback.0,
original_name: None,
inferred_type: None,
})))
}
pub fn abs(expr: Expr) -> Expr {
Expr(Expression::Abs(Box::new(UnaryFunc::new(expr.0))))
}
pub fn round(expr: Expr, decimals: Option<Expr>) -> Expr {
Expr(Expression::Round(Box::new(RoundFunc {
this: expr.0,
decimals: decimals.map(|d| d.0),
})))
}
pub fn floor(expr: Expr) -> Expr {
Expr(Expression::Floor(Box::new(FloorFunc {
this: expr.0,
scale: None,
to: None,
})))
}
pub fn ceil(expr: Expr) -> Expr {
Expr(Expression::Ceil(Box::new(CeilFunc {
this: expr.0,
decimals: None,
to: None,
})))
}
pub fn power(base: Expr, exponent: Expr) -> Expr {
Expr(Expression::Power(Box::new(BinaryFunc {
this: base.0,
expression: exponent.0,
original_name: None,
inferred_type: None,
})))
}
pub fn sqrt(expr: Expr) -> Expr {
Expr(Expression::Sqrt(Box::new(UnaryFunc::new(expr.0))))
}
pub fn ln(expr: Expr) -> Expr {
Expr(Expression::Ln(Box::new(UnaryFunc::new(expr.0))))
}
pub fn exp_(expr: Expr) -> Expr {
Expr(Expression::Exp(Box::new(UnaryFunc::new(expr.0))))
}
pub fn sign(expr: Expr) -> Expr {
Expr(Expression::Sign(Box::new(UnaryFunc::new(expr.0))))
}
pub fn greatest(exprs: impl IntoIterator<Item = Expr>) -> Expr {
Expr(Expression::Greatest(Box::new(VarArgFunc {
expressions: exprs.into_iter().map(|e| e.0).collect(),
original_name: None,
inferred_type: None,
})))
}
pub fn least(exprs: impl IntoIterator<Item = Expr>) -> Expr {
Expr(Expression::Least(Box::new(VarArgFunc {
expressions: exprs.into_iter().map(|e| e.0).collect(),
original_name: None,
inferred_type: None,
})))
}
pub fn current_date_() -> Expr {
Expr(Expression::CurrentDate(CurrentDate))
}
pub fn current_time_() -> Expr {
Expr(Expression::CurrentTime(CurrentTime { precision: None }))
}
pub fn current_timestamp_() -> Expr {
Expr(Expression::CurrentTimestamp(CurrentTimestamp {
precision: None,
sysdate: false,
}))
}
pub fn extract_(field: &str, expr: Expr) -> Expr {
Expr(Expression::Extract(Box::new(ExtractFunc {
this: expr.0,
field: parse_datetime_field(field),
})))
}
fn parse_datetime_field(field: &str) -> DateTimeField {
match field.to_uppercase().as_str() {
"YEAR" => DateTimeField::Year,
"MONTH" => DateTimeField::Month,
"DAY" => DateTimeField::Day,
"HOUR" => DateTimeField::Hour,
"MINUTE" => DateTimeField::Minute,
"SECOND" => DateTimeField::Second,
"MILLISECOND" => DateTimeField::Millisecond,
"MICROSECOND" => DateTimeField::Microsecond,
"DOW" | "DAYOFWEEK" => DateTimeField::DayOfWeek,
"DOY" | "DAYOFYEAR" => DateTimeField::DayOfYear,
"WEEK" => DateTimeField::Week,
"QUARTER" => DateTimeField::Quarter,
"EPOCH" => DateTimeField::Epoch,
"TIMEZONE" => DateTimeField::Timezone,
"TIMEZONE_HOUR" => DateTimeField::TimezoneHour,
"TIMEZONE_MINUTE" => DateTimeField::TimezoneMinute,
"DATE" => DateTimeField::Date,
"TIME" => DateTimeField::Time,
other => DateTimeField::Custom(other.to_string()),
}
}
pub fn row_number() -> Expr {
Expr(Expression::RowNumber(RowNumber))
}
pub fn rank_() -> Expr {
Expr(Expression::Rank(Rank {
order_by: None,
args: vec![],
}))
}
pub fn dense_rank() -> Expr {
Expr(Expression::DenseRank(DenseRank { args: vec![] }))
}
pub fn select<I, E>(expressions: I) -> SelectBuilder
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
SelectBuilder::new().select_cols(expressions)
}
pub fn from(table_name: &str) -> SelectBuilder {
SelectBuilder::new().from(table_name)
}
pub fn delete(table_name: &str) -> DeleteBuilder {
DeleteBuilder {
delete: Delete {
table: builder_table_ref(table_name),
hint: None,
on_cluster: None,
alias: None,
alias_explicit_as: false,
using: Vec::new(),
where_clause: None,
output: None,
leading_comments: Vec::new(),
with: None,
limit: None,
order_by: None,
returning: Vec::new(),
tables: Vec::new(),
tables_from_using: false,
joins: Vec::new(),
force_index: None,
no_from: false,
},
}
}
pub fn insert_into(table_name: &str) -> InsertBuilder {
InsertBuilder {
insert: Insert {
table: builder_table_ref(table_name),
columns: Vec::new(),
values: Vec::new(),
query: None,
overwrite: false,
partition: Vec::new(),
directory: None,
returning: Vec::new(),
output: None,
on_conflict: None,
leading_comments: Vec::new(),
if_exists: false,
with: None,
ignore: false,
source_alias: None,
alias: None,
alias_explicit_as: false,
default_values: false,
by_name: false,
conflict_action: None,
is_replace: false,
hint: None,
replace_where: None,
source: None,
function_target: None,
partition_by: None,
settings: Vec::new(),
},
}
}
pub fn update(table_name: &str) -> UpdateBuilder {
UpdateBuilder {
update: Update {
table: builder_table_ref(table_name),
hint: None,
extra_tables: Vec::new(),
table_joins: Vec::new(),
set: Vec::new(),
from_clause: None,
from_joins: Vec::new(),
where_clause: None,
returning: Vec::new(),
output: None,
with: None,
leading_comments: Vec::new(),
limit: None,
order_by: None,
from_before_set: false,
},
}
}
#[derive(Debug, Clone)]
pub struct Expr(pub Expression);
impl Expr {
pub fn into_inner(self) -> Expression {
self.0
}
pub fn to_sql(&self) -> String {
generate_builder_sql(&self.0)
}
pub fn eq(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Eq, self.0, other.0))
}
pub fn neq(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Neq, self.0, other.0))
}
pub fn lt(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Lt, self.0, other.0))
}
pub fn lte(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Lte, self.0, other.0))
}
pub fn gt(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Gt, self.0, other.0))
}
pub fn gte(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Gte, self.0, other.0))
}
pub fn and(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::And, self.0, other.0))
}
pub fn or(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Or, self.0, other.0))
}
pub fn not(self) -> Expr {
Expr(engine::unary(engine::UnaryKind::Not, self.0))
}
pub fn xor(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Xor, self.0, other.0))
}
pub fn add(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Add, self.0, other.0))
}
pub fn sub(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Sub, self.0, other.0))
}
pub fn mul(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Mul, self.0, other.0))
}
pub fn div(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Div, self.0, other.0))
}
pub fn modulo(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Mod, self.0, other.0))
}
pub fn neg(self) -> Expr {
Expr(engine::unary(engine::UnaryKind::Neg, self.0))
}
pub fn is(self, other: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Is, self.0, other.0))
}
pub fn is_null(self) -> Expr {
Expr(engine::unary(engine::UnaryKind::IsNull, self.0))
}
pub fn is_not_null(self) -> Expr {
Expr(engine::unary(engine::UnaryKind::IsNotNull, self.0))
}
pub fn in_list(self, values: impl IntoIterator<Item = Expr>) -> Expr {
Expr(Expression::In(Box::new(In {
this: self.0,
expressions: values.into_iter().map(|v| v.0).collect(),
query: None,
not: false,
global: false,
unnest: None,
is_field: false,
})))
}
pub fn between(self, low: Expr, high: Expr) -> Expr {
Expr(Expression::Between(Box::new(Between {
this: self.0,
low: low.0,
high: high.0,
not: false,
symmetric: None,
})))
}
pub fn like(self, pattern: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::Like, self.0, pattern.0))
}
pub fn alias(self, name: &str) -> Expr {
alias(self, name)
}
pub fn cast(self, to: &str) -> Expr {
cast(self, to)
}
pub fn asc(self) -> Expr {
Expr(Expression::Ordered(Box::new(Ordered {
this: self.0,
desc: false,
nulls_first: None,
explicit_asc: true,
with_fill: None,
})))
}
pub fn desc(self) -> Expr {
Expr(Expression::Ordered(Box::new(Ordered {
this: self.0,
desc: true,
nulls_first: None,
explicit_asc: false,
with_fill: None,
})))
}
pub fn ilike(self, pattern: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::ILike, self.0, pattern.0))
}
pub fn rlike(self, pattern: Expr) -> Expr {
Expr(engine::binary(engine::BinaryKind::RLike, self.0, pattern.0))
}
pub fn not_in(self, values: impl IntoIterator<Item = Expr>) -> Expr {
Expr(Expression::In(Box::new(In {
this: self.0,
expressions: values.into_iter().map(|v| v.0).collect(),
query: None,
not: true,
global: false,
unnest: None,
is_field: false,
})))
}
}
pub struct SelectBuilder {
select: Select,
}
impl SelectBuilder {
fn new() -> Self {
SelectBuilder {
select: Select::new(),
}
}
fn edit(mut self, edit: impl FnOnce(&mut Expression)) -> Self {
let mut expression = Expression::Select(Box::new(self.select));
edit(&mut expression);
self.select = match expression {
Expression::Select(select) => *select,
_ => unreachable!("select builder engine changed the expression kind"),
};
self
}
fn join_with_kind(self, table_name: &str, on: Option<Expr>, kind: JoinKind) -> Self {
let join = Join {
kind,
this: Expression::Table(Box::new(builder_table_ref(table_name))),
on: on.map(|expression| expression.0),
using: Vec::new(),
use_inner_keyword: false,
use_outer_keyword: false,
deferred_condition: false,
join_hint: None,
match_condition: None,
pivots: Vec::new(),
comments: Vec::new(),
nesting_group: 0,
directed: false,
};
self.edit(|expression| {
engine::append_join(expression, join).expect("select builder accepts JOIN clauses")
})
}
pub fn select_cols<I, E>(self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.select_cols_with_options(expressions, ClauseOptions::default())
}
pub fn select_cols_with_options<I, E>(self, expressions: I, options: ClauseOptions) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
let values = expressions
.into_iter()
.map(|expression| expression.into_expr().0)
.collect();
self.edit(|expression| {
engine::append_select(expression, values, options.append)
.expect("select builder accepts SELECT clauses")
})
}
pub fn from(self, table_name: &str) -> Self {
self.edit(|expression| {
engine::set_from(
expression,
vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
)
.expect("select builder accepts FROM clauses")
})
}
pub fn from_expr(self, expr: Expr) -> Self {
self.edit(|expression| {
engine::set_from(expression, vec![expr.0]).expect("select builder accepts FROM clauses")
})
}
pub fn join(self, table_name: &str, on: Expr) -> Self {
self.join_with_kind(table_name, Some(on), JoinKind::Inner)
}
pub fn left_join(self, table_name: &str, on: Expr) -> Self {
self.join_with_kind(table_name, Some(on), JoinKind::Left)
}
pub fn where_(self, condition: Expr) -> Self {
self.where_with_options(condition, ClauseOptions::default())
}
pub fn where_with_options(self, condition: Expr, options: ClauseOptions) -> Self {
self.edit(|expression| {
engine::apply_where(expression, condition.0, options.append)
.expect("select builder accepts WHERE clauses")
})
}
pub fn group_by<I, E>(self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.group_by_with_options(expressions, ClauseOptions::default())
}
pub fn group_by_with_options<I, E>(self, expressions: I, options: ClauseOptions) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
let values = expressions
.into_iter()
.map(|expression| expression.into_expr().0)
.collect();
self.edit(|expression| {
engine::apply_group_by(expression, values, options.append)
.expect("select builder accepts GROUP BY clauses")
})
}
pub fn having(self, condition: Expr) -> Self {
self.having_with_options(condition, ClauseOptions::default())
}
pub fn having_with_options(self, condition: Expr, options: ClauseOptions) -> Self {
self.edit(|expression| {
engine::apply_having(expression, condition.0, options.append)
.expect("select builder accepts HAVING clauses")
})
}
pub fn order_by<I, E>(self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.order_by_with_options(expressions, ClauseOptions::default())
}
pub fn order_by_with_options<I, E>(self, expressions: I, options: ClauseOptions) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
let values = expressions
.into_iter()
.map(|expression| engine::ordered(expression.into_expr().0))
.collect();
self.edit(|expression| {
engine::apply_order_by(expression, values, options.append)
.expect("select builder accepts ORDER BY clauses")
})
}
pub fn sort_by<I, E>(self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.sort_by_with_options(expressions, ClauseOptions::default())
}
pub fn sort_by_with_options<I, E>(self, expressions: I, options: ClauseOptions) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
let values = expressions
.into_iter()
.map(|expression| engine::ordered(expression.into_expr().0))
.collect();
self.edit(|expression| {
engine::apply_sort_by(expression, values, options.append)
.expect("select builder accepts SORT BY clauses")
})
}
pub fn limit(self, count: usize) -> Self {
self.edit(|expression| {
engine::apply_limit(
expression,
Expression::Literal(Box::new(Literal::Number(count.to_string()))),
)
.expect("select builder accepts LIMIT clauses")
})
}
pub fn offset(self, count: usize) -> Self {
self.edit(|expression| {
engine::apply_offset(
expression,
Expression::Literal(Box::new(Literal::Number(count.to_string()))),
)
.expect("select builder accepts OFFSET clauses")
})
}
pub fn distinct(self) -> Self {
self.edit(|expression| {
engine::apply_distinct(expression, true)
.expect("select builder accepts DISTINCT clauses")
})
}
pub fn qualify(self, condition: Expr) -> Self {
self.qualify_with_options(condition, ClauseOptions::default())
}
pub fn qualify_with_options(self, condition: Expr, options: ClauseOptions) -> Self {
self.edit(|expression| {
engine::apply_qualify(expression, condition.0, options.append)
.expect("select builder accepts QUALIFY clauses")
})
}
pub fn right_join(self, table_name: &str, on: Expr) -> Self {
self.join_with_kind(table_name, Some(on), JoinKind::Right)
}
pub fn full_join(self, table_name: &str, on: Expr) -> Self {
self.join_with_kind(table_name, Some(on), JoinKind::Full)
}
pub fn cross_join(self, table_name: &str) -> Self {
self.join_with_kind(table_name, None, JoinKind::Cross)
}
pub fn lateral_view<S: AsRef<str>>(
self,
table_function: Expr,
table_alias: &str,
column_aliases: impl IntoIterator<Item = S>,
) -> Self {
self.lateral_view_with_options(
table_function,
table_alias,
column_aliases,
LateralViewOptions::default(),
)
}
pub fn lateral_view_with_options<S: AsRef<str>>(
self,
table_function: Expr,
table_alias: &str,
column_aliases: impl IntoIterator<Item = S>,
options: LateralViewOptions,
) -> Self {
let aliases = column_aliases
.into_iter()
.map(|c| builder_identifier(c.as_ref()))
.collect();
self.edit(|expression| {
engine::append_lateral_view(
expression,
table_function.0,
Some(builder_identifier(table_alias)),
aliases,
options.outer,
)
.expect("select builder accepts LATERAL VIEW clauses")
})
}
pub fn window(self, name: &str, def: WindowDefBuilder) -> Self {
let order_by = def.order_by;
self.edit(|expression| {
engine::append_window(
expression,
builder_identifier(name),
def.partition_by,
order_by,
)
.expect("select builder accepts WINDOW clauses")
})
}
pub fn for_update(self) -> Self {
self.edit(|expression| {
engine::append_lock(expression, engine::LockKind::Update)
.expect("select builder accepts locking clauses")
})
}
pub fn for_share(self) -> Self {
self.edit(|expression| {
engine::append_lock(expression, engine::LockKind::Share)
.expect("select builder accepts locking clauses")
})
}
pub fn hint(self, hint_text: &str) -> Self {
self.edit(|expression| {
engine::append_hint(expression, hint_text.to_string())
.expect("select builder accepts query hints")
})
}
pub fn ctas(self, table_name: &str) -> Expression {
self.ctas_with_options(table_name, CtasOptions::default())
}
pub fn ctas_with_options(self, table_name: &str, options: CtasOptions) -> Expression {
engine::create_table_as(
self.build(),
builder_table_ref(table_name),
options.replace,
options.temporary,
)
.expect("select builder CTAS source is a query")
}
pub fn union(self, other: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Union, self, other, false)
}
pub fn union_all(self, other: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Union, self, other, true)
}
pub fn intersect(self, other: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Intersect, self, other, false)
}
pub fn except_(self, other: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Except, self, other, false)
}
pub fn build(self) -> Expression {
Expression::Select(Box::new(self.select))
}
pub fn to_sql(self) -> String {
generate_builder_sql(&self.build())
}
}
pub struct DeleteBuilder {
delete: Delete,
}
impl DeleteBuilder {
pub fn where_(self, condition: Expr) -> Self {
self.where_with_options(condition, ClauseOptions::default())
}
pub fn where_with_options(mut self, condition: Expr, options: ClauseOptions) -> Self {
let mut expression = Expression::Delete(Box::new(self.delete));
engine::apply_where(&mut expression, condition.0, options.append)
.expect("delete builder accepts WHERE clauses");
self.delete = match expression {
Expression::Delete(delete) => *delete,
_ => unreachable!("delete builder engine changed the expression kind"),
};
self
}
pub fn build(self) -> Expression {
Expression::Delete(Box::new(self.delete))
}
pub fn to_sql(self) -> String {
generate_builder_sql(&self.build())
}
}
pub struct InsertBuilder {
insert: Insert,
}
impl InsertBuilder {
fn edit(mut self, edit: impl FnOnce(&mut Expression)) -> Self {
let mut expression = Expression::Insert(Box::new(self.insert));
edit(&mut expression);
self.insert = match expression {
Expression::Insert(insert) => *insert,
_ => unreachable!("insert builder engine changed the expression kind"),
};
self
}
pub fn columns<I, S>(self, columns: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let columns = columns
.into_iter()
.map(|c| builder_identifier(c.as_ref()))
.collect();
self.edit(|expression| {
engine::set_insert_columns(expression, columns)
.expect("insert builder accepts target columns")
})
}
pub fn values<I>(self, values: I) -> Self
where
I: IntoIterator<Item = Expr>,
{
let row = values.into_iter().map(|v| v.0).collect();
self.edit(|expression| {
engine::apply_insert_values(expression, vec![row], true)
.expect("insert builder accepts VALUES clauses")
})
}
pub fn query(self, query: SelectBuilder) -> Self {
self.edit(|expression| {
engine::set_insert_query(expression, query.build())
.expect("insert builder accepts query sources")
})
}
pub fn build(self) -> Expression {
Expression::Insert(Box::new(self.insert))
}
pub fn to_sql(self) -> String {
generate_builder_sql(&self.build())
}
}
pub struct UpdateBuilder {
update: Update,
}
impl UpdateBuilder {
fn edit(mut self, edit: impl FnOnce(&mut Expression)) -> Self {
let mut expression = Expression::Update(Box::new(self.update));
edit(&mut expression);
self.update = match expression {
Expression::Update(update) => *update,
_ => unreachable!("update builder engine changed the expression kind"),
};
self
}
pub fn set(self, column: &str, value: Expr) -> Self {
self.edit(|expression| {
engine::append_update_assignments(
expression,
vec![(builder_identifier(column), value.0)],
)
.expect("update builder accepts SET assignments")
})
}
pub fn where_(self, condition: Expr) -> Self {
self.where_with_options(condition, ClauseOptions::default())
}
pub fn where_with_options(mut self, condition: Expr, options: ClauseOptions) -> Self {
let mut expression = Expression::Update(Box::new(self.update));
engine::apply_where(&mut expression, condition.0, options.append)
.expect("update builder accepts WHERE clauses");
self.update = match expression {
Expression::Update(update) => *update,
_ => unreachable!("update builder engine changed the expression kind"),
};
self
}
pub fn from(self, table_name: &str) -> Self {
self.edit(|expression| {
engine::set_from(
expression,
vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
)
.expect("update builder accepts FROM clauses")
})
}
pub fn build(self) -> Expression {
Expression::Update(Box::new(self.update))
}
pub fn to_sql(self) -> String {
generate_builder_sql(&self.build())
}
}
pub fn case() -> CaseBuilder {
CaseBuilder {
operand: None,
whens: Vec::new(),
else_: None,
}
}
pub fn case_of(operand: Expr) -> CaseBuilder {
CaseBuilder {
operand: Some(operand.0),
whens: Vec::new(),
else_: None,
}
}
pub struct CaseBuilder {
operand: Option<Expression>,
whens: Vec<(Expression, Expression)>,
else_: Option<Expression>,
}
impl CaseBuilder {
pub fn when(mut self, condition: Expr, result: Expr) -> Self {
self.whens.push((condition.0, result.0));
self
}
pub fn else_(mut self, result: Expr) -> Self {
self.else_ = Some(result.0);
self
}
pub fn build(self) -> Expr {
Expr(self.build_expr())
}
pub fn build_expr(self) -> Expression {
let mut expression = engine::case(self.operand);
for (condition, result) in self.whens {
engine::append_case_when(&mut expression, condition, result)
.expect("case builder accepts WHEN branches");
}
if let Some(result) = self.else_ {
engine::set_case_else(&mut expression, result)
.expect("case builder accepts ELSE branches");
}
expression
}
}
pub fn subquery(query: SelectBuilder, alias_name: &str) -> Expr {
subquery_expr(query.build(), alias_name)
}
pub fn subquery_expr(expr: Expression, alias_name: &str) -> Expr {
Expr(
engine::subquery(expr, Some(builder_identifier(alias_name)), true)
.expect("subquery builder source is a query"),
)
}
#[derive(Debug, Clone, Copy)]
enum SetOpKind {
Union,
Intersect,
Except,
}
pub struct SetOpBuilder {
kind: SetOpKind,
left: Expression,
right: Expression,
all: bool,
order_by: Option<OrderBy>,
limit: Option<Box<Expression>>,
offset: Option<Box<Expression>>,
}
impl SetOpBuilder {
fn new(kind: SetOpKind, left: SelectBuilder, right: SelectBuilder, all: bool) -> Self {
SetOpBuilder {
kind,
left: left.build(),
right: right.build(),
all,
order_by: None,
limit: None,
offset: None,
}
}
pub fn order_by<I, E>(self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.order_by_with_options(expressions, ClauseOptions::default())
}
pub fn order_by_with_options<I, E>(mut self, expressions: I, options: ClauseOptions) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
let values: Vec<_> = expressions
.into_iter()
.map(|expression| engine::ordered(expression.into_expr().0))
.collect();
if options.append {
self.order_by
.get_or_insert_with(|| OrderBy {
siblings: false,
comments: Vec::new(),
expressions: Vec::new(),
})
.expressions
.extend(values);
} else {
self.order_by = Some(OrderBy {
siblings: false,
comments: Vec::new(),
expressions: values,
});
}
self
}
pub fn limit(mut self, count: usize) -> Self {
self.limit = Some(Box::new(Expression::Literal(Box::new(Literal::Number(
count.to_string(),
)))));
self
}
pub fn offset(mut self, count: usize) -> Self {
self.offset = Some(Box::new(Expression::Literal(Box::new(Literal::Number(
count.to_string(),
)))));
self
}
pub fn build(self) -> Expression {
let kind = match self.kind {
SetOpKind::Union => engine::SetKind::Union,
SetOpKind::Intersect => engine::SetKind::Intersect,
SetOpKind::Except => engine::SetKind::Except,
};
let mut expression = engine::set_operation(kind, self.left, self.right, !self.all)
.expect("set builder operands are queries");
if let Some(order_by) = self.order_by {
engine::apply_order_by(&mut expression, order_by.expressions, false)
.expect("set operations accept ORDER BY clauses");
}
if let Some(limit) = self.limit {
engine::apply_limit(&mut expression, *limit)
.expect("set operations accept LIMIT clauses");
}
if let Some(offset) = self.offset {
engine::apply_offset(&mut expression, *offset)
.expect("set operations accept OFFSET clauses");
}
expression
}
pub fn to_sql(self) -> String {
generate_builder_sql(&self.build())
}
}
pub fn union(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Union, left, right, false)
}
pub fn union_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Union, left, right, true)
}
pub fn intersect(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Intersect, left, right, false)
}
pub fn intersect_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Intersect, left, right, true)
}
pub fn except_(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Except, left, right, false)
}
pub fn except_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
SetOpBuilder::new(SetOpKind::Except, left, right, true)
}
pub struct WindowDefBuilder {
partition_by: Vec<Expression>,
order_by: Vec<Ordered>,
}
impl WindowDefBuilder {
pub fn new() -> Self {
WindowDefBuilder {
partition_by: Vec::new(),
order_by: Vec::new(),
}
}
pub fn partition_by<I, E>(mut self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.partition_by = expressions.into_iter().map(|e| e.into_expr().0).collect();
self
}
pub fn order_by<I, E>(mut self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.order_by = expressions
.into_iter()
.map(|e| {
let expr = e.into_expr().0;
match expr {
Expression::Ordered(o) => *o,
other => Ordered {
this: other,
desc: false,
nulls_first: None,
explicit_asc: false,
with_fill: None,
},
}
})
.collect();
self
}
}
pub trait IntoExpr {
fn into_expr(self) -> Expr;
}
impl IntoExpr for Expr {
fn into_expr(self) -> Expr {
self
}
}
impl IntoExpr for &str {
fn into_expr(self) -> Expr {
col(self)
}
}
impl IntoExpr for String {
fn into_expr(self) -> Expr {
col(&self)
}
}
impl IntoExpr for Expression {
fn into_expr(self) -> Expr {
Expr(self)
}
}
pub trait IntoLiteral {
fn into_literal(self) -> Expr;
}
impl IntoLiteral for &str {
fn into_literal(self) -> Expr {
Expr(Expression::Literal(Box::new(Literal::String(
self.to_string(),
))))
}
}
impl IntoLiteral for String {
fn into_literal(self) -> Expr {
Expr(Expression::Literal(Box::new(Literal::String(self))))
}
}
impl IntoLiteral for i64 {
fn into_literal(self) -> Expr {
Expr(Expression::Literal(Box::new(Literal::Number(
self.to_string(),
))))
}
}
impl IntoLiteral for i32 {
fn into_literal(self) -> Expr {
Expr(Expression::Literal(Box::new(Literal::Number(
self.to_string(),
))))
}
}
impl IntoLiteral for usize {
fn into_literal(self) -> Expr {
Expr(Expression::Literal(Box::new(Literal::Number(
self.to_string(),
))))
}
}
impl IntoLiteral for f64 {
fn into_literal(self) -> Expr {
Expr(Expression::Literal(Box::new(Literal::Number(
self.to_string(),
))))
}
}
impl IntoLiteral for bool {
fn into_literal(self) -> Expr {
Expr(Expression::Boolean(BooleanLiteral { value: self }))
}
}
pub fn merge_into(target: &str) -> MergeBuilder {
MergeBuilder {
expression: engine::merge(Expression::Table(Box::new(builder_table_ref(target)))),
}
}
pub struct MergeBuilder {
expression: Expression,
}
impl MergeBuilder {
pub fn using(mut self, source: &str, on: Expr) -> Self {
engine::set_merge_using(
&mut self.expression,
Expression::Table(Box::new(builder_table_ref(source))),
on.0,
)
.expect("merge builder accepts a USING clause");
self
}
pub fn when_matched_update(mut self, assignments: Vec<(&str, Expr)>) -> Self {
let assignments = assignments
.into_iter()
.map(|(column, value)| (builder_identifier(column), value.0))
.collect();
engine::append_merge_update(&mut self.expression, assignments, None)
.expect("merge builder accepts matched update actions");
self
}
pub fn when_matched_update_where(
mut self,
condition: Expr,
assignments: Vec<(&str, Expr)>,
) -> Self {
let assignments = assignments
.into_iter()
.map(|(column, value)| (builder_identifier(column), value.0))
.collect();
engine::append_merge_update(&mut self.expression, assignments, Some(condition.0))
.expect("merge builder accepts conditional matched update actions");
self
}
pub fn when_matched_delete(mut self) -> Self {
engine::append_merge_delete(&mut self.expression, None)
.expect("merge builder accepts matched delete actions");
self
}
pub fn when_matched_delete_where(mut self, condition: Expr) -> Self {
engine::append_merge_delete(&mut self.expression, Some(condition.0))
.expect("merge builder accepts conditional matched delete actions");
self
}
pub fn when_not_matched_insert(mut self, columns: &[&str], values: Vec<Expr>) -> Self {
engine::append_merge_insert(
&mut self.expression,
columns
.iter()
.map(|column| builder_identifier(column))
.collect(),
values.into_iter().map(|value| value.0).collect(),
None,
)
.expect("merge builder accepts not-matched insert actions");
self
}
pub fn when_not_matched_insert_where(
mut self,
condition: Expr,
columns: &[&str],
values: Vec<Expr>,
) -> Self {
engine::append_merge_insert(
&mut self.expression,
columns
.iter()
.map(|column| builder_identifier(column))
.collect(),
values.into_iter().map(|value| value.0).collect(),
Some(condition.0),
)
.expect("merge builder accepts conditional not-matched insert actions");
self
}
pub fn build(self) -> Expression {
self.expression
}
pub fn to_sql(self) -> String {
generate_builder_sql(&self.build())
}
}
fn parse_simple_data_type(name: &str) -> DataType {
let upper = name.trim().to_uppercase();
match upper.as_str() {
"INT" | "INTEGER" => DataType::Int {
length: None,
integer_spelling: upper == "INTEGER",
},
"BIGINT" => DataType::BigInt { length: None },
"SMALLINT" => DataType::SmallInt { length: None },
"TINYINT" => DataType::TinyInt { length: None },
"FLOAT" => DataType::Float {
precision: None,
scale: None,
real_spelling: false,
},
"DOUBLE" => DataType::Double {
precision: None,
scale: None,
},
"BOOLEAN" | "BOOL" => DataType::Boolean,
"TEXT" => DataType::Text,
"DATE" => DataType::Date,
"TIMESTAMP" => DataType::Timestamp {
precision: None,
timezone: false,
},
"VARCHAR" => DataType::VarChar {
length: None,
parenthesized_length: false,
},
"CHAR" => DataType::Char { length: None },
_ => {
if let Ok(ast) =
crate::parser::Parser::parse_sql(&format!("SELECT CAST(x AS {})", name))
{
if let Expression::Select(s) = &ast[0] {
if let Some(Expression::Cast(c)) = s.expressions.first() {
return c.to.clone();
}
}
}
DataType::Custom {
name: name.to_string(),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_select() {
let sql = select(["id", "name"]).from("users").to_sql();
assert_eq!(sql, "SELECT id, name FROM users");
}
#[test]
fn test_builder_quotes_unsafe_identifier_tokens() {
let sql = select(["Name; DROP TABLE titanic"]).to_sql();
assert_eq!(sql, r#"SELECT "Name; DROP TABLE titanic""#);
}
#[test]
fn test_builder_string_literal_requires_lit() {
let sql = select([lit("Name; DROP TABLE titanic")]).to_sql();
assert_eq!(sql, "SELECT 'Name; DROP TABLE titanic'");
}
#[test]
fn test_builder_quotes_unsafe_table_name_tokens() {
let sql = select(["id"]).from("users; DROP TABLE x").to_sql();
assert_eq!(sql, r#"SELECT id FROM "users; DROP TABLE x""#);
}
#[test]
fn test_select_star() {
let sql = select([star()]).from("users").to_sql();
assert_eq!(sql, "SELECT * FROM users");
}
#[test]
fn test_select_with_where() {
let sql = select(["id", "name"])
.from("users")
.where_(col("age").gt(lit(18)))
.to_sql();
assert_eq!(sql, "SELECT id, name FROM users WHERE age > 18");
}
#[test]
fn test_select_with_join() {
let sql = select(["u.id", "o.amount"])
.from("users")
.join("orders", col("u.id").eq(col("o.user_id")))
.to_sql();
assert_eq!(
sql,
"SELECT u.id, o.amount FROM users JOIN orders ON u.id = o.user_id"
);
}
#[test]
fn test_select_with_group_by_having() {
let sql = select([col("dept"), func("COUNT", [star()]).alias("cnt")])
.from("employees")
.group_by(["dept"])
.having(func("COUNT", [star()]).gt(lit(5)))
.to_sql();
assert_eq!(
sql,
"SELECT dept, COUNT(*) AS cnt FROM employees GROUP BY dept HAVING COUNT(*) > 5"
);
}
#[test]
fn test_select_with_order_limit_offset() {
let sql = select(["id", "name"])
.from("users")
.order_by(["name"])
.limit(10)
.offset(20)
.to_sql();
assert_eq!(
sql,
"SELECT id, name FROM users ORDER BY name LIMIT 10 OFFSET 20"
);
}
#[test]
fn test_select_distinct() {
let sql = select(["name"]).from("users").distinct().to_sql();
assert_eq!(sql, "SELECT DISTINCT name FROM users");
}
#[test]
fn test_insert_values() {
let sql = insert_into("users")
.columns(["id", "name"])
.values([lit(1), lit("Alice")])
.values([lit(2), lit("Bob")])
.to_sql();
assert_eq!(
sql,
"INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob')"
);
}
#[test]
fn test_insert_select() {
let sql = insert_into("archive")
.columns(["id", "name"])
.query(select(["id", "name"]).from("users"))
.to_sql();
assert_eq!(
sql,
"INSERT INTO archive (id, name) SELECT id, name FROM users"
);
}
#[test]
fn test_update() {
let sql = update("users")
.set("name", lit("Bob"))
.set("age", lit(30))
.where_(col("id").eq(lit(1)))
.to_sql();
assert_eq!(sql, "UPDATE users SET name = 'Bob', age = 30 WHERE id = 1");
}
#[test]
fn test_delete() {
let sql = delete("users").where_(col("id").eq(lit(1))).to_sql();
assert_eq!(sql, "DELETE FROM users WHERE id = 1");
}
#[test]
fn test_complex_where() {
let sql = select(["id"])
.from("users")
.where_(
col("age")
.gte(lit(18))
.and(col("active").eq(boolean(true)))
.and(col("name").like(lit("%test%"))),
)
.to_sql();
assert_eq!(
sql,
"SELECT id FROM users WHERE age >= 18 AND active = TRUE AND name LIKE '%test%'"
);
}
#[test]
fn test_in_list() {
let sql = select(["id"])
.from("users")
.where_(col("status").in_list([lit("active"), lit("pending")]))
.to_sql();
assert_eq!(
sql,
"SELECT id FROM users WHERE status IN ('active', 'pending')"
);
}
#[test]
fn test_between() {
let sql = select(["id"])
.from("orders")
.where_(col("amount").between(lit(100), lit(500)))
.to_sql();
assert_eq!(
sql,
"SELECT id FROM orders WHERE amount BETWEEN 100 AND 500"
);
}
#[test]
fn test_is_null() {
let sql = select(["id"])
.from("users")
.where_(col("email").is_null())
.to_sql();
assert_eq!(sql, "SELECT id FROM users WHERE email IS NULL");
}
#[test]
fn test_arithmetic() {
let sql = select([col("price").mul(col("quantity")).alias("total")])
.from("items")
.to_sql();
assert_eq!(sql, "SELECT price * quantity AS total FROM items");
}
#[test]
fn test_cast() {
let sql = select([col("id").cast("VARCHAR")]).from("users").to_sql();
assert_eq!(sql, "SELECT CAST(id AS VARCHAR) FROM users");
}
#[test]
fn test_from_starter() {
let sql = from("users").select_cols(["id", "name"]).to_sql();
assert_eq!(sql, "SELECT id, name FROM users");
}
#[test]
fn test_qualified_column() {
let sql = select([col("u.id"), col("u.name")]).from("users").to_sql();
assert_eq!(sql, "SELECT u.id, u.name FROM users");
}
#[test]
fn test_nested_dot_column() {
let sql = select([col("t.s.f")]).from("users").to_sql();
assert_eq!(sql, "SELECT t.s.f FROM users");
}
#[test]
fn test_not_condition() {
let sql = select(["id"])
.from("users")
.where_(not(col("active").eq(boolean(true))))
.to_sql();
assert_eq!(sql, "SELECT id FROM users WHERE NOT active = TRUE");
}
#[test]
fn test_order_by_desc() {
let sql = select(["id", "name"])
.from("users")
.order_by([col("name").desc()])
.to_sql();
assert_eq!(sql, "SELECT id, name FROM users ORDER BY name DESC");
}
#[test]
fn test_left_join() {
let sql = select(["u.id", "o.amount"])
.from("users")
.left_join("orders", col("u.id").eq(col("o.user_id")))
.to_sql();
assert_eq!(
sql,
"SELECT u.id, o.amount FROM users LEFT JOIN orders ON u.id = o.user_id"
);
}
#[test]
fn test_build_returns_expression() {
let expr = select(["id"]).from("users").build();
assert!(matches!(expr, Expression::Select(_)));
}
#[test]
fn test_expr_interop() {
let age_check = col("age").gt(lit(18));
let sql = select([col("id"), age_check.alias("is_adult")])
.from("users")
.to_sql();
assert_eq!(sql, "SELECT id, age > 18 AS is_adult FROM users");
}
#[test]
fn test_sql_expr_simple() {
let expr = sql_expr("age > 18");
let sql = select(["id"]).from("users").where_(expr).to_sql();
assert_eq!(sql, "SELECT id FROM users WHERE age > 18");
}
#[test]
fn test_sql_expr_compound() {
let expr = sql_expr("a > 1 AND b < 10");
let sql = select(["*"]).from("t").where_(expr).to_sql();
assert_eq!(sql, "SELECT * FROM t WHERE a > 1 AND b < 10");
}
#[test]
fn test_sql_expr_function() {
let expr = sql_expr("COALESCE(a, b, 0)");
let sql = select([expr.alias("val")]).from("t").to_sql();
assert_eq!(sql, "SELECT COALESCE(a, b, 0) AS val FROM t");
}
#[test]
fn test_condition_alias() {
let cond = condition("x > 0");
let sql = select(["*"]).from("t").where_(cond).to_sql();
assert_eq!(sql, "SELECT * FROM t WHERE x > 0");
}
#[test]
fn test_ilike() {
let sql = select(["id"])
.from("users")
.where_(col("name").ilike(lit("%test%")))
.to_sql();
assert_eq!(sql, "SELECT id FROM users WHERE name ILIKE '%test%'");
}
#[test]
fn test_rlike() {
let sql = select(["id"])
.from("users")
.where_(col("name").rlike(lit("^[A-Z]")))
.to_sql();
assert_eq!(
sql,
"SELECT id FROM users WHERE REGEXP_LIKE(name, '^[A-Z]')"
);
}
#[test]
fn test_not_in() {
let sql = select(["id"])
.from("users")
.where_(col("status").not_in([lit("deleted"), lit("banned")]))
.to_sql();
assert_eq!(
sql,
"SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
);
}
#[test]
fn repeated_clauses_append_unless_replacement_is_requested() {
let appended = select(["x"])
.where_(col("x").gt(lit(0)))
.where_(col("x").lt(lit(10)))
.group_by(["x"])
.group_by(["y"])
.to_sql();
assert_eq!(appended, "SELECT x WHERE x > 0 AND x < 10 GROUP BY x, y");
let replaced = select(["x"])
.where_(col("x").gt(lit(0)))
.where_with_options(col("x").eq(lit(5)), ClauseOptions { append: false })
.group_by(["x"])
.group_by_with_options(["y"], ClauseOptions { append: false })
.to_sql();
assert_eq!(replaced, "SELECT x WHERE x = 5 GROUP BY y");
}
#[test]
fn test_case_searched() {
let expr = case()
.when(col("x").gt(lit(0)), lit("positive"))
.when(col("x").eq(lit(0)), lit("zero"))
.else_(lit("negative"))
.build();
let sql = select([expr.alias("label")]).from("t").to_sql();
assert_eq!(
sql,
"SELECT CASE WHEN x > 0 THEN 'positive' WHEN x = 0 THEN 'zero' ELSE 'negative' END AS label FROM t"
);
}
#[test]
fn test_case_simple() {
let expr = case_of(col("status"))
.when(lit(1), lit("active"))
.when(lit(0), lit("inactive"))
.build();
let sql = select([expr.alias("status_label")]).from("t").to_sql();
assert_eq!(
sql,
"SELECT CASE status WHEN 1 THEN 'active' WHEN 0 THEN 'inactive' END AS status_label FROM t"
);
}
#[test]
fn test_case_no_else() {
let expr = case().when(col("x").gt(lit(0)), lit("yes")).build();
let sql = select([expr]).from("t").to_sql();
assert_eq!(sql, "SELECT CASE WHEN x > 0 THEN 'yes' END FROM t");
}
#[test]
fn test_subquery_in_from() {
let inner = select(["id", "name"])
.from("users")
.where_(col("active").eq(boolean(true)));
let outer = select(["sub.id"])
.from_expr(subquery(inner, "sub"))
.to_sql();
assert_eq!(
outer,
"SELECT sub.id FROM (SELECT id, name FROM users WHERE active = TRUE) AS sub"
);
}
#[test]
fn test_subquery_in_join() {
let inner = select([col("user_id"), func("SUM", [col("amount")]).alias("total")])
.from("orders")
.group_by(["user_id"]);
let sql = select(["u.name", "o.total"])
.from("users")
.join("orders", col("u.id").eq(col("o.user_id")))
.to_sql();
assert!(sql.contains("JOIN"));
let _sub = subquery(inner, "o");
}
#[test]
fn test_union() {
let sql = union(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
assert_eq!(sql, "SELECT id FROM a UNION SELECT id FROM b");
}
#[test]
fn test_union_all() {
let sql = union_all(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
assert_eq!(sql, "SELECT id FROM a UNION ALL SELECT id FROM b");
}
#[test]
fn test_intersect_builder() {
let sql = intersect(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
assert_eq!(sql, "SELECT id FROM a INTERSECT SELECT id FROM b");
}
#[test]
fn test_except_builder() {
let sql = except_(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
assert_eq!(sql, "SELECT id FROM a EXCEPT SELECT id FROM b");
}
#[test]
fn test_union_with_order_limit() {
let sql = union(select(["id"]).from("a"), select(["id"]).from("b"))
.order_by(["id"])
.limit(10)
.to_sql();
assert!(sql.contains("UNION"));
assert!(sql.contains("ORDER BY"));
assert!(sql.contains("LIMIT"));
}
#[test]
fn test_select_builder_union() {
let sql = select(["id"])
.from("a")
.union(select(["id"]).from("b"))
.to_sql();
assert_eq!(sql, "SELECT id FROM a UNION SELECT id FROM b");
}
#[test]
fn test_qualify() {
let sql = select(["id", "name"])
.from("users")
.qualify(col("rn").eq(lit(1)))
.to_sql();
assert_eq!(sql, "SELECT id, name FROM users QUALIFY rn = 1");
}
#[test]
fn test_right_join() {
let sql = select(["u.id", "o.amount"])
.from("users")
.right_join("orders", col("u.id").eq(col("o.user_id")))
.to_sql();
assert_eq!(
sql,
"SELECT u.id, o.amount FROM users RIGHT JOIN orders ON u.id = o.user_id"
);
}
#[test]
fn test_cross_join() {
let sql = select(["a.x", "b.y"]).from("a").cross_join("b").to_sql();
assert_eq!(sql, "SELECT a.x, b.y FROM a CROSS JOIN b");
}
#[test]
fn test_lateral_view() {
let sql = select(["id", "col_val"])
.from("t")
.lateral_view(func("EXPLODE", [col("arr")]), "lv", ["col_val"])
.to_sql();
assert!(sql.contains("LATERAL VIEW"));
assert!(sql.contains("EXPLODE"));
let expression = select(["id"])
.from("t")
.lateral_view_with_options(
func("EXPLODE", [col("arr")]),
"lv",
["value"],
LateralViewOptions { outer: true },
)
.build();
let Expression::Select(select) = expression else {
panic!("expected SELECT")
};
assert!(select.lateral_views[0].outer);
}
#[test]
fn test_window_clause() {
let sql = select(["id"])
.from("t")
.window(
"w",
WindowDefBuilder::new()
.partition_by(["dept"])
.order_by(["salary"]),
)
.to_sql();
assert!(sql.contains("WINDOW"));
assert!(sql.contains("PARTITION BY"));
}
#[test]
fn test_xor() {
let sql = select(["*"])
.from("t")
.where_(col("a").xor(col("b")))
.to_sql();
assert_eq!(sql, "SELECT * FROM t WHERE a XOR b");
}
#[test]
fn test_for_update() {
let sql = select(["id"]).from("t").for_update().to_sql();
assert_eq!(sql, "SELECT id FROM t FOR UPDATE");
}
#[test]
fn test_for_share() {
let sql = select(["id"]).from("t").for_share().to_sql();
assert_eq!(sql, "SELECT id FROM t FOR SHARE");
}
#[test]
fn test_hint() {
let sql = select(["*"]).from("t").hint("FULL(t)").to_sql();
assert!(sql.contains("FULL(t)"), "Expected hint in: {}", sql);
}
#[test]
fn test_ctas() {
let expr = select(["*"]).from("t").ctas("new_table");
let sql = Generator::sql(&expr).unwrap();
assert_eq!(sql, "CREATE TABLE new_table AS SELECT * FROM t");
let expr = select(["*"]).from("t").ctas_with_options(
"new_table",
CtasOptions {
replace: true,
temporary: true,
},
);
let Expression::CreateTable(create) = expr else {
panic!("expected CREATE TABLE")
};
assert!(create.or_replace);
assert!(create.temporary);
}
#[test]
fn test_merge_update_insert() {
let sql = merge_into("target")
.using("source", col("target.id").eq(col("source.id")))
.when_matched_update(vec![("name", col("source.name"))])
.when_not_matched_insert(&["id", "name"], vec![col("source.id"), col("source.name")])
.to_sql();
assert!(
sql.contains("MERGE INTO"),
"Expected MERGE INTO in: {}",
sql
);
assert!(sql.contains("USING"), "Expected USING in: {}", sql);
assert!(
sql.contains("WHEN MATCHED"),
"Expected WHEN MATCHED in: {}",
sql
);
assert!(
sql.contains("UPDATE SET"),
"Expected UPDATE SET in: {}",
sql
);
assert!(
sql.contains("WHEN NOT MATCHED"),
"Expected WHEN NOT MATCHED in: {}",
sql
);
assert!(sql.contains("INSERT"), "Expected INSERT in: {}", sql);
}
#[test]
fn test_merge_delete() {
let sql = merge_into("target")
.using("source", col("target.id").eq(col("source.id")))
.when_matched_delete()
.to_sql();
assert!(
sql.contains("MERGE INTO"),
"Expected MERGE INTO in: {}",
sql
);
assert!(
sql.contains("WHEN MATCHED THEN DELETE"),
"Expected WHEN MATCHED THEN DELETE in: {}",
sql
);
}
#[test]
fn test_merge_with_condition() {
let sql = merge_into("target")
.using("source", col("target.id").eq(col("source.id")))
.when_matched_update_where(
col("source.active").eq(boolean(true)),
vec![("name", col("source.name"))],
)
.to_sql();
assert!(
sql.contains("MERGE INTO"),
"Expected MERGE INTO in: {}",
sql
);
assert!(
sql.contains("AND source.active = TRUE"),
"Expected condition in: {}",
sql
);
}
}