1use crate::DataTypeRef;
8use crate::SqlRenderer;
9
10pub trait FormulaRenderer: SqlRenderer {
14 fn render_string_agg(
16 &self,
17 expr: &str,
18 sep: &str,
19 distinct: bool,
20 order: Option<&str>,
21 ) -> String {
22 let d = if distinct { "DISTINCT " } else { "" };
23 match order {
24 Some(o) => format!("STRING_AGG({d}{expr}, '{sep}' ORDER BY {o})"),
25 None => format!("STRING_AGG({d}{expr}, '{sep}')"),
26 }
27 }
28
29 fn render_concat_ws(&self, separator: &str, parts: &[&str]) -> String {
31 format!("CONCAT_WS('{separator}', {})", parts.join(", "))
32 }
33
34 fn render_first_value(&self, expr: &str, ignore_nulls: bool) -> String {
36 if ignore_nulls {
37 format!("FIRST_VALUE({expr}) IGNORE NULLS")
38 } else {
39 format!("FIRST_VALUE({expr})")
40 }
41 }
42
43 fn render_stddev(&self, expr: &str, population: bool) -> String {
45 let func = if population { "STDDEV_POP" } else { "STDDEV_SAMP" };
46 format!("{func}({expr})")
47 }
48
49 fn render_variance(&self, expr: &str, population: bool) -> String {
51 let func = if population { "VAR_POP" } else { "VAR_SAMP" };
52 format!("{func}({expr})")
53 }
54
55 fn render_percentile(&self, expr: &str, percentile: f64, continuous: bool) -> String {
57 let func = if continuous { "PERCENTILE_CONT" } else { "PERCENTILE_DISC" };
58 format!("{func}({percentile}) WITHIN GROUP (ORDER BY {expr})")
59 }
60
61 fn render_median(&self, expr: &str) -> String {
63 format!("PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY {expr})")
64 }
65
66 fn render_count_if(&self, condition: &str) -> String {
68 format!("COUNT(CASE WHEN {condition} THEN 1 END)")
69 }
70
71 fn render_sum_if(&self, expr: &str, condition: &str) -> String {
73 format!("SUM(CASE WHEN {condition} THEN {expr} END)")
74 }
75
76 fn render_avg_if(&self, expr: &str, condition: &str) -> String {
78 format!("AVG(CASE WHEN {condition} THEN {expr} END)")
79 }
80
81 fn render_split(&self, expr: &str, delimiter: &str) -> String {
83 format!("STRING_TO_ARRAY({expr}, '{delimiter}')")
84 }
85
86 fn render_regexp_like(&self, expr: &str, pattern: &str) -> String {
88 format!("REGEXP_LIKE({expr}, '{pattern}')")
89 }
90
91 fn render_regexp_replace(&self, expr: &str, pattern: &str, replacement: &str) -> String {
93 format!("REGEXP_REPLACE({expr}, '{pattern}', '{replacement}')")
94 }
95
96 fn render_regexp_extract(&self, expr: &str, pattern: &str, group: Option<usize>) -> String {
98 match group {
99 Some(g) => format!("REGEXP_SUBSTR({expr}, '{pattern}', 1, 1, 'e', {g})"),
100 None => format!("REGEXP_SUBSTR({expr}, '{pattern}')"),
101 }
102 }
103
104 fn render_nvl2(&self, expr: &str, not_null: &str, is_null: &str) -> String {
113 format!("CASE WHEN {expr} IS NOT NULL THEN {not_null} ELSE {is_null} END")
114 }
115
116 fn render_multi_if(&self, conditions: &[(&str, &str)], else_: &str) -> String {
118 use std::fmt::Write;
119 let mut sql = "CASE".to_string();
120 for (cond, then_expr) in conditions {
121 let _ = write!(sql, " WHEN {cond} THEN {then_expr}");
122 }
123 let _ = write!(sql, " ELSE {else_} END");
124 sql
125 }
126
127 fn render_power_operator(&self) -> Option<String> {
144 Some("^".to_string())
145 }
146
147 fn render_ilike_operator(&self) -> Option<String> {
149 Some("ILIKE".to_string())
150 }
151
152 fn render_not_ilike_operator(&self) -> Option<String> {
155 Some("NOT ILIKE".to_string())
156 }
157
158 fn render_concat_operator(&self) -> Option<String> {
161 Some("||".to_string())
162 }
163
164 #[deprecated(
166 since = "0.14.0",
167 note = "answer `render_bitwise_operator(BitwiseOperator::Xor)` instead, which can say \
168 the engine has none"
169 )]
170 fn render_bitwise_xor_operator(&self) -> String {
171 "^".to_string()
172 }
173
174 fn render_bitwise_operator(&self, operator: BitwiseOperator) -> Option<String> {
187 Some(match operator {
188 BitwiseOperator::And => "&".to_owned(),
189 BitwiseOperator::Or => "|".to_owned(),
190 #[allow(deprecated)]
191 BitwiseOperator::Xor => self.render_bitwise_xor_operator(),
192 BitwiseOperator::Not => "~".to_owned(),
193 BitwiseOperator::ShiftLeft => "<<".to_owned(),
194 BitwiseOperator::ShiftRight => ">>".to_owned(),
195 })
196 }
197
198 fn render_try_cast_str(&self, expr: &str, type_str: &str) -> String {
204 format!("CAST({expr} AS {type_str})")
205 }
206
207 fn render_convert(&self, expr: &str, type_str: &str, style: Option<i32>) -> String {
209 let _ = style;
210 format!("CAST({expr} AS {type_str})")
211 }
212
213 fn render_safe_cast(&self, expr: &str, type_str: &str) -> String {
215 format!("CAST({expr} AS {type_str})")
216 }
217
218 fn render_if_null(&self, expr: &str, replacement: &str) -> String {
224 format!("COALESCE({expr}, {replacement})")
225 }
226
227 fn render_named_param(&self, name: &str) -> String {
233 format!(":{name}")
234 }
235
236 fn render_positional_param(&self, pos: usize) -> String {
238 let _ = pos;
239 "?".to_string()
240 }
241
242 fn render_data_type_ref(&self, dt: &DataTypeRef) -> String {
252 match dt {
253 DataTypeRef::Int => "INT".to_string(),
254 DataTypeRef::BigInt => "BIGINT".to_string(),
255 DataTypeRef::SmallInt => "SMALLINT".to_string(),
256 DataTypeRef::TinyInt => "TINYINT".to_string(),
257 DataTypeRef::Float => "FLOAT".to_string(),
258 DataTypeRef::Double => "DOUBLE PRECISION".to_string(),
259 DataTypeRef::Real => "REAL".to_string(),
260 DataTypeRef::Decimal { precision, scale } => format!("DECIMAL({precision}, {scale})"),
261 DataTypeRef::Numeric { precision, scale } => format!("NUMERIC({precision}, {scale})"),
262 DataTypeRef::Varchar { length } => match length {
263 Some(len) => format!("VARCHAR({len})"),
264 None => "VARCHAR".to_string(),
265 },
266 DataTypeRef::Char { length } => format!("CHAR({length})"),
267 DataTypeRef::Text | DataTypeRef::Xml => "TEXT".to_string(),
268 DataTypeRef::NVarchar { length } => match length {
269 Some(len) => format!("NVARCHAR({len})"),
270 None => "NVARCHAR(MAX)".to_string(),
271 },
272 DataTypeRef::NChar { length } => format!("NCHAR({length})"),
273 DataTypeRef::Binary { length } => match length {
274 Some(len) => format!("BINARY({len})"),
275 None => "BINARY".to_string(),
276 },
277 DataTypeRef::VarBinary { length } => match length {
278 Some(len) => format!("VARBINARY({len})"),
279 None => "VARBINARY".to_string(),
280 },
281 DataTypeRef::Blob => "BLOB".to_string(),
282 DataTypeRef::Date => "DATE".to_string(),
283 DataTypeRef::Time { precision } => match precision {
284 Some(p) => format!("TIME({p})"),
285 None => "TIME".to_string(),
286 },
287 DataTypeRef::DateTime => "DATETIME".to_string(),
288 DataTypeRef::DateTime2 { precision } => match precision {
289 Some(p) => format!("DATETIME2({p})"),
290 None => "DATETIME2".to_string(),
291 },
292 DataTypeRef::Timestamp { precision } => match precision {
293 Some(p) => format!("TIMESTAMP({p})"),
294 None => "TIMESTAMP".to_string(),
295 },
296 DataTypeRef::TimestampTz { precision } => match precision {
297 Some(p) => format!("TIMESTAMP({p}) WITH TIME ZONE"),
298 None => "TIMESTAMP WITH TIME ZONE".to_string(),
299 },
300 DataTypeRef::Interval => "INTERVAL".to_string(),
301 DataTypeRef::Boolean => "BOOLEAN".to_string(),
302 DataTypeRef::Bit => "BIT".to_string(),
303 DataTypeRef::Json => "VARIANT".to_string(),
304 DataTypeRef::Jsonb | DataTypeRef::Variant => "JSON".to_string(),
305 DataTypeRef::Uuid => "UUID".to_string(),
306 DataTypeRef::Custom(name) => name.clone(),
307 }
308 }
309}
310
311#[must_use]
315pub fn render_data_type_ref_ansi(dt: &DataTypeRef) -> String {
316 match dt {
317 DataTypeRef::Int => "INT".to_string(),
318 DataTypeRef::BigInt => "BIGINT".to_string(),
319 DataTypeRef::SmallInt => "SMALLINT".to_string(),
320 DataTypeRef::TinyInt => "TINYINT".to_string(),
321 DataTypeRef::Float => "FLOAT".to_string(),
322 DataTypeRef::Double => "DOUBLE PRECISION".to_string(),
323 DataTypeRef::Real => "REAL".to_string(),
324 DataTypeRef::Decimal { precision, scale } => format!("DECIMAL({precision}, {scale})"),
325 DataTypeRef::Numeric { precision, scale } => format!("NUMERIC({precision}, {scale})"),
326 DataTypeRef::Varchar { length } => match length {
327 Some(len) => format!("VARCHAR({len})"),
328 None => "VARCHAR".to_string(),
329 },
330 DataTypeRef::Char { length } => format!("CHAR({length})"),
331 DataTypeRef::Text | DataTypeRef::Xml => "TEXT".to_string(),
332 DataTypeRef::NVarchar { length } => match length {
333 Some(len) => format!("NVARCHAR({len})"),
334 None => "NVARCHAR(MAX)".to_string(),
335 },
336 DataTypeRef::NChar { length } => format!("NCHAR({length})"),
337 DataTypeRef::Binary { length } => match length {
338 Some(len) => format!("BINARY({len})"),
339 None => "BINARY".to_string(),
340 },
341 DataTypeRef::VarBinary { length } => match length {
342 Some(len) => format!("VARBINARY({len})"),
343 None => "VARBINARY".to_string(),
344 },
345 DataTypeRef::Blob => "BLOB".to_string(),
346 DataTypeRef::Date => "DATE".to_string(),
347 DataTypeRef::Time { precision } => match precision {
348 Some(p) => format!("TIME({p})"),
349 None => "TIME".to_string(),
350 },
351 DataTypeRef::DateTime => "DATETIME".to_string(),
352 DataTypeRef::DateTime2 { precision } => match precision {
353 Some(p) => format!("DATETIME2({p})"),
354 None => "DATETIME2".to_string(),
355 },
356 DataTypeRef::Timestamp { precision } => match precision {
357 Some(p) => format!("TIMESTAMP({p})"),
358 None => "TIMESTAMP".to_string(),
359 },
360 DataTypeRef::TimestampTz { precision } => match precision {
361 Some(p) => format!("TIMESTAMP({p}) WITH TIME ZONE"),
362 None => "TIMESTAMP WITH TIME ZONE".to_string(),
363 },
364 DataTypeRef::Interval => "INTERVAL".to_string(),
365 DataTypeRef::Boolean => "BOOLEAN".to_string(),
366 DataTypeRef::Bit => "BIT".to_string(),
367 DataTypeRef::Json => "VARIANT".to_string(),
368 DataTypeRef::Jsonb | DataTypeRef::Variant => "JSON".to_string(),
369 DataTypeRef::Uuid => "UUID".to_string(),
370 DataTypeRef::Custom(name) => name.clone(),
371 }
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
377#[non_exhaustive]
378pub enum BitwiseOperator {
379 And,
381 Or,
383 Xor,
385 Not,
387 ShiftLeft,
389 ShiftRight,
391}
392
393impl BitwiseOperator {
394 pub const ALL: [Self; 6] =
396 [Self::And, Self::Or, Self::Xor, Self::Not, Self::ShiftLeft, Self::ShiftRight];
397
398 #[must_use]
401 pub const fn standard_spelling(self) -> &'static str {
402 match self {
403 Self::And => "&",
404 Self::Or => "|",
405 Self::Xor => "^",
406 Self::Not => "~",
407 Self::ShiftLeft => "<<",
408 Self::ShiftRight => ">>",
409 }
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use crate::DataType;
417
418 struct TestDialect;
419
420 impl SqlRenderer for TestDialect {
421 fn render_data_type(&self, _dt: &DataType) -> String {
422 "TEXT".to_string()
423 }
424
425 fn quote_identifier(&self, name: &str) -> String {
426 format!("\"{name}\"")
427 }
428
429 fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
430 match schema {
431 Some(s) => format!("\"{s}\".\"{name}\""),
432 None => format!("\"{name}\""),
433 }
434 }
435
436 fn render_current_timestamp(&self) -> String {
437 "CURRENT_TIMESTAMP".to_string()
438 }
439 }
440
441 impl FormulaRenderer for TestDialect {}
442
443 #[test]
444 fn formula_renderer_defaults_render_string_agg_with_order() {
445 let d = TestDialect;
446 assert_eq!(
447 d.render_string_agg("name", ",", false, Some("name ASC")),
448 "STRING_AGG(name, ',' ORDER BY name ASC)"
449 );
450 }
451
452 #[test]
453 fn formula_renderer_defaults_render_string_agg_with_distinct() {
454 let d = TestDialect;
455 assert_eq!(d.render_string_agg("name", ",", true, None), "STRING_AGG(DISTINCT name, ',')");
456 }
457
458 #[test]
459 fn formula_renderer_defaults_render_count_if() {
460 let d = TestDialect;
461 assert_eq!(d.render_count_if("x > 0"), "COUNT(CASE WHEN x > 0 THEN 1 END)");
462 }
463
464 #[test]
470 fn a_dialect_that_states_no_spelling_renders_the_guess_and_is_named_as_one() {
471 use crate::sid_spelling::UNKNOWN_PLATFORM;
472 let d = TestDialect;
473 let spelling = crate::SqlRenderer::sid_spelling(&d);
474 assert_eq!(spelling.platform, UNKNOWN_PLATFORM);
475 assert_eq!(spelling.hash, ("xxhash64(", ")"));
476 }
477}