use std::fmt::{Debug, Display};
use api::v1;
use common_query::AddColumnLocation;
use datatypes::schema::FulltextOptions;
use itertools::Itertools;
use sqlparser::ast::{ColumnDef, DataType, Ident, ObjectName, TableConstraint};
use sqlparser_derive::{Visit, VisitMut};
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct AlterTable {
pub table_name: ObjectName,
pub alter_operation: AlterTableOperation,
}
impl AlterTable {
pub(crate) fn new(table_name: ObjectName, alter_operation: AlterTableOperation) -> Self {
Self {
table_name,
alter_operation,
}
}
pub fn table_name(&self) -> &ObjectName {
&self.table_name
}
pub fn alter_operation(&self) -> &AlterTableOperation {
&self.alter_operation
}
pub fn alter_operation_mut(&mut self) -> &mut AlterTableOperation {
&mut self.alter_operation
}
}
impl Display for AlterTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let table_name = self.table_name();
let alter_operation = self.alter_operation();
write!(f, r#"ALTER TABLE {table_name} {alter_operation}"#)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub enum AlterTableOperation {
AddConstraint(TableConstraint),
AddColumn {
column_def: ColumnDef,
location: Option<AddColumnLocation>,
},
ChangeColumnType {
column_name: Ident,
target_type: DataType,
},
ChangeTableOptions { options: Vec<ChangeTableOption> },
DropColumn { name: Ident },
RenameTable { new_table_name: String },
ChangeColumnFulltext {
column_name: Ident,
options: FulltextOptions,
},
}
impl Display for AlterTableOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AlterTableOperation::AddConstraint(constraint) => write!(f, r#"ADD {constraint}"#),
AlterTableOperation::AddColumn {
column_def,
location,
} => {
if let Some(location) = location {
write!(f, r#"ADD COLUMN {column_def} {location}"#)
} else {
write!(f, r#"ADD COLUMN {column_def}"#)
}
}
AlterTableOperation::DropColumn { name } => write!(f, r#"DROP COLUMN {name}"#),
AlterTableOperation::RenameTable { new_table_name } => {
write!(f, r#"RENAME {new_table_name}"#)
}
AlterTableOperation::ChangeColumnType {
column_name,
target_type,
} => {
write!(f, r#"MODIFY COLUMN {column_name} {target_type}"#)
}
AlterTableOperation::ChangeTableOptions { options } => {
let kvs = options
.iter()
.map(|ChangeTableOption { key, value }| {
if !value.is_empty() {
format!("'{key}'='{value}'")
} else {
format!("'{key}'=NULL")
}
})
.join(",");
write!(f, "SET {kvs}")?;
Ok(())
}
AlterTableOperation::ChangeColumnFulltext {
column_name,
options,
} => match options.enable {
true => {
write!(f, "MODIFY COLUMN {column_name} SET FULLTEXT WITH(analyzer={0}, case_sensitive={1})", options.analyzer, options.case_sensitive)?;
Ok(())
}
false => {
write!(f, "MODIFY COLUMN {column_name} UNSET FULLTEXT")?;
Ok(())
}
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
pub struct ChangeTableOption {
pub key: String,
pub value: String,
}
impl From<ChangeTableOption> for v1::ChangeTableOption {
fn from(c: ChangeTableOption) -> Self {
v1::ChangeTableOption {
key: c.key,
value: c.value,
}
}
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;
use crate::dialect::GreptimeDbDialect;
use crate::parser::{ParseOptions, ParserContext};
use crate::statements::statement::Statement;
#[test]
fn test_display_alter() {
let sql = r"alter table monitor add column app string default 'shop' primary key;";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::Alter { .. });
match &stmts[0] {
Statement::Alter(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER TABLE monitor ADD COLUMN app STRING DEFAULT 'shop' PRIMARY KEY"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
let sql = r"alter table monitor modify column load_15 string;";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::Alter { .. });
match &stmts[0] {
Statement::Alter(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER TABLE monitor MODIFY COLUMN load_15 STRING"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
let sql = r"alter table monitor drop column load_15;";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::Alter { .. });
match &stmts[0] {
Statement::Alter(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER TABLE monitor DROP COLUMN load_15"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
let sql = r"alter table monitor rename monitor_new;";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::Alter { .. });
match &stmts[0] {
Statement::Alter(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER TABLE monitor RENAME monitor_new"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
let sql = "ALTER TABLE monitor MODIFY COLUMN a SET FULLTEXT WITH(analyzer='English',case_sensitive='false')";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::Alter { .. });
match &stmts[0] {
Statement::Alter(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER TABLE monitor MODIFY COLUMN a SET FULLTEXT WITH(analyzer=English, case_sensitive=false)"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
}
}