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