diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 9c48e3aea..689e27b49 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1098,6 +1098,13 @@ impl fmt::Display for ColumnOptionDef { } } +/// Identity is a column option for defining an identity or autoincrement column in a creating table statement. +/// Syntax +/// ```sql +/// { IDENTITY | AUTOINCREMENT } [ (seed , increment) | START num INCREMENT num ] [ ORDER | NOORDER ] +/// ``` +/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property +/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -1114,6 +1121,20 @@ pub struct IdentityProperty { pub order: Option, } +/// A format of parameters of identity column. +/// +/// It is [Snowflake] specific. +/// Syntax +/// ```sql +/// (seed , increment) | START num INCREMENT num +/// ``` +/// [MS SQL Server] uses one way of representing these parameters. +/// Syntax +/// ```sql +/// (seed , increment) +/// ``` +/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property +/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -1135,7 +1156,7 @@ pub struct IdentityParameters { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum IdentityOrder { Order, - Noorder, + NoOrder, } impl fmt::Display for Identity { @@ -1176,11 +1197,18 @@ impl fmt::Display for IdentityOrder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { IdentityOrder::Order => write!(f, " ORDER"), - IdentityOrder::Noorder => write!(f, " NOORDER"), + IdentityOrder::NoOrder => write!(f, " NOORDER"), } } } +/// Column policy that identify a security policy of access to a column. +/// Syntax +/// ```sql +/// [ WITH ] MASKING POLICY [ USING ( , , ... ) ] +/// [ WITH ] PROJECTION POLICY +/// ``` +/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -1193,6 +1221,7 @@ pub enum ColumnPolicy { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct ColumnPolicyProperty { + pub with: bool, pub policy_name: Ident, pub using_columns: Option>, } @@ -1203,10 +1232,37 @@ impl fmt::Display for ColumnPolicy { ColumnPolicy::MaskingPolicy(property) => ("MASKING POLICY", property), ColumnPolicy::ProjectionPolicy(property) => ("PROJECTION POLICY", property), }; - write!(f, "WITH {command} {}", property.policy_name)?; + if property.with { + write!(f, "WITH ")?; + } + write!(f, "{command} {}", property.policy_name)?; if let Some(using_columns) = &property.using_columns { - write!(f, "USING ({})", display_comma_separated(using_columns))?; + write!(f, " USING ({})", display_comma_separated(using_columns))?; + } + Ok(()) + } +} + +/// Tags option of column +/// Syntax +/// ```sql +/// [ WITH ] TAG ( = '' [ , = '' , ... ] ) +/// ``` +/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct TagsColumnOption { + pub with: bool, + pub tags: Vec, +} + +impl fmt::Display for TagsColumnOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.with { + write!(f, "WITH ")?; } + write!(f, "TAG ({})", display_comma_separated(&self.tags))?; Ok(()) } } @@ -1303,7 +1359,7 @@ pub enum ColumnOption { /// [ WITH ] TAG ( = '' [ , = '' , ... ] ) /// ``` /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table - Tags(Vec), + Tags(TagsColumnOption), } impl fmt::Display for ColumnOption { @@ -1412,7 +1468,7 @@ impl fmt::Display for ColumnOption { write!(f, "{parameters}") } Tags(tags) => { - write!(f, "WITH TAG ({})", display_comma_separated(tags)) + write!(f, "{tags}") } } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b96d11b8d..fd97ea0eb 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -44,7 +44,7 @@ pub use self::ddl::{ ConstraintCharacteristics, Deduplicate, DeferrableInitial, GeneratedAs, GeneratedExpressionMode, Identity, IdentityFormat, IdentityOrder, IdentityParameters, IdentityProperty, IndexOption, IndexType, KeyOrIndexDisplay, Owner, Partition, ProcedureParam, - ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef, + ReferentialAction, TableConstraint, TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, }; pub use self::dml::{CreateIndex, CreateTable, Delete, Insert}; diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 29ad0b98a..2a61c3194 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -49,7 +49,7 @@ pub use self::postgresql::PostgreSqlDialect; pub use self::redshift::RedshiftSqlDialect; pub use self::snowflake::SnowflakeDialect; pub use self::sqlite::SQLiteDialect; -use crate::ast::{Expr, Statement}; +use crate::ast::{ColumnOption, Expr, Statement}; pub use crate::keywords; use crate::keywords::Keyword; use crate::parser::{Parser, ParserError}; @@ -478,6 +478,19 @@ pub trait Dialect: Debug + Any { None } + /// Dialect-specific column option parser override + /// + /// This method is called to parse the next column option. + /// + /// If `None` is returned, falls back to the default behavior. + fn parse_column_option( + &self, + _parser: &mut Parser, + ) -> Option, ParserError>> { + // return None to fall back to the default behavior + None + } + /// Decide the lexical Precedence of operators. /// /// Uses (APPROXIMATELY) as a reference diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 6fcbfe09b..1424b9844 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -22,7 +22,11 @@ use crate::ast::helpers::stmt_data_loading::{ DataLoadingOption, DataLoadingOptionType, DataLoadingOptions, StageLoadSelectItem, StageParamsObject, }; -use crate::ast::{CommentDef, Ident, ObjectName, RowAccessPolicy, Statement, WrappedCollection}; +use crate::ast::{ + ColumnOption, ColumnPolicy, ColumnPolicyProperty, CommentDef, Ident, Identity, IdentityFormat, + IdentityOrder, IdentityParameters, IdentityProperty, ObjectName, RowAccessPolicy, Statement, + WrappedCollection, +}; use crate::dialect::{Dialect, Precedence}; use crate::keywords::Keyword; use crate::parser::{Parser, ParserError}; @@ -149,6 +153,41 @@ impl Dialect for SnowflakeDialect { None } + fn parse_column_option( + &self, + parser: &mut Parser, + ) -> Option, ParserError>> { + let with = parser.parse_keyword(Keyword::WITH); + + if parser.parse_keyword(Keyword::IDENTITY) { + Some( + parse_identity_property(parser) + .map(|p| Some(ColumnOption::Identity(Identity::Identity(p)))), + ) + } else if parser.parse_keyword(Keyword::AUTOINCREMENT) { + Some( + parse_identity_property(parser) + .map(|p| Some(ColumnOption::Identity(Identity::Autoincrement(p)))), + ) + } else if parser.parse_keywords(&[Keyword::MASKING, Keyword::POLICY]) { + Some( + parse_column_policy_property(parser, with) + .map(|p| Some(ColumnOption::Policy(ColumnPolicy::MaskingPolicy(p)))), + ) + } else if parser.parse_keywords(&[Keyword::PROJECTION, Keyword::POLICY]) { + Some( + parse_column_policy_property(parser, with) + .map(|p| Some(ColumnOption::Policy(ColumnPolicy::ProjectionPolicy(p)))), + ) + } else { + // needs to revert initial state of parser if dialect finds any matching + if with { + parser.prev_token(); + } + None + } + } + fn get_next_precedence(&self, parser: &Parser) -> Option> { let token = parser.peek_token(); // Snowflake supports the `:` cast operator unlike other dialects @@ -772,3 +811,67 @@ fn parse_parentheses_options(parser: &mut Parser) -> Result Result { + let parameters = if parser.consume_token(&Token::LParen) { + let seed = parser.parse_number()?; + parser.expect_token(&Token::Comma)?; + let increment = parser.parse_number()?; + parser.expect_token(&Token::RParen)?; + + Some(IdentityFormat::FunctionCall(IdentityParameters { + seed, + increment, + })) + } else if parser.parse_keyword(Keyword::START) { + let seed = parser.parse_number()?; + parser.expect_keyword(Keyword::INCREMENT)?; + let increment = parser.parse_number()?; + + Some(IdentityFormat::StartAndIncrement(IdentityParameters { + seed, + increment, + })) + } else { + None + }; + let order = match parser.parse_one_of_keywords(&[Keyword::ORDER, Keyword::NOORDER]) { + Some(Keyword::ORDER) => Some(IdentityOrder::Order), + Some(Keyword::NOORDER) => Some(IdentityOrder::NoOrder), + _ => None, + }; + Ok(IdentityProperty { parameters, order }) +} + +/// Parsing a policy property of column option +/// Syntax: +/// ```sql +/// [ USING ( , , ... ) +/// ``` +/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table +fn parse_column_policy_property( + parser: &mut Parser, + with: bool, +) -> Result { + let policy_name = parser.parse_identifier(false)?; + let using_columns = if parser.parse_keyword(Keyword::USING) { + parser.expect_token(&Token::LParen)?; + let columns = parser.parse_comma_separated(|p| p.parse_identifier(false))?; + parser.expect_token(&Token::RParen)?; + Some(columns) + } else { + None + }; + + Ok(ColumnPolicyProperty { + with, + policy_name, + using_columns, + }) +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1c74c3e85..7abdbc4bf 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6083,6 +6083,12 @@ impl<'a> Parser<'a> { } pub fn parse_optional_column_option(&mut self) -> Result, ParserError> { + if let Some(option) = self.dialect.parse_column_option(self) { + return option; + } + + let with = self.parse_keyword(Keyword::WITH); + if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) { Ok(Some(ColumnOption::CharacterSet( self.parse_object_name(false)?, @@ -6168,6 +6174,13 @@ impl<'a> Parser<'a> { Ok(Some(ColumnOption::DialectSpecific(vec![ Token::make_keyword("AUTO_INCREMENT"), ]))) + } else if self.parse_keyword(Keyword::AUTOINCREMENT) + && dialect_of!(self is SQLiteDialect | GenericDialect) + { + // Support AUTOINCREMENT for SQLite + Ok(Some(ColumnOption::DialectSpecific(vec![ + Token::make_keyword("AUTOINCREMENT"), + ]))) } else if self.parse_keywords(&[Keyword::ON, Keyword::UPDATE]) && dialect_of!(self is MySqlDialect | GenericDialect) { @@ -6186,26 +6199,9 @@ impl<'a> Parser<'a> { && dialect_of!(self is MySqlDialect | SQLiteDialect | DuckDbDialect | GenericDialect) { self.parse_optional_column_option_as() - } else if self.parse_keyword(Keyword::AUTOINCREMENT) - && dialect_of!(self is SnowflakeDialect | SQLiteDialect | GenericDialect) - { - if dialect_of!(self is SnowflakeDialect) { - self.prev_token(); - return self.parse_snowflake_autoincrement_or_identity_option_column(); - } - - // Support AUTOINCREMENT for SQLite - Ok(Some(ColumnOption::DialectSpecific(vec![ - Token::make_keyword("AUTOINCREMENT"), - ]))) } else if self.parse_keyword(Keyword::IDENTITY) - && dialect_of!(self is MsSqlDialect | SnowflakeDialect | GenericDialect) + && dialect_of!(self is MsSqlDialect | GenericDialect) { - if dialect_of!(self is SnowflakeDialect) { - self.prev_token(); - return self.parse_snowflake_autoincrement_or_identity_option_column(); - } - let parameters = if self.consume_token(&Token::LParen) { let seed = self.parse_number()?; self.expect_token(&Token::Comma)?; @@ -6225,20 +6221,6 @@ impl<'a> Parser<'a> { order: None, }, )))) - } else if ((self.parse_keyword(Keyword::WITH) - && self - .parse_one_of_keywords(&[Keyword::MASKING, Keyword::PROJECTION]) - .is_some()) - || self - .parse_one_of_keywords(&[Keyword::MASKING, Keyword::PROJECTION]) - .is_some()) - && dialect_of!(self is SnowflakeDialect | GenericDialect) - { - self.prev_token(); - let Some(policy) = self.parse_snowflake_column_policy()? else { - return Ok(None); - }; - Ok(Some(ColumnOption::Policy(policy))) } else if self.parse_keywords(&[Keyword::TAG]) && dialect_of!(self is SnowflakeDialect | GenericDialect) { @@ -6246,92 +6228,15 @@ impl<'a> Parser<'a> { let tags = self.parse_comma_separated(Self::parse_tag)?; self.expect_token(&Token::RParen)?; - Ok(Some(ColumnOption::Tags(tags))) - } else { - Ok(None) - } - } - - fn parse_snowflake_autoincrement_or_identity_option_column( - &mut self, - ) -> Result, ParserError> { - let token_location = self.peek_token(); - let Some(keyword) = - self.parse_one_of_keywords(&[Keyword::IDENTITY, Keyword::AUTOINCREMENT]) - else { - return self.expected("IDENTITY or AUTOINCREMENT", token_location.clone()); - }; - let parameters = if self.consume_token(&Token::LParen) { - let seed = self.parse_number()?; - self.expect_token(&Token::Comma)?; - let increment = self.parse_number()?; - self.expect_token(&Token::RParen)?; - - Some(IdentityFormat::FunctionCall(IdentityParameters { - seed, - increment, - })) - } else if self.parse_keyword(Keyword::START) { - let seed = self.parse_number()?; - self.expect_keyword(Keyword::INCREMENT)?; - let increment = self.parse_number()?; - - Some(IdentityFormat::StartAndIncrement(IdentityParameters { - seed, - increment, - })) - } else { - None - }; - let order = match self.parse_one_of_keywords(&[Keyword::ORDER, Keyword::NOORDER]) { - Some(Keyword::ORDER) => Some(IdentityOrder::Order), - Some(Keyword::NOORDER) => Some(IdentityOrder::Noorder), - _ => None, - }; - let property = IdentityProperty { parameters, order }; - let identity = match keyword { - Keyword::IDENTITY => Identity::Identity(property), - Keyword::AUTOINCREMENT => Identity::Autoincrement(property), - _ => self.expected("IDENTITY or AUTOINCREMENT", token_location)?, - }; - Ok(Some(ColumnOption::Identity(identity))) - } - - fn parse_snowflake_column_policy(&mut self) -> Result, ParserError> { - if self.parse_keywords(&[Keyword::MASKING, Keyword::POLICY]) - && dialect_of!(self is SnowflakeDialect | GenericDialect) - { - let property = self.parse_snowflake_column_policy_property()?; - Ok(Some(ColumnPolicy::MaskingPolicy(property))) - } else if self.parse_keywords(&[Keyword::PROJECTION, Keyword::POLICY]) - && dialect_of!(self is SnowflakeDialect | GenericDialect) - { - let property = self.parse_snowflake_column_policy_property()?; - Ok(Some(ColumnPolicy::ProjectionPolicy(property))) + Ok(Some(ColumnOption::Tags(TagsColumnOption { with, tags }))) } else { + if with { + self.prev_token(); + } Ok(None) } } - fn parse_snowflake_column_policy_property( - &mut self, - ) -> Result { - let policy_name = self.parse_identifier(false)?; - let using_columns = if self.parse_keyword(Keyword::USING) { - self.expect_token(&Token::LParen)?; - let columns = self.parse_comma_separated(|p| p.parse_identifier(false))?; - self.expect_token(&Token::RParen)?; - Some(columns) - } else { - None - }; - - Ok(ColumnPolicyProperty { - policy_name, - using_columns, - }) - } - pub fn parse_tag(&mut self) -> Result { let name = self.parse_identifier(false)?; self.expect_token(&Token::Eq)?; diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 727e7d100..768ca3da0 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -589,7 +589,7 @@ fn test_snowflake_create_table_with_autoincrement_columns() { )), } )), - order: Some(IdentityOrder::Noorder), + order: Some(IdentityOrder::NoOrder), } )) }] @@ -667,162 +667,124 @@ fn test_snowflake_create_table_with_collated_column() { #[test] fn test_snowflake_create_table_with_columns_masking_policy() { - match snowflake_and_generic() - .verified_stmt("CREATE TABLE my_table (a INT WITH MASKING POLICY p)") - { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "a".into(), - data_type: DataType::Int(None), - collation: None, - options: vec![ColumnOptionDef { - name: None, - option: ColumnOption::Policy(ColumnPolicy::MaskingPolicy( - ColumnPolicyProperty { - policy_name: "p".into(), - using_columns: None, - } - )) - }], - },] - ); - } - _ => unreachable!(), - } - match snowflake_and_generic() - .parse_sql_statements("CREATE TABLE my_table (a INT MASKING POLICY p USING (a, b))") - .unwrap() - .pop() - .unwrap() - { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "a".into(), - data_type: DataType::Int(None), - collation: None, - options: vec![ColumnOptionDef { - name: None, - option: ColumnOption::Policy(ColumnPolicy::MaskingPolicy( - ColumnPolicyProperty { - policy_name: "p".into(), - using_columns: Some(vec!["a".into(), "b".into()]), - } - )) - }], - },] - ); + for (sql, with, using_columns) in [ + ( + "CREATE TABLE my_table (a INT WITH MASKING POLICY p)", + true, + None, + ), + ( + "CREATE TABLE my_table (a INT MASKING POLICY p)", + false, + None, + ), + ( + "CREATE TABLE my_table (a INT WITH MASKING POLICY p USING (a, b))", + true, + Some(vec!["a".into(), "b".into()]), + ), + ( + "CREATE TABLE my_table (a INT MASKING POLICY p USING (a, b))", + false, + Some(vec!["a".into(), "b".into()]), + ), + ] { + match snowflake().verified_stmt(sql) { + Statement::CreateTable(CreateTable { columns, .. }) => { + assert_eq!( + columns, + vec![ColumnDef { + name: "a".into(), + data_type: DataType::Int(None), + collation: None, + options: vec![ColumnOptionDef { + name: None, + option: ColumnOption::Policy(ColumnPolicy::MaskingPolicy( + ColumnPolicyProperty { + with, + policy_name: "p".into(), + using_columns, + } + )) + }], + },] + ); + } + _ => unreachable!(), } - _ => unreachable!(), } } #[test] fn test_snowflake_create_table_with_columns_projection_policy() { - match snowflake_and_generic() - .verified_stmt("CREATE TABLE my_table (a INT WITH PROJECTION POLICY p)") - { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "a".into(), - data_type: DataType::Int(None), - collation: None, - options: vec![ColumnOptionDef { - name: None, - option: ColumnOption::Policy(ColumnPolicy::ProjectionPolicy( - ColumnPolicyProperty { - policy_name: "p".into(), - using_columns: None, - } - )) - }], - },] - ); - } - _ => unreachable!(), - } - match snowflake_and_generic() - .parse_sql_statements("CREATE TABLE my_table (a INT PROJECTION POLICY p)") - .unwrap() - .pop() - .unwrap() - { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "a".into(), - data_type: DataType::Int(None), - collation: None, - options: vec![ColumnOptionDef { - name: None, - option: ColumnOption::Policy(ColumnPolicy::ProjectionPolicy( - ColumnPolicyProperty { - policy_name: "p".into(), - using_columns: None, - } - )) - }], - },] - ); + for (sql, with) in [ + ( + "CREATE TABLE my_table (a INT WITH PROJECTION POLICY p)", + true, + ), + ("CREATE TABLE my_table (a INT PROJECTION POLICY p)", false), + ] { + match snowflake().verified_stmt(sql) { + Statement::CreateTable(CreateTable { columns, .. }) => { + assert_eq!( + columns, + vec![ColumnDef { + name: "a".into(), + data_type: DataType::Int(None), + collation: None, + options: vec![ColumnOptionDef { + name: None, + option: ColumnOption::Policy(ColumnPolicy::ProjectionPolicy( + ColumnPolicyProperty { + with, + policy_name: "p".into(), + using_columns: None, + } + )) + }], + },] + ); + } + _ => unreachable!(), } - _ => unreachable!(), } } #[test] fn test_snowflake_create_table_with_columns_tags() { - match snowflake_and_generic() - .verified_stmt("CREATE TABLE my_table (a INT WITH TAG (A='TAG A', B='TAG B'))") - { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "a".into(), - data_type: DataType::Int(None), - collation: None, - options: vec![ColumnOptionDef { - name: None, - option: ColumnOption::Tags(vec![ - Tag::new("A".into(), "TAG A".into()), - Tag::new("B".into(), "TAG B".into()), - ]), - }], - },] - ); - } - _ => unreachable!(), - } - match snowflake_and_generic() - .parse_sql_statements("CREATE TABLE my_table (a INT TAG (A='TAG A', B='TAG B'))") - .unwrap() - .pop() - .unwrap() - { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "a".into(), - data_type: DataType::Int(None), - collation: None, - options: vec![ColumnOptionDef { - name: None, - option: ColumnOption::Tags(vec![ - Tag::new("A".into(), "TAG A".into()), - Tag::new("B".into(), "TAG B".into()), - ]), - }], - },] - ); + for (sql, with) in [ + ( + "CREATE TABLE my_table (a INT WITH TAG (A='TAG A', B='TAG B'))", + true, + ), + ( + "CREATE TABLE my_table (a INT TAG (A='TAG A', B='TAG B'))", + false, + ), + ] { + match snowflake_and_generic().verified_stmt(sql) { + Statement::CreateTable(CreateTable { columns, .. }) => { + assert_eq!( + columns, + vec![ColumnDef { + name: "a".into(), + data_type: DataType::Int(None), + collation: None, + options: vec![ColumnOptionDef { + name: None, + option: ColumnOption::Tags(TagsColumnOption { + with, + tags: vec![ + Tag::new("A".into(), "TAG A".into()), + Tag::new("B".into(), "TAG B".into()), + ] + }), + }], + },] + ); + } + _ => unreachable!(), } - _ => unreachable!(), } }