1use std::borrow::Cow;
28
29use either::Either;
30#[cfg(test)]
31use insta::assert_snapshot;
32use rowan::{GreenNodeData, GreenTokenData, NodeOrToken};
33use squawk_line_index::{LineEnding, find_newline};
34
35#[cfg(test)]
36use crate::SourceFile;
37use rowan::Direction;
38
39use crate::ast;
40use crate::ast::AstNode;
41use crate::unescape::{escape_unicode_esc_str, uescape_char};
42use crate::{SyntaxKind, SyntaxNode, SyntaxToken, TokenText};
43
44use super::support;
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum LitKind {
48 BitString(SyntaxToken),
49 ByteString(SyntaxToken),
50 Default(SyntaxToken),
51 DollarQuotedString(SyntaxToken),
52 EscString(SyntaxToken),
53 False(SyntaxToken),
54 IntNumber(SyntaxToken),
55 NationalString(SyntaxToken),
56 Null(SyntaxToken),
57 NumericNumber(SyntaxToken),
58 PositionalParam(SyntaxToken),
59 String(SyntaxToken),
60 True(SyntaxToken),
61 UnicodeEscString(SyntaxToken),
62}
63
64impl ast::SourceFile {
65 pub fn line_ending(&self) -> LineEnding {
66 find_newline(&self.syntax().text().to_string())
67 .map(|(_, line_ending)| line_ending)
68 .unwrap_or_default()
69 }
70}
71
72impl ast::Literal {
73 pub fn kind(&self) -> Option<LitKind> {
74 let token = self.syntax().first_child_or_token()?.into_token()?;
75 let kind = match token.kind() {
76 SyntaxKind::BIT_STRING => LitKind::BitString(token),
77 SyntaxKind::BYTE_STRING => LitKind::ByteString(token),
78 SyntaxKind::DEFAULT_KW => LitKind::Default(token),
79 SyntaxKind::DOLLAR_QUOTED_STRING => LitKind::DollarQuotedString(token),
80 SyntaxKind::ESC_STRING => LitKind::EscString(token),
81 SyntaxKind::FALSE_KW => LitKind::False(token),
82 SyntaxKind::INT_NUMBER => LitKind::IntNumber(token),
83 SyntaxKind::NATIONAL_STRING => LitKind::NationalString(token),
84 SyntaxKind::NULL_KW => LitKind::Null(token),
85 SyntaxKind::NUMERIC_NUMBER => LitKind::NumericNumber(token),
86 SyntaxKind::POSITIONAL_PARAM => LitKind::PositionalParam(token),
87 SyntaxKind::STRING => LitKind::String(token),
88 SyntaxKind::TRUE_KW => LitKind::True(token),
89 SyntaxKind::UNICODE_ESC_STRING => LitKind::UnicodeEscString(token),
90 _ => return None,
91 };
92 Some(kind)
93 }
94}
95
96impl ast::Constraint {
97 #[inline]
98 pub fn constraint_name(&self) -> Option<ast::ConstraintName> {
99 support::child::<ast::ConstraintNameClause>(self.syntax())
100 .and_then(|clause| clause.constraint_name())
101 }
102
103 #[inline]
104 pub fn constraint_options(&self) -> ast::AstChildren<ast::ConstraintOption> {
105 match self {
106 ast::Constraint::CheckConstraint(it) => it.constraint_options(),
107 ast::Constraint::DefaultConstraint(it) => it.constraint_options(),
108 ast::Constraint::ExcludeConstraint(it) => it.constraint_options(),
109 ast::Constraint::ForeignKeyConstraint(it) => it.constraint_options(),
110 ast::Constraint::GeneratedConstraint(it) => it.constraint_options(),
111 ast::Constraint::NotNullConstraint(it) => it.constraint_options(),
112 ast::Constraint::NullConstraint(it) => it.constraint_options(),
113 ast::Constraint::PrimaryKeyConstraint(it) => it.constraint_options(),
114 ast::Constraint::ReferencesConstraint(it) => it.constraint_options(),
115 ast::Constraint::UniqueConstraint(it) => it.constraint_options(),
116 }
117 }
118
119 pub fn is_not_valid(&self) -> bool {
120 self.constraint_options()
121 .any(|option| matches!(option, ast::ConstraintOption::NotValid(_)))
122 }
123}
124
125impl ast::CreateSchema {
126 pub fn schema_name(&self) -> Option<SyntaxNode> {
127 match self.create_schema_target()? {
128 ast::CreateSchemaTarget::AuthorizationSchema(auth) => {
129 Some(auth.role()?.syntax().clone())
130 }
131 ast::CreateSchemaTarget::NamedSchema(named) => Some(named.schema()?.syntax().clone()),
132 }
133 }
134}
135
136impl ast::FromItem {
137 pub fn alias(&self) -> Option<ast::FromAlias> {
138 match self {
139 ast::FromItem::ExprFromItem(it) => it.alias(),
140 ast::FromItem::FunctionFromItem(it) => it.alias(),
141 ast::FromItem::GraphTableFromItem(it) => it.alias(),
142 ast::FromItem::JsonTableFromItem(it) => it.alias(),
143 ast::FromItem::ParenFromItem(it) => it.alias(),
144 ast::FromItem::RelationFromItem(it) => it.alias(),
145 ast::FromItem::RowsFromItem(it) => it.alias(),
146 ast::FromItem::XmlTableFromItem(it) => it.alias(),
147 }
148 }
149
150 pub fn with_ordinality(&self) -> Option<ast::WithOrdinality> {
151 match self {
152 ast::FromItem::FunctionFromItem(it) => it.with_ordinality(),
153 ast::FromItem::RowsFromItem(it) => it.with_ordinality(),
154 _ => None,
155 }
156 }
157}
158
159impl ast::ColumnDefList {
160 pub fn column_names(self) -> impl Iterator<Item = ast::ColumnName> {
161 self.column_defs().filter_map(|column| column.name())
162 }
163}
164
165impl ast::FromAliasColumns {
166 pub fn column_names(self) -> impl Iterator<Item = ast::ColumnName> {
167 match self {
168 ast::FromAliasColumns::ColumnList(it) => Either::Left(it.column_names()),
169 ast::FromAliasColumns::ColumnDefList(it) => Either::Right(it.column_names()),
170 }
171 }
172}
173
174impl ast::RelationFromItem {
175 pub fn path_ref(&self) -> Option<ast::PathRef> {
176 self.relation_name_ref()?.path_ref()
177 }
178
179 pub fn name_ref(&self) -> Option<ast::PathSegmentRef> {
180 self.path_ref()?.segment()
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub enum BinOp {
186 And(SyntaxToken),
187 AtTimeZone(ast::AtTimeZone),
188 Caret(SyntaxToken),
189 ColonColon(ast::ColonColon),
190 ColonEq(SyntaxToken),
191 CustomOp(ast::CustomOp),
192 Eq(SyntaxToken),
193 Escape(SyntaxToken),
194 FatArrow(SyntaxToken),
195 Gteq(SyntaxToken),
196 Ilike(SyntaxToken),
197 In(SyntaxToken),
198 Is(SyntaxToken),
199 IsDistinctFrom(ast::IsDistinctFrom),
200 IsNot(ast::IsNot),
201 IsNotDistinctFrom(ast::IsNotDistinctFrom),
202 LAngle(SyntaxToken),
203 Like(SyntaxToken),
204 Lteq(SyntaxToken),
205 Minus(SyntaxToken),
206 Neq(SyntaxToken),
207 Neqb(SyntaxToken),
208 NotIlike(ast::NotIlike),
209 NotIn(ast::NotIn),
210 NotLike(ast::NotLike),
211 NotSimilarTo(ast::NotSimilarTo),
212 OperatorCall(ast::OperatorCall),
213 Or(SyntaxToken),
214 Overlaps(SyntaxToken),
215 Percent(SyntaxToken),
216 Plus(SyntaxToken),
217 RAngle(SyntaxToken),
218 SimilarTo(ast::SimilarTo),
219 Slash(SyntaxToken),
220 Star(SyntaxToken),
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub enum PostfixOp {
225 AtLocal(ast::AtLocal),
226 IsJson(ast::IsJson),
227 IsJsonArray(ast::IsJsonArray),
228 IsJsonObject(ast::IsJsonObject),
229 IsJsonScalar(ast::IsJsonScalar),
230 IsJsonValue(ast::IsJsonValue),
231 IsNormalized(ast::IsNormalized),
232 IsNotJson(ast::IsNotJson),
233 IsNotJsonArray(ast::IsNotJsonArray),
234 IsNotJsonObject(ast::IsNotJsonObject),
235 IsNotJsonScalar(ast::IsNotJsonScalar),
236 IsNotJsonValue(ast::IsNotJsonValue),
237 IsNotNormalized(ast::IsNotNormalized),
238 IsNull(SyntaxToken),
239 NotNull(SyntaxToken),
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub enum PrefixOp {
244 CustomOp(ast::CustomOp),
245 Minus(SyntaxToken),
246 Not(SyntaxToken),
247 OperatorCall(ast::OperatorCall),
248 Plus(SyntaxToken),
249}
250
251impl ast::BinExpr {
252 #[inline]
253 pub fn lhs(&self) -> Option<ast::Expr> {
254 support::children(self.syntax()).next()
255 }
256
257 #[inline]
258 pub fn rhs(&self) -> Option<ast::Expr> {
259 support::children(self.syntax()).nth(1)
260 }
261
262 pub fn op(&self) -> Option<BinOp> {
263 let lhs = self.lhs()?;
264 for child in lhs.syntax().siblings_with_tokens(Direction::Next).skip(1) {
265 match child {
266 NodeOrToken::Token(token) => {
267 let op = match token.kind() {
268 SyntaxKind::AND_KW => BinOp::And(token),
269 SyntaxKind::CARET => BinOp::Caret(token),
270 SyntaxKind::COLON_EQ => BinOp::ColonEq(token),
271 SyntaxKind::EQ => BinOp::Eq(token),
272 SyntaxKind::ESCAPE_KW => BinOp::Escape(token),
273 SyntaxKind::FAT_ARROW => BinOp::FatArrow(token),
274 SyntaxKind::GTEQ => BinOp::Gteq(token),
275 SyntaxKind::ILIKE_KW => BinOp::Ilike(token),
276 SyntaxKind::IN_KW => BinOp::In(token),
277 SyntaxKind::IS_KW => BinOp::Is(token),
278 SyntaxKind::L_ANGLE => BinOp::LAngle(token),
279 SyntaxKind::LIKE_KW => BinOp::Like(token),
280 SyntaxKind::LTEQ => BinOp::Lteq(token),
281 SyntaxKind::MINUS => BinOp::Minus(token),
282 SyntaxKind::NEQ => BinOp::Neq(token),
283 SyntaxKind::NEQB => BinOp::Neqb(token),
284 SyntaxKind::OR_KW => BinOp::Or(token),
285 SyntaxKind::OVERLAPS_KW => BinOp::Overlaps(token),
286 SyntaxKind::PERCENT => BinOp::Percent(token),
287 SyntaxKind::PLUS => BinOp::Plus(token),
288 SyntaxKind::R_ANGLE => BinOp::RAngle(token),
289 SyntaxKind::SLASH => BinOp::Slash(token),
290 SyntaxKind::STAR => BinOp::Star(token),
291 _ => continue,
292 };
293 return Some(op);
294 }
295 NodeOrToken::Node(node) => {
296 let op = match node.kind() {
297 SyntaxKind::AT_TIME_ZONE => {
298 BinOp::AtTimeZone(ast::AtTimeZone { syntax: node })
299 }
300 SyntaxKind::COLON_COLON => {
301 BinOp::ColonColon(ast::ColonColon { syntax: node })
302 }
303 SyntaxKind::CUSTOM_OP => BinOp::CustomOp(ast::CustomOp { syntax: node }),
304 SyntaxKind::IS_DISTINCT_FROM => {
305 BinOp::IsDistinctFrom(ast::IsDistinctFrom { syntax: node })
306 }
307 SyntaxKind::IS_NOT => BinOp::IsNot(ast::IsNot { syntax: node }),
308 SyntaxKind::IS_NOT_DISTINCT_FROM => {
309 BinOp::IsNotDistinctFrom(ast::IsNotDistinctFrom { syntax: node })
310 }
311 SyntaxKind::NOT_ILIKE => BinOp::NotIlike(ast::NotIlike { syntax: node }),
312 SyntaxKind::NOT_IN => BinOp::NotIn(ast::NotIn { syntax: node }),
313 SyntaxKind::NOT_LIKE => BinOp::NotLike(ast::NotLike { syntax: node }),
314 SyntaxKind::NOT_SIMILAR_TO => {
315 BinOp::NotSimilarTo(ast::NotSimilarTo { syntax: node })
316 }
317 SyntaxKind::OPERATOR_CALL => {
318 BinOp::OperatorCall(ast::OperatorCall { syntax: node })
319 }
320 SyntaxKind::SIMILAR_TO => BinOp::SimilarTo(ast::SimilarTo { syntax: node }),
321 _ => continue,
322 };
323 return Some(op);
324 }
325 }
326 }
327 None
328 }
329}
330
331impl ast::PrefixExpr {
332 pub fn op(&self) -> Option<PrefixOp> {
333 for child in self.syntax().children_with_tokens() {
334 match child {
335 NodeOrToken::Token(token) => {
336 let op = match token.kind() {
337 SyntaxKind::MINUS => PrefixOp::Minus(token),
338 SyntaxKind::NOT_KW => PrefixOp::Not(token),
339 SyntaxKind::PLUS => PrefixOp::Plus(token),
340 _ => continue,
341 };
342 return Some(op);
343 }
344 NodeOrToken::Node(node) => {
345 let op = match node.kind() {
346 SyntaxKind::CUSTOM_OP => PrefixOp::CustomOp(ast::CustomOp { syntax: node }),
347 SyntaxKind::OPERATOR_CALL => {
348 PrefixOp::OperatorCall(ast::OperatorCall { syntax: node })
349 }
350 _ => continue,
351 };
352 return Some(op);
353 }
354 }
355 }
356 None
357 }
358}
359
360impl ast::PostfixExpr {
361 pub fn op(&self) -> Option<PostfixOp> {
362 let lhs = self.expr()?;
363
364 let siblings = lhs.syntax().siblings_with_tokens(Direction::Next).skip(1);
365 for child in siblings {
366 match child {
367 NodeOrToken::Token(token) => {
368 let op = match token.kind() {
369 SyntaxKind::ISNULL_KW => PostfixOp::IsNull(token),
370 SyntaxKind::NOTNULL_KW => PostfixOp::NotNull(token),
371 _ => continue,
372 };
373 return Some(op);
374 }
375 NodeOrToken::Node(node) => {
376 let op = match node.kind() {
377 SyntaxKind::AT_LOCAL => PostfixOp::AtLocal(ast::AtLocal { syntax: node }),
378 SyntaxKind::IS_JSON => PostfixOp::IsJson(ast::IsJson { syntax: node }),
379 SyntaxKind::IS_JSON_ARRAY => {
380 PostfixOp::IsJsonArray(ast::IsJsonArray { syntax: node })
381 }
382 SyntaxKind::IS_JSON_OBJECT => {
383 PostfixOp::IsJsonObject(ast::IsJsonObject { syntax: node })
384 }
385 SyntaxKind::IS_JSON_SCALAR => {
386 PostfixOp::IsJsonScalar(ast::IsJsonScalar { syntax: node })
387 }
388 SyntaxKind::IS_JSON_VALUE => {
389 PostfixOp::IsJsonValue(ast::IsJsonValue { syntax: node })
390 }
391 SyntaxKind::IS_NORMALIZED => {
392 PostfixOp::IsNormalized(ast::IsNormalized { syntax: node })
393 }
394 SyntaxKind::IS_NOT_JSON => {
395 PostfixOp::IsNotJson(ast::IsNotJson { syntax: node })
396 }
397 SyntaxKind::IS_NOT_JSON_ARRAY => {
398 PostfixOp::IsNotJsonArray(ast::IsNotJsonArray { syntax: node })
399 }
400 SyntaxKind::IS_NOT_JSON_OBJECT => {
401 PostfixOp::IsNotJsonObject(ast::IsNotJsonObject { syntax: node })
402 }
403 SyntaxKind::IS_NOT_JSON_SCALAR => {
404 PostfixOp::IsNotJsonScalar(ast::IsNotJsonScalar { syntax: node })
405 }
406 SyntaxKind::IS_NOT_JSON_VALUE => {
407 PostfixOp::IsNotJsonValue(ast::IsNotJsonValue { syntax: node })
408 }
409 SyntaxKind::IS_NOT_NORMALIZED => {
410 PostfixOp::IsNotNormalized(ast::IsNotNormalized { syntax: node })
411 }
412 _ => continue,
413 };
414 return Some(op);
415 }
416 }
417 }
418
419 None
420 }
421}
422
423impl ast::FieldExpr {
424 #[inline]
428 pub fn base(&self) -> Option<ast::Expr> {
429 support::children(self.syntax()).next()
430 }
431 #[inline]
432 pub fn field(&self) -> Option<ast::NameRef> {
433 support::children(self.syntax()).last()
434 }
435}
436
437impl ast::IndexExpr {
438 #[inline]
439 pub fn base(&self) -> Option<ast::Expr> {
440 support::children(&self.syntax).next()
441 }
442 #[inline]
443 pub fn index(&self) -> Option<ast::Expr> {
444 support::children(&self.syntax).nth(1)
445 }
446}
447
448impl ast::SliceExpr {
449 #[inline]
450 pub fn base(&self) -> Option<ast::Expr> {
451 support::children(&self.syntax).next()
452 }
453
454 #[inline]
455 pub fn start(&self) -> Option<ast::Expr> {
456 let colon = self.colon_token()?;
461 support::children(&self.syntax)
462 .skip(1)
463 .find(|expr: &ast::Expr| expr.syntax().text_range().end() <= colon.text_range().start())
464 }
465
466 #[inline]
467 pub fn end(&self) -> Option<ast::Expr> {
468 let colon = self.colon_token()?;
471 support::children(&self.syntax)
472 .find(|expr: &ast::Expr| expr.syntax().text_range().start() >= colon.text_range().end())
473 }
474}
475
476impl ast::RenameValue {
477 #[inline]
478 pub fn from(&self) -> Option<ast::Literal> {
479 support::children(&self.syntax).nth(0)
480 }
481 #[inline]
482 pub fn to(&self) -> Option<ast::Literal> {
483 support::children(&self.syntax).nth(1)
484 }
485}
486
487impl ast::ForeignKeyConstraint {
488 #[inline]
489 pub fn from_columns(&self) -> Option<ast::ForeignKeyColumnList> {
490 support::children(&self.syntax).nth(0)
491 }
492 #[inline]
493 pub fn to_columns(&self) -> Option<ast::ForeignKeyColumnList> {
494 support::children(&self.syntax).nth(1)
495 }
496}
497
498impl ast::XmlPiFn {
499 #[inline]
500 pub fn target(&self) -> Option<ast::XmlPiTarget> {
501 support::child(&self.syntax)
502 }
503}
504
505impl ast::BetweenExpr {
506 #[inline]
507 pub fn target(&self) -> Option<ast::Expr> {
508 support::children(&self.syntax).nth(0)
509 }
510 #[inline]
511 pub fn start(&self) -> Option<ast::Expr> {
512 support::children(&self.syntax).nth(1)
513 }
514 #[inline]
515 pub fn end(&self) -> Option<ast::Expr> {
516 support::children(&self.syntax).nth(2)
517 }
518}
519
520impl ast::FrameBetween {
521 #[inline]
522 pub fn start(&self) -> Option<ast::FrameBound> {
523 support::children(&self.syntax).nth(0)
524 }
525 #[inline]
526 pub fn end(&self) -> Option<ast::FrameBound> {
527 support::children(&self.syntax).nth(1)
528 }
529}
530
531impl ast::WhenClause {
532 #[inline]
533 pub fn condition(&self) -> Option<ast::Expr> {
534 support::children(&self.syntax).next()
535 }
536 #[inline]
537 pub fn then(&self) -> Option<ast::Expr> {
538 support::children(&self.syntax).nth(1)
539 }
540}
541
542impl ast::ReturningOption {
543 #[inline]
544 pub fn name(&self) -> Option<ast::TableAlias> {
545 match self {
546 ast::ReturningOption::ReturningOld(it) => it.name(),
547 ast::ReturningOption::ReturningNew(it) => it.name(),
548 }
549 }
550}
551
552impl ast::CompoundSelect {
553 #[inline]
554 pub fn lhs(&self) -> Option<ast::SelectVariant> {
555 support::children(&self.syntax).next()
556 }
557 #[inline]
558 pub fn rhs(&self) -> Option<ast::SelectVariant> {
559 support::children(&self.syntax).nth(1)
560 }
561 #[inline]
562 pub fn op(&self) -> Option<ast::CompoundOp> {
563 support::child(&self.syntax)
564 }
565}
566
567impl ast::NameRef {
568 #[inline]
569 pub fn text(&self) -> String {
570 normalize_name_node(self.syntax())
571 }
572
573 #[inline]
574 pub fn is_quoted(&self) -> bool {
575 is_quoted(self.syntax())
576 }
577}
578
579impl ast::ColumnName {
580 #[inline]
581 pub fn text(&self) -> String {
582 normalize_name_node(self.syntax())
583 }
584
585 #[inline]
586 pub fn is_quoted(&self) -> bool {
587 is_quoted(self.syntax())
588 }
589}
590
591impl ast::PathSegment {
592 #[inline]
593 pub fn text(&self) -> String {
594 normalize_name_node(self.syntax())
595 }
596
597 #[inline]
598 pub fn is_quoted(&self) -> bool {
599 is_quoted(self.syntax())
600 }
601}
602
603impl ast::PathSegmentRef {
604 #[inline]
605 pub fn text(&self) -> String {
606 normalize_name_node(self.syntax())
607 }
608
609 #[inline]
610 pub fn is_quoted(&self) -> bool {
611 is_quoted(self.syntax())
612 }
613}
614
615fn is_quoted(node: &SyntaxNode) -> bool {
616 let text = node.text();
617 let first = text.char_at(0.into());
618 let second = text.char_at(1.into());
619 matches!(
620 (first, second),
621 (Some('u' | 'U'), Some('"')) | (Some('"'), Some(_))
622 )
623}
624
625pub(crate) fn normalize_name_node(node: &SyntaxNode) -> String {
627 let mut tokens = node
628 .children_with_tokens()
629 .filter_map(|el| el.into_token())
630 .filter(|t| !t.kind().is_trivia());
631
632 let Some(mut ident_token) = tokens.next() else {
633 return String::new();
634 };
635 if matches!(node.kind(), SyntaxKind::ROLE | SyntaxKind::ROLE_REF)
638 && ident_token.kind() == SyntaxKind::GROUP_KW
639 {
640 let Some(role_name) = tokens.next() else {
641 return String::new();
642 };
643 ident_token = role_name;
644 }
645 let raw = ident_token.text();
646
647 let unicode_inner = raw
648 .strip_prefix(['u', 'U'])
649 .and_then(|s| s.strip_prefix("&\""))
650 .and_then(|s| s.strip_suffix('"'));
651
652 if let Some(inner) = unicode_inner {
653 let mut escape_char = '\\';
654 if let Some(uesc) = tokens.next()
655 && uesc.kind() == SyntaxKind::UESCAPE_KW
656 && let Some(token) = tokens.next()
657 && let Some(ch) = uescape_char(token.text())
658 {
659 escape_char = ch;
660 }
661
662 let inner = inner.replace(r#""""#, "\"");
663 let mut result = String::with_capacity(inner.len());
664 escape_unicode_esc_str(&inner, escape_char, |_range, r| {
665 if let Ok(ch) = r {
666 result.push(ch);
667 }
668 });
669 return result;
670 }
671
672 raw.strip_prefix('"')
673 .and_then(|t| t.strip_suffix('"'))
674 .map(|x| x.replace(r#""""#, "\""))
675 .unwrap_or_else(|| raw.to_ascii_lowercase())
676}
677
678impl ast::CharType {
679 #[inline]
680 pub fn text(&self) -> TokenText<'_> {
681 text_of_first_token(self.syntax())
682 }
683}
684
685fn string_literal_contents(token: &SyntaxToken) -> Option<&str> {
686 match token.kind() {
687 SyntaxKind::STRING => token.text().strip_prefix('\'')?.strip_suffix('\''),
688 SyntaxKind::ESC_STRING | SyntaxKind::NATIONAL_STRING => {
689 token.text().get(2..)?.strip_suffix('\'')
690 }
691 SyntaxKind::UNICODE_ESC_STRING => token.text().get(3..)?.strip_suffix('\''),
692 SyntaxKind::DOLLAR_QUOTED_STRING => {
693 let text = token.text();
694 let rest = text.strip_prefix('$')?;
695 let tag_len = rest.find('$')?;
696 let delimiter = text.get(..=tag_len + 1)?;
697 text.get(delimiter.len()..)?.strip_suffix(delimiter)
698 }
699 _ => None,
700 }
701}
702
703fn is_falsey_token(token: &SyntaxToken) -> bool {
704 match token.kind() {
705 SyntaxKind::FALSE_KW | SyntaxKind::NO_KW | SyntaxKind::OFF_KW => true,
706 SyntaxKind::INT_NUMBER => token.text() == "0",
707 SyntaxKind::STRING
708 | SyntaxKind::ESC_STRING
709 | SyntaxKind::NATIONAL_STRING
710 | SyntaxKind::UNICODE_ESC_STRING
711 | SyntaxKind::DOLLAR_QUOTED_STRING => string_literal_contents(token)
712 .is_some_and(|text| matches!(text.to_ascii_lowercase().as_str(), "false" | "off")),
713 _ => false,
714 }
715}
716
717fn is_falsey_vacuum_option_value(value: &ast::VacuumOptionValue) -> bool {
718 value
719 .syntax()
720 .first_token()
721 .is_some_and(|token| is_falsey_token(&token))
722}
723
724impl ast::ReindexTarget {
725 pub fn concurrently_token(&self) -> Option<SyntaxToken> {
726 match self {
727 ast::ReindexTarget::ReindexTargetDatabase(it) => it.concurrently_token(),
728 ast::ReindexTarget::ReindexTargetIndex(it) => it.concurrently_token(),
729 ast::ReindexTarget::ReindexTargetSchema(it) => it.concurrently_token(),
730 ast::ReindexTarget::ReindexTargetSystem(it) => it.concurrently_token(),
731 ast::ReindexTarget::ReindexTargetTable(it) => it.concurrently_token(),
732 }
733 }
734}
735
736impl ast::Reindex {
737 pub fn is_concurrently(&self) -> bool {
738 self.reindex_target()
739 .is_some_and(|target| target.concurrently_token().is_some())
740 || self.reindex_option_list().is_some_and(|options| {
741 options.reindex_options().any(|option| match option {
742 ast::ReindexOption::ReindexOptionConcurrently(option) => {
743 !option.literal().is_some_and(|literal| {
744 literal
745 .syntax()
746 .first_token()
747 .is_some_and(|token| is_falsey_token(&token))
748 })
749 }
750 _ => false,
751 })
752 })
753 }
754}
755
756impl ast::Vacuum {
757 pub fn is_full(&self) -> bool {
758 self.full_token().is_some()
759 || self.vacuum_option_list().is_some_and(|opt_list| {
761 opt_list.vacuum_options().any(|opt| {
762 opt.vacuum_option_name().is_some_and(|name| {
763 name.syntax()
764 .first_token()
765 .is_some_and(|token| token.text().eq_ignore_ascii_case("full"))
766 }) && opt
767 .vacuum_option_value()
768 .is_none_or(|value| !is_falsey_vacuum_option_value(&value))
769 })
770 })
771 }
772}
773
774impl ast::OpSig {
775 #[inline]
776 pub fn lhs(&self) -> Option<ast::Type> {
777 support::children(self.syntax()).next()
778 }
779
780 #[inline]
781 pub fn rhs(&self) -> Option<ast::Type> {
782 support::children(self.syntax()).nth(1)
783 }
784}
785
786impl ast::CastSig {
787 #[inline]
788 pub fn lhs(&self) -> Option<ast::Type> {
789 support::children(self.syntax()).next()
790 }
791
792 #[inline]
793 pub fn rhs(&self) -> Option<ast::Type> {
794 support::children(self.syntax()).nth(1)
795 }
796}
797
798impl ast::ObjectOperator {
799 #[inline]
800 pub fn lhs(&self) -> Option<ast::Type> {
801 support::children(self.syntax()).next()
802 }
803
804 #[inline]
805 pub fn rhs(&self) -> Option<ast::Type> {
806 support::children(self.syntax()).nth(1)
807 }
808}
809
810impl ast::OpClassOptionOperator {
811 #[inline]
812 pub fn lhs(&self) -> Option<ast::Type> {
813 support::children(self.syntax()).next()
814 }
815
816 #[inline]
817 pub fn rhs(&self) -> Option<ast::Type> {
818 support::children(self.syntax()).nth(1)
819 }
820}
821
822impl ast::CreateConversion {
823 #[inline]
825 pub fn for_(&self) -> Option<ast::Literal> {
826 support::children(self.syntax()).next()
827 }
828
829 #[inline]
831 pub fn to(&self) -> Option<ast::Literal> {
832 support::children(self.syntax()).nth(1)
833 }
834}
835
836impl ast::ExtractFieldName {
837 pub fn text(&self) -> String {
838 normalize_name_node(self.syntax())
839 }
840}
841
842impl ast::PositionFn {
843 #[inline]
844 pub fn pos(&self) -> Option<ast::Expr> {
845 support::children(self.syntax()).next()
846 }
847
848 #[inline]
849 pub fn string(&self) -> Option<ast::Expr> {
850 support::children(self.syntax()).nth(1)
851 }
852}
853
854impl ast::SubstringForFrom {
855 #[inline]
856 pub fn string(&self) -> Option<ast::Expr> {
857 support::children(self.syntax()).next()
858 }
859
860 #[inline]
861 pub fn count(&self) -> Option<ast::Expr> {
862 support::children(self.syntax()).nth(1)
863 }
864
865 #[inline]
866 pub fn start(&self) -> Option<ast::Expr> {
867 support::children(self.syntax()).nth(2)
868 }
869}
870
871impl ast::SubstringFromFor {
872 #[inline]
873 pub fn string(&self) -> Option<ast::Expr> {
874 support::children(self.syntax()).next()
875 }
876
877 #[inline]
878 pub fn start(&self) -> Option<ast::Expr> {
879 support::children(self.syntax()).nth(1)
880 }
881
882 #[inline]
883 pub fn count(&self) -> Option<ast::Expr> {
884 support::children(self.syntax()).nth(2)
885 }
886}
887
888impl ast::SubstringSimilarEscape {
889 #[inline]
890 pub fn string(&self) -> Option<ast::Expr> {
891 support::children(self.syntax()).next()
892 }
893
894 #[inline]
895 pub fn pattern(&self) -> Option<ast::Expr> {
896 support::children(self.syntax()).nth(1)
897 }
898
899 #[inline]
900 pub fn escape(&self) -> Option<ast::Expr> {
901 support::children(self.syntax()).nth(2)
902 }
903}
904
905impl ast::OverlayPlacing {
906 #[inline]
907 pub fn string(&self) -> Option<ast::Expr> {
908 support::children(self.syntax()).next()
909 }
910
911 #[inline]
912 pub fn placing(&self) -> Option<ast::Expr> {
913 support::children(self.syntax()).nth(1)
914 }
915
916 #[inline]
917 pub fn from(&self) -> Option<ast::Expr> {
918 support::children(self.syntax()).nth(2)
919 }
920
921 #[inline]
922 pub fn for_(&self) -> Option<ast::Expr> {
923 support::children(self.syntax()).nth(3)
924 }
925}
926
927impl ast::PartitionForValuesFrom {
928 #[inline]
929 pub fn from(&self) -> Option<ast::PartitionFromValues> {
930 support::child(self.syntax())
931 }
932
933 #[inline]
934 pub fn to(&self) -> Option<ast::PartitionToValues> {
935 support::child(self.syntax())
936 }
937}
938
939impl ast::PortionFromTo {
940 #[inline]
941 pub fn from(&self) -> Option<ast::Expr> {
942 support::children(self.syntax()).next()
943 }
944
945 #[inline]
946 pub fn to(&self) -> Option<ast::Expr> {
947 support::children(self.syntax()).nth(1)
948 }
949}
950
951impl ast::ReplaceDictionary {
952 #[inline]
953 pub fn before(&self) -> Option<ast::TextSearchDictionaryRef> {
954 support::children(self.syntax()).next()
955 }
956
957 #[inline]
958 pub fn after(&self) -> Option<ast::TextSearchDictionaryRef> {
959 support::children(self.syntax()).nth(1)
960 }
961}
962
963impl ast::Reassign {
964 #[inline]
965 pub fn before(&self) -> Option<ast::RoleRefList> {
966 support::children(self.syntax()).next()
967 }
968
969 #[inline]
970 pub fn after(&self) -> Option<ast::RoleRefList> {
971 support::children(self.syntax()).nth(1)
972 }
973}
974
975impl ast::AsObjFile {
976 #[inline]
977 pub fn obj_file(&self) -> Option<ast::Literal> {
978 support::children(self.syntax()).next()
979 }
980
981 #[inline]
982 pub fn link_symbol(&self) -> Option<ast::Literal> {
983 support::children(self.syntax()).nth(1)
984 }
985}
986
987impl ast::ColumnConstraint {
988 #[inline]
989 pub fn constraint_name(&self) -> Option<ast::ConstraintName> {
990 match self {
991 ast::ColumnConstraint::CheckConstraint(check_constraint) => check_constraint
992 .constraint_name_clause()
993 .and_then(|clause| clause.constraint_name()),
994 ast::ColumnConstraint::DefaultConstraint(default_constraint) => default_constraint
995 .constraint_name_clause()
996 .and_then(|clause| clause.constraint_name()),
997 ast::ColumnConstraint::ExcludeConstraint(exclude_constraint) => exclude_constraint
998 .constraint_name_clause()
999 .and_then(|clause| clause.constraint_name()),
1000 ast::ColumnConstraint::GeneratedConstraint(generated_constraint) => {
1001 generated_constraint
1002 .constraint_name_clause()
1003 .and_then(|clause| clause.constraint_name())
1004 }
1005 ast::ColumnConstraint::NotNullConstraint(not_null_constraint) => not_null_constraint
1006 .constraint_name_clause()
1007 .and_then(|clause| clause.constraint_name()),
1008 ast::ColumnConstraint::NullConstraint(null_constraint) => null_constraint
1009 .constraint_name_clause()
1010 .and_then(|clause| clause.constraint_name()),
1011 ast::ColumnConstraint::PrimaryKeyConstraint(primary_key_constraint) => {
1012 primary_key_constraint
1013 .constraint_name_clause()
1014 .and_then(|clause| clause.constraint_name())
1015 }
1016 ast::ColumnConstraint::ReferencesConstraint(references_constraint) => {
1017 references_constraint
1018 .constraint_name_clause()
1019 .and_then(|clause| clause.constraint_name())
1020 }
1021 ast::ColumnConstraint::UniqueConstraint(unique_constraint) => unique_constraint
1022 .constraint_name_clause()
1023 .and_then(|clause| clause.constraint_name()),
1024 }
1025 }
1026}
1027
1028impl ast::TableConstraint {
1029 #[inline]
1030 pub fn constraint_name(&self) -> Option<ast::ConstraintName> {
1031 match self {
1032 ast::TableConstraint::CheckConstraint(check_constraint) => check_constraint
1033 .constraint_name_clause()
1034 .and_then(|clause| clause.constraint_name()),
1035 ast::TableConstraint::ExcludeConstraint(exclude_constraint) => exclude_constraint
1036 .constraint_name_clause()
1037 .and_then(|clause| clause.constraint_name()),
1038 ast::TableConstraint::ForeignKeyConstraint(foreign_key_constraint) => {
1039 foreign_key_constraint
1040 .constraint_name_clause()
1041 .and_then(|clause| clause.constraint_name())
1042 }
1043 ast::TableConstraint::PrimaryKeyConstraint(primary_key_constraint) => {
1044 primary_key_constraint
1045 .constraint_name_clause()
1046 .and_then(|clause| clause.constraint_name())
1047 }
1048 ast::TableConstraint::UniqueConstraint(unique_constraint) => unique_constraint
1049 .constraint_name_clause()
1050 .and_then(|clause| clause.constraint_name()),
1051 }
1052 }
1053}
1054
1055pub(crate) fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> {
1056 fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData {
1057 green_ref
1058 .children()
1059 .next()
1060 .and_then(NodeOrToken::into_token)
1061 .unwrap()
1062 }
1063
1064 match node.green() {
1065 Cow::Borrowed(green_ref) => TokenText::borrowed(first_token(green_ref).text()),
1066 Cow::Owned(green) => TokenText::owned(first_token(&green).to_owned()),
1067 }
1068}
1069
1070impl ast::WithQuery {
1071 #[inline]
1072 pub fn with_clause(&self) -> Option<ast::WithClause> {
1073 support::child(self.syntax())
1074 }
1075}
1076
1077impl ast::CreateTableAsQuery {
1078 #[inline]
1079 pub fn select_variant(&self) -> Option<ast::SelectVariant> {
1080 match self {
1081 ast::CreateTableAsQuery::Execute(_) => None,
1082 ast::CreateTableAsQuery::SelectVariant(select_variant) => Some(select_variant.clone()),
1083 }
1084 }
1085}
1086
1087impl ast::SelectVariant {
1088 #[inline]
1089 pub fn target_list(&self) -> Option<ast::TargetList> {
1090 match self {
1091 ast::SelectVariant::Select(select) => {
1092 return select.select_clause()?.target_list();
1093 }
1094 ast::SelectVariant::SelectInto(select_into) => {
1095 return select_into.select_clause()?.target_list();
1096 }
1097 ast::SelectVariant::ParenSelect(paren_select) => {
1098 return paren_select.select()?.target_list();
1099 }
1100 _ => return None,
1101 }
1102 }
1103}
1104
1105impl ast::HasParamList {
1106 #[inline]
1107 pub fn param_list(&self) -> Option<ast::ParamList> {
1108 support::child(self.syntax())
1109 }
1110 #[inline]
1111 pub fn path(&self) -> Option<ast::Path> {
1112 match self {
1113 ast::HasParamList::CreateFunction(function) => function.name()?.path(),
1114 ast::HasParamList::CreateProcedure(procedure) => procedure.name()?.path(),
1115 _ => support::child(self.syntax()),
1116 }
1117 }
1118 #[inline]
1119 pub fn path_ref(&self) -> Option<ast::PathRef> {
1120 match self {
1121 ast::HasParamList::FunctionSig(signature) => signature.function_name_ref()?.path_ref(),
1122 ast::HasParamList::ProcedureSig(signature) => {
1123 signature.procedure_name_ref()?.path_ref()
1124 }
1125 ast::HasParamList::RoutineSig(signature) => signature.routine_name_ref()?.path_ref(),
1126 _ => support::child(self.syntax()),
1127 }
1128 }
1129}
1130
1131impl<T> ast::NameLike for T
1132where
1133 T: AstNode,
1134 ast::AnyName: From<T>,
1135{
1136 #[inline]
1137 fn text(&self) -> String {
1138 normalize_name_node(self.syntax())
1139 }
1140
1141 #[inline]
1142 fn is_quoted(&self) -> bool {
1143 is_quoted(self.syntax())
1144 }
1145}
1146
1147impl ast::HasWithClause for ast::Select {}
1148impl ast::HasWithClause for ast::SelectInto {}
1149impl ast::HasWithClause for ast::Insert {}
1150impl ast::HasWithClause for ast::Update {}
1151impl ast::HasWithClause for ast::Delete {}
1152
1153impl ast::HasCreateTable for ast::CreateTable {}
1154impl ast::HasCreateTable for ast::CreateForeignTable {}
1155impl ast::HasCreateTable for ast::CreateTableLike {}
1156
1157#[test]
1158fn name() {
1159 assert_snapshot!(extract_name("select 1 foo"), @"foo");
1160 assert_snapshot!(extract_name("select 1 FOO"), @"foo");
1161 assert_snapshot!(extract_name(r#"select 1 "foo""#), @"foo");
1162 assert_snapshot!(extract_name(r#"select 1 "Foo""#), @"Foo");
1163 assert_snapshot!(extract_name(r#"select 1 "FOO""#), @"FOO");
1164 assert_snapshot!(extract_name(r#"select 1 U&"\0066\006f\006f""#), @"foo");
1165 assert_snapshot!(extract_name(r#"select 1 U&"@0066@006f@006f" uescape '@'"#), @"foo");
1166
1167 fn extract_name(source_code: &str) -> String {
1168 let parse = SourceFile::parse(source_code);
1169 assert!(parse.errors().is_empty());
1170 let stmt = parse.tree().stmts().next().unwrap();
1171 let ast::Stmt::Select(select) = stmt else {
1172 unreachable!()
1173 };
1174 let name = select
1175 .select_clause()
1176 .unwrap()
1177 .target_list()
1178 .unwrap()
1179 .targets()
1180 .next()
1181 .unwrap()
1182 .as_name()
1183 .unwrap()
1184 .name()
1185 .unwrap();
1186 name.text().to_string()
1187 }
1188}
1189
1190#[test]
1191fn name_ref() {
1192 assert_snapshot!(extract_name_ref("select foo"), @"foo");
1193 assert_snapshot!(extract_name_ref("select FOO"), @"foo");
1194 assert_snapshot!(extract_name_ref(r#"select "foo""#), @"foo");
1195 assert_snapshot!(extract_name_ref(r#"select "Foo""#), @"Foo");
1196 assert_snapshot!(extract_name_ref(r#"select "FOO""#), @"FOO");
1197 assert_snapshot!(extract_name_ref(r#"select U&"\0066\006f\006f""#), @"foo");
1198 assert_snapshot!(extract_name_ref(r#"select U&"@0066@006f@006f" uescape '@'"#), @"foo");
1199
1200 fn extract_name_ref(source_code: &str) -> String {
1201 let parse = SourceFile::parse(source_code);
1202 assert!(parse.errors().is_empty());
1203 let stmt = parse.tree().stmts().next().unwrap();
1204 let ast::Stmt::Select(select) = stmt else {
1205 unreachable!()
1206 };
1207 let select_clause = select.select_clause().unwrap();
1208 let target = select_clause
1209 .target_list()
1210 .unwrap()
1211 .targets()
1212 .next()
1213 .unwrap();
1214 let ast::Expr::NameRef(name_ref) = target.expr().unwrap() else {
1215 unreachable!()
1216 };
1217 name_ref.text().to_string()
1218 }
1219}
1220
1221#[test]
1222fn unicode_quoted_name_keeps_doubled_single_quotes() {
1223 let parse = SourceFile::parse(r#"select 1 U&"a''b""#);
1224 assert!(parse.errors().is_empty());
1225 let stmt = parse.tree().stmts().next().unwrap();
1226 let ast::Stmt::Select(select) = stmt else {
1227 unreachable!()
1228 };
1229 let name = select
1230 .select_clause()
1231 .unwrap()
1232 .target_list()
1233 .unwrap()
1234 .targets()
1235 .next()
1236 .unwrap()
1237 .as_name()
1238 .unwrap()
1239 .name()
1240 .unwrap();
1241
1242 assert_snapshot!(name.text().to_string(), @"a''b");
1243}
1244
1245#[test]
1246fn index_expr() {
1247 let source_code = "
1248 select foo[bar];
1249 ";
1250 let parse = SourceFile::parse(source_code);
1251 assert!(parse.errors().is_empty());
1252 let stmt = parse.tree().stmts().next().unwrap();
1253 let ast::Stmt::Select(select) = stmt else {
1254 unreachable!()
1255 };
1256 let select_clause = select.select_clause().unwrap();
1257 let target = select_clause
1258 .target_list()
1259 .unwrap()
1260 .targets()
1261 .next()
1262 .unwrap();
1263 let ast::Expr::IndexExpr(index_expr) = target.expr().unwrap() else {
1264 unreachable!()
1265 };
1266 let base = index_expr.base().unwrap();
1267 let index = index_expr.index().unwrap();
1268 assert_eq!(base.syntax().text(), "foo");
1269 assert_eq!(index.syntax().text(), "bar");
1270}
1271
1272#[test]
1273fn slice_expr() {
1274 use insta::assert_snapshot;
1275 let source_code = "
1276 select x[1:2], x[2:], x[:3], x[:];
1277 ";
1278 let parse = SourceFile::parse(source_code);
1279 assert!(parse.errors().is_empty());
1280 let stmt = parse.tree().stmts().next().unwrap();
1281 let ast::Stmt::Select(select) = stmt else {
1282 unreachable!()
1283 };
1284 let select_clause = select.select_clause().unwrap();
1285 let mut targets = select_clause.target_list().unwrap().targets();
1286
1287 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1288 unreachable!()
1289 };
1290 assert_snapshot!(slice.syntax(), @"x[1:2]");
1291 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1292 assert_eq!(slice.start().unwrap().syntax().text(), "1");
1293 assert_eq!(slice.end().unwrap().syntax().text(), "2");
1294
1295 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1296 unreachable!()
1297 };
1298 assert_snapshot!(slice.syntax(), @"x[2:]");
1299 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1300 assert_eq!(slice.start().unwrap().syntax().text(), "2");
1301 assert!(slice.end().is_none());
1302
1303 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1304 unreachable!()
1305 };
1306 assert_snapshot!(slice.syntax(), @"x[:3]");
1307 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1308 assert!(slice.start().is_none());
1309 assert_eq!(slice.end().unwrap().syntax().text(), "3");
1310
1311 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1312 unreachable!()
1313 };
1314 assert_snapshot!(slice.syntax(), @"x[:]");
1315 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1316 assert!(slice.start().is_none());
1317 assert!(slice.end().is_none());
1318}
1319
1320#[test]
1321fn field_expr() {
1322 let source_code = "
1323 select foo.bar;
1324 ";
1325 let parse = SourceFile::parse(source_code);
1326 assert!(parse.errors().is_empty());
1327 let stmt = parse.tree().stmts().next().unwrap();
1328 let ast::Stmt::Select(select) = stmt else {
1329 unreachable!()
1330 };
1331 let select_clause = select.select_clause().unwrap();
1332 let target = select_clause
1333 .target_list()
1334 .unwrap()
1335 .targets()
1336 .next()
1337 .unwrap();
1338 let ast::Expr::FieldExpr(field_expr) = target.expr().unwrap() else {
1339 unreachable!()
1340 };
1341 let base = field_expr.base().unwrap();
1342 let field = field_expr.field().unwrap();
1343 assert_eq!(base.syntax().text(), "foo");
1344 assert_eq!(field.syntax().text(), "bar");
1345}
1346
1347#[test]
1348fn between_expr() {
1349 let source_code = "
1350 select 2 between 1 and 3;
1351 ";
1352 let parse = SourceFile::parse(source_code);
1353 assert!(parse.errors().is_empty());
1354 let stmt = parse.tree().stmts().next().unwrap();
1355 let ast::Stmt::Select(select) = stmt else {
1356 unreachable!()
1357 };
1358 let select_clause = select.select_clause().unwrap();
1359 let target = select_clause
1360 .target_list()
1361 .unwrap()
1362 .targets()
1363 .next()
1364 .unwrap();
1365 let ast::Expr::BetweenExpr(between_expr) = target.expr().unwrap() else {
1366 unreachable!()
1367 };
1368 let target = between_expr.target().unwrap();
1369 let start = between_expr.start().unwrap();
1370 let end = between_expr.end().unwrap();
1371 assert_eq!(target.syntax().text(), "2");
1372 assert_eq!(start.syntax().text(), "1");
1373 assert_eq!(end.syntax().text(), "3");
1374}
1375
1376#[test]
1377fn cast_expr() {
1378 use insta::assert_snapshot;
1379
1380 let cast = extract_expr("select cast('123' as int)");
1381 assert!(cast.expr().is_some());
1382 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1383 assert!(cast.ty().is_some());
1384 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
1385
1386 let cast = extract_expr("select cast('123' as pg_catalog.int4)");
1387 assert!(cast.expr().is_some());
1388 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1389 assert!(cast.ty().is_some());
1390 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
1391
1392 let cast = extract_expr("select int '123'");
1393 assert!(cast.expr().is_some());
1394 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1395 assert!(cast.ty().is_some());
1396 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
1397
1398 let cast = extract_expr("select pg_catalog.int4 '123'");
1399 assert!(cast.expr().is_some());
1400 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1401 assert!(cast.ty().is_some());
1402 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
1403
1404 let cast = extract_expr("select '123'::int");
1405 assert!(cast.expr().is_some());
1406 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1407 assert!(cast.ty().is_some());
1408 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
1409
1410 let cast = extract_expr("select '123'::int4");
1411 assert!(cast.expr().is_some());
1412 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1413 assert!(cast.ty().is_some());
1414 assert_snapshot!(cast.ty().unwrap().syntax(), @"int4");
1415
1416 let cast = extract_expr("select '123'::pg_catalog.int4");
1417 assert!(cast.expr().is_some());
1418 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1419 assert!(cast.ty().is_some());
1420 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
1421
1422 let cast = extract_expr("select '{123}'::pg_catalog.varchar(10)[]");
1423 assert!(cast.expr().is_some());
1424 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
1425 assert!(cast.ty().is_some());
1426 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)[]");
1427
1428 let cast = extract_expr("select cast('{123}' as pg_catalog.varchar(10)[])");
1429 assert!(cast.expr().is_some());
1430 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
1431 assert!(cast.ty().is_some());
1432 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)[]");
1433
1434 let cast = extract_expr("select pg_catalog.varchar(10) '{123}'");
1435 assert!(cast.expr().is_some());
1436 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
1437 assert!(cast.ty().is_some());
1438 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)");
1439
1440 let cast = extract_expr("select interval '1' month");
1441 assert!(cast.expr().is_some());
1442 assert_snapshot!(cast.expr().unwrap().syntax(), @"'1'");
1443 assert!(cast.ty().is_some());
1444 assert_snapshot!(cast.ty().unwrap().syntax(), @"interval");
1445
1446 fn extract_expr(sql: &str) -> ast::CastExpr {
1447 let parse = SourceFile::parse(sql);
1448 assert!(parse.errors().is_empty());
1449 let node = parse
1450 .tree()
1451 .stmts()
1452 .map(|x| match x {
1453 ast::Stmt::Select(select) => select
1454 .select_clause()
1455 .unwrap()
1456 .target_list()
1457 .unwrap()
1458 .targets()
1459 .next()
1460 .unwrap()
1461 .expr()
1462 .unwrap()
1463 .clone(),
1464 _ => unreachable!(),
1465 })
1466 .next()
1467 .unwrap();
1468 match node {
1469 ast::Expr::CastExpr(cast) => cast,
1470 _ => unreachable!(),
1471 }
1472 }
1473}
1474
1475#[test]
1476fn op_sig() {
1477 let source_code = "
1478 alter operator p.+ (int4, int8)
1479 owner to u;
1480 ";
1481 let parse = SourceFile::parse(source_code);
1482 assert!(parse.errors().is_empty());
1483 let stmt = parse.tree().stmts().next().unwrap();
1484 let ast::Stmt::AlterOperator(alter_op) = stmt else {
1485 unreachable!()
1486 };
1487 let op_sig = alter_op.op_sig().unwrap();
1488 let lhs = op_sig.lhs().unwrap();
1489 let rhs = op_sig.rhs().unwrap();
1490 assert_snapshot!(lhs.syntax().text(), @"int4");
1491 assert_snapshot!(rhs.syntax().text(), @"int8");
1492}
1493
1494#[test]
1495fn cast_sig() {
1496 let source_code = "
1497 drop cast (text as int);
1498 ";
1499 let parse = SourceFile::parse(source_code);
1500 assert!(parse.errors().is_empty());
1501 let stmt = parse.tree().stmts().next().unwrap();
1502 let ast::Stmt::DropCast(alter_op) = stmt else {
1503 unreachable!()
1504 };
1505 let cast_sig = alter_op.cast_sig().unwrap();
1506 let lhs = cast_sig.lhs().unwrap();
1507 let rhs = cast_sig.rhs().unwrap();
1508 assert_snapshot!(lhs.syntax().text(), @"text");
1509 assert_snapshot!(rhs.syntax().text(), @"int");
1510}
1511
1512#[cfg(test)]
1513fn extract_vacuum(sql: &str) -> ast::Vacuum {
1514 let parse = SourceFile::parse(sql);
1515 assert!(parse.errors().is_empty());
1516 let stmt = parse.tree().stmts().next().unwrap();
1517 let ast::Stmt::Vacuum(vacuum) = stmt else {
1518 unreachable!()
1519 };
1520 vacuum
1521}
1522
1523#[test]
1524fn vacuum_full_is_full() {
1525 assert!(extract_vacuum("VACUUM FULL foo;").is_full());
1526}
1527
1528#[test]
1529fn vacuum_option_list_full_is_full() {
1530 assert!(extract_vacuum("VACUUM (FULL) foo;").is_full());
1531}
1532
1533#[test]
1534fn vacuum_full_true_is_full() {
1535 assert!(extract_vacuum("VACUUM (FULL TRUE) foo;").is_full());
1536}
1537
1538#[test]
1539fn vacuum_full_on_is_full() {
1540 assert!(extract_vacuum("VACUUM (FULL ON) foo;").is_full());
1541}
1542
1543#[test]
1544fn vacuum_full_1_is_full() {
1545 assert!(extract_vacuum("VACUUM (FULL 1) foo;").is_full());
1546}
1547
1548#[test]
1549fn vacuum_no_full_is_not_full() {
1550 assert!(!extract_vacuum("VACUUM foo;").is_full());
1551}
1552
1553#[test]
1554fn vacuum_other_option_is_not_full() {
1555 assert!(!extract_vacuum("VACUUM (FREEZE) foo;").is_full());
1556}
1557
1558#[test]
1559fn vacuum_full_false_is_not_full() {
1560 assert!(!extract_vacuum("VACUUM (FULL FALSE) foo;").is_full());
1561}
1562
1563#[test]
1564fn vacuum_full_off_is_not_full() {
1565 assert!(!extract_vacuum("VACUUM (FULL OFF) foo;").is_full());
1566}
1567
1568#[test]
1569fn vacuum_full_no_is_not_full() {
1570 assert!(!extract_vacuum("VACUUM (FULL NO) foo;").is_full());
1571}
1572
1573#[test]
1574fn vacuum_full_quoted_off_is_not_full() {
1575 assert!(!extract_vacuum("VACUUM (FULL 'off') foo;").is_full());
1576}
1577
1578#[test]
1579fn vacuum_full_escaped_string_off_is_not_full() {
1580 assert!(!extract_vacuum("VACUUM (FULL E'off') foo;").is_full());
1581}
1582
1583#[test]
1584fn vacuum_full_unicode_escaped_string_off_is_not_full() {
1585 assert!(!extract_vacuum("VACUUM (FULL U&'off') foo;").is_full());
1586}
1587
1588#[test]
1589fn vacuum_full_dollar_quoted_off_is_not_full() {
1590 assert!(!extract_vacuum("VACUUM (FULL $$off$$) t;").is_full());
1591}
1592
1593#[test]
1594fn vacuum_full_0_is_not_full() {
1595 assert!(!extract_vacuum("VACUUM (FULL 0) foo;").is_full());
1596}