use crate::expressions::*;
use crate::generator::Generator;
use crate::parser::Parser;
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 {
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(),
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,
{
let mut builder = SelectBuilder::new();
for expr in expressions {
builder.select = builder.select.column(expr.into_expr().0);
}
builder
}
pub fn from(table_name: &str) -> SelectBuilder {
let mut builder = SelectBuilder::new();
builder.select.from = Some(From {
expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
});
builder
}
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 {
Generator::sql(&self.0).unwrap_or_default()
}
pub fn eq(self, other: Expr) -> Expr {
Expr(Expression::Eq(Box::new(binary_op(self.0, other.0))))
}
pub fn neq(self, other: Expr) -> Expr {
Expr(Expression::Neq(Box::new(binary_op(self.0, other.0))))
}
pub fn lt(self, other: Expr) -> Expr {
Expr(Expression::Lt(Box::new(binary_op(self.0, other.0))))
}
pub fn lte(self, other: Expr) -> Expr {
Expr(Expression::Lte(Box::new(binary_op(self.0, other.0))))
}
pub fn gt(self, other: Expr) -> Expr {
Expr(Expression::Gt(Box::new(binary_op(self.0, other.0))))
}
pub fn gte(self, other: Expr) -> Expr {
Expr(Expression::Gte(Box::new(binary_op(self.0, other.0))))
}
pub fn and(self, other: Expr) -> Expr {
Expr(Expression::And(Box::new(binary_op(self.0, other.0))))
}
pub fn or(self, other: Expr) -> Expr {
Expr(Expression::Or(Box::new(binary_op(self.0, other.0))))
}
pub fn not(self) -> Expr {
Expr(Expression::Not(Box::new(UnaryOp::new(self.0))))
}
pub fn xor(self, other: Expr) -> Expr {
Expr(Expression::Xor(Box::new(Xor {
this: Some(Box::new(self.0)),
expression: Some(Box::new(other.0)),
expressions: vec![],
})))
}
pub fn add(self, other: Expr) -> Expr {
Expr(Expression::Add(Box::new(binary_op(self.0, other.0))))
}
pub fn sub(self, other: Expr) -> Expr {
Expr(Expression::Sub(Box::new(binary_op(self.0, other.0))))
}
pub fn mul(self, other: Expr) -> Expr {
Expr(Expression::Mul(Box::new(binary_op(self.0, other.0))))
}
pub fn div(self, other: Expr) -> Expr {
Expr(Expression::Div(Box::new(binary_op(self.0, other.0))))
}
pub fn is_null(self) -> Expr {
Expr(Expression::Is(Box::new(BinaryOp {
left: self.0,
right: Expression::Null(Null),
left_comments: Vec::new(),
operator_comments: Vec::new(),
trailing_comments: Vec::new(),
inferred_type: None,
})))
}
pub fn is_not_null(self) -> Expr {
Expr(Expression::Not(Box::new(UnaryOp::new(Expression::Is(
Box::new(BinaryOp {
left: self.0,
right: Expression::Null(Null),
left_comments: Vec::new(),
operator_comments: Vec::new(),
trailing_comments: Vec::new(),
inferred_type: None,
}),
)))))
}
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(Expression::Like(Box::new(LikeOp {
left: self.0,
right: pattern.0,
escape: None,
quantifier: None,
inferred_type: None,
})))
}
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(Expression::ILike(Box::new(LikeOp {
left: self.0,
right: pattern.0,
escape: None,
quantifier: None,
inferred_type: None,
})))
}
pub fn rlike(self, pattern: Expr) -> Expr {
Expr(Expression::RegexpLike(Box::new(RegexpFunc {
this: self.0,
pattern: pattern.0,
flags: None,
})))
}
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(),
}
}
pub fn select_cols<I, E>(mut self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
for expr in expressions {
self.select.expressions.push(expr.into_expr().0);
}
self
}
pub fn from(mut self, table_name: &str) -> Self {
self.select.from = Some(From {
expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
});
self
}
pub fn from_expr(mut self, expr: Expr) -> Self {
self.select.from = Some(From {
expressions: vec![expr.0],
});
self
}
pub fn join(mut self, table_name: &str, on: Expr) -> Self {
self.select.joins.push(Join {
kind: JoinKind::Inner,
this: Expression::Table(Box::new(builder_table_ref(table_name))),
on: Some(on.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
}
pub fn left_join(mut self, table_name: &str, on: Expr) -> Self {
self.select.joins.push(Join {
kind: JoinKind::Left,
this: Expression::Table(Box::new(builder_table_ref(table_name))),
on: Some(on.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
}
pub fn where_(mut self, condition: Expr) -> Self {
self.select.where_clause = Some(Where { this: condition.0 });
self
}
pub fn group_by<I, E>(mut self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.select.group_by = Some(GroupBy {
expressions: expressions.into_iter().map(|e| e.into_expr().0).collect(),
all: None,
totals: false,
comments: Vec::new(),
});
self
}
pub fn having(mut self, condition: Expr) -> Self {
self.select.having = Some(Having {
this: condition.0,
comments: Vec::new(),
});
self
}
pub fn order_by<I, E>(mut self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.select.order_by = Some(OrderBy {
siblings: false,
comments: Vec::new(),
expressions: expressions
.into_iter()
.map(|e| {
let expr = e.into_expr().0;
match expr {
Expression::Ordered(_) => expr,
other => Expression::Ordered(Box::new(Ordered {
this: other,
desc: false,
nulls_first: None,
explicit_asc: false,
with_fill: None,
})),
}
})
.collect::<Vec<_>>()
.into_iter()
.map(|e| {
if let Expression::Ordered(o) = e {
*o
} else {
Ordered {
this: e,
desc: false,
nulls_first: None,
explicit_asc: false,
with_fill: None,
}
}
})
.collect(),
});
self
}
pub fn sort_by<I, E>(mut self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.select.sort_by = Some(SortBy {
expressions: 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 fn limit(mut self, count: usize) -> Self {
self.select.limit = Some(Limit {
this: Expression::Literal(Box::new(Literal::Number(count.to_string()))),
percent: false,
comments: Vec::new(),
});
self
}
pub fn offset(mut self, count: usize) -> Self {
self.select.offset = Some(Offset {
this: Expression::Literal(Box::new(Literal::Number(count.to_string()))),
rows: None,
});
self
}
pub fn distinct(mut self) -> Self {
self.select.distinct = true;
self
}
pub fn qualify(mut self, condition: Expr) -> Self {
self.select.qualify = Some(Qualify { this: condition.0 });
self
}
pub fn right_join(mut self, table_name: &str, on: Expr) -> Self {
self.select.joins.push(Join {
kind: JoinKind::Right,
this: Expression::Table(Box::new(builder_table_ref(table_name))),
on: Some(on.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
}
pub fn cross_join(mut self, table_name: &str) -> Self {
self.select.joins.push(Join {
kind: JoinKind::Cross,
this: Expression::Table(Box::new(builder_table_ref(table_name))),
on: None,
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
}
pub fn lateral_view<S: AsRef<str>>(
mut self,
table_function: Expr,
table_alias: &str,
column_aliases: impl IntoIterator<Item = S>,
) -> Self {
self.select.lateral_views.push(LateralView {
this: table_function.0,
table_alias: Some(builder_identifier(table_alias)),
column_aliases: column_aliases
.into_iter()
.map(|c| builder_identifier(c.as_ref()))
.collect(),
outer: false,
});
self
}
pub fn window(mut self, name: &str, def: WindowDefBuilder) -> Self {
let named_window = NamedWindow {
name: builder_identifier(name),
spec: Over {
window_name: None,
partition_by: def.partition_by,
order_by: def.order_by,
frame: None,
alias: None,
},
};
match self.select.windows {
Some(ref mut windows) => windows.push(named_window),
None => self.select.windows = Some(vec![named_window]),
}
self
}
pub fn for_update(mut self) -> Self {
self.select.locks.push(Lock {
update: Some(Box::new(Expression::Boolean(BooleanLiteral {
value: true,
}))),
expressions: vec![],
wait: None,
key: None,
});
self
}
pub fn for_share(mut self) -> Self {
self.select.locks.push(Lock {
update: None,
expressions: vec![],
wait: None,
key: None,
});
self
}
pub fn hint(mut self, hint_text: &str) -> Self {
let hint_expr = HintExpression::Raw(hint_text.to_string());
match &mut self.select.hint {
Some(h) => h.expressions.push(hint_expr),
None => {
self.select.hint = Some(Hint {
expressions: vec![hint_expr],
})
}
}
self
}
pub fn ctas(self, table_name: &str) -> Expression {
Expression::CreateTable(Box::new(CreateTable {
name: builder_table_ref(table_name),
on_cluster: None,
columns: vec![],
constraints: vec![],
if_not_exists: false,
temporary: false,
or_replace: false,
table_modifier: None,
as_select: Some(self.build()),
as_select_parenthesized: false,
on_commit: None,
clone_source: None,
clone_at_clause: None,
is_copy: false,
shallow_clone: false,
leading_comments: vec![],
with_properties: vec![],
teradata_post_name_options: vec![],
with_data: None,
with_statistics: None,
teradata_indexes: vec![],
with_cte: None,
properties: vec![],
partition_of: None,
post_table_properties: vec![],
mysql_table_options: vec![],
inherits: vec![],
on_property: None,
copy_grants: false,
using_template: None,
rollup: None,
uuid: None,
with_partition_columns: vec![],
with_connection: None,
}))
}
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 {
Generator::sql(&self.build()).unwrap_or_default()
}
}
pub struct DeleteBuilder {
delete: Delete,
}
impl DeleteBuilder {
pub fn where_(mut self, condition: Expr) -> Self {
self.delete.where_clause = Some(Where { this: condition.0 });
self
}
pub fn build(self) -> Expression {
Expression::Delete(Box::new(self.delete))
}
pub fn to_sql(self) -> String {
Generator::sql(&self.build()).unwrap_or_default()
}
}
pub struct InsertBuilder {
insert: Insert,
}
impl InsertBuilder {
pub fn columns<I, S>(mut self, columns: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.insert.columns = columns
.into_iter()
.map(|c| builder_identifier(c.as_ref()))
.collect();
self
}
pub fn values<I>(mut self, values: I) -> Self
where
I: IntoIterator<Item = Expr>,
{
self.insert
.values
.push(values.into_iter().map(|v| v.0).collect());
self
}
pub fn query(mut self, query: SelectBuilder) -> Self {
self.insert.query = Some(query.build());
self
}
pub fn build(self) -> Expression {
Expression::Insert(Box::new(self.insert))
}
pub fn to_sql(self) -> String {
Generator::sql(&self.build()).unwrap_or_default()
}
}
pub struct UpdateBuilder {
update: Update,
}
impl UpdateBuilder {
pub fn set(mut self, column: &str, value: Expr) -> Self {
self.update.set.push((builder_identifier(column), value.0));
self
}
pub fn where_(mut self, condition: Expr) -> Self {
self.update.where_clause = Some(Where { this: condition.0 });
self
}
pub fn from(mut self, table_name: &str) -> Self {
self.update.from_clause = Some(From {
expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
});
self
}
pub fn build(self) -> Expression {
Expression::Update(Box::new(self.update))
}
pub fn to_sql(self) -> String {
Generator::sql(&self.build()).unwrap_or_default()
}
}
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 {
Expression::Case(Box::new(Case {
operand: self.operand,
whens: self.whens,
else_: self.else_,
comments: Vec::new(),
inferred_type: None,
}))
}
}
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(Expression::Subquery(Box::new(Subquery {
this: expr,
alias: Some(builder_identifier(alias_name)),
column_aliases: Vec::new(),
order_by: None,
limit: None,
offset: None,
distribute_by: None,
sort_by: None,
cluster_by: None,
lateral: false,
modifiers_inside: true,
trailing_comments: Vec::new(),
inferred_type: None,
})))
}
#[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>(mut self, expressions: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoExpr,
{
self.order_by = Some(OrderBy {
siblings: false,
comments: Vec::new(),
expressions: 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 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 {
match self.kind {
SetOpKind::Union => Expression::Union(Box::new(Union {
left: self.left,
right: self.right,
all: self.all,
distinct: false,
with: None,
order_by: self.order_by,
limit: self.limit,
offset: self.offset,
distribute_by: None,
sort_by: None,
cluster_by: None,
by_name: false,
side: None,
kind: None,
corresponding: false,
strict: false,
on_columns: Vec::new(),
})),
SetOpKind::Intersect => Expression::Intersect(Box::new(Intersect {
left: self.left,
right: self.right,
all: self.all,
distinct: false,
with: None,
order_by: self.order_by,
limit: self.limit,
offset: self.offset,
distribute_by: None,
sort_by: None,
cluster_by: None,
by_name: false,
side: None,
kind: None,
corresponding: false,
strict: false,
on_columns: Vec::new(),
})),
SetOpKind::Except => Expression::Except(Box::new(Except {
left: self.left,
right: self.right,
all: self.all,
distinct: false,
with: None,
order_by: self.order_by,
limit: self.limit,
offset: self.offset,
distribute_by: None,
sort_by: None,
cluster_by: None,
by_name: false,
side: None,
kind: None,
corresponding: false,
strict: false,
on_columns: Vec::new(),
})),
}
}
pub fn to_sql(self) -> String {
Generator::sql(&self.build()).unwrap_or_default()
}
}
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 }))
}
}
fn binary_op(left: Expression, right: Expression) -> BinaryOp {
BinaryOp {
left,
right,
left_comments: Vec::new(),
operator_comments: Vec::new(),
trailing_comments: Vec::new(),
inferred_type: None,
}
}
pub fn merge_into(target: &str) -> MergeBuilder {
MergeBuilder {
target: Expression::Table(Box::new(builder_table_ref(target))),
using: None,
on: None,
whens: Vec::new(),
}
}
pub struct MergeBuilder {
target: Expression,
using: Option<Expression>,
on: Option<Expression>,
whens: Vec<Expression>,
}
impl MergeBuilder {
pub fn using(mut self, source: &str, on: Expr) -> Self {
self.using = Some(Expression::Table(Box::new(builder_table_ref(source))));
self.on = Some(on.0);
self
}
pub fn when_matched_update(mut self, assignments: Vec<(&str, Expr)>) -> Self {
let eqs: Vec<Expression> = assignments
.into_iter()
.map(|(col_name, val)| {
Expression::Eq(Box::new(BinaryOp {
left: Expression::boxed_column(Column {
name: builder_identifier(col_name),
table: None,
join_mark: false,
trailing_comments: Vec::new(),
span: None,
inferred_type: None,
}),
right: val.0,
left_comments: Vec::new(),
operator_comments: Vec::new(),
trailing_comments: Vec::new(),
inferred_type: None,
}))
})
.collect();
let action = Expression::Tuple(Box::new(Tuple {
expressions: vec![
Expression::Var(Box::new(Var {
this: "UPDATE".to_string(),
})),
Expression::Tuple(Box::new(Tuple { expressions: eqs })),
],
}));
let when = Expression::When(Box::new(When {
matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
value: true,
}))),
source: None,
condition: None,
then: Box::new(action),
}));
self.whens.push(when);
self
}
pub fn when_matched_update_where(
mut self,
condition: Expr,
assignments: Vec<(&str, Expr)>,
) -> Self {
let eqs: Vec<Expression> = assignments
.into_iter()
.map(|(col_name, val)| {
Expression::Eq(Box::new(BinaryOp {
left: Expression::boxed_column(Column {
name: builder_identifier(col_name),
table: None,
join_mark: false,
trailing_comments: Vec::new(),
span: None,
inferred_type: None,
}),
right: val.0,
left_comments: Vec::new(),
operator_comments: Vec::new(),
trailing_comments: Vec::new(),
inferred_type: None,
}))
})
.collect();
let action = Expression::Tuple(Box::new(Tuple {
expressions: vec![
Expression::Var(Box::new(Var {
this: "UPDATE".to_string(),
})),
Expression::Tuple(Box::new(Tuple { expressions: eqs })),
],
}));
let when = Expression::When(Box::new(When {
matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
value: true,
}))),
source: None,
condition: Some(Box::new(condition.0)),
then: Box::new(action),
}));
self.whens.push(when);
self
}
pub fn when_matched_delete(mut self) -> Self {
let action = Expression::Var(Box::new(Var {
this: "DELETE".to_string(),
}));
let when = Expression::When(Box::new(When {
matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
value: true,
}))),
source: None,
condition: None,
then: Box::new(action),
}));
self.whens.push(when);
self
}
pub fn when_not_matched_insert(mut self, columns: &[&str], values: Vec<Expr>) -> Self {
let col_exprs: Vec<Expression> = columns
.iter()
.map(|c| {
Expression::boxed_column(Column {
name: builder_identifier(c),
table: None,
join_mark: false,
trailing_comments: Vec::new(),
span: None,
inferred_type: None,
})
})
.collect();
let val_exprs: Vec<Expression> = values.into_iter().map(|v| v.0).collect();
let action = Expression::Tuple(Box::new(Tuple {
expressions: vec![
Expression::Var(Box::new(Var {
this: "INSERT".to_string(),
})),
Expression::Tuple(Box::new(Tuple {
expressions: col_exprs,
})),
Expression::Tuple(Box::new(Tuple {
expressions: val_exprs,
})),
],
}));
let when = Expression::When(Box::new(When {
matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
value: false,
}))),
source: None,
condition: None,
then: Box::new(action),
}));
self.whens.push(when);
self
}
pub fn build(self) -> Expression {
let whens_expr = Expression::Whens(Box::new(Whens {
expressions: self.whens,
}));
Expression::Merge(Box::new(Merge {
this: Box::new(self.target),
using: Box::new(
self.using
.unwrap_or(Expression::Null(crate::expressions::Null)),
),
on: self.on.map(Box::new),
using_cond: None,
whens: Some(Box::new(whens_expr)),
with_: None,
returning: None,
}))
}
pub fn to_sql(self) -> String {
Generator::sql(&self.build()).unwrap_or_default()
}
}
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_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 NOT status IN ('deleted', 'banned')"
);
}
#[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"));
}
#[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");
}
#[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
);
}
}