Skip to main content

squawk_syntax/ast/
node_ext.rs

1// via https://github.com/rust-lang/rust-analyzer/blob/d8887c0758bbd2d5f752d5bd405d4491e90e7ed6/crates/syntax/src/ast/node_ext.rs
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27use 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    // We have NameRef as a variant of Expr which complicates things (and it
572    // might not be worth it).
573    // Rust analyzer doesn't do this so it doesn't have to special case this.
574    #[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        // With `select x[1:]`, we have two exprs, `x` and `1`.
627        // We skip over the first one, and then we want the second one, but we
628        // want to make sure we don't choose the end expr if instead we had:
629        // `select x[:1]`
630        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        // We want to make sure we get the last expr after the `:` which is the
639        // end of the slice, i.e., `2` in: `select x[:2]`
640        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    /// The select this operand ultimately wraps, looking through any
774    /// parenthesized expressions the parser tagged as `ParenExpr`.
775    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
853// TODO: return a NewType wrapper around String?
854pub 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    // Support some deprecated syntax where you can plop a `group` keyword
864    // before a role name.
865    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            // TODO: we need a better way of handling option lists
993            || 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    /// The source encoding.
1057    #[inline]
1058    pub fn for_(&self) -> Option<ast::Literal> {
1059        support::children(self.syntax()).next()
1060    }
1061
1062    /// The destination encoding.
1063    #[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}