1use datafusion_common::DataFusionError;
24use datafusion_common::config::{ConfigNonZeroUsize, SqlParserOptions};
25use datafusion_common::format::{ExplainFormat, ExplainStatementOptions};
26use datafusion_common::{Diagnostic, Span, sql_err};
27use sqlparser::ast::{ExprWithAlias, Ident, OrderByOptions};
28use sqlparser::tokenizer::TokenWithSpan;
29use sqlparser::{
30 ast::{
31 ColumnDef, ColumnOptionDef, ObjectName, OrderByExpr, Query,
32 Statement as SQLStatement, TableConstraint, Value,
33 },
34 dialect::{Dialect, GenericDialect, keywords::Keyword},
35 parser::{Parser, ParserError},
36 tokenizer::{Token, Tokenizer, Word},
37};
38use std::collections::VecDeque;
39use std::fmt;
40use std::str::FromStr;
41
42macro_rules! parser_err {
44 ($MSG:expr $(; diagnostic = $DIAG:expr)?) => {{
45
46 let err = DataFusionError::from(ParserError::ParserError($MSG.to_string()));
47 $(
48 let err = err.with_diagnostic($DIAG);
49 )?
50 Err(err)
51 }};
52}
53
54fn parse_file_type(s: &str) -> Result<String, DataFusionError> {
55 Ok(s.to_uppercase())
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct ExplainStatement {
76 pub options: ExplainStatementOptions,
79 pub statement: Box<Statement>,
83}
84
85impl fmt::Display for ExplainStatement {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let Self { options, statement } = self;
88
89 let uses_parenthesized = options.analyze_level.is_some()
92 || options.analyze_categories.is_some()
93 || options.show_statistics.is_some();
94
95 write!(f, "EXPLAIN ")?;
96 if uses_parenthesized {
97 let mut parts: Vec<String> = Vec::new();
99 if options.analyze {
100 parts.push("ANALYZE".to_string());
101 }
102 if options.verbose {
103 parts.push("VERBOSE".to_string());
104 }
105 if let Some(format) = &options.format {
106 parts.push(format!("FORMAT {format}"));
107 }
108 if let Some(level) = options.analyze_level {
109 parts.push(format!("LEVEL {level}"));
110 }
111 if let Some(cats) = &options.analyze_categories {
112 parts.push(format!("METRICS '{cats}'"));
113 }
114 if let Some(stats) = options.show_statistics {
115 parts.push(format!("COSTS {}", if stats { "ON" } else { "OFF" }));
116 }
117 write!(f, "({}) ", parts.join(", "))?;
118 } else {
119 if options.analyze {
120 write!(f, "ANALYZE ")?;
121 }
122 if options.verbose {
123 write!(f, "VERBOSE ")?;
124 }
125 if let Some(format) = &options.format {
126 write!(f, "FORMAT {format} ")?;
127 }
128 }
129
130 write!(f, "{statement}")
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct CopyToStatement {
159 pub source: CopyToSource,
161 pub target: String,
163 pub partitioned_by: Vec<String>,
165 pub stored_as: Option<String>,
167 pub options: Vec<(String, Value)>,
169}
170
171impl fmt::Display for CopyToStatement {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 let Self {
174 source,
175 target,
176 partitioned_by,
177 stored_as,
178 options,
179 ..
180 } = self;
181
182 write!(f, "COPY {source} TO {target}")?;
183 if let Some(file_type) = stored_as {
184 write!(f, " STORED AS {file_type}")?;
185 }
186 if !partitioned_by.is_empty() {
187 write!(f, " PARTITIONED BY ({})", partitioned_by.join(", "))?;
188 }
189
190 if !options.is_empty() {
191 let opts: Vec<_> =
192 options.iter().map(|(k, v)| format!("'{k}' {v}")).collect();
193 write!(f, " OPTIONS ({})", opts.join(", "))?;
194 }
195
196 Ok(())
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub enum CopyToSource {
202 Relation(ObjectName),
204 Query(Box<Query>),
206}
207
208impl fmt::Display for CopyToSource {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 match self {
211 CopyToSource::Relation(r) => write!(f, "{r}"),
212 CopyToSource::Query(q) => write!(f, "({q})"),
213 }
214 }
215}
216
217pub(crate) type LexOrdering = Vec<OrderByExpr>;
219
220#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct CreateExternalTable {
246 pub name: ObjectName,
248 pub columns: Vec<ColumnDef>,
250 pub file_type: String,
252 pub locations: Vec<String>,
254 pub table_partition_cols: Vec<String>,
256 pub order_exprs: Vec<LexOrdering>,
258 pub if_not_exists: bool,
260 pub or_replace: bool,
262 pub temporary: bool,
264 pub unbounded: bool,
266 pub options: Vec<(String, Value)>,
268 pub constraints: Vec<TableConstraint>,
270}
271
272impl fmt::Display for CreateExternalTable {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 write!(f, "CREATE EXTERNAL TABLE ")?;
275 if self.if_not_exists {
276 write!(f, "IF NOT EXISTS ")?;
277 }
278 write!(f, "{} ", self.name)?;
279 write!(f, "STORED AS {} ", self.file_type)?;
280 if !self.order_exprs.is_empty() {
281 write!(f, "WITH ORDER (")?;
282 let mut first = true;
283 for expr in self.order_exprs.iter().flatten() {
284 if !first {
285 write!(f, ", ")?;
286 }
287 write!(f, "{expr}")?;
288 first = false;
289 }
290 write!(f, ") ")?;
291 }
292 match self.locations.as_slice() {
293 [location] => write!(
294 f,
295 "LOCATION {}",
296 Value::SingleQuotedString(location.clone())
297 ),
298 locations => {
299 write!(f, "LOCATION (")?;
300 for (idx, location) in locations.iter().enumerate() {
301 if idx > 0 {
302 write!(f, ", ")?;
303 }
304 write!(f, "{}", Value::SingleQuotedString(location.clone()))?;
305 }
306 write!(f, ")")
307 }
308 }
309 }
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
314pub enum ResetStatement {
315 Variable(ObjectName),
317}
318
319impl fmt::Display for ResetStatement {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 match self {
322 ResetStatement::Variable(name) => write!(f, "RESET {name}"),
323 }
324 }
325}
326
327#[derive(Debug, Clone, PartialEq, Eq)]
335pub enum Statement {
336 Statement(Box<SQLStatement>),
338 CreateExternalTable(CreateExternalTable),
340 CopyTo(CopyToStatement),
342 Explain(ExplainStatement),
344 Reset(ResetStatement),
346}
347
348impl fmt::Display for Statement {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 match self {
351 Statement::Statement(stmt) => write!(f, "{stmt}"),
352 Statement::CreateExternalTable(stmt) => write!(f, "{stmt}"),
353 Statement::CopyTo(stmt) => write!(f, "{stmt}"),
354 Statement::Explain(stmt) => write!(f, "{stmt}"),
355 Statement::Reset(stmt) => write!(f, "{stmt}"),
356 }
357 }
358}
359
360fn ensure_not_set<T>(field: &Option<T>, name: &str) -> Result<(), DataFusionError> {
361 if field.is_some() {
362 parser_err!(format!("{name} specified more than once",))?
363 }
364 Ok(())
365}
366
367pub struct DFParser<'a> {
376 pub parser: Parser<'a>,
377 options: SqlParserOptions,
378 supports_explain_with_utility_options: bool,
382}
383
384const DEFAULT_RECURSION_LIMIT: usize = 50;
386const DEFAULT_DIALECT: GenericDialect = GenericDialect {};
387
388pub struct DFParserBuilder<'a, 'b> {
421 input: ParserInput<'a>,
423 dialect: &'b dyn Dialect,
425 recursion_limit: usize,
427}
428
429pub enum ParserInput<'a> {
431 Sql(&'a str),
434 Tokens(Vec<TokenWithSpan>),
436}
437
438impl<'a> From<&'a str> for ParserInput<'a> {
439 fn from(sql: &'a str) -> Self {
440 Self::Sql(sql)
441 }
442}
443
444impl From<Vec<TokenWithSpan>> for ParserInput<'static> {
445 fn from(tokens: Vec<TokenWithSpan>) -> Self {
446 Self::Tokens(tokens)
447 }
448}
449
450impl<'a, 'b> DFParserBuilder<'a, 'b> {
451 pub fn new(input: impl Into<ParserInput<'a>>) -> Self {
454 Self {
455 input: input.into(),
456 dialect: &DEFAULT_DIALECT,
457 recursion_limit: DEFAULT_RECURSION_LIMIT,
458 }
459 }
460
461 pub fn with_dialect(mut self, dialect: &'b dyn Dialect) -> Self {
463 self.dialect = dialect;
464 self
465 }
466
467 pub fn with_recursion_limit(mut self, recursion_limit: usize) -> Self {
469 self.recursion_limit = recursion_limit;
470 self
471 }
472
473 pub fn build(self) -> Result<DFParser<'b>, DataFusionError> {
475 let tokens = match self.input {
476 ParserInput::Tokens(tokens) => tokens,
477 ParserInput::Sql(sql) => {
478 let mut tokenizer = Tokenizer::new(self.dialect, sql);
479 tokenizer
481 .tokenize_with_location()
482 .map_err(ParserError::from)?
483 }
484 };
485
486 Ok(DFParser {
487 parser: Parser::new(self.dialect)
488 .with_tokens_with_locations(tokens)
489 .with_recursion_limit(self.recursion_limit),
490 options: SqlParserOptions {
491 recursion_limit: ConfigNonZeroUsize::try_new(self.recursion_limit)?,
492 ..Default::default()
493 },
494 supports_explain_with_utility_options: self
495 .dialect
496 .supports_explain_with_utility_options(),
497 })
498 }
499}
500
501fn token_starts_query(tok: &Token) -> bool {
505 match tok {
506 Token::LParen => true,
507 Token::Word(Word { keyword, .. }) => matches!(
508 keyword,
509 Keyword::SELECT
510 | Keyword::WITH
511 | Keyword::VALUES
512 | Keyword::TABLE
513 | Keyword::INSERT
514 | Keyword::UPDATE
515 | Keyword::DELETE
516 | Keyword::MERGE
517 ),
518 _ => false,
519 }
520}
521
522impl<'a> DFParser<'a> {
523 pub fn parse_sql(sql: &'a str) -> Result<VecDeque<Statement>, DataFusionError> {
526 let mut parser = DFParserBuilder::new(sql).build()?;
527
528 parser.parse_statements()
529 }
530
531 pub fn parse_sql_with_dialect(
534 sql: &str,
535 dialect: &dyn Dialect,
536 ) -> Result<VecDeque<Statement>, DataFusionError> {
537 let mut parser = DFParserBuilder::new(sql).with_dialect(dialect).build()?;
538 parser.parse_statements()
539 }
540
541 pub fn parse_sql_into_expr(sql: &str) -> Result<ExprWithAlias, DataFusionError> {
542 DFParserBuilder::new(sql).build()?.parse_into_expr()
543 }
544
545 pub fn parse_sql_into_expr_with_dialect(
546 sql: &str,
547 dialect: &dyn Dialect,
548 ) -> Result<ExprWithAlias, DataFusionError> {
549 DFParserBuilder::new(sql)
550 .with_dialect(dialect)
551 .build()?
552 .parse_into_expr()
553 }
554
555 pub fn parse_statements(&mut self) -> Result<VecDeque<Statement>, DataFusionError> {
557 let mut stmts = VecDeque::new();
558 let mut expecting_statement_delimiter = false;
559 loop {
560 while self.parser.consume_token(&Token::SemiColon) {
562 expecting_statement_delimiter = false;
563 }
564
565 if self.parser.peek_token() == Token::EOF {
566 break;
567 }
568 if expecting_statement_delimiter {
569 return self.expected("end of statement", &self.parser.peek_token());
570 }
571
572 let statement = self.parse_statement()?;
573 stmts.push_back(statement);
574 expecting_statement_delimiter = true;
575 }
576 Ok(stmts)
577 }
578
579 fn expected<T>(
581 &self,
582 expected: &str,
583 found: &TokenWithSpan,
584 ) -> Result<T, DataFusionError> {
585 let sql_parser_span = found.span;
586 let span = Span::try_from_sqlparser_span(sql_parser_span);
587 let diagnostic = Diagnostic::new_error(
588 format!("Expected: {expected}, found: {found}{}", found.span.start),
589 span,
590 );
591 parser_err!(
592 format!("Expected: {expected}, found: {found}{}", found.span.start);
593 diagnostic=
594 diagnostic
595 )
596 }
597
598 fn expect_token(
599 &mut self,
600 expected: &str,
601 token: &Token,
602 ) -> Result<(), DataFusionError> {
603 let next_token = self.parser.peek_token_ref();
604 if next_token.token != *token {
605 self.expected(expected, next_token)
606 } else {
607 Ok(())
608 }
609 }
610
611 pub fn parse_statement(&mut self) -> Result<Statement, DataFusionError> {
613 match self.parser.peek_token().token {
614 Token::Word(w) => {
615 match w.keyword {
616 Keyword::CREATE => {
617 self.parser.next_token(); self.parse_create()
619 }
620 Keyword::COPY => {
621 if let Token::Word(w) = self.parser.peek_nth_token(1).token {
622 if w.keyword == Keyword::INTO {
624 return self.parse_and_handle_statement();
625 }
626 }
627 self.parser.next_token(); self.parse_copy()
629 }
630 Keyword::EXPLAIN => {
631 self.parser.next_token(); self.parse_explain()
633 }
634 Keyword::RESET => {
635 self.parser.next_token(); self.parse_reset()
637 }
638 _ => {
639 self.parse_and_handle_statement()
641 }
642 }
643 }
644 _ => {
645 self.parse_and_handle_statement()
647 }
648 }
649 }
650
651 pub fn parse_expr(&mut self) -> Result<ExprWithAlias, DataFusionError> {
652 if let Token::Word(w) = self.parser.peek_token().token {
653 match w.keyword {
654 Keyword::CREATE | Keyword::COPY | Keyword::EXPLAIN => {
655 return parser_err!("Unsupported command in expression")?;
656 }
657 _ => {}
658 }
659 }
660
661 Ok(self.parser.parse_expr_with_alias()?)
662 }
663
664 pub fn parse_into_expr(&mut self) -> Result<ExprWithAlias, DataFusionError> {
669 let expr = self.parse_expr()?;
670 self.expect_token("end of expression", &Token::EOF)?;
671 Ok(expr)
672 }
673
674 fn parse_and_handle_statement(&mut self) -> Result<Statement, DataFusionError> {
676 self.parser
677 .parse_statement()
678 .map(|stmt| Statement::Statement(Box::from(stmt)))
679 .map_err(|e| match e {
680 ParserError::RecursionLimitExceeded => DataFusionError::SQL(
681 Box::new(ParserError::RecursionLimitExceeded),
682 Some(format!(
683 " (current limit: {})",
684 self.options.recursion_limit
685 )),
686 ),
687 other => DataFusionError::SQL(Box::new(other), None),
688 })
689 }
690
691 pub fn parse_copy(&mut self) -> Result<Statement, DataFusionError> {
693 let source = if self.parser.consume_token(&Token::LParen) {
695 let query = self.parser.parse_query()?;
696 self.parser.expect_token(&Token::RParen)?;
697 CopyToSource::Query(query)
698 } else {
699 let table_name = self.parser.parse_object_name(true)?;
701 CopyToSource::Relation(table_name)
702 };
703
704 #[derive(Default)]
705 struct Builder {
706 stored_as: Option<String>,
707 target: Option<String>,
708 partitioned_by: Option<Vec<String>>,
709 options: Option<Vec<(String, Value)>>,
710 }
711
712 let mut builder = Builder::default();
713
714 loop {
715 if let Some(keyword) = self.parser.parse_one_of_keywords(&[
716 Keyword::STORED,
717 Keyword::TO,
718 Keyword::PARTITIONED,
719 Keyword::OPTIONS,
720 Keyword::WITH,
721 ]) {
722 match keyword {
723 Keyword::STORED => {
724 self.parser.expect_keyword(Keyword::AS)?;
725 ensure_not_set(&builder.stored_as, "STORED AS")?;
726 builder.stored_as = Some(self.parse_file_format()?);
727 }
728 Keyword::TO => {
729 ensure_not_set(&builder.target, "TO")?;
730 builder.target = Some(self.parser.parse_literal_string()?);
731 }
732 Keyword::WITH => {
733 self.parser.expect_keyword(Keyword::HEADER)?;
734 self.parser.expect_keyword(Keyword::ROW)?;
735 return parser_err!(
736 "WITH HEADER ROW clause is no longer in use. Please use the OPTIONS clause with 'format.has_header' set appropriately, e.g., OPTIONS ('format.has_header' 'true')"
737 )?;
738 }
739 Keyword::PARTITIONED => {
740 self.parser.expect_keyword(Keyword::BY)?;
741 ensure_not_set(&builder.partitioned_by, "PARTITIONED BY")?;
742 builder.partitioned_by = Some(self.parse_partitions()?);
743 }
744 Keyword::OPTIONS => {
745 ensure_not_set(&builder.options, "OPTIONS")?;
746 builder.options = Some(self.parse_value_options()?);
747 }
748 _ => {
749 unreachable!()
750 }
751 }
752 } else {
753 let token = self.parser.peek_token();
754 if token == Token::EOF || token == Token::SemiColon {
755 break;
756 } else {
757 return self.expected("end of statement or ;", &token)?;
758 }
759 }
760 }
761
762 let Some(target) = builder.target else {
763 return parser_err!("Missing TO clause in COPY statement")?;
764 };
765
766 Ok(Statement::CopyTo(CopyToStatement {
767 source,
768 target,
769 partitioned_by: builder.partitioned_by.unwrap_or(vec![]),
770 stored_as: builder.stored_as,
771 options: builder.options.unwrap_or(vec![]),
772 }))
773 }
774
775 pub fn parse_option_key(&mut self) -> Result<String, DataFusionError> {
782 let next_token = self.parser.next_token();
783 match next_token.token {
784 Token::Word(Word { value, .. }) => {
785 let mut parts = vec![value];
786 while self.parser.consume_token(&Token::Period) {
787 let next_token = self.parser.next_token();
788 if let Token::Word(Word { value, .. }) = next_token.token {
789 parts.push(value);
790 } else {
791 return self.expected("key name", &next_token);
795 }
796 }
797 Ok(parts.join("."))
798 }
799 Token::SingleQuotedString(s) => Ok(s),
800 Token::DoubleQuotedString(s) => Ok(s),
801 Token::EscapedStringLiteral(s) => Ok(s),
802 _ => self.expected("key name", &next_token),
803 }
804 }
805
806 pub fn parse_option_value(&mut self) -> Result<Value, DataFusionError> {
813 let next_token = self.parser.next_token();
814 match next_token.token {
815 Token::Word(word) => Ok(Value::SingleQuotedString(word.value)),
817 Token::SingleQuotedString(s) => Ok(Value::SingleQuotedString(s)),
818 Token::DoubleQuotedString(s) => Ok(Value::DoubleQuotedString(s)),
819 Token::EscapedStringLiteral(s) => Ok(Value::EscapedStringLiteral(s)),
820 Token::Number(n, l) => Ok(Value::Number(n, l)),
821 _ => self.expected("string or numeric value", &next_token),
822 }
823 }
824
825 pub fn parse_explain(&mut self) -> Result<Statement, DataFusionError> {
833 if self.supports_explain_with_utility_options
834 && self.parser.peek_token().token == Token::LParen
835 && !token_starts_query(&self.parser.peek_nth_token(1).token)
836 {
837 let raw = self.parser.parse_utility_options()?;
838 let options = ExplainStatementOptions::from_utility_options(&raw)?;
839 let statement = self.parse_statement()?;
840 return Ok(Statement::Explain(ExplainStatement {
841 statement: Box::new(statement),
842 options,
843 }));
844 }
845
846 let analyze = self.parser.parse_keyword(Keyword::ANALYZE);
848 let verbose = self.parser.parse_keyword(Keyword::VERBOSE);
849 let format = self
850 .parse_explain_format()?
851 .map(|s| ExplainFormat::from_str(&s))
852 .transpose()?;
853
854 let statement = self.parse_statement()?;
855
856 let options = ExplainStatementOptions {
857 analyze,
858 verbose,
859 format,
860 ..Default::default()
861 };
862
863 Ok(Statement::Explain(ExplainStatement {
864 statement: Box::new(statement),
865 options,
866 }))
867 }
868
869 pub fn parse_reset(&mut self) -> Result<Statement, DataFusionError> {
871 let mut parts: Vec<String> = Vec::new();
872 let mut expecting_segment = true;
873
874 loop {
875 let next_token = self.parser.peek_token();
876 match &next_token.token {
877 Token::Word(word) => {
878 self.parser.next_token();
879 parts.push(word.value.clone());
880 expecting_segment = false;
881 }
882 Token::SingleQuotedString(s)
883 | Token::DoubleQuotedString(s)
884 | Token::EscapedStringLiteral(s) => {
885 self.parser.next_token();
886 parts.push(s.clone());
887 expecting_segment = false;
888 }
889 Token::Period => {
890 self.parser.next_token();
891 if expecting_segment || parts.is_empty() {
892 return self.expected("configuration parameter", &next_token);
893 }
894 expecting_segment = true;
895 }
896 Token::EOF | Token::SemiColon => break,
897 _ => return self.expected("configuration parameter", &next_token),
898 }
899 }
900
901 if parts.is_empty() || expecting_segment {
902 return self.expected("configuration parameter", &self.parser.peek_token());
903 }
904
905 let idents: Vec<Ident> = parts.into_iter().map(Ident::new).collect();
906 let variable = ObjectName::from(idents);
907 Ok(Statement::Reset(ResetStatement::Variable(variable)))
908 }
909
910 pub fn parse_explain_format(&mut self) -> Result<Option<String>, DataFusionError> {
911 if !self.parser.parse_keyword(Keyword::FORMAT) {
912 return Ok(None);
913 }
914
915 let next_token = self.parser.next_token();
916 let format = match next_token.token {
917 Token::Word(w) => Ok(w.value),
918 Token::SingleQuotedString(w) => Ok(w),
919 Token::DoubleQuotedString(w) => Ok(w),
920 _ => self.expected("an explain format such as TREE", &next_token),
921 }?;
922 Ok(Some(format))
923 }
924
925 pub fn parse_create(&mut self) -> Result<Statement, DataFusionError> {
927 if self
929 .parser
930 .parse_keywords(&[Keyword::OR, Keyword::REPLACE, Keyword::EXTERNAL])
931 {
932 self.parse_create_external_table(false, true)
933 } else if self.parser.parse_keywords(&[
934 Keyword::OR,
935 Keyword::REPLACE,
936 Keyword::UNBOUNDED,
937 Keyword::EXTERNAL,
938 ]) {
939 self.parse_create_external_table(true, true)
940 } else if self.parser.parse_keyword(Keyword::EXTERNAL) {
941 self.parse_create_external_table(false, false)
942 } else if self
943 .parser
944 .parse_keywords(&[Keyword::UNBOUNDED, Keyword::EXTERNAL])
945 {
946 self.parse_create_external_table(true, false)
947 } else {
948 Ok(Statement::Statement(Box::from(self.parser.parse_create()?)))
949 }
950 }
951
952 fn parse_partitions(&mut self) -> Result<Vec<String>, DataFusionError> {
953 let mut partitions: Vec<String> = vec![];
954 if !self.parser.consume_token(&Token::LParen)
955 || self.parser.consume_token(&Token::RParen)
956 {
957 return Ok(partitions);
958 }
959
960 loop {
961 if let Token::Word(_) = self.parser.peek_token().token {
962 let identifier = self.parser.parse_identifier()?;
963 partitions.push(identifier.to_string());
964 } else {
965 return self.expected("partition name", &self.parser.peek_token());
966 }
967 let comma = self.parser.consume_token(&Token::Comma);
968 if self.parser.consume_token(&Token::RParen) {
969 break;
971 } else if !comma {
972 return self.expected(
973 "',' or ')' after partition definition",
974 &self.parser.peek_token(),
975 );
976 }
977 }
978 Ok(partitions)
979 }
980
981 pub fn parse_order_by_exprs(&mut self) -> Result<Vec<OrderByExpr>, DataFusionError> {
983 let mut values = vec![];
984 self.parser.expect_token(&Token::LParen)?;
985 loop {
986 values.push(self.parse_order_by_expr()?);
987 if !self.parser.consume_token(&Token::Comma) {
988 self.parser.expect_token(&Token::RParen)?;
989 return Ok(values);
990 }
991 }
992 }
993
994 pub fn parse_order_by_expr(&mut self) -> Result<OrderByExpr, DataFusionError> {
996 let expr = self.parser.parse_expr()?;
997
998 let asc = if self.parser.parse_keyword(Keyword::ASC) {
999 Some(true)
1000 } else if self.parser.parse_keyword(Keyword::DESC) {
1001 Some(false)
1002 } else {
1003 None
1004 };
1005
1006 let nulls_first = if self
1007 .parser
1008 .parse_keywords(&[Keyword::NULLS, Keyword::FIRST])
1009 {
1010 Some(true)
1011 } else if self.parser.parse_keywords(&[Keyword::NULLS, Keyword::LAST]) {
1012 Some(false)
1013 } else {
1014 None
1015 };
1016
1017 Ok(OrderByExpr {
1018 expr,
1019 options: OrderByOptions { asc, nulls_first },
1020 with_fill: None,
1021 })
1022 }
1023
1024 fn parse_columns(
1026 &mut self,
1027 ) -> Result<(Vec<ColumnDef>, Vec<TableConstraint>), DataFusionError> {
1028 let mut columns = vec![];
1029 let mut constraints = vec![];
1030 if !self.parser.consume_token(&Token::LParen)
1031 || self.parser.consume_token(&Token::RParen)
1032 {
1033 return Ok((columns, constraints));
1034 }
1035
1036 loop {
1037 if let Some(constraint) = self.parser.parse_optional_table_constraint()? {
1038 constraints.push(constraint);
1039 } else if let Token::Word(_) = self.parser.peek_token().token {
1040 let column_def = self.parse_column_def()?;
1041 columns.push(column_def);
1042 } else {
1043 return self.expected(
1044 "column name or constraint definition",
1045 &self.parser.peek_token(),
1046 );
1047 }
1048 let comma = self.parser.consume_token(&Token::Comma);
1049 if self.parser.consume_token(&Token::RParen) {
1050 break;
1052 } else if !comma {
1053 return self.expected(
1054 "',' or ')' after column definition",
1055 &self.parser.peek_token(),
1056 );
1057 }
1058 }
1059
1060 Ok((columns, constraints))
1061 }
1062
1063 fn parse_column_def(&mut self) -> Result<ColumnDef, DataFusionError> {
1064 let name = self.parser.parse_identifier()?;
1065 let data_type = self.parser.parse_data_type()?;
1066 let mut options = vec![];
1067 loop {
1068 if self.parser.parse_keyword(Keyword::CONSTRAINT) {
1069 let name = Some(self.parser.parse_identifier()?);
1070 if let Some(option) = self.parser.parse_optional_column_option()? {
1071 options.push(ColumnOptionDef { name, option });
1072 } else {
1073 return self.expected(
1074 "constraint details after CONSTRAINT <name>",
1075 &self.parser.peek_token(),
1076 );
1077 }
1078 } else if let Some(option) = self.parser.parse_optional_column_option()? {
1079 options.push(ColumnOptionDef { name: None, option });
1080 } else {
1081 break;
1082 };
1083 }
1084 Ok(ColumnDef {
1085 name,
1086 data_type,
1087 options,
1088 })
1089 }
1090
1091 fn parse_create_external_table(
1092 &mut self,
1093 unbounded: bool,
1094 or_replace: bool,
1095 ) -> Result<Statement, DataFusionError> {
1096 let temporary = self
1097 .parser
1098 .parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
1099 .is_some();
1100
1101 self.parser.expect_keyword(Keyword::TABLE)?;
1102 let if_not_exists =
1103 self.parser
1104 .parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
1105
1106 if if_not_exists && or_replace {
1107 return parser_err!("'IF NOT EXISTS' cannot coexist with 'REPLACE'");
1108 }
1109
1110 let table_name = self.parser.parse_object_name(true)?;
1111 let (mut columns, constraints) = self.parse_columns()?;
1112
1113 #[derive(Default)]
1114 struct Builder {
1115 file_type: Option<String>,
1116 locations: Option<Vec<String>>,
1117 table_partition_cols: Option<Vec<String>>,
1118 order_exprs: Vec<LexOrdering>,
1119 options: Option<Vec<(String, Value)>>,
1120 }
1121 let mut builder = Builder::default();
1122
1123 loop {
1124 if let Some(keyword) = self.parser.parse_one_of_keywords(&[
1125 Keyword::STORED,
1126 Keyword::LOCATION,
1127 Keyword::WITH,
1128 Keyword::DELIMITER,
1129 Keyword::COMPRESSION,
1130 Keyword::PARTITIONED,
1131 Keyword::OPTIONS,
1132 ]) {
1133 match keyword {
1134 Keyword::STORED => {
1135 self.parser.expect_keyword(Keyword::AS)?;
1136 ensure_not_set(&builder.file_type, "STORED AS")?;
1137 builder.file_type = Some(self.parse_file_format()?);
1138 }
1139 Keyword::LOCATION => {
1140 ensure_not_set(&builder.locations, "LOCATION")?;
1141 builder.locations = Some(self.parse_locations()?);
1142 }
1143 Keyword::WITH => {
1144 if self.parser.parse_keyword(Keyword::ORDER) {
1145 builder.order_exprs.push(self.parse_order_by_exprs()?);
1146 } else {
1147 self.parser.expect_keyword(Keyword::HEADER)?;
1148 self.parser.expect_keyword(Keyword::ROW)?;
1149 return parser_err!(
1150 "WITH HEADER ROW clause is no longer in use. Please use the OPTIONS clause with 'format.has_header' set appropriately, e.g., OPTIONS (format.has_header true)"
1151 )?;
1152 }
1153 }
1154 Keyword::DELIMITER => {
1155 return parser_err!(
1156 "DELIMITER clause is no longer in use. Please use the OPTIONS clause with 'format.delimiter' set appropriately, e.g., OPTIONS (format.delimiter ',')"
1157 )?;
1158 }
1159 Keyword::COMPRESSION => {
1160 self.parser.expect_keyword(Keyword::TYPE)?;
1161 return parser_err!(
1162 "COMPRESSION TYPE clause is no longer in use. Please use the OPTIONS clause with 'format.compression' set appropriately, e.g., OPTIONS (format.compression gzip)"
1163 )?;
1164 }
1165 Keyword::PARTITIONED => {
1166 self.parser.expect_keyword(Keyword::BY)?;
1167 ensure_not_set(&builder.table_partition_cols, "PARTITIONED BY")?;
1168 let peeked = self.parser.peek_nth_token(2);
1173 if peeked == Token::Comma || peeked == Token::RParen {
1174 builder.table_partition_cols = Some(self.parse_partitions()?)
1176 } else {
1177 let (cols, cons) = self.parse_columns()?;
1179 builder.table_partition_cols = Some(
1180 cols.iter().map(|col| col.name.to_string()).collect(),
1181 );
1182
1183 columns.extend(cols);
1184
1185 if !cons.is_empty() {
1186 return sql_err!(ParserError::ParserError(
1187 "Constraints on Partition Columns are not supported"
1188 .to_string(),
1189 ));
1190 }
1191 }
1192 }
1193 Keyword::OPTIONS => {
1194 ensure_not_set(&builder.options, "OPTIONS")?;
1195 builder.options = Some(self.parse_value_options()?);
1196 }
1197 _ => {
1198 unreachable!()
1199 }
1200 }
1201 } else {
1202 let token = self.parser.peek_token();
1203 if token == Token::EOF || token == Token::SemiColon {
1204 break;
1205 } else {
1206 return self.expected("end of statement or ;", &token)?;
1207 }
1208 }
1209 }
1210
1211 if builder.file_type.is_none() {
1213 return sql_err!(ParserError::ParserError(
1214 "Missing STORED AS clause in CREATE EXTERNAL TABLE statement".into(),
1215 ));
1216 }
1217 if builder.locations.is_none() {
1218 return sql_err!(ParserError::ParserError(
1219 "Missing LOCATION clause in CREATE EXTERNAL TABLE statement".into(),
1220 ));
1221 }
1222
1223 let locations = builder.locations.unwrap();
1224 if locations.is_empty() {
1225 return parser_err!("LOCATION requires at least one path");
1226 }
1227
1228 let create = CreateExternalTable {
1229 name: table_name,
1230 columns,
1231 file_type: builder.file_type.unwrap(),
1232 locations,
1233 table_partition_cols: builder.table_partition_cols.unwrap_or(vec![]),
1234 order_exprs: builder.order_exprs,
1235 if_not_exists,
1236 or_replace,
1237 temporary,
1238 unbounded,
1239 options: builder.options.unwrap_or(Vec::new()),
1240 constraints,
1241 };
1242 Ok(Statement::CreateExternalTable(create))
1243 }
1244
1245 fn parse_locations(&mut self) -> Result<Vec<String>, DataFusionError> {
1247 if !self.parser.consume_token(&Token::LParen) {
1248 return Ok(vec![self.parser.parse_literal_string()?]);
1249 }
1250
1251 let mut locations = vec![];
1252 loop {
1253 locations.push(self.parser.parse_literal_string()?);
1254 let comma = self.parser.consume_token(&Token::Comma);
1255 if self.parser.consume_token(&Token::RParen) {
1256 break;
1258 } else if !comma {
1259 return self.expected(
1260 "',' or ')' after location definition",
1261 &self.parser.peek_token(),
1262 );
1263 }
1264 }
1265 Ok(locations)
1266 }
1267
1268 fn parse_file_format(&mut self) -> Result<String, DataFusionError> {
1270 let token = self.parser.next_token();
1271 match &token.token {
1272 Token::Word(w) => parse_file_type(&w.value),
1273 _ => self.expected("one of ARROW, PARQUET, NDJSON, or CSV", &token),
1274 }
1275 }
1276
1277 fn parse_value_options(&mut self) -> Result<Vec<(String, Value)>, DataFusionError> {
1282 let mut options = vec![];
1283 self.parser.expect_token(&Token::LParen)?;
1284
1285 loop {
1286 let key = self.parse_option_key()?;
1287 let value = self.parse_option_value()?;
1288 options.push((key, value));
1289 let comma = self.parser.consume_token(&Token::Comma);
1290 if self.parser.consume_token(&Token::RParen) {
1291 break;
1293 } else if !comma {
1294 return self.expected(
1295 "',' or ')' after option definition",
1296 &self.parser.peek_token(),
1297 );
1298 }
1299 }
1300 Ok(options)
1301 }
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306 use super::*;
1307 use datafusion_common::assert_contains;
1308 use sqlparser::ast::Expr::Identifier;
1309 use sqlparser::ast::{
1310 BinaryOperator, DataType, ExactNumberInfo, Expr, Ident, ValueWithSpan,
1311 };
1312 use sqlparser::dialect::SnowflakeDialect;
1313 use sqlparser::tokenizer::{Location, Span, Whitespace};
1314
1315 fn expect_parse_ok(sql: &str, expected: Statement) -> Result<(), DataFusionError> {
1316 let statements = DFParser::parse_sql(sql)?;
1317 assert_eq!(
1318 statements.len(),
1319 1,
1320 "Expected to parse exactly one statement"
1321 );
1322 assert_eq!(statements[0], expected, "actual:\n{:#?}", statements[0]);
1323 Ok(())
1324 }
1325
1326 fn expect_parse_error(sql: &str, expected_error: &str) {
1328 match DFParser::parse_sql(sql) {
1329 Ok(statements) => {
1330 panic!(
1331 "Expected parse error for '{sql}', but was successful: {statements:?}"
1332 );
1333 }
1334 Err(e) => {
1335 let error_message = e.to_string();
1336 assert!(
1337 error_message.contains(expected_error),
1338 "Expected error '{expected_error}' not found in actual error '{error_message}'"
1339 );
1340 }
1341 }
1342 }
1343
1344 fn make_column_def(name: impl Into<String>, data_type: DataType) -> ColumnDef {
1345 ColumnDef {
1346 name: Ident {
1347 value: name.into(),
1348 quote_style: None,
1349 span: Span::empty(),
1350 },
1351 data_type,
1352 options: vec![],
1353 }
1354 }
1355
1356 fn make_create_external_table(location: &str) -> CreateExternalTable {
1357 make_create_external_table_with_locations(&[location])
1358 }
1359
1360 fn make_create_external_table_with_locations(
1361 locations: &[&str],
1362 ) -> CreateExternalTable {
1363 let locations = locations
1364 .iter()
1365 .map(|location| location.to_string())
1366 .collect::<Vec<_>>();
1367
1368 CreateExternalTable {
1369 name: ObjectName::from(vec![Ident::from("t")]),
1370 columns: vec![],
1371 file_type: "CSV".to_string(),
1372 locations,
1373 table_partition_cols: vec![],
1374 order_exprs: vec![],
1375 if_not_exists: false,
1376 or_replace: false,
1377 temporary: false,
1378 unbounded: false,
1379 options: vec![],
1380 constraints: vec![],
1381 }
1382 }
1383
1384 #[test]
1385 fn create_external_table() -> Result<(), DataFusionError> {
1386 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv'";
1388 let display = None;
1389 let expected = Statement::CreateExternalTable(CreateExternalTable {
1390 columns: vec![make_column_def("c1", DataType::Int(display))],
1391 ..make_create_external_table("foo.csv")
1392 });
1393 expect_parse_ok(sql, expected)?;
1394
1395 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo,bar.csv'";
1397 let expected = Statement::CreateExternalTable(CreateExternalTable {
1398 columns: vec![make_column_def("c1", DataType::Int(display))],
1399 ..make_create_external_table("foo,bar.csv")
1400 });
1401 expect_parse_ok(sql, expected)?;
1402
1403 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION ('foo.csv', 'bar.csv')";
1405 let expected = Statement::CreateExternalTable(CreateExternalTable {
1406 columns: vec![make_column_def("c1", DataType::Int(display))],
1407 ..make_create_external_table_with_locations(&["foo.csv", "bar.csv"])
1408 });
1409 expect_parse_ok(sql, expected)?;
1410
1411 assert_eq!(
1412 Statement::CreateExternalTable(make_create_external_table("foo.csv"))
1413 .to_string(),
1414 "CREATE EXTERNAL TABLE t STORED AS CSV LOCATION 'foo.csv'"
1415 );
1416 assert_eq!(
1417 Statement::CreateExternalTable(make_create_external_table_with_locations(&[
1418 "foo.csv", "bar.csv"
1419 ]))
1420 .to_string(),
1421 "CREATE EXTERNAL TABLE t STORED AS CSV LOCATION ('foo.csv', 'bar.csv')"
1422 );
1423 assert_eq!(
1424 Statement::CreateExternalTable(make_create_external_table("foo'bar.csv"))
1425 .to_string(),
1426 "CREATE EXTERNAL TABLE t STORED AS CSV LOCATION 'foo''bar.csv'"
1427 );
1428
1429 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' ";
1431 let expected = Statement::CreateExternalTable(CreateExternalTable {
1432 columns: vec![make_column_def("c1", DataType::Int(None))],
1433 ..make_create_external_table("foo.csv")
1434 });
1435 expect_parse_ok(sql, expected)?;
1436
1437 let sql =
1439 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' ;";
1440 let expected = Statement::CreateExternalTable(CreateExternalTable {
1441 columns: vec![make_column_def("c1", DataType::Int(None))],
1442 ..make_create_external_table("foo.csv")
1443 });
1444 expect_parse_ok(sql, expected)?;
1445
1446 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS (format.delimiter '|')";
1448 let display = None;
1449 let expected = Statement::CreateExternalTable(CreateExternalTable {
1450 columns: vec![make_column_def("c1", DataType::Int(display))],
1451 options: vec![(
1452 "format.delimiter".into(),
1453 Value::SingleQuotedString("|".into()),
1454 )],
1455 ..make_create_external_table("foo.csv")
1456 });
1457 expect_parse_ok(sql, expected)?;
1458
1459 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV PARTITIONED BY (p1, p2) LOCATION 'foo.csv'";
1461 let display = None;
1462 let expected = Statement::CreateExternalTable(CreateExternalTable {
1463 columns: vec![make_column_def("c1", DataType::Int(display))],
1464 table_partition_cols: vec!["p1".to_string(), "p2".to_string()],
1465 ..make_create_external_table("foo.csv")
1466 });
1467 expect_parse_ok(sql, expected)?;
1468
1469 let sqls =
1471 vec![
1472 ("CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS
1473 ('format.compression' 'GZIP')", "GZIP"),
1474 ("CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS
1475 ('format.compression' 'BZIP2')", "BZIP2"),
1476 ("CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS
1477 ('format.compression' 'XZ')", "XZ"),
1478 ("CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv' OPTIONS
1479 ('format.compression' 'ZSTD')", "ZSTD"),
1480 ];
1481 for (sql, compression) in sqls {
1482 let expected = Statement::CreateExternalTable(CreateExternalTable {
1483 columns: vec![make_column_def("c1", DataType::Int(display))],
1484 options: vec![(
1485 "format.compression".into(),
1486 Value::SingleQuotedString(compression.into()),
1487 )],
1488 ..make_create_external_table("foo.csv")
1489 });
1490 expect_parse_ok(sql, expected)?;
1491 }
1492
1493 let sql = "CREATE EXTERNAL TABLE t STORED AS PARQUET LOCATION 'foo.parquet'";
1495 let expected = Statement::CreateExternalTable(CreateExternalTable {
1496 file_type: "PARQUET".to_string(),
1497 ..make_create_external_table("foo.parquet")
1498 });
1499 expect_parse_ok(sql, expected)?;
1500
1501 let sql = "CREATE EXTERNAL TABLE t STORED AS parqueT LOCATION 'foo.parquet'";
1503 let expected = Statement::CreateExternalTable(CreateExternalTable {
1504 file_type: "PARQUET".to_string(),
1505 ..make_create_external_table("foo.parquet")
1506 });
1507 expect_parse_ok(sql, expected)?;
1508
1509 let sql = "CREATE EXTERNAL TABLE t STORED AS AVRO LOCATION 'foo.avro'";
1511 let expected = Statement::CreateExternalTable(CreateExternalTable {
1512 file_type: "AVRO".to_string(),
1513 ..make_create_external_table("foo.avro")
1514 });
1515 expect_parse_ok(sql, expected)?;
1516
1517 let sql = "CREATE EXTERNAL TABLE IF NOT EXISTS t STORED AS PARQUET LOCATION 'foo.parquet'";
1519 let expected = Statement::CreateExternalTable(CreateExternalTable {
1520 file_type: "PARQUET".to_string(),
1521 if_not_exists: true,
1522 ..make_create_external_table("foo.parquet")
1523 });
1524 expect_parse_ok(sql, expected)?;
1525
1526 let sql =
1528 "CREATE OR REPLACE EXTERNAL TABLE t STORED AS PARQUET LOCATION 'foo.parquet'";
1529 let expected = Statement::CreateExternalTable(CreateExternalTable {
1530 file_type: "PARQUET".to_string(),
1531 or_replace: true,
1532 ..make_create_external_table("foo.parquet")
1533 });
1534 expect_parse_ok(sql, expected)?;
1535
1536 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV PARTITIONED BY (p1 int) LOCATION 'foo.csv'";
1538 let expected = Statement::CreateExternalTable(CreateExternalTable {
1539 columns: vec![
1540 make_column_def("c1", DataType::Int(None)),
1541 make_column_def("p1", DataType::Int(None)),
1542 ],
1543 table_partition_cols: vec!["p1".to_string()],
1544 ..make_create_external_table("foo.csv")
1545 });
1546 expect_parse_ok(sql, expected)?;
1547
1548 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV PARTITIONED BY (p1 int, c1) LOCATION 'foo.csv'";
1550 expect_parse_error(
1551 sql,
1552 "SQL error: ParserError(\"Expected: a data type name, found: ) at Line: 1, Column: 73\")",
1553 );
1554
1555 let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV PARTITIONED BY (c1, p1 int) LOCATION 'foo.csv'";
1557 expect_parse_error(
1558 sql,
1559 "SQL error: ParserError(\"Expected: ',' or ')' after partition definition, found: int at Line: 1, Column: 70\")",
1560 );
1561
1562 let sql =
1564 "CREATE EXTERNAL TABLE t STORED AS x OPTIONS ('k1' 'v1') LOCATION 'blahblah'";
1565 let expected = Statement::CreateExternalTable(CreateExternalTable {
1566 file_type: "X".to_string(),
1567 options: vec![("k1".into(), Value::SingleQuotedString("v1".into()))],
1568 ..make_create_external_table("blahblah")
1569 });
1570 expect_parse_ok(sql, expected)?;
1571
1572 let sql = "CREATE EXTERNAL TABLE t STORED AS x OPTIONS ('k1' 'v1', k2 v2) LOCATION 'blahblah'";
1574 let expected = Statement::CreateExternalTable(CreateExternalTable {
1575 file_type: "X".to_string(),
1576 options: vec![
1577 ("k1".into(), Value::SingleQuotedString("v1".into())),
1578 ("k2".into(), Value::SingleQuotedString("v2".into())),
1579 ],
1580 ..make_create_external_table("blahblah")
1581 });
1582 expect_parse_ok(sql, expected)?;
1583
1584 let sqls = [
1586 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1) LOCATION 'foo.csv'",
1587 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 NULLS FIRST) LOCATION 'foo.csv'",
1588 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 NULLS LAST) LOCATION 'foo.csv'",
1589 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 ASC) LOCATION 'foo.csv'",
1590 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 DESC) LOCATION 'foo.csv'",
1591 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 DESC NULLS FIRST) LOCATION 'foo.csv'",
1592 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 DESC NULLS LAST) LOCATION 'foo.csv'",
1593 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 ASC NULLS FIRST) LOCATION 'foo.csv'",
1594 "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV WITH ORDER (c1 ASC NULLS LAST) LOCATION 'foo.csv'",
1595 ];
1596 let expected = vec![
1597 (None, None),
1598 (None, Some(true)),
1599 (None, Some(false)),
1600 (Some(true), None),
1601 (Some(false), None),
1602 (Some(false), Some(true)),
1603 (Some(false), Some(false)),
1604 (Some(true), Some(true)),
1605 (Some(true), Some(false)),
1606 ];
1607 for (sql, (asc, nulls_first)) in sqls.iter().zip(expected) {
1608 let expected = Statement::CreateExternalTable(CreateExternalTable {
1609 columns: vec![make_column_def("c1", DataType::Int(None))],
1610 order_exprs: vec![vec![OrderByExpr {
1611 expr: Identifier(Ident {
1612 value: "c1".to_owned(),
1613 quote_style: None,
1614 span: Span::empty(),
1615 }),
1616 options: OrderByOptions { asc, nulls_first },
1617 with_fill: None,
1618 }]],
1619 ..make_create_external_table("foo.csv")
1620 });
1621 expect_parse_ok(sql, expected)?;
1622 }
1623
1624 let sql = "CREATE EXTERNAL TABLE t(c1 int, c2 int) STORED AS CSV WITH ORDER (c1 ASC, c2 DESC NULLS FIRST) LOCATION 'foo.csv'";
1626 let display = None;
1627 let expected = Statement::CreateExternalTable(CreateExternalTable {
1628 columns: vec![
1629 make_column_def("c1", DataType::Int(display)),
1630 make_column_def("c2", DataType::Int(display)),
1631 ],
1632 order_exprs: vec![vec![
1633 OrderByExpr {
1634 expr: Identifier(Ident {
1635 value: "c1".to_owned(),
1636 quote_style: None,
1637 span: Span::empty(),
1638 }),
1639 options: OrderByOptions {
1640 asc: Some(true),
1641 nulls_first: None,
1642 },
1643 with_fill: None,
1644 },
1645 OrderByExpr {
1646 expr: Identifier(Ident {
1647 value: "c2".to_owned(),
1648 quote_style: None,
1649 span: Span::empty(),
1650 }),
1651 options: OrderByOptions {
1652 asc: Some(false),
1653 nulls_first: Some(true),
1654 },
1655 with_fill: None,
1656 },
1657 ]],
1658 ..make_create_external_table("foo.csv")
1659 });
1660 expect_parse_ok(sql, expected)?;
1661
1662 let sql = "CREATE EXTERNAL TABLE t(c1 int, c2 int) STORED AS CSV WITH ORDER (c1 - c2 ASC) LOCATION 'foo.csv'";
1664 let display = None;
1665 let expected = Statement::CreateExternalTable(CreateExternalTable {
1666 columns: vec![
1667 make_column_def("c1", DataType::Int(display)),
1668 make_column_def("c2", DataType::Int(display)),
1669 ],
1670 order_exprs: vec![vec![OrderByExpr {
1671 expr: Expr::BinaryOp {
1672 left: Box::new(Identifier(Ident {
1673 value: "c1".to_owned(),
1674 quote_style: None,
1675 span: Span::empty(),
1676 })),
1677 op: BinaryOperator::Minus,
1678 right: Box::new(Identifier(Ident {
1679 value: "c2".to_owned(),
1680 quote_style: None,
1681 span: Span::empty(),
1682 })),
1683 },
1684 options: OrderByOptions {
1685 asc: Some(true),
1686 nulls_first: None,
1687 },
1688 with_fill: None,
1689 }]],
1690 ..make_create_external_table("foo.csv")
1691 });
1692 expect_parse_ok(sql, expected)?;
1693
1694 let sql = "
1696 CREATE UNBOUNDED EXTERNAL TABLE IF NOT EXISTS t (c1 int, c2 float)
1697 STORED AS PARQUET
1698 WITH ORDER (c1 - c2 ASC)
1699 PARTITIONED BY (c1)
1700 LOCATION 'foo.parquet'
1701 OPTIONS ('format.compression' 'zstd',
1702 'format.delimiter' '*',
1703 'ROW_GROUP_SIZE' '1024',
1704 'TRUNCATE' 'NO',
1705 'format.has_header' 'true')";
1706 let expected = Statement::CreateExternalTable(CreateExternalTable {
1707 columns: vec![
1708 make_column_def("c1", DataType::Int(None)),
1709 make_column_def("c2", DataType::Float(ExactNumberInfo::None)),
1710 ],
1711 file_type: "PARQUET".to_string(),
1712 table_partition_cols: vec!["c1".into()],
1713 order_exprs: vec![vec![OrderByExpr {
1714 expr: Expr::BinaryOp {
1715 left: Box::new(Identifier(Ident {
1716 value: "c1".to_owned(),
1717 quote_style: None,
1718 span: Span::empty(),
1719 })),
1720 op: BinaryOperator::Minus,
1721 right: Box::new(Identifier(Ident {
1722 value: "c2".to_owned(),
1723 quote_style: None,
1724 span: Span::empty(),
1725 })),
1726 },
1727 options: OrderByOptions {
1728 asc: Some(true),
1729 nulls_first: None,
1730 },
1731 with_fill: None,
1732 }]],
1733 if_not_exists: true,
1734 unbounded: true,
1735 options: vec![
1736 (
1737 "format.compression".into(),
1738 Value::SingleQuotedString("zstd".into()),
1739 ),
1740 (
1741 "format.delimiter".into(),
1742 Value::SingleQuotedString("*".into()),
1743 ),
1744 (
1745 "ROW_GROUP_SIZE".into(),
1746 Value::SingleQuotedString("1024".into()),
1747 ),
1748 ("TRUNCATE".into(), Value::SingleQuotedString("NO".into())),
1749 (
1750 "format.has_header".into(),
1751 Value::SingleQuotedString("true".into()),
1752 ),
1753 ],
1754 ..make_create_external_table("foo.parquet")
1755 });
1756 expect_parse_ok(sql, expected)?;
1757
1758 let sql = "
1760 CREATE OR REPLACE UNBOUNDED EXTERNAL TABLE t (c1 int, c2 float)
1761 STORED AS PARQUET
1762 WITH ORDER (c1 - c2 ASC)
1763 PARTITIONED BY (c1)
1764 LOCATION 'foo.parquet'
1765 OPTIONS ('format.compression' 'zstd',
1766 'format.delimiter' '*',
1767 'ROW_GROUP_SIZE' '1024',
1768 'TRUNCATE' 'NO',
1769 'format.has_header' 'true')";
1770 let expected = Statement::CreateExternalTable(CreateExternalTable {
1771 columns: vec![
1772 make_column_def("c1", DataType::Int(None)),
1773 make_column_def("c2", DataType::Float(ExactNumberInfo::None)),
1774 ],
1775 file_type: "PARQUET".to_string(),
1776 table_partition_cols: vec!["c1".into()],
1777 order_exprs: vec![vec![OrderByExpr {
1778 expr: Expr::BinaryOp {
1779 left: Box::new(Identifier(Ident {
1780 value: "c1".to_owned(),
1781 quote_style: None,
1782 span: Span::empty(),
1783 })),
1784 op: BinaryOperator::Minus,
1785 right: Box::new(Identifier(Ident {
1786 value: "c2".to_owned(),
1787 quote_style: None,
1788 span: Span::empty(),
1789 })),
1790 },
1791 options: OrderByOptions {
1792 asc: Some(true),
1793 nulls_first: None,
1794 },
1795 with_fill: None,
1796 }]],
1797 or_replace: true,
1798 unbounded: true,
1799 options: vec![
1800 (
1801 "format.compression".into(),
1802 Value::SingleQuotedString("zstd".into()),
1803 ),
1804 (
1805 "format.delimiter".into(),
1806 Value::SingleQuotedString("*".into()),
1807 ),
1808 (
1809 "ROW_GROUP_SIZE".into(),
1810 Value::SingleQuotedString("1024".into()),
1811 ),
1812 ("TRUNCATE".into(), Value::SingleQuotedString("NO".into())),
1813 (
1814 "format.has_header".into(),
1815 Value::SingleQuotedString("true".into()),
1816 ),
1817 ],
1818 ..make_create_external_table("foo.parquet")
1819 });
1820 expect_parse_ok(sql, expected)?;
1821
1822 Ok(())
1825 }
1826
1827 #[test]
1828 fn copy_to_table_to_table() -> Result<(), DataFusionError> {
1829 let sql = "COPY foo TO bar STORED AS CSV";
1831 let expected = Statement::CopyTo(CopyToStatement {
1832 source: object_name("foo"),
1833 target: "bar".to_string(),
1834 partitioned_by: vec![],
1835 stored_as: Some("CSV".to_owned()),
1836 options: vec![],
1837 });
1838
1839 assert_eq!(verified_stmt(sql), expected);
1840 Ok(())
1841 }
1842
1843 #[test]
1844 fn skip_copy_into_snowflake() -> Result<(), DataFusionError> {
1845 let sql = "COPY INTO foo FROM @~/staged FILE_FORMAT = (FORMAT_NAME = 'mycsv');";
1846 let dialect = Box::new(SnowflakeDialect);
1847 let statements = DFParser::parse_sql_with_dialect(sql, dialect.as_ref())?;
1848
1849 assert_eq!(
1850 statements.len(),
1851 1,
1852 "Expected to parse exactly one statement"
1853 );
1854 if let Statement::CopyTo(_) = &statements[0] {
1855 panic!("Expected non COPY TO statement, but was successful: {statements:?}");
1856 }
1857 Ok(())
1858 }
1859
1860 #[test]
1861 fn explain_copy_to_table_to_table() -> Result<(), DataFusionError> {
1862 let cases = vec![
1863 ("EXPLAIN COPY foo TO bar STORED AS PARQUET", false, false),
1864 (
1865 "EXPLAIN ANALYZE COPY foo TO bar STORED AS PARQUET",
1866 true,
1867 false,
1868 ),
1869 (
1870 "EXPLAIN VERBOSE COPY foo TO bar STORED AS PARQUET",
1871 false,
1872 true,
1873 ),
1874 (
1875 "EXPLAIN ANALYZE VERBOSE COPY foo TO bar STORED AS PARQUET",
1876 true,
1877 true,
1878 ),
1879 ];
1880 for (sql, analyze, verbose) in cases {
1881 println!("sql: {sql}, analyze: {analyze}, verbose: {verbose}");
1882
1883 let expected_copy = Statement::CopyTo(CopyToStatement {
1884 source: object_name("foo"),
1885 target: "bar".to_string(),
1886 partitioned_by: vec![],
1887 stored_as: Some("PARQUET".to_owned()),
1888 options: vec![],
1889 });
1890 let expected = Statement::Explain(ExplainStatement {
1891 options: ExplainStatementOptions {
1892 analyze,
1893 verbose,
1894 format: None,
1895 analyze_level: None,
1896 analyze_categories: None,
1897 show_statistics: None,
1898 },
1899 statement: Box::new(expected_copy),
1900 });
1901 assert_eq!(verified_stmt(sql), expected);
1902 }
1903 Ok(())
1904 }
1905
1906 #[test]
1907 fn copy_to_query_to_table() -> Result<(), DataFusionError> {
1908 let statement = verified_stmt("SELECT 1");
1909
1910 let statement = if let Statement::Statement(statement) = statement {
1912 *statement
1913 } else {
1914 panic!("Expected statement, got {statement:?}");
1915 };
1916
1917 let query = if let SQLStatement::Query(query) = statement {
1918 query
1919 } else {
1920 panic!("Expected query, got {statement:?}");
1921 };
1922
1923 let sql =
1924 "COPY (SELECT 1) TO bar STORED AS CSV OPTIONS ('format.has_header' 'true')";
1925 let expected = Statement::CopyTo(CopyToStatement {
1926 source: CopyToSource::Query(query),
1927 target: "bar".to_string(),
1928 partitioned_by: vec![],
1929 stored_as: Some("CSV".to_owned()),
1930 options: vec![(
1931 "format.has_header".into(),
1932 Value::SingleQuotedString("true".into()),
1933 )],
1934 });
1935 assert_eq!(verified_stmt(sql), expected);
1936 Ok(())
1937 }
1938
1939 #[test]
1940 fn copy_to_options() -> Result<(), DataFusionError> {
1941 let sql = "COPY foo TO bar STORED AS CSV OPTIONS ('row_group_size' '55')";
1942 let expected = Statement::CopyTo(CopyToStatement {
1943 source: object_name("foo"),
1944 target: "bar".to_string(),
1945 partitioned_by: vec![],
1946 stored_as: Some("CSV".to_owned()),
1947 options: vec![(
1948 "row_group_size".to_string(),
1949 Value::SingleQuotedString("55".to_string()),
1950 )],
1951 });
1952 assert_eq!(verified_stmt(sql), expected);
1953 Ok(())
1954 }
1955
1956 #[test]
1957 fn copy_to_partitioned_by() -> Result<(), DataFusionError> {
1958 let sql = "COPY foo TO bar STORED AS CSV PARTITIONED BY (a) OPTIONS ('row_group_size' '55')";
1959 let expected = Statement::CopyTo(CopyToStatement {
1960 source: object_name("foo"),
1961 target: "bar".to_string(),
1962 partitioned_by: vec!["a".to_string()],
1963 stored_as: Some("CSV".to_owned()),
1964 options: vec![(
1965 "row_group_size".to_string(),
1966 Value::SingleQuotedString("55".to_string()),
1967 )],
1968 });
1969 assert_eq!(verified_stmt(sql), expected);
1970 Ok(())
1971 }
1972
1973 #[test]
1974 fn copy_to_multi_options() -> Result<(), DataFusionError> {
1975 let sql = "COPY foo TO bar STORED AS parquet OPTIONS ('format.row_group_size' 55, 'format.compression' snappy, 'execution.keep_partition_by_columns' true)";
1977
1978 let expected_options = vec![
1979 (
1980 "format.row_group_size".to_string(),
1981 Value::Number("55".to_string(), false),
1982 ),
1983 (
1984 "format.compression".to_string(),
1985 Value::SingleQuotedString("snappy".to_string()),
1986 ),
1987 (
1988 "execution.keep_partition_by_columns".to_string(),
1989 Value::SingleQuotedString("true".to_string()),
1990 ),
1991 ];
1992
1993 let mut statements = DFParser::parse_sql(sql).unwrap();
1994 assert_eq!(statements.len(), 1);
1995 let only_statement = statements.pop_front().unwrap();
1996
1997 let options = if let Statement::CopyTo(copy_to) = only_statement {
1998 copy_to.options
1999 } else {
2000 panic!("Expected copy");
2001 };
2002
2003 assert_eq!(options, expected_options);
2004
2005 Ok(())
2006 }
2007
2008 fn object_name(name: &str) -> CopyToSource {
2011 CopyToSource::Relation(ObjectName::from(vec![Ident::new(name)]))
2012 }
2013
2014 fn one_statement_parses_to(sql: &str, canonical: &str) -> Statement {
2028 let mut statements = DFParser::parse_sql(sql).unwrap();
2029 assert_eq!(statements.len(), 1);
2030
2031 if sql != canonical {
2032 assert_eq!(DFParser::parse_sql(canonical).unwrap(), statements);
2033 }
2034
2035 let only_statement = statements.pop_front().unwrap();
2036 assert_eq!(
2037 canonical.to_uppercase(),
2038 only_statement.to_string().to_uppercase()
2039 );
2040 only_statement
2041 }
2042
2043 fn verified_stmt(sql: &str) -> Statement {
2047 one_statement_parses_to(sql, sql)
2048 }
2049
2050 #[test]
2051 fn test_recursion_limit() {
2054 let sql = "SELECT 1 OR 2";
2055
2056 DFParserBuilder::new(sql)
2058 .build()
2059 .unwrap()
2060 .parse_statements()
2061 .unwrap();
2062
2063 let err = DFParserBuilder::new(sql)
2064 .with_recursion_limit(1)
2065 .build()
2066 .unwrap()
2067 .parse_statements()
2068 .unwrap_err();
2069
2070 assert_contains!(
2071 err.to_string(),
2072 "SQL error: RecursionLimitExceeded (current limit: 1)"
2073 );
2074 }
2075
2076 #[test]
2077 fn test_multistatement() {
2078 let sql = "COPY foo TO bar STORED AS CSV; \
2079 CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv'; \
2080 RESET var;";
2081 let statements = DFParser::parse_sql(sql).unwrap();
2082 assert_eq!(
2083 statements,
2084 vec![
2085 Statement::CopyTo(CopyToStatement {
2086 source: object_name("foo"),
2087 target: "bar".to_string(),
2088 partitioned_by: vec![],
2089 stored_as: Some("CSV".to_owned()),
2090 options: vec![],
2091 }),
2092 {
2093 let display = None;
2094 Statement::CreateExternalTable(CreateExternalTable {
2095 columns: vec![make_column_def("c1", DataType::Int(display))],
2096 ..make_create_external_table("foo.csv")
2097 })
2098 },
2099 {
2100 let name = ObjectName::from(vec![Ident::from("var")]);
2101 Statement::Reset(ResetStatement::Variable(name))
2102 }
2103 ]
2104 );
2105 }
2106
2107 #[test]
2108 fn test_custom_tokens() {
2109 let span = Span {
2111 start: Location { line: 0, column: 0 },
2112 end: Location { line: 0, column: 0 },
2113 };
2114 let tokens = vec![
2115 TokenWithSpan {
2116 token: Token::make_keyword("SELECT"),
2117 span,
2118 },
2119 TokenWithSpan {
2120 token: Token::Whitespace(Whitespace::Space),
2121 span,
2122 },
2123 TokenWithSpan {
2124 token: Token::Placeholder("1".to_string()),
2125 span,
2126 },
2127 ];
2128
2129 let statements = DFParserBuilder::new(tokens)
2130 .build()
2131 .unwrap()
2132 .parse_statements()
2133 .unwrap();
2134 assert_eq!(statements.len(), 1);
2135 }
2136
2137 fn expect_parse_expr_ok(sql: &str, expected: ExprWithAlias) {
2138 let expr = DFParser::parse_sql_into_expr(sql).unwrap();
2139 assert_eq!(expr, expected, "actual:\n{expr:#?}");
2140 }
2141
2142 fn expect_parse_expr_error(sql: &str, expected_error: &str) {
2144 match DFParser::parse_sql_into_expr(sql) {
2145 Ok(expr) => {
2146 panic!("Expected parse error for '{sql}', but was successful: {expr:#?}");
2147 }
2148 Err(e) => {
2149 let error_message = e.to_string();
2150 assert!(
2151 error_message.contains(expected_error),
2152 "Expected error '{expected_error}' not found in actual error '{error_message}'"
2153 );
2154 }
2155 }
2156 }
2157
2158 #[test]
2159 fn literal() {
2160 expect_parse_expr_ok(
2161 "1234",
2162 ExprWithAlias {
2163 expr: Expr::Value(ValueWithSpan::from(Value::Number(
2164 "1234".to_string(),
2165 false,
2166 ))),
2167 alias: None,
2168 },
2169 )
2170 }
2171
2172 #[test]
2173 fn literal_with_alias() {
2174 expect_parse_expr_ok(
2175 "1234 as foo",
2176 ExprWithAlias {
2177 expr: Expr::Value(ValueWithSpan::from(Value::Number(
2178 "1234".to_string(),
2179 false,
2180 ))),
2181 alias: Some(Ident::from("foo")),
2182 },
2183 )
2184 }
2185
2186 #[test]
2187 fn literal_with_alias_and_trailing_tokens() {
2188 expect_parse_expr_error(
2189 "1234 as foo.bar",
2190 "Expected: end of expression, found: .",
2191 )
2192 }
2193
2194 #[test]
2195 fn literal_with_alias_and_trailing_whitespace() {
2196 expect_parse_expr_ok(
2197 "1234 as foo ",
2198 ExprWithAlias {
2199 expr: Expr::Value(ValueWithSpan::from(Value::Number(
2200 "1234".to_string(),
2201 false,
2202 ))),
2203 alias: Some(Ident::from("foo")),
2204 },
2205 )
2206 }
2207
2208 #[test]
2209 fn literal_with_alias_and_trailing_whitespace_and_token() {
2210 expect_parse_expr_error(
2211 "1234 as foo bar",
2212 "Expected: end of expression, found: bar",
2213 )
2214 }
2215
2216 fn parse_with_pg(sql: &str) -> Result<Statement, DataFusionError> {
2221 let dialect = sqlparser::dialect::PostgreSqlDialect {};
2222 let mut statements = DFParser::parse_sql_with_dialect(sql, &dialect)?;
2223 assert_eq!(statements.len(), 1, "Expected exactly one statement");
2224 Ok(statements.pop_front().unwrap())
2225 }
2226
2227 fn parse_with_generic(sql: &str) -> Result<Statement, DataFusionError> {
2228 let mut statements = DFParser::parse_sql(sql)?;
2229 assert_eq!(statements.len(), 1, "Expected exactly one statement");
2230 Ok(statements.pop_front().unwrap())
2231 }
2232
2233 #[test]
2234 fn explain_legacy_keyword_form_postgres_dialect() {
2235 let stmt = parse_with_pg("EXPLAIN ANALYZE VERBOSE SELECT 1").unwrap();
2237 let Statement::Explain(ExplainStatement { options, .. }) = stmt else {
2238 panic!("Expected Statement::Explain");
2239 };
2240 assert!(options.analyze);
2241 assert!(options.verbose);
2242 assert!(options.format.is_none());
2243 assert!(options.analyze_level.is_none());
2244 }
2245
2246 #[test]
2247 fn explain_paren_form_on_generic_supports_utility_options() {
2248 let stmt = parse_with_generic("EXPLAIN (FORMAT TREE) SELECT 1").unwrap();
2252 let Statement::Explain(ExplainStatement { options, .. }) = stmt else {
2253 panic!("Expected Statement::Explain");
2254 };
2255 assert_eq!(options.format, Some(ExplainFormat::Tree));
2256 }
2257
2258 #[test]
2259 fn explain_paren_form_on_non_supporting_dialect_is_parse_error() {
2260 use sqlparser::dialect::SnowflakeDialect;
2264 let dialect = SnowflakeDialect {};
2265 let res =
2266 DFParser::parse_sql_with_dialect("EXPLAIN (FORMAT TREE) SELECT 1", &dialect);
2267 assert!(
2268 res.is_err(),
2269 "expected parse error under non-supporting dialect"
2270 );
2271 }
2272
2273 #[test]
2274 fn explain_paren_grouping_query_is_not_mistaken_for_options() {
2275 for sql in [
2280 "EXPLAIN (SELECT 1)",
2281 "EXPLAIN (WITH t AS (SELECT 1) SELECT * FROM t)",
2282 "EXPLAIN (VALUES (1), (2))",
2283 "EXPLAIN ((SELECT 1))",
2284 ] {
2285 let stmt = parse_with_pg(sql).unwrap_or_else(|e| {
2286 panic!("{sql} failed under PG dialect: {e}");
2287 });
2288 let Statement::Explain(ExplainStatement { options, .. }) = stmt else {
2289 panic!("Expected Statement::Explain for {sql}");
2290 };
2291 assert!(!options.analyze, "{sql} should not be ANALYZE");
2292 assert!(!options.verbose, "{sql} should not be VERBOSE");
2293 assert!(options.format.is_none(), "{sql} should have no FORMAT");
2294 }
2295 }
2296
2297 #[test]
2298 fn explain_paren_form_analyze_verbose() {
2299 let stmt = parse_with_pg("EXPLAIN (ANALYZE, VERBOSE) SELECT 1").unwrap();
2300 let Statement::Explain(ExplainStatement { options, .. }) = stmt else {
2301 panic!("Expected Statement::Explain");
2302 };
2303 assert!(options.analyze);
2304 assert!(options.verbose);
2305 }
2306
2307 #[test]
2308 fn explain_paren_form_format_tree() {
2309 let stmt = parse_with_pg("EXPLAIN (FORMAT tree) SELECT 1").unwrap();
2310 let Statement::Explain(ExplainStatement { options, .. }) = stmt else {
2311 panic!("Expected Statement::Explain");
2312 };
2313 assert!(!options.analyze);
2314 assert_eq!(options.format, Some(ExplainFormat::Tree));
2315 }
2316
2317 #[test]
2318 fn explain_paren_form_metrics_level() {
2319 use datafusion_common::format::{
2320 ExplainAnalyzeCategories, MetricCategory, MetricType,
2321 };
2322 let stmt =
2323 parse_with_pg("EXPLAIN (ANALYZE, METRICS 'rows,bytes', LEVEL dev) SELECT 1")
2324 .unwrap();
2325 let Statement::Explain(ExplainStatement { options, .. }) = stmt else {
2326 panic!("Expected Statement::Explain");
2327 };
2328 assert!(options.analyze);
2329 assert_eq!(options.analyze_level, Some(MetricType::Dev));
2330 assert_eq!(
2331 options.analyze_categories,
2332 Some(ExplainAnalyzeCategories::Only(vec![
2333 MetricCategory::Rows,
2334 MetricCategory::Bytes,
2335 ]))
2336 );
2337 }
2338
2339 #[test]
2340 fn explain_paren_form_bool_spellings() {
2341 let stmt =
2342 parse_with_pg("EXPLAIN (ANALYZE ON, VERBOSE OFF, COSTS TRUE) SELECT 1")
2343 .unwrap();
2344 let Statement::Explain(ExplainStatement { options, .. }) = stmt else {
2345 panic!("Expected Statement::Explain");
2346 };
2347 assert!(options.analyze);
2348 assert!(!options.verbose);
2349 assert_eq!(options.show_statistics, Some(true));
2350 }
2351
2352 #[test]
2353 fn explain_paren_form_buffers_rejected() {
2354 let err = parse_with_pg("EXPLAIN (BUFFERS) SELECT 1").unwrap_err();
2355 let msg = err.to_string();
2356 assert!(
2357 msg.contains("BUFFERS"),
2358 "error should mention BUFFERS: {msg}"
2359 );
2360 assert!(
2361 msg.contains("not supported"),
2362 "error should say not supported: {msg}"
2363 );
2364 }
2365
2366 #[test]
2367 fn explain_paren_form_unknown_option_rejected() {
2368 let err = parse_with_pg("EXPLAIN (ASDF) SELECT 1").unwrap_err();
2369 let msg = err.to_string();
2370 assert!(
2371 msg.contains("unknown EXPLAIN option"),
2372 "error should describe unknown option: {msg}"
2373 );
2374 }
2375}