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