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