Skip to main content

polyglot_sql/dialects/
tableau.rs

1//! Tableau SQL Dialect
2//!
3//! Tableau-specific SQL dialect based on sqlglot patterns.
4//!
5//! Key characteristics:
6//! - Uses square brackets for identifiers: [x]
7//! - Single and double quotes for strings
8//! - COALESCE → IFNULL
9//! - COUNT(DISTINCT x) → COUNTD(x)
10//! - No join hints, table hints, or query hints
11//! - IF x THEN y ELSE z END syntax
12
13use super::{DialectImpl, DialectType};
14use crate::error::Result;
15use crate::expressions::{Expression, Function};
16use crate::generator::GeneratorConfig;
17use crate::tokens::TokenizerConfig;
18
19/// Tableau dialect
20pub struct TableauDialect;
21
22impl DialectImpl for TableauDialect {
23    fn dialect_type(&self) -> DialectType {
24        DialectType::Tableau
25    }
26
27    fn tokenizer_config(&self) -> TokenizerConfig {
28        let mut config = TokenizerConfig::default();
29        // Tableau uses square brackets for identifiers
30        config.identifiers.insert('[', ']');
31        config
32    }
33
34    fn generator_config(&self) -> GeneratorConfig {
35        use crate::generator::IdentifierQuoteStyle;
36        GeneratorConfig {
37            identifier_quote: '[',
38            identifier_quote_style: IdentifierQuoteStyle::BRACKET,
39            dialect: Some(DialectType::Tableau),
40            ..Default::default()
41        }
42    }
43
44    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
45        match expr {
46            // COALESCE → IFNULL in Tableau
47            Expression::Coalesce(f) => {
48                if f.expressions.len() == 2 {
49                    Ok(Expression::Function(Box::new(Function::new(
50                        "IFNULL".to_string(),
51                        f.expressions,
52                    ))))
53                } else {
54                    // For more than 2 args, keep as-is or nest IFNULL calls
55                    Ok(Expression::Coalesce(f))
56                }
57            }
58
59            // NVL → IFNULL in Tableau
60            Expression::Nvl(f) => Ok(Expression::Function(Box::new(Function::new(
61                "IFNULL".to_string(),
62                vec![f.this, f.expression],
63            )))),
64
65            // IfNull stays as IFNULL
66            Expression::IfNull(f) => Ok(Expression::Function(Box::new(Function::new(
67                "IFNULL".to_string(),
68                vec![f.this, f.expression],
69            )))),
70
71            // Generic function transformations
72            Expression::Function(f) => self.transform_function(*f),
73
74            // Aggregate function transformations (for COUNT DISTINCT)
75            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
76
77            // Pass through everything else
78            _ => Ok(expr),
79        }
80    }
81}
82
83impl TableauDialect {
84    fn transform_function(&self, f: Function) -> Result<Expression> {
85        let name_upper = f.name.to_uppercase();
86        match name_upper.as_str() {
87            // COALESCE → IFNULL
88            "COALESCE" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
89                "IFNULL".to_string(),
90                f.args,
91            )))),
92
93            // NVL → IFNULL
94            "NVL" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
95                "IFNULL".to_string(),
96                f.args,
97            )))),
98
99            // ISNULL → IFNULL
100            "ISNULL" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
101                "IFNULL".to_string(),
102                f.args,
103            )))),
104
105            // FIND is native to Tableau (similar to STRPOS/POSITION)
106            "STRPOS" | "POSITION" | "INSTR" if f.args.len() >= 2 => {
107                Ok(Expression::Function(Box::new(Function::new(
108                    "FIND".to_string(),
109                    f.args,
110                ))))
111            }
112
113            // CHARINDEX → FIND
114            "CHARINDEX" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
115                "FIND".to_string(),
116                f.args,
117            )))),
118
119            // Pass through everything else
120            _ => Ok(Expression::Function(Box::new(f))),
121        }
122    }
123
124    fn transform_aggregate_function(
125        &self,
126        f: Box<crate::expressions::AggregateFunction>,
127    ) -> Result<Expression> {
128        let name_upper = f.name.to_uppercase();
129        match name_upper.as_str() {
130            // COUNT with DISTINCT → COUNTD in Tableau
131            "COUNT" if f.distinct => {
132                Ok(Expression::Function(Box::new(Function::new(
133                    "COUNTD".to_string(),
134                    f.args,
135                ))))
136            }
137
138            // Pass through everything else
139            _ => Ok(Expression::AggregateFunction(f)),
140        }
141    }
142}