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