diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 2d1778c7b..b5444b8da 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -57,6 +57,33 @@ pub enum AlterTableOperation { name: Ident, select: ProjectionSelect, }, + + /// `DROP PROJECTION [IF EXISTS] name` + /// + /// Note: this is a ClickHouse-specific operation. + /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#drop-projection) + DropProjection { if_exists: bool, name: Ident }, + + /// `MATERIALIZE PROJECTION [IF EXISTS] name [IN PARTITION partition_name]` + /// + /// Note: this is a ClickHouse-specific operation. + /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#materialize-projection) + MaterializeProjection { + if_exists: bool, + name: Ident, + partition: Option, + }, + + /// `CLEAR PROJECTION [IF EXISTS] name [IN PARTITION partition_name]` + /// + /// Note: this is a ClickHouse-specific operation. + /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#clear-projection) + ClearProjection { + if_exists: bool, + name: Ident, + partition: Option, + }, + /// `DISABLE ROW LEVEL SECURITY` /// /// Note: this is a PostgreSQL-specific operation. @@ -275,6 +302,43 @@ impl fmt::Display for AlterTableOperation { } write!(f, " {} ({})", name, query) } + AlterTableOperation::DropProjection { if_exists, name } => { + write!(f, "DROP PROJECTION")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", name) + } + AlterTableOperation::MaterializeProjection { + if_exists, + name, + partition, + } => { + write!(f, "MATERIALIZE PROJECTION")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", name)?; + if let Some(partition) = partition { + write!(f, " IN PARTITION {}", partition)?; + } + Ok(()) + } + AlterTableOperation::ClearProjection { + if_exists, + name, + partition, + } => { + write!(f, "CLEAR PROJECTION")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", name)?; + if let Some(partition) = partition { + write!(f, " IN PARTITION {}", partition)?; + } + Ok(()) + } AlterTableOperation::AlterColumn { column_name, op } => { write!(f, "ALTER COLUMN {column_name} {op}") } diff --git a/src/keywords.rs b/src/keywords.rs index ae0f14f18..3ee447cfe 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -153,6 +153,7 @@ define_keywords!( CHARSET, CHAR_LENGTH, CHECK, + CLEAR, CLOB, CLONE, CLOSE, @@ -450,6 +451,7 @@ define_keywords!( MATCHES, MATCH_CONDITION, MATCH_RECOGNIZE, + MATERIALIZE, MATERIALIZED, MAX, MAXVALUE, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 413f5a690..70454e119 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6615,6 +6615,36 @@ impl<'a> Parser<'a> { self.peek_token(), ); } + } else if self.parse_keywords(&[Keyword::CLEAR, Keyword::PROJECTION]) + && dialect_of!(self is ClickHouseDialect|GenericDialect) + { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let name = self.parse_identifier(false)?; + let partition = if self.parse_keywords(&[Keyword::IN, Keyword::PARTITION]) { + Some(self.parse_identifier(false)?) + } else { + None + }; + AlterTableOperation::ClearProjection { + if_exists, + name, + partition, + } + } else if self.parse_keywords(&[Keyword::MATERIALIZE, Keyword::PROJECTION]) + && dialect_of!(self is ClickHouseDialect|GenericDialect) + { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let name = self.parse_identifier(false)?; + let partition = if self.parse_keywords(&[Keyword::IN, Keyword::PARTITION]) { + Some(self.parse_identifier(false)?) + } else { + None + }; + AlterTableOperation::MaterializeProjection { + if_exists, + name, + partition, + } } else if self.parse_keyword(Keyword::DROP) { if self.parse_keywords(&[Keyword::IF, Keyword::EXISTS, Keyword::PARTITION]) { self.expect_token(&Token::LParen)?; @@ -6645,6 +6675,12 @@ impl<'a> Parser<'a> { && dialect_of!(self is MySqlDialect | GenericDialect) { AlterTableOperation::DropPrimaryKey + } else if self.parse_keyword(Keyword::PROJECTION) + && dialect_of!(self is ClickHouseDialect|GenericDialect) + { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let name = self.parse_identifier(false)?; + AlterTableOperation::DropProjection { if_exists, name } } else { let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ] let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index c8157bced..f89da7ee9 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -359,6 +359,102 @@ fn parse_alter_table_add_projection() { ); } +#[test] +fn parse_alter_table_drop_projection() { + match clickhouse_and_generic().verified_stmt("ALTER TABLE t0 DROP PROJECTION IF EXISTS my_name") + { + Statement::AlterTable { + name, operations, .. + } => { + assert_eq!(name, ObjectName(vec!["t0".into()])); + assert_eq!(1, operations.len()); + assert_eq!( + operations[0], + AlterTableOperation::DropProjection { + if_exists: true, + name: "my_name".into(), + } + ) + } + _ => unreachable!(), + } + // allow to skip `IF EXISTS` + clickhouse_and_generic().verified_stmt("ALTER TABLE t0 DROP PROJECTION my_name"); + + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements("ALTER TABLE t0 DROP PROJECTION") + .unwrap_err(), + ParserError("Expected: identifier, found: EOF".to_string()) + ); +} + +#[test] +fn parse_alter_table_clear_and_materialize_projection() { + for keyword in ["CLEAR", "MATERIALIZE"] { + match clickhouse_and_generic().verified_stmt( + format!("ALTER TABLE t0 {keyword} PROJECTION IF EXISTS my_name IN PARTITION p0",) + .as_str(), + ) { + Statement::AlterTable { + name, operations, .. + } => { + assert_eq!(name, ObjectName(vec!["t0".into()])); + assert_eq!(1, operations.len()); + assert_eq!( + operations[0], + if keyword == "CLEAR" { + AlterTableOperation::ClearProjection { + if_exists: true, + name: "my_name".into(), + partition: Some(Ident::new("p0")), + } + } else { + AlterTableOperation::MaterializeProjection { + if_exists: true, + name: "my_name".into(), + partition: Some(Ident::new("p0")), + } + } + ) + } + _ => unreachable!(), + } + // allow to skip `IF EXISTS` + clickhouse_and_generic().verified_stmt( + format!("ALTER TABLE t0 {keyword} PROJECTION my_name IN PARTITION p0",).as_str(), + ); + // allow to skip `IN PARTITION partition_name` + clickhouse_and_generic() + .verified_stmt(format!("ALTER TABLE t0 {keyword} PROJECTION my_name",).as_str()); + + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements(format!("ALTER TABLE t0 {keyword} PROJECTION",).as_str()) + .unwrap_err(), + ParserError("Expected: identifier, found: EOF".to_string()) + ); + + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements( + format!("ALTER TABLE t0 {keyword} PROJECTION my_name IN PARTITION",).as_str() + ) + .unwrap_err(), + ParserError("Expected: identifier, found: EOF".to_string()) + ); + + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements( + format!("ALTER TABLE t0 {keyword} PROJECTION my_name IN",).as_str() + ) + .unwrap_err(), + ParserError("Expected: end of statement, found: IN".to_string()) + ); + } +} + #[test] fn parse_optimize_table() { clickhouse_and_generic().verified_stmt("OPTIMIZE TABLE t0");