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