Skip to main content

polyglot_sql/dialects/
tsql.rs

1//! T-SQL (SQL Server) Dialect
2//!
3//! SQL Server-specific transformations based on sqlglot patterns.
4//! Key differences:
5//! - TOP instead of LIMIT
6//! - ISNULL instead of COALESCE (though COALESCE also works)
7//! - Square brackets for identifiers
8//! - + for string concatenation
9//! - CONVERT vs CAST
10//! - CROSS APPLY / OUTER APPLY for lateral joins
11//! - Different date functions (GETDATE, DATEADD, DATEDIFF, DATENAME)
12
13use super::{DialectImpl, DialectType};
14use crate::error::Result;
15use crate::expressions::{
16    Alias, BinaryOp, Cast, Column, Cte, DataType, Exists, Expression, Function, Identifier, In,
17    Join, JoinKind, LikeOp, Literal, Null, Over, QuantifiedOp, Select, StringAggFunc, Subquery,
18    UnaryFunc, Where,
19};
20#[cfg(feature = "generate")]
21use crate::generator::GeneratorConfig;
22use crate::tokens::TokenizerConfig;
23use std::collections::HashMap;
24
25/// T-SQL (SQL Server) dialect
26pub struct TSQLDialect;
27
28impl DialectImpl for TSQLDialect {
29    fn dialect_type(&self) -> DialectType {
30        DialectType::TSQL
31    }
32
33    fn tokenizer_config(&self) -> TokenizerConfig {
34        let mut config = TokenizerConfig::default();
35        // SQL Server uses square brackets for identifiers
36        config.identifiers.insert('[', ']');
37        // SQL Server also supports double quotes (when QUOTED_IDENTIFIER is ON)
38        config.identifiers.insert('"', '"');
39        // SQL Server uses 0x-prefixed binary/varbinary hex literals.
40        config.hex_number_strings = true;
41        config
42    }
43
44    #[cfg(feature = "generate")]
45
46    fn generator_config(&self) -> GeneratorConfig {
47        use crate::generator::IdentifierQuoteStyle;
48        GeneratorConfig {
49            // Use square brackets by default for SQL Server
50            identifier_quote: '[',
51            identifier_quote_style: IdentifierQuoteStyle::BRACKET,
52            dialect: Some(DialectType::TSQL),
53            // T-SQL specific settings from Python sqlglot
54            // SQL Server uses TOP/FETCH instead of LIMIT
55            limit_fetch_style: crate::generator::LimitFetchStyle::FetchFirst,
56            // NULLS FIRST/LAST not supported in SQL Server
57            null_ordering_supported: false,
58            // SQL Server does not support SQL:2003 aggregate FILTER clauses.
59            aggregate_filter_supported: false,
60            // SQL Server supports SELECT INTO
61            supports_select_into: true,
62            // ALTER TABLE doesn't require COLUMN keyword
63            alter_table_include_column_keyword: false,
64            // Computed columns don't need type declaration
65            computed_column_with_type: false,
66            // RECURSIVE keyword not required in CTEs
67            cte_recursive_keyword_required: false,
68            // Ensure boolean expressions
69            ensure_bools: true,
70            // CONCAT requires at least 2 args
71            supports_single_arg_concat: false,
72            // TABLESAMPLE REPEATABLE
73            tablesample_seed_keyword: "REPEATABLE",
74            // JSON path without brackets
75            json_path_bracketed_key_supported: false,
76            // No TO_NUMBER function
77            supports_to_number: false,
78            // SET operation modifiers not supported
79            set_op_modifiers: false,
80            // COPY params need equals sign
81            copy_params_eq_required: true,
82            // No ALL clause for EXCEPT/INTERSECT
83            except_intersect_support_all_clause: false,
84            // ALTER SET is wrapped
85            alter_set_wrapped: true,
86            // T-SQL supports TRY_CAST
87            try_supported: true,
88            // No NVL2 support
89            nvl2_supported: false,
90            // TSQL uses = instead of DEFAULT for parameter defaults
91            parameter_default_equals: true,
92            // No window EXCLUDE support
93            supports_window_exclude: false,
94            // No DISTINCT with multiple args
95            multi_arg_distinct: false,
96            // TSQL doesn't support FOR UPDATE/SHARE
97            locking_reads_supported: false,
98            ..Default::default()
99        }
100    }
101
102    #[cfg(feature = "transpile")]
103
104    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
105        // Transform column data types in DDL (transform_recursive skips them by design).
106        if let Expression::CreateTable(mut ct) = expr {
107            for col in &mut ct.columns {
108                if let Ok(Expression::DataType(new_dt)) =
109                    self.transform_data_type(col.data_type.clone())
110                {
111                    col.data_type = new_dt;
112                }
113            }
114            return Ok(Expression::CreateTable(ct));
115        }
116
117        match expr {
118            // ===== SELECT a = 1 → SELECT 1 AS a =====
119            // In T-SQL, `SELECT a = expr` is equivalent to `SELECT expr AS a`
120            // BUT: `SELECT @a = expr` is a variable assignment, not an alias!
121            // Python sqlglot handles this at parser level via _parse_projections()
122            Expression::Select(mut select) => {
123                select.expressions = select
124                    .expressions
125                    .into_iter()
126                    .map(|e| {
127                        match e {
128                            Expression::Eq(op) => {
129                                // Check if left side is an identifier (column name)
130                                // Don't transform if it's a variable (starts with @)
131                                match &op.left {
132                                    Expression::Column(col)
133                                        if col.table.is_none()
134                                            && !col.name.name.starts_with('@') =>
135                                    {
136                                        Expression::Alias(Box::new(Alias {
137                                            this: op.right,
138                                            alias: col.name.clone(),
139                                            column_aliases: Vec::new(),
140                                            alias_explicit_as: false,
141                                            alias_keyword: None,
142                                            pre_alias_comments: Vec::new(),
143                                            trailing_comments: Vec::new(),
144                                            inferred_type: None,
145                                        }))
146                                    }
147                                    Expression::Identifier(ident)
148                                        if !ident.name.starts_with('@') =>
149                                    {
150                                        Expression::Alias(Box::new(Alias {
151                                            this: op.right,
152                                            alias: ident.clone(),
153                                            column_aliases: Vec::new(),
154                                            alias_explicit_as: false,
155                                            alias_keyword: None,
156                                            pre_alias_comments: Vec::new(),
157                                            trailing_comments: Vec::new(),
158                                            inferred_type: None,
159                                        }))
160                                    }
161                                    _ => Expression::Eq(op),
162                                }
163                            }
164                            other => other,
165                        }
166                    })
167                    .collect();
168
169                Self::normalize_frame_incompatible_window_functions(&mut select);
170
171                let outer_qualifier = Self::single_select_source_qualifier(&select);
172                if let Some(ref mut where_clause) = select.where_clause {
173                    where_clause.this = Self::rewrite_tuple_in_subquery_predicates(
174                        std::mem::replace(&mut where_clause.this, Expression::Null(Null)),
175                        outer_qualifier.as_ref(),
176                        false,
177                    );
178                }
179
180                // Transform CTEs in the WITH clause to add auto-aliases
181                if let Some(ref mut with) = select.with {
182                    with.ctes = with
183                        .ctes
184                        .drain(..)
185                        .map(|cte| self.transform_cte_inner(cte))
186                        .collect();
187                }
188
189                Ok(Expression::Select(select))
190            }
191
192            // ===== Data Type Mappings =====
193            Expression::DataType(dt) => self.transform_data_type(dt),
194
195            // ===== Boolean IS TRUE/FALSE -> = 1/0 for TSQL =====
196            // TSQL doesn't have IS TRUE/IS FALSE syntax
197            Expression::IsTrue(it) => {
198                let one = Expression::Literal(Box::new(crate::expressions::Literal::Number(
199                    "1".to_string(),
200                )));
201                if it.not {
202                    // a IS NOT TRUE -> NOT a = 1
203                    Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
204                        this: Expression::Eq(Box::new(crate::expressions::BinaryOp {
205                            left: it.this,
206                            right: one,
207                            left_comments: vec![],
208                            operator_comments: vec![],
209                            trailing_comments: vec![],
210                            inferred_type: None,
211                        })),
212                        inferred_type: None,
213                    })))
214                } else {
215                    // a IS TRUE -> a = 1
216                    Ok(Expression::Eq(Box::new(crate::expressions::BinaryOp {
217                        left: it.this,
218                        right: one,
219                        left_comments: vec![],
220                        operator_comments: vec![],
221                        trailing_comments: vec![],
222                        inferred_type: None,
223                    })))
224                }
225            }
226            Expression::IsFalse(it) => {
227                let zero = Expression::Literal(Box::new(crate::expressions::Literal::Number(
228                    "0".to_string(),
229                )));
230                if it.not {
231                    // a IS NOT FALSE -> NOT a = 0
232                    Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
233                        this: Expression::Eq(Box::new(crate::expressions::BinaryOp {
234                            left: it.this,
235                            right: zero,
236                            left_comments: vec![],
237                            operator_comments: vec![],
238                            trailing_comments: vec![],
239                            inferred_type: None,
240                        })),
241                        inferred_type: None,
242                    })))
243                } else {
244                    // a IS FALSE -> a = 0
245                    Ok(Expression::Eq(Box::new(crate::expressions::BinaryOp {
246                        left: it.this,
247                        right: zero,
248                        left_comments: vec![],
249                        operator_comments: vec![],
250                        trailing_comments: vec![],
251                        inferred_type: None,
252                    })))
253                }
254            }
255
256            // Note: CASE WHEN boolean conditions are handled in ensure_bools preprocessing
257
258            // NOT IN -> NOT ... IN for TSQL (TSQL prefers NOT prefix)
259            Expression::In(mut in_expr) if in_expr.not => {
260                in_expr.not = false;
261                Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
262                    this: Expression::In(in_expr),
263                    inferred_type: None,
264                })))
265            }
266
267            // COALESCE with 2 args -> ISNULL in SQL Server (optimization)
268            // Note: COALESCE works in SQL Server, ISNULL is just more idiomatic
269            Expression::Coalesce(f) if f.expressions.len() == 2 => Ok(Expression::Function(
270                Box::new(Function::new("ISNULL".to_string(), f.expressions)),
271            )),
272
273            // NVL -> ISNULL in SQL Server
274            Expression::Nvl(f) => Ok(Expression::Function(Box::new(Function::new(
275                "ISNULL".to_string(),
276                vec![f.this, f.expression],
277            )))),
278
279            // GROUP_CONCAT -> STRING_AGG in SQL Server (SQL Server 2017+)
280            Expression::GroupConcat(f) => Ok(Expression::StringAgg(Box::new(StringAggFunc {
281                this: f.this,
282                separator: f.separator,
283                order_by: f.order_by,
284                distinct: f.distinct,
285                filter: f.filter,
286                limit: None,
287                inferred_type: None,
288            }))),
289
290            // LISTAGG -> STRING_AGG in SQL Server (SQL Server 2017+)
291            Expression::ListAgg(f) => Ok(Expression::StringAgg(Box::new(StringAggFunc {
292                this: f.this,
293                separator: f.separator,
294                order_by: f.order_by,
295                distinct: f.distinct,
296                filter: f.filter,
297                limit: None,
298                inferred_type: None,
299            }))),
300
301            // T-SQL/Fabric do not have boolean aggregates. Preserve PostgreSQL NULL
302            // semantics by returning NULL for unknown input predicates.
303            Expression::LogicalAnd(f) => Self::transform_logical_aggregate(f.this, f.filter, "MIN"),
304            Expression::LogicalOr(f) => Self::transform_logical_aggregate(f.this, f.filter, "MAX"),
305
306            // TryCast -> TRY_CAST (SQL Server supports TRY_CAST starting from 2012)
307            Expression::TryCast(c) => Ok(Expression::TryCast(c)),
308
309            // SafeCast -> TRY_CAST
310            Expression::SafeCast(c) => Ok(Expression::TryCast(c)),
311
312            // ILIKE -> LOWER() LIKE LOWER() in SQL Server (no ILIKE support)
313            Expression::ILike(op) => {
314                // SQL Server is case-insensitive by default based on collation
315                // But for explicit case-insensitive matching, use LOWER
316                let lower_left = Expression::Lower(Box::new(UnaryFunc::new(op.left)));
317                let lower_right = Expression::Lower(Box::new(UnaryFunc::new(op.right)));
318                Ok(Expression::Like(Box::new(LikeOp {
319                    left: lower_left,
320                    right: lower_right,
321                    escape: op.escape,
322                    quantifier: op.quantifier,
323                    inferred_type: None,
324                })))
325            }
326
327            // || (Concat operator) -> + in SQL Server
328            // SQL Server uses + for string concatenation
329            Expression::Concat(op) => {
330                // Convert || to + operator (Add)
331                Ok(Expression::Add(op))
332            }
333
334            // RANDOM -> RAND in SQL Server
335            Expression::Random(_) => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
336                seed: None,
337                lower: None,
338                upper: None,
339            }))),
340
341            // UNNEST -> Not directly supported, use CROSS APPLY with STRING_SPLIT or OPENJSON
342            Expression::Unnest(f) => {
343                // For basic cases, we'll use a placeholder
344                // Full support would require context-specific transformation
345                Ok(Expression::Function(Box::new(Function::new(
346                    "OPENJSON".to_string(),
347                    vec![f.this],
348                ))))
349            }
350
351            // EXPLODE -> Similar to UNNEST, use CROSS APPLY
352            Expression::Explode(f) => Ok(Expression::Function(Box::new(Function::new(
353                "OPENJSON".to_string(),
354                vec![f.this],
355            )))),
356
357            // PostgreSQL LATERAL join forms -> SQL Server APPLY.
358            Expression::Join(join) => Ok(Expression::Join(Box::new(
359                Self::transform_lateral_join_to_apply(*join),
360            ))),
361
362            // LENGTH -> LEN in SQL Server
363            Expression::Length(f) => Ok(Expression::Function(Box::new(Function::new(
364                "LEN".to_string(),
365                vec![f.this],
366            )))),
367
368            // STDDEV -> STDEV in SQL Server
369            Expression::Stddev(f) => Ok(Expression::Function(Box::new(Function::new(
370                "STDEV".to_string(),
371                vec![f.this],
372            )))),
373            Expression::StddevSamp(f) => Ok(Expression::Function(Box::new(Function::new(
374                "STDEV".to_string(),
375                vec![f.this],
376            )))),
377            Expression::StddevPop(f) => Ok(Expression::Function(Box::new(Function::new(
378                "STDEVP".to_string(),
379                vec![f.this],
380            )))),
381
382            // Boolean literals TRUE/FALSE -> 1/0 in SQL Server
383            Expression::Boolean(b) => {
384                let value = if b.value { 1 } else { 0 };
385                Ok(Expression::Literal(Box::new(
386                    crate::expressions::Literal::Number(value.to_string()),
387                )))
388            }
389
390            // LN -> LOG in SQL Server
391            Expression::Ln(f) => Ok(Expression::Function(Box::new(Function::new(
392                "LOG".to_string(),
393                vec![f.this],
394            )))),
395
396            // ===== Date/time =====
397            // CurrentDate -> CAST(GETDATE() AS DATE) in SQL Server
398            Expression::CurrentDate(_) => {
399                let getdate =
400                    Expression::Function(Box::new(Function::new("GETDATE".to_string(), vec![])));
401                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
402                    this: getdate,
403                    to: crate::expressions::DataType::Date,
404                    trailing_comments: Vec::new(),
405                    double_colon_syntax: false,
406                    format: None,
407                    default: None,
408                    inferred_type: None,
409                })))
410            }
411
412            // CurrentTimestamp -> GETDATE() in SQL Server
413            Expression::CurrentTimestamp(_) => Ok(Expression::Function(Box::new(Function::new(
414                "GETDATE".to_string(),
415                vec![],
416            )))),
417
418            // DateDiff -> DATEDIFF
419            Expression::DateDiff(f) => {
420                // TSQL: DATEDIFF(unit, start, end)
421                let unit_str = match f.unit {
422                    Some(crate::expressions::IntervalUnit::Year) => "YEAR",
423                    Some(crate::expressions::IntervalUnit::Quarter) => "QUARTER",
424                    Some(crate::expressions::IntervalUnit::Month) => "MONTH",
425                    Some(crate::expressions::IntervalUnit::Week) => "WEEK",
426                    Some(crate::expressions::IntervalUnit::Day) => "DAY",
427                    Some(crate::expressions::IntervalUnit::Hour) => "HOUR",
428                    Some(crate::expressions::IntervalUnit::Minute) => "MINUTE",
429                    Some(crate::expressions::IntervalUnit::Second) => "SECOND",
430                    Some(crate::expressions::IntervalUnit::Millisecond) => "MILLISECOND",
431                    Some(crate::expressions::IntervalUnit::Microsecond) => "MICROSECOND",
432                    Some(crate::expressions::IntervalUnit::Nanosecond) => "NANOSECOND",
433                    None => "DAY",
434                };
435                let unit = Expression::Identifier(crate::expressions::Identifier {
436                    name: unit_str.to_string(),
437                    quoted: false,
438                    trailing_comments: Vec::new(),
439                    span: None,
440                });
441                Ok(Expression::Function(Box::new(Function::new(
442                    "DATEDIFF".to_string(),
443                    vec![unit, f.expression, f.this], // Note: order is different in TSQL
444                ))))
445            }
446
447            // DateAdd -> DATEADD
448            Expression::DateAdd(f) => {
449                let unit_str = match f.unit {
450                    crate::expressions::IntervalUnit::Year => "YEAR",
451                    crate::expressions::IntervalUnit::Quarter => "QUARTER",
452                    crate::expressions::IntervalUnit::Month => "MONTH",
453                    crate::expressions::IntervalUnit::Week => "WEEK",
454                    crate::expressions::IntervalUnit::Day => "DAY",
455                    crate::expressions::IntervalUnit::Hour => "HOUR",
456                    crate::expressions::IntervalUnit::Minute => "MINUTE",
457                    crate::expressions::IntervalUnit::Second => "SECOND",
458                    crate::expressions::IntervalUnit::Millisecond => "MILLISECOND",
459                    crate::expressions::IntervalUnit::Microsecond => "MICROSECOND",
460                    crate::expressions::IntervalUnit::Nanosecond => "NANOSECOND",
461                };
462                let unit = Expression::Identifier(crate::expressions::Identifier {
463                    name: unit_str.to_string(),
464                    quoted: false,
465                    trailing_comments: Vec::new(),
466                    span: None,
467                });
468                Ok(Expression::Function(Box::new(Function::new(
469                    "DATEADD".to_string(),
470                    vec![unit, f.interval, f.this],
471                ))))
472            }
473
474            // ===== UUID =====
475            // Uuid -> NEWID in SQL Server
476            Expression::Uuid(_) => Ok(Expression::Function(Box::new(Function::new(
477                "NEWID".to_string(),
478                vec![],
479            )))),
480
481            // ===== Conditional =====
482            // IfFunc -> IIF in SQL Server
483            Expression::IfFunc(f) => {
484                let false_val = f
485                    .false_value
486                    .unwrap_or(Expression::Null(crate::expressions::Null));
487                Ok(Expression::Function(Box::new(Function::new(
488                    "IIF".to_string(),
489                    vec![f.condition, f.true_value, false_val],
490                ))))
491            }
492
493            // ===== String functions =====
494            // StringAgg -> STRING_AGG in SQL Server 2017+ - keep as-is to preserve ORDER BY
495            Expression::StringAgg(f) => Ok(Expression::StringAgg(f)),
496
497            // LastDay -> EOMONTH (note: TSQL doesn't support date part argument)
498            Expression::LastDay(f) => Ok(Expression::Function(Box::new(Function::new(
499                "EOMONTH".to_string(),
500                vec![f.this.clone()],
501            )))),
502
503            // Ceil -> CEILING
504            Expression::Ceil(f) => Ok(Expression::Function(Box::new(Function::new(
505                "CEILING".to_string(),
506                vec![f.this],
507            )))),
508
509            // Repeat -> REPLICATE in SQL Server
510            Expression::Repeat(f) => Ok(Expression::Function(Box::new(Function::new(
511                "REPLICATE".to_string(),
512                vec![f.this, f.times],
513            )))),
514
515            // Chr -> CHAR in SQL Server
516            Expression::Chr(f) => Ok(Expression::Function(Box::new(Function::new(
517                "CHAR".to_string(),
518                vec![f.this],
519            )))),
520
521            // ===== Variance =====
522            // VarPop -> VARP
523            Expression::VarPop(f) => Ok(Expression::Function(Box::new(Function::new(
524                "VARP".to_string(),
525                vec![f.this],
526            )))),
527
528            // Variance -> VAR
529            Expression::Variance(f) => Ok(Expression::Function(Box::new(Function::new(
530                "VAR".to_string(),
531                vec![f.this],
532            )))),
533            Expression::VarSamp(f) => Ok(Expression::Function(Box::new(Function::new(
534                "VAR".to_string(),
535                vec![f.this],
536            )))),
537
538            // ===== Hash functions =====
539            // MD5Digest -> HASHBYTES('MD5', ...)
540            Expression::MD5Digest(f) => Ok(Expression::Function(Box::new(Function::new(
541                "HASHBYTES".to_string(),
542                vec![Expression::string("MD5"), *f.this],
543            )))),
544
545            // SHA -> HASHBYTES('SHA1', ...)
546            Expression::SHA(f) => Ok(Expression::Function(Box::new(Function::new(
547                "HASHBYTES".to_string(),
548                vec![Expression::string("SHA1"), f.this],
549            )))),
550
551            // SHA1Digest -> HASHBYTES('SHA1', ...)
552            Expression::SHA1Digest(f) => Ok(Expression::Function(Box::new(Function::new(
553                "HASHBYTES".to_string(),
554                vec![Expression::string("SHA1"), f.this],
555            )))),
556
557            // ===== Array functions =====
558            // ArrayToString -> STRING_AGG
559            Expression::ArrayToString(f) => Ok(Expression::Function(Box::new(Function::new(
560                "STRING_AGG".to_string(),
561                vec![f.this],
562            )))),
563
564            // ===== DDL Column Constraints =====
565            // AutoIncrementColumnConstraint -> IDENTITY in SQL Server
566            Expression::AutoIncrementColumnConstraint(_) => Ok(Expression::Function(Box::new(
567                Function::new("IDENTITY".to_string(), vec![]),
568            ))),
569
570            // ===== DDL three-part name stripping =====
571            // TSQL strips database (catalog) prefix from 3-part names for CREATE VIEW/DROP VIEW
572            // Python sqlglot: expression.this.set("catalog", None)
573            Expression::CreateView(mut view) => {
574                // Strip catalog from three-part name (a.b.c -> b.c)
575                view.name.catalog = None;
576                Ok(Expression::CreateView(view))
577            }
578
579            Expression::DropView(mut view) => {
580                // Strip catalog from three-part name (a.b.c -> b.c)
581                view.name.catalog = None;
582                Ok(Expression::DropView(view))
583            }
584
585            // ParseJson: handled by generator (emits just the string literal for TSQL)
586
587            // JSONExtract with variant_extract (Snowflake colon syntax) -> ISNULL(JSON_QUERY, JSON_VALUE)
588            Expression::JSONExtract(e) if e.variant_extract.is_some() => {
589                let path = match *e.expression {
590                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
591                        let Literal::String(s) = lit.as_ref() else {
592                            unreachable!()
593                        };
594                        let normalized = if s.starts_with('$') {
595                            s.clone()
596                        } else if s.starts_with('[') {
597                            format!("${}", s)
598                        } else {
599                            format!("$.{}", s)
600                        };
601                        Expression::Literal(Box::new(Literal::String(normalized)))
602                    }
603                    other => other,
604                };
605                let json_query = Expression::Function(Box::new(Function::new(
606                    "JSON_QUERY".to_string(),
607                    vec![(*e.this).clone(), path.clone()],
608                )));
609                let json_value = Expression::Function(Box::new(Function::new(
610                    "JSON_VALUE".to_string(),
611                    vec![*e.this, path],
612                )));
613                Ok(Expression::Function(Box::new(Function::new(
614                    "ISNULL".to_string(),
615                    vec![json_query, json_value],
616                ))))
617            }
618
619            // Generic function transformations
620            Expression::Function(f) => self.transform_function(*f),
621
622            // Generic aggregate function transformations
623            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
624
625            // ===== CTEs need auto-aliased outputs =====
626            // In TSQL, bare expressions in CTEs need explicit aliases
627            Expression::Cte(cte) => self.transform_cte(*cte),
628
629            // ===== Subqueries need auto-aliased outputs =====
630            // In TSQL, bare expressions in aliased subqueries need explicit aliases
631            Expression::Subquery(subquery) => self.transform_subquery(*subquery),
632
633            // Convert JsonQuery struct to ISNULL(JSON_QUERY(..., path), JSON_VALUE(..., path))
634            Expression::JsonQuery(f) => {
635                let json_query = Expression::Function(Box::new(Function::new(
636                    "JSON_QUERY".to_string(),
637                    vec![f.this.clone(), f.path.clone()],
638                )));
639                let json_value = Expression::Function(Box::new(Function::new(
640                    "JSON_VALUE".to_string(),
641                    vec![f.this, f.path],
642                )));
643                Ok(Expression::Function(Box::new(Function::new(
644                    "ISNULL".to_string(),
645                    vec![json_query, json_value],
646                ))))
647            }
648            // Convert JsonValue struct to Function("JSON_VALUE", ...) for uniform handling
649            Expression::JsonValue(f) => Ok(Expression::Function(Box::new(Function::new(
650                "JSON_VALUE".to_string(),
651                vec![f.this, f.path],
652            )))),
653
654            // PostgreSQL pg_get_querydef can emit scalar array comparisons for
655            // literal arrays/tuples. T-SQL/Fabric require IN for this shape.
656            Expression::Any(ref q) if matches!(&q.op, Some(QuantifiedOp::Eq)) => {
657                let values: Option<Vec<Expression>> = match &q.subquery {
658                    Expression::ArrayFunc(a) => Some(a.expressions.clone()),
659                    Expression::Array(a) => Some(a.expressions.clone()),
660                    Expression::Tuple(t) => Some(t.expressions.clone()),
661                    _ => None,
662                };
663
664                match values {
665                    Some(expressions) if expressions.is_empty() => {
666                        Ok(Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
667                            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
668                            Expression::Literal(Box::new(Literal::Number("0".to_string()))),
669                        ))))
670                    }
671                    Some(expressions) => Ok(Expression::In(Box::new(In {
672                        this: q.this.clone(),
673                        expressions,
674                        query: None,
675                        not: false,
676                        global: false,
677                        unnest: None,
678                        is_field: false,
679                    }))),
680                    None => Ok(expr.clone()),
681                }
682            }
683
684            // Pass through everything else
685            _ => Ok(expr),
686        }
687    }
688}
689
690#[cfg(feature = "transpile")]
691impl TSQLDialect {
692    fn normalize_frame_incompatible_window_functions(select: &mut Select) {
693        let window_map: HashMap<String, Over> = select
694            .windows
695            .as_ref()
696            .map(|windows| {
697                windows
698                    .iter()
699                    .map(|window| (window.name.name.to_lowercase(), window.spec.clone()))
700                    .collect()
701            })
702            .unwrap_or_default();
703
704        for expr in &mut select.expressions {
705            Self::normalize_frame_incompatible_window_expr(expr, &window_map);
706        }
707
708        if let Some(order_by) = &mut select.order_by {
709            for ordered in &mut order_by.expressions {
710                Self::normalize_frame_incompatible_window_expr(&mut ordered.this, &window_map);
711            }
712        }
713
714        if let Some(qualify) = &mut select.qualify {
715            Self::normalize_frame_incompatible_window_expr(&mut qualify.this, &window_map);
716        }
717    }
718
719    fn normalize_frame_incompatible_window_expr(
720        expr: &mut Expression,
721        window_map: &HashMap<String, Over>,
722    ) {
723        match expr {
724            Expression::WindowFunction(wf) => {
725                Self::normalize_frame_incompatible_window_expr(&mut wf.this, window_map);
726
727                if !Self::is_tsql_frame_incompatible_window_function(&wf.this) {
728                    return;
729                }
730
731                wf.over.frame = None;
732
733                let Some(window_name) = wf.over.window_name.clone() else {
734                    return;
735                };
736                let Some(named_spec) =
737                    Self::resolve_named_window_spec(&window_name.name, window_map, &mut Vec::new())
738                else {
739                    return;
740                };
741
742                if named_spec.frame.is_none() {
743                    return;
744                }
745
746                if wf.over.partition_by.is_empty() {
747                    wf.over.partition_by = named_spec.partition_by;
748                }
749                if wf.over.order_by.is_empty() {
750                    wf.over.order_by = named_spec.order_by;
751                }
752                wf.over.window_name = None;
753                wf.over.frame = None;
754            }
755            Expression::Alias(alias) => {
756                Self::normalize_frame_incompatible_window_expr(&mut alias.this, window_map);
757            }
758            Expression::Paren(paren) => {
759                Self::normalize_frame_incompatible_window_expr(&mut paren.this, window_map);
760            }
761            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
762                Self::normalize_frame_incompatible_window_expr(&mut cast.this, window_map);
763            }
764            Expression::Function(function) => {
765                for arg in &mut function.args {
766                    Self::normalize_frame_incompatible_window_expr(arg, window_map);
767                }
768            }
769            Expression::Case(case) => {
770                if let Some(operand) = &mut case.operand {
771                    Self::normalize_frame_incompatible_window_expr(operand, window_map);
772                }
773                for (condition, result) in &mut case.whens {
774                    Self::normalize_frame_incompatible_window_expr(condition, window_map);
775                    Self::normalize_frame_incompatible_window_expr(result, window_map);
776                }
777                if let Some(else_expr) = &mut case.else_ {
778                    Self::normalize_frame_incompatible_window_expr(else_expr, window_map);
779                }
780            }
781            Expression::And(op)
782            | Expression::Or(op)
783            | Expression::Add(op)
784            | Expression::Sub(op)
785            | Expression::Mul(op)
786            | Expression::Div(op)
787            | Expression::Mod(op)
788            | Expression::Eq(op)
789            | Expression::Neq(op)
790            | Expression::Lt(op)
791            | Expression::Lte(op)
792            | Expression::Gt(op)
793            | Expression::Gte(op)
794            | Expression::Match(op)
795            | Expression::BitwiseAnd(op)
796            | Expression::BitwiseOr(op)
797            | Expression::BitwiseXor(op)
798            | Expression::Concat(op)
799            | Expression::Adjacent(op)
800            | Expression::TsMatch(op)
801            | Expression::PropertyEQ(op)
802            | Expression::ArrayContainsAll(op)
803            | Expression::ArrayContainedBy(op)
804            | Expression::ArrayOverlaps(op)
805            | Expression::JSONBContainsAllTopKeys(op)
806            | Expression::JSONBContainsAnyTopKeys(op)
807            | Expression::JSONBDeleteAtPath(op)
808            | Expression::ExtendsLeft(op)
809            | Expression::ExtendsRight(op)
810            | Expression::Is(op)
811            | Expression::MemberOf(op) => {
812                Self::normalize_frame_incompatible_window_expr(&mut op.left, window_map);
813                Self::normalize_frame_incompatible_window_expr(&mut op.right, window_map);
814            }
815            Expression::Like(op) | Expression::ILike(op) => {
816                Self::normalize_frame_incompatible_window_expr(&mut op.left, window_map);
817                Self::normalize_frame_incompatible_window_expr(&mut op.right, window_map);
818                if let Some(escape) = &mut op.escape {
819                    Self::normalize_frame_incompatible_window_expr(escape, window_map);
820                }
821            }
822            Expression::Not(op) | Expression::Neg(op) | Expression::BitwiseNot(op) => {
823                Self::normalize_frame_incompatible_window_expr(&mut op.this, window_map);
824            }
825            Expression::In(in_expr) => {
826                Self::normalize_frame_incompatible_window_expr(&mut in_expr.this, window_map);
827                for value in &mut in_expr.expressions {
828                    Self::normalize_frame_incompatible_window_expr(value, window_map);
829                }
830            }
831            Expression::Between(between) => {
832                Self::normalize_frame_incompatible_window_expr(&mut between.this, window_map);
833                Self::normalize_frame_incompatible_window_expr(&mut between.low, window_map);
834                Self::normalize_frame_incompatible_window_expr(&mut between.high, window_map);
835            }
836            Expression::IsNull(is_null) => {
837                Self::normalize_frame_incompatible_window_expr(&mut is_null.this, window_map);
838            }
839            Expression::IsTrue(is_true) | Expression::IsFalse(is_true) => {
840                Self::normalize_frame_incompatible_window_expr(&mut is_true.this, window_map);
841            }
842            _ => {}
843        }
844    }
845
846    fn is_tsql_frame_incompatible_window_function(expr: &Expression) -> bool {
847        matches!(
848            expr,
849            Expression::RowNumber(_)
850                | Expression::Rank(_)
851                | Expression::DenseRank(_)
852                | Expression::NTile(_)
853                | Expression::Ntile(_)
854                | Expression::Lead(_)
855                | Expression::Lag(_)
856                | Expression::PercentRank(_)
857                | Expression::CumeDist(_)
858        )
859    }
860
861    fn resolve_named_window_spec(
862        name: &str,
863        window_map: &HashMap<String, Over>,
864        seen: &mut Vec<String>,
865    ) -> Option<Over> {
866        let key = name.to_lowercase();
867        if seen.iter().any(|seen_name| seen_name == &key) {
868            return None;
869        }
870
871        let named_spec = window_map.get(&key)?.clone();
872        seen.push(key);
873
874        let mut resolved = if let Some(base_window) = &named_spec.window_name {
875            Self::resolve_named_window_spec(&base_window.name, window_map, seen)
876                .unwrap_or_else(Self::empty_over)
877        } else {
878            Self::empty_over()
879        };
880
881        if !named_spec.partition_by.is_empty() {
882            resolved.partition_by = named_spec.partition_by;
883        }
884        if !named_spec.order_by.is_empty() {
885            resolved.order_by = named_spec.order_by;
886        }
887        if named_spec.frame.is_some() {
888            resolved.frame = named_spec.frame;
889        }
890
891        Some(resolved)
892    }
893
894    fn empty_over() -> Over {
895        Over {
896            window_name: None,
897            partition_by: Vec::new(),
898            order_by: Vec::new(),
899            frame: None,
900            alias: None,
901        }
902    }
903
904    fn transform_lateral_join_to_apply(mut join: Join) -> Join {
905        let Some(apply_kind) = Self::lateral_apply_kind(&join) else {
906            return join;
907        };
908
909        join.this = Self::remove_lateral_marker(join.this);
910        join.on = None;
911        join.using.clear();
912        join.kind = apply_kind;
913        join.use_inner_keyword = false;
914        join.use_outer_keyword = false;
915        join.deferred_condition = false;
916        join.join_hint = None;
917        join.match_condition = None;
918        join.directed = false;
919        join
920    }
921
922    fn lateral_apply_kind(join: &Join) -> Option<JoinKind> {
923        let has_apply_condition = join.using.is_empty() && Self::has_no_or_true_on(&join.on);
924
925        match join.kind {
926            JoinKind::Lateral if has_apply_condition => Some(JoinKind::CrossApply),
927            JoinKind::LeftLateral if has_apply_condition => Some(JoinKind::OuterApply),
928            JoinKind::Cross | JoinKind::Inner
929                if has_apply_condition && Self::is_lateral_table_expression(&join.this) =>
930            {
931                Some(JoinKind::CrossApply)
932            }
933            JoinKind::Left
934                if has_apply_condition && Self::is_lateral_table_expression(&join.this) =>
935            {
936                Some(JoinKind::OuterApply)
937            }
938            _ => None,
939        }
940    }
941
942    fn has_no_or_true_on(on: &Option<Expression>) -> bool {
943        match on {
944            None => true,
945            Some(expr) => Self::is_true_condition(expr),
946        }
947    }
948
949    fn is_true_condition(expr: &Expression) -> bool {
950        match expr {
951            Expression::Boolean(boolean) => boolean.value,
952            Expression::Literal(lit) => {
953                matches!(lit.as_ref(), Literal::Number(value) if value.trim() == "1")
954            }
955            Expression::Eq(op) => {
956                Self::is_true_condition(&op.left) && Self::is_true_condition(&op.right)
957            }
958            Expression::Paren(paren) => Self::is_true_condition(&paren.this),
959            _ => false,
960        }
961    }
962
963    fn is_lateral_table_expression(expr: &Expression) -> bool {
964        match expr {
965            Expression::Subquery(subquery) => subquery.lateral,
966            Expression::Lateral(_) => true,
967            Expression::Alias(alias) => Self::is_lateral_table_expression(&alias.this),
968            _ => false,
969        }
970    }
971
972    fn remove_lateral_marker(expr: Expression) -> Expression {
973        match expr {
974            Expression::Subquery(mut subquery) => {
975                subquery.lateral = false;
976                Expression::Subquery(subquery)
977            }
978            Expression::Lateral(lateral) => Self::lateral_to_table_expression(*lateral),
979            Expression::Alias(mut alias) => {
980                alias.this = Self::remove_lateral_marker(alias.this);
981                Expression::Alias(alias)
982            }
983            other => other,
984        }
985    }
986
987    fn lateral_to_table_expression(lateral: crate::expressions::Lateral) -> Expression {
988        let expr = *lateral.this;
989        let Some(alias) = lateral.alias else {
990            return expr;
991        };
992
993        Expression::Alias(Box::new(Alias {
994            this: expr,
995            alias: if lateral.alias_quoted {
996                Identifier::quoted(alias)
997            } else {
998                Identifier::new(alias)
999            },
1000            column_aliases: lateral
1001                .column_aliases
1002                .into_iter()
1003                .map(Identifier::new)
1004                .collect(),
1005            alias_explicit_as: true,
1006            alias_keyword: None,
1007            pre_alias_comments: Vec::new(),
1008            trailing_comments: Vec::new(),
1009            inferred_type: None,
1010        }))
1011    }
1012
1013    fn rewrite_tuple_in_subquery_predicates(
1014        expr: Expression,
1015        outer_qualifier: Option<&Identifier>,
1016        under_not: bool,
1017    ) -> Expression {
1018        match expr {
1019            Expression::In(in_expr) if !under_not => {
1020                let in_expr = *in_expr;
1021                Self::tuple_in_subquery_to_exists(&in_expr, outer_qualifier)
1022                    .unwrap_or_else(|| Expression::In(Box::new(in_expr)))
1023            }
1024            Expression::And(mut op) => {
1025                op.left =
1026                    Self::rewrite_tuple_in_subquery_predicates(op.left, outer_qualifier, under_not);
1027                op.right = Self::rewrite_tuple_in_subquery_predicates(
1028                    op.right,
1029                    outer_qualifier,
1030                    under_not,
1031                );
1032                Expression::And(op)
1033            }
1034            Expression::Or(mut op) => {
1035                op.left =
1036                    Self::rewrite_tuple_in_subquery_predicates(op.left, outer_qualifier, under_not);
1037                op.right = Self::rewrite_tuple_in_subquery_predicates(
1038                    op.right,
1039                    outer_qualifier,
1040                    under_not,
1041                );
1042                Expression::Or(op)
1043            }
1044            Expression::Paren(mut paren) => {
1045                paren.this = Self::rewrite_tuple_in_subquery_predicates(
1046                    paren.this,
1047                    outer_qualifier,
1048                    under_not,
1049                );
1050                Expression::Paren(paren)
1051            }
1052            Expression::Not(mut not) => {
1053                not.this =
1054                    Self::rewrite_tuple_in_subquery_predicates(not.this, outer_qualifier, true);
1055                Expression::Not(not)
1056            }
1057            other => other,
1058        }
1059    }
1060
1061    fn tuple_in_subquery_to_exists(
1062        in_expr: &In,
1063        outer_qualifier: Option<&Identifier>,
1064    ) -> Option<Expression> {
1065        if in_expr.not || !in_expr.expressions.is_empty() || in_expr.unnest.is_some() {
1066            return None;
1067        }
1068
1069        let left_expressions = Self::tuple_expressions(&in_expr.this)?;
1070        let mut select = match in_expr.query.as_ref()? {
1071            Expression::Select(select) => (**select).clone(),
1072            _ => return None,
1073        };
1074
1075        if left_expressions.len() != select.expressions.len() || left_expressions.is_empty() {
1076            return None;
1077        }
1078
1079        let inner_qualifier = Self::single_select_source_qualifier(&select);
1080        let mut predicates = Vec::with_capacity(left_expressions.len() + 1);
1081        for (projection, left) in select
1082            .expressions
1083            .iter()
1084            .cloned()
1085            .zip(left_expressions.iter().cloned())
1086        {
1087            let inner = Self::tuple_in_projection_expr(projection, inner_qualifier.as_ref())?;
1088            let outer = Self::qualify_tuple_operand(left, outer_qualifier);
1089            predicates.push(Expression::Eq(Box::new(BinaryOp::new(inner, outer))));
1090        }
1091
1092        if let Some(where_clause) = select.where_clause.take() {
1093            predicates.push(where_clause.this);
1094        }
1095
1096        select.expressions = vec![Expression::number(1)];
1097        select.where_clause = Some(Where {
1098            this: Self::and_all(predicates)?,
1099        });
1100
1101        Some(Expression::Exists(Box::new(Exists {
1102            this: Expression::Select(Box::new(select)),
1103            not: false,
1104        })))
1105    }
1106
1107    fn tuple_expressions(expr: &Expression) -> Option<&[Expression]> {
1108        match expr {
1109            Expression::Tuple(tuple) => Some(&tuple.expressions),
1110            Expression::Paren(paren) => Self::tuple_expressions(&paren.this),
1111            _ => None,
1112        }
1113    }
1114
1115    fn tuple_in_projection_expr(
1116        expr: Expression,
1117        qualifier: Option<&Identifier>,
1118    ) -> Option<Expression> {
1119        match expr {
1120            Expression::Alias(alias) => Self::tuple_in_projection_expr(alias.this, qualifier),
1121            Expression::Column(mut column) => {
1122                if column.table.is_none() {
1123                    column.table = qualifier.cloned();
1124                }
1125                Some(Expression::Column(column))
1126            }
1127            Expression::Identifier(identifier) => {
1128                Some(Self::column_from_identifier(identifier, qualifier.cloned()))
1129            }
1130            Expression::Dot(_) => Some(expr),
1131            _ => None,
1132        }
1133    }
1134
1135    fn qualify_tuple_operand(expr: Expression, qualifier: Option<&Identifier>) -> Expression {
1136        match expr {
1137            Expression::Column(mut column) => {
1138                if column.table.is_none() {
1139                    column.table = qualifier.cloned();
1140                }
1141                Expression::Column(column)
1142            }
1143            Expression::Identifier(identifier) => {
1144                Self::column_from_identifier(identifier, qualifier.cloned())
1145            }
1146            other => other,
1147        }
1148    }
1149
1150    fn column_from_identifier(identifier: Identifier, table: Option<Identifier>) -> Expression {
1151        Expression::Column(Box::new(Column {
1152            name: identifier,
1153            table,
1154            join_mark: false,
1155            trailing_comments: Vec::new(),
1156            span: None,
1157            inferred_type: None,
1158        }))
1159    }
1160
1161    fn single_select_source_qualifier(select: &Select) -> Option<Identifier> {
1162        if !select.joins.is_empty() {
1163            return None;
1164        }
1165
1166        let from = select.from.as_ref()?;
1167        if from.expressions.len() != 1 {
1168            return None;
1169        }
1170
1171        Self::source_qualifier(&from.expressions[0])
1172    }
1173
1174    fn source_qualifier(source: &Expression) -> Option<Identifier> {
1175        match source {
1176            Expression::Table(table) => table.alias.clone().or_else(|| Some(table.name.clone())),
1177            Expression::Subquery(subquery) => subquery.alias.clone(),
1178            _ => None,
1179        }
1180    }
1181
1182    fn and_all(mut predicates: Vec<Expression>) -> Option<Expression> {
1183        if predicates.is_empty() {
1184            return None;
1185        }
1186
1187        let first = predicates.remove(0);
1188        Some(predicates.into_iter().fold(first, |left, right| {
1189            Expression::And(Box::new(BinaryOp::new(left, right)))
1190        }))
1191    }
1192
1193    /// Transform data types according to T-SQL TYPE_MAPPING
1194    pub(super) fn transform_data_type(
1195        &self,
1196        dt: crate::expressions::DataType,
1197    ) -> Result<Expression> {
1198        use crate::expressions::DataType;
1199        let transformed = match dt {
1200            // BOOLEAN -> BIT
1201            DataType::Boolean => DataType::Custom {
1202                name: "BIT".to_string(),
1203            },
1204            // INT stays as INT in TSQL (native type)
1205            DataType::Int { .. } => dt,
1206            // DOUBLE stays as Double internally (TSQL generator outputs FLOAT for it)
1207            // DECIMAL -> NUMERIC
1208            DataType::Decimal { precision, scale } => DataType::Custom {
1209                name: if let (Some(p), Some(s)) = (&precision, &scale) {
1210                    format!("NUMERIC({}, {})", p, s)
1211                } else if let Some(p) = &precision {
1212                    format!("NUMERIC({})", p)
1213                } else {
1214                    "NUMERIC".to_string()
1215                },
1216            },
1217            // TEXT -> VARCHAR(MAX)
1218            DataType::Text => DataType::Custom {
1219                name: "VARCHAR(MAX)".to_string(),
1220            },
1221            // TIMESTAMP -> DATETIME2
1222            DataType::Timestamp { .. } => DataType::Custom {
1223                name: "DATETIME2".to_string(),
1224            },
1225            // UUID -> UNIQUEIDENTIFIER
1226            DataType::Uuid => DataType::Custom {
1227                name: "UNIQUEIDENTIFIER".to_string(),
1228            },
1229            // Normalise custom type names that have PostgreSQL aliases
1230            DataType::Custom { ref name } => {
1231                let upper = name.trim().to_uppercase();
1232                let (base_name, precision, _scale) = Self::parse_type_precision_and_scale(&upper);
1233                match base_name.as_str() {
1234                    // BPCHAR is PostgreSQL's blank-padded CHAR alias — map to CHAR
1235                    "BPCHAR" => {
1236                        if let Some(len) = precision {
1237                            DataType::Char { length: Some(len) }
1238                        } else {
1239                            DataType::Char { length: None }
1240                        }
1241                    }
1242                    _ => dt,
1243                }
1244            }
1245            // Keep all other types as-is
1246            other => other,
1247        };
1248        Ok(Expression::DataType(transformed))
1249    }
1250
1251    /// Parse a type name that may embed precision/scale: `"TYPENAME(n, m)"` → `("TYPENAME", Some(n), Some(m))`.
1252    pub(super) fn parse_type_precision_and_scale(name: &str) -> (String, Option<u32>, Option<u32>) {
1253        if let Some(paren_pos) = name.find('(') {
1254            let base = name[..paren_pos].to_string();
1255            let rest = &name[paren_pos + 1..];
1256            if let Some(close_pos) = rest.find(')') {
1257                let args = &rest[..close_pos];
1258                let parts: Vec<&str> = args.split(',').map(|s| s.trim()).collect();
1259                let precision = parts.first().and_then(|s| s.parse::<u32>().ok());
1260                let scale = parts.get(1).and_then(|s| s.parse::<u32>().ok());
1261                return (base, precision, scale);
1262            }
1263            (base, None, None)
1264        } else {
1265            (name.to_string(), None, None)
1266        }
1267    }
1268
1269    fn transform_logical_aggregate(
1270        condition: Expression,
1271        filter: Option<Expression>,
1272        aggregate_name: &str,
1273    ) -> Result<Expression> {
1274        let false_condition = Expression::Not(Box::new(crate::expressions::UnaryOp {
1275            this: condition.clone(),
1276            inferred_type: None,
1277        }));
1278        let true_condition = Self::apply_aggregate_filter(condition, filter.clone());
1279        let false_condition = Self::apply_aggregate_filter(false_condition, filter);
1280
1281        let case_expr = Expression::Case(Box::new(crate::expressions::Case {
1282            operand: None,
1283            whens: vec![
1284                (true_condition, Expression::number(1)),
1285                (false_condition, Expression::number(0)),
1286            ],
1287            else_: Some(Expression::null()),
1288            comments: Vec::new(),
1289            inferred_type: None,
1290        }));
1291
1292        let case_expr = crate::transforms::ensure_bools(case_expr)?;
1293        let aggregate = Expression::Function(Box::new(Function::new(
1294            aggregate_name.to_string(),
1295            vec![case_expr],
1296        )));
1297
1298        Ok(Expression::Cast(Box::new(Cast {
1299            this: aggregate,
1300            to: DataType::Custom {
1301                name: "BIT".to_string(),
1302            },
1303            trailing_comments: Vec::new(),
1304            double_colon_syntax: false,
1305            format: None,
1306            default: None,
1307            inferred_type: None,
1308        })))
1309    }
1310
1311    fn apply_aggregate_filter(condition: Expression, filter: Option<Expression>) -> Expression {
1312        match filter {
1313            Some(filter) => Expression::And(Box::new(crate::expressions::BinaryOp::new(
1314                filter, condition,
1315            ))),
1316            None => condition,
1317        }
1318    }
1319
1320    fn transform_function(&self, f: Function) -> Result<Expression> {
1321        let name_upper = f.name.to_uppercase();
1322        match name_upper.as_str() {
1323            // COALESCE -> ISNULL for 2 args (optimization)
1324            "COALESCE" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
1325                "ISNULL".to_string(),
1326                f.args,
1327            )))),
1328
1329            // NVL -> ISNULL (SQL Server function)
1330            "NVL" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
1331                "ISNULL".to_string(),
1332                f.args,
1333            )))),
1334
1335            // GROUP_CONCAT -> STRING_AGG in SQL Server 2017+
1336            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1337                Function::new("STRING_AGG".to_string(), f.args),
1338            ))),
1339
1340            // STRING_AGG is native to SQL Server 2017+
1341            "STRING_AGG" => Ok(Expression::Function(Box::new(f))),
1342
1343            // LISTAGG -> STRING_AGG
1344            "LISTAGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1345                "STRING_AGG".to_string(),
1346                f.args,
1347            )))),
1348
1349            // SUBSTR -> SUBSTRING
1350            "SUBSTR" => Ok(Expression::Function(Box::new(Function::new(
1351                "SUBSTRING".to_string(),
1352                f.args,
1353            )))),
1354
1355            // LENGTH -> LEN in SQL Server
1356            "LENGTH" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
1357                "LEN".to_string(),
1358                f.args,
1359            )))),
1360
1361            // RANDOM -> RAND
1362            "RANDOM" => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
1363                seed: None,
1364                lower: None,
1365                upper: None,
1366            }))),
1367
1368            // NOW -> GETDATE or CURRENT_TIMESTAMP (both work)
1369            "NOW" => Ok(Expression::Function(Box::new(Function::new(
1370                "GETDATE".to_string(),
1371                vec![],
1372            )))),
1373
1374            // CURRENT_TIMESTAMP -> GETDATE (SQL Server prefers GETDATE)
1375            "CURRENT_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
1376                "GETDATE".to_string(),
1377                vec![],
1378            )))),
1379
1380            // CURRENT_DATE -> CAST(GETDATE() AS DATE)
1381            "CURRENT_DATE" => {
1382                // In SQL Server, use CAST(GETDATE() AS DATE)
1383                Ok(Expression::Function(Box::new(Function::new(
1384                    "CAST".to_string(),
1385                    vec![
1386                        Expression::Function(Box::new(Function::new(
1387                            "GETDATE".to_string(),
1388                            vec![],
1389                        ))),
1390                        Expression::Identifier(crate::expressions::Identifier::new("DATE")),
1391                    ],
1392                ))))
1393            }
1394
1395            // TO_DATE -> CONVERT or CAST
1396            "TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
1397                "CONVERT".to_string(),
1398                f.args,
1399            )))),
1400
1401            // TO_TIMESTAMP -> CONVERT
1402            "TO_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
1403                "CONVERT".to_string(),
1404                f.args,
1405            )))),
1406
1407            // TO_CHAR -> FORMAT in SQL Server 2012+
1408            "TO_CHAR" => Ok(Expression::Function(Box::new(Function::new(
1409                "FORMAT".to_string(),
1410                f.args,
1411            )))),
1412
1413            // DATE_FORMAT -> FORMAT
1414            "DATE_FORMAT" => Ok(Expression::Function(Box::new(Function::new(
1415                "FORMAT".to_string(),
1416                f.args,
1417            )))),
1418
1419            // DATE_TRUNC -> DATETRUNC in SQL Server 2022+
1420            // For older versions, use DATEADD/DATEDIFF combo
1421            "DATE_TRUNC" | "DATETRUNC" => {
1422                let mut args = Self::uppercase_first_arg_if_identifier(f.args);
1423                // Cast string literal date arg to DATETIME2
1424                if args.len() >= 2 {
1425                    if let Expression::Literal(lit) = &args[1] {
1426                        if let Literal::String(_) = lit.as_ref() {
1427                            args[1] = Expression::Cast(Box::new(Cast {
1428                                this: args[1].clone(),
1429                                to: DataType::Custom {
1430                                    name: "DATETIME2".to_string(),
1431                                },
1432                                trailing_comments: Vec::new(),
1433                                double_colon_syntax: false,
1434                                format: None,
1435                                default: None,
1436                                inferred_type: None,
1437                            }));
1438                        }
1439                    }
1440                }
1441                Ok(Expression::Function(Box::new(Function::new(
1442                    "DATETRUNC".to_string(),
1443                    args,
1444                ))))
1445            }
1446
1447            // DATEADD is native to SQL Server - uppercase the unit
1448            "DATEADD" => {
1449                let args = Self::uppercase_first_arg_if_identifier(f.args);
1450                Ok(Expression::Function(Box::new(Function::new(
1451                    "DATEADD".to_string(),
1452                    args,
1453                ))))
1454            }
1455
1456            // DATEDIFF is native to SQL Server - uppercase the unit
1457            "DATEDIFF" => {
1458                let args = Self::uppercase_first_arg_if_identifier(f.args);
1459                Ok(Expression::Function(Box::new(Function::new(
1460                    "DATEDIFF".to_string(),
1461                    args,
1462                ))))
1463            }
1464
1465            // EXTRACT -> DATEPART in SQL Server
1466            "EXTRACT" => Ok(Expression::Function(Box::new(Function::new(
1467                "DATEPART".to_string(),
1468                f.args,
1469            )))),
1470
1471            // STRPOS / POSITION -> CHARINDEX
1472            "STRPOS" | "POSITION" if f.args.len() >= 2 => {
1473                // CHARINDEX(substring, string) - same arg order as POSITION
1474                Ok(Expression::Function(Box::new(Function::new(
1475                    "CHARINDEX".to_string(),
1476                    f.args,
1477                ))))
1478            }
1479
1480            // CHARINDEX is native
1481            "CHARINDEX" => Ok(Expression::Function(Box::new(f))),
1482
1483            // CEILING -> CEILING (native)
1484            "CEILING" | "CEIL" if f.args.len() == 1 => Ok(Expression::Function(Box::new(
1485                Function::new("CEILING".to_string(), f.args),
1486            ))),
1487
1488            // ARRAY functions don't exist in SQL Server
1489            // Would need JSON or table-valued parameters
1490
1491            // JSON_EXTRACT -> JSON_VALUE or JSON_QUERY
1492            "JSON_EXTRACT" => Ok(Expression::Function(Box::new(Function::new(
1493                "JSON_VALUE".to_string(),
1494                f.args,
1495            )))),
1496
1497            // JSON_EXTRACT_SCALAR -> JSON_VALUE
1498            "JSON_EXTRACT_SCALAR" => Ok(Expression::Function(Box::new(Function::new(
1499                "JSON_VALUE".to_string(),
1500                f.args,
1501            )))),
1502
1503            // PARSE_JSON -> strip in TSQL (just keep the string argument)
1504            "PARSE_JSON" if f.args.len() == 1 => Ok(f.args.into_iter().next().unwrap()),
1505
1506            // GET_PATH(obj, path) -> ISNULL(JSON_QUERY(obj, path), JSON_VALUE(obj, path)) in TSQL
1507            "GET_PATH" if f.args.len() == 2 => {
1508                let mut args = f.args;
1509                let this = args.remove(0);
1510                let path = args.remove(0);
1511                let json_path = match &path {
1512                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
1513                        let Literal::String(s) = lit.as_ref() else {
1514                            unreachable!()
1515                        };
1516                        let normalized = if s.starts_with('$') {
1517                            s.clone()
1518                        } else if s.starts_with('[') {
1519                            format!("${}", s)
1520                        } else {
1521                            format!("$.{}", s)
1522                        };
1523                        Expression::Literal(Box::new(Literal::String(normalized)))
1524                    }
1525                    _ => path,
1526                };
1527                // ISNULL(JSON_QUERY(obj, path), JSON_VALUE(obj, path))
1528                let json_query = Expression::Function(Box::new(Function::new(
1529                    "JSON_QUERY".to_string(),
1530                    vec![this.clone(), json_path.clone()],
1531                )));
1532                let json_value = Expression::Function(Box::new(Function::new(
1533                    "JSON_VALUE".to_string(),
1534                    vec![this, json_path],
1535                )));
1536                Ok(Expression::Function(Box::new(Function::new(
1537                    "ISNULL".to_string(),
1538                    vec![json_query, json_value],
1539                ))))
1540            }
1541
1542            // JSON_QUERY with 1 arg: add '$' path and wrap in ISNULL
1543            // JSON_QUERY with 2 args: leave as-is (already processed or inside ISNULL)
1544            "JSON_QUERY" if f.args.len() == 1 => {
1545                let this = f.args.into_iter().next().unwrap();
1546                let path = Expression::Literal(Box::new(Literal::String("$".to_string())));
1547                let json_query = Expression::Function(Box::new(Function::new(
1548                    "JSON_QUERY".to_string(),
1549                    vec![this.clone(), path.clone()],
1550                )));
1551                let json_value = Expression::Function(Box::new(Function::new(
1552                    "JSON_VALUE".to_string(),
1553                    vec![this, path],
1554                )));
1555                Ok(Expression::Function(Box::new(Function::new(
1556                    "ISNULL".to_string(),
1557                    vec![json_query, json_value],
1558                ))))
1559            }
1560
1561            // SPLIT -> STRING_SPLIT (returns a table, needs CROSS APPLY)
1562            "SPLIT" => Ok(Expression::Function(Box::new(Function::new(
1563                "STRING_SPLIT".to_string(),
1564                f.args,
1565            )))),
1566
1567            // REGEXP_LIKE -> Not directly supported, use LIKE or PATINDEX
1568            // SQL Server has limited regex support via PATINDEX and LIKE
1569            "REGEXP_LIKE" => {
1570                // Fall back to LIKE (loses regex functionality)
1571                Ok(Expression::Function(Box::new(Function::new(
1572                    "PATINDEX".to_string(),
1573                    f.args,
1574                ))))
1575            }
1576
1577            // LN -> LOG in SQL Server
1578            "LN" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
1579                "LOG".to_string(),
1580                f.args,
1581            )))),
1582
1583            // LOG with 2 args is LOG(base, value) in most DBs but LOG(value, base) in SQL Server
1584            // This needs careful handling
1585
1586            // STDDEV -> STDEV in SQL Server
1587            "STDDEV" | "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
1588                "STDEV".to_string(),
1589                f.args,
1590            )))),
1591
1592            // STDDEV_POP -> STDEVP in SQL Server
1593            "STDDEV_POP" => Ok(Expression::Function(Box::new(Function::new(
1594                "STDEVP".to_string(),
1595                f.args,
1596            )))),
1597
1598            // VAR_SAMP -> VAR in SQL Server
1599            "VARIANCE" | "VAR_SAMP" => Ok(Expression::Function(Box::new(Function::new(
1600                "VAR".to_string(),
1601                f.args,
1602            )))),
1603
1604            // VAR_POP -> VARP in SQL Server
1605            "VAR_POP" => Ok(Expression::Function(Box::new(Function::new(
1606                "VARP".to_string(),
1607                f.args,
1608            )))),
1609
1610            // Boolean aggregates -> MIN/MAX over a null-preserving CASE, cast back to BIT.
1611            "BOOL_AND" | "LOGICAL_AND" | "BOOLAND_AGG" | "EVERY" if f.args.len() == 1 => {
1612                let mut args = f.args;
1613                Self::transform_logical_aggregate(args.remove(0), None, "MIN")
1614            }
1615            "BOOL_OR" | "LOGICAL_OR" | "BOOLOR_AGG" if f.args.len() == 1 => {
1616                let mut args = f.args;
1617                Self::transform_logical_aggregate(args.remove(0), None, "MAX")
1618            }
1619
1620            // DATE_ADD(date, interval) -> DATEADD(DAY, interval, date)
1621            "DATE_ADD" => {
1622                if f.args.len() == 2 {
1623                    let mut args = f.args;
1624                    let date = args.remove(0);
1625                    let interval = args.remove(0);
1626                    let unit = Expression::Identifier(crate::expressions::Identifier {
1627                        name: "DAY".to_string(),
1628                        quoted: false,
1629                        trailing_comments: Vec::new(),
1630                        span: None,
1631                    });
1632                    Ok(Expression::Function(Box::new(Function::new(
1633                        "DATEADD".to_string(),
1634                        vec![unit, interval, date],
1635                    ))))
1636                } else {
1637                    let args = Self::uppercase_first_arg_if_identifier(f.args);
1638                    Ok(Expression::Function(Box::new(Function::new(
1639                        "DATEADD".to_string(),
1640                        args,
1641                    ))))
1642                }
1643            }
1644
1645            // INSERT → STUFF (Snowflake/MySQL string INSERT → T-SQL STUFF)
1646            "INSERT" => Ok(Expression::Function(Box::new(Function::new(
1647                "STUFF".to_string(),
1648                f.args,
1649            )))),
1650
1651            // SUSER_NAME(), SUSER_SNAME(), SYSTEM_USER() -> CURRENT_USER
1652            "SUSER_NAME" | "SUSER_SNAME" | "SYSTEM_USER" => Ok(Expression::CurrentUser(Box::new(
1653                crate::expressions::CurrentUser { this: None },
1654            ))),
1655
1656            // Pass through everything else
1657            _ => Ok(Expression::Function(Box::new(f))),
1658        }
1659    }
1660
1661    fn transform_aggregate_function(
1662        &self,
1663        f: Box<crate::expressions::AggregateFunction>,
1664    ) -> Result<Expression> {
1665        let name_upper = f.name.to_uppercase();
1666        match name_upper.as_str() {
1667            // GROUP_CONCAT -> STRING_AGG
1668            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1669                Function::new("STRING_AGG".to_string(), f.args),
1670            ))),
1671
1672            // LISTAGG -> STRING_AGG
1673            "LISTAGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1674                "STRING_AGG".to_string(),
1675                f.args,
1676            )))),
1677
1678            // ARRAY_AGG -> Not directly supported in SQL Server
1679            // Would need to use FOR XML PATH or STRING_AGG
1680            "ARRAY_AGG" if !f.args.is_empty() => {
1681                // Fall back to STRING_AGG (loses array semantics)
1682                Ok(Expression::Function(Box::new(Function::new(
1683                    "STRING_AGG".to_string(),
1684                    f.args,
1685                ))))
1686            }
1687
1688            // Boolean aggregates -> MIN/MAX over a null-preserving CASE, cast back to BIT.
1689            "BOOL_AND" | "LOGICAL_AND" | "BOOLAND_AGG" | "EVERY" if f.args.len() == 1 => {
1690                let mut args = f.args;
1691                Self::transform_logical_aggregate(args.remove(0), f.filter, "MIN")
1692            }
1693            "BOOL_OR" | "LOGICAL_OR" | "BOOLOR_AGG" if f.args.len() == 1 => {
1694                let mut args = f.args;
1695                Self::transform_logical_aggregate(args.remove(0), f.filter, "MAX")
1696            }
1697
1698            // Pass through everything else
1699            _ => Ok(Expression::AggregateFunction(f)),
1700        }
1701    }
1702
1703    /// Transform CTEs to add auto-aliases to bare expressions in SELECT
1704    /// In TSQL, when a CTE doesn't have explicit column aliases, bare expressions
1705    /// in the SELECT need to be aliased
1706    fn transform_cte(&self, cte: Cte) -> Result<Expression> {
1707        Ok(Expression::Cte(Box::new(self.transform_cte_inner(cte))))
1708    }
1709
1710    /// Inner method to transform a CTE, returning the modified Cte struct
1711    fn transform_cte_inner(&self, mut cte: Cte) -> Cte {
1712        // Only transform if the CTE doesn't have explicit column aliases
1713        // If it has column aliases like `WITH t(a, b) AS (...)`, we don't need to auto-alias
1714        if cte.columns.is_empty() {
1715            cte.this = self.qualify_derived_table_outputs(cte.this);
1716        }
1717        cte
1718    }
1719
1720    /// Transform Subqueries to add auto-aliases to bare expressions in SELECT
1721    /// In TSQL, when a subquery has a table alias but no column aliases,
1722    /// bare expressions need to be aliased
1723    fn transform_subquery(&self, mut subquery: Subquery) -> Result<Expression> {
1724        // Only transform if the subquery has a table alias but no column aliases
1725        // e.g., `(SELECT 1) AS subq` needs aliasing, but `(SELECT 1) AS subq(a)` doesn't
1726        if subquery.alias.is_some() && subquery.column_aliases.is_empty() {
1727            subquery.this = self.qualify_derived_table_outputs(subquery.this);
1728        }
1729        Ok(Expression::Subquery(Box::new(subquery)))
1730    }
1731
1732    /// Add aliases to bare (unaliased) expressions in a SELECT statement
1733    /// This transforms expressions like `SELECT 1` into `SELECT 1 AS [1]`
1734    /// BUT only when the SELECT has no FROM clause (i.e., it's a value expression)
1735    fn qualify_derived_table_outputs(&self, expr: Expression) -> Expression {
1736        match expr {
1737            Expression::Select(mut select) => {
1738                // Only auto-alias if the SELECT has NO from clause
1739                // If there's a FROM clause, column references already have names from the source tables
1740                let has_from = select.from.is_some();
1741                if !has_from {
1742                    select.expressions = select
1743                        .expressions
1744                        .into_iter()
1745                        .map(|e| self.maybe_alias_expression(e))
1746                        .collect();
1747                }
1748                Expression::Select(select)
1749            }
1750            // For UNION/INTERSECT/EXCEPT, transform the first SELECT
1751            Expression::Union(mut u) => {
1752                let left = std::mem::replace(&mut u.left, Expression::Null(Null));
1753                u.left = self.qualify_derived_table_outputs(left);
1754                Expression::Union(u)
1755            }
1756            Expression::Intersect(mut i) => {
1757                let left = std::mem::replace(&mut i.left, Expression::Null(Null));
1758                i.left = self.qualify_derived_table_outputs(left);
1759                Expression::Intersect(i)
1760            }
1761            Expression::Except(mut e) => {
1762                let left = std::mem::replace(&mut e.left, Expression::Null(Null));
1763                e.left = self.qualify_derived_table_outputs(left);
1764                Expression::Except(e)
1765            }
1766            // Already wrapped in a Subquery (nested), transform the inner
1767            Expression::Subquery(mut s) => {
1768                s.this = self.qualify_derived_table_outputs(s.this);
1769                Expression::Subquery(s)
1770            }
1771            // Pass through anything else
1772            other => other,
1773        }
1774    }
1775
1776    /// Add an alias to a bare expression if needed
1777    /// Returns the expression unchanged if it already has an alias or is a star
1778    /// NOTE: This is only called for SELECTs without a FROM clause, so all bare
1779    /// expressions (including identifiers and columns) need to be aliased.
1780    fn maybe_alias_expression(&self, expr: Expression) -> Expression {
1781        match &expr {
1782            // Already has an alias, leave it alone
1783            Expression::Alias(_) => expr,
1784            // Multiple aliases, leave it alone
1785            Expression::Aliases(_) => expr,
1786            // Star (including qualified star like t.*) doesn't need an alias
1787            Expression::Star(_) => expr,
1788            // When there's no FROM clause (which is the only case when this method is called),
1789            // we need to alias columns and identifiers too since they're standalone values
1790            // that need explicit names for the derived table output.
1791            // Everything else (literals, functions, columns, identifiers, etc.) needs an alias
1792            _ => {
1793                if let Some(output_name) = self.get_output_name(&expr) {
1794                    Expression::Alias(Box::new(Alias {
1795                        this: expr,
1796                        alias: Identifier {
1797                            name: output_name,
1798                            quoted: true, // Force quoting for TSQL bracket syntax
1799                            trailing_comments: Vec::new(),
1800                            span: None,
1801                        },
1802                        column_aliases: Vec::new(),
1803                        alias_explicit_as: false,
1804                        alias_keyword: None,
1805                        pre_alias_comments: Vec::new(),
1806                        trailing_comments: Vec::new(),
1807                        inferred_type: None,
1808                    }))
1809                } else {
1810                    // No output name, leave as-is (shouldn't happen for valid expressions)
1811                    expr
1812                }
1813            }
1814        }
1815    }
1816
1817    /// Get the "output name" of an expression for auto-aliasing
1818    /// For literals, this is the literal value
1819    /// For columns, this is the column name
1820    fn get_output_name(&self, expr: &Expression) -> Option<String> {
1821        match expr {
1822            // Literals - use the literal value as the name
1823            Expression::Literal(lit) => match lit.as_ref() {
1824                Literal::Number(n) => Some(n.clone()),
1825                Literal::String(s) => Some(s.clone()),
1826                Literal::HexString(h) => Some(format!("0x{}", h)),
1827                Literal::HexNumber(h) => Some(format!("0x{}", h)),
1828                Literal::BitString(b) => Some(format!("b{}", b)),
1829                Literal::ByteString(b) => Some(format!("b'{}'", b)),
1830                Literal::NationalString(s) => Some(format!("N'{}'", s)),
1831                Literal::Date(d) => Some(d.clone()),
1832                Literal::Time(t) => Some(t.clone()),
1833                Literal::Timestamp(ts) => Some(ts.clone()),
1834                Literal::Datetime(dt) => Some(dt.clone()),
1835                Literal::TripleQuotedString(s, _) => Some(s.clone()),
1836                Literal::EscapeString(s) => Some(s.clone()),
1837                Literal::DollarString(s) => Some(s.clone()),
1838                Literal::RawString(s) => Some(s.clone()),
1839            },
1840            // Columns - use the column name
1841            Expression::Column(col) => Some(col.name.name.clone()),
1842            // Identifiers - use the identifier name
1843            Expression::Identifier(ident) => Some(ident.name.clone()),
1844            // Boolean literals
1845            Expression::Boolean(b) => Some(if b.value { "1" } else { "0" }.to_string()),
1846            // NULL
1847            Expression::Null(_) => Some("NULL".to_string()),
1848            // For functions, use the function name as a fallback
1849            Expression::Function(f) => Some(f.name.clone()),
1850            // For aggregates, use the function name
1851            Expression::AggregateFunction(f) => Some(f.name.clone()),
1852            // For other expressions, generate a generic name
1853            _ => Some(format!("_col_{}", 0)),
1854        }
1855    }
1856
1857    /// Helper to uppercase the first argument if it's an identifier or column (for DATEDIFF, DATEADD units)
1858    fn uppercase_first_arg_if_identifier(mut args: Vec<Expression>) -> Vec<Expression> {
1859        use crate::expressions::Identifier;
1860        if !args.is_empty() {
1861            match &args[0] {
1862                Expression::Identifier(id) => {
1863                    args[0] = Expression::Identifier(Identifier {
1864                        name: id.name.to_uppercase(),
1865                        quoted: id.quoted,
1866                        trailing_comments: id.trailing_comments.clone(),
1867                        span: None,
1868                    });
1869                }
1870                Expression::Var(v) => {
1871                    args[0] = Expression::Identifier(Identifier {
1872                        name: v.this.to_uppercase(),
1873                        quoted: false,
1874                        trailing_comments: Vec::new(),
1875                        span: None,
1876                    });
1877                }
1878                Expression::Column(col) if col.table.is_none() => {
1879                    args[0] = Expression::Identifier(Identifier {
1880                        name: col.name.name.to_uppercase(),
1881                        quoted: col.name.quoted,
1882                        trailing_comments: col.name.trailing_comments.clone(),
1883                        span: None,
1884                    });
1885                }
1886                _ => {}
1887            }
1888        }
1889        args
1890    }
1891}
1892
1893#[cfg(test)]
1894mod tests {
1895    use super::*;
1896    use crate::dialects::Dialect;
1897
1898    fn transpile_to_tsql(sql: &str) -> String {
1899        let dialect = Dialect::get(DialectType::Generic);
1900        let result = dialect
1901            .transpile(sql, DialectType::TSQL)
1902            .expect("Transpile failed");
1903        result[0].clone()
1904    }
1905
1906    #[test]
1907    fn test_nvl_to_isnull() {
1908        let result = transpile_to_tsql("SELECT NVL(a, b)");
1909        assert!(
1910            result.contains("ISNULL"),
1911            "Expected ISNULL, got: {}",
1912            result
1913        );
1914    }
1915
1916    #[test]
1917    fn test_coalesce_to_isnull() {
1918        let result = transpile_to_tsql("SELECT COALESCE(a, b)");
1919        assert!(
1920            result.contains("ISNULL"),
1921            "Expected ISNULL, got: {}",
1922            result
1923        );
1924    }
1925
1926    #[test]
1927    fn test_basic_select() {
1928        let result = transpile_to_tsql("SELECT a, b FROM users WHERE id = 1");
1929        assert!(result.contains("SELECT"));
1930        assert!(result.contains("FROM users"));
1931    }
1932
1933    #[test]
1934    fn test_length_to_len() {
1935        let result = transpile_to_tsql("SELECT LENGTH(name)");
1936        assert!(result.contains("LEN"), "Expected LEN, got: {}", result);
1937    }
1938
1939    #[test]
1940    fn test_now_to_getdate() {
1941        let result = transpile_to_tsql("SELECT NOW()");
1942        assert!(
1943            result.contains("GETDATE"),
1944            "Expected GETDATE, got: {}",
1945            result
1946        );
1947    }
1948
1949    #[test]
1950    fn test_group_concat_to_string_agg() {
1951        let result = transpile_to_tsql("SELECT GROUP_CONCAT(name)");
1952        assert!(
1953            result.contains("STRING_AGG"),
1954            "Expected STRING_AGG, got: {}",
1955            result
1956        );
1957    }
1958
1959    #[test]
1960    fn test_listagg_to_string_agg() {
1961        let result = transpile_to_tsql("SELECT LISTAGG(name)");
1962        assert!(
1963            result.contains("STRING_AGG"),
1964            "Expected STRING_AGG, got: {}",
1965            result
1966        );
1967    }
1968
1969    #[test]
1970    fn test_ln_to_log() {
1971        let result = transpile_to_tsql("SELECT LN(x)");
1972        assert!(result.contains("LOG"), "Expected LOG, got: {}", result);
1973    }
1974
1975    #[test]
1976    fn test_stddev_to_stdev() {
1977        let result = transpile_to_tsql("SELECT STDDEV(x)");
1978        assert!(result.contains("STDEV"), "Expected STDEV, got: {}", result);
1979    }
1980
1981    #[test]
1982    fn test_bracket_identifiers() {
1983        // SQL Server uses square brackets for identifiers
1984        let dialect = Dialect::get(DialectType::TSQL);
1985        let config = dialect.generator_config();
1986        assert_eq!(config.identifier_quote, '[');
1987    }
1988
1989    #[test]
1990    fn test_json_query_isnull_wrapper_simple() {
1991        // JSON_QUERY with two args needs ISNULL wrapper when transpiling to TSQL
1992        let dialect = Dialect::get(DialectType::TSQL);
1993        let result = dialect
1994            .transpile(r#"JSON_QUERY(x, '$')"#, DialectType::TSQL)
1995            .expect("transpile failed");
1996        assert!(
1997            result[0].contains("ISNULL"),
1998            "JSON_QUERY should be wrapped with ISNULL: {}",
1999            result[0]
2000        );
2001    }
2002
2003    #[test]
2004    fn test_json_query_isnull_wrapper_nested() {
2005        let dialect = Dialect::get(DialectType::TSQL);
2006        let result = dialect
2007            .transpile(
2008                r#"JSON_QUERY(REPLACE(REPLACE(x, '''', '"'), '""', '"'))"#,
2009                DialectType::TSQL,
2010            )
2011            .expect("transpile failed");
2012        let expected = r#"ISNULL(JSON_QUERY(REPLACE(REPLACE(x, '''', '"'), '""', '"'), '$'), JSON_VALUE(REPLACE(REPLACE(x, '''', '"'), '""', '"'), '$'))"#;
2013        assert_eq!(
2014            result[0], expected,
2015            "JSON_QUERY should be wrapped with ISNULL"
2016        );
2017    }
2018}