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 supports_split(&self) -> bool {
111 true
112 }
113
114 fn supports_regexp_like(&self) -> bool {
120 true
121 }
122
123 fn supports_regexp_extract(&self) -> bool {
130 true
131 }
132
133 fn supports_ordered_string_agg(&self) -> bool {
141 true
142 }
143
144 fn render_nvl2(&self, expr: &str, not_null: &str, is_null: &str) -> String {
153 format!("CASE WHEN {expr} IS NOT NULL THEN {not_null} ELSE {is_null} END")
154 }
155
156 fn render_multi_if(&self, conditions: &[(&str, &str)], else_: &str) -> String {
158 use std::fmt::Write;
159 let mut sql = "CASE".to_string();
160 for (cond, then_expr) in conditions {
161 let _ = write!(sql, " WHEN {cond} THEN {then_expr}");
162 }
163 let _ = write!(sql, " ELSE {else_} END");
164 sql
165 }
166
167 fn render_power_operator(&self) -> Option<String> {
184 Some("^".to_string())
185 }
186
187 fn render_ilike_operator(&self) -> Option<String> {
189 Some("ILIKE".to_string())
190 }
191
192 fn render_not_ilike_operator(&self) -> Option<String> {
195 Some("NOT ILIKE".to_string())
196 }
197
198 fn render_concat_operator(&self) -> Option<String> {
201 Some("||".to_string())
202 }
203
204 #[deprecated(
206 since = "0.14.0",
207 note = "answer `render_bitwise_operator(BitwiseOperator::Xor)` instead, which can say \
208 the engine has none"
209 )]
210 fn render_bitwise_xor_operator(&self) -> String {
211 "^".to_string()
212 }
213
214 fn render_bitwise_operator(&self, operator: BitwiseOperator) -> Option<String> {
227 Some(match operator {
228 BitwiseOperator::And => "&".to_owned(),
229 BitwiseOperator::Or => "|".to_owned(),
230 #[allow(deprecated)]
231 BitwiseOperator::Xor => self.render_bitwise_xor_operator(),
232 BitwiseOperator::Not => "~".to_owned(),
233 BitwiseOperator::ShiftLeft => "<<".to_owned(),
234 BitwiseOperator::ShiftRight => ">>".to_owned(),
235 })
236 }
237
238 fn render_try_cast_str(&self, expr: &str, type_str: &str) -> String {
244 format!("CAST({expr} AS {type_str})")
245 }
246
247 fn render_convert(&self, expr: &str, type_str: &str, style: Option<i32>) -> String {
249 let _ = style;
250 format!("CAST({expr} AS {type_str})")
251 }
252
253 fn render_safe_cast(&self, expr: &str, type_str: &str) -> String {
255 format!("CAST({expr} AS {type_str})")
256 }
257
258 fn render_if_null(&self, expr: &str, replacement: &str) -> String {
264 format!("COALESCE({expr}, {replacement})")
265 }
266
267 fn render_named_param(&self, name: &str) -> String {
273 format!(":{name}")
274 }
275
276 fn render_positional_param(&self, pos: usize) -> String {
278 let _ = pos;
279 "?".to_string()
280 }
281
282 fn render_data_type_ref(&self, dt: &DataTypeRef) -> String {
292 match dt {
293 DataTypeRef::Int => "INT".to_string(),
294 DataTypeRef::BigInt => "BIGINT".to_string(),
295 DataTypeRef::SmallInt => "SMALLINT".to_string(),
296 DataTypeRef::TinyInt => "TINYINT".to_string(),
297 DataTypeRef::Float => "FLOAT".to_string(),
298 DataTypeRef::Double => "DOUBLE PRECISION".to_string(),
299 DataTypeRef::Real => "REAL".to_string(),
300 DataTypeRef::Decimal { precision, scale } => format!("DECIMAL({precision}, {scale})"),
301 DataTypeRef::Numeric { precision, scale } => format!("NUMERIC({precision}, {scale})"),
302 DataTypeRef::Varchar { length } => match length {
303 Some(len) => format!("VARCHAR({len})"),
304 None => "VARCHAR".to_string(),
305 },
306 DataTypeRef::Char { length } => format!("CHAR({length})"),
307 DataTypeRef::Text | DataTypeRef::Xml => "TEXT".to_string(),
308 DataTypeRef::NVarchar { length } => match length {
309 Some(len) => format!("NVARCHAR({len})"),
310 None => "NVARCHAR(MAX)".to_string(),
311 },
312 DataTypeRef::NChar { length } => format!("NCHAR({length})"),
313 DataTypeRef::Binary { length } => match length {
314 Some(len) => format!("BINARY({len})"),
315 None => "BINARY".to_string(),
316 },
317 DataTypeRef::VarBinary { length } => match length {
318 Some(len) => format!("VARBINARY({len})"),
319 None => "VARBINARY".to_string(),
320 },
321 DataTypeRef::Blob => "BLOB".to_string(),
322 DataTypeRef::Date => "DATE".to_string(),
323 DataTypeRef::Time { precision } => match precision {
324 Some(p) => format!("TIME({p})"),
325 None => "TIME".to_string(),
326 },
327 DataTypeRef::DateTime => "DATETIME".to_string(),
328 DataTypeRef::DateTime2 { precision } => match precision {
329 Some(p) => format!("DATETIME2({p})"),
330 None => "DATETIME2".to_string(),
331 },
332 DataTypeRef::Timestamp { precision } => match precision {
333 Some(p) => format!("TIMESTAMP({p})"),
334 None => "TIMESTAMP".to_string(),
335 },
336 DataTypeRef::TimestampTz { precision } => match precision {
337 Some(p) => format!("TIMESTAMP({p}) WITH TIME ZONE"),
338 None => "TIMESTAMP WITH TIME ZONE".to_string(),
339 },
340 DataTypeRef::Interval => "INTERVAL".to_string(),
341 DataTypeRef::Boolean => "BOOLEAN".to_string(),
342 DataTypeRef::Bit => "BIT".to_string(),
343 DataTypeRef::Json => "VARIANT".to_string(),
344 DataTypeRef::Jsonb | DataTypeRef::Variant => "JSON".to_string(),
345 DataTypeRef::Uuid => "UUID".to_string(),
346 DataTypeRef::Custom(name) => name.clone(),
347 }
348 }
349}
350
351#[must_use]
355pub fn render_data_type_ref_ansi(dt: &DataTypeRef) -> String {
356 match dt {
357 DataTypeRef::Int => "INT".to_string(),
358 DataTypeRef::BigInt => "BIGINT".to_string(),
359 DataTypeRef::SmallInt => "SMALLINT".to_string(),
360 DataTypeRef::TinyInt => "TINYINT".to_string(),
361 DataTypeRef::Float => "FLOAT".to_string(),
362 DataTypeRef::Double => "DOUBLE PRECISION".to_string(),
363 DataTypeRef::Real => "REAL".to_string(),
364 DataTypeRef::Decimal { precision, scale } => format!("DECIMAL({precision}, {scale})"),
365 DataTypeRef::Numeric { precision, scale } => format!("NUMERIC({precision}, {scale})"),
366 DataTypeRef::Varchar { length } => match length {
367 Some(len) => format!("VARCHAR({len})"),
368 None => "VARCHAR".to_string(),
369 },
370 DataTypeRef::Char { length } => format!("CHAR({length})"),
371 DataTypeRef::Text | DataTypeRef::Xml => "TEXT".to_string(),
372 DataTypeRef::NVarchar { length } => match length {
373 Some(len) => format!("NVARCHAR({len})"),
374 None => "NVARCHAR(MAX)".to_string(),
375 },
376 DataTypeRef::NChar { length } => format!("NCHAR({length})"),
377 DataTypeRef::Binary { length } => match length {
378 Some(len) => format!("BINARY({len})"),
379 None => "BINARY".to_string(),
380 },
381 DataTypeRef::VarBinary { length } => match length {
382 Some(len) => format!("VARBINARY({len})"),
383 None => "VARBINARY".to_string(),
384 },
385 DataTypeRef::Blob => "BLOB".to_string(),
386 DataTypeRef::Date => "DATE".to_string(),
387 DataTypeRef::Time { precision } => match precision {
388 Some(p) => format!("TIME({p})"),
389 None => "TIME".to_string(),
390 },
391 DataTypeRef::DateTime => "DATETIME".to_string(),
392 DataTypeRef::DateTime2 { precision } => match precision {
393 Some(p) => format!("DATETIME2({p})"),
394 None => "DATETIME2".to_string(),
395 },
396 DataTypeRef::Timestamp { precision } => match precision {
397 Some(p) => format!("TIMESTAMP({p})"),
398 None => "TIMESTAMP".to_string(),
399 },
400 DataTypeRef::TimestampTz { precision } => match precision {
401 Some(p) => format!("TIMESTAMP({p}) WITH TIME ZONE"),
402 None => "TIMESTAMP WITH TIME ZONE".to_string(),
403 },
404 DataTypeRef::Interval => "INTERVAL".to_string(),
405 DataTypeRef::Boolean => "BOOLEAN".to_string(),
406 DataTypeRef::Bit => "BIT".to_string(),
407 DataTypeRef::Json => "VARIANT".to_string(),
408 DataTypeRef::Jsonb | DataTypeRef::Variant => "JSON".to_string(),
409 DataTypeRef::Uuid => "UUID".to_string(),
410 DataTypeRef::Custom(name) => name.clone(),
411 }
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
417#[non_exhaustive]
418pub enum BitwiseOperator {
419 And,
421 Or,
423 Xor,
425 Not,
427 ShiftLeft,
429 ShiftRight,
431}
432
433impl BitwiseOperator {
434 pub const ALL: [Self; 6] =
436 [Self::And, Self::Or, Self::Xor, Self::Not, Self::ShiftLeft, Self::ShiftRight];
437
438 #[must_use]
441 pub const fn standard_spelling(self) -> &'static str {
442 match self {
443 Self::And => "&",
444 Self::Or => "|",
445 Self::Xor => "^",
446 Self::Not => "~",
447 Self::ShiftLeft => "<<",
448 Self::ShiftRight => ">>",
449 }
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use crate::DataType;
457
458 struct TestDialect;
459
460 impl SqlRenderer for TestDialect {
461 fn render_data_type(&self, _dt: &DataType) -> String {
462 "TEXT".to_string()
463 }
464
465 fn quote_identifier(&self, name: &str) -> String {
466 format!("\"{name}\"")
467 }
468
469 fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
470 match schema {
471 Some(s) => format!("\"{s}\".\"{name}\""),
472 None => format!("\"{name}\""),
473 }
474 }
475
476 fn render_current_timestamp(&self) -> String {
477 "CURRENT_TIMESTAMP".to_string()
478 }
479 }
480
481 impl FormulaRenderer for TestDialect {}
482
483 #[test]
484 fn formula_renderer_defaults_render_string_agg_with_order() {
485 let d = TestDialect;
486 assert_eq!(
487 d.render_string_agg("name", ",", false, Some("name ASC")),
488 "STRING_AGG(name, ',' ORDER BY name ASC)"
489 );
490 }
491
492 #[test]
493 fn formula_renderer_defaults_render_string_agg_with_distinct() {
494 let d = TestDialect;
495 assert_eq!(d.render_string_agg("name", ",", true, None), "STRING_AGG(DISTINCT name, ',')");
496 }
497
498 #[test]
499 fn formula_renderer_defaults_render_count_if() {
500 let d = TestDialect;
501 assert_eq!(d.render_count_if("x > 0"), "COUNT(CASE WHEN x > 0 THEN 1 END)");
502 }
503
504 #[test]
510 fn a_dialect_that_states_no_spelling_renders_the_guess_and_is_named_as_one() {
511 use crate::sid_spelling::UNKNOWN_PLATFORM;
512 let d = TestDialect;
513 let spelling = crate::SqlRenderer::sid_spelling(&d);
514 assert_eq!(spelling.platform, UNKNOWN_PLATFORM);
515 assert_eq!(spelling.hash, ("xxhash64(", ")"));
516 }
517}