1use std::borrow::Cow;
28
29#[cfg(test)]
30use insta::assert_snapshot;
31use rowan::{GreenNodeData, GreenTokenData, NodeOrToken};
32
33#[cfg(test)]
34use crate::SourceFile;
35use rowan::Direction;
36
37use crate::ast;
38use crate::ast::AstNode;
39use crate::{SyntaxKind, SyntaxNode, SyntaxToken, TokenText};
40
41use super::support;
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum LitKind {
45 BitString(SyntaxToken),
46 ByteString(SyntaxToken),
47 DollarQuotedString(SyntaxToken),
48 EscString(SyntaxToken),
49 FloatNumber(SyntaxToken),
50 IntNumber(SyntaxToken),
51 Null(SyntaxToken),
52 PositionalParam(SyntaxToken),
53 String(SyntaxToken),
54 UnicodeEscString(SyntaxToken),
55}
56
57impl ast::Literal {
58 pub fn kind(&self) -> Option<LitKind> {
59 let token = self.syntax().first_child_or_token()?.into_token()?;
60 let kind = match token.kind() {
61 SyntaxKind::STRING => LitKind::String(token),
62 SyntaxKind::NULL_KW => LitKind::Null(token),
63 SyntaxKind::FLOAT_NUMBER => LitKind::FloatNumber(token),
64 SyntaxKind::INT_NUMBER => LitKind::IntNumber(token),
65 SyntaxKind::BYTE_STRING => LitKind::ByteString(token),
66 SyntaxKind::BIT_STRING => LitKind::BitString(token),
67 SyntaxKind::DOLLAR_QUOTED_STRING => LitKind::DollarQuotedString(token),
68 SyntaxKind::UNICODE_ESC_STRING => LitKind::UnicodeEscString(token),
69 SyntaxKind::ESC_STRING => LitKind::EscString(token),
70 SyntaxKind::POSITIONAL_PARAM => LitKind::PositionalParam(token),
71 _ => return None,
72 };
73 Some(kind)
74 }
75}
76
77impl ast::Constraint {
78 #[inline]
79 pub fn constraint_name(&self) -> Option<ast::ConstraintName> {
80 support::child(self.syntax())
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum BinOp {
86 And(SyntaxToken),
87 AtTimeZone(ast::AtTimeZone),
88 Caret(SyntaxToken),
89 Collate(SyntaxToken),
90 ColonColon(ast::ColonColon),
91 ColonEq(ast::ColonEq),
92 CustomOp(ast::CustomOp),
93 Eq(SyntaxToken),
94 FatArrow(ast::FatArrow),
95 Gteq(ast::Gteq),
96 Ilike(SyntaxToken),
97 In(SyntaxToken),
98 Is(SyntaxToken),
99 IsDistinctFrom(ast::IsDistinctFrom),
100 IsNot(ast::IsNot),
101 IsNotDistinctFrom(ast::IsNotDistinctFrom),
102 LAngle(SyntaxToken),
103 Like(SyntaxToken),
104 Lteq(ast::Lteq),
105 Minus(SyntaxToken),
106 Neq(ast::Neq),
107 Neqb(ast::Neqb),
108 NotIlike(ast::NotIlike),
109 NotIn(ast::NotIn),
110 NotLike(ast::NotLike),
111 NotSimilarTo(ast::NotSimilarTo),
112 OperatorCall(ast::OperatorCall),
113 Or(SyntaxToken),
114 Overlaps(SyntaxToken),
115 Percent(SyntaxToken),
116 Plus(SyntaxToken),
117 RAngle(SyntaxToken),
118 SimilarTo(ast::SimilarTo),
119 Slash(SyntaxToken),
120 Star(SyntaxToken),
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum PostfixOp {
125 AtLocal(SyntaxToken),
126 IsJson(ast::IsJson),
127 IsJsonArray(ast::IsJsonArray),
128 IsJsonObject(ast::IsJsonObject),
129 IsJsonScalar(ast::IsJsonScalar),
130 IsJsonValue(ast::IsJsonValue),
131 IsNormalized(ast::IsNormalized),
132 IsNotJson(ast::IsNotJson),
133 IsNotJsonArray(ast::IsNotJsonArray),
134 IsNotJsonObject(ast::IsNotJsonObject),
135 IsNotJsonScalar(ast::IsNotJsonScalar),
136 IsNotJsonValue(ast::IsNotJsonValue),
137 IsNotNormalized(ast::IsNotNormalized),
138 IsNull(SyntaxToken),
139 NotNull(SyntaxToken),
140}
141
142impl ast::BinExpr {
143 #[inline]
144 pub fn lhs(&self) -> Option<ast::Expr> {
145 support::children(self.syntax()).next()
146 }
147
148 #[inline]
149 pub fn rhs(&self) -> Option<ast::Expr> {
150 support::children(self.syntax()).nth(1)
151 }
152
153 pub fn op(&self) -> Option<BinOp> {
154 let lhs = self.lhs()?;
155 for child in lhs.syntax().siblings_with_tokens(Direction::Next).skip(1) {
156 match child {
157 NodeOrToken::Token(token) => {
158 let op = match token.kind() {
159 SyntaxKind::AND_KW => BinOp::And(token),
160 SyntaxKind::CARET => BinOp::Caret(token),
161 SyntaxKind::COLLATE_KW => BinOp::Collate(token),
162 SyntaxKind::EQ => BinOp::Eq(token),
163 SyntaxKind::ILIKE_KW => BinOp::Ilike(token),
164 SyntaxKind::IN_KW => BinOp::In(token),
165 SyntaxKind::IS_KW => BinOp::Is(token),
166 SyntaxKind::L_ANGLE => BinOp::LAngle(token),
167 SyntaxKind::LIKE_KW => BinOp::Like(token),
168 SyntaxKind::MINUS => BinOp::Minus(token),
169 SyntaxKind::OR_KW => BinOp::Or(token),
170 SyntaxKind::OVERLAPS_KW => BinOp::Overlaps(token),
171 SyntaxKind::PERCENT => BinOp::Percent(token),
172 SyntaxKind::PLUS => BinOp::Plus(token),
173 SyntaxKind::R_ANGLE => BinOp::RAngle(token),
174 SyntaxKind::SLASH => BinOp::Slash(token),
175 SyntaxKind::STAR => BinOp::Star(token),
176 _ => continue,
177 };
178 return Some(op);
179 }
180 NodeOrToken::Node(node) => {
181 let op = match node.kind() {
182 SyntaxKind::AT_TIME_ZONE => {
183 BinOp::AtTimeZone(ast::AtTimeZone { syntax: node })
184 }
185 SyntaxKind::COLON_COLON => {
186 BinOp::ColonColon(ast::ColonColon { syntax: node })
187 }
188 SyntaxKind::COLON_EQ => BinOp::ColonEq(ast::ColonEq { syntax: node }),
189 SyntaxKind::CUSTOM_OP => BinOp::CustomOp(ast::CustomOp { syntax: node }),
190 SyntaxKind::FAT_ARROW => BinOp::FatArrow(ast::FatArrow { syntax: node }),
191 SyntaxKind::GTEQ => BinOp::Gteq(ast::Gteq { syntax: node }),
192 SyntaxKind::IS_DISTINCT_FROM => {
193 BinOp::IsDistinctFrom(ast::IsDistinctFrom { syntax: node })
194 }
195 SyntaxKind::IS_NOT => BinOp::IsNot(ast::IsNot { syntax: node }),
196 SyntaxKind::IS_NOT_DISTINCT_FROM => {
197 BinOp::IsNotDistinctFrom(ast::IsNotDistinctFrom { syntax: node })
198 }
199 SyntaxKind::LTEQ => BinOp::Lteq(ast::Lteq { syntax: node }),
200 SyntaxKind::NEQ => BinOp::Neq(ast::Neq { syntax: node }),
201 SyntaxKind::NEQB => BinOp::Neqb(ast::Neqb { syntax: node }),
202 SyntaxKind::NOT_ILIKE => BinOp::NotIlike(ast::NotIlike { syntax: node }),
203 SyntaxKind::NOT_IN => BinOp::NotIn(ast::NotIn { syntax: node }),
204 SyntaxKind::NOT_LIKE => BinOp::NotLike(ast::NotLike { syntax: node }),
205 SyntaxKind::NOT_SIMILAR_TO => {
206 BinOp::NotSimilarTo(ast::NotSimilarTo { syntax: node })
207 }
208 SyntaxKind::OPERATOR_CALL => {
209 BinOp::OperatorCall(ast::OperatorCall { syntax: node })
210 }
211 SyntaxKind::SIMILAR_TO => BinOp::SimilarTo(ast::SimilarTo { syntax: node }),
212 _ => continue,
213 };
214 return Some(op);
215 }
216 }
217 }
218 None
219 }
220}
221
222impl ast::PostfixExpr {
223 pub fn op(&self) -> Option<PostfixOp> {
224 let lhs = self.expr()?;
225
226 let siblings = lhs.syntax().siblings_with_tokens(Direction::Next).skip(1);
227 for child in siblings {
228 match child {
229 NodeOrToken::Token(token) => {
230 let op = match token.kind() {
231 SyntaxKind::AT_KW => PostfixOp::AtLocal(token),
232 SyntaxKind::ISNULL_KW => PostfixOp::IsNull(token),
233 SyntaxKind::NOTNULL_KW => PostfixOp::NotNull(token),
234 _ => continue,
235 };
236 return Some(op);
237 }
238 NodeOrToken::Node(node) => {
239 let op = match node.kind() {
240 SyntaxKind::IS_JSON => PostfixOp::IsJson(ast::IsJson { syntax: node }),
241 SyntaxKind::IS_JSON_ARRAY => {
242 PostfixOp::IsJsonArray(ast::IsJsonArray { syntax: node })
243 }
244 SyntaxKind::IS_JSON_OBJECT => {
245 PostfixOp::IsJsonObject(ast::IsJsonObject { syntax: node })
246 }
247 SyntaxKind::IS_JSON_SCALAR => {
248 PostfixOp::IsJsonScalar(ast::IsJsonScalar { syntax: node })
249 }
250 SyntaxKind::IS_JSON_VALUE => {
251 PostfixOp::IsJsonValue(ast::IsJsonValue { syntax: node })
252 }
253 SyntaxKind::IS_NORMALIZED => {
254 PostfixOp::IsNormalized(ast::IsNormalized { syntax: node })
255 }
256 SyntaxKind::IS_NOT_JSON => {
257 PostfixOp::IsNotJson(ast::IsNotJson { syntax: node })
258 }
259 SyntaxKind::IS_NOT_JSON_ARRAY => {
260 PostfixOp::IsNotJsonArray(ast::IsNotJsonArray { syntax: node })
261 }
262 SyntaxKind::IS_NOT_JSON_OBJECT => {
263 PostfixOp::IsNotJsonObject(ast::IsNotJsonObject { syntax: node })
264 }
265 SyntaxKind::IS_NOT_JSON_SCALAR => {
266 PostfixOp::IsNotJsonScalar(ast::IsNotJsonScalar { syntax: node })
267 }
268 SyntaxKind::IS_NOT_JSON_VALUE => {
269 PostfixOp::IsNotJsonValue(ast::IsNotJsonValue { syntax: node })
270 }
271 SyntaxKind::IS_NOT_NORMALIZED => {
272 PostfixOp::IsNotNormalized(ast::IsNotNormalized { syntax: node })
273 }
274 _ => continue,
275 };
276 return Some(op);
277 }
278 }
279 }
280
281 None
282 }
283}
284
285impl ast::FieldExpr {
286 #[inline]
290 pub fn base(&self) -> Option<ast::Expr> {
291 support::children(self.syntax()).next()
292 }
293 #[inline]
294 pub fn field(&self) -> Option<ast::NameRef> {
295 support::children(self.syntax()).last()
296 }
297}
298
299impl ast::IndexExpr {
300 #[inline]
301 pub fn base(&self) -> Option<ast::Expr> {
302 support::children(&self.syntax).next()
303 }
304 #[inline]
305 pub fn index(&self) -> Option<ast::Expr> {
306 support::children(&self.syntax).nth(1)
307 }
308}
309
310impl ast::SliceExpr {
311 #[inline]
312 pub fn base(&self) -> Option<ast::Expr> {
313 support::children(&self.syntax).next()
314 }
315
316 #[inline]
317 pub fn start(&self) -> Option<ast::Expr> {
318 let colon = self.colon_token()?;
323 support::children(&self.syntax)
324 .skip(1)
325 .find(|expr: &ast::Expr| expr.syntax().text_range().end() <= colon.text_range().start())
326 }
327
328 #[inline]
329 pub fn end(&self) -> Option<ast::Expr> {
330 let colon = self.colon_token()?;
333 support::children(&self.syntax)
334 .find(|expr: &ast::Expr| expr.syntax().text_range().start() >= colon.text_range().end())
335 }
336}
337
338impl ast::RenameColumn {
339 #[inline]
340 pub fn from(&self) -> Option<ast::NameRef> {
341 support::children(&self.syntax).nth(0)
342 }
343 #[inline]
344 pub fn to(&self) -> Option<ast::NameRef> {
345 support::children(&self.syntax).nth(1)
346 }
347}
348
349impl ast::ForeignKeyConstraint {
350 #[inline]
351 pub fn from_columns(&self) -> Option<ast::ColumnList> {
352 support::children(&self.syntax).nth(0)
353 }
354 #[inline]
355 pub fn to_columns(&self) -> Option<ast::ColumnList> {
356 support::children(&self.syntax).nth(1)
357 }
358}
359
360impl ast::BetweenExpr {
361 #[inline]
362 pub fn target(&self) -> Option<ast::Expr> {
363 support::children(&self.syntax).nth(0)
364 }
365 #[inline]
366 pub fn start(&self) -> Option<ast::Expr> {
367 support::children(&self.syntax).nth(1)
368 }
369 #[inline]
370 pub fn end(&self) -> Option<ast::Expr> {
371 support::children(&self.syntax).nth(2)
372 }
373}
374
375impl ast::WhenClause {
376 #[inline]
377 pub fn condition(&self) -> Option<ast::Expr> {
378 support::children(&self.syntax).next()
379 }
380 #[inline]
381 pub fn then(&self) -> Option<ast::Expr> {
382 support::children(&self.syntax).nth(1)
383 }
384}
385
386impl ast::CompoundSelect {
387 #[inline]
388 pub fn lhs(&self) -> Option<ast::SelectVariant> {
389 support::children(&self.syntax).next()
390 }
391 #[inline]
392 pub fn rhs(&self) -> Option<ast::SelectVariant> {
393 support::children(&self.syntax).nth(1)
394 }
395}
396
397impl ast::NameRef {
398 #[inline]
399 pub fn text(&self) -> TokenText<'_> {
400 text_of_first_token(self.syntax())
401 }
402}
403
404impl ast::Name {
405 #[inline]
406 pub fn text(&self) -> TokenText<'_> {
407 text_of_first_token(self.syntax())
408 }
409}
410
411impl ast::CharType {
412 #[inline]
413 pub fn text(&self) -> TokenText<'_> {
414 text_of_first_token(self.syntax())
415 }
416}
417
418impl ast::OpSig {
419 #[inline]
420 pub fn lhs(&self) -> Option<ast::Type> {
421 support::children(self.syntax()).next()
422 }
423
424 #[inline]
425 pub fn rhs(&self) -> Option<ast::Type> {
426 support::children(self.syntax()).nth(1)
427 }
428}
429
430impl ast::CastSig {
431 #[inline]
432 pub fn lhs(&self) -> Option<ast::Type> {
433 support::children(self.syntax()).next()
434 }
435
436 #[inline]
437 pub fn rhs(&self) -> Option<ast::Type> {
438 support::children(self.syntax()).nth(1)
439 }
440}
441
442pub(crate) fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> {
443 fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData {
444 green_ref
445 .children()
446 .next()
447 .and_then(NodeOrToken::into_token)
448 .unwrap()
449 }
450
451 match node.green() {
452 Cow::Borrowed(green_ref) => TokenText::borrowed(first_token(green_ref).text()),
453 Cow::Owned(green) => TokenText::owned(first_token(&green).to_owned()),
454 }
455}
456
457impl ast::WithQuery {
458 #[inline]
459 pub fn with_clause(&self) -> Option<ast::WithClause> {
460 support::child(self.syntax())
461 }
462}
463
464impl ast::HasParamList for ast::FunctionSig {}
465impl ast::HasParamList for ast::Aggregate {}
466
467impl ast::NameLike for ast::Name {}
468impl ast::NameLike for ast::NameRef {}
469
470impl ast::HasWithClause for ast::Select {}
471impl ast::HasWithClause for ast::SelectInto {}
472impl ast::HasWithClause for ast::Insert {}
473impl ast::HasWithClause for ast::Update {}
474impl ast::HasWithClause for ast::Delete {}
475
476impl ast::HasCreateTable for ast::CreateTable {}
477impl ast::HasCreateTable for ast::CreateForeignTable {}
478impl ast::HasCreateTable for ast::CreateTableLike {}
479
480#[test]
481fn index_expr() {
482 let source_code = "
483 select foo[bar];
484 ";
485 let parse = SourceFile::parse(source_code);
486 assert!(parse.errors().is_empty());
487 let file: SourceFile = parse.tree();
488 let stmt = file.stmts().next().unwrap();
489 let ast::Stmt::Select(select) = stmt else {
490 unreachable!()
491 };
492 let select_clause = select.select_clause().unwrap();
493 let target = select_clause
494 .target_list()
495 .unwrap()
496 .targets()
497 .next()
498 .unwrap();
499 let ast::Expr::IndexExpr(index_expr) = target.expr().unwrap() else {
500 unreachable!()
501 };
502 let base = index_expr.base().unwrap();
503 let index = index_expr.index().unwrap();
504 assert_eq!(base.syntax().text(), "foo");
505 assert_eq!(index.syntax().text(), "bar");
506}
507
508#[test]
509fn slice_expr() {
510 use insta::assert_snapshot;
511 let source_code = "
512 select x[1:2], x[2:], x[:3], x[:];
513 ";
514 let parse = SourceFile::parse(source_code);
515 assert!(parse.errors().is_empty());
516 let file: SourceFile = parse.tree();
517 let stmt = file.stmts().next().unwrap();
518 let ast::Stmt::Select(select) = stmt else {
519 unreachable!()
520 };
521 let select_clause = select.select_clause().unwrap();
522 let mut targets = select_clause.target_list().unwrap().targets();
523
524 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
525 unreachable!()
526 };
527 assert_snapshot!(slice.syntax(), @"x[1:2]");
528 assert_eq!(slice.base().unwrap().syntax().text(), "x");
529 assert_eq!(slice.start().unwrap().syntax().text(), "1");
530 assert_eq!(slice.end().unwrap().syntax().text(), "2");
531
532 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
533 unreachable!()
534 };
535 assert_snapshot!(slice.syntax(), @"x[2:]");
536 assert_eq!(slice.base().unwrap().syntax().text(), "x");
537 assert_eq!(slice.start().unwrap().syntax().text(), "2");
538 assert!(slice.end().is_none());
539
540 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
541 unreachable!()
542 };
543 assert_snapshot!(slice.syntax(), @"x[:3]");
544 assert_eq!(slice.base().unwrap().syntax().text(), "x");
545 assert!(slice.start().is_none());
546 assert_eq!(slice.end().unwrap().syntax().text(), "3");
547
548 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
549 unreachable!()
550 };
551 assert_snapshot!(slice.syntax(), @"x[:]");
552 assert_eq!(slice.base().unwrap().syntax().text(), "x");
553 assert!(slice.start().is_none());
554 assert!(slice.end().is_none());
555}
556
557#[test]
558fn field_expr() {
559 let source_code = "
560 select foo.bar;
561 ";
562 let parse = SourceFile::parse(source_code);
563 assert!(parse.errors().is_empty());
564 let file: SourceFile = parse.tree();
565 let stmt = file.stmts().next().unwrap();
566 let ast::Stmt::Select(select) = stmt else {
567 unreachable!()
568 };
569 let select_clause = select.select_clause().unwrap();
570 let target = select_clause
571 .target_list()
572 .unwrap()
573 .targets()
574 .next()
575 .unwrap();
576 let ast::Expr::FieldExpr(field_expr) = target.expr().unwrap() else {
577 unreachable!()
578 };
579 let base = field_expr.base().unwrap();
580 let field = field_expr.field().unwrap();
581 assert_eq!(base.syntax().text(), "foo");
582 assert_eq!(field.syntax().text(), "bar");
583}
584
585#[test]
586fn between_expr() {
587 let source_code = "
588 select 2 between 1 and 3;
589 ";
590 let parse = SourceFile::parse(source_code);
591 assert!(parse.errors().is_empty());
592 let file: SourceFile = parse.tree();
593 let stmt = file.stmts().next().unwrap();
594 let ast::Stmt::Select(select) = stmt else {
595 unreachable!()
596 };
597 let select_clause = select.select_clause().unwrap();
598 let target = select_clause
599 .target_list()
600 .unwrap()
601 .targets()
602 .next()
603 .unwrap();
604 let ast::Expr::BetweenExpr(between_expr) = target.expr().unwrap() else {
605 unreachable!()
606 };
607 let target = between_expr.target().unwrap();
608 let start = between_expr.start().unwrap();
609 let end = between_expr.end().unwrap();
610 assert_eq!(target.syntax().text(), "2");
611 assert_eq!(start.syntax().text(), "1");
612 assert_eq!(end.syntax().text(), "3");
613}
614
615#[test]
616fn cast_expr() {
617 use insta::assert_snapshot;
618
619 let cast = extract_expr("select cast('123' as int)");
620 assert!(cast.expr().is_some());
621 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
622 assert!(cast.ty().is_some());
623 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
624
625 let cast = extract_expr("select cast('123' as pg_catalog.int4)");
626 assert!(cast.expr().is_some());
627 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
628 assert!(cast.ty().is_some());
629 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
630
631 let cast = extract_expr("select int '123'");
632 assert!(cast.expr().is_some());
633 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
634 assert!(cast.ty().is_some());
635 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
636
637 let cast = extract_expr("select pg_catalog.int4 '123'");
638 assert!(cast.expr().is_some());
639 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
640 assert!(cast.ty().is_some());
641 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
642
643 let cast = extract_expr("select '123'::int");
644 assert!(cast.expr().is_some());
645 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
646 assert!(cast.ty().is_some());
647 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
648
649 let cast = extract_expr("select '123'::int4");
650 assert!(cast.expr().is_some());
651 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
652 assert!(cast.ty().is_some());
653 assert_snapshot!(cast.ty().unwrap().syntax(), @"int4");
654
655 let cast = extract_expr("select '123'::pg_catalog.int4");
656 assert!(cast.expr().is_some());
657 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
658 assert!(cast.ty().is_some());
659 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
660
661 let cast = extract_expr("select '{123}'::pg_catalog.varchar(10)[]");
662 assert!(cast.expr().is_some());
663 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
664 assert!(cast.ty().is_some());
665 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)[]");
666
667 let cast = extract_expr("select cast('{123}' as pg_catalog.varchar(10)[])");
668 assert!(cast.expr().is_some());
669 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
670 assert!(cast.ty().is_some());
671 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)[]");
672
673 let cast = extract_expr("select pg_catalog.varchar(10) '{123}'");
674 assert!(cast.expr().is_some());
675 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
676 assert!(cast.ty().is_some());
677 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)");
678
679 let cast = extract_expr("select interval '1' month");
680 assert!(cast.expr().is_some());
681 assert_snapshot!(cast.expr().unwrap().syntax(), @"'1'");
682 assert!(cast.ty().is_some());
683 assert_snapshot!(cast.ty().unwrap().syntax(), @"interval");
684
685 fn extract_expr(sql: &str) -> ast::CastExpr {
686 let parse = SourceFile::parse(sql);
687 assert!(parse.errors().is_empty());
688 let file: SourceFile = parse.tree();
689 let node = file
690 .stmts()
691 .map(|x| match x {
692 ast::Stmt::Select(select) => select
693 .select_clause()
694 .unwrap()
695 .target_list()
696 .unwrap()
697 .targets()
698 .next()
699 .unwrap()
700 .expr()
701 .unwrap()
702 .clone(),
703 _ => unreachable!(),
704 })
705 .next()
706 .unwrap();
707 match node {
708 ast::Expr::CastExpr(cast) => cast,
709 _ => unreachable!(),
710 }
711 }
712}
713
714#[test]
715fn op_sig() {
716 let source_code = "
717 alter operator p.+ (int4, int8)
718 owner to u;
719 ";
720 let parse = SourceFile::parse(source_code);
721 assert!(parse.errors().is_empty());
722 let file: SourceFile = parse.tree();
723 let stmt = file.stmts().next().unwrap();
724 let ast::Stmt::AlterOperator(alter_op) = stmt else {
725 unreachable!()
726 };
727 let op_sig = alter_op.op_sig().unwrap();
728 let lhs = op_sig.lhs().unwrap();
729 let rhs = op_sig.rhs().unwrap();
730 assert_snapshot!(lhs.syntax().text(), @"int4");
731 assert_snapshot!(rhs.syntax().text(), @"int8");
732}
733
734#[test]
735fn cast_sig() {
736 let source_code = "
737 drop cast (text as int);
738 ";
739 let parse = SourceFile::parse(source_code);
740 assert!(parse.errors().is_empty());
741 let file: SourceFile = parse.tree();
742 let stmt = file.stmts().next().unwrap();
743 let ast::Stmt::DropCast(alter_op) = stmt else {
744 unreachable!()
745 };
746 let cast_sig = alter_op.cast_sig().unwrap();
747 let lhs = cast_sig.lhs().unwrap();
748 let rhs = cast_sig.rhs().unwrap();
749 assert_snapshot!(lhs.syntax().text(), @"text");
750 assert_snapshot!(rhs.syntax().text(), @"int");
751}