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