Skip to main content

squawk_ide/
column_name.rs

1use squawk_syntax::{
2    SyntaxKind, SyntaxNode,
3    ast::{self, AstNode},
4};
5
6#[derive(Clone, Debug, PartialEq)]
7pub(crate) enum ColumnName {
8    Column(String),
9    /// There's a fallback mechanism that we need to propagate through the
10    /// expressions/types.
11    //
12    /// We can see this with:
13    /// ```sql
14    /// select case when true then 'a' else now()::text end;
15    /// -- column named `now`, propagating the function name
16    /// -- vs
17    /// select case when true then 'a' else 'b' end;
18    /// -- column named `case`
19    /// ```
20    UnknownColumn(Option<String>),
21    Star,
22}
23
24impl ColumnName {
25    // Get the alias, otherwise infer the column name.
26    pub(crate) fn from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
27        if let Some(as_name) = target.as_name()
28            && let Some(name_node) = as_name.name()
29        {
30            return Some((
31                ColumnName::Column(name_node.text()),
32                name_node.syntax().clone(),
33            ));
34        }
35        Self::inferred_from_target(target)
36    }
37
38    // Ignore any aliases, just infer the what the column name.
39    pub(crate) fn inferred_from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
40        if let Some(expr) = target.expr()
41            && let Some(name) = name_from_expr(expr, false)
42        {
43            return Some(name);
44        } else if target.star_token().is_some() {
45            return Some((ColumnName::Star, target.syntax().clone()));
46        }
47        None
48    }
49
50    fn new(name: String, unknown_column: bool) -> ColumnName {
51        if unknown_column {
52            ColumnName::UnknownColumn(Some(name))
53        } else {
54            ColumnName::Column(name)
55        }
56    }
57
58    pub(crate) fn to_string(&self) -> Option<String> {
59        match self {
60            ColumnName::Column(string) => Some(string.to_string()),
61            ColumnName::Star => None,
62            ColumnName::UnknownColumn(c) => {
63                Some(c.clone().unwrap_or_else(|| "?column?".to_string()))
64            }
65        }
66    }
67}
68
69fn name_from_type(ty: ast::Type, unknown_column: bool) -> Option<(ColumnName, SyntaxNode)> {
70    match ty {
71        ast::Type::PathType(path_type) => {
72            if let Some(name_ref) = path_type
73                .path()
74                .and_then(|x| x.segment())
75                .and_then(|x| x.name_ref())
76            {
77                return name_from_name_ref(name_ref, true, path_type.arg_list().as_ref()).map(
78                    |(column, node)| {
79                        let column = match column {
80                            ColumnName::Column(c) => ColumnName::new(c, unknown_column),
81                            _ => column,
82                        };
83                        (column, node)
84                    },
85                );
86            }
87        }
88        ast::Type::BitType(bit_type) => {
89            let name = if bit_type.varying_token().is_some() {
90                "varbit"
91            } else {
92                "bit"
93            };
94            return Some((
95                ColumnName::new(name.to_string(), unknown_column),
96                bit_type.syntax().clone(),
97            ));
98        }
99        ast::Type::CharType(char_type) => {
100            let name = if char_type.varchar_token().is_some() || char_type.varying_token().is_some()
101            {
102                "varchar"
103            } else {
104                "bpchar"
105            };
106            return Some((
107                ColumnName::new(name.to_string(), unknown_column),
108                char_type.syntax().clone(),
109            ));
110        }
111        ast::Type::DoubleType(double_type) => {
112            return Some((
113                ColumnName::new("float8".to_string(), unknown_column),
114                double_type.syntax().clone(),
115            ));
116        }
117        ast::Type::IntervalType(interval_type) => {
118            return Some((
119                ColumnName::new("interval".to_string(), unknown_column),
120                interval_type.syntax().clone(),
121            ));
122        }
123        ast::Type::TimeType(time_type) => {
124            let mut name = if time_type.timestamp_token().is_some() {
125                "timestamp".to_owned()
126            } else {
127                "time".to_owned()
128            };
129            if let Some(ast::Timezone::WithTimezone(_)) = time_type.timezone() {
130                // time -> timetz
131                // timestamp -> timestamptz
132                name.push_str("tz");
133            };
134            return Some((
135                ColumnName::new(name, unknown_column),
136                time_type.syntax().clone(),
137            ));
138        }
139        ast::Type::ArrayType(array_type) => {
140            if let Some(inner_ty) = array_type.ty() {
141                return name_from_type(inner_ty, unknown_column);
142            }
143        }
144        // we shouldn't ever hit this since the following isn't valid syntax:
145        // select cast('foo' as t.a%TYPE);
146        ast::Type::PercentType(_) => return None,
147        ast::Type::ExprType(expr_type) => {
148            if let Some(expr) = expr_type.expr() {
149                return name_from_expr(expr, true).map(|(column, node)| {
150                    let column = match column {
151                        ColumnName::Column(c) => ColumnName::new(c, unknown_column),
152                        _ => column,
153                    };
154                    (column, node)
155                });
156            }
157        }
158    }
159    None
160}
161
162fn name_from_name_ref(
163    name_ref: ast::NameRef,
164    in_type: bool,
165    arg_list: Option<&ast::ArgList>,
166) -> Option<(ColumnName, SyntaxNode)> {
167    if in_type {
168        for node in name_ref.syntax().children_with_tokens() {
169            match node.kind() {
170                SyntaxKind::BIGINT_KW => {
171                    return Some((
172                        ColumnName::Column("int8".to_owned()),
173                        name_ref.syntax().clone(),
174                    ));
175                }
176                SyntaxKind::BOOLEAN_KW => {
177                    return Some((
178                        ColumnName::Column("bool".to_owned()),
179                        name_ref.syntax().clone(),
180                    ));
181                }
182                SyntaxKind::DEC_KW | SyntaxKind::DECIMAL_KW => {
183                    return Some((
184                        ColumnName::Column("numeric".to_owned()),
185                        name_ref.syntax().clone(),
186                    ));
187                }
188                SyntaxKind::FLOAT_KW => {
189                    let precision = arg_list.and_then(|arg| {
190                        arg.args_().find_map(|arg| {
191                            if let ast::Expr::Literal(lit) = arg.expr()? {
192                                lit.syntax().text().to_string().parse::<u32>().ok()
193                            } else {
194                                None
195                            }
196                        })
197                    });
198                    let name = if matches!(precision, Some(p) if p <= 24) {
199                        "float4"
200                    } else {
201                        "float8"
202                    };
203                    return Some((
204                        ColumnName::Column(name.to_owned()),
205                        name_ref.syntax().clone(),
206                    ));
207                }
208                SyntaxKind::INT_KW | SyntaxKind::INTEGER_KW => {
209                    return Some((
210                        ColumnName::Column("int4".to_owned()),
211                        name_ref.syntax().clone(),
212                    ));
213                }
214                SyntaxKind::SMALLINT_KW => {
215                    return Some((
216                        ColumnName::Column("int2".to_owned()),
217                        name_ref.syntax().clone(),
218                    ));
219                }
220                SyntaxKind::REAL_KW => {
221                    return Some((
222                        ColumnName::Column("float4".to_owned()),
223                        name_ref.syntax().clone(),
224                    ));
225                }
226                _ => (),
227            }
228        }
229    }
230    return Some((
231        ColumnName::Column(name_ref.text()),
232        name_ref.syntax().clone(),
233    ));
234}
235
236/*
237TODO:
238
239unnest(anyarray, anyarray [, ... ]) → setof anyelement, anyelement [, ... ]
240
241select * from unnest(ARRAY[1,2], ARRAY['foo','bar','baz']) →
242 unnset | unnset
243--------+-----
244      1 | foo
245      2 | bar
246        | baz
247*/
248
249// NOTE: we have to have this in_type param because we parse some casts as exprs
250// instead of types.
251fn name_from_expr(expr: ast::Expr, in_type: bool) -> Option<(ColumnName, SyntaxNode)> {
252    let node = expr.syntax().clone();
253    match expr {
254        ast::Expr::ArrayExpr(_) => {
255            return Some((ColumnName::Column("array".to_string()), node));
256        }
257        ast::Expr::BetweenExpr(_) => {
258            return Some((ColumnName::UnknownColumn(None), node));
259        }
260        ast::Expr::BinExpr(bin_expr) => match bin_expr.op() {
261            Some(ast::BinOp::AtTimeZone(_)) => {
262                return Some((ColumnName::Column("timezone".to_string()), node));
263            }
264            Some(ast::BinOp::Overlaps(_)) => {
265                return Some((ColumnName::Column("overlaps".to_string()), node));
266            }
267            _ => return Some((ColumnName::UnknownColumn(None), node)),
268        },
269        ast::Expr::CallExpr(call_expr) => {
270            if let Some(exists_fn) = call_expr.exists_fn() {
271                return Some((
272                    ColumnName::Column("exists".to_string()),
273                    exists_fn.syntax().clone(),
274                ));
275            }
276            if let Some(extract_fn) = call_expr.extract_fn() {
277                return Some((
278                    ColumnName::Column("extract".to_string()),
279                    extract_fn.syntax().clone(),
280                ));
281            }
282            if let Some(json_exists_fn) = call_expr.json_exists_fn() {
283                return Some((
284                    ColumnName::Column("json_exists".to_string()),
285                    json_exists_fn.syntax().clone(),
286                ));
287            }
288            if let Some(json_array_fn) = call_expr.json_array_fn() {
289                return Some((
290                    ColumnName::Column("json_array".to_string()),
291                    json_array_fn.syntax().clone(),
292                ));
293            }
294            if let Some(json_object_fn) = call_expr.json_object_fn() {
295                return Some((
296                    ColumnName::Column("json_object".to_string()),
297                    json_object_fn.syntax().clone(),
298                ));
299            }
300            if let Some(json_object_agg_fn) = call_expr.json_object_agg_fn() {
301                return Some((
302                    ColumnName::Column("json_objectagg".to_string()),
303                    json_object_agg_fn.syntax().clone(),
304                ));
305            }
306            if let Some(json_array_agg_fn) = call_expr.json_array_agg_fn() {
307                return Some((
308                    ColumnName::Column("json_arrayagg".to_string()),
309                    json_array_agg_fn.syntax().clone(),
310                ));
311            }
312            if let Some(json_query_fn) = call_expr.json_query_fn() {
313                return Some((
314                    ColumnName::Column("json_query".to_string()),
315                    json_query_fn.syntax().clone(),
316                ));
317            }
318            if let Some(json_scalar_fn) = call_expr.json_scalar_fn() {
319                return Some((
320                    ColumnName::Column("json_scalar".to_string()),
321                    json_scalar_fn.syntax().clone(),
322                ));
323            }
324            if let Some(json_serialize_fn) = call_expr.json_serialize_fn() {
325                return Some((
326                    ColumnName::Column("json_serialize".to_string()),
327                    json_serialize_fn.syntax().clone(),
328                ));
329            }
330            if let Some(json_value_fn) = call_expr.json_value_fn() {
331                return Some((
332                    ColumnName::Column("json_value".to_string()),
333                    json_value_fn.syntax().clone(),
334                ));
335            }
336            if let Some(json_fn) = call_expr.json_fn() {
337                return Some((
338                    ColumnName::Column("json".to_string()),
339                    json_fn.syntax().clone(),
340                ));
341            }
342            if let Some(substring_fn) = call_expr.substring_fn() {
343                return Some((
344                    ColumnName::Column("substring".to_string()),
345                    substring_fn.syntax().clone(),
346                ));
347            }
348            if let Some(position_fn) = call_expr.position_fn() {
349                return Some((
350                    ColumnName::Column("position".to_string()),
351                    position_fn.syntax().clone(),
352                ));
353            }
354            if let Some(overlay_fn) = call_expr.overlay_fn() {
355                return Some((
356                    ColumnName::Column("overlay".to_string()),
357                    overlay_fn.syntax().clone(),
358                ));
359            }
360            if let Some(trim_fn) = call_expr.trim_fn() {
361                let name = if trim_fn.leading_token().is_some() {
362                    "ltrim"
363                } else if trim_fn.trailing_token().is_some() {
364                    "rtrim"
365                } else {
366                    "btrim"
367                };
368                return Some((
369                    ColumnName::Column(name.to_string()),
370                    trim_fn.syntax().clone(),
371                ));
372            }
373            if let Some(xml_root_fn) = call_expr.xml_root_fn() {
374                return Some((
375                    ColumnName::Column("xml_root".to_string()),
376                    xml_root_fn.syntax().clone(),
377                ));
378            }
379            if let Some(xml_serialize_fn) = call_expr.xml_serialize_fn() {
380                return Some((
381                    ColumnName::Column("xml_serialize".to_string()),
382                    xml_serialize_fn.syntax().clone(),
383                ));
384            }
385            if let Some(xml_element_fn) = call_expr.xml_element_fn() {
386                return Some((
387                    ColumnName::Column("xml_element".to_string()),
388                    xml_element_fn.syntax().clone(),
389                ));
390            }
391            if let Some(xml_forest_fn) = call_expr.xml_forest_fn() {
392                return Some((
393                    ColumnName::Column("xml_forest".to_string()),
394                    xml_forest_fn.syntax().clone(),
395                ));
396            }
397            if let Some(xml_exists_fn) = call_expr.xml_exists_fn() {
398                return Some((
399                    ColumnName::Column("xml_exists".to_string()),
400                    xml_exists_fn.syntax().clone(),
401                ));
402            }
403            if let Some(xml_parse_fn) = call_expr.xml_parse_fn() {
404                return Some((
405                    ColumnName::Column("xml_parse".to_string()),
406                    xml_parse_fn.syntax().clone(),
407                ));
408            }
409            if let Some(xml_pi_fn) = call_expr.xml_pi_fn() {
410                return Some((
411                    ColumnName::Column("xml_pi".to_string()),
412                    xml_pi_fn.syntax().clone(),
413                ));
414            }
415            if let Some(collation_for_fn) = call_expr.collation_for_fn() {
416                return Some((
417                    ColumnName::Column("pg_collation_for".to_string()),
418                    collation_for_fn.syntax().clone(),
419                ));
420            }
421            if let Some(func_name) = call_expr.expr() {
422                match func_name {
423                    ast::Expr::ArrayExpr(_)
424                    | ast::Expr::BetweenExpr(_)
425                    | ast::Expr::ParenExpr(_)
426                    | ast::Expr::BinExpr(_)
427                    | ast::Expr::CallExpr(_)
428                    | ast::Expr::CaseExpr(_)
429                    | ast::Expr::CastExpr(_)
430                    | ast::Expr::Literal(_)
431                    | ast::Expr::PostfixExpr(_)
432                    | ast::Expr::PrefixExpr(_)
433                    | ast::Expr::TupleExpr(_)
434                    | ast::Expr::IndexExpr(_)
435                    | ast::Expr::SliceExpr(_) => unreachable!("not possible in the grammar"),
436                    ast::Expr::FieldExpr(field_expr) => {
437                        if let Some(name_ref) = field_expr.field() {
438                            return name_from_name_ref(name_ref, in_type, None);
439                        }
440                    }
441                    ast::Expr::NameRef(name_ref) => {
442                        return name_from_name_ref(name_ref, in_type, None);
443                    }
444                }
445            }
446        }
447        ast::Expr::CaseExpr(case) => {
448            if let Some(else_clause) = case.else_clause()
449                && let Some(expr) = else_clause.expr()
450                && let Some((column, node)) = name_from_expr(expr, in_type)
451            {
452                if !matches!(column, ColumnName::UnknownColumn(_)) {
453                    return Some((column, node));
454                }
455            }
456            return Some((ColumnName::Column("case".to_string()), node));
457        }
458        ast::Expr::CastExpr(cast_expr) => {
459            let mut unknown_column = false;
460            if let Some(expr) = cast_expr.expr()
461                && let Some((column, node)) = name_from_expr(expr, in_type)
462            {
463                match column {
464                    ColumnName::Column(_) => return Some((column, node)),
465                    ColumnName::UnknownColumn(_) => unknown_column = true,
466                    ColumnName::Star => (),
467                }
468            }
469            if let Some(ty) = cast_expr.ty() {
470                return name_from_type(ty, unknown_column);
471            }
472        }
473        ast::Expr::FieldExpr(field_expr) => {
474            if let Some(name_ref) = field_expr.field() {
475                return name_from_name_ref(name_ref, in_type, None);
476            }
477        }
478        ast::Expr::IndexExpr(index_expr) => {
479            if let Some(base) = index_expr.base() {
480                return name_from_expr(base, in_type);
481            }
482        }
483        ast::Expr::SliceExpr(slice_expr) => {
484            if let Some(base) = slice_expr.base() {
485                return name_from_expr(base, in_type);
486            }
487        }
488        ast::Expr::Literal(_) | ast::Expr::PrefixExpr(_) => {
489            return Some((ColumnName::UnknownColumn(None), node));
490        }
491        ast::Expr::PostfixExpr(postfix_expr) => match postfix_expr.op() {
492            Some(ast::PostfixOp::AtLocal(_)) => {
493                return Some((ColumnName::Column("timezone".to_string()), node));
494            }
495            Some(ast::PostfixOp::IsNormalized(_)) => {
496                return Some((ColumnName::Column("is_normalized".to_string()), node));
497            }
498            _ => return Some((ColumnName::UnknownColumn(None), node)),
499        },
500        ast::Expr::NameRef(name_ref) => {
501            return name_from_name_ref(name_ref, in_type, None);
502        }
503        ast::Expr::ParenExpr(paren_expr) => {
504            if let Some(expr) = paren_expr.expr() {
505                return name_from_expr(expr, in_type);
506            } else if let Some(select) = paren_expr.select()
507                && let Some(mut targets) = select
508                    .select_clause()
509                    .and_then(|x| x.target_list())
510                    .map(|x| x.targets())
511                && let Some(target) = targets.next()
512            {
513                return ColumnName::from_target(target);
514            }
515        }
516        ast::Expr::TupleExpr(_) => {
517            return Some((ColumnName::Column("row".to_string()), node));
518        }
519    }
520    None
521}
522
523#[test]
524fn examples() {
525    use insta::assert_snapshot;
526
527    // array
528    assert_snapshot!(name("array(select 1)"), @"array");
529    assert_snapshot!(name("array[1, 2, 3]"), @"array");
530
531    // unknown columns
532    assert_snapshot!(name("1 between 0 and 10"), @"?column?");
533    assert_snapshot!(name("1 + 2"), @"?column?");
534    assert_snapshot!(name("42"), @"?column?");
535    assert_snapshot!(name("'string'"), @"?column?");
536    // prefix
537    assert_snapshot!(name("-42"), @"?column?");
538    assert_snapshot!(name("|/ 42"), @"?column?");
539    // postfix
540    assert_snapshot!(name("x is null"), @"?column?");
541    assert_snapshot!(name("x is not null"), @"?column?");
542    assert_snapshot!(name("'foo' is normalized"), @"is_normalized");
543    assert_snapshot!(name("'foo' is not normalized"), @"?column?");
544    assert_snapshot!(name("now() at local"), @"timezone");
545    // bin expr
546    assert_snapshot!(name("now() at time zone 'America/Chicago'"), @"timezone");
547    assert_snapshot!(
548        name("(DATE '2001-02-16', DATE '2001-12-21') OVERLAPS (DATE '2001-10-30', DATE '2002-10-30')"),
549        @"overlaps"
550    );
551    // paren expr
552    assert_snapshot!(name("(1 * 2)"), @"?column?");
553    assert_snapshot!(name("(select 1 as a)"), @"a");
554
555    // func
556    assert_snapshot!(name("count(*)"), @"count");
557    assert_snapshot!(name("schema.func_name(1)"), @"func_name");
558
559    // special funcs
560    assert_snapshot!(name("collation for ('bar')"), @"pg_collation_for");
561    assert_snapshot!(name("extract(year from now())"), @"extract");
562    assert_snapshot!(name("exists(select 1)"), @"exists");
563    assert_snapshot!(name(r#"json_exists('{"a":1}', '$.a')"#), @"json_exists");
564    assert_snapshot!(name("json_array(1, 2)"), @"json_array");
565    assert_snapshot!(name("json_object('a': 1)"), @"json_object");
566    assert_snapshot!(name("json_objectagg('a': 1)"), @"json_objectagg");
567    assert_snapshot!(name("json_arrayagg(1)"), @"json_arrayagg");
568    assert_snapshot!(name(r#"json_query('{"a":1}', '$.a')"#), @"json_query");
569    assert_snapshot!(name("json_scalar(1)"), @"json_scalar");
570    assert_snapshot!(name(r#"json_serialize('{"a":1}')"#), @"json_serialize");
571    assert_snapshot!(name(r#"json_value('{"a":1}', '$.a')"#), @"json_value");
572    assert_snapshot!(name(r#"json('{"a":1}')"#), @"json");
573    assert_snapshot!(name("substring('hello' from 2 for 3)"), @"substring");
574    assert_snapshot!(name("position('a' in 'abc')"), @"position");
575    assert_snapshot!(name("overlay('hello' placing 'X' from 2)"), @"overlay");
576    assert_snapshot!(name("trim('  hi  ')"), @"btrim");
577    assert_snapshot!(name("trim(leading ' ' from '  hi  ')"), @"ltrim");
578    assert_snapshot!(name("trim(trailing ' ' from '  hi  ')"), @"rtrim");
579    assert_snapshot!(name("trim(both ' ' from '  hi  ')"), @"btrim");
580    assert_snapshot!(name("xmlroot('<a/>', version '1.0')"), @"xml_root");
581    assert_snapshot!(name("xmlserialize(document '<a/>' as text)"), @"xml_serialize");
582    assert_snapshot!(name("xmlelement(name foo, 'bar')"), @"xml_element");
583    assert_snapshot!(name("xmlforest('bar' as foo)"), @"xml_forest");
584    assert_snapshot!(name("xmlexists('//a' passing '<a/>')"), @"xml_exists");
585    assert_snapshot!(name("xmlparse(document '<a/>')"), @"xml_parse");
586    assert_snapshot!(name("xmlpi(name foo, 'bar')"), @"xml_pi");
587
588    // index
589    assert_snapshot!(name("foo[bar]"), @"foo");
590    assert_snapshot!(name("foo[1]"), @"foo");
591
592    // column
593    assert_snapshot!(name("database.schema.table.column"), @"column");
594    assert_snapshot!(name("t.a"), @"a");
595    assert_snapshot!(name("col_name"), @"col_name");
596    assert_snapshot!(name("(c)"), @"c");
597
598    // case
599    assert_snapshot!(name("case when true then 'foo' end"), @"case");
600    assert_snapshot!(name("case when true then 'foo' else now()::text end"), @"now");
601    assert_snapshot!(name("case when true then 'foo' else 'bar' end"), @"case");
602    assert_snapshot!(name("case when true then 'foo' else '1'::bigint::text end"), @"case");
603
604    // casts
605    assert_snapshot!(name("now()::text"), @"now");
606    assert_snapshot!(name("cast(col_name as text)"), @"col_name");
607    assert_snapshot!(name("col_name::text"), @"col_name");
608    assert_snapshot!(name("col_name::int::text"), @"col_name");
609    assert_snapshot!(name("'1'::bigint"), @"int8");
610    assert_snapshot!(name("'1'::decimal"), @"numeric");
611    assert_snapshot!(name("'1'::boolean"), @"bool");
612    assert_snapshot!(name("'1'::int"), @"int4");
613    assert_snapshot!(name("'1'::smallint"), @"int2");
614    assert_snapshot!(name("'{{1, 2}, {3, 4}}'::bigint[][]"), @"int8");
615    assert_snapshot!(name("'{{1, 2}, {3, 4}}'::int[][]"), @"int4");
616    assert_snapshot!(name("'{{1, 2}, {3, 4}}'::smallint[]"), @"int2");
617    assert_snapshot!(name("pg_catalog.varchar(100) '{1}'"), @"varchar");
618    assert_snapshot!(name("'{1}'::integer[];"), @"int4");
619    assert_snapshot!(name("'{1}'::pg_catalog.varchar(1)[]::integer[];"), @"int4");
620    assert_snapshot!(name("'1'::bigint::smallint"), @"int2");
621
622    // alias
623    // with quoting
624    assert_snapshot!(name(r#"'foo' as "FOO""#), @"FOO");
625    assert_snapshot!(name(r#"'foo' as "foo""#), @"foo");
626    // without quoting
627    assert_snapshot!(name(r#"'foo' as FOO"#), @"foo");
628    assert_snapshot!(name(r#"'foo' as foo"#), @"foo");
629
630    // tuple
631    assert_snapshot!(name("(1, 2, 3)"), @"row");
632    assert_snapshot!(name("(1, 2, 3)::address"), @"row");
633
634    // composite type
635    assert_snapshot!(name("(x).city"), @"city");
636
637    // array types
638    assert_snapshot!(name("'{{1, 2}, {3, 4}}'::int[]"), @"int4");
639    assert_snapshot!(name("cast('{foo}' as text[])"), @"text");
640
641    // bit types
642    assert_snapshot!(name("cast('1010' as bit varying(10))"), @"varbit");
643    assert_snapshot!(name("cast('1010' as bit varying)"), @"varbit");
644    assert_snapshot!(name("cast('1010' as bit)"), @"bit");
645
646    // decimal
647    assert_snapshot!(name("cast('1010' as dec)"), @"numeric");
648    assert_snapshot!(name("cast('1010' as dec(10))"), @"numeric");
649    assert_snapshot!(name("cast('1010' as decimal)"), @"numeric");
650    assert_snapshot!(name("cast('1010' as decimal(10))"), @"numeric");
651
652    // char types
653    assert_snapshot!(name("cast('hello' as character varying(10))"), @"varchar");
654    assert_snapshot!(name("cast('hello' as char varying(5))"), @"varchar");
655    assert_snapshot!(name("cast('hello' as nchar varying(10))"), @"varchar");
656    assert_snapshot!(name("cast('hello' as char(5))"), @"bpchar");
657    assert_snapshot!(name("cast('hello' as character)"), @"bpchar");
658    assert_snapshot!(name("cast('hello' as bpchar)"), @"bpchar");
659    assert_snapshot!(name("cast('hello' as nchar(10))"), @"bpchar");
660
661    assert_snapshot!(name(r#"cast('hello' as "char")"#), @"char");
662
663    // double types
664    assert_snapshot!(name("cast(1.5 as double precision)"), @"float8");
665    // real
666    assert_snapshot!(name("cast(1.5 as real)"), @"float4");
667    assert_snapshot!(name("cast(1.5 as float(8))"), @"float4");
668    assert_snapshot!(name("cast(2.5 as float(25))"), @"float8");
669
670    // interval types
671    assert_snapshot!(name("cast('1 hour' as interval hour to minute)"), @"interval");
672
673    // percent types
674    assert_snapshot!(name("cast(foo as schema.%TYPE)"), @"foo");
675
676    // time types
677    assert_snapshot!(name("cast('12:00:00' as time(6) without time zone)"), @"time");
678    assert_snapshot!(name("cast('12:00:00' as time(6) with time zone)"), @"timetz");
679    assert_snapshot!(name("cast('2024-01-01 12:00:00' as timestamp(6) with time zone)"), @"timestamptz");
680    assert_snapshot!(name("cast('2024-01-01 12:00:00' as timestamp(6) without time zone)"), @"timestamp");
681
682    #[track_caller]
683    fn name(sql: &str) -> String {
684        let sql = "select ".to_string() + sql;
685        let parse = squawk_syntax::SourceFile::parse(&sql);
686        assert_eq!(parse.errors(), vec![]);
687        let file = parse.tree();
688
689        let stmt = file.stmts().next().unwrap();
690        let ast::Stmt::Select(select) = stmt else {
691            unreachable!()
692        };
693
694        let target = select
695            .select_clause()
696            .and_then(|sc| sc.target_list())
697            .and_then(|tl| tl.targets().next())
698            .unwrap();
699
700        ColumnName::from_target(target)
701            .and_then(|x| x.0.to_string())
702            .unwrap()
703    }
704}