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