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 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 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 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 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
239fn 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 assert_snapshot!(name("array(select 1)"), @"array");
523 assert_snapshot!(name("array[1, 2, 3]"), @"array");
524
525 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 assert_snapshot!(name("-42"), @"?column?");
535 assert_snapshot!(name("|/ 42"), @"?column?");
536 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 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 assert_snapshot!(name("(1 * 2)"), @"?column?");
550 assert_snapshot!(name("(select 1 as a)"), @"a");
551
552 assert_snapshot!(name("count(*)"), @"count");
554 assert_snapshot!(name("schema.func_name(1)"), @"func_name");
555
556 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 assert_snapshot!(name("foo[bar]"), @"foo");
587 assert_snapshot!(name("foo[1]"), @"foo");
588
589 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 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 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 assert_snapshot!(name(r#"'foo' as "FOO""#), @"FOO");
622 assert_snapshot!(name(r#"'foo' as "foo""#), @"foo");
623 assert_snapshot!(name(r#"'foo' as FOO"#), @"foo");
625 assert_snapshot!(name(r#"'foo' as foo"#), @"foo");
626
627 assert_snapshot!(name("(1, 2, 3)"), @"row");
629 assert_snapshot!(name("(1, 2, 3)::address"), @"row");
630
631 assert_snapshot!(name("(x).city"), @"city");
633
634 assert_snapshot!(name("'{{1, 2}, {3, 4}}'::int[]"), @"int4");
636 assert_snapshot!(name("cast('{foo}' as text[])"), @"text");
637
638 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 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 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 assert_snapshot!(name("cast(1.5 as double precision)"), @"float8");
662 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 assert_snapshot!(name("cast('1 hour' as interval hour to minute)"), @"interval");
669
670 assert_snapshot!(name("cast(foo as schema.%TYPE)"), @"foo");
672
673 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}