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