diff --git a/src/ast/dml.rs b/src/ast/dml.rs index 3a76e7db2..349e50c52 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -32,8 +32,8 @@ use sqlparser_derive::{Visit, VisitMut}; pub use super::ddl::{ColumnDef, TableConstraint}; use super::{ - display_comma_separated, display_separated, Assignment, ClusteredBy, CommentDef, Expr, - FileFormat, FormatClause, FromTable, HiveDistributionStyle, HiveFormat, HiveIOFormat, + display_comma_separated, display_separated, query::InputFormatClause, Assignment, ClusteredBy, + CommentDef, Expr, FileFormat, FromTable, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query, RowAccessPolicy, SelectItem, Setting, SqlOption, SqliteOnConflict, TableEngine, TableWithJoins, Tag, WrappedCollection, @@ -510,7 +510,7 @@ pub struct Insert { /// ClickHouse syntax: `INSERT INTO tbl FORMAT JSONEachRow {"foo": 1, "bar": 2}, {"foo": 3}` /// /// [ClickHouse formats JSON insert](https://clickhouse.com/docs/en/interfaces/formats#json-inserting-data) - pub format_clause: Option, + pub format_clause: Option, } impl Display for Insert { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index f46438b3e..2c6597421 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -61,10 +61,10 @@ pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::query::{ AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode, ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml, - FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Interpolate, - InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn, - JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn, LateralView, - LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, + FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, + InputFormatClause, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, + JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn, + LateralView, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, diff --git a/src/ast/query.rs b/src/ast/query.rs index f4101c3d2..a3ac4488e 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2465,27 +2465,39 @@ impl fmt::Display for GroupByExpr { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum FormatClause { - Identifier { - ident: Ident, - expr: Option>, - }, + Identifier(Ident), Null, } impl fmt::Display for FormatClause { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - FormatClause::Identifier { ident, expr } => { - write!(f, "FORMAT {}", ident)?; + FormatClause::Identifier(ident) => write!(f, "FORMAT {}", ident), + FormatClause::Null => write!(f, "FORMAT NULL"), + } + } +} - if let Some(exprs) = expr { - write!(f, " {}", display_comma_separated(exprs))?; - } +/// FORMAT identifier in input context, specific to ClickHouse. +/// +/// [ClickHouse]: +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct InputFormatClause { + pub ident: Ident, + pub values: Vec, +} - Ok(()) - } - FormatClause::Null => write!(f, "FORMAT NULL"), +impl fmt::Display for InputFormatClause { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "FORMAT {}", self.ident)?; + + if !self.values.is_empty() { + write!(f, " {}", display_comma_separated(self.values.as_slice()))?; } + + Ok(()) } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 48f593747..b12638cf6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9589,7 +9589,12 @@ impl<'a> Parser<'a> { let format_clause = if dialect_of!(self is ClickHouseDialect | GenericDialect) && self.parse_keyword(Keyword::FORMAT) { - Some(self.parse_format_clause(false)?) + if self.parse_keyword(Keyword::NULL) { + Some(FormatClause::Null) + } else { + let ident = self.parse_identifier()?; + Some(FormatClause::Identifier(ident)) + } } else { None }; @@ -11934,7 +11939,7 @@ impl<'a> Parser<'a> { let settings = self.parse_settings()?; let format = if self.parse_keyword(Keyword::FORMAT) { - Some(self.parse_format_clause(true)?) + Some(self.parse_input_format_clause()?) } else { None }; @@ -12035,32 +12040,17 @@ impl<'a> Parser<'a> { } // Parses format clause used for [ClickHouse]. Formats are different when using `SELECT` and - // `INSERT` and also when using the CLI for pipes. It may or may not take an additional - // expression after the format so we try to parse the expression but allow failure. - // - // Since we know we never take an additional expression in `SELECT` context we never only try - // to parse if `can_have_expression` is true. + // `INSERT` and also when using the CLI for pipes. For `INSERT` it can take an optional values + // list which we try to parse here. // // - pub fn parse_format_clause( - &mut self, - can_have_expression: bool, - ) -> Result { - if self.parse_keyword(Keyword::NULL) { - Ok(FormatClause::Null) - } else { - let ident = self.parse_identifier()?; - let expr = if can_have_expression { - match self.try_parse(|p| p.parse_comma_separated(|p| p.parse_expr())) { - Ok(expr) => Some(expr), - _ => None, - } - } else { - None - }; + pub fn parse_input_format_clause(&mut self) -> Result { + let ident = self.parse_identifier()?; + let values = self + .try_parse(|p| p.parse_comma_separated(|p| p.parse_expr())) + .unwrap_or_default(); - Ok(FormatClause::Identifier { ident, expr }) - } + Ok(InputFormatClause { ident, values }) } /// Returns true if the immediate tokens look like the diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 5771d5e58..b06873c99 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1378,10 +1378,7 @@ fn test_query_with_format_clause() { } else { assert_eq!( query.format_clause, - Some(FormatClause::Identifier { - ident: Ident::new(*format), - expr: None - }) + Some(FormatClause::Identifier(Ident::new(*format))), ); } }