Skip to main content

polyglot_sql/dialects/
trino.rs

1//! Trino Dialect
2//!
3//! Trino-specific transformations based on sqlglot patterns.
4//! Trino is largely compatible with Presto but has some differences.
5
6use super::{DialectImpl, DialectType};
7use crate::error::Result;
8use crate::expressions::{
9    AggFunc, AggregateFunction, Case, Cast, DataType, Expression, Function, IntervalUnit,
10    IntervalUnitSpec, LikeOp, Literal, UnaryFunc, VarArgFunc,
11};
12#[cfg(feature = "generate")]
13use crate::generator::GeneratorConfig;
14use crate::tokens::TokenizerConfig;
15
16/// Trino dialect
17pub struct TrinoDialect;
18
19impl DialectImpl for TrinoDialect {
20    fn dialect_type(&self) -> DialectType {
21        DialectType::Trino
22    }
23
24    fn tokenizer_config(&self) -> TokenizerConfig {
25        let mut config = TokenizerConfig::default();
26        // Trino uses double quotes for identifiers
27        config.identifiers.insert('"', '"');
28        // Trino does NOT support nested comments
29        config.nested_comments = false;
30        // Trino does NOT support QUALIFY - it's a valid identifier
31        // (unlike Snowflake, BigQuery, DuckDB which have QUALIFY clause)
32        config.keywords.remove("QUALIFY");
33        config
34    }
35
36    #[cfg(feature = "generate")]
37
38    fn generator_config(&self) -> GeneratorConfig {
39        use crate::generator::IdentifierQuoteStyle;
40        GeneratorConfig {
41            identifier_quote: '"',
42            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
43            dialect: Some(DialectType::Trino),
44            limit_only_literals: true,
45            tz_to_with_time_zone: true,
46            ..Default::default()
47        }
48    }
49
50    #[cfg(feature = "transpile")]
51
52    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
53        match expr {
54            // IFNULL -> COALESCE in Trino
55            Expression::IfNull(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
56                original_name: None,
57                expressions: vec![f.this, f.expression],
58                inferred_type: None,
59            }))),
60
61            // NVL -> COALESCE in Trino
62            Expression::Nvl(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
63                original_name: None,
64                expressions: vec![f.this, f.expression],
65                inferred_type: None,
66            }))),
67
68            // Coalesce with original_name (e.g., IFNULL parsed as Coalesce) -> clear original_name
69            Expression::Coalesce(mut f) => {
70                f.original_name = None;
71                Ok(Expression::Coalesce(f))
72            }
73
74            // TryCast stays as TryCast (Trino supports TRY_CAST)
75            Expression::TryCast(c) => Ok(Expression::TryCast(c)),
76
77            // SafeCast -> TRY_CAST in Trino
78            Expression::SafeCast(c) => Ok(Expression::TryCast(c)),
79
80            // ILike -> LOWER() LIKE LOWER() (Trino doesn't support ILIKE)
81            Expression::ILike(op) => {
82                let lower_left = Expression::Lower(Box::new(UnaryFunc::new(op.left.clone())));
83                let lower_right = Expression::Lower(Box::new(UnaryFunc::new(op.right.clone())));
84                Ok(Expression::Like(Box::new(LikeOp {
85                    left: lower_left,
86                    right: lower_right,
87                    escape: op.escape,
88                    quantifier: op.quantifier.clone(),
89                    inferred_type: None,
90                })))
91            }
92
93            // CountIf is native in Trino (keep as-is)
94            Expression::CountIf(f) => Ok(Expression::CountIf(f)),
95
96            // EXPLODE -> UNNEST in Trino
97            Expression::Explode(f) => Ok(Expression::Unnest(Box::new(
98                crate::expressions::UnnestFunc {
99                    this: f.this,
100                    expressions: Vec::new(),
101                    with_ordinality: false,
102                    alias: None,
103                    offset_alias: None,
104                    inferred_type: None,
105                },
106            ))),
107
108            // ExplodeOuter -> UNNEST in Trino
109            Expression::ExplodeOuter(f) => Ok(Expression::Unnest(Box::new(
110                crate::expressions::UnnestFunc {
111                    this: f.this,
112                    expressions: Vec::new(),
113                    with_ordinality: false,
114                    alias: None,
115                    offset_alias: None,
116                    inferred_type: None,
117                },
118            ))),
119
120            // Generic function transformations
121            Expression::Function(f) => self.transform_function(*f),
122
123            // Generic aggregate function transformations
124            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
125
126            // Cast transformations
127            Expression::Cast(c) => self.transform_cast(*c),
128
129            // TRIM: Convert comma syntax TRIM(str, chars) to SQL standard TRIM(chars FROM str)
130            // Trino requires SQL standard syntax for TRIM with characters
131            Expression::Trim(mut f) => {
132                if !f.sql_standard_syntax && f.characters.is_some() {
133                    // Convert from TRIM(str, chars) to TRIM(chars FROM str)
134                    f.sql_standard_syntax = true;
135                }
136                Ok(Expression::Trim(f))
137            }
138
139            // LISTAGG: Add default separator ',' if none is specified (Trino style)
140            Expression::ListAgg(mut f) => {
141                if f.separator.is_none() {
142                    f.separator = Some(Expression::Literal(Box::new(Literal::String(
143                        ",".to_string(),
144                    ))));
145                }
146                Ok(Expression::ListAgg(f))
147            }
148
149            // Interval: Split compound string intervals like INTERVAL '1 day' into INTERVAL '1' DAY
150            Expression::Interval(mut interval) => {
151                if interval.unit.is_none() {
152                    if let Some(Expression::Literal(ref lit)) = interval.this {
153                        if let Literal::String(ref s) = lit.as_ref() {
154                            if let Some((value, unit)) = Self::parse_compound_interval(s) {
155                                interval.this =
156                                    Some(Expression::Literal(Box::new(Literal::String(value))));
157                                interval.unit = Some(unit);
158                            }
159                        }
160                    }
161                }
162                Ok(Expression::Interval(interval))
163            }
164
165            // Pass through everything else
166            _ => Ok(expr),
167        }
168    }
169}
170
171#[cfg(feature = "transpile")]
172impl TrinoDialect {
173    /// Parse a compound interval string like "1 day" into (value, unit_spec).
174    /// Returns None if the string doesn't match a known pattern.
175    fn parse_compound_interval(s: &str) -> Option<(String, IntervalUnitSpec)> {
176        let s = s.trim();
177        let parts: Vec<&str> = s.split_whitespace().collect();
178        if parts.len() != 2 {
179            return None;
180        }
181        let value = parts[0].to_string();
182        let unit = match parts[1].to_uppercase().as_str() {
183            "YEAR" | "YEARS" => IntervalUnit::Year,
184            "MONTH" | "MONTHS" => IntervalUnit::Month,
185            "DAY" | "DAYS" => IntervalUnit::Day,
186            "HOUR" | "HOURS" => IntervalUnit::Hour,
187            "MINUTE" | "MINUTES" => IntervalUnit::Minute,
188            "SECOND" | "SECONDS" => IntervalUnit::Second,
189            "MILLISECOND" | "MILLISECONDS" => IntervalUnit::Millisecond,
190            "MICROSECOND" | "MICROSECONDS" => IntervalUnit::Microsecond,
191            _ => return None,
192        };
193        Some((
194            value,
195            IntervalUnitSpec::Simple {
196                unit,
197                use_plural: false,
198            },
199        ))
200    }
201
202    fn transform_function(&self, f: Function) -> Result<Expression> {
203        let name_upper = f.name.to_uppercase();
204        match name_upper.as_str() {
205            // IFNULL -> COALESCE
206            "IFNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
207                original_name: None,
208                expressions: f.args,
209                inferred_type: None,
210            }))),
211
212            // NVL -> COALESCE
213            "NVL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
214                original_name: None,
215                expressions: f.args,
216                inferred_type: None,
217            }))),
218
219            // ISNULL -> COALESCE
220            "ISNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
221                original_name: None,
222                expressions: f.args,
223                inferred_type: None,
224            }))),
225
226            // GETDATE -> CURRENT_TIMESTAMP
227            "GETDATE" => Ok(Expression::CurrentTimestamp(
228                crate::expressions::CurrentTimestamp {
229                    precision: None,
230                    sysdate: false,
231                },
232            )),
233
234            // NOW -> CURRENT_TIMESTAMP
235            "NOW" => Ok(Expression::CurrentTimestamp(
236                crate::expressions::CurrentTimestamp {
237                    precision: None,
238                    sysdate: false,
239                },
240            )),
241
242            // RAND -> RANDOM in Trino
243            "RAND" => Ok(Expression::Function(Box::new(Function::new(
244                "RANDOM".to_string(),
245                vec![],
246            )))),
247
248            // GROUP_CONCAT -> LISTAGG in Trino (Trino supports LISTAGG)
249            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
250                Function::new("LISTAGG".to_string(), f.args),
251            ))),
252
253            // STRING_AGG -> LISTAGG in Trino
254            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
255                Function::new("LISTAGG".to_string(), f.args),
256            ))),
257
258            // LISTAGG is native in Trino
259            "LISTAGG" => Ok(Expression::Function(Box::new(f))),
260
261            // SUBSTR -> SUBSTRING
262            "SUBSTR" => Ok(Expression::Function(Box::new(Function::new(
263                "SUBSTRING".to_string(),
264                f.args,
265            )))),
266
267            // LEN -> LENGTH
268            "LEN" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc::new(
269                f.args.into_iter().next().unwrap(),
270            )))),
271
272            // CHARINDEX -> STRPOS in Trino (with swapped args)
273            "CHARINDEX" if f.args.len() >= 2 => {
274                let mut args = f.args;
275                let substring = args.remove(0);
276                let string = args.remove(0);
277                Ok(Expression::Function(Box::new(Function::new(
278                    "STRPOS".to_string(),
279                    vec![string, substring],
280                ))))
281            }
282
283            // INSTR -> STRPOS
284            "INSTR" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
285                "STRPOS".to_string(),
286                f.args,
287            )))),
288
289            // LOCATE -> STRPOS in Trino (with swapped args)
290            "LOCATE" if f.args.len() >= 2 => {
291                let mut args = f.args;
292                let substring = args.remove(0);
293                let string = args.remove(0);
294                Ok(Expression::Function(Box::new(Function::new(
295                    "STRPOS".to_string(),
296                    vec![string, substring],
297                ))))
298            }
299
300            // ARRAY_LENGTH -> CARDINALITY in Trino
301            "ARRAY_LENGTH" if f.args.len() == 1 => Ok(Expression::Function(Box::new(
302                Function::new("CARDINALITY".to_string(), f.args),
303            ))),
304
305            // SIZE -> CARDINALITY in Trino
306            "SIZE" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
307                "CARDINALITY".to_string(),
308                f.args,
309            )))),
310
311            // ARRAY_CONTAINS -> CONTAINS in Trino
312            "ARRAY_CONTAINS" if f.args.len() == 2 => Ok(Expression::Function(Box::new(
313                Function::new("CONTAINS".to_string(), f.args),
314            ))),
315
316            // TO_DATE -> CAST to DATE or DATE_PARSE
317            "TO_DATE" if !f.args.is_empty() => {
318                if f.args.len() == 1 {
319                    Ok(Expression::Cast(Box::new(Cast {
320                        this: f.args.into_iter().next().unwrap(),
321                        to: DataType::Date,
322                        trailing_comments: Vec::new(),
323                        double_colon_syntax: false,
324                        format: None,
325                        default: None,
326                        inferred_type: None,
327                    })))
328                } else {
329                    Ok(Expression::Function(Box::new(Function::new(
330                        "DATE_PARSE".to_string(),
331                        f.args,
332                    ))))
333                }
334            }
335
336            // TO_TIMESTAMP -> CAST or DATE_PARSE
337            "TO_TIMESTAMP" if !f.args.is_empty() => {
338                if f.args.len() == 1 {
339                    Ok(Expression::Cast(Box::new(Cast {
340                        this: f.args.into_iter().next().unwrap(),
341                        to: DataType::Timestamp {
342                            precision: None,
343                            timezone: false,
344                        },
345                        trailing_comments: Vec::new(),
346                        double_colon_syntax: false,
347                        format: None,
348                        default: None,
349                        inferred_type: None,
350                    })))
351                } else {
352                    Ok(Expression::Function(Box::new(Function::new(
353                        "DATE_PARSE".to_string(),
354                        f.args,
355                    ))))
356                }
357            }
358
359            // strftime -> DATE_FORMAT in Trino
360            "STRFTIME" if f.args.len() >= 2 => {
361                let mut args = f.args;
362                let format = args.remove(0);
363                let date = args.remove(0);
364                Ok(Expression::Function(Box::new(Function::new(
365                    "DATE_FORMAT".to_string(),
366                    vec![date, format],
367                ))))
368            }
369
370            // TO_CHAR -> DATE_FORMAT in Trino
371            "TO_CHAR" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
372                "DATE_FORMAT".to_string(),
373                f.args,
374            )))),
375
376            // LEVENSHTEIN -> LEVENSHTEIN_DISTANCE in Trino
377            "LEVENSHTEIN" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
378                Function::new("LEVENSHTEIN_DISTANCE".to_string(), f.args),
379            ))),
380
381            // GET_JSON_OBJECT -> JSON_EXTRACT_SCALAR in Trino
382            "GET_JSON_OBJECT" if f.args.len() == 2 => Ok(Expression::Function(Box::new(
383                Function::new("JSON_EXTRACT_SCALAR".to_string(), f.args),
384            ))),
385
386            // COLLECT_LIST -> ARRAY_AGG
387            "COLLECT_LIST" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
388                Function::new("ARRAY_AGG".to_string(), f.args),
389            ))),
390
391            // COLLECT_SET -> ARRAY_DISTINCT(ARRAY_AGG())
392            "COLLECT_SET" if !f.args.is_empty() => {
393                let array_agg =
394                    Expression::Function(Box::new(Function::new("ARRAY_AGG".to_string(), f.args)));
395                Ok(Expression::Function(Box::new(Function::new(
396                    "ARRAY_DISTINCT".to_string(),
397                    vec![array_agg],
398                ))))
399            }
400
401            // RLIKE -> REGEXP_LIKE in Trino
402            "RLIKE" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
403                "REGEXP_LIKE".to_string(),
404                f.args,
405            )))),
406
407            // REGEXP -> REGEXP_LIKE in Trino
408            "REGEXP" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
409                "REGEXP_LIKE".to_string(),
410                f.args,
411            )))),
412
413            // ARRAY_SUM -> REDUCE in Trino (complex transformation)
414            // For simplicity, we'll use a different approach
415            "ARRAY_SUM" if f.args.len() == 1 => {
416                // This is a complex transformation in Presto/Trino
417                // ARRAY_SUM(arr) -> REDUCE(arr, 0, (s, x) -> s + x, s -> s)
418                // For now, pass through and let user handle it
419                Ok(Expression::Function(Box::new(f)))
420            }
421
422            // Pass through everything else
423            _ => Ok(Expression::Function(Box::new(f))),
424        }
425    }
426
427    fn transform_aggregate_function(
428        &self,
429        f: Box<crate::expressions::AggregateFunction>,
430    ) -> Result<Expression> {
431        let name_upper = f.name.to_uppercase();
432        match name_upper.as_str() {
433            // COUNT_IF -> SUM(CASE WHEN...)
434            "COUNT_IF" if !f.args.is_empty() => {
435                let condition = f.args.into_iter().next().unwrap();
436                let case_expr = Expression::Case(Box::new(Case {
437                    operand: None,
438                    whens: vec![(condition, Expression::number(1))],
439                    else_: Some(Expression::number(0)),
440                    comments: Vec::new(),
441                    inferred_type: None,
442                }));
443                Ok(Expression::Sum(Box::new(AggFunc {
444                    ignore_nulls: None,
445                    having_max: None,
446                    this: case_expr,
447                    distinct: f.distinct,
448                    filter: f.filter,
449                    order_by: Vec::new(),
450                    name: None,
451                    limit: None,
452                    inferred_type: None,
453                })))
454            }
455
456            // ANY_VALUE -> ARBITRARY in Trino
457            "ANY_VALUE" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
458                "ARBITRARY".to_string(),
459                f.args,
460            )))),
461
462            // GROUP_CONCAT -> LISTAGG in Trino
463            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
464                Function::new("LISTAGG".to_string(), f.args),
465            ))),
466
467            // STRING_AGG -> LISTAGG in Trino
468            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
469                Function::new("LISTAGG".to_string(), f.args),
470            ))),
471
472            // VAR -> VAR_POP in Trino
473            "VAR" if !f.args.is_empty() => {
474                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
475                    name: "VAR_POP".to_string(),
476                    args: f.args,
477                    distinct: f.distinct,
478                    filter: f.filter,
479                    order_by: Vec::new(),
480                    limit: None,
481                    ignore_nulls: None,
482                    inferred_type: None,
483                })))
484            }
485
486            // VARIANCE -> VAR_SAMP in Trino
487            "VARIANCE" if !f.args.is_empty() => {
488                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
489                    name: "VAR_SAMP".to_string(),
490                    args: f.args,
491                    distinct: f.distinct,
492                    filter: f.filter,
493                    order_by: Vec::new(),
494                    limit: None,
495                    ignore_nulls: None,
496                    inferred_type: None,
497                })))
498            }
499
500            // Pass through everything else
501            _ => Ok(Expression::AggregateFunction(f)),
502        }
503    }
504
505    fn transform_cast(&self, c: Cast) -> Result<Expression> {
506        // Trino type mappings are handled in the generator
507        Ok(Expression::Cast(Box::new(c)))
508    }
509}