use super::{DialectImpl, DialectType};
use crate::error::Result;
use crate::expressions::{AggFunc, Case, Cast, Expression, Function, UnaryFunc, VarArgFunc};
use crate::generator::GeneratorConfig;
use crate::tokens::TokenizerConfig;
pub struct TeradataDialect;
impl DialectImpl for TeradataDialect {
fn dialect_type(&self) -> DialectType {
DialectType::Teradata
}
fn tokenizer_config(&self) -> TokenizerConfig {
let mut config = TokenizerConfig::default();
config.identifiers.insert('"', '"');
config.nested_comments = false;
config
.keywords
.insert("SEL".to_string(), crate::tokens::TokenType::Select);
config
.keywords
.insert("UPD".to_string(), crate::tokens::TokenType::Update);
config
.keywords
.insert("DEL".to_string(), crate::tokens::TokenType::Delete);
config
.keywords
.insert("INS".to_string(), crate::tokens::TokenType::Insert);
config
.keywords
.insert("SAMPLE".to_string(), crate::tokens::TokenType::Sample);
config
.keywords
.insert("LOCKING".to_string(), crate::tokens::TokenType::Lock);
config
.keywords
.insert("HELP".to_string(), crate::tokens::TokenType::Command);
config
.keywords
.insert("COLLECT".to_string(), crate::tokens::TokenType::Command);
config
.keywords
.insert("EQ".to_string(), crate::tokens::TokenType::Eq);
config
.keywords
.insert("NE".to_string(), crate::tokens::TokenType::Neq);
config
.keywords
.insert("GE".to_string(), crate::tokens::TokenType::Gte);
config
.keywords
.insert("GT".to_string(), crate::tokens::TokenType::Gt);
config
.keywords
.insert("LE".to_string(), crate::tokens::TokenType::Lte);
config
.keywords
.insert("LT".to_string(), crate::tokens::TokenType::Lt);
config
.keywords
.insert("MOD".to_string(), crate::tokens::TokenType::Mod);
config
.keywords
.insert("BYTEINT".to_string(), crate::tokens::TokenType::SmallInt);
config.keywords.insert(
"ST_GEOMETRY".to_string(),
crate::tokens::TokenType::Geometry,
);
config.single_tokens.remove(&'%');
config.hex_number_strings = true;
config
}
fn generator_config(&self) -> GeneratorConfig {
use crate::generator::IdentifierQuoteStyle;
GeneratorConfig {
identifier_quote: '"',
identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
dialect: Some(DialectType::Teradata),
tablesample_keywords: "SAMPLE",
tablesample_requires_parens: false,
tz_to_with_time_zone: true,
..Default::default()
}
}
fn transform_expr(&self, expr: Expression) -> Result<Expression> {
match expr {
Expression::IfNull(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
original_name: None,
expressions: vec![f.this, f.expression],
inferred_type: None,
}))),
Expression::Nvl(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
original_name: None,
expressions: vec![f.this, f.expression],
inferred_type: None,
}))),
Expression::Coalesce(mut f) => {
f.original_name = None;
Ok(Expression::Coalesce(f))
}
Expression::TryCast(c) => Ok(Expression::TryCast(c)),
Expression::SafeCast(c) => Ok(Expression::TryCast(c)),
Expression::CountIf(f) => {
let case_expr = Expression::Case(Box::new(Case {
operand: None,
whens: vec![(f.this.clone(), Expression::number(1))],
else_: Some(Expression::number(0)),
comments: Vec::new(),
inferred_type: None,
}));
Ok(Expression::Sum(Box::new(AggFunc {
ignore_nulls: None,
having_max: None,
this: case_expr,
distinct: f.distinct,
filter: f.filter,
order_by: Vec::new(),
name: None,
limit: None,
inferred_type: None,
})))
}
Expression::Rand(r) => {
if r.lower.is_some() || r.upper.is_some() {
Ok(Expression::Rand(r))
} else {
Ok(Expression::Random(crate::expressions::Random))
}
}
Expression::Function(f) => self.transform_function(*f),
Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
Expression::Cast(c) => self.transform_cast(*c),
_ => Ok(expr),
}
}
}
impl TeradataDialect {
fn transform_function(&self, f: Function) -> Result<Expression> {
let name_upper = f.name.to_uppercase();
match name_upper.as_str() {
"IFNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
original_name: None,
expressions: f.args,
inferred_type: None,
}))),
"NVL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
original_name: None,
expressions: f.args,
inferred_type: None,
}))),
"ISNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
original_name: None,
expressions: f.args,
inferred_type: None,
}))),
"NOW" => Ok(Expression::CurrentTimestamp(
crate::expressions::CurrentTimestamp {
precision: None,
sysdate: false,
},
)),
"GETDATE" => Ok(Expression::CurrentTimestamp(
crate::expressions::CurrentTimestamp {
precision: None,
sysdate: false,
},
)),
"RAND" => Ok(Expression::Random(crate::expressions::Random)),
"LEN" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc::new(
f.args.into_iter().next().unwrap(),
)))),
"LENGTH" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc::new(
f.args.into_iter().next().unwrap(),
)))),
"CHARINDEX" if f.args.len() >= 2 => {
let mut args = f.args;
let substring = args.remove(0);
let string = args.remove(0);
Ok(Expression::Function(Box::new(Function::new(
"INSTR".to_string(),
vec![string, substring],
))))
}
"STRPOS" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
"INSTR".to_string(),
f.args,
)))),
"LOCATE" if f.args.len() >= 2 => {
let mut args = f.args;
let substring = args.remove(0);
let string = args.remove(0);
Ok(Expression::Function(Box::new(Function::new(
"INSTR".to_string(),
vec![string, substring],
))))
}
"ARRAY_LENGTH" if f.args.len() == 1 => Ok(Expression::Function(Box::new(
Function::new("CARDINALITY".to_string(), f.args),
))),
"SIZE" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
"CARDINALITY".to_string(),
f.args,
)))),
"SUBSTR" => Ok(Expression::Function(Box::new(Function::new(
"SUBSTRING".to_string(),
f.args,
)))),
"DATE_FORMAT" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(
Function::new("TO_CHAR".to_string(), f.args),
))),
"STRFTIME" if f.args.len() >= 2 => {
let mut args = f.args;
let format = args.remove(0);
let date = args.remove(0);
Ok(Expression::Function(Box::new(Function::new(
"TO_CHAR".to_string(),
vec![date, format],
))))
}
"GREATEST" => Ok(Expression::Function(Box::new(f))),
"LEAST" => Ok(Expression::Function(Box::new(f))),
_ => Ok(Expression::Function(Box::new(f))),
}
}
fn transform_aggregate_function(
&self,
f: Box<crate::expressions::AggregateFunction>,
) -> Result<Expression> {
let name_upper = f.name.to_uppercase();
match name_upper.as_str() {
"COUNT_IF" if !f.args.is_empty() => {
let condition = f.args.into_iter().next().unwrap();
let case_expr = Expression::Case(Box::new(Case {
operand: None,
whens: vec![(condition, Expression::number(1))],
else_: Some(Expression::number(0)),
comments: Vec::new(),
inferred_type: None,
}));
Ok(Expression::Sum(Box::new(AggFunc {
ignore_nulls: None,
having_max: None,
this: case_expr,
distinct: f.distinct,
filter: f.filter,
order_by: Vec::new(),
name: None,
limit: None,
inferred_type: None,
})))
}
"MAX_BY" => Ok(Expression::AggregateFunction(f)),
"MIN_BY" => Ok(Expression::AggregateFunction(f)),
_ => Ok(Expression::AggregateFunction(f)),
}
}
fn transform_cast(&self, c: Cast) -> Result<Expression> {
if let Some(format_expr) = &c.format {
let is_date = matches!(c.to, crate::expressions::DataType::Date);
let is_timestamp = matches!(c.to, crate::expressions::DataType::Timestamp { .. });
if is_date || is_timestamp {
let fmt_str = match format_expr.as_ref() {
Expression::Literal(lit)
if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
{
let crate::expressions::Literal::String(s) = lit.as_ref() else {
unreachable!()
};
Some(s.clone())
}
_ => None,
};
if let Some(teradata_fmt) = fmt_str {
let strftime_fmt = Self::teradata_to_strftime(&teradata_fmt);
if is_date {
return Ok(Expression::StrToDate(Box::new(
crate::expressions::StrToDate {
this: Box::new(c.this),
format: Some(strftime_fmt),
safe: None,
},
)));
} else {
return Ok(Expression::StrToTime(Box::new(
crate::expressions::StrToTime {
this: Box::new(c.this),
format: strftime_fmt,
zone: None,
safe: None,
target_type: None,
},
)));
}
}
}
}
Ok(Expression::Cast(Box::new(c)))
}
fn teradata_to_strftime(fmt: &str) -> String {
let mut result = fmt.to_string();
result = result.replace("YYYY", "%Y");
result = result.replace("Y4", "%Y");
result = result.replace("YY", "%y");
result = result.replace("MMMM", "%B");
result = result.replace("MMM", "%b");
result = result.replace("MM", "%m");
result = result.replace("M4", "%B");
result = result.replace("M3", "%b");
result = result.replace("EEEE", "%A");
result = result.replace("EEE", "%a");
result = result.replace("EE", "%a");
result = result.replace("E4", "%A");
result = result.replace("E3", "%a");
result = result.replace("DDD", "%j");
result = result.replace("DD", "%d");
result = result.replace("D3", "%j");
result = result.replace("HH24", "%H");
result = result.replace("HH", "%H");
result = result.replace("SSSSSS", "%f");
result = result.replace("SS", "%S");
result = result.replace("MI", "%M");
result
}
}