Add support of DROP|CLEAR|MATERIALIZE PROJECTION syntax for ClickHouse (#1417)

This commit is contained in:
hulk 2024-09-11 03:26:07 +08:00 committed by GitHub
parent 4875dadbf5
commit a7b49b5072
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 198 additions and 0 deletions

View file

@ -57,6 +57,33 @@ pub enum AlterTableOperation {
name: Ident, name: Ident,
select: ProjectionSelect, 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<Ident>,
},
/// `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<Ident>,
},
/// `DISABLE ROW LEVEL SECURITY` /// `DISABLE ROW LEVEL SECURITY`
/// ///
/// Note: this is a PostgreSQL-specific operation. /// Note: this is a PostgreSQL-specific operation.
@ -275,6 +302,43 @@ impl fmt::Display for AlterTableOperation {
} }
write!(f, " {} ({})", name, query) 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 } => { AlterTableOperation::AlterColumn { column_name, op } => {
write!(f, "ALTER COLUMN {column_name} {op}") write!(f, "ALTER COLUMN {column_name} {op}")
} }

View file

@ -153,6 +153,7 @@ define_keywords!(
CHARSET, CHARSET,
CHAR_LENGTH, CHAR_LENGTH,
CHECK, CHECK,
CLEAR,
CLOB, CLOB,
CLONE, CLONE,
CLOSE, CLOSE,
@ -450,6 +451,7 @@ define_keywords!(
MATCHES, MATCHES,
MATCH_CONDITION, MATCH_CONDITION,
MATCH_RECOGNIZE, MATCH_RECOGNIZE,
MATERIALIZE,
MATERIALIZED, MATERIALIZED,
MAX, MAX,
MAXVALUE, MAXVALUE,

View file

@ -6615,6 +6615,36 @@ impl<'a> Parser<'a> {
self.peek_token(), 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) { } else if self.parse_keyword(Keyword::DROP) {
if self.parse_keywords(&[Keyword::IF, Keyword::EXISTS, Keyword::PARTITION]) { if self.parse_keywords(&[Keyword::IF, Keyword::EXISTS, Keyword::PARTITION]) {
self.expect_token(&Token::LParen)?; self.expect_token(&Token::LParen)?;
@ -6645,6 +6675,12 @@ impl<'a> Parser<'a> {
&& dialect_of!(self is MySqlDialect | GenericDialect) && dialect_of!(self is MySqlDialect | GenericDialect)
{ {
AlterTableOperation::DropPrimaryKey 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 { } else {
let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ] let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ]
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);

View file

@ -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] #[test]
fn parse_optimize_table() { fn parse_optimize_table() {
clickhouse_and_generic().verified_stmt("OPTIMIZE TABLE t0"); clickhouse_and_generic().verified_stmt("OPTIMIZE TABLE t0");