Skip to main content

squawk_syntax/
column_name.rs

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