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