Skip to main content

polyglot_sql/dialects/
postgres.rs

1//! PostgreSQL Dialect
2//!
3//! PostgreSQL-specific transformations based on sqlglot patterns.
4//! Comprehensive batch translation from Python sqlglot postgres.py
5//!
6//! Key differences from other dialects:
7//! - TRY_CAST not supported (falls back to CAST)
8//! - RANDOM() instead of RAND()
9//! - STRING_AGG instead of GROUP_CONCAT
10//! - Bitwise XOR is # operator
11//! - BOOL_AND/BOOL_OR for logical aggregates
12//! - GEN_RANDOM_UUID() for UUID generation
13//! - UNNEST instead of EXPLODE
14//! - Type mappings: TINYINT→SMALLINT, FLOAT→REAL, DOUBLE→DOUBLE PRECISION, etc.
15//! - RegexpLike uses ~ operator, RegexpILike uses ~* operator
16//! - JSONB operators: #>, #>>, ?, ?|, ?&
17
18use super::{DialectImpl, DialectType};
19use crate::error::Result;
20use crate::expressions::{
21    AggFunc, AggregateFunction, BinaryOp, BooleanLiteral, Case, Cast, CeilFunc, DataType,
22    DateTimeField, Expression, ExtractFunc, Function, Interval, IntervalUnit, IntervalUnitSpec,
23    Join, JoinKind, Literal, Paren, UnaryFunc, VarArgFunc,
24};
25#[cfg(feature = "generate")]
26use crate::generator::GeneratorConfig;
27use crate::tokens::TokenizerConfig;
28
29/// Helper to wrap JSON arrow expressions in parentheses when they appear
30/// in contexts that require it (Binary, In, Not expressions)
31/// This matches Python sqlglot's WRAPPED_JSON_EXTRACT_EXPRESSIONS behavior
32fn wrap_if_json_arrow(expr: Expression) -> Expression {
33    match &expr {
34        Expression::JsonExtract(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
35            this: expr,
36            trailing_comments: Vec::new(),
37        })),
38        Expression::JsonExtractScalar(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
39            this: expr,
40            trailing_comments: Vec::new(),
41        })),
42        _ => expr,
43    }
44}
45
46/// PostgreSQL dialect
47pub struct PostgresDialect;
48
49impl DialectImpl for PostgresDialect {
50    fn dialect_type(&self) -> DialectType {
51        DialectType::PostgreSQL
52    }
53
54    fn tokenizer_config(&self) -> TokenizerConfig {
55        use crate::tokens::TokenType;
56        let mut config = TokenizerConfig::default();
57        // PostgreSQL supports $$ string literals (heredoc strings)
58        config.quotes.insert("$$".to_string(), "$$".to_string());
59        // PostgreSQL uses double quotes for identifiers
60        config.identifiers.insert('"', '"');
61        // Nested comments supported
62        config.nested_comments = true;
63        // PostgreSQL treats EXEC as a generic command (not TSQL EXEC statement)
64        // Note: EXECUTE is kept as-is since it's used in GRANT/REVOKE EXECUTE ON FUNCTION
65        config
66            .keywords
67            .insert("EXEC".to_string(), TokenType::Command);
68        for command in [
69            "BASE_BACKUP",
70            "CREATE_REPLICATION_SLOT",
71            "DROP_REPLICATION_SLOT",
72            "IDENTIFY_SYSTEM",
73            "READ_REPLICATION_SLOT",
74            "START_REPLICATION",
75            "TIMELINE_HISTORY",
76        ] {
77            config
78                .keywords
79                .insert(command.to_string(), TokenType::Command);
80        }
81        config
82    }
83
84    #[cfg(feature = "generate")]
85
86    fn generator_config(&self) -> GeneratorConfig {
87        use crate::generator::IdentifierQuoteStyle;
88        GeneratorConfig {
89            identifier_quote: '"',
90            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
91            dialect: Some(DialectType::PostgreSQL),
92            // PostgreSQL uses TIMESTAMPTZ shorthand
93            tz_to_with_time_zone: false,
94            // PostgreSQL prefers INTERVAL '1 day' syntax
95            single_string_interval: true,
96            // TABLESAMPLE uses REPEATABLE in PostgreSQL
97            tablesample_seed_keyword: "REPEATABLE",
98            // PostgreSQL doesn't support NVL2
99            nvl2_supported: false,
100            // PostgreSQL uses $ for parameters
101            parameter_token: "$",
102            // PostgreSQL uses % for named placeholders
103            named_placeholder_token: "%",
104            // PostgreSQL supports SELECT INTO
105            supports_select_into: true,
106            // PostgreSQL: USING btree(col) without space before parens
107            index_using_no_space: true,
108            // PostgreSQL supports UNLOGGED tables
109            supports_unlogged_tables: true,
110            // PostgreSQL doesn't support multi-arg DISTINCT
111            multi_arg_distinct: false,
112            // PostgreSQL uses ANY (subquery) with space
113            quantified_no_paren_space: false,
114            // PostgreSQL supports window EXCLUDE clause
115            supports_window_exclude: true,
116            // PostgreSQL normalizes single-bound window frames to BETWEEN form
117            normalize_window_frame_between: true,
118            // PostgreSQL COPY doesn't use INTO keyword
119            copy_has_into_keyword: false,
120            // PostgreSQL ARRAY_SIZE requires dimension argument
121            array_size_dim_required: Some(true),
122            // PostgreSQL supports BETWEEN flags
123            supports_between_flags: true,
124            // PostgreSQL doesn't support hints
125            join_hints: false,
126            table_hints: false,
127            query_hints: false,
128            // PostgreSQL supports locking reads
129            locking_reads_supported: true,
130            // PostgreSQL doesn't rename tables with DB
131            rename_table_with_db: false,
132            // PostgreSQL can implement array any
133            can_implement_array_any: true,
134            // PostgreSQL ARRAY_CONCAT is not var-len
135            array_concat_is_var_len: false,
136            // PostgreSQL doesn't support MEDIAN
137            supports_median: false,
138            // PostgreSQL requires JSON type for extraction
139            json_type_required_for_extraction: true,
140            // PostgreSQL LIKE property inside schema
141            like_property_inside_schema: true,
142            ..Default::default()
143        }
144    }
145
146    #[cfg(feature = "transpile")]
147
148    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
149        match expr {
150            // ============================================
151            // DATA TYPE MAPPINGS (from TYPE_MAPPING)
152            // These are handled specially - transform DataType variants
153            // ============================================
154            Expression::DataType(dt) => self.transform_data_type(dt),
155
156            // ============================================
157            // NULL HANDLING
158            // ============================================
159            // IFNULL -> COALESCE in PostgreSQL
160            Expression::IfNull(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
161                original_name: None,
162                expressions: vec![f.this, f.expression],
163                inferred_type: None,
164            }))),
165
166            // NVL -> COALESCE in PostgreSQL
167            Expression::Nvl(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
168                original_name: None,
169                expressions: vec![f.this, f.expression],
170                inferred_type: None,
171            }))),
172
173            // Coalesce with original_name (e.g., IFNULL parsed as Coalesce) -> clear original_name
174            // so it outputs as COALESCE instead of the original function name
175            Expression::Coalesce(mut f) => {
176                f.original_name = None;
177                Ok(Expression::Coalesce(f))
178            }
179
180            // ============================================
181            // CAST OPERATIONS
182            // ============================================
183            // TryCast -> CAST (PostgreSQL doesn't support TRY_CAST)
184            Expression::TryCast(c) => Ok(Expression::Cast(c)),
185
186            // SafeCast -> CAST (PostgreSQL doesn't support safe casts)
187            Expression::SafeCast(c) => Ok(Expression::Cast(c)),
188
189            // ============================================
190            // RANDOM
191            // ============================================
192            // RAND -> RANDOM in PostgreSQL
193            Expression::Rand(r) => {
194                // PostgreSQL's RANDOM() doesn't take a seed argument
195                let _ = r.seed; // Ignore seed
196                Ok(Expression::Random(crate::expressions::Random))
197            }
198
199            // ============================================
200            // UUID
201            // ============================================
202            // Uuid -> GEN_RANDOM_UUID in PostgreSQL
203            Expression::Uuid(_) => Ok(Expression::Function(Box::new(Function::new(
204                "GEN_RANDOM_UUID".to_string(),
205                vec![],
206            )))),
207
208            // ============================================
209            // ARRAY OPERATIONS
210            // ============================================
211            // EXPLODE -> UNNEST in PostgreSQL
212            Expression::Explode(f) => Ok(Expression::Unnest(Box::new(
213                crate::expressions::UnnestFunc {
214                    this: f.this,
215                    expressions: Vec::new(),
216                    with_ordinality: false,
217                    alias: None,
218                    offset_alias: None,
219                },
220            ))),
221
222            // ExplodeOuter -> UNNEST in PostgreSQL
223            Expression::ExplodeOuter(f) => Ok(Expression::Unnest(Box::new(
224                crate::expressions::UnnestFunc {
225                    this: f.this,
226                    expressions: Vec::new(),
227                    with_ordinality: false,
228                    alias: None,
229                    offset_alias: None,
230                },
231            ))),
232
233            // ArrayConcat -> ARRAY_CAT in PostgreSQL
234            Expression::ArrayConcat(f) => Ok(Expression::Function(Box::new(Function::new(
235                "ARRAY_CAT".to_string(),
236                f.expressions,
237            )))),
238
239            // ArrayPrepend -> ARRAY_PREPEND in PostgreSQL (note: args swapped from other dialects)
240            Expression::ArrayPrepend(f) => Ok(Expression::Function(Box::new(Function::new(
241                "ARRAY_PREPEND".to_string(),
242                vec![f.expression, f.this], // PostgreSQL: ARRAY_PREPEND(element, array)
243            )))),
244
245            // BitwiseAndAgg -> BIT_AND
246            Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(Function::new(
247                "BIT_AND".to_string(),
248                vec![f.this],
249            )))),
250
251            // BitwiseOrAgg -> BIT_OR
252            Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(Function::new(
253                "BIT_OR".to_string(),
254                vec![f.this],
255            )))),
256
257            // BitwiseXorAgg -> BIT_XOR
258            Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(Function::new(
259                "BIT_XOR".to_string(),
260                vec![f.this],
261            )))),
262
263            // ============================================
264            // BOOLEAN AGGREGATES
265            // ============================================
266            // LogicalAnd -> BOOL_AND
267            Expression::LogicalAnd(f) => {
268                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
269                    name: "BOOL_AND".to_string(),
270                    args: vec![f.this],
271                    distinct: f.distinct,
272                    filter: f.filter,
273                    order_by: f.order_by,
274                    limit: f.limit,
275                    ignore_nulls: f.ignore_nulls,
276                    inferred_type: f.inferred_type,
277                })))
278            }
279
280            // LogicalOr -> BOOL_OR
281            Expression::LogicalOr(f) => {
282                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
283                    name: "BOOL_OR".to_string(),
284                    args: vec![f.this],
285                    distinct: f.distinct,
286                    filter: f.filter,
287                    order_by: f.order_by,
288                    limit: f.limit,
289                    ignore_nulls: f.ignore_nulls,
290                    inferred_type: f.inferred_type,
291                })))
292            }
293
294            // Xor -> PostgreSQL bool_xor pattern: a <> b for boolean values
295            Expression::Xor(f) => {
296                if let (Some(a), Some(b)) = (f.this, f.expression) {
297                    Ok(Expression::Neq(Box::new(BinaryOp {
298                        left: *a,
299                        right: *b,
300                        left_comments: Vec::new(),
301                        operator_comments: Vec::new(),
302                        trailing_comments: Vec::new(),
303                        inferred_type: None,
304                    })))
305                } else {
306                    Ok(Expression::Boolean(BooleanLiteral { value: false }))
307                }
308            }
309
310            // ============================================
311            // REGEXP OPERATIONS (PostgreSQL uses ~ and ~* operators)
312            // ============================================
313            // RegexpLike -> keep as-is, generator handles ~ operator output
314            Expression::RegexpLike(f) => {
315                // Generator will output as: expr ~ pattern
316                Ok(Expression::RegexpLike(f))
317            }
318
319            // ============================================
320            // DATE/TIME FUNCTIONS
321            // ============================================
322            // DateAdd -> date + INTERVAL in PostgreSQL
323            Expression::DateAdd(f) => {
324                let is_literal = matches!(&f.interval, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_) | Literal::String(_)));
325                let right_expr = if is_literal {
326                    // Literal value: INTERVAL 'value' unit
327                    Expression::Interval(Box::new(Interval {
328                        this: Some(f.interval),
329                        unit: Some(IntervalUnitSpec::Simple {
330                            unit: f.unit,
331                            use_plural: false,
332                        }),
333                    }))
334                } else {
335                    // Non-literal (column ref, expression): INTERVAL '1 unit' * value
336                    let unit_str = match f.unit {
337                        IntervalUnit::Year => "YEAR",
338                        IntervalUnit::Quarter => "QUARTER",
339                        IntervalUnit::Month => "MONTH",
340                        IntervalUnit::Week => "WEEK",
341                        IntervalUnit::Day => "DAY",
342                        IntervalUnit::Hour => "HOUR",
343                        IntervalUnit::Minute => "MINUTE",
344                        IntervalUnit::Second => "SECOND",
345                        IntervalUnit::Millisecond => "MILLISECOND",
346                        IntervalUnit::Microsecond => "MICROSECOND",
347                        IntervalUnit::Nanosecond => "NANOSECOND",
348                    };
349                    let interval_one = Expression::Interval(Box::new(Interval {
350                        this: Some(Expression::Literal(Box::new(Literal::String(format!(
351                            "1 {unit_str}"
352                        ))))),
353                        unit: None,
354                    }));
355                    Expression::Mul(Box::new(BinaryOp {
356                        left: interval_one,
357                        right: f.interval,
358                        left_comments: Vec::new(),
359                        operator_comments: Vec::new(),
360                        trailing_comments: Vec::new(),
361                        inferred_type: None,
362                    }))
363                };
364                Ok(Expression::Add(Box::new(BinaryOp {
365                    left: f.this,
366                    right: right_expr,
367                    left_comments: Vec::new(),
368                    operator_comments: Vec::new(),
369                    trailing_comments: Vec::new(),
370                    inferred_type: None,
371                })))
372            }
373
374            // DateSub -> date - INTERVAL in PostgreSQL
375            Expression::DateSub(f) => {
376                let interval_expr = Expression::Interval(Box::new(Interval {
377                    this: Some(f.interval),
378                    unit: Some(IntervalUnitSpec::Simple {
379                        unit: f.unit,
380                        use_plural: false,
381                    }),
382                }));
383                Ok(Expression::Sub(Box::new(BinaryOp {
384                    left: f.this,
385                    right: interval_expr,
386                    left_comments: Vec::new(),
387                    operator_comments: Vec::new(),
388                    trailing_comments: Vec::new(),
389                    inferred_type: None,
390                })))
391            }
392
393            // DateDiff -> Complex PostgreSQL pattern using AGE/EXTRACT
394            Expression::DateDiff(f) => {
395                // For PostgreSQL, DATEDIFF is converted to EXTRACT(epoch FROM ...) pattern
396                // matching the 3-arg string-based DATEDIFF handler below
397                let unit = f.unit.unwrap_or(IntervalUnit::Day);
398
399                // Helper: CAST(expr AS TIMESTAMP)
400                let cast_ts = |e: Expression| -> Expression {
401                    Expression::Cast(Box::new(Cast {
402                        this: e,
403                        to: DataType::Timestamp {
404                            precision: None,
405                            timezone: false,
406                        },
407                        trailing_comments: Vec::new(),
408                        double_colon_syntax: false,
409                        format: None,
410                        default: None,
411                        inferred_type: None,
412                    }))
413                };
414
415                // Helper: CAST(expr AS BIGINT)
416                let cast_bigint = |e: Expression| -> Expression {
417                    Expression::Cast(Box::new(Cast {
418                        this: e,
419                        to: DataType::BigInt { length: None },
420                        trailing_comments: Vec::new(),
421                        double_colon_syntax: false,
422                        format: None,
423                        default: None,
424                        inferred_type: None,
425                    }))
426                };
427
428                // Clone end/start for reuse
429                let end_expr = f.this;
430                let start = f.expression;
431
432                // Helper: end_ts - start_ts
433                let ts_diff = || -> Expression {
434                    Expression::Sub(Box::new(BinaryOp::new(
435                        cast_ts(end_expr.clone()),
436                        cast_ts(start.clone()),
437                    )))
438                };
439
440                // Helper: AGE(end_ts, start_ts)
441                let age_call = || -> Expression {
442                    Expression::Function(Box::new(Function::new(
443                        "AGE".to_string(),
444                        vec![cast_ts(end_expr.clone()), cast_ts(start.clone())],
445                    )))
446                };
447
448                // Helper: EXTRACT(field FROM expr)
449                let extract = |field: DateTimeField, from: Expression| -> Expression {
450                    Expression::Extract(Box::new(ExtractFunc { this: from, field }))
451                };
452
453                // Helper: number literal
454                let num = |n: i64| -> Expression {
455                    Expression::Literal(Box::new(Literal::Number(n.to_string())))
456                };
457
458                let epoch_field = DateTimeField::Custom("epoch".to_string());
459
460                let result = match unit {
461                    IntervalUnit::Nanosecond => {
462                        let epoch = extract(epoch_field.clone(), ts_diff());
463                        cast_bigint(Expression::Mul(Box::new(BinaryOp::new(
464                            epoch,
465                            num(1000000000),
466                        ))))
467                    }
468                    IntervalUnit::Microsecond => {
469                        let epoch = extract(epoch_field, ts_diff());
470                        cast_bigint(Expression::Mul(Box::new(BinaryOp::new(
471                            epoch,
472                            num(1000000),
473                        ))))
474                    }
475                    IntervalUnit::Millisecond => {
476                        let epoch = extract(epoch_field, ts_diff());
477                        cast_bigint(Expression::Mul(Box::new(BinaryOp::new(epoch, num(1000)))))
478                    }
479                    IntervalUnit::Second => {
480                        let epoch = extract(epoch_field, ts_diff());
481                        cast_bigint(epoch)
482                    }
483                    IntervalUnit::Minute => {
484                        let epoch = extract(epoch_field, ts_diff());
485                        cast_bigint(Expression::Div(Box::new(BinaryOp::new(epoch, num(60)))))
486                    }
487                    IntervalUnit::Hour => {
488                        let epoch = extract(epoch_field, ts_diff());
489                        cast_bigint(Expression::Div(Box::new(BinaryOp::new(epoch, num(3600)))))
490                    }
491                    IntervalUnit::Day => {
492                        let epoch = extract(epoch_field, ts_diff());
493                        cast_bigint(Expression::Div(Box::new(BinaryOp::new(epoch, num(86400)))))
494                    }
495                    IntervalUnit::Week => {
496                        let diff_parens = Expression::Paren(Box::new(Paren {
497                            this: ts_diff(),
498                            trailing_comments: Vec::new(),
499                        }));
500                        let days = extract(DateTimeField::Custom("days".to_string()), diff_parens);
501                        cast_bigint(Expression::Div(Box::new(BinaryOp::new(days, num(7)))))
502                    }
503                    IntervalUnit::Month => {
504                        let year_part =
505                            extract(DateTimeField::Custom("year".to_string()), age_call());
506                        let month_part =
507                            extract(DateTimeField::Custom("month".to_string()), age_call());
508                        let year_months =
509                            Expression::Mul(Box::new(BinaryOp::new(year_part, num(12))));
510                        cast_bigint(Expression::Add(Box::new(BinaryOp::new(
511                            year_months,
512                            month_part,
513                        ))))
514                    }
515                    IntervalUnit::Quarter => {
516                        let year_part =
517                            extract(DateTimeField::Custom("year".to_string()), age_call());
518                        let month_part =
519                            extract(DateTimeField::Custom("month".to_string()), age_call());
520                        let year_quarters =
521                            Expression::Mul(Box::new(BinaryOp::new(year_part, num(4))));
522                        let month_quarters =
523                            Expression::Div(Box::new(BinaryOp::new(month_part, num(3))));
524                        cast_bigint(Expression::Add(Box::new(BinaryOp::new(
525                            year_quarters,
526                            month_quarters,
527                        ))))
528                    }
529                    IntervalUnit::Year => cast_bigint(extract(
530                        DateTimeField::Custom("year".to_string()),
531                        age_call(),
532                    )),
533                };
534                Ok(result)
535            }
536
537            // UnixToTime -> TO_TIMESTAMP
538            Expression::UnixToTime(f) => Ok(Expression::Function(Box::new(Function::new(
539                "TO_TIMESTAMP".to_string(),
540                vec![*f.this],
541            )))),
542
543            // TimeToUnix -> DATE_PART('epoch', ...) in PostgreSQL
544            Expression::TimeToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
545                "DATE_PART".to_string(),
546                vec![Expression::string("epoch"), f.this],
547            )))),
548
549            // StrToTime -> TO_TIMESTAMP in PostgreSQL
550            Expression::ToTimestamp(f) => {
551                let mut args = vec![f.this];
552                if let Some(fmt) = f.format {
553                    args.push(fmt);
554                }
555                Ok(Expression::Function(Box::new(Function::new(
556                    "TO_TIMESTAMP".to_string(),
557                    args,
558                ))))
559            }
560
561            // StrToDate -> TO_DATE in PostgreSQL
562            Expression::ToDate(f) => {
563                let mut args = vec![f.this];
564                if let Some(fmt) = f.format {
565                    args.push(fmt);
566                }
567                Ok(Expression::Function(Box::new(Function::new(
568                    "TO_DATE".to_string(),
569                    args,
570                ))))
571            }
572
573            // TimestampTrunc -> DATE_TRUNC
574            Expression::TimestampTrunc(f) => {
575                // Convert DateTimeField to string expression for DATE_TRUNC
576                let unit_str = format!("{:?}", f.unit).to_lowercase();
577                let args = vec![Expression::string(&unit_str), f.this];
578                Ok(Expression::Function(Box::new(Function::new(
579                    "DATE_TRUNC".to_string(),
580                    args,
581                ))))
582            }
583
584            // TimeFromParts -> MAKE_TIME
585            Expression::TimeFromParts(f) => {
586                let mut args = Vec::new();
587                if let Some(h) = f.hour {
588                    args.push(*h);
589                }
590                if let Some(m) = f.min {
591                    args.push(*m);
592                }
593                if let Some(s) = f.sec {
594                    args.push(*s);
595                }
596                Ok(Expression::Function(Box::new(Function::new(
597                    "MAKE_TIME".to_string(),
598                    args,
599                ))))
600            }
601
602            // TimestampFromParts -> MAKE_TIMESTAMP
603            Expression::MakeTimestamp(f) => {
604                // MakeTimestampFunc has direct Expression fields, not Options
605                let args = vec![f.year, f.month, f.day, f.hour, f.minute, f.second];
606                Ok(Expression::Function(Box::new(Function::new(
607                    "MAKE_TIMESTAMP".to_string(),
608                    args,
609                ))))
610            }
611
612            // ============================================
613            // STRING FUNCTIONS
614            // ============================================
615            // StringAgg is native to PostgreSQL - keep as-is
616            Expression::StringAgg(f) => Ok(Expression::StringAgg(f)),
617
618            // GroupConcat -> STRING_AGG in PostgreSQL
619            Expression::GroupConcat(f) => {
620                let mut args = vec![f.this.clone()];
621                if let Some(sep) = f.separator.clone() {
622                    args.push(sep);
623                } else {
624                    args.push(Expression::string(","));
625                }
626                Ok(Expression::Function(Box::new(Function::new(
627                    "STRING_AGG".to_string(),
628                    args,
629                ))))
630            }
631
632            // StrPosition -> POSITION function
633            Expression::Position(f) => {
634                // PostgreSQL: POSITION(substring IN string)
635                // Keep as Position, generator handles it
636                Ok(Expression::Position(f))
637            }
638
639            // ============================================
640            // AGGREGATE FUNCTIONS
641            // ============================================
642            // CountIf -> SUM(CASE WHEN condition THEN 1 ELSE 0 END) in PostgreSQL
643            Expression::CountIf(f) => {
644                let case_expr = Expression::Case(Box::new(Case {
645                    operand: None,
646                    whens: vec![(f.this.clone(), Expression::number(1))],
647                    else_: Some(Expression::number(0)),
648                    comments: Vec::new(),
649                    inferred_type: None,
650                }));
651                Ok(Expression::Sum(Box::new(AggFunc {
652                    ignore_nulls: None,
653                    having_max: None,
654                    this: case_expr,
655                    distinct: f.distinct,
656                    filter: f.filter,
657                    order_by: Vec::new(),
658                    name: None,
659                    limit: None,
660                    inferred_type: None,
661                })))
662            }
663
664            // AnyValue -> keep as ANY_VALUE for PostgreSQL (supported since PG 16)
665            Expression::AnyValue(f) => Ok(Expression::AnyValue(f)),
666
667            // Variance -> VAR_SAMP in PostgreSQL
668            Expression::Variance(f) => Ok(Expression::Function(Box::new(Function::new(
669                "VAR_SAMP".to_string(),
670                vec![f.this],
671            )))),
672
673            // VarPop -> VAR_POP in PostgreSQL
674            Expression::VarPop(f) => Ok(Expression::Function(Box::new(Function::new(
675                "VAR_POP".to_string(),
676                vec![f.this],
677            )))),
678
679            // ============================================
680            // JSON FUNCTIONS
681            // ============================================
682            // JSONExtract -> use arrow syntax (->) in PostgreSQL for simple literal paths
683            // Complex paths (like column references) should use JSON_EXTRACT_PATH function
684            Expression::JsonExtract(mut f) => {
685                // Only use arrow syntax for simple literal paths (string or non-negative number)
686                // Complex expressions like column references should use function form
687                f.arrow_syntax = Self::is_simple_json_path(&f.path);
688                Ok(Expression::JsonExtract(f))
689            }
690
691            // JSONExtractScalar -> use arrow syntax (->>) in PostgreSQL for simple paths
692            // Complex paths (like negative indices) should use JSON_EXTRACT_PATH_TEXT function
693            // #>> (hash_arrow_syntax) stays as #>>
694            Expression::JsonExtractScalar(mut f) => {
695                if !f.hash_arrow_syntax {
696                    // Only use arrow syntax for simple literal paths (string or non-negative number)
697                    // Complex expressions like Neg(-1) should use function form
698                    f.arrow_syntax = Self::is_simple_json_path(&f.path);
699                }
700                Ok(Expression::JsonExtractScalar(f))
701            }
702
703            // ParseJson: handled by generator (outputs CAST(x AS JSON) for PostgreSQL)
704
705            // JSONObjectAgg -> JSON_OBJECT_AGG
706            Expression::JsonObjectAgg(f) => {
707                // JsonObjectAggFunc has key and value as Expression, not Option
708                let args = vec![f.key, f.value];
709                Ok(Expression::Function(Box::new(Function::new(
710                    "JSON_OBJECT_AGG".to_string(),
711                    args,
712                ))))
713            }
714
715            // JSONArrayAgg -> JSON_AGG
716            Expression::JsonArrayAgg(f) => Ok(Expression::Function(Box::new(Function::new(
717                "JSON_AGG".to_string(),
718                vec![f.this],
719            )))),
720
721            // JSONPathRoot -> empty string ($ is implicit in PostgreSQL)
722            Expression::JSONPathRoot(_) => Ok(Expression::Literal(Box::new(Literal::String(
723                String::new(),
724            )))),
725
726            // ============================================
727            // MISC FUNCTIONS
728            // ============================================
729            // IntDiv -> DIV in PostgreSQL
730            Expression::IntDiv(f) => Ok(Expression::Function(Box::new(Function::new(
731                "DIV".to_string(),
732                vec![f.this, f.expression],
733            )))),
734
735            // Unicode -> ASCII in PostgreSQL
736            Expression::Unicode(f) => Ok(Expression::Function(Box::new(Function::new(
737                "ASCII".to_string(),
738                vec![f.this],
739            )))),
740
741            // LastDay -> Complex expression (PostgreSQL doesn't have LAST_DAY)
742            Expression::LastDay(f) => {
743                // (DATE_TRUNC('month', date) + INTERVAL '1 month' - INTERVAL '1 day')::DATE
744                let truncated = Expression::Function(Box::new(Function::new(
745                    "DATE_TRUNC".to_string(),
746                    vec![Expression::string("month"), f.this.clone()],
747                )));
748                let plus_month = Expression::Add(Box::new(BinaryOp {
749                    left: truncated,
750                    right: Expression::Interval(Box::new(Interval {
751                        this: Some(Expression::string("1")),
752                        unit: Some(IntervalUnitSpec::Simple {
753                            unit: IntervalUnit::Month,
754                            use_plural: false,
755                        }),
756                    })),
757                    left_comments: Vec::new(),
758                    operator_comments: Vec::new(),
759                    trailing_comments: Vec::new(),
760                    inferred_type: None,
761                }));
762                let minus_day = Expression::Sub(Box::new(BinaryOp {
763                    left: plus_month,
764                    right: Expression::Interval(Box::new(Interval {
765                        this: Some(Expression::string("1")),
766                        unit: Some(IntervalUnitSpec::Simple {
767                            unit: IntervalUnit::Day,
768                            use_plural: false,
769                        }),
770                    })),
771                    left_comments: Vec::new(),
772                    operator_comments: Vec::new(),
773                    trailing_comments: Vec::new(),
774                    inferred_type: None,
775                }));
776                Ok(Expression::Cast(Box::new(Cast {
777                    this: minus_day,
778                    to: DataType::Date,
779                    trailing_comments: Vec::new(),
780                    double_colon_syntax: true, // Use PostgreSQL :: syntax
781                    format: None,
782                    default: None,
783                    inferred_type: None,
784                })))
785            }
786
787            // GenerateSeries is native to PostgreSQL
788            Expression::GenerateSeries(f) => Ok(Expression::GenerateSeries(f)),
789
790            // ExplodingGenerateSeries -> GENERATE_SERIES
791            Expression::ExplodingGenerateSeries(f) => {
792                let mut args = vec![f.start, f.stop];
793                if let Some(step) = f.step {
794                    args.push(step); // step is Expression, not Box<Expression>
795                }
796                Ok(Expression::Function(Box::new(Function::new(
797                    "GENERATE_SERIES".to_string(),
798                    args,
799                ))))
800            }
801
802            // ============================================
803            // SESSION/TIME FUNCTIONS (no parentheses in PostgreSQL)
804            // ============================================
805            // CurrentTimestamp -> CURRENT_TIMESTAMP (no parens)
806            Expression::CurrentTimestamp(_) => Ok(Expression::Function(Box::new(Function {
807                name: "CURRENT_TIMESTAMP".to_string(),
808                args: vec![],
809                distinct: false,
810                trailing_comments: vec![],
811                use_bracket_syntax: false,
812                no_parens: true,
813                quoted: false,
814                span: None,
815                inferred_type: None,
816            }))),
817
818            // CurrentUser -> CURRENT_USER (no parens)
819            Expression::CurrentUser(_) => Ok(Expression::Function(Box::new(Function::new(
820                "CURRENT_USER".to_string(),
821                vec![],
822            )))),
823
824            // CurrentDate -> CURRENT_DATE (no parens)
825            Expression::CurrentDate(_) => Ok(Expression::Function(Box::new(Function {
826                name: "CURRENT_DATE".to_string(),
827                args: vec![],
828                distinct: false,
829                trailing_comments: vec![],
830                use_bracket_syntax: false,
831                no_parens: true,
832                quoted: false,
833                span: None,
834                inferred_type: None,
835            }))),
836
837            // ============================================
838            // JOIN TRANSFORMATIONS
839            // ============================================
840            // CROSS APPLY -> INNER JOIN LATERAL ... ON TRUE in PostgreSQL
841            Expression::Join(join) if join.kind == JoinKind::CrossApply => {
842                Ok(Expression::Join(Box::new(Join {
843                    this: join.this,
844                    on: Some(Expression::Boolean(BooleanLiteral { value: true })),
845                    using: join.using,
846                    kind: JoinKind::CrossApply,
847                    use_inner_keyword: false,
848                    use_outer_keyword: false,
849                    deferred_condition: false,
850                    join_hint: None,
851                    match_condition: None,
852                    pivots: join.pivots,
853                    comments: join.comments,
854                    nesting_group: 0,
855                    directed: false,
856                })))
857            }
858
859            // OUTER APPLY -> LEFT JOIN LATERAL ... ON TRUE in PostgreSQL
860            Expression::Join(join) if join.kind == JoinKind::OuterApply => {
861                Ok(Expression::Join(Box::new(Join {
862                    this: join.this,
863                    on: Some(Expression::Boolean(BooleanLiteral { value: true })),
864                    using: join.using,
865                    kind: JoinKind::OuterApply,
866                    use_inner_keyword: false,
867                    use_outer_keyword: false,
868                    deferred_condition: false,
869                    join_hint: None,
870                    match_condition: None,
871                    pivots: join.pivots,
872                    comments: join.comments,
873                    nesting_group: 0,
874                    directed: false,
875                })))
876            }
877
878            // ============================================
879            // GENERIC FUNCTION TRANSFORMATIONS
880            // ============================================
881            Expression::Function(f) => self.transform_function(*f),
882
883            // Generic aggregate function transformations
884            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
885
886            // ===== Context-aware JSON arrow wrapping =====
887            // When JSON arrow expressions appear in Binary/In/Not contexts,
888            // they need to be wrapped in parentheses for correct precedence.
889            // This matches Python sqlglot's WRAPPED_JSON_EXTRACT_EXPRESSIONS behavior.
890
891            // Binary operators that need JSON wrapping
892            Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
893                left: wrap_if_json_arrow(op.left),
894                right: wrap_if_json_arrow(op.right),
895                ..*op
896            }))),
897            Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
898                left: wrap_if_json_arrow(op.left),
899                right: wrap_if_json_arrow(op.right),
900                ..*op
901            }))),
902            Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
903                left: wrap_if_json_arrow(op.left),
904                right: wrap_if_json_arrow(op.right),
905                ..*op
906            }))),
907            Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
908                left: wrap_if_json_arrow(op.left),
909                right: wrap_if_json_arrow(op.right),
910                ..*op
911            }))),
912            Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
913                left: wrap_if_json_arrow(op.left),
914                right: wrap_if_json_arrow(op.right),
915                ..*op
916            }))),
917            Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
918                left: wrap_if_json_arrow(op.left),
919                right: wrap_if_json_arrow(op.right),
920                ..*op
921            }))),
922
923            // In expression - wrap the this part if it's JSON arrow
924            Expression::In(mut i) => {
925                i.this = wrap_if_json_arrow(i.this);
926                Ok(Expression::In(i))
927            }
928
929            // Not expression - wrap the this part if it's JSON arrow
930            Expression::Not(mut n) => {
931                n.this = wrap_if_json_arrow(n.this);
932                Ok(Expression::Not(n))
933            }
934
935            // MERGE: qualifier stripping is handled by the generator (dialect-aware)
936            // PostgreSQL generator strips qualifiers, Snowflake generator keeps them
937            Expression::Merge(m) => Ok(Expression::Merge(m)),
938
939            // JSONExtract with variant_extract (Databricks colon syntax) -> JSON_EXTRACT_PATH
940            Expression::JSONExtract(je) if je.variant_extract.is_some() => {
941                // Convert path from bracketed format to simple key
942                // e.g., '["fr''uit"]' -> 'fr''uit'
943                let path = match *je.expression {
944                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
945                        let Literal::String(s) = lit.as_ref() else {
946                            unreachable!()
947                        };
948                        // Strip bracketed JSON path format: ["key"] -> key
949                        let cleaned = if s.starts_with("[\"") && s.ends_with("\"]") {
950                            s[2..s.len() - 2].to_string()
951                        } else {
952                            s.clone()
953                        };
954                        Expression::Literal(Box::new(Literal::String(cleaned)))
955                    }
956                    other => other,
957                };
958                Ok(Expression::Function(Box::new(Function::new(
959                    "JSON_EXTRACT_PATH".to_string(),
960                    vec![*je.this, path],
961                ))))
962            }
963
964            // TRIM(str, chars) -> TRIM(chars FROM str) for PostgreSQL SQL standard syntax
965            Expression::Trim(t) if !t.sql_standard_syntax && t.characters.is_some() => {
966                Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
967                    this: t.this,
968                    characters: t.characters,
969                    position: t.position,
970                    sql_standard_syntax: true,
971                    position_explicit: t.position_explicit,
972                })))
973            }
974
975            // b'a' -> CAST(e'a' AS BYTEA) for PostgreSQL
976            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::ByteString(_)) => {
977                let Literal::ByteString(s) = lit.as_ref() else {
978                    unreachable!()
979                };
980                Ok(Expression::Cast(Box::new(Cast {
981                    this: Expression::Literal(Box::new(Literal::EscapeString(s.clone()))),
982                    to: DataType::VarBinary { length: None },
983                    trailing_comments: Vec::new(),
984                    double_colon_syntax: false,
985                    format: None,
986                    default: None,
987                    inferred_type: None,
988                })))
989            }
990
991            // Pass through everything else
992            _ => Ok(expr),
993        }
994    }
995}
996
997#[cfg(feature = "transpile")]
998impl PostgresDialect {
999    /// Check if a JSON path expression is "simple" (string literal or non-negative integer)
1000    /// Simple paths can use arrow syntax (->>) in PostgreSQL
1001    /// Complex paths (like negative indices) should use JSON_EXTRACT_PATH_TEXT function
1002    fn is_simple_json_path(path: &Expression) -> bool {
1003        match path {
1004            // String literals are always simple
1005            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => true,
1006            // Non-negative integer literals are simple
1007            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1008                let Literal::Number(n) = lit.as_ref() else {
1009                    unreachable!()
1010                };
1011                // Check if it's non-negative
1012                !n.starts_with('-')
1013            }
1014            // JSONPath expressions are simple (they're already parsed paths)
1015            Expression::JSONPath(_) => true,
1016            // Everything else (Neg, function calls, etc.) is complex
1017            _ => false,
1018        }
1019    }
1020
1021    /// Transform data types according to PostgreSQL TYPE_MAPPING
1022    fn transform_data_type(&self, dt: DataType) -> Result<Expression> {
1023        let transformed = match dt {
1024            // TINYINT -> SMALLINT
1025            DataType::TinyInt { .. } => DataType::SmallInt { length: None },
1026
1027            // REAL stays single precision; FLOAT without precision maps to DOUBLE PRECISION.
1028            DataType::Float { real_spelling, .. } => DataType::Custom {
1029                name: if real_spelling {
1030                    "REAL".to_string()
1031                } else {
1032                    "DOUBLE PRECISION".to_string()
1033                },
1034            },
1035
1036            // DOUBLE -> DOUBLE PRECISION
1037            DataType::Double { .. } => DataType::Custom {
1038                name: "DOUBLE PRECISION".to_string(),
1039            },
1040
1041            // BINARY -> BYTEA (handled by generator which preserves length)
1042            DataType::Binary { .. } => dt,
1043
1044            // VARBINARY -> BYTEA (handled by generator which preserves length)
1045            DataType::VarBinary { .. } => dt,
1046
1047            // BLOB -> BYTEA
1048            DataType::Blob => DataType::Custom {
1049                name: "BYTEA".to_string(),
1050            },
1051
1052            // Custom type normalizations
1053            DataType::Custom { ref name } => {
1054                let upper = name.to_uppercase();
1055                match upper.as_str() {
1056                    // INT8 -> BIGINT (PostgreSQL alias)
1057                    "INT8" => DataType::BigInt { length: None },
1058                    // FLOAT8 -> DOUBLE PRECISION (PostgreSQL alias)
1059                    "FLOAT8" => DataType::Custom {
1060                        name: "DOUBLE PRECISION".to_string(),
1061                    },
1062                    // FLOAT4 -> REAL (PostgreSQL alias)
1063                    "FLOAT4" => DataType::Custom {
1064                        name: "REAL".to_string(),
1065                    },
1066                    // INT4 -> INTEGER (PostgreSQL alias)
1067                    "INT4" => DataType::Int {
1068                        length: None,
1069                        integer_spelling: false,
1070                    },
1071                    // INT2 -> SMALLINT (PostgreSQL alias)
1072                    "INT2" => DataType::SmallInt { length: None },
1073                    _ => dt,
1074                }
1075            }
1076
1077            // Keep all other types as-is
1078            other => other,
1079        };
1080        Ok(Expression::DataType(transformed))
1081    }
1082
1083    fn transform_function(&self, f: Function) -> Result<Expression> {
1084        let name_upper = f.name.to_uppercase();
1085        match name_upper.as_str() {
1086            // IFNULL -> COALESCE
1087            "IFNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1088                original_name: None,
1089                expressions: f.args,
1090                inferred_type: None,
1091            }))),
1092
1093            // NVL -> COALESCE
1094            "NVL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1095                original_name: None,
1096                expressions: f.args,
1097                inferred_type: None,
1098            }))),
1099
1100            // ISNULL (SQL Server) -> COALESCE
1101            "ISNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1102                original_name: None,
1103                expressions: f.args,
1104                inferred_type: None,
1105            }))),
1106
1107            // GROUP_CONCAT -> STRING_AGG in PostgreSQL
1108            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1109                Function::new("STRING_AGG".to_string(), f.args),
1110            ))),
1111
1112            // SUBSTR -> SUBSTRING (standard SQL)
1113            "SUBSTR" => Ok(Expression::Function(Box::new(Function::new(
1114                "SUBSTRING".to_string(),
1115                f.args,
1116            )))),
1117
1118            // RAND -> RANDOM in PostgreSQL
1119            "RAND" => Ok(Expression::Random(crate::expressions::Random)),
1120
1121            // CEILING -> CEIL (both work in PostgreSQL, but CEIL is preferred)
1122            "CEILING" if f.args.len() == 1 => Ok(Expression::Ceil(Box::new(CeilFunc {
1123                this: f.args.into_iter().next().unwrap(),
1124                decimals: None,
1125                to: None,
1126            }))),
1127
1128            // LEN -> LENGTH in PostgreSQL
1129            "LEN" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc {
1130                this: f.args.into_iter().next().unwrap(),
1131                original_name: None,
1132                inferred_type: None,
1133            }))),
1134
1135            // CHAR_LENGTH -> LENGTH in PostgreSQL
1136            "CHAR_LENGTH" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc {
1137                this: f.args.into_iter().next().unwrap(),
1138                original_name: None,
1139                inferred_type: None,
1140            }))),
1141
1142            // CHARACTER_LENGTH -> LENGTH in PostgreSQL
1143            "CHARACTER_LENGTH" if f.args.len() == 1 => {
1144                Ok(Expression::Length(Box::new(UnaryFunc {
1145                    this: f.args.into_iter().next().unwrap(),
1146                    original_name: None,
1147                    inferred_type: None,
1148                })))
1149            }
1150
1151            // CHARINDEX -> POSITION in PostgreSQL
1152            // CHARINDEX(substring, string) -> POSITION(substring IN string)
1153            "CHARINDEX" if f.args.len() >= 2 => {
1154                let mut args = f.args;
1155                let substring = args.remove(0);
1156                let string = args.remove(0);
1157                Ok(Expression::Position(Box::new(
1158                    crate::expressions::PositionFunc {
1159                        substring,
1160                        string,
1161                        start: args.pop(),
1162                    },
1163                )))
1164            }
1165
1166            // GETDATE -> CURRENT_TIMESTAMP in PostgreSQL
1167            "GETDATE" => Ok(Expression::CurrentTimestamp(
1168                crate::expressions::CurrentTimestamp {
1169                    precision: None,
1170                    sysdate: false,
1171                },
1172            )),
1173
1174            // SYSDATETIME -> CURRENT_TIMESTAMP in PostgreSQL
1175            "SYSDATETIME" => Ok(Expression::CurrentTimestamp(
1176                crate::expressions::CurrentTimestamp {
1177                    precision: None,
1178                    sysdate: false,
1179                },
1180            )),
1181
1182            // NOW -> CURRENT_TIMESTAMP in PostgreSQL (NOW() is also valid)
1183            "NOW" => Ok(Expression::CurrentTimestamp(
1184                crate::expressions::CurrentTimestamp {
1185                    precision: None,
1186                    sysdate: false,
1187                },
1188            )),
1189
1190            // PostgreSQL random UUID generators -> normalized UUID expression.
1191            "GEN_RANDOM_UUID" | "UUID_GENERATE_V4" | "UUIDV4" if f.args.is_empty() => {
1192                Ok(Expression::Uuid(Box::new(crate::expressions::Uuid {
1193                    this: None,
1194                    name: None,
1195                    is_string: None,
1196                })))
1197            }
1198
1199            // NEWID -> GEN_RANDOM_UUID in PostgreSQL
1200            "NEWID" => Ok(Expression::Function(Box::new(Function::new(
1201                "GEN_RANDOM_UUID".to_string(),
1202                vec![],
1203            )))),
1204
1205            // UUID() -> GEN_RANDOM_UUID in PostgreSQL
1206            "UUID" if f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1207                "GEN_RANDOM_UUID".to_string(),
1208                vec![],
1209            )))),
1210
1211            // UNNEST is native to PostgreSQL
1212            "UNNEST" => Ok(Expression::Function(Box::new(f))),
1213
1214            // GENERATE_SERIES is native to PostgreSQL
1215            "GENERATE_SERIES" => Ok(Expression::Function(Box::new(f))),
1216
1217            // SHA256 -> SHA256 in PostgreSQL (via pgcrypto extension)
1218            "SHA256" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
1219                "SHA256".to_string(),
1220                f.args,
1221            )))),
1222
1223            // SHA2 -> SHA256/SHA512 based on length argument
1224            "SHA2" if f.args.len() == 2 => {
1225                // SHA2(data, length) -> SHA256/SHA384/SHA512
1226                let args = f.args;
1227                let data = args[0].clone();
1228                // Default to SHA256 - would need runtime inspection for exact mapping
1229                Ok(Expression::Function(Box::new(Function::new(
1230                    "SHA256".to_string(),
1231                    vec![data],
1232                ))))
1233            }
1234
1235            // LEVENSHTEIN is native to PostgreSQL (fuzzystrmatch extension)
1236            "LEVENSHTEIN" => Ok(Expression::Function(Box::new(f))),
1237
1238            // EDITDISTANCE -> LEVENSHTEIN_LESS_EQUAL (with max distance) or LEVENSHTEIN
1239            "EDITDISTANCE" if f.args.len() == 3 => Ok(Expression::Function(Box::new(
1240                Function::new("LEVENSHTEIN_LESS_EQUAL".to_string(), f.args),
1241            ))),
1242            "EDITDISTANCE" if f.args.len() == 2 => Ok(Expression::Function(Box::new(
1243                Function::new("LEVENSHTEIN".to_string(), f.args),
1244            ))),
1245
1246            // TRIM(value, chars) -> TRIM(chars FROM value) for Postgres
1247            "TRIM" if f.args.len() == 2 => {
1248                let value = f.args[0].clone();
1249                let chars = f.args[1].clone();
1250                Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
1251                    this: value,
1252                    characters: Some(chars),
1253                    position: crate::expressions::TrimPosition::Both,
1254                    sql_standard_syntax: true,
1255                    position_explicit: false,
1256                })))
1257            }
1258
1259            // DATEDIFF(unit, start, end) -> PostgreSQL EXTRACT/AGE patterns
1260            "DATEDIFF" if f.args.len() >= 2 => {
1261                let mut args = f.args;
1262                if args.len() == 2 {
1263                    // 2-arg form: DATEDIFF(start, end) -> AGE(start, end)
1264                    let first = args.remove(0);
1265                    let second = args.remove(0);
1266                    Ok(Expression::Function(Box::new(Function::new(
1267                        "AGE".to_string(),
1268                        vec![first, second],
1269                    ))))
1270                } else {
1271                    // 3-arg form: DATEDIFF(unit, start, end)
1272                    let unit_expr = args.remove(0);
1273                    let start = args.remove(0);
1274                    let end_expr = args.remove(0);
1275
1276                    // Extract unit name from identifier or column
1277                    let unit_name = match &unit_expr {
1278                        Expression::Identifier(id) => id.name.to_uppercase(),
1279                        Expression::Var(v) => v.this.to_uppercase(),
1280                        Expression::Column(col) if col.table.is_none() => {
1281                            col.name.name.to_uppercase()
1282                        }
1283                        _ => "DAY".to_string(),
1284                    };
1285
1286                    // Helper: CAST(expr AS TIMESTAMP)
1287                    let cast_ts = |e: Expression| -> Expression {
1288                        Expression::Cast(Box::new(Cast {
1289                            this: e,
1290                            to: DataType::Timestamp {
1291                                precision: None,
1292                                timezone: false,
1293                            },
1294                            trailing_comments: Vec::new(),
1295                            double_colon_syntax: false,
1296                            format: None,
1297                            default: None,
1298                            inferred_type: None,
1299                        }))
1300                    };
1301
1302                    // Helper: CAST(expr AS BIGINT)
1303                    let cast_bigint = |e: Expression| -> Expression {
1304                        Expression::Cast(Box::new(Cast {
1305                            this: e,
1306                            to: DataType::BigInt { length: None },
1307                            trailing_comments: Vec::new(),
1308                            double_colon_syntax: false,
1309                            format: None,
1310                            default: None,
1311                            inferred_type: None,
1312                        }))
1313                    };
1314
1315                    let end_ts = cast_ts(end_expr.clone());
1316                    let start_ts = cast_ts(start.clone());
1317
1318                    // Helper: end_ts - start_ts
1319                    let ts_diff = || -> Expression {
1320                        Expression::Sub(Box::new(BinaryOp::new(
1321                            cast_ts(end_expr.clone()),
1322                            cast_ts(start.clone()),
1323                        )))
1324                    };
1325
1326                    // Helper: AGE(end_ts, start_ts)
1327                    let age_call = || -> Expression {
1328                        Expression::Function(Box::new(Function::new(
1329                            "AGE".to_string(),
1330                            vec![cast_ts(end_expr.clone()), cast_ts(start.clone())],
1331                        )))
1332                    };
1333
1334                    // Helper: EXTRACT(field FROM expr)
1335                    let extract = |field: DateTimeField, from: Expression| -> Expression {
1336                        Expression::Extract(Box::new(ExtractFunc { this: from, field }))
1337                    };
1338
1339                    // Helper: number literal
1340                    let num = |n: i64| -> Expression {
1341                        Expression::Literal(Box::new(Literal::Number(n.to_string())))
1342                    };
1343
1344                    // Use Custom DateTimeField for lowercase output (PostgreSQL convention)
1345                    let epoch_field = DateTimeField::Custom("epoch".to_string());
1346
1347                    let result = match unit_name.as_str() {
1348                        "MICROSECOND" => {
1349                            // CAST(EXTRACT(epoch FROM end_ts - start_ts) * 1000000 AS BIGINT)
1350                            let epoch = extract(epoch_field, ts_diff());
1351                            cast_bigint(Expression::Mul(Box::new(BinaryOp::new(
1352                                epoch,
1353                                num(1000000),
1354                            ))))
1355                        }
1356                        "MILLISECOND" => {
1357                            let epoch = extract(epoch_field, ts_diff());
1358                            cast_bigint(Expression::Mul(Box::new(BinaryOp::new(epoch, num(1000)))))
1359                        }
1360                        "SECOND" => {
1361                            let epoch = extract(epoch_field, ts_diff());
1362                            cast_bigint(epoch)
1363                        }
1364                        "MINUTE" => {
1365                            let epoch = extract(epoch_field, ts_diff());
1366                            cast_bigint(Expression::Div(Box::new(BinaryOp::new(epoch, num(60)))))
1367                        }
1368                        "HOUR" => {
1369                            let epoch = extract(epoch_field, ts_diff());
1370                            cast_bigint(Expression::Div(Box::new(BinaryOp::new(epoch, num(3600)))))
1371                        }
1372                        "DAY" => {
1373                            let epoch = extract(epoch_field, ts_diff());
1374                            cast_bigint(Expression::Div(Box::new(BinaryOp::new(epoch, num(86400)))))
1375                        }
1376                        "WEEK" => {
1377                            // CAST(EXTRACT(days FROM (end_ts - start_ts)) / 7 AS BIGINT)
1378                            let diff_parens = Expression::Paren(Box::new(Paren {
1379                                this: ts_diff(),
1380                                trailing_comments: Vec::new(),
1381                            }));
1382                            let days =
1383                                extract(DateTimeField::Custom("days".to_string()), diff_parens);
1384                            cast_bigint(Expression::Div(Box::new(BinaryOp::new(days, num(7)))))
1385                        }
1386                        "MONTH" => {
1387                            // CAST(EXTRACT(year FROM AGE(...)) * 12 + EXTRACT(month FROM AGE(...)) AS BIGINT)
1388                            let year_part =
1389                                extract(DateTimeField::Custom("year".to_string()), age_call());
1390                            let month_part =
1391                                extract(DateTimeField::Custom("month".to_string()), age_call());
1392                            let year_months =
1393                                Expression::Mul(Box::new(BinaryOp::new(year_part, num(12))));
1394                            cast_bigint(Expression::Add(Box::new(BinaryOp::new(
1395                                year_months,
1396                                month_part,
1397                            ))))
1398                        }
1399                        "QUARTER" => {
1400                            // CAST(EXTRACT(year FROM AGE(...)) * 4 + EXTRACT(month FROM AGE(...)) / 3 AS BIGINT)
1401                            let year_part =
1402                                extract(DateTimeField::Custom("year".to_string()), age_call());
1403                            let month_part =
1404                                extract(DateTimeField::Custom("month".to_string()), age_call());
1405                            let year_quarters =
1406                                Expression::Mul(Box::new(BinaryOp::new(year_part, num(4))));
1407                            let month_quarters =
1408                                Expression::Div(Box::new(BinaryOp::new(month_part, num(3))));
1409                            cast_bigint(Expression::Add(Box::new(BinaryOp::new(
1410                                year_quarters,
1411                                month_quarters,
1412                            ))))
1413                        }
1414                        "YEAR" => {
1415                            // CAST(EXTRACT(year FROM AGE(...)) AS BIGINT)
1416                            cast_bigint(extract(
1417                                DateTimeField::Custom("year".to_string()),
1418                                age_call(),
1419                            ))
1420                        }
1421                        _ => {
1422                            // Fallback: simple AGE
1423                            Expression::Function(Box::new(Function::new(
1424                                "AGE".to_string(),
1425                                vec![end_ts, start_ts],
1426                            )))
1427                        }
1428                    };
1429                    Ok(result)
1430                }
1431            }
1432
1433            // TIMESTAMPDIFF -> AGE or EXTRACT pattern
1434            "TIMESTAMPDIFF" if f.args.len() >= 3 => {
1435                let mut args = f.args;
1436                let _unit = args.remove(0); // Unit (ignored, AGE returns full interval)
1437                let start = args.remove(0);
1438                let end = args.remove(0);
1439                Ok(Expression::Function(Box::new(Function::new(
1440                    "AGE".to_string(),
1441                    vec![end, start],
1442                ))))
1443            }
1444
1445            // FROM_UNIXTIME -> TO_TIMESTAMP
1446            "FROM_UNIXTIME" => Ok(Expression::Function(Box::new(Function::new(
1447                "TO_TIMESTAMP".to_string(),
1448                f.args,
1449            )))),
1450
1451            // UNIX_TIMESTAMP -> EXTRACT(EPOCH FROM ...)
1452            "UNIX_TIMESTAMP" if f.args.len() == 1 => {
1453                let arg = f.args.into_iter().next().unwrap();
1454                Ok(Expression::Function(Box::new(Function::new(
1455                    "DATE_PART".to_string(),
1456                    vec![Expression::string("epoch"), arg],
1457                ))))
1458            }
1459
1460            // UNIX_TIMESTAMP() with no args -> EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)
1461            "UNIX_TIMESTAMP" if f.args.is_empty() => {
1462                Ok(Expression::Function(Box::new(Function::new(
1463                    "DATE_PART".to_string(),
1464                    vec![
1465                        Expression::string("epoch"),
1466                        Expression::CurrentTimestamp(crate::expressions::CurrentTimestamp {
1467                            precision: None,
1468                            sysdate: false,
1469                        }),
1470                    ],
1471                ))))
1472            }
1473
1474            // DATEADD -> date + interval pattern
1475            "DATEADD" if f.args.len() == 3 => {
1476                // DATEADD(unit, count, date) -> date + interval 'count unit'
1477                // This is a simplified version - full impl would construct proper interval
1478                let mut args = f.args;
1479                let _unit = args.remove(0);
1480                let count = args.remove(0);
1481                let date = args.remove(0);
1482                Ok(Expression::Add(Box::new(BinaryOp {
1483                    left: date,
1484                    right: count,
1485                    left_comments: Vec::new(),
1486                    operator_comments: Vec::new(),
1487                    trailing_comments: Vec::new(),
1488                    inferred_type: None,
1489                })))
1490            }
1491
1492            // INSTR -> POSITION (simplified)
1493            "INSTR" if f.args.len() >= 2 => {
1494                let mut args = f.args;
1495                let string = args.remove(0);
1496                let substring = args.remove(0);
1497                Ok(Expression::Position(Box::new(
1498                    crate::expressions::PositionFunc {
1499                        substring,
1500                        string,
1501                        start: args.pop(),
1502                    },
1503                )))
1504            }
1505
1506            // CONCAT_WS is native to PostgreSQL
1507            "CONCAT_WS" => Ok(Expression::Function(Box::new(f))),
1508
1509            // REGEXP_REPLACE: pass through without adding 'g' flag
1510            // The 'g' flag handling is managed by cross_dialect_normalize based on source dialect's default behavior
1511            "REGEXP_REPLACE" if f.args.len() == 3 || f.args.len() == 4 => {
1512                Ok(Expression::Function(Box::new(f)))
1513            }
1514            // 6 args from Snowflake: (subject, pattern, replacement, position, occurrence, params)
1515            // If occurrence is 0 (global), append 'g' to flags
1516            "REGEXP_REPLACE" if f.args.len() == 6 => {
1517                let is_global = match &f.args[4] {
1518                    Expression::Literal(lit)
1519                        if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
1520                    {
1521                        let crate::expressions::Literal::Number(n) = lit.as_ref() else {
1522                            unreachable!()
1523                        };
1524                        n == "0"
1525                    }
1526                    _ => false,
1527                };
1528                if is_global {
1529                    let subject = f.args[0].clone();
1530                    let pattern = f.args[1].clone();
1531                    let replacement = f.args[2].clone();
1532                    let position = f.args[3].clone();
1533                    let occurrence = f.args[4].clone();
1534                    let params = &f.args[5];
1535                    let mut flags = if let Expression::Literal(lit) = params {
1536                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
1537                            s.clone()
1538                        } else {
1539                            String::new()
1540                        }
1541                    } else {
1542                        String::new()
1543                    };
1544                    if !flags.contains('g') {
1545                        flags.push('g');
1546                    }
1547                    Ok(Expression::Function(Box::new(Function::new(
1548                        "REGEXP_REPLACE".to_string(),
1549                        vec![
1550                            subject,
1551                            pattern,
1552                            replacement,
1553                            position,
1554                            occurrence,
1555                            Expression::Literal(Box::new(crate::expressions::Literal::String(
1556                                flags,
1557                            ))),
1558                        ],
1559                    ))))
1560                } else {
1561                    Ok(Expression::Function(Box::new(f)))
1562                }
1563            }
1564            // Default: pass through
1565            "REGEXP_REPLACE" => Ok(Expression::Function(Box::new(f))),
1566
1567            // Pass through everything else
1568            _ => Ok(Expression::Function(Box::new(f))),
1569        }
1570    }
1571
1572    fn transform_aggregate_function(
1573        &self,
1574        f: Box<crate::expressions::AggregateFunction>,
1575    ) -> Result<Expression> {
1576        let name_upper = f.name.to_uppercase();
1577        match name_upper.as_str() {
1578            // COUNT_IF -> SUM(CASE WHEN...)
1579            "COUNT_IF" if !f.args.is_empty() => {
1580                let condition = f.args.into_iter().next().unwrap();
1581                let case_expr = Expression::Case(Box::new(Case {
1582                    operand: None,
1583                    whens: vec![(condition, Expression::number(1))],
1584                    else_: Some(Expression::number(0)),
1585                    comments: Vec::new(),
1586                    inferred_type: None,
1587                }));
1588                Ok(Expression::Sum(Box::new(AggFunc {
1589                    ignore_nulls: None,
1590                    having_max: None,
1591                    this: case_expr,
1592                    distinct: f.distinct,
1593                    filter: f.filter,
1594                    order_by: Vec::new(),
1595                    name: None,
1596                    limit: None,
1597                    inferred_type: None,
1598                })))
1599            }
1600
1601            // GROUP_CONCAT -> STRING_AGG
1602            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1603                Function::new("STRING_AGG".to_string(), f.args),
1604            ))),
1605
1606            // STDEV -> STDDEV in PostgreSQL
1607            "STDEV" if !f.args.is_empty() => Ok(Expression::Stddev(Box::new(AggFunc {
1608                ignore_nulls: None,
1609                having_max: None,
1610                this: f.args.into_iter().next().unwrap(),
1611                distinct: f.distinct,
1612                filter: f.filter,
1613                order_by: Vec::new(),
1614                name: None,
1615                limit: None,
1616                inferred_type: None,
1617            }))),
1618
1619            // STDEVP -> STDDEV_POP in PostgreSQL
1620            "STDEVP" if !f.args.is_empty() => Ok(Expression::StddevPop(Box::new(AggFunc {
1621                ignore_nulls: None,
1622                having_max: None,
1623                this: f.args.into_iter().next().unwrap(),
1624                distinct: f.distinct,
1625                filter: f.filter,
1626                order_by: Vec::new(),
1627                name: None,
1628                limit: None,
1629                inferred_type: None,
1630            }))),
1631
1632            // VAR -> VAR_SAMP in PostgreSQL
1633            "VAR" if !f.args.is_empty() => Ok(Expression::VarSamp(Box::new(AggFunc {
1634                ignore_nulls: None,
1635                having_max: None,
1636                this: f.args.into_iter().next().unwrap(),
1637                distinct: f.distinct,
1638                filter: f.filter,
1639                order_by: Vec::new(),
1640                name: None,
1641                limit: None,
1642                inferred_type: None,
1643            }))),
1644
1645            // VARP -> VAR_POP in PostgreSQL
1646            "VARP" if !f.args.is_empty() => Ok(Expression::VarPop(Box::new(AggFunc {
1647                ignore_nulls: None,
1648                having_max: None,
1649                this: f.args.into_iter().next().unwrap(),
1650                distinct: f.distinct,
1651                filter: f.filter,
1652                order_by: Vec::new(),
1653                name: None,
1654                limit: None,
1655                inferred_type: None,
1656            }))),
1657
1658            // BIT_AND is native to PostgreSQL
1659            "BIT_AND" => Ok(Expression::AggregateFunction(f)),
1660
1661            // BIT_OR is native to PostgreSQL
1662            "BIT_OR" => Ok(Expression::AggregateFunction(f)),
1663
1664            // BIT_XOR is native to PostgreSQL
1665            "BIT_XOR" => Ok(Expression::AggregateFunction(f)),
1666
1667            // BOOL_AND is native to PostgreSQL
1668            "BOOL_AND" => Ok(Expression::AggregateFunction(f)),
1669
1670            // BOOL_OR is native to PostgreSQL
1671            "BOOL_OR" => Ok(Expression::AggregateFunction(f)),
1672
1673            // VARIANCE -> VAR_SAMP in PostgreSQL
1674            "VARIANCE" if !f.args.is_empty() => Ok(Expression::VarSamp(Box::new(AggFunc {
1675                ignore_nulls: None,
1676                having_max: None,
1677                this: f.args.into_iter().next().unwrap(),
1678                distinct: f.distinct,
1679                filter: f.filter,
1680                order_by: Vec::new(),
1681                name: None,
1682                limit: None,
1683                inferred_type: None,
1684            }))),
1685
1686            // LOGICAL_OR -> BOOL_OR in PostgreSQL
1687            "LOGICAL_OR" if !f.args.is_empty() => {
1688                let mut new_agg = f.clone();
1689                new_agg.name = "BOOL_OR".to_string();
1690                Ok(Expression::AggregateFunction(new_agg))
1691            }
1692
1693            // LOGICAL_AND -> BOOL_AND in PostgreSQL
1694            "LOGICAL_AND" if !f.args.is_empty() => {
1695                let mut new_agg = f.clone();
1696                new_agg.name = "BOOL_AND".to_string();
1697                Ok(Expression::AggregateFunction(new_agg))
1698            }
1699
1700            // Pass through everything else
1701            _ => Ok(Expression::AggregateFunction(f)),
1702        }
1703    }
1704}
1705
1706#[cfg(test)]
1707mod tests {
1708    use super::*;
1709    use crate::dialects::Dialect;
1710
1711    fn transpile_to_postgres(sql: &str) -> String {
1712        let dialect = Dialect::get(DialectType::Generic);
1713        let result = dialect
1714            .transpile(sql, DialectType::PostgreSQL)
1715            .expect("Transpile failed");
1716        result[0].clone()
1717    }
1718
1719    #[test]
1720    fn test_ifnull_to_coalesce() {
1721        let result = transpile_to_postgres("SELECT IFNULL(a, b)");
1722        assert!(
1723            result.contains("COALESCE"),
1724            "Expected COALESCE, got: {}",
1725            result
1726        );
1727    }
1728
1729    #[test]
1730    fn test_nvl_to_coalesce() {
1731        let result = transpile_to_postgres("SELECT NVL(a, b)");
1732        assert!(
1733            result.contains("COALESCE"),
1734            "Expected COALESCE, got: {}",
1735            result
1736        );
1737    }
1738
1739    #[test]
1740    fn test_rand_to_random() {
1741        let result = transpile_to_postgres("SELECT RAND()");
1742        assert!(
1743            result.contains("RANDOM"),
1744            "Expected RANDOM, got: {}",
1745            result
1746        );
1747    }
1748
1749    #[test]
1750    fn test_basic_select() {
1751        let result = transpile_to_postgres("SELECT a, b FROM users WHERE id = 1");
1752        assert!(result.contains("SELECT"));
1753        assert!(result.contains("FROM users"));
1754    }
1755
1756    #[test]
1757    fn test_len_to_length() {
1758        let result = transpile_to_postgres("SELECT LEN(name)");
1759        assert!(
1760            result.contains("LENGTH"),
1761            "Expected LENGTH, got: {}",
1762            result
1763        );
1764    }
1765
1766    #[test]
1767    fn test_getdate_to_current_timestamp() {
1768        let result = transpile_to_postgres("SELECT GETDATE()");
1769        assert!(
1770            result.contains("CURRENT_TIMESTAMP"),
1771            "Expected CURRENT_TIMESTAMP, got: {}",
1772            result
1773        );
1774    }
1775
1776    #[test]
1777    fn test_substr_to_substring() {
1778        let result = transpile_to_postgres("SELECT SUBSTR(name, 1, 3)");
1779        assert!(
1780            result.contains("SUBSTRING"),
1781            "Expected SUBSTRING, got: {}",
1782            result
1783        );
1784    }
1785
1786    #[test]
1787    fn test_group_concat_to_string_agg() {
1788        let result = transpile_to_postgres("SELECT GROUP_CONCAT(name)");
1789        assert!(
1790            result.contains("STRING_AGG"),
1791            "Expected STRING_AGG, got: {}",
1792            result
1793        );
1794    }
1795
1796    #[test]
1797    fn test_double_quote_identifiers() {
1798        // PostgreSQL uses double quotes for identifiers
1799        let dialect = PostgresDialect;
1800        let config = dialect.generator_config();
1801        assert_eq!(config.identifier_quote, '"');
1802    }
1803
1804    #[test]
1805    fn test_char_length_to_length() {
1806        let result = transpile_to_postgres("SELECT CHAR_LENGTH(name)");
1807        assert!(
1808            result.contains("LENGTH"),
1809            "Expected LENGTH, got: {}",
1810            result
1811        );
1812    }
1813
1814    #[test]
1815    fn test_character_length_to_length() {
1816        let result = transpile_to_postgres("SELECT CHARACTER_LENGTH(name)");
1817        assert!(
1818            result.contains("LENGTH"),
1819            "Expected LENGTH, got: {}",
1820            result
1821        );
1822    }
1823
1824    /// Helper for PostgreSQL identity tests (parse and regenerate with PostgreSQL dialect)
1825    fn identity_postgres(sql: &str) -> String {
1826        let dialect = Dialect::get(DialectType::PostgreSQL);
1827        let exprs = dialect.parse(sql).expect("Parse failed");
1828        let transformed = dialect
1829            .transform(exprs[0].clone())
1830            .expect("Transform failed");
1831        dialect.generate(&transformed).expect("Generate failed")
1832    }
1833
1834    #[test]
1835    fn test_json_extract_with_column_path() {
1836        // When the path is a column reference (not a literal), should use function form
1837        let result = identity_postgres("json_data.data -> field_ids.field_id");
1838        assert!(
1839            result.contains("JSON_EXTRACT_PATH"),
1840            "Expected JSON_EXTRACT_PATH for column path, got: {}",
1841            result
1842        );
1843    }
1844
1845    #[test]
1846    fn test_json_extract_scalar_with_negative_index() {
1847        // When the path is a negative index, should use JSON_EXTRACT_PATH_TEXT function
1848        let result = identity_postgres("x::JSON -> 'duration' ->> -1");
1849        assert!(
1850            result.contains("JSON_EXTRACT_PATH_TEXT"),
1851            "Expected JSON_EXTRACT_PATH_TEXT for negative index, got: {}",
1852            result
1853        );
1854        // The first -> should still be arrow syntax since 'duration' is a string literal
1855        assert!(
1856            result.contains("->"),
1857            "Expected -> for string literal path, got: {}",
1858            result
1859        );
1860    }
1861
1862    #[test]
1863    fn test_json_extract_with_string_literal() {
1864        // When the path is a string literal, should keep arrow syntax
1865        let result = identity_postgres("data -> 'key'");
1866        assert!(
1867            result.contains("->"),
1868            "Expected -> for string literal path, got: {}",
1869            result
1870        );
1871        assert!(
1872            !result.contains("JSON_EXTRACT_PATH"),
1873            "Should NOT use function form for string literal, got: {}",
1874            result
1875        );
1876    }
1877}