1use squawk_syntax::{
2 SyntaxKind, SyntaxNode,
3 ast::{self, AstNode},
4};
5
6use squawk_syntax::quote::normalize_identifier;
7
8#[derive(Clone, Debug, PartialEq)]
9pub(crate) enum ColumnName {
10 Column(String),
11 UnknownColumn(Option<String>),
23 Star,
24}
25
26impl ColumnName {
27 pub(crate) fn from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
29 if let Some(as_name) = target.as_name()
30 && let Some(name_node) = as_name.name()
31 {
32 let text = name_node.text();
33 let normalized = normalize_identifier(&text);
34 return Some((ColumnName::Column(normalized), name_node.syntax().clone()));
35 }
36 Self::inferred_from_target(target)
37 }
38
39 pub(crate) fn inferred_from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
41 if let Some(expr) = target.expr()
42 && let Some(name) = name_from_expr(expr, false)
43 {
44 return Some(name);
45 } else if target.star_token().is_some() {
46 return Some((ColumnName::Star, target.syntax().clone()));
47 }
48 None
49 }
50
51 fn new(name: String, unknown_column: bool) -> ColumnName {
52 if unknown_column {
53 ColumnName::UnknownColumn(Some(name))
54 } else {
55 ColumnName::Column(name)
56 }
57 }
58
59 pub(crate) fn to_string(&self) -> Option<String> {
60 match self {
61 ColumnName::Column(string) => Some(string.to_string()),
62 ColumnName::Star => None,
63 ColumnName::UnknownColumn(c) => {
64 Some(c.clone().unwrap_or_else(|| "?column?".to_string()))
65 }
66 }
67 }
68}
69
70fn name_from_type(ty: ast::Type, unknown_column: bool) -> Option<(ColumnName, SyntaxNode)> {
71 match ty {
72 ast::Type::PathType(path_type) => {
73 if let Some(name_ref) = path_type
74 .path()
75 .and_then(|x| x.segment())
76 .and_then(|x| x.name_ref())
77 {
78 return name_from_name_ref(name_ref, true, path_type.arg_list().as_ref()).map(
79 |(column, node)| {
80 let column = match column {
81 ColumnName::Column(c) => ColumnName::new(c, unknown_column),
82 _ => column,
83 };
84 (column, node)
85 },
86 );
87 }
88 }
89 ast::Type::BitType(bit_type) => {
90 let name = if bit_type.varying_token().is_some() {
91 "varbit"
92 } else {
93 "bit"
94 };
95 return Some((
96 ColumnName::new(name.to_string(), unknown_column),
97 bit_type.syntax().clone(),
98 ));
99 }
100 ast::Type::CharType(char_type) => {
101 let name = if char_type.varchar_token().is_some() || char_type.varying_token().is_some()
102 {
103 "varchar"
104 } else {
105 "bpchar"
106 };
107 return Some((
108 ColumnName::new(name.to_string(), unknown_column),
109 char_type.syntax().clone(),
110 ));
111 }
112 ast::Type::DoubleType(double_type) => {
113 return Some((
114 ColumnName::new("float8".to_string(), unknown_column),
115 double_type.syntax().clone(),
116 ));
117 }
118 ast::Type::IntervalType(interval_type) => {
119 return Some((
120 ColumnName::new("interval".to_string(), unknown_column),
121 interval_type.syntax().clone(),
122 ));
123 }
124 ast::Type::TimeType(time_type) => {
125 let mut name = if time_type.timestamp_token().is_some() {
126 "timestamp".to_owned()
127 } else {
128 "time".to_owned()
129 };
130 if let Some(ast::Timezone::WithTimezone(_)) = time_type.timezone() {
131 name.push_str("tz");
134 };
135 return Some((
136 ColumnName::new(name.to_string(), unknown_column),
137 time_type.syntax().clone(),
138 ));
139 }
140 ast::Type::ArrayType(array_type) => {
141 if let Some(inner_ty) = array_type.ty() {
142 return name_from_type(inner_ty, unknown_column);
143 }
144 }
145 ast::Type::PercentType(_) => return None,
148 ast::Type::ExprType(expr_type) => {
149 if let Some(expr) = expr_type.expr() {
150 return name_from_expr(expr, true).map(|(column, node)| {
151 let column = match column {
152 ColumnName::Column(c) => ColumnName::new(c, unknown_column),
153 _ => column,
154 };
155 (column, node)
156 });
157 }
158 }
159 }
160 None
161}
162
163fn name_from_name_ref(
164 name_ref: ast::NameRef,
165 in_type: bool,
166 arg_list: Option<&ast::ArgList>,
167) -> Option<(ColumnName, SyntaxNode)> {
168 if in_type {
169 for node in name_ref.syntax().children_with_tokens() {
170 match node.kind() {
171 SyntaxKind::BIGINT_KW => {
172 return Some((
173 ColumnName::Column("int8".to_owned()),
174 name_ref.syntax().clone(),
175 ));
176 }
177 SyntaxKind::BOOLEAN_KW => {
178 return Some((
179 ColumnName::Column("bool".to_owned()),
180 name_ref.syntax().clone(),
181 ));
182 }
183 SyntaxKind::DEC_KW | SyntaxKind::DECIMAL_KW => {
184 return Some((
185 ColumnName::Column("numeric".to_owned()),
186 name_ref.syntax().clone(),
187 ));
188 }
189 SyntaxKind::FLOAT_KW => {
190 let precision = arg_list.and_then(|arg| {
191 arg.args_().find_map(|arg| {
192 if let ast::Expr::Literal(lit) = arg.expr()? {
193 lit.syntax().text().to_string().parse::<u32>().ok()
194 } else {
195 None
196 }
197 })
198 });
199 let name = if matches!(precision, Some(p) if p <= 24) {
200 "float4"
201 } else {
202 "float8"
203 };
204 return Some((
205 ColumnName::Column(name.to_owned()),
206 name_ref.syntax().clone(),
207 ));
208 }
209 SyntaxKind::INT_KW | SyntaxKind::INTEGER_KW => {
210 return Some((
211 ColumnName::Column("int4".to_owned()),
212 name_ref.syntax().clone(),
213 ));
214 }
215 SyntaxKind::SMALLINT_KW => {
216 return Some((
217 ColumnName::Column("int2".to_owned()),
218 name_ref.syntax().clone(),
219 ));
220 }
221 SyntaxKind::REAL_KW => {
222 return Some((
223 ColumnName::Column("float4".to_owned()),
224 name_ref.syntax().clone(),
225 ));
226 }
227 _ => (),
228 }
229 }
230 }
231 let text = name_ref.text();
232 let normalized = normalize_identifier(&text);
233 return Some((ColumnName::Column(normalized), name_ref.syntax().clone()));
234}
235
236fn 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 assert_snapshot!(name("array(select 1)"), @"array");
529 assert_snapshot!(name("array[1, 2, 3]"), @"array");
530
531 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 assert_snapshot!(name("-42"), @"?column?");
538 assert_snapshot!(name("|/ 42"), @"?column?");
539 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 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 assert_snapshot!(name("(1 * 2)"), @"?column?");
553 assert_snapshot!(name("(select 1 as a)"), @"a");
554
555 assert_snapshot!(name("count(*)"), @"count");
557 assert_snapshot!(name("schema.func_name(1)"), @"func_name");
558
559 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 assert_snapshot!(name("foo[bar]"), @"foo");
590 assert_snapshot!(name("foo[1]"), @"foo");
591
592 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 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 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 assert_snapshot!(name(r#"'foo' as "FOO""#), @"FOO");
625 assert_snapshot!(name(r#"'foo' as "foo""#), @"foo");
626 assert_snapshot!(name(r#"'foo' as FOO"#), @"foo");
628 assert_snapshot!(name(r#"'foo' as foo"#), @"foo");
629
630 assert_snapshot!(name("(1, 2, 3)"), @"row");
632 assert_snapshot!(name("(1, 2, 3)::address"), @"row");
633
634 assert_snapshot!(name("(x).city"), @"city");
636
637 assert_snapshot!(name("'{{1, 2}, {3, 4}}'::int[]"), @"int4");
639 assert_snapshot!(name("cast('{foo}' as text[])"), @"text");
640
641 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 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 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 assert_snapshot!(name("cast(1.5 as double precision)"), @"float8");
665 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 assert_snapshot!(name("cast('1 hour' as interval hour to minute)"), @"interval");
672
673 assert_snapshot!(name("cast(foo as schema.%TYPE)"), @"foo");
675
676 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}