use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum QuoteStyle {
#[default]
None,
DoubleQuote,
Backtick,
Bracket,
}
impl QuoteStyle {
#[must_use]
pub fn for_dialect(dialect: crate::dialects::Dialect) -> Self {
use crate::dialects::Dialect;
match dialect {
Dialect::Tsql | Dialect::Fabric => QuoteStyle::Bracket,
Dialect::Mysql | Dialect::BigQuery | Dialect::Hive | Dialect::Spark
| Dialect::Databricks | Dialect::Doris | Dialect::SingleStore
| Dialect::StarRocks => QuoteStyle::Backtick,
_ => QuoteStyle::DoubleQuote,
}
}
#[must_use]
pub fn is_quoted(self) -> bool {
!matches!(self, QuoteStyle::None)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Statement {
Select(SelectStatement),
Insert(InsertStatement),
Update(UpdateStatement),
Delete(DeleteStatement),
CreateTable(CreateTableStatement),
DropTable(DropTableStatement),
SetOperation(SetOperationStatement),
AlterTable(AlterTableStatement),
CreateView(CreateViewStatement),
DropView(DropViewStatement),
Truncate(TruncateStatement),
Transaction(TransactionStatement),
Explain(ExplainStatement),
Use(UseStatement),
Expression(Expr),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SelectStatement {
pub ctes: Vec<Cte>,
pub distinct: bool,
pub top: Option<Box<Expr>>,
pub columns: Vec<SelectItem>,
pub from: Option<FromClause>,
pub joins: Vec<JoinClause>,
pub where_clause: Option<Expr>,
pub group_by: Vec<Expr>,
pub having: Option<Expr>,
pub order_by: Vec<OrderByItem>,
pub limit: Option<Expr>,
pub offset: Option<Expr>,
pub fetch_first: Option<Expr>,
pub qualify: Option<Expr>,
pub window_definitions: Vec<WindowDefinition>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Cte {
pub name: String,
pub columns: Vec<String>,
pub query: Box<Statement>,
pub materialized: Option<bool>,
pub recursive: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WindowDefinition {
pub name: String,
pub spec: WindowSpec,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SetOperationStatement {
pub op: SetOperationType,
pub all: bool,
pub left: Box<Statement>,
pub right: Box<Statement>,
pub order_by: Vec<OrderByItem>,
pub limit: Option<Expr>,
pub offset: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SetOperationType {
Union,
Intersect,
Except,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SelectItem {
Wildcard,
QualifiedWildcard { table: String },
Expr { expr: Expr, alias: Option<String> },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FromClause {
pub source: TableSource,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TableSource {
Table(TableRef),
Subquery {
query: Box<Statement>,
alias: Option<String>,
},
TableFunction {
name: String,
args: Vec<Expr>,
alias: Option<String>,
},
Lateral {
source: Box<TableSource>,
},
Unnest {
expr: Box<Expr>,
alias: Option<String>,
with_offset: bool,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TableRef {
pub catalog: Option<String>,
pub schema: Option<String>,
pub name: String,
pub alias: Option<String>,
#[serde(default)]
pub name_quote_style: QuoteStyle,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct JoinClause {
pub join_type: JoinType,
pub table: TableSource,
pub on: Option<Expr>,
pub using: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum JoinType {
Inner,
Left,
Right,
Full,
Cross,
Natural,
Lateral,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrderByItem {
pub expr: Expr,
pub ascending: bool,
pub nulls_first: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Expr {
Column {
table: Option<String>,
name: String,
#[serde(default)]
quote_style: QuoteStyle,
#[serde(default)]
table_quote_style: QuoteStyle,
},
Number(String),
StringLiteral(String),
Boolean(bool),
Null,
BinaryOp {
left: Box<Expr>,
op: BinaryOperator,
right: Box<Expr>,
},
UnaryOp {
op: UnaryOperator,
expr: Box<Expr>,
},
Function {
name: String,
args: Vec<Expr>,
distinct: bool,
filter: Option<Box<Expr>>,
over: Option<WindowSpec>,
},
Between {
expr: Box<Expr>,
low: Box<Expr>,
high: Box<Expr>,
negated: bool,
},
InList {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
InSubquery {
expr: Box<Expr>,
subquery: Box<Statement>,
negated: bool,
},
AnyOp {
expr: Box<Expr>,
op: BinaryOperator,
right: Box<Expr>,
},
AllOp {
expr: Box<Expr>,
op: BinaryOperator,
right: Box<Expr>,
},
IsNull {
expr: Box<Expr>,
negated: bool,
},
IsBool {
expr: Box<Expr>,
value: bool,
negated: bool,
},
Like {
expr: Box<Expr>,
pattern: Box<Expr>,
negated: bool,
escape: Option<Box<Expr>>,
},
ILike {
expr: Box<Expr>,
pattern: Box<Expr>,
negated: bool,
escape: Option<Box<Expr>>,
},
Case {
operand: Option<Box<Expr>>,
when_clauses: Vec<(Expr, Expr)>,
else_clause: Option<Box<Expr>>,
},
Nested(Box<Expr>),
Wildcard,
Subquery(Box<Statement>),
Exists {
subquery: Box<Statement>,
negated: bool,
},
Cast {
expr: Box<Expr>,
data_type: DataType,
},
TryCast {
expr: Box<Expr>,
data_type: DataType,
},
Extract {
field: DateTimeField,
expr: Box<Expr>,
},
Interval {
value: Box<Expr>,
unit: Option<DateTimeField>,
},
ArrayLiteral(Vec<Expr>),
Tuple(Vec<Expr>),
Coalesce(Vec<Expr>),
If {
condition: Box<Expr>,
true_val: Box<Expr>,
false_val: Option<Box<Expr>>,
},
NullIf {
expr: Box<Expr>,
r#else: Box<Expr>,
},
Collate {
expr: Box<Expr>,
collation: String,
},
Parameter(String),
TypeExpr(DataType),
QualifiedWildcard { table: String },
Star,
Alias {
expr: Box<Expr>,
name: String,
},
ArrayIndex {
expr: Box<Expr>,
index: Box<Expr>,
},
JsonAccess {
expr: Box<Expr>,
path: Box<Expr>,
as_text: bool,
},
Lambda {
params: Vec<String>,
body: Box<Expr>,
},
Default,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WindowSpec {
pub window_ref: Option<String>,
pub partition_by: Vec<Expr>,
pub order_by: Vec<OrderByItem>,
pub frame: Option<WindowFrame>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WindowFrame {
pub kind: WindowFrameKind,
pub start: WindowFrameBound,
pub end: Option<WindowFrameBound>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum WindowFrameKind {
Rows,
Range,
Groups,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WindowFrameBound {
CurrentRow,
Preceding(Option<Box<Expr>>), Following(Option<Box<Expr>>), }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DateTimeField {
Year,
Quarter,
Month,
Week,
Day,
DayOfWeek,
DayOfYear,
Hour,
Minute,
Second,
Millisecond,
Microsecond,
Nanosecond,
Epoch,
Timezone,
TimezoneHour,
TimezoneMinute,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOperator {
Plus,
Minus,
Multiply,
Divide,
Modulo,
Eq,
Neq,
Lt,
Gt,
LtEq,
GtEq,
And,
Or,
Xor,
Concat,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
ShiftLeft,
ShiftRight,
Arrow,
DoubleArrow,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOperator {
Not,
Minus,
Plus,
BitwiseNot,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InsertStatement {
pub table: TableRef,
pub columns: Vec<String>,
pub source: InsertSource,
pub on_conflict: Option<OnConflict>,
pub returning: Vec<SelectItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum InsertSource {
Values(Vec<Vec<Expr>>),
Query(Box<Statement>),
Default,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OnConflict {
pub columns: Vec<String>,
pub action: ConflictAction,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ConflictAction {
DoNothing,
DoUpdate(Vec<(String, Expr)>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UpdateStatement {
pub table: TableRef,
pub assignments: Vec<(String, Expr)>,
pub from: Option<FromClause>,
pub where_clause: Option<Expr>,
pub returning: Vec<SelectItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeleteStatement {
pub table: TableRef,
pub using: Option<FromClause>,
pub where_clause: Option<Expr>,
pub returning: Vec<SelectItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateTableStatement {
pub if_not_exists: bool,
pub temporary: bool,
pub table: TableRef,
pub columns: Vec<ColumnDef>,
pub constraints: Vec<TableConstraint>,
pub as_select: Option<Box<Statement>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TableConstraint {
PrimaryKey {
name: Option<String>,
columns: Vec<String>,
},
Unique {
name: Option<String>,
columns: Vec<String>,
},
ForeignKey {
name: Option<String>,
columns: Vec<String>,
ref_table: TableRef,
ref_columns: Vec<String>,
on_delete: Option<ReferentialAction>,
on_update: Option<ReferentialAction>,
},
Check {
name: Option<String>,
expr: Expr,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReferentialAction {
Cascade,
Restrict,
NoAction,
SetNull,
SetDefault,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ColumnDef {
pub name: String,
pub data_type: DataType,
pub nullable: Option<bool>,
pub default: Option<Expr>,
pub primary_key: bool,
pub unique: bool,
pub auto_increment: bool,
pub collation: Option<String>,
pub comment: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlterTableStatement {
pub table: TableRef,
pub actions: Vec<AlterTableAction>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AlterTableAction {
AddColumn(ColumnDef),
DropColumn { name: String, if_exists: bool },
RenameColumn { old_name: String, new_name: String },
AlterColumnType { name: String, data_type: DataType },
AddConstraint(TableConstraint),
DropConstraint { name: String },
RenameTable { new_name: String },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateViewStatement {
pub name: TableRef,
pub columns: Vec<String>,
pub query: Box<Statement>,
pub or_replace: bool,
pub materialized: bool,
pub if_not_exists: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DropViewStatement {
pub name: TableRef,
pub if_exists: bool,
pub materialized: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TruncateStatement {
pub table: TableRef,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TransactionStatement {
Begin,
Commit,
Rollback,
Savepoint(String),
ReleaseSavepoint(String),
RollbackTo(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExplainStatement {
pub analyze: bool,
pub statement: Box<Statement>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UseStatement {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DropTableStatement {
pub if_exists: bool,
pub table: TableRef,
pub cascade: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DataType {
TinyInt,
SmallInt,
Int,
BigInt,
Float,
Double,
Decimal { precision: Option<u32>, scale: Option<u32> },
Numeric { precision: Option<u32>, scale: Option<u32> },
Real,
Varchar(Option<u32>),
Char(Option<u32>),
Text,
String,
Binary(Option<u32>),
Varbinary(Option<u32>),
Boolean,
Date,
Time { precision: Option<u32> },
Timestamp { precision: Option<u32>, with_tz: bool },
Interval,
DateTime,
Blob,
Bytea,
Bytes,
Json,
Jsonb,
Uuid,
Array(Option<Box<DataType>>),
Map { key: Box<DataType>, value: Box<DataType> },
Struct(Vec<(String, DataType)>),
Tuple(Vec<DataType>),
Null,
Unknown(String),
Variant,
Object,
Xml,
Inet,
Cidr,
Macaddr,
Bit(Option<u32>),
Money,
Serial,
BigSerial,
SmallSerial,
Regclass,
Regtype,
Hstore,
Geography,
Geometry,
Super,
}
impl Expr {
pub fn walk<F>(&self, visitor: &mut F)
where
F: FnMut(&Expr) -> bool,
{
if !visitor(self) {
return;
}
match self {
Expr::BinaryOp { left, right, .. } => {
left.walk(visitor);
right.walk(visitor);
}
Expr::UnaryOp { expr, .. } => expr.walk(visitor),
Expr::Function { args, filter, .. } => {
for arg in args {
arg.walk(visitor);
}
if let Some(f) = filter {
f.walk(visitor);
}
}
Expr::Between { expr, low, high, .. } => {
expr.walk(visitor);
low.walk(visitor);
high.walk(visitor);
}
Expr::InList { expr, list, .. } => {
expr.walk(visitor);
for item in list {
item.walk(visitor);
}
}
Expr::InSubquery { expr, .. } => {
expr.walk(visitor);
}
Expr::IsNull { expr, .. } => expr.walk(visitor),
Expr::IsBool { expr, .. } => expr.walk(visitor),
Expr::AnyOp { expr, right, .. } | Expr::AllOp { expr, right, .. } => {
expr.walk(visitor);
right.walk(visitor);
}
Expr::Like { expr, pattern, .. } | Expr::ILike { expr, pattern, .. } => {
expr.walk(visitor);
pattern.walk(visitor);
}
Expr::Case {
operand,
when_clauses,
else_clause,
} => {
if let Some(op) = operand {
op.walk(visitor);
}
for (cond, result) in when_clauses {
cond.walk(visitor);
result.walk(visitor);
}
if let Some(el) = else_clause {
el.walk(visitor);
}
}
Expr::Nested(inner) => inner.walk(visitor),
Expr::Cast { expr, .. } | Expr::TryCast { expr, .. } => expr.walk(visitor),
Expr::Extract { expr, .. } => expr.walk(visitor),
Expr::Interval { value, .. } => value.walk(visitor),
Expr::ArrayLiteral(items) | Expr::Tuple(items) | Expr::Coalesce(items) => {
for item in items {
item.walk(visitor);
}
}
Expr::If { condition, true_val, false_val } => {
condition.walk(visitor);
true_val.walk(visitor);
if let Some(fv) = false_val {
fv.walk(visitor);
}
}
Expr::NullIf { expr, r#else } => {
expr.walk(visitor);
r#else.walk(visitor);
}
Expr::Collate { expr, .. } => expr.walk(visitor),
Expr::Alias { expr, .. } => expr.walk(visitor),
Expr::ArrayIndex { expr, index } => {
expr.walk(visitor);
index.walk(visitor);
}
Expr::JsonAccess { expr, path, .. } => {
expr.walk(visitor);
path.walk(visitor);
}
Expr::Lambda { body, .. } => body.walk(visitor),
Expr::Column { .. }
| Expr::Number(_)
| Expr::StringLiteral(_)
| Expr::Boolean(_)
| Expr::Null
| Expr::Wildcard
| Expr::Star
| Expr::Parameter(_)
| Expr::TypeExpr(_)
| Expr::QualifiedWildcard { .. }
| Expr::Default
| Expr::Subquery(_)
| Expr::Exists { .. } => {}
}
}
#[must_use]
pub fn find<F>(&self, predicate: &F) -> Option<&Expr>
where
F: Fn(&Expr) -> bool,
{
let mut result = None;
self.walk(&mut |expr| {
if result.is_some() {
return false;
}
if predicate(expr) {
result = Some(expr as *const Expr);
false
} else {
true
}
});
result.map(|p| unsafe { &*p })
}
#[must_use]
pub fn find_all<F>(&self, predicate: &F) -> Vec<&Expr>
where
F: Fn(&Expr) -> bool,
{
let mut results: Vec<*const Expr> = Vec::new();
self.walk(&mut |expr| {
if predicate(expr) {
results.push(expr as *const Expr);
}
true
});
results.into_iter().map(|p| unsafe { &*p }).collect()
}
#[must_use]
pub fn transform<F>(self, func: &F) -> Expr
where
F: Fn(Expr) -> Expr,
{
let transformed = match self {
Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
left: Box::new(left.transform(func)),
op,
right: Box::new(right.transform(func)),
},
Expr::UnaryOp { op, expr } => Expr::UnaryOp {
op,
expr: Box::new(expr.transform(func)),
},
Expr::Function { name, args, distinct, filter, over } => Expr::Function {
name,
args: args.into_iter().map(|a| a.transform(func)).collect(),
distinct,
filter: filter.map(|f| Box::new(f.transform(func))),
over,
},
Expr::Nested(inner) => Expr::Nested(Box::new(inner.transform(func))),
Expr::Cast { expr, data_type } => Expr::Cast {
expr: Box::new(expr.transform(func)),
data_type,
},
Expr::Between { expr, low, high, negated } => Expr::Between {
expr: Box::new(expr.transform(func)),
low: Box::new(low.transform(func)),
high: Box::new(high.transform(func)),
negated,
},
Expr::Case { operand, when_clauses, else_clause } => Expr::Case {
operand: operand.map(|o| Box::new(o.transform(func))),
when_clauses: when_clauses
.into_iter()
.map(|(c, r)| (c.transform(func), r.transform(func)))
.collect(),
else_clause: else_clause.map(|e| Box::new(e.transform(func))),
},
Expr::IsBool { expr, value, negated } => Expr::IsBool {
expr: Box::new(expr.transform(func)),
value,
negated,
},
Expr::AnyOp { expr, op, right } => Expr::AnyOp {
expr: Box::new(expr.transform(func)),
op,
right: Box::new(right.transform(func)),
},
Expr::AllOp { expr, op, right } => Expr::AllOp {
expr: Box::new(expr.transform(func)),
op,
right: Box::new(right.transform(func)),
},
other => other,
};
func(transformed)
}
#[must_use]
pub fn is_column(&self) -> bool {
matches!(self, Expr::Column { .. })
}
#[must_use]
pub fn is_literal(&self) -> bool {
matches!(
self,
Expr::Number(_) | Expr::StringLiteral(_) | Expr::Boolean(_) | Expr::Null
)
}
#[must_use]
pub fn sql(&self) -> String {
use crate::generator::Generator;
Generator::expr_to_sql(self)
}
}
#[must_use]
pub fn find_columns(expr: &Expr) -> Vec<&Expr> {
expr.find_all(&|e| matches!(e, Expr::Column { .. }))
}
#[must_use]
pub fn find_tables(statement: &Statement) -> Vec<&TableRef> {
match statement {
Statement::Select(sel) => {
let mut tables = Vec::new();
if let Some(from) = &sel.from {
collect_table_refs_from_source(&from.source, &mut tables);
}
for join in &sel.joins {
collect_table_refs_from_source(&join.table, &mut tables);
}
tables
}
Statement::Insert(ins) => vec![&ins.table],
Statement::Update(upd) => vec![&upd.table],
Statement::Delete(del) => vec![&del.table],
Statement::CreateTable(ct) => vec![&ct.table],
Statement::DropTable(dt) => vec![&dt.table],
_ => vec![],
}
}
fn collect_table_refs_from_source<'a>(source: &'a TableSource, tables: &mut Vec<&'a TableRef>) {
match source {
TableSource::Table(table_ref) => tables.push(table_ref),
TableSource::Subquery { .. } => {}
TableSource::TableFunction { .. } => {}
TableSource::Lateral { source } => collect_table_refs_from_source(source, tables),
TableSource::Unnest { .. } => {}
}
}