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
46fn children_either<L: AstNode, R: AstNode>(
47 parent: &SyntaxNode,
48) -> impl Iterator<Item = Either<L, R>> {
49 parent.children().filter_map(|child| {
50 L::cast(child.clone())
51 .map(Either::Left)
52 .or_else(|| R::cast(child).map(Either::Right))
53 })
54}
55
56impl ast::Param {
57 pub fn mode_and_name(&self) -> impl Iterator<Item = Either<ast::ParamMode, ast::ParamName>> {
58 children_either(self.syntax())
59 }
60}
61
62impl ast::Do {
63 pub fn language_and_body(&self) -> impl Iterator<Item = Either<ast::DoLanguage, ast::Literal>> {
64 children_either(self.syntax())
65 }
66}
67
68impl ast::CustomOp {
69 pub fn tokens(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
70 self.syntax()
71 .children_with_tokens()
72 .filter_map(|element| element.into_token())
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum CastKind {
78 Cast,
79 DoubleColon,
80 Treat,
81 TypeLiteral,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum LitKind {
86 BitString(SyntaxToken),
87 ByteString(SyntaxToken),
88 Default(SyntaxToken),
89 DollarQuotedString(SyntaxToken),
90 EscString(SyntaxToken),
91 False(SyntaxToken),
92 IntNumber(SyntaxToken),
93 NationalString(SyntaxToken),
94 Null(SyntaxToken),
95 NumericNumber(SyntaxToken),
96 Off(SyntaxToken),
97 On(SyntaxToken),
98 PositionalParam(SyntaxToken),
99 String(SyntaxToken),
100 True(SyntaxToken),
101 UnicodeEscString(SyntaxToken),
102}
103
104impl ast::SourceFile {
105 pub fn line_ending(&self) -> LineEnding {
106 find_newline(&self.syntax().text().to_string())
107 .map(|(_, line_ending)| line_ending)
108 .unwrap_or_default()
109 }
110}
111
112impl ast::TransactionMode {
113 pub fn comma_after(&self) -> Option<SyntaxToken> {
114 for element in self.syntax().siblings_with_tokens(Direction::Next).skip(1) {
115 match element {
116 NodeOrToken::Token(token)
117 if matches!(token.kind(), SyntaxKind::COMMENT | SyntaxKind::WHITESPACE) => {}
118 NodeOrToken::Token(token) if token.kind() == SyntaxKind::COMMA => {
119 return Some(token);
120 }
121 _ => return None,
122 }
123 }
124 None
125 }
126}
127
128impl ast::Type {
129 pub fn arg_list(&self) -> Option<ast::ArgList> {
130 match self {
131 ast::Type::BitType(ty) => ty.arg_list(),
132 ast::Type::BitVaryingType(ty) => ty.arg_list(),
133 ast::Type::CharacterType(ty) => ty.arg_list(),
134 ast::Type::PathType(ty) => ty.arg_list(),
135 ast::Type::VarcharType(ty) => ty.arg_list(),
136 ast::Type::ArrayType(_)
137 | ast::Type::DoubleType(_)
138 | ast::Type::IntervalType(_)
139 | ast::Type::TimeType(_)
140 | ast::Type::TimestampType(_) => None,
141 }
142 }
143}
144
145impl ast::Arg {
146 pub fn expr(&self) -> Option<ast::Expr> {
147 match self.func_arg_expr()? {
148 ast::FuncArgExpr::Expr(expr) => Some(expr),
149 ast::FuncArgExpr::NamedArg(_) => None,
150 }
151 }
152}
153
154impl ast::CastExpr {
155 pub fn kind(&self) -> Option<CastKind> {
156 if self.cast_token().is_some() {
157 Some(CastKind::Cast)
158 } else if self.treat_token().is_some() {
159 Some(CastKind::Treat)
160 } else if self.colon_colon().is_some() {
161 Some(CastKind::DoubleColon)
162 } else if self.ty().is_some() && self.literal().is_some() {
163 Some(CastKind::TypeLiteral)
164 } else {
165 None
166 }
167 }
168}
169
170impl ast::Literal {
171 pub fn kind(&self) -> Option<LitKind> {
172 let token = self.syntax().first_child_or_token()?.into_token()?;
173 let kind = match token.kind() {
174 SyntaxKind::BIT_STRING => LitKind::BitString(token),
175 SyntaxKind::BYTE_STRING => LitKind::ByteString(token),
176 SyntaxKind::DEFAULT_KW => LitKind::Default(token),
177 SyntaxKind::DOLLAR_QUOTED_STRING => LitKind::DollarQuotedString(token),
178 SyntaxKind::ESC_STRING => LitKind::EscString(token),
179 SyntaxKind::FALSE_KW => LitKind::False(token),
180 SyntaxKind::INT_NUMBER => LitKind::IntNumber(token),
181 SyntaxKind::NATIONAL_STRING => LitKind::NationalString(token),
182 SyntaxKind::NULL_KW => LitKind::Null(token),
183 SyntaxKind::NUMERIC_NUMBER => LitKind::NumericNumber(token),
184 SyntaxKind::OFF_KW => LitKind::Off(token),
185 SyntaxKind::ON_KW => LitKind::On(token),
186 SyntaxKind::POSITIONAL_PARAM => LitKind::PositionalParam(token),
187 SyntaxKind::STRING => LitKind::String(token),
188 SyntaxKind::TRUE_KW => LitKind::True(token),
189 SyntaxKind::UNICODE_ESC_STRING => LitKind::UnicodeEscString(token),
190 _ => return None,
191 };
192 Some(kind)
193 }
194}
195
196impl ast::Constraint {
197 #[inline]
198 pub fn constraint_name(&self) -> Option<ast::ConstraintName> {
199 support::child::<ast::ConstraintNameClause>(self.syntax())
200 .and_then(|clause| clause.constraint_name())
201 }
202
203 #[inline]
204 pub fn constraint_options(&self) -> ast::AstChildren<ast::ConstraintOption> {
205 match self {
206 ast::Constraint::CheckConstraint(it) => it.constraint_options(),
207 ast::Constraint::DefaultConstraint(it) => it.constraint_options(),
208 ast::Constraint::ExcludeConstraint(it) => it.constraint_options(),
209 ast::Constraint::ForeignKeyConstraint(it) => it.constraint_options(),
210 ast::Constraint::GeneratedConstraint(it) => it.constraint_options(),
211 ast::Constraint::NotNullConstraint(it) => it.constraint_options(),
212 ast::Constraint::NullConstraint(it) => it.constraint_options(),
213 ast::Constraint::PrimaryKeyConstraint(it) => it.constraint_options(),
214 ast::Constraint::ReferencesConstraint(it) => it.constraint_options(),
215 ast::Constraint::UniqueConstraint(it) => it.constraint_options(),
216 }
217 }
218
219 pub fn is_not_valid(&self) -> bool {
220 self.constraint_options()
221 .any(|option| matches!(option, ast::ConstraintOption::NotValid(_)))
222 }
223}
224
225impl ast::CreateSchema {
226 pub fn schema_name(&self) -> Option<SyntaxNode> {
227 match self.create_schema_target()? {
228 ast::CreateSchemaTarget::AuthorizationSchema(auth) => {
229 Some(auth.role()?.syntax().clone())
230 }
231 ast::CreateSchemaTarget::NamedSchema(named) => Some(named.schema()?.syntax().clone()),
232 }
233 }
234}
235
236impl ast::AlterSetStatistics {
237 pub fn literal(&self) -> Option<ast::Literal> {
238 match self.expr()? {
239 ast::Expr::Literal(literal) => Some(literal),
240 _ => None,
241 }
242 }
243}
244
245impl ast::Restart {
246 pub fn literal(&self) -> Option<ast::Literal> {
247 match self.expr()? {
248 ast::Expr::Literal(literal) => Some(literal),
249 _ => None,
250 }
251 }
252}
253
254impl ast::FromItem {
255 pub fn alias(&self) -> Option<ast::FromAlias> {
256 match self {
257 ast::FromItem::ExprFromItem(it) => it.alias(),
258 ast::FromItem::FunctionFromItem(it) => it.alias(),
259 ast::FromItem::GraphTableFromItem(it) => it.alias(),
260 ast::FromItem::JsonTableFromItem(it) => it.alias(),
261 ast::FromItem::ParenFromItem(it) => it.alias(),
262 ast::FromItem::RelationFromItem(it) => it.alias(),
263 ast::FromItem::RowsFromItem(it) => it.alias(),
264 ast::FromItem::XmlTableFromItem(it) => it.alias(),
265 }
266 }
267
268 pub fn with_ordinality(&self) -> Option<ast::WithOrdinality> {
269 match self {
270 ast::FromItem::FunctionFromItem(it) => it.with_ordinality(),
271 ast::FromItem::RowsFromItem(it) => it.with_ordinality(),
272 _ => None,
273 }
274 }
275}
276
277impl ast::RowList {
278 pub fn trailing_comma_token(&self) -> Option<SyntaxToken> {
279 self.syntax()
280 .last_token()
281 .filter(|token| token.kind() == SyntaxKind::COMMA)
282 }
283}
284
285impl ast::TableAndColumnsList {
286 pub fn trailing_comma_token(&self) -> Option<SyntaxToken> {
287 self.syntax()
288 .last_token()
289 .filter(|token| token.kind() == SyntaxKind::COMMA)
290 }
291}
292
293impl ast::GroupByList {
294 pub fn trailing_comma_token(&self) -> Option<SyntaxToken> {
295 self.syntax()
296 .last_token()
297 .filter(|token| token.kind() == SyntaxKind::COMMA)
298 }
299}
300
301impl ast::SortByList {
302 pub fn trailing_comma_token(&self) -> Option<SyntaxToken> {
303 self.syntax()
304 .last_token()
305 .filter(|token| token.kind() == SyntaxKind::COMMA)
306 }
307}
308
309impl ast::ColumnDefList {
310 pub fn column_names(self) -> impl Iterator<Item = ast::ColumnName> {
311 self.column_defs().filter_map(|column| column.name())
312 }
313}
314
315impl ast::FromAliasColumns {
316 pub fn column_names(self) -> impl Iterator<Item = ast::ColumnName> {
317 match self {
318 ast::FromAliasColumns::ColumnList(it) => Either::Left(it.column_names()),
319 ast::FromAliasColumns::ColumnDefList(it) => Either::Right(it.column_names()),
320 }
321 }
322}
323
324impl ast::RelationFromItem {
325 pub fn path_ref(&self) -> Option<ast::PathRef> {
326 self.relation_name_ref()?.path_ref()
327 }
328
329 pub fn name_ref(&self) -> Option<ast::PathSegmentRef> {
330 self.path_ref()?.segment()
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
335pub enum BinOp {
336 And(SyntaxToken),
337 AtTimeZone(ast::AtTimeZone),
338 Caret(SyntaxToken),
339 ColonColon(ast::ColonColon),
340 ColonEq(SyntaxToken),
341 CustomOp(ast::CustomOp),
342 Eq(SyntaxToken),
343 Escape(SyntaxToken),
344 FatArrow(SyntaxToken),
345 Gteq(SyntaxToken),
346 Ilike(SyntaxToken),
347 In(SyntaxToken),
348 Is(SyntaxToken),
349 IsDistinctFrom(ast::IsDistinctFrom),
350 IsNot(ast::IsNot),
351 IsNotDistinctFrom(ast::IsNotDistinctFrom),
352 LAngle(SyntaxToken),
353 Like(SyntaxToken),
354 Lteq(SyntaxToken),
355 Minus(SyntaxToken),
356 Neq(SyntaxToken),
357 Neqb(SyntaxToken),
358 NotIlike(ast::NotIlike),
359 NotIn(ast::NotIn),
360 NotLike(ast::NotLike),
361 NotSimilarTo(ast::NotSimilarTo),
362 OperatorCall(ast::OperatorCall),
363 Or(SyntaxToken),
364 Overlaps(SyntaxToken),
365 Percent(SyntaxToken),
366 Plus(SyntaxToken),
367 RAngle(SyntaxToken),
368 SimilarTo(ast::SimilarTo),
369 Slash(SyntaxToken),
370 Star(SyntaxToken),
371}
372
373#[derive(Debug, Clone, PartialEq, Eq)]
374pub enum PostfixOp {
375 AtLocal(ast::AtLocal),
376 IsJson(ast::IsJson),
377 IsJsonArray(ast::IsJsonArray),
378 IsJsonObject(ast::IsJsonObject),
379 IsJsonScalar(ast::IsJsonScalar),
380 IsJsonValue(ast::IsJsonValue),
381 IsNormalized(ast::IsNormalized),
382 IsNotJson(ast::IsNotJson),
383 IsNotJsonArray(ast::IsNotJsonArray),
384 IsNotJsonObject(ast::IsNotJsonObject),
385 IsNotJsonScalar(ast::IsNotJsonScalar),
386 IsNotJsonValue(ast::IsNotJsonValue),
387 IsNotNormalized(ast::IsNotNormalized),
388 IsNull(SyntaxToken),
389 NotNull(SyntaxToken),
390}
391
392#[derive(Debug, Clone, PartialEq, Eq)]
393pub enum PrefixOp {
394 CustomOp(ast::CustomOp),
395 Minus(SyntaxToken),
396 Not(SyntaxToken),
397 OperatorCall(ast::OperatorCall),
398 Plus(SyntaxToken),
399}
400
401impl ast::BinExpr {
402 #[inline]
403 pub fn lhs(&self) -> Option<ast::Expr> {
404 support::children(self.syntax()).next()
405 }
406
407 #[inline]
408 pub fn rhs(&self) -> Option<ast::Expr> {
409 support::children(self.syntax()).nth(1)
410 }
411
412 pub fn op(&self) -> Option<BinOp> {
413 let lhs = self.lhs()?;
414 for child in lhs.syntax().siblings_with_tokens(Direction::Next).skip(1) {
415 match child {
416 NodeOrToken::Token(token) => {
417 let op = match token.kind() {
418 SyntaxKind::AND_KW => BinOp::And(token),
419 SyntaxKind::CARET => BinOp::Caret(token),
420 SyntaxKind::COLON_EQ => BinOp::ColonEq(token),
421 SyntaxKind::EQ => BinOp::Eq(token),
422 SyntaxKind::ESCAPE_KW => BinOp::Escape(token),
423 SyntaxKind::FAT_ARROW => BinOp::FatArrow(token),
424 SyntaxKind::GTEQ => BinOp::Gteq(token),
425 SyntaxKind::ILIKE_KW => BinOp::Ilike(token),
426 SyntaxKind::IN_KW => BinOp::In(token),
427 SyntaxKind::IS_KW => BinOp::Is(token),
428 SyntaxKind::L_ANGLE => BinOp::LAngle(token),
429 SyntaxKind::LIKE_KW => BinOp::Like(token),
430 SyntaxKind::LTEQ => BinOp::Lteq(token),
431 SyntaxKind::MINUS => BinOp::Minus(token),
432 SyntaxKind::NEQ => BinOp::Neq(token),
433 SyntaxKind::NEQB => BinOp::Neqb(token),
434 SyntaxKind::OR_KW => BinOp::Or(token),
435 SyntaxKind::OVERLAPS_KW => BinOp::Overlaps(token),
436 SyntaxKind::PERCENT => BinOp::Percent(token),
437 SyntaxKind::PLUS => BinOp::Plus(token),
438 SyntaxKind::R_ANGLE => BinOp::RAngle(token),
439 SyntaxKind::SLASH => BinOp::Slash(token),
440 SyntaxKind::STAR => BinOp::Star(token),
441 _ => continue,
442 };
443 return Some(op);
444 }
445 NodeOrToken::Node(node) => {
446 let op = match node.kind() {
447 SyntaxKind::AT_TIME_ZONE => {
448 BinOp::AtTimeZone(ast::AtTimeZone { syntax: node })
449 }
450 SyntaxKind::COLON_COLON => {
451 BinOp::ColonColon(ast::ColonColon { syntax: node })
452 }
453 SyntaxKind::CUSTOM_OP => BinOp::CustomOp(ast::CustomOp { syntax: node }),
454 SyntaxKind::IS_DISTINCT_FROM => {
455 BinOp::IsDistinctFrom(ast::IsDistinctFrom { syntax: node })
456 }
457 SyntaxKind::IS_NOT => BinOp::IsNot(ast::IsNot { syntax: node }),
458 SyntaxKind::IS_NOT_DISTINCT_FROM => {
459 BinOp::IsNotDistinctFrom(ast::IsNotDistinctFrom { syntax: node })
460 }
461 SyntaxKind::NOT_ILIKE => BinOp::NotIlike(ast::NotIlike { syntax: node }),
462 SyntaxKind::NOT_IN => BinOp::NotIn(ast::NotIn { syntax: node }),
463 SyntaxKind::NOT_LIKE => BinOp::NotLike(ast::NotLike { syntax: node }),
464 SyntaxKind::NOT_SIMILAR_TO => {
465 BinOp::NotSimilarTo(ast::NotSimilarTo { syntax: node })
466 }
467 SyntaxKind::OPERATOR_CALL => {
468 BinOp::OperatorCall(ast::OperatorCall { syntax: node })
469 }
470 SyntaxKind::SIMILAR_TO => BinOp::SimilarTo(ast::SimilarTo { syntax: node }),
471 _ => continue,
472 };
473 return Some(op);
474 }
475 }
476 }
477 None
478 }
479}
480
481impl ast::PrefixExpr {
482 pub fn op(&self) -> Option<PrefixOp> {
483 for child in self.syntax().children_with_tokens() {
484 match child {
485 NodeOrToken::Token(token) => {
486 let op = match token.kind() {
487 SyntaxKind::MINUS => PrefixOp::Minus(token),
488 SyntaxKind::NOT_KW => PrefixOp::Not(token),
489 SyntaxKind::PLUS => PrefixOp::Plus(token),
490 _ => continue,
491 };
492 return Some(op);
493 }
494 NodeOrToken::Node(node) => {
495 let op = match node.kind() {
496 SyntaxKind::CUSTOM_OP => PrefixOp::CustomOp(ast::CustomOp { syntax: node }),
497 SyntaxKind::OPERATOR_CALL => {
498 PrefixOp::OperatorCall(ast::OperatorCall { syntax: node })
499 }
500 _ => continue,
501 };
502 return Some(op);
503 }
504 }
505 }
506 None
507 }
508}
509
510impl ast::PostfixExpr {
511 pub fn op(&self) -> Option<PostfixOp> {
512 for child in self.syntax().children_with_tokens() {
513 match child {
514 NodeOrToken::Token(token) => {
515 let op = match token.kind() {
516 SyntaxKind::ISNULL_KW => PostfixOp::IsNull(token),
517 SyntaxKind::NOTNULL_KW => PostfixOp::NotNull(token),
518 _ => continue,
519 };
520 return Some(op);
521 }
522 NodeOrToken::Node(node) => {
523 let op = match node.kind() {
524 SyntaxKind::AT_LOCAL => PostfixOp::AtLocal(ast::AtLocal { syntax: node }),
525 SyntaxKind::IS_JSON => PostfixOp::IsJson(ast::IsJson { syntax: node }),
526 SyntaxKind::IS_JSON_ARRAY => {
527 PostfixOp::IsJsonArray(ast::IsJsonArray { syntax: node })
528 }
529 SyntaxKind::IS_JSON_OBJECT => {
530 PostfixOp::IsJsonObject(ast::IsJsonObject { syntax: node })
531 }
532 SyntaxKind::IS_JSON_SCALAR => {
533 PostfixOp::IsJsonScalar(ast::IsJsonScalar { syntax: node })
534 }
535 SyntaxKind::IS_JSON_VALUE => {
536 PostfixOp::IsJsonValue(ast::IsJsonValue { syntax: node })
537 }
538 SyntaxKind::IS_NORMALIZED => {
539 PostfixOp::IsNormalized(ast::IsNormalized { syntax: node })
540 }
541 SyntaxKind::IS_NOT_JSON => {
542 PostfixOp::IsNotJson(ast::IsNotJson { syntax: node })
543 }
544 SyntaxKind::IS_NOT_JSON_ARRAY => {
545 PostfixOp::IsNotJsonArray(ast::IsNotJsonArray { syntax: node })
546 }
547 SyntaxKind::IS_NOT_JSON_OBJECT => {
548 PostfixOp::IsNotJsonObject(ast::IsNotJsonObject { syntax: node })
549 }
550 SyntaxKind::IS_NOT_JSON_SCALAR => {
551 PostfixOp::IsNotJsonScalar(ast::IsNotJsonScalar { syntax: node })
552 }
553 SyntaxKind::IS_NOT_JSON_VALUE => {
554 PostfixOp::IsNotJsonValue(ast::IsNotJsonValue { syntax: node })
555 }
556 SyntaxKind::IS_NOT_NORMALIZED => {
557 PostfixOp::IsNotNormalized(ast::IsNotNormalized { syntax: node })
558 }
559 _ => continue,
560 };
561 return Some(op);
562 }
563 }
564 }
565
566 None
567 }
568}
569
570impl ast::FieldExpr {
571 #[inline]
575 pub fn base(&self) -> Option<ast::Expr> {
576 support::children(self.syntax()).next()
577 }
578 #[inline]
579 pub fn field(&self) -> Option<ast::NameRef> {
580 support::children(self.syntax()).last()
581 }
582}
583
584impl ast::IndexAccessor {
585 #[inline]
586 pub fn index(&self) -> Option<ast::Expr> {
587 support::child(self.syntax())
588 }
589}
590
591impl ast::SliceAccessor {
592 #[inline]
593 pub fn start(&self) -> Option<ast::Expr> {
594 let colon = self.colon_token()?;
595 support::children(self.syntax())
596 .find(|expr: &ast::Expr| expr.syntax().text_range().end() <= colon.text_range().start())
597 }
598
599 #[inline]
600 pub fn end(&self) -> Option<ast::Expr> {
601 let colon = self.colon_token()?;
602 support::children(self.syntax())
603 .find(|expr: &ast::Expr| expr.syntax().text_range().start() >= colon.text_range().end())
604 }
605}
606
607impl ast::IndexExpr {
608 #[inline]
609 pub fn base(&self) -> Option<ast::Expr> {
610 support::children(&self.syntax).next()
611 }
612 #[inline]
613 pub fn index(&self) -> Option<ast::Expr> {
614 support::children(&self.syntax).nth(1)
615 }
616}
617
618impl ast::SliceExpr {
619 #[inline]
620 pub fn base(&self) -> Option<ast::Expr> {
621 support::children(&self.syntax).next()
622 }
623
624 #[inline]
625 pub fn start(&self) -> Option<ast::Expr> {
626 let colon = self.colon_token()?;
631 support::children(&self.syntax)
632 .skip(1)
633 .find(|expr: &ast::Expr| expr.syntax().text_range().end() <= colon.text_range().start())
634 }
635
636 #[inline]
637 pub fn end(&self) -> Option<ast::Expr> {
638 let colon = self.colon_token()?;
641 support::children(&self.syntax)
642 .find(|expr: &ast::Expr| expr.syntax().text_range().start() >= colon.text_range().end())
643 }
644}
645
646impl ast::RenameValue {
647 #[inline]
648 pub fn from(&self) -> Option<ast::Literal> {
649 support::children(&self.syntax).nth(0)
650 }
651 #[inline]
652 pub fn to(&self) -> Option<ast::Literal> {
653 support::children(&self.syntax).nth(1)
654 }
655}
656
657impl ast::ForeignKeyConstraint {
658 #[inline]
659 pub fn from_columns(&self) -> Option<ast::ForeignKeyColumnList> {
660 support::children(&self.syntax).nth(0)
661 }
662 #[inline]
663 pub fn to_columns(&self) -> Option<ast::ForeignKeyColumnList> {
664 support::children(&self.syntax).nth(1)
665 }
666}
667
668fn second_minus_token(node: &SyntaxNode) -> Option<SyntaxToken> {
669 node.children_with_tokens()
670 .filter_map(|element| element.into_token())
671 .filter(|token| token.kind() == SyntaxKind::MINUS)
672 .nth(1)
673}
674
675impl ast::EdgeAny {
676 pub fn end_minus_token(&self) -> Option<SyntaxToken> {
677 second_minus_token(self.syntax())
678 }
679}
680
681impl ast::EdgeLeft {
682 pub fn end_minus_token(&self) -> Option<SyntaxToken> {
683 second_minus_token(self.syntax())
684 }
685}
686
687impl ast::EdgeRight {
688 pub fn end_minus_token(&self) -> Option<SyntaxToken> {
689 second_minus_token(self.syntax())
690 }
691}
692
693impl ast::XmlPiFn {
694 #[inline]
695 pub fn target(&self) -> Option<ast::XmlPiTarget> {
696 support::child(&self.syntax)
697 }
698}
699
700impl ast::BetweenExpr {
701 #[inline]
702 pub fn target(&self) -> Option<ast::Expr> {
703 support::children(&self.syntax).nth(0)
704 }
705 #[inline]
706 pub fn start(&self) -> Option<ast::Expr> {
707 support::children(&self.syntax).nth(1)
708 }
709 #[inline]
710 pub fn end(&self) -> Option<ast::Expr> {
711 support::children(&self.syntax).nth(2)
712 }
713}
714
715impl ast::FrameBetween {
716 #[inline]
717 pub fn start(&self) -> Option<ast::FrameBound> {
718 support::children(&self.syntax).nth(0)
719 }
720 #[inline]
721 pub fn end(&self) -> Option<ast::FrameBound> {
722 support::children(&self.syntax).nth(1)
723 }
724}
725
726impl ast::WhenClause {
727 #[inline]
728 pub fn condition(&self) -> Option<ast::Expr> {
729 support::children(&self.syntax).next()
730 }
731 #[inline]
732 pub fn then(&self) -> Option<ast::Expr> {
733 support::children(&self.syntax).nth(1)
734 }
735}
736
737impl ast::ReturningOption {
738 #[inline]
739 pub fn name(&self) -> Option<ast::TableAlias> {
740 match self {
741 ast::ReturningOption::ReturningOld(it) => it.name(),
742 ast::ReturningOption::ReturningNew(it) => it.name(),
743 }
744 }
745}
746
747impl ast::CompoundSelect {
748 #[inline]
749 pub fn lhs_operand(&self) -> Option<ast::CompoundSelectOperand> {
750 support::children(&self.syntax).next()
751 }
752
753 #[inline]
754 pub fn rhs_operand(&self) -> Option<ast::CompoundSelectOperand> {
755 support::children(&self.syntax).nth(1)
756 }
757
758 #[inline]
759 pub fn lhs(&self) -> Option<ast::SelectVariant> {
760 self.lhs_operand()?.select_variant()
761 }
762 #[inline]
763 pub fn rhs(&self) -> Option<ast::SelectVariant> {
764 self.rhs_operand()?.select_variant()
765 }
766 #[inline]
767 pub fn op(&self) -> Option<ast::CompoundOp> {
768 support::child(&self.syntax)
769 }
770}
771
772impl ast::CompoundSelectOperand {
773 pub fn select_variant(&self) -> Option<ast::SelectVariant> {
776 match self {
777 ast::CompoundSelectOperand::SelectVariant(select) => Some(select.clone()),
778 ast::CompoundSelectOperand::ParenExpr(paren) => {
779 let mut node = paren.syntax().clone();
780 loop {
781 let child = node.children().find(|child| {
782 ast::SelectVariant::can_cast(child.kind())
783 || ast::ParenExpr::can_cast(child.kind())
784 })?;
785 if let Some(select) = ast::SelectVariant::cast(child.clone()) {
786 return Some(select);
787 }
788 node = child;
789 }
790 }
791 }
792 }
793}
794
795impl ast::NameRef {
796 #[inline]
797 pub fn text(&self) -> String {
798 normalize_name_node(self.syntax())
799 }
800
801 #[inline]
802 pub fn is_quoted(&self) -> bool {
803 is_quoted_name_node(self.syntax())
804 }
805}
806
807impl ast::ColumnName {
808 #[inline]
809 pub fn text(&self) -> String {
810 normalize_name_node(self.syntax())
811 }
812
813 #[inline]
814 pub fn is_quoted(&self) -> bool {
815 is_quoted_name_node(self.syntax())
816 }
817}
818
819impl ast::PathSegment {
820 #[inline]
821 pub fn text(&self) -> String {
822 normalize_name_node(self.syntax())
823 }
824
825 #[inline]
826 pub fn is_quoted(&self) -> bool {
827 is_quoted_name_node(self.syntax())
828 }
829}
830
831impl ast::PathSegmentRef {
832 #[inline]
833 pub fn text(&self) -> String {
834 normalize_name_node(self.syntax())
835 }
836
837 #[inline]
838 pub fn is_quoted(&self) -> bool {
839 is_quoted_name_node(self.syntax())
840 }
841}
842
843pub fn is_quoted_name_node(node: &SyntaxNode) -> bool {
844 let text = node.text();
845 let first = text.char_at(0.into());
846 let second = text.char_at(1.into());
847 matches!(
848 (first, second),
849 (Some('u' | 'U'), Some('"')) | (Some('"'), Some(_))
850 )
851}
852
853pub fn normalize_name_node(node: &SyntaxNode) -> String {
855 let mut tokens = node
856 .children_with_tokens()
857 .filter_map(|el| el.into_token())
858 .filter(|t| !t.kind().is_trivia());
859
860 let Some(mut ident_token) = tokens.next() else {
861 return String::new();
862 };
863 if node.kind() == SyntaxKind::ROLE_REF && ident_token.kind() == SyntaxKind::GROUP_KW {
866 let Some(role_name) = tokens.next() else {
867 return String::new();
868 };
869 ident_token = role_name;
870 }
871 let raw = ident_token.text();
872
873 let unicode_inner = raw
874 .strip_prefix(['u', 'U'])
875 .and_then(|s| s.strip_prefix("&\""))
876 .and_then(|s| s.strip_suffix('"'));
877
878 if let Some(inner) = unicode_inner {
879 let mut escape_char = '\\';
880 if let Some(uesc) = tokens.next()
881 && uesc.kind() == SyntaxKind::UESCAPE_KW
882 && let Some(token) = tokens.next()
883 && let Some(ch) = uescape_char(token.text())
884 {
885 escape_char = ch;
886 }
887
888 let inner = inner.replace(r#""""#, "\"");
889 let mut result = String::with_capacity(inner.len());
890 escape_unicode_esc_str(&inner, escape_char, |_range, r| {
891 if let Ok(ch) = r {
892 result.push(ch);
893 }
894 });
895 return result;
896 }
897
898 raw.strip_prefix('"')
899 .and_then(|t| t.strip_suffix('"'))
900 .map(|x| x.replace(r#""""#, "\""))
901 .unwrap_or_else(|| raw.to_ascii_lowercase())
902}
903
904impl ast::VarcharType {
905 #[inline]
906 pub fn text(&self) -> TokenText<'_> {
907 text_of_first_token(self.syntax())
908 }
909}
910
911impl ast::CharacterType {
912 #[inline]
913 pub fn text(&self) -> TokenText<'_> {
914 text_of_first_token(self.syntax())
915 }
916}
917
918fn string_literal_contents(token: &SyntaxToken) -> Option<&str> {
919 match token.kind() {
920 SyntaxKind::STRING => token.text().strip_prefix('\'')?.strip_suffix('\''),
921 SyntaxKind::ESC_STRING | SyntaxKind::NATIONAL_STRING => {
922 token.text().get(2..)?.strip_suffix('\'')
923 }
924 SyntaxKind::UNICODE_ESC_STRING => token.text().get(3..)?.strip_suffix('\''),
925 SyntaxKind::DOLLAR_QUOTED_STRING => {
926 let text = token.text();
927 let rest = text.strip_prefix('$')?;
928 let tag_len = rest.find('$')?;
929 let delimiter = text.get(..=tag_len + 1)?;
930 text.get(delimiter.len()..)?.strip_suffix(delimiter)
931 }
932 _ => None,
933 }
934}
935
936fn is_falsey_token(token: &SyntaxToken) -> bool {
937 match token.kind() {
938 SyntaxKind::FALSE_KW | SyntaxKind::NO_KW | SyntaxKind::OFF_KW => true,
939 SyntaxKind::INT_NUMBER => token.text() == "0",
940 SyntaxKind::STRING
941 | SyntaxKind::ESC_STRING
942 | SyntaxKind::NATIONAL_STRING
943 | SyntaxKind::UNICODE_ESC_STRING
944 | SyntaxKind::DOLLAR_QUOTED_STRING => string_literal_contents(token)
945 .is_some_and(|text| matches!(text.to_ascii_lowercase().as_str(), "false" | "off")),
946 _ => false,
947 }
948}
949
950fn is_falsey_vacuum_option_value(value: &ast::VacuumOptionValue) -> bool {
951 value
952 .syntax()
953 .first_token()
954 .is_some_and(|token| is_falsey_token(&token))
955}
956
957impl ast::ReindexTarget {
958 pub fn concurrently_token(&self) -> Option<SyntaxToken> {
959 match self {
960 ast::ReindexTarget::ReindexTargetDatabase(it) => it.concurrently_token(),
961 ast::ReindexTarget::ReindexTargetIndex(it) => it.concurrently_token(),
962 ast::ReindexTarget::ReindexTargetSchema(it) => it.concurrently_token(),
963 ast::ReindexTarget::ReindexTargetSystem(it) => it.concurrently_token(),
964 ast::ReindexTarget::ReindexTargetTable(it) => it.concurrently_token(),
965 }
966 }
967}
968
969impl ast::Reindex {
970 pub fn is_concurrently(&self) -> bool {
971 self.reindex_target()
972 .is_some_and(|target| target.concurrently_token().is_some())
973 || self.reindex_option_list().is_some_and(|options| {
974 options.reindex_options().any(|option| match option {
975 ast::ReindexOption::ReindexOptionConcurrently(option) => {
976 !option.literal().is_some_and(|literal| {
977 literal
978 .syntax()
979 .first_token()
980 .is_some_and(|token| is_falsey_token(&token))
981 })
982 }
983 _ => false,
984 })
985 })
986 }
987}
988
989impl ast::Vacuum {
990 pub fn is_full(&self) -> bool {
991 self.full_token().is_some()
992 || self.vacuum_option_list().is_some_and(|opt_list| {
994 opt_list.vacuum_options().any(|opt| {
995 opt.vacuum_option_name().is_some_and(|name| {
996 name.syntax()
997 .first_token()
998 .is_some_and(|token| token.text().eq_ignore_ascii_case("full"))
999 }) && opt
1000 .vacuum_option_value()
1001 .is_none_or(|value| !is_falsey_vacuum_option_value(&value))
1002 })
1003 })
1004 }
1005}
1006
1007impl ast::OpSig {
1008 #[inline]
1009 pub fn lhs(&self) -> Option<ast::Type> {
1010 support::children(self.syntax()).next()
1011 }
1012
1013 #[inline]
1014 pub fn rhs(&self) -> Option<ast::Type> {
1015 support::children(self.syntax()).nth(1)
1016 }
1017}
1018
1019impl ast::CastSig {
1020 #[inline]
1021 pub fn lhs(&self) -> Option<ast::Type> {
1022 support::children(self.syntax()).next()
1023 }
1024
1025 #[inline]
1026 pub fn rhs(&self) -> Option<ast::Type> {
1027 support::children(self.syntax()).nth(1)
1028 }
1029}
1030
1031impl ast::ObjectOperator {
1032 #[inline]
1033 pub fn lhs(&self) -> Option<ast::Type> {
1034 support::children(self.syntax()).next()
1035 }
1036
1037 #[inline]
1038 pub fn rhs(&self) -> Option<ast::Type> {
1039 support::children(self.syntax()).nth(1)
1040 }
1041}
1042
1043impl ast::OpClassOptionOperator {
1044 #[inline]
1045 pub fn lhs(&self) -> Option<ast::Type> {
1046 support::children(self.syntax()).next()
1047 }
1048
1049 #[inline]
1050 pub fn rhs(&self) -> Option<ast::Type> {
1051 support::children(self.syntax()).nth(1)
1052 }
1053}
1054
1055impl ast::CreateConversion {
1056 #[inline]
1058 pub fn for_(&self) -> Option<ast::Literal> {
1059 support::children(self.syntax()).next()
1060 }
1061
1062 #[inline]
1064 pub fn to(&self) -> Option<ast::Literal> {
1065 support::children(self.syntax()).nth(1)
1066 }
1067}
1068
1069impl ast::ExtractFieldName {
1070 pub fn text(&self) -> String {
1071 normalize_name_node(self.syntax())
1072 }
1073}
1074
1075impl ast::JsonNullOnNull {
1076 #[inline]
1077 pub fn on_null_token(&self) -> Option<SyntaxToken> {
1078 self.syntax()
1079 .children_with_tokens()
1080 .filter_map(|element| element.into_token())
1081 .filter(|token| token.kind() == SyntaxKind::NULL_KW)
1082 .nth(1)
1083 }
1084}
1085
1086impl ast::JsonTable {
1087 pub fn document_expr(&self) -> Option<ast::Expr> {
1088 support::children(self.syntax()).next()
1089 }
1090
1091 pub fn path_expr(&self) -> Option<ast::Expr> {
1092 support::children(self.syntax()).nth(1)
1093 }
1094}
1095
1096impl ast::JsonTablePlanJoin {
1097 pub fn lhs(&self) -> Option<ast::JsonTablePlan> {
1098 support::children(self.syntax()).next()
1099 }
1100
1101 pub fn rhs(&self) -> Option<ast::JsonTablePlan> {
1102 support::children(self.syntax()).nth(1)
1103 }
1104}
1105
1106impl ast::JsonExistsFn {
1107 #[inline]
1108 pub fn document(&self) -> Option<ast::Expr> {
1109 support::children(self.syntax()).next()
1110 }
1111
1112 #[inline]
1113 pub fn path(&self) -> Option<ast::Expr> {
1114 support::children(self.syntax()).nth(1)
1115 }
1116}
1117
1118impl ast::JsonQueryFn {
1119 #[inline]
1120 pub fn document(&self) -> Option<ast::Expr> {
1121 support::children(self.syntax()).next()
1122 }
1123
1124 #[inline]
1125 pub fn path(&self) -> Option<ast::Expr> {
1126 support::children(self.syntax()).nth(1)
1127 }
1128}
1129
1130impl ast::JsonValueFn {
1131 #[inline]
1132 pub fn document(&self) -> Option<ast::Expr> {
1133 support::children(self.syntax()).next()
1134 }
1135
1136 #[inline]
1137 pub fn path(&self) -> Option<ast::Expr> {
1138 support::children(self.syntax()).nth(1)
1139 }
1140}
1141
1142impl ast::PositionFn {
1143 #[inline]
1144 pub fn pos(&self) -> Option<ast::Expr> {
1145 support::children(self.syntax()).next()
1146 }
1147
1148 #[inline]
1149 pub fn string(&self) -> Option<ast::Expr> {
1150 support::children(self.syntax()).nth(1)
1151 }
1152}
1153
1154impl ast::SubstringForFrom {
1155 #[inline]
1156 pub fn string(&self) -> Option<ast::Expr> {
1157 support::children(self.syntax()).next()
1158 }
1159
1160 #[inline]
1161 pub fn count(&self) -> Option<ast::Expr> {
1162 support::children(self.syntax()).nth(1)
1163 }
1164
1165 #[inline]
1166 pub fn start(&self) -> Option<ast::Expr> {
1167 support::children(self.syntax()).nth(2)
1168 }
1169}
1170
1171impl ast::SubstringFromFor {
1172 #[inline]
1173 pub fn string(&self) -> Option<ast::Expr> {
1174 support::children(self.syntax()).next()
1175 }
1176
1177 #[inline]
1178 pub fn start(&self) -> Option<ast::Expr> {
1179 support::children(self.syntax()).nth(1)
1180 }
1181
1182 #[inline]
1183 pub fn count(&self) -> Option<ast::Expr> {
1184 support::children(self.syntax()).nth(2)
1185 }
1186}
1187
1188impl ast::SubstringSimilarEscape {
1189 #[inline]
1190 pub fn string(&self) -> Option<ast::Expr> {
1191 support::children(self.syntax()).next()
1192 }
1193
1194 #[inline]
1195 pub fn pattern(&self) -> Option<ast::Expr> {
1196 support::children(self.syntax()).nth(1)
1197 }
1198
1199 #[inline]
1200 pub fn escape(&self) -> Option<ast::Expr> {
1201 support::children(self.syntax()).nth(2)
1202 }
1203}
1204
1205impl ast::OverlayPlacing {
1206 #[inline]
1207 pub fn string(&self) -> Option<ast::Expr> {
1208 support::children(self.syntax()).next()
1209 }
1210
1211 #[inline]
1212 pub fn placing(&self) -> Option<ast::Expr> {
1213 support::children(self.syntax()).nth(1)
1214 }
1215
1216 #[inline]
1217 pub fn from(&self) -> Option<ast::Expr> {
1218 support::children(self.syntax()).nth(2)
1219 }
1220
1221 #[inline]
1222 pub fn for_(&self) -> Option<ast::Expr> {
1223 support::children(self.syntax()).nth(3)
1224 }
1225}
1226
1227impl ast::PartitionForValuesFrom {
1228 #[inline]
1229 pub fn from(&self) -> Option<ast::PartitionFromValues> {
1230 support::child(self.syntax())
1231 }
1232
1233 #[inline]
1234 pub fn to(&self) -> Option<ast::PartitionToValues> {
1235 support::child(self.syntax())
1236 }
1237}
1238
1239impl ast::PortionFromTo {
1240 #[inline]
1241 pub fn from(&self) -> Option<ast::Expr> {
1242 support::children(self.syntax()).next()
1243 }
1244
1245 #[inline]
1246 pub fn to(&self) -> Option<ast::Expr> {
1247 support::children(self.syntax()).nth(1)
1248 }
1249}
1250
1251impl ast::ReplaceDictionary {
1252 #[inline]
1253 pub fn before(&self) -> Option<ast::TextSearchDictionaryRef> {
1254 support::children(self.syntax()).next()
1255 }
1256
1257 #[inline]
1258 pub fn after(&self) -> Option<ast::TextSearchDictionaryRef> {
1259 support::children(self.syntax()).nth(1)
1260 }
1261}
1262
1263impl ast::Reassign {
1264 #[inline]
1265 pub fn before(&self) -> Option<ast::RoleRefList> {
1266 support::children(self.syntax()).next()
1267 }
1268
1269 #[inline]
1270 pub fn after(&self) -> Option<ast::RoleRefList> {
1271 support::children(self.syntax()).nth(1)
1272 }
1273}
1274
1275impl ast::AsObjFile {
1276 #[inline]
1277 pub fn obj_file(&self) -> Option<ast::Literal> {
1278 support::children(self.syntax()).next()
1279 }
1280
1281 #[inline]
1282 pub fn link_symbol(&self) -> Option<ast::Literal> {
1283 support::children(self.syntax()).nth(1)
1284 }
1285}
1286
1287impl ast::ColumnConstraint {
1288 #[inline]
1289 pub fn constraint_name(&self) -> Option<ast::ConstraintName> {
1290 match self {
1291 ast::ColumnConstraint::CheckConstraint(check_constraint) => check_constraint
1292 .constraint_name_clause()
1293 .and_then(|clause| clause.constraint_name()),
1294 ast::ColumnConstraint::DefaultConstraint(default_constraint) => default_constraint
1295 .constraint_name_clause()
1296 .and_then(|clause| clause.constraint_name()),
1297 ast::ColumnConstraint::ExcludeConstraint(exclude_constraint) => exclude_constraint
1298 .constraint_name_clause()
1299 .and_then(|clause| clause.constraint_name()),
1300 ast::ColumnConstraint::GeneratedConstraint(generated_constraint) => {
1301 generated_constraint
1302 .constraint_name_clause()
1303 .and_then(|clause| clause.constraint_name())
1304 }
1305 ast::ColumnConstraint::NotNullConstraint(not_null_constraint) => not_null_constraint
1306 .constraint_name_clause()
1307 .and_then(|clause| clause.constraint_name()),
1308 ast::ColumnConstraint::NullConstraint(null_constraint) => null_constraint
1309 .constraint_name_clause()
1310 .and_then(|clause| clause.constraint_name()),
1311 ast::ColumnConstraint::PrimaryKeyConstraint(primary_key_constraint) => {
1312 primary_key_constraint
1313 .constraint_name_clause()
1314 .and_then(|clause| clause.constraint_name())
1315 }
1316 ast::ColumnConstraint::ReferencesConstraint(references_constraint) => {
1317 references_constraint
1318 .constraint_name_clause()
1319 .and_then(|clause| clause.constraint_name())
1320 }
1321 ast::ColumnConstraint::UniqueConstraint(unique_constraint) => unique_constraint
1322 .constraint_name_clause()
1323 .and_then(|clause| clause.constraint_name()),
1324 }
1325 }
1326}
1327
1328impl ast::TableConstraint {
1329 #[inline]
1330 pub fn constraint_name(&self) -> Option<ast::ConstraintName> {
1331 match self {
1332 ast::TableConstraint::CheckConstraint(check_constraint) => check_constraint
1333 .constraint_name_clause()
1334 .and_then(|clause| clause.constraint_name()),
1335 ast::TableConstraint::ExcludeConstraint(exclude_constraint) => exclude_constraint
1336 .constraint_name_clause()
1337 .and_then(|clause| clause.constraint_name()),
1338 ast::TableConstraint::ForeignKeyConstraint(foreign_key_constraint) => {
1339 foreign_key_constraint
1340 .constraint_name_clause()
1341 .and_then(|clause| clause.constraint_name())
1342 }
1343 ast::TableConstraint::NotNullConstraint(not_null_constraint) => not_null_constraint
1344 .constraint_name_clause()
1345 .and_then(|clause| clause.constraint_name()),
1346 ast::TableConstraint::PrimaryKeyConstraint(primary_key_constraint) => {
1347 primary_key_constraint
1348 .constraint_name_clause()
1349 .and_then(|clause| clause.constraint_name())
1350 }
1351 ast::TableConstraint::UniqueConstraint(unique_constraint) => unique_constraint
1352 .constraint_name_clause()
1353 .and_then(|clause| clause.constraint_name()),
1354 }
1355 }
1356}
1357
1358pub(crate) fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> {
1359 fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData {
1360 green_ref
1361 .children()
1362 .next()
1363 .and_then(NodeOrToken::into_token)
1364 .unwrap()
1365 }
1366
1367 match node.green() {
1368 Cow::Borrowed(green_ref) => TokenText::borrowed(first_token(green_ref).text()),
1369 Cow::Owned(green) => TokenText::owned(first_token(&green).to_owned()),
1370 }
1371}
1372
1373impl ast::WithQuery {
1374 #[inline]
1375 pub fn with_clause(&self) -> Option<ast::WithClause> {
1376 support::child(self.syntax())
1377 }
1378}
1379
1380impl ast::CreateTableAsQuery {
1381 #[inline]
1382 pub fn select_variant(&self) -> Option<ast::SelectVariant> {
1383 match self {
1384 ast::CreateTableAsQuery::Execute(_) => None,
1385 ast::CreateTableAsQuery::SelectVariant(select_variant) => Some(select_variant.clone()),
1386 }
1387 }
1388}
1389
1390impl ast::SelectVariant {
1391 #[inline]
1392 pub fn target_list(&self) -> Option<ast::TargetList> {
1393 match self {
1394 ast::SelectVariant::Select(select) => {
1395 return select.select_clause()?.target_list();
1396 }
1397 ast::SelectVariant::SelectInto(select_into) => {
1398 return select_into.select_clause()?.target_list();
1399 }
1400 ast::SelectVariant::ParenSelect(paren_select) => {
1401 return paren_select.select()?.target_list();
1402 }
1403 _ => return None,
1404 }
1405 }
1406}
1407
1408impl ast::ParamList {
1409 pub fn all_params(&self) -> impl Iterator<Item = ast::Param> {
1410 self.params().chain(
1411 self.aggregate_order_by()
1412 .into_iter()
1413 .flat_map(|order_by| order_by.params()),
1414 )
1415 }
1416}
1417
1418impl ast::HasParamList {
1419 #[inline]
1420 pub fn param_list(&self) -> Option<ast::ParamList> {
1421 support::child(self.syntax())
1422 }
1423 #[inline]
1424 pub fn path(&self) -> Option<ast::Path> {
1425 match self {
1426 ast::HasParamList::CreateFunction(function) => function.name()?.path(),
1427 ast::HasParamList::CreateProcedure(procedure) => procedure.name()?.path(),
1428 _ => support::child(self.syntax()),
1429 }
1430 }
1431 #[inline]
1432 pub fn path_ref(&self) -> Option<ast::PathRef> {
1433 match self {
1434 ast::HasParamList::FunctionSig(signature) => signature.function_name_ref()?.path_ref(),
1435 ast::HasParamList::ProcedureSig(signature) => {
1436 signature.procedure_name_ref()?.path_ref()
1437 }
1438 ast::HasParamList::RoutineSig(signature) => signature.routine_name_ref()?.path_ref(),
1439 _ => support::child(self.syntax()),
1440 }
1441 }
1442}
1443
1444impl<T> ast::NameLike for T
1445where
1446 T: AstNode,
1447 ast::AnyName: From<T>,
1448{
1449 #[inline]
1450 fn text(&self) -> String {
1451 normalize_name_node(self.syntax())
1452 }
1453
1454 #[inline]
1455 fn is_quoted(&self) -> bool {
1456 is_quoted_name_node(self.syntax())
1457 }
1458}
1459
1460impl ast::HasPathRef for ast::Aggregate {}
1461impl ast::HasPathRef for ast::CollationRef {}
1462impl ast::HasPathRef for ast::ConfigParameterRef {}
1463impl ast::HasPathRef for ast::ConstraintNameRef {}
1464impl ast::HasPathRef for ast::ConversionRef {}
1465impl ast::HasPathRef for ast::DomainRef {}
1466impl ast::HasPathRef for ast::FunctionNameRef {}
1467impl ast::HasPathRef for ast::IndexRef {}
1468impl ast::HasPathRef for ast::Op {}
1469impl ast::HasPathRef for ast::OpClassRef {}
1470impl ast::HasPathRef for ast::OpFamilyRef {}
1471impl ast::HasPathRef for ast::PathType {}
1472impl ast::HasPathRef for ast::PercentType {}
1473impl ast::HasPathRef for ast::ProcedureNameRef {}
1474impl ast::HasPathRef for ast::PropertyGraphRef {}
1475impl ast::HasPathRef for ast::QualifiedColumnNameRef {}
1476impl ast::HasPathRef for ast::RelationNameRef {}
1477impl ast::HasPathRef for ast::RoutineNameRef {}
1478impl ast::HasPathRef for ast::SequenceRef {}
1479impl ast::HasPathRef for ast::StatisticsRef {}
1480impl ast::HasPathRef for ast::TableNameRef {}
1481impl ast::HasPathRef for ast::TextSearchConfigurationRef {}
1482impl ast::HasPathRef for ast::TextSearchDictionaryRef {}
1483impl ast::HasPathRef for ast::TextSearchParserRef {}
1484impl ast::HasPathRef for ast::TextSearchTemplateRef {}
1485impl ast::HasPathRef for ast::TypeNameRef {}
1486impl ast::HasPathRef for ast::ViewRef {}
1487
1488impl ast::HasSelectTail for ast::Select {}
1489impl ast::HasSelectTail for ast::SelectInto {}
1490impl ast::HasSelectTail for ast::ParenSelect {}
1491impl ast::HasSelectTail for ast::CompoundSelect {}
1492impl ast::HasSelectTail for ast::Values {}
1493impl ast::HasSelectTail for ast::Table {}
1494
1495impl ast::HasWithClause for ast::Select {}
1496impl ast::HasWithClause for ast::SelectInto {}
1497impl ast::HasWithClause for ast::Insert {}
1498impl ast::HasWithClause for ast::Update {}
1499impl ast::HasWithClause for ast::Delete {}
1500
1501impl ast::HasCreateTable for ast::CreateTable {}
1502impl ast::HasCreateTable for ast::CreateForeignTable {}
1503impl ast::HasCreateTable for ast::CreateTableLike {}
1504
1505#[test]
1506fn name() {
1507 assert_snapshot!(extract_name("select 1 foo"), @"foo");
1508 assert_snapshot!(extract_name("select 1 FOO"), @"foo");
1509 assert_snapshot!(extract_name(r#"select 1 "foo""#), @"foo");
1510 assert_snapshot!(extract_name(r#"select 1 "Foo""#), @"Foo");
1511 assert_snapshot!(extract_name(r#"select 1 "FOO""#), @"FOO");
1512 assert_snapshot!(extract_name(r#"select 1 "foo""bar""#), @r#"foo"bar"#);
1513 assert_snapshot!(extract_name(r#"select 1 U&"\0066\006f\006f""#), @"foo");
1514 assert_snapshot!(extract_name(r#"select 1 U&"@0066@006f@006f" uescape '@'"#), @"foo");
1515
1516 fn extract_name(source_code: &str) -> String {
1517 let parse = SourceFile::parse(source_code);
1518 assert!(parse.errors().is_empty());
1519 let stmt = parse.tree().stmts().next().unwrap();
1520 let ast::Stmt::Select(select) = stmt else {
1521 unreachable!()
1522 };
1523 let name = select
1524 .select_clause()
1525 .unwrap()
1526 .target_list()
1527 .unwrap()
1528 .targets()
1529 .next()
1530 .unwrap()
1531 .as_name()
1532 .unwrap()
1533 .name()
1534 .unwrap();
1535 name.text().to_string()
1536 }
1537}
1538
1539#[test]
1540fn name_ref() {
1541 assert_snapshot!(extract_name_ref("select foo"), @"foo");
1542 assert_snapshot!(extract_name_ref("select FOO"), @"foo");
1543 assert_snapshot!(extract_name_ref(r#"select "foo""#), @"foo");
1544 assert_snapshot!(extract_name_ref(r#"select "Foo""#), @"Foo");
1545 assert_snapshot!(extract_name_ref(r#"select "FOO""#), @"FOO");
1546 assert_snapshot!(extract_name_ref(r#"select U&"\0066\006f\006f""#), @"foo");
1547 assert_snapshot!(extract_name_ref(r#"select U&"@0066@006f@006f" uescape '@'"#), @"foo");
1548
1549 fn extract_name_ref(source_code: &str) -> String {
1550 let parse = SourceFile::parse(source_code);
1551 assert!(parse.errors().is_empty());
1552 let stmt = parse.tree().stmts().next().unwrap();
1553 let ast::Stmt::Select(select) = stmt else {
1554 unreachable!()
1555 };
1556 let select_clause = select.select_clause().unwrap();
1557 let target = select_clause
1558 .target_list()
1559 .unwrap()
1560 .targets()
1561 .next()
1562 .unwrap();
1563 let ast::Expr::NameRef(name_ref) = target.expr().unwrap() else {
1564 unreachable!()
1565 };
1566 name_ref.text().to_string()
1567 }
1568}
1569
1570#[test]
1571fn unicode_quoted_name_keeps_doubled_single_quotes() {
1572 let parse = SourceFile::parse(r#"select 1 U&"a''b""#);
1573 assert!(parse.errors().is_empty());
1574 let stmt = parse.tree().stmts().next().unwrap();
1575 let ast::Stmt::Select(select) = stmt else {
1576 unreachable!()
1577 };
1578 let name = select
1579 .select_clause()
1580 .unwrap()
1581 .target_list()
1582 .unwrap()
1583 .targets()
1584 .next()
1585 .unwrap()
1586 .as_name()
1587 .unwrap()
1588 .name()
1589 .unwrap();
1590
1591 assert_snapshot!(name.text().to_string(), @"a''b");
1592}
1593
1594#[test]
1595fn index_expr() {
1596 let source_code = "
1597 select foo[bar];
1598 ";
1599 let parse = SourceFile::parse(source_code);
1600 assert!(parse.errors().is_empty());
1601 let stmt = parse.tree().stmts().next().unwrap();
1602 let ast::Stmt::Select(select) = stmt else {
1603 unreachable!()
1604 };
1605 let select_clause = select.select_clause().unwrap();
1606 let target = select_clause
1607 .target_list()
1608 .unwrap()
1609 .targets()
1610 .next()
1611 .unwrap();
1612 let ast::Expr::IndexExpr(index_expr) = target.expr().unwrap() else {
1613 unreachable!()
1614 };
1615 let base = index_expr.base().unwrap();
1616 let index = index_expr.index().unwrap();
1617 assert_eq!(base.syntax().text(), "foo");
1618 assert_eq!(index.syntax().text(), "bar");
1619}
1620
1621#[test]
1622fn slice_expr() {
1623 use insta::assert_snapshot;
1624 let source_code = "
1625 select x[1:2], x[2:], x[:3], x[:];
1626 ";
1627 let parse = SourceFile::parse(source_code);
1628 assert!(parse.errors().is_empty());
1629 let stmt = parse.tree().stmts().next().unwrap();
1630 let ast::Stmt::Select(select) = stmt else {
1631 unreachable!()
1632 };
1633 let select_clause = select.select_clause().unwrap();
1634 let mut targets = select_clause.target_list().unwrap().targets();
1635
1636 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1637 unreachable!()
1638 };
1639 assert_snapshot!(slice.syntax(), @"x[1:2]");
1640 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1641 assert_eq!(slice.start().unwrap().syntax().text(), "1");
1642 assert_eq!(slice.end().unwrap().syntax().text(), "2");
1643
1644 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1645 unreachable!()
1646 };
1647 assert_snapshot!(slice.syntax(), @"x[2:]");
1648 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1649 assert_eq!(slice.start().unwrap().syntax().text(), "2");
1650 assert!(slice.end().is_none());
1651
1652 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1653 unreachable!()
1654 };
1655 assert_snapshot!(slice.syntax(), @"x[:3]");
1656 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1657 assert!(slice.start().is_none());
1658 assert_eq!(slice.end().unwrap().syntax().text(), "3");
1659
1660 let ast::Expr::SliceExpr(slice) = targets.next().unwrap().expr().unwrap() else {
1661 unreachable!()
1662 };
1663 assert_snapshot!(slice.syntax(), @"x[:]");
1664 assert_eq!(slice.base().unwrap().syntax().text(), "x");
1665 assert!(slice.start().is_none());
1666 assert!(slice.end().is_none());
1667}
1668
1669#[test]
1670fn field_expr() {
1671 let source_code = "
1672 select foo.bar;
1673 ";
1674 let parse = SourceFile::parse(source_code);
1675 assert!(parse.errors().is_empty());
1676 let stmt = parse.tree().stmts().next().unwrap();
1677 let ast::Stmt::Select(select) = stmt else {
1678 unreachable!()
1679 };
1680 let select_clause = select.select_clause().unwrap();
1681 let target = select_clause
1682 .target_list()
1683 .unwrap()
1684 .targets()
1685 .next()
1686 .unwrap();
1687 let ast::Expr::FieldExpr(field_expr) = target.expr().unwrap() else {
1688 unreachable!()
1689 };
1690 let base = field_expr.base().unwrap();
1691 let field = field_expr.field().unwrap();
1692 assert_eq!(base.syntax().text(), "foo");
1693 assert_eq!(field.syntax().text(), "bar");
1694}
1695
1696#[test]
1697fn between_expr() {
1698 let source_code = "
1699 select 2 between 1 and 3;
1700 ";
1701 let parse = SourceFile::parse(source_code);
1702 assert!(parse.errors().is_empty());
1703 let stmt = parse.tree().stmts().next().unwrap();
1704 let ast::Stmt::Select(select) = stmt else {
1705 unreachable!()
1706 };
1707 let select_clause = select.select_clause().unwrap();
1708 let target = select_clause
1709 .target_list()
1710 .unwrap()
1711 .targets()
1712 .next()
1713 .unwrap();
1714 let ast::Expr::BetweenExpr(between_expr) = target.expr().unwrap() else {
1715 unreachable!()
1716 };
1717 let target = between_expr.target().unwrap();
1718 let start = between_expr.start().unwrap();
1719 let end = between_expr.end().unwrap();
1720 assert_eq!(target.syntax().text(), "2");
1721 assert_eq!(start.syntax().text(), "1");
1722 assert_eq!(end.syntax().text(), "3");
1723}
1724
1725#[test]
1726fn cast_expr() {
1727 use insta::assert_snapshot;
1728
1729 let cast = extract_expr("select cast('123' as int)");
1730 assert_eq!(cast.kind(), Some(CastKind::Cast));
1731 assert!(cast.expr().is_some());
1732 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1733 assert!(cast.ty().is_some());
1734 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
1735
1736 let cast = extract_expr("select cast('123' as pg_catalog.int4)");
1737 assert!(cast.expr().is_some());
1738 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1739 assert!(cast.ty().is_some());
1740 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
1741
1742 let cast = extract_expr("select treat('123' as int)");
1743 assert_eq!(cast.kind(), Some(CastKind::Treat));
1744
1745 let cast = extract_expr("select int '123'");
1746 assert_eq!(cast.kind(), Some(CastKind::TypeLiteral));
1747 assert!(cast.expr().is_some());
1748 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1749 assert!(cast.ty().is_some());
1750 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
1751
1752 let cast = extract_expr("select pg_catalog.int4 '123'");
1753 assert!(cast.expr().is_some());
1754 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1755 assert!(cast.ty().is_some());
1756 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
1757
1758 let cast = extract_expr("select '123'::int");
1759 assert_eq!(cast.kind(), Some(CastKind::DoubleColon));
1760 assert!(cast.expr().is_some());
1761 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1762 assert!(cast.ty().is_some());
1763 assert_snapshot!(cast.ty().unwrap().syntax(), @"int");
1764
1765 let cast = extract_expr("select '123'::int4");
1766 assert!(cast.expr().is_some());
1767 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1768 assert!(cast.ty().is_some());
1769 assert_snapshot!(cast.ty().unwrap().syntax(), @"int4");
1770
1771 let cast = extract_expr("select '123'::pg_catalog.int4");
1772 assert!(cast.expr().is_some());
1773 assert_snapshot!(cast.expr().unwrap().syntax(), @"'123'");
1774 assert!(cast.ty().is_some());
1775 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.int4");
1776
1777 let cast = extract_expr("select '{123}'::pg_catalog.varchar(10)[]");
1778 assert!(cast.expr().is_some());
1779 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
1780 assert!(cast.ty().is_some());
1781 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)[]");
1782
1783 let cast = extract_expr("select cast('{123}' as pg_catalog.varchar(10)[])");
1784 assert!(cast.expr().is_some());
1785 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
1786 assert!(cast.ty().is_some());
1787 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)[]");
1788
1789 let cast = extract_expr("select pg_catalog.varchar(10) '{123}'");
1790 assert!(cast.expr().is_some());
1791 assert_snapshot!(cast.expr().unwrap().syntax(), @"'{123}'");
1792 assert!(cast.ty().is_some());
1793 assert_snapshot!(cast.ty().unwrap().syntax(), @"pg_catalog.varchar(10)");
1794
1795 let cast = extract_expr("select interval '1' month");
1796 assert!(cast.expr().is_some());
1797 assert_snapshot!(cast.expr().unwrap().syntax(), @"'1'");
1798 assert!(cast.ty().is_some());
1799 assert_snapshot!(cast.ty().unwrap().syntax(), @"interval");
1800
1801 fn extract_expr(sql: &str) -> ast::CastExpr {
1802 let parse = SourceFile::parse(sql);
1803 assert!(parse.errors().is_empty());
1804 let node = parse
1805 .tree()
1806 .stmts()
1807 .map(|x| match x {
1808 ast::Stmt::Select(select) => select
1809 .select_clause()
1810 .unwrap()
1811 .target_list()
1812 .unwrap()
1813 .targets()
1814 .next()
1815 .unwrap()
1816 .expr()
1817 .unwrap()
1818 .clone(),
1819 _ => unreachable!(),
1820 })
1821 .next()
1822 .unwrap();
1823 match node {
1824 ast::Expr::CastExpr(cast) => cast,
1825 _ => unreachable!(),
1826 }
1827 }
1828}
1829
1830#[test]
1831fn op_sig() {
1832 let source_code = "
1833 alter operator p.+ (int4, int8)
1834 owner to u;
1835 ";
1836 let parse = SourceFile::parse(source_code);
1837 assert!(parse.errors().is_empty());
1838 let stmt = parse.tree().stmts().next().unwrap();
1839 let ast::Stmt::AlterOperator(alter_op) = stmt else {
1840 unreachable!()
1841 };
1842 let op_sig = alter_op.op_sig().unwrap();
1843 let lhs = op_sig.lhs().unwrap();
1844 let rhs = op_sig.rhs().unwrap();
1845 assert_snapshot!(lhs.syntax().text(), @"int4");
1846 assert_snapshot!(rhs.syntax().text(), @"int8");
1847}
1848
1849#[test]
1850fn cast_sig() {
1851 let source_code = "
1852 drop cast (text as int);
1853 ";
1854 let parse = SourceFile::parse(source_code);
1855 assert!(parse.errors().is_empty());
1856 let stmt = parse.tree().stmts().next().unwrap();
1857 let ast::Stmt::DropCast(alter_op) = stmt else {
1858 unreachable!()
1859 };
1860 let cast_sig = alter_op.cast_sig().unwrap();
1861 let lhs = cast_sig.lhs().unwrap();
1862 let rhs = cast_sig.rhs().unwrap();
1863 assert_snapshot!(lhs.syntax().text(), @"text");
1864 assert_snapshot!(rhs.syntax().text(), @"int");
1865}
1866
1867#[cfg(test)]
1868fn extract_vacuum(sql: &str) -> ast::Vacuum {
1869 let parse = SourceFile::parse(sql);
1870 assert!(parse.errors().is_empty());
1871 let stmt = parse.tree().stmts().next().unwrap();
1872 let ast::Stmt::Vacuum(vacuum) = stmt else {
1873 unreachable!()
1874 };
1875 vacuum
1876}
1877
1878#[test]
1879fn vacuum_full_is_full() {
1880 assert!(extract_vacuum("VACUUM FULL foo;").is_full());
1881}
1882
1883#[test]
1884fn vacuum_option_list_full_is_full() {
1885 assert!(extract_vacuum("VACUUM (FULL) foo;").is_full());
1886}
1887
1888#[test]
1889fn vacuum_full_true_is_full() {
1890 assert!(extract_vacuum("VACUUM (FULL TRUE) foo;").is_full());
1891}
1892
1893#[test]
1894fn vacuum_full_on_is_full() {
1895 assert!(extract_vacuum("VACUUM (FULL ON) foo;").is_full());
1896}
1897
1898#[test]
1899fn vacuum_full_1_is_full() {
1900 assert!(extract_vacuum("VACUUM (FULL 1) foo;").is_full());
1901}
1902
1903#[test]
1904fn vacuum_no_full_is_not_full() {
1905 assert!(!extract_vacuum("VACUUM foo;").is_full());
1906}
1907
1908#[test]
1909fn vacuum_other_option_is_not_full() {
1910 assert!(!extract_vacuum("VACUUM (FREEZE) foo;").is_full());
1911}
1912
1913#[test]
1914fn vacuum_full_false_is_not_full() {
1915 assert!(!extract_vacuum("VACUUM (FULL FALSE) foo;").is_full());
1916}
1917
1918#[test]
1919fn vacuum_full_off_is_not_full() {
1920 assert!(!extract_vacuum("VACUUM (FULL OFF) foo;").is_full());
1921}
1922
1923#[test]
1924fn vacuum_full_no_is_not_full() {
1925 assert!(!extract_vacuum("VACUUM (FULL NO) foo;").is_full());
1926}
1927
1928#[test]
1929fn vacuum_full_quoted_off_is_not_full() {
1930 assert!(!extract_vacuum("VACUUM (FULL 'off') foo;").is_full());
1931}
1932
1933#[test]
1934fn vacuum_full_escaped_string_off_is_not_full() {
1935 assert!(!extract_vacuum("VACUUM (FULL E'off') foo;").is_full());
1936}
1937
1938#[test]
1939fn vacuum_full_unicode_escaped_string_off_is_not_full() {
1940 assert!(!extract_vacuum("VACUUM (FULL U&'off') foo;").is_full());
1941}
1942
1943#[test]
1944fn vacuum_full_dollar_quoted_off_is_not_full() {
1945 assert!(!extract_vacuum("VACUUM (FULL $$off$$) t;").is_full());
1946}
1947
1948#[test]
1949fn vacuum_full_0_is_not_full() {
1950 assert!(!extract_vacuum("VACUUM (FULL 0) foo;").is_full());
1951}