use std::fmt::{Debug, Display};
use api::v1;
use common_query::AddColumnLocation;
use datatypes::schema::FulltextOptions;
use itertools::Itertools;
use serde::Serialize;
use sqlparser::ast::{ColumnDef, DataType, Ident, ObjectName, TableConstraint};
use sqlparser_derive::{Visit, VisitMut};
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
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, Serialize)]
pub enum AlterTableOperation {
AddConstraint(TableConstraint),
AddColumn {
column_def: ColumnDef,
location: Option<AddColumnLocation>,
},
ModifyColumnType {
column_name: Ident,
target_type: DataType,
},
SetTableOptions { options: Vec<KeyValueOption> },
UnsetTableOptions { keys: Vec<String> },
DropColumn { name: Ident },
RenameTable { new_table_name: String },
SetColumnFulltext {
column_name: Ident,
options: FulltextOptions,
},
UnsetColumnFulltext { column_name: Ident },
}
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::ModifyColumnType {
column_name,
target_type,
} => {
write!(f, r#"MODIFY COLUMN {column_name} {target_type}"#)
}
AlterTableOperation::SetTableOptions { options } => {
let kvs = options
.iter()
.map(|KeyValueOption { key, value }| {
if !value.is_empty() {
format!("'{key}'='{value}'")
} else {
format!("'{key}'=NULL")
}
})
.join(",");
write!(f, "SET {kvs}")
}
AlterTableOperation::UnsetTableOptions { keys } => {
let keys = keys.iter().map(|k| format!("'{k}'")).join(",");
write!(f, "UNSET {keys}")
}
AlterTableOperation::SetColumnFulltext {
column_name,
options,
} => {
write!(f, "MODIFY COLUMN {column_name} SET FULLTEXT WITH(analyzer={0}, case_sensitive={1})", options.analyzer, options.case_sensitive)
}
AlterTableOperation::UnsetColumnFulltext { column_name } => {
write!(f, "MODIFY COLUMN {column_name} UNSET FULLTEXT")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
pub struct KeyValueOption {
pub key: String,
pub value: String,
}
impl From<KeyValueOption> for v1::Option {
fn from(c: KeyValueOption) -> Self {
v1::Option {
key: c.key,
value: c.value,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
pub struct AlterDatabase {
pub database_name: ObjectName,
pub alter_operation: AlterDatabaseOperation,
}
impl AlterDatabase {
pub(crate) fn new(database_name: ObjectName, alter_operation: AlterDatabaseOperation) -> Self {
Self {
database_name,
alter_operation,
}
}
pub fn database_name(&self) -> &ObjectName {
&self.database_name
}
pub fn alter_operation(&self) -> &AlterDatabaseOperation {
&self.alter_operation
}
}
impl Display for AlterDatabase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let database_name = self.database_name();
let alter_operation = self.alter_operation();
write!(f, r#"ALTER DATABASE {database_name} {alter_operation}"#)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
pub enum AlterDatabaseOperation {
SetDatabaseOption { options: Vec<KeyValueOption> },
UnsetDatabaseOption { keys: Vec<String> },
}
impl Display for AlterDatabaseOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AlterDatabaseOperation::SetDatabaseOption { options } => {
let kvs = options
.iter()
.map(|KeyValueOption { key, value }| {
if !value.is_empty() {
format!("'{key}'='{value}'")
} else {
format!("'{key}'=NULL")
}
})
.join(",");
write!(f, "SET {kvs}")?;
Ok(())
}
AlterDatabaseOperation::UnsetDatabaseOption { keys } => {
let keys = keys.iter().map(|key| format!("'{key}'")).join(",");
write!(f, "UNSET {keys}")?;
Ok(())
}
}
}
}
#[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 DATABASE db SET 'a' = 'b', 'c' = 'd'";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::AlterDatabase { .. });
match &stmts[0] {
Statement::AlterDatabase(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER DATABASE db SET 'a'='b','c'='d'"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
let sql = r"ALTER DATABASE db UNSET 'a', 'c'";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
match &stmts[0] {
Statement::AlterDatabase(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER DATABASE db UNSET 'a','c'"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
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::AlterTable { .. });
match &stmts[0] {
Statement::AlterTable(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::AlterTable { .. });
match &stmts[0] {
Statement::AlterTable(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::AlterTable { .. });
match &stmts[0] {
Statement::AlterTable(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::AlterTable { .. });
match &stmts[0] {
Statement::AlterTable(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::AlterTable { .. });
match &stmts[0] {
Statement::AlterTable(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!();
}
}
let sql = "ALTER TABLE monitor MODIFY COLUMN a UNSET FULLTEXT";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::AlterTable { .. });
match &stmts[0] {
Statement::AlterTable(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER TABLE monitor MODIFY COLUMN a UNSET FULLTEXT"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
}
}