1use 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 #[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 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 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 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
1035pub 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 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 || 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 #[inline]
1247 pub fn for_(&self) -> Option<ast::Literal> {
1248 support::children(self.syntax()).next()
1249 }
1250
1251 #[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}