diff --git a/Cargo.lock b/Cargo.lock index e57a6542af..534b8c465a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12197,6 +12197,7 @@ dependencies = [ "arbitrary", "async-trait", "chrono", + "common-base", "common-error", "common-macro", "common-query", diff --git a/src/common/base/src/readable_size.rs b/src/common/base/src/readable_size.rs index 21908526c7..4298989291 100644 --- a/src/common/base/src/readable_size.rs +++ b/src/common/base/src/readable_size.rs @@ -19,7 +19,7 @@ pub const GIB: u64 = MIB * BINARY_DATA_MAGNITUDE; pub const TIB: u64 = GIB * BINARY_DATA_MAGNITUDE; pub const PIB: u64 = TIB * BINARY_DATA_MAGNITUDE; -#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] +#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default)] pub struct ReadableSize(pub u64); impl ReadableSize { diff --git a/src/sql/src/statements/alter.rs b/src/sql/src/statements/alter.rs index 174bdbbdc3..df148ae5b6 100644 --- a/src/sql/src/statements/alter.rs +++ b/src/sql/src/statements/alter.rs @@ -72,29 +72,20 @@ pub enum AlterTableOperation { target_type: DataType, }, /// `SET =
` - SetTableOptions { - options: Vec, - }, - UnsetTableOptions { - keys: Vec, - }, + SetTableOptions { options: Vec }, + /// `UNSET
` + UnsetTableOptions { keys: Vec }, /// `DROP COLUMN ` - DropColumn { - name: Ident, - }, + DropColumn { name: Ident }, /// `RENAME ` - RenameTable { - new_table_name: String, - }, + RenameTable { new_table_name: String }, /// `MODIFY COLUMN SET FULLTEXT [WITH ]` SetColumnFulltext { column_name: Ident, options: FulltextOptions, }, /// `MODIFY COLUMN UNSET FULLTEXT` - UnsetColumnFulltext { - column_name: Ident, - }, + UnsetColumnFulltext { column_name: Ident }, } impl Display for AlterTableOperation { diff --git a/tests-fuzz/Cargo.toml b/tests-fuzz/Cargo.toml index cbac9df713..c408992bd5 100644 --- a/tests-fuzz/Cargo.toml +++ b/tests-fuzz/Cargo.toml @@ -18,6 +18,7 @@ unstable = ["nix"] arbitrary = { version = "1.3.0", features = ["derive"] } async-trait = { workspace = true } chrono = { workspace = true } +common-base = { workspace = true } common-error = { workspace = true } common-macro = { workspace = true } common-query = { workspace = true } @@ -67,14 +68,14 @@ dotenv.workspace = true [[bin]] name = "fuzz_create_table" -path = "targets/fuzz_create_table.rs" +path = "targets/ddl/fuzz_create_table.rs" test = false bench = false doc = false [[bin]] name = "fuzz_create_logical_table" -path = "targets/fuzz_create_logical_table.rs" +path = "targets/ddl/fuzz_create_logical_table.rs" test = false bench = false doc = false @@ -95,21 +96,21 @@ doc = false [[bin]] name = "fuzz_alter_table" -path = "targets/fuzz_alter_table.rs" +path = "targets/ddl/fuzz_alter_table.rs" test = false bench = false doc = false [[bin]] name = "fuzz_alter_logical_table" -path = "targets/fuzz_alter_logical_table.rs" +path = "targets/ddl/fuzz_alter_logical_table.rs" test = false bench = false doc = false [[bin]] name = "fuzz_create_database" -path = "targets/fuzz_create_database.rs" +path = "targets/ddl/fuzz_create_database.rs" test = false bench = false doc = false diff --git a/tests-fuzz/src/context.rs b/tests-fuzz/src/context.rs index 8cfd0ca9fa..d0d5dee72d 100644 --- a/tests-fuzz/src/context.rs +++ b/tests-fuzz/src/context.rs @@ -21,7 +21,7 @@ use snafu::{ensure, OptionExt}; use crate::error::{self, Result}; use crate::generator::Random; -use crate::ir::alter_expr::AlterTableOperation; +use crate::ir::alter_expr::{AlterTableOperation, AlterTableOption}; use crate::ir::{AlterTableExpr, Column, CreateTableExpr, Ident}; pub type TableContextRef = Arc; @@ -35,6 +35,7 @@ pub struct TableContext { // GreptimeDB specific options pub partition: Option, pub primary_keys: Vec, + pub table_options: Vec, } impl From<&CreateTableExpr> for TableContext { @@ -52,6 +53,7 @@ impl From<&CreateTableExpr> for TableContext { columns: columns.clone(), partition: partition.clone(), primary_keys: primary_keys.clone(), + table_options: vec![], } } } @@ -64,7 +66,7 @@ impl TableContext { /// Applies the [AlterTableExpr]. pub fn alter(mut self, expr: AlterTableExpr) -> Result { - match expr.alter_options { + match expr.alter_kinds { AlterTableOperation::AddColumn { column, location } => { ensure!( !self.columns.iter().any(|col| col.name == column.name), @@ -140,6 +142,25 @@ impl TableContext { } Ok(self) } + AlterTableOperation::SetTableOptions { options } => { + for option in options { + if let Some(idx) = self + .table_options + .iter() + .position(|opt| opt.key() == option.key()) + { + self.table_options[idx] = option; + } else { + self.table_options.push(option); + } + } + Ok(self) + } + AlterTableOperation::UnsetTableOptions { keys } => { + self.table_options + .retain(|opt| !keys.contains(&opt.key().to_string())); + Ok(self) + } } } @@ -171,10 +192,11 @@ impl TableContext { #[cfg(test)] mod tests { use common_query::AddColumnLocation; + use common_time::Duration; use datatypes::data_type::ConcreteDataType; use super::TableContext; - use crate::ir::alter_expr::AlterTableOperation; + use crate::ir::alter_expr::{AlterTableOperation, AlterTableOption, Ttl}; use crate::ir::create_expr::ColumnOption; use crate::ir::{AlterTableExpr, Column, Ident}; @@ -185,11 +207,12 @@ mod tests { columns: vec![], partition: None, primary_keys: vec![], + table_options: vec![], }; // Add a column let expr = AlterTableExpr { table_name: "foo".into(), - alter_options: AlterTableOperation::AddColumn { + alter_kinds: AlterTableOperation::AddColumn { column: Column { name: "a".into(), column_type: ConcreteDataType::timestamp_microsecond_datatype(), @@ -205,7 +228,7 @@ mod tests { // Add a column at first let expr = AlterTableExpr { table_name: "foo".into(), - alter_options: AlterTableOperation::AddColumn { + alter_kinds: AlterTableOperation::AddColumn { column: Column { name: "b".into(), column_type: ConcreteDataType::timestamp_microsecond_datatype(), @@ -221,7 +244,7 @@ mod tests { // Add a column after "b" let expr = AlterTableExpr { table_name: "foo".into(), - alter_options: AlterTableOperation::AddColumn { + alter_kinds: AlterTableOperation::AddColumn { column: Column { name: "c".into(), column_type: ConcreteDataType::timestamp_microsecond_datatype(), @@ -239,10 +262,32 @@ mod tests { // Drop the column "b" let expr = AlterTableExpr { table_name: "foo".into(), - alter_options: AlterTableOperation::DropColumn { name: "b".into() }, + alter_kinds: AlterTableOperation::DropColumn { name: "b".into() }, }; let table_ctx = table_ctx.alter(expr).unwrap(); assert_eq!(table_ctx.columns[1].name, Ident::new("a")); assert_eq!(table_ctx.primary_keys, vec![0, 1]); + + // Set table options + let ttl_option = AlterTableOption::Ttl(Ttl::Duration(Duration::new_second(60))); + let expr = AlterTableExpr { + table_name: "foo".into(), + alter_kinds: AlterTableOperation::SetTableOptions { + options: vec![ttl_option.clone()], + }, + }; + let table_ctx = table_ctx.alter(expr).unwrap(); + assert_eq!(table_ctx.table_options.len(), 1); + assert_eq!(table_ctx.table_options[0], ttl_option); + + // Unset table options + let expr = AlterTableExpr { + table_name: "foo".into(), + alter_kinds: AlterTableOperation::UnsetTableOptions { + keys: vec![ttl_option.key().to_string()], + }, + }; + let table_ctx = table_ctx.alter(expr).unwrap(); + assert_eq!(table_ctx.table_options.len(), 0); } } diff --git a/tests-fuzz/src/generator/alter_expr.rs b/tests-fuzz/src/generator/alter_expr.rs index 03aed702fb..0c5a628999 100644 --- a/tests-fuzz/src/generator/alter_expr.rs +++ b/tests-fuzz/src/generator/alter_expr.rs @@ -14,17 +14,19 @@ use std::marker::PhantomData; +use common_base::readable_size::ReadableSize; use common_query::AddColumnLocation; use datatypes::data_type::ConcreteDataType; use derive_builder::Builder; use rand::Rng; use snafu::ensure; +use strum::IntoEnumIterator; use crate::context::TableContextRef; use crate::error::{self, Error, Result}; use crate::fake::WordGenerator; use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Generator, Random}; -use crate::ir::alter_expr::{AlterTableExpr, AlterTableOperation}; +use crate::ir::alter_expr::{AlterTableExpr, AlterTableOperation, AlterTableOption, Ttl}; use crate::ir::create_expr::ColumnOption; use crate::ir::{ droppable_columns, generate_columns, generate_random_value, modifiable_columns, Column, @@ -107,7 +109,7 @@ impl Generator for AlterExprAddColumnGenera .remove(0); Ok(AlterTableExpr { table_name: self.table_ctx.name.clone(), - alter_options: AlterTableOperation::AddColumn { column, location }, + alter_kinds: AlterTableOperation::AddColumn { column, location }, }) } } @@ -130,7 +132,7 @@ impl Generator for AlterExprDropColumnGenerator { let name = droppable[rng.gen_range(0..droppable.len())].name.clone(); Ok(AlterTableExpr { table_name: self.table_ctx.name.clone(), - alter_options: AlterTableOperation::DropColumn { name }, + alter_kinds: AlterTableOperation::DropColumn { name }, }) } } @@ -153,7 +155,7 @@ impl Generator for AlterExprRenameGenerator { .generate_unique_table_name(rng, self.name_generator.as_ref()); Ok(AlterTableExpr { table_name: self.table_ctx.name.clone(), - alter_options: AlterTableOperation::RenameTable { new_table_name }, + alter_kinds: AlterTableOperation::RenameTable { new_table_name }, }) } } @@ -180,7 +182,7 @@ impl Generator for AlterExprModifyDataTypeGenerator Generator for AlterExprModifyDataTypeGenerator { + table_ctx: TableContextRef, + #[builder(default)] + _phantom: PhantomData, +} + +impl Generator for AlterExprSetTableOptionsGenerator { + type Error = Error; + + fn generate(&self, rng: &mut R) -> Result { + let all_options = AlterTableOption::iter().collect::>(); + // Generate random distinct options + let mut option_templates_idx = vec![]; + for _ in 1..rng.gen_range(2..=all_options.len()) { + let option = rng.gen_range(0..all_options.len()); + if !option_templates_idx.contains(&option) { + option_templates_idx.push(option); + } + } + let options = option_templates_idx + .iter() + .map(|idx| match all_options[*idx] { + AlterTableOption::Ttl(_) => { + let ttl_type = rng.gen_range(0..3); + match ttl_type { + 0 => { + let duration: u32 = rng.gen(); + AlterTableOption::Ttl(Ttl::Duration((duration as i64).into())) + } + 1 => AlterTableOption::Ttl(Ttl::Instant), + 2 => AlterTableOption::Ttl(Ttl::Forever), + _ => unreachable!(), + } + } + AlterTableOption::TwcsTimeWindow(_) => { + let time_window: u32 = rng.gen(); + AlterTableOption::TwcsTimeWindow((time_window as i64).into()) + } + AlterTableOption::TwcsMaxOutputFileSize(_) => { + let max_output_file_size: u64 = rng.gen(); + AlterTableOption::TwcsMaxOutputFileSize(ReadableSize(max_output_file_size)) + } + AlterTableOption::TwcsMaxInactiveWindowRuns(_) => { + let max_inactive_window_runs: u64 = rng.gen(); + AlterTableOption::TwcsMaxInactiveWindowRuns(max_inactive_window_runs) + } + AlterTableOption::TwcsMaxActiveWindowFiles(_) => { + let max_active_window_files: u64 = rng.gen(); + AlterTableOption::TwcsMaxActiveWindowFiles(max_active_window_files) + } + AlterTableOption::TwcsMaxActiveWindowRuns(_) => { + let max_active_window_runs: u64 = rng.gen(); + AlterTableOption::TwcsMaxActiveWindowRuns(max_active_window_runs) + } + AlterTableOption::TwcsMaxInactiveWindowFiles(_) => { + let max_inactive_window_files: u64 = rng.gen(); + AlterTableOption::TwcsMaxInactiveWindowFiles(max_inactive_window_files) + } + }) + .collect(); + Ok(AlterTableExpr { + table_name: self.table_ctx.name.clone(), + alter_kinds: AlterTableOperation::SetTableOptions { options }, + }) + } +} + +/// Generates the [AlterTableOperation::UnsetTableOptions] of [AlterTableExpr]. +#[derive(Builder)] +#[builder(pattern = "owned")] +pub struct AlterExprUnsetTableOptionsGenerator { + table_ctx: TableContextRef, + #[builder(default)] + _phantom: PhantomData, +} + +impl Generator for AlterExprUnsetTableOptionsGenerator { + type Error = Error; + + fn generate(&self, rng: &mut R) -> Result { + let all_options = AlterTableOption::iter().collect::>(); + // Generate random distinct options + let mut option_templates_idx = vec![]; + for _ in 1..rng.gen_range(2..=all_options.len()) { + let option = rng.gen_range(0..all_options.len()); + if !option_templates_idx.contains(&option) { + option_templates_idx.push(option); + } + } + let options = option_templates_idx + .iter() + .map(|idx| all_options[*idx].key().to_string()) + .collect(); + Ok(AlterTableExpr { + table_name: self.table_ctx.name.clone(), + alter_kinds: AlterTableOperation::UnsetTableOptions { keys: options }, + }) + } +} + #[cfg(test)] mod tests { use std::sync::Arc; @@ -220,7 +325,7 @@ mod tests { .generate(&mut rng) .unwrap(); let serialized = serde_json::to_string(&expr).unwrap(); - let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"AddColumn":{"column":{"name":{"value":"velit","quote_style":null},"column_type":{"Int32":{}},"options":[{"DefaultValue":{"Int32":1606462472}}]},"location":null}}}"#; + let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_kinds":{"AddColumn":{"column":{"name":{"value":"velit","quote_style":null},"column_type":{"Int32":{}},"options":[{"DefaultValue":{"Int32":1606462472}}]},"location":null}}}"#; assert_eq!(expected, serialized); let expr = AlterExprRenameGeneratorBuilder::default() @@ -230,7 +335,7 @@ mod tests { .generate(&mut rng) .unwrap(); let serialized = serde_json::to_string(&expr).unwrap(); - let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"RenameTable":{"new_table_name":{"value":"nihil","quote_style":null}}}}"#; + let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_kinds":{"RenameTable":{"new_table_name":{"value":"nihil","quote_style":null}}}}"#; assert_eq!(expected, serialized); let expr = AlterExprDropColumnGeneratorBuilder::default() @@ -240,17 +345,37 @@ mod tests { .generate(&mut rng) .unwrap(); let serialized = serde_json::to_string(&expr).unwrap(); - let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"DropColumn":{"name":{"value":"cUmquE","quote_style":null}}}}"#; + let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_kinds":{"DropColumn":{"name":{"value":"cUmquE","quote_style":null}}}}"#; assert_eq!(expected, serialized); let expr = AlterExprModifyDataTypeGeneratorBuilder::default() + .table_ctx(table_ctx.clone()) + .build() + .unwrap() + .generate(&mut rng) + .unwrap(); + let serialized = serde_json::to_string(&expr).unwrap(); + let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_kinds":{"ModifyDataType":{"column":{"name":{"value":"toTAm","quote_style":null},"column_type":{"Int64":{}},"options":[]}}}}"#; + assert_eq!(expected, serialized); + + let expr = AlterExprSetTableOptionsGeneratorBuilder::default() + .table_ctx(table_ctx.clone()) + .build() + .unwrap() + .generate(&mut rng) + .unwrap(); + let serialized = serde_json::to_string(&expr).unwrap(); + let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_kinds":{"SetTableOptions":{"options":[{"TwcsMaxActiveWindowRuns":14908016120444947142},{"TwcsMaxActiveWindowFiles":5840340123887173415},{"TwcsMaxOutputFileSize":17740311466571102265}]}}}"#; + assert_eq!(expected, serialized); + + let expr = AlterExprUnsetTableOptionsGeneratorBuilder::default() .table_ctx(table_ctx) .build() .unwrap() .generate(&mut rng) .unwrap(); let serialized = serde_json::to_string(&expr).unwrap(); - let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"ModifyDataType":{"column":{"name":{"value":"toTAm","quote_style":null},"column_type":{"Int64":{}},"options":[]}}}}"#; + let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_kinds":{"UnsetTableOptions":{"keys":["compaction.twcs.max_active_window_runs"]}}}"#; assert_eq!(expected, serialized); } } diff --git a/tests-fuzz/src/ir.rs b/tests-fuzz/src/ir.rs index b9d13ca9fb..ae6edd595c 100644 --- a/tests-fuzz/src/ir.rs +++ b/tests-fuzz/src/ir.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::Duration; -pub use alter_expr::AlterTableExpr; +pub use alter_expr::{AlterTableExpr, AlterTableOption}; use common_time::timestamp::TimeUnit; use common_time::{Date, DateTime, Timestamp}; pub use create_expr::{CreateDatabaseExpr, CreateTableExpr}; diff --git a/tests-fuzz/src/ir/alter_expr.rs b/tests-fuzz/src/ir/alter_expr.rs index a9fdc18c22..1d637ff660 100644 --- a/tests-fuzz/src/ir/alter_expr.rs +++ b/tests-fuzz/src/ir/alter_expr.rs @@ -12,16 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Display; +use std::str::FromStr; + +use common_base::readable_size::ReadableSize; use common_query::AddColumnLocation; +use common_time::{Duration, FOREVER, INSTANT}; use derive_builder::Builder; use serde::{Deserialize, Serialize}; +use store_api::mito_engine_options::{ + APPEND_MODE_KEY, COMPACTION_TYPE, TTL_KEY, TWCS_MAX_ACTIVE_WINDOW_FILES, + TWCS_MAX_ACTIVE_WINDOW_RUNS, TWCS_MAX_INACTIVE_WINDOW_FILES, TWCS_MAX_INACTIVE_WINDOW_RUNS, + TWCS_MAX_OUTPUT_FILE_SIZE, TWCS_TIME_WINDOW, +}; +use strum::EnumIter; +use crate::error::{self, Result}; use crate::ir::{Column, Ident}; #[derive(Debug, Builder, Clone, Serialize, Deserialize)] pub struct AlterTableExpr { pub table_name: Ident, - pub alter_options: AlterTableOperation, + pub alter_kinds: AlterTableOperation, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -37,4 +49,196 @@ pub enum AlterTableOperation { RenameTable { new_table_name: Ident }, /// `MODIFY COLUMN ` ModifyDataType { column: Column }, + /// `SET
=
` + SetTableOptions { options: Vec }, + /// `UNSET
` + UnsetTableOptions { keys: Vec }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +pub enum Ttl { + Duration(Duration), + Instant, + #[default] + Forever, +} + +impl Display for Ttl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Ttl::Duration(d) => write!(f, "{}", d), + Ttl::Instant => write!(f, "{}", INSTANT), + Ttl::Forever => write!(f, "{}", FOREVER), + } + } +} + +#[derive(Debug, EnumIter, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum AlterTableOption { + Ttl(Ttl), + TwcsTimeWindow(Duration), + TwcsMaxOutputFileSize(ReadableSize), + TwcsMaxInactiveWindowFiles(u64), + TwcsMaxActiveWindowFiles(u64), + TwcsMaxInactiveWindowRuns(u64), + TwcsMaxActiveWindowRuns(u64), +} + +impl AlterTableOption { + pub fn key(&self) -> &str { + match self { + AlterTableOption::Ttl(_) => TTL_KEY, + AlterTableOption::TwcsTimeWindow(_) => TWCS_TIME_WINDOW, + AlterTableOption::TwcsMaxOutputFileSize(_) => TWCS_MAX_OUTPUT_FILE_SIZE, + AlterTableOption::TwcsMaxInactiveWindowFiles(_) => TWCS_MAX_INACTIVE_WINDOW_FILES, + AlterTableOption::TwcsMaxActiveWindowFiles(_) => TWCS_MAX_ACTIVE_WINDOW_FILES, + AlterTableOption::TwcsMaxInactiveWindowRuns(_) => TWCS_MAX_INACTIVE_WINDOW_RUNS, + AlterTableOption::TwcsMaxActiveWindowRuns(_) => TWCS_MAX_ACTIVE_WINDOW_RUNS, + } + } + + /// Parses the AlterTableOption from a key-value pair + fn parse_kv(key: &str, value: &str) -> Result { + match key { + TTL_KEY => { + let ttl = if value.to_lowercase() == INSTANT { + Ttl::Instant + } else if value.to_lowercase() == FOREVER { + Ttl::Forever + } else { + let duration = humantime::parse_duration(value).unwrap(); + Ttl::Duration(duration.into()) + }; + Ok(AlterTableOption::Ttl(ttl)) + } + TWCS_MAX_ACTIVE_WINDOW_RUNS => { + let runs = value.parse().unwrap(); + Ok(AlterTableOption::TwcsMaxActiveWindowRuns(runs)) + } + TWCS_MAX_ACTIVE_WINDOW_FILES => { + let files = value.parse().unwrap(); + Ok(AlterTableOption::TwcsMaxActiveWindowFiles(files)) + } + TWCS_MAX_INACTIVE_WINDOW_RUNS => { + let runs = value.parse().unwrap(); + Ok(AlterTableOption::TwcsMaxInactiveWindowRuns(runs)) + } + TWCS_MAX_INACTIVE_WINDOW_FILES => { + let files = value.parse().unwrap(); + Ok(AlterTableOption::TwcsMaxInactiveWindowFiles(files)) + } + TWCS_MAX_OUTPUT_FILE_SIZE => { + // may be "1M" instead of "1 MiB" + let value = if value.ends_with("B") { + value.to_string() + } else { + format!("{}B", value) + }; + let size = ReadableSize::from_str(&value).unwrap(); + Ok(AlterTableOption::TwcsMaxOutputFileSize(size)) + } + TWCS_TIME_WINDOW => { + let time = humantime::parse_duration(value).unwrap(); + Ok(AlterTableOption::TwcsTimeWindow(time.into())) + } + _ => error::UnexpectedSnafu { + violated: format!("Unknown table option key: {}", key), + } + .fail(), + } + } + + /// Parses the AlterTableOption from comma-separated string + pub fn parse_kv_pairs(option_string: &str) -> Result> { + let mut options = vec![]; + for pair in option_string.split(',') { + let pair = pair.trim(); + let (key, value) = pair.split_once('=').unwrap(); + let key = key.trim().replace("\'", ""); + let value = value.trim().replace('\'', ""); + // Currently we have only one compaction type, so we ignore it + // Cautious: COMPACTION_TYPE may be kept even if there are no compaction options enabled + if key == COMPACTION_TYPE || key == APPEND_MODE_KEY { + continue; + } else { + let option = AlterTableOption::parse_kv(&key, &value)?; + options.push(option); + } + } + Ok(options) + } +} + +impl Display for AlterTableOption { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AlterTableOption::Ttl(d) => write!(f, "'{}' = '{}'", TTL_KEY, d), + AlterTableOption::TwcsTimeWindow(d) => write!(f, "'{}' = '{}'", TWCS_TIME_WINDOW, d), + AlterTableOption::TwcsMaxOutputFileSize(s) => { + // Caution: to_string loses precision for ReadableSize + write!(f, "'{}' = '{}'", TWCS_MAX_OUTPUT_FILE_SIZE, s) + } + AlterTableOption::TwcsMaxInactiveWindowFiles(u) => { + write!(f, "'{}' = '{}'", TWCS_MAX_INACTIVE_WINDOW_FILES, u) + } + AlterTableOption::TwcsMaxActiveWindowFiles(u) => { + write!(f, "'{}' = '{}'", TWCS_MAX_ACTIVE_WINDOW_FILES, u) + } + AlterTableOption::TwcsMaxInactiveWindowRuns(u) => { + write!(f, "'{}' = '{}'", TWCS_MAX_INACTIVE_WINDOW_RUNS, u) + } + AlterTableOption::TwcsMaxActiveWindowRuns(u) => { + write!(f, "'{}' = '{}'", TWCS_MAX_ACTIVE_WINDOW_RUNS, u) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_kv_pairs() { + let option_string = + "compaction.twcs.max_output_file_size = '1M', compaction.type = 'twcs', ttl = 'forever'"; + let options = AlterTableOption::parse_kv_pairs(option_string).unwrap(); + assert_eq!(options.len(), 2); + assert_eq!( + options, + vec![ + AlterTableOption::TwcsMaxOutputFileSize(ReadableSize::from_str("1MB").unwrap()), + AlterTableOption::Ttl(Ttl::Forever), + ] + ); + + let option_string = "compaction.twcs.max_active_window_files = '5030469694939972912', + compaction.twcs.max_active_window_runs = '8361168990283879099', + compaction.twcs.max_inactive_window_files = '6028716566907830876', + compaction.twcs.max_inactive_window_runs = '10622283085591494074', + compaction.twcs.max_output_file_size = '15686.4PiB', + compaction.twcs.time_window = '2061999256ms', + compaction.type = 'twcs', + ttl = '1month 3days 15h 49m 8s 279ms'"; + let options = AlterTableOption::parse_kv_pairs(option_string).unwrap(); + assert_eq!(options.len(), 7); + let expected = vec![ + AlterTableOption::TwcsMaxActiveWindowFiles(5030469694939972912), + AlterTableOption::TwcsMaxActiveWindowRuns(8361168990283879099), + AlterTableOption::TwcsMaxInactiveWindowFiles(6028716566907830876), + AlterTableOption::TwcsMaxInactiveWindowRuns(10622283085591494074), + AlterTableOption::TwcsMaxOutputFileSize(ReadableSize::from_str("15686.4PiB").unwrap()), + AlterTableOption::TwcsTimeWindow(Duration::new_nanosecond(2_061_999_256_000_000)), + AlterTableOption::Ttl(Ttl::Duration(Duration::new_millisecond( + // A month is 2_630_016 seconds + 2_630_016 * 1000 + + 3 * 24 * 60 * 60 * 1000 + + 15 * 60 * 60 * 1000 + + 49 * 60 * 1000 + + 8 * 1000 + + 279, + ))), + ]; + assert_eq!(options, expected); + } } diff --git a/tests-fuzz/src/test_utils.rs b/tests-fuzz/src/test_utils.rs index e65548969a..bef96a1fd7 100644 --- a/tests-fuzz/src/test_utils.rs +++ b/tests-fuzz/src/test_utils.rs @@ -55,5 +55,6 @@ pub fn new_test_ctx() -> TableContext { ], partition: None, primary_keys: vec![], + table_options: vec![], } } diff --git a/tests-fuzz/src/translator.rs b/tests-fuzz/src/translator.rs index 1745aa9336..673b543f2c 100644 --- a/tests-fuzz/src/translator.rs +++ b/tests-fuzz/src/translator.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod common; pub mod mysql; pub mod postgres; diff --git a/tests-fuzz/src/translator/common.rs b/tests-fuzz/src/translator/common.rs new file mode 100644 index 0000000000..2b968ed439 --- /dev/null +++ b/tests-fuzz/src/translator/common.rs @@ -0,0 +1,67 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Display; + +use super::DslTranslator; +use crate::error::{Error, Result}; +use crate::ir::alter_expr::AlterTableOperation; +use crate::ir::{AlterTableExpr, AlterTableOption}; + +/// Shared translator for `ALTER TABLE` operations. +pub(crate) struct CommonAlterTableTranslator; + +impl DslTranslator for CommonAlterTableTranslator { + type Error = Error; + + fn translate(&self, input: &AlterTableExpr) -> Result { + Ok(match &input.alter_kinds { + AlterTableOperation::DropColumn { name } => Self::format_drop(&input.table_name, name), + AlterTableOperation::SetTableOptions { options } => { + Self::format_set_table_options(&input.table_name, options) + } + AlterTableOperation::UnsetTableOptions { keys } => { + Self::format_unset_table_options(&input.table_name, keys) + } + _ => unimplemented!(), + }) + } +} + +impl CommonAlterTableTranslator { + fn format_drop(name: impl Display, column: impl Display) -> String { + format!("ALTER TABLE {name} DROP COLUMN {column};") + } + + fn format_set_table_options(name: impl Display, options: &[AlterTableOption]) -> String { + format!( + "ALTER TABLE {name} SET {};", + options + .iter() + .map(|option| option.to_string()) + .collect::>() + .join(", ") + ) + } + + fn format_unset_table_options(name: impl Display, keys: &[String]) -> String { + format!( + "ALTER TABLE {name} UNSET {};", + keys.iter() + .map(|key| format!("'{}'", key)) + .collect::>() + .join(", ") + ) + } +} diff --git a/tests-fuzz/src/translator/mysql/alter_expr.rs b/tests-fuzz/src/translator/mysql/alter_expr.rs index c973d7cb4b..3bf30b09a3 100644 --- a/tests-fuzz/src/translator/mysql/alter_expr.rs +++ b/tests-fuzz/src/translator/mysql/alter_expr.rs @@ -22,6 +22,7 @@ use crate::error::{Error, Result}; use crate::ir::alter_expr::AlterTableOperation; use crate::ir::create_expr::ColumnOption; use crate::ir::{AlterTableExpr, Column}; +use crate::translator::common::CommonAlterTableTranslator; use crate::translator::DslTranslator; pub struct AlterTableExprTranslator; @@ -30,26 +31,22 @@ impl DslTranslator for AlterTableExprTranslator { type Error = Error; fn translate(&self, input: &AlterTableExpr) -> Result { - Ok(match &input.alter_options { + Ok(match &input.alter_kinds { AlterTableOperation::AddColumn { column, location } => { Self::format_add_column(&input.table_name, column, location) } - AlterTableOperation::DropColumn { name } => Self::format_drop(&input.table_name, name), AlterTableOperation::RenameTable { new_table_name } => { Self::format_rename(&input.table_name, new_table_name) } AlterTableOperation::ModifyDataType { column } => { Self::format_modify_data_type(&input.table_name, column) } + _ => CommonAlterTableTranslator.translate(input)?, }) } } impl AlterTableExprTranslator { - fn format_drop(name: impl Display, column: impl Display) -> String { - format!("ALTER TABLE {name} DROP COLUMN {column};") - } - fn format_rename(name: impl Display, new_name: impl Display) -> String { format!("ALTER TABLE {name} RENAME {new_name};") } @@ -119,11 +116,15 @@ impl AlterTableExprTranslator { #[cfg(test)] mod tests { + use std::str::FromStr; + + use common_base::readable_size::ReadableSize; use common_query::AddColumnLocation; + use common_time::Duration; use datatypes::data_type::ConcreteDataType; use super::AlterTableExprTranslator; - use crate::ir::alter_expr::AlterTableOperation; + use crate::ir::alter_expr::{AlterTableOperation, AlterTableOption, Ttl}; use crate::ir::create_expr::ColumnOption; use crate::ir::{AlterTableExpr, Column}; use crate::translator::DslTranslator; @@ -132,7 +133,7 @@ mod tests { fn test_alter_table_expr() { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::AddColumn { + alter_kinds: AlterTableOperation::AddColumn { column: Column { name: "host".into(), column_type: ConcreteDataType::string_datatype(), @@ -150,7 +151,7 @@ mod tests { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::RenameTable { + alter_kinds: AlterTableOperation::RenameTable { new_table_name: "foo".into(), }, }; @@ -160,7 +161,7 @@ mod tests { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::DropColumn { name: "foo".into() }, + alter_kinds: AlterTableOperation::DropColumn { name: "foo".into() }, }; let output = AlterTableExprTranslator.translate(&alter_expr).unwrap(); @@ -168,7 +169,7 @@ mod tests { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::ModifyDataType { + alter_kinds: AlterTableOperation::ModifyDataType { column: Column { name: "host".into(), column_type: ConcreteDataType::string_datatype(), @@ -180,4 +181,48 @@ mod tests { let output = AlterTableExprTranslator.translate(&alter_expr).unwrap(); assert_eq!("ALTER TABLE test MODIFY COLUMN host STRING;", output); } + + #[test] + fn test_alter_table_expr_set_table_options() { + let alter_expr = AlterTableExpr { + table_name: "test".into(), + alter_kinds: AlterTableOperation::SetTableOptions { + options: vec![ + AlterTableOption::Ttl(Ttl::Duration(Duration::new_second(60))), + AlterTableOption::TwcsTimeWindow(Duration::new_second(60)), + AlterTableOption::TwcsMaxOutputFileSize(ReadableSize::from_str("1GB").unwrap()), + AlterTableOption::TwcsMaxActiveWindowFiles(10), + AlterTableOption::TwcsMaxActiveWindowRuns(10), + AlterTableOption::TwcsMaxInactiveWindowFiles(5), + AlterTableOption::TwcsMaxInactiveWindowRuns(5), + ], + }, + }; + + let output = AlterTableExprTranslator.translate(&alter_expr).unwrap(); + let expected = concat!( + "ALTER TABLE test SET 'ttl' = '60s', ", + "'compaction.twcs.time_window' = '60s', ", + "'compaction.twcs.max_output_file_size' = '1.0GiB', ", + "'compaction.twcs.max_active_window_files' = '10', ", + "'compaction.twcs.max_active_window_runs' = '10', ", + "'compaction.twcs.max_inactive_window_files' = '5', ", + "'compaction.twcs.max_inactive_window_runs' = '5';" + ); + assert_eq!(expected, output); + } + + #[test] + fn test_alter_table_expr_unset_table_options() { + let alter_expr = AlterTableExpr { + table_name: "test".into(), + alter_kinds: AlterTableOperation::UnsetTableOptions { + keys: vec!["ttl".into(), "compaction.twcs.time_window".into()], + }, + }; + + let output = AlterTableExprTranslator.translate(&alter_expr).unwrap(); + let expected = "ALTER TABLE test UNSET 'ttl', 'compaction.twcs.time_window';"; + assert_eq!(expected, output); + } } diff --git a/tests-fuzz/src/translator/postgres/alter_expr.rs b/tests-fuzz/src/translator/postgres/alter_expr.rs index 42db202efe..f66ce0db92 100644 --- a/tests-fuzz/src/translator/postgres/alter_expr.rs +++ b/tests-fuzz/src/translator/postgres/alter_expr.rs @@ -21,6 +21,7 @@ use crate::error::{Error, Result}; use crate::ir::alter_expr::AlterTableOperation; use crate::ir::create_expr::ColumnOption; use crate::ir::{AlterTableExpr, Column}; +use crate::translator::common::CommonAlterTableTranslator; use crate::translator::postgres::sql_data_type_to_postgres_data_type; use crate::translator::DslTranslator; @@ -30,26 +31,22 @@ impl DslTranslator for AlterTableExprTranslator { type Error = Error; fn translate(&self, input: &AlterTableExpr) -> Result { - Ok(match &input.alter_options { + Ok(match &input.alter_kinds { AlterTableOperation::AddColumn { column, .. } => { Self::format_add_column(&input.table_name, column) } - AlterTableOperation::DropColumn { name } => Self::format_drop(&input.table_name, name), AlterTableOperation::RenameTable { new_table_name } => { Self::format_rename(&input.table_name, new_table_name) } AlterTableOperation::ModifyDataType { column } => { Self::format_modify_data_type(&input.table_name, column) } + _ => CommonAlterTableTranslator.translate(input)?, }) } } impl AlterTableExprTranslator { - fn format_drop(name: impl Display, column: impl Display) -> String { - format!("ALTER TABLE {name} DROP COLUMN {column};") - } - fn format_rename(name: impl Display, new_name: impl Display) -> String { format!("ALTER TABLE {name} RENAME TO {new_name};") } @@ -116,11 +113,15 @@ impl AlterTableExprTranslator { #[cfg(test)] mod tests { + use std::str::FromStr; + + use common_base::readable_size::ReadableSize; use common_query::AddColumnLocation; + use common_time::Duration; use datatypes::data_type::ConcreteDataType; use super::AlterTableExprTranslator; - use crate::ir::alter_expr::AlterTableOperation; + use crate::ir::alter_expr::{AlterTableOperation, AlterTableOption, Ttl}; use crate::ir::create_expr::ColumnOption; use crate::ir::{AlterTableExpr, Column}; use crate::translator::DslTranslator; @@ -129,7 +130,7 @@ mod tests { fn test_alter_table_expr() { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::AddColumn { + alter_kinds: AlterTableOperation::AddColumn { column: Column { name: "host".into(), column_type: ConcreteDataType::string_datatype(), @@ -145,7 +146,7 @@ mod tests { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::RenameTable { + alter_kinds: AlterTableOperation::RenameTable { new_table_name: "foo".into(), }, }; @@ -155,7 +156,7 @@ mod tests { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::DropColumn { name: "foo".into() }, + alter_kinds: AlterTableOperation::DropColumn { name: "foo".into() }, }; let output = AlterTableExprTranslator.translate(&alter_expr).unwrap(); @@ -163,7 +164,7 @@ mod tests { let alter_expr = AlterTableExpr { table_name: "test".into(), - alter_options: AlterTableOperation::ModifyDataType { + alter_kinds: AlterTableOperation::ModifyDataType { column: Column { name: "host".into(), column_type: ConcreteDataType::string_datatype(), @@ -176,4 +177,48 @@ mod tests { // Ignores the location and primary key option. assert_eq!("ALTER TABLE test MODIFY COLUMN host STRING;", output); } + + #[test] + fn test_alter_table_expr_set_table_options() { + let alter_expr = AlterTableExpr { + table_name: "test".into(), + alter_kinds: AlterTableOperation::SetTableOptions { + options: vec![ + AlterTableOption::Ttl(Ttl::Duration(Duration::new_second(60))), + AlterTableOption::TwcsTimeWindow(Duration::new_second(60)), + AlterTableOption::TwcsMaxOutputFileSize(ReadableSize::from_str("1GB").unwrap()), + AlterTableOption::TwcsMaxActiveWindowFiles(10), + AlterTableOption::TwcsMaxActiveWindowRuns(10), + AlterTableOption::TwcsMaxInactiveWindowFiles(5), + AlterTableOption::TwcsMaxInactiveWindowRuns(5), + ], + }, + }; + + let output = AlterTableExprTranslator.translate(&alter_expr).unwrap(); + let expected = concat!( + "ALTER TABLE test SET 'ttl' = '60s', ", + "'compaction.twcs.time_window' = '60s', ", + "'compaction.twcs.max_output_file_size' = '1.0GiB', ", + "'compaction.twcs.max_active_window_files' = '10', ", + "'compaction.twcs.max_active_window_runs' = '10', ", + "'compaction.twcs.max_inactive_window_files' = '5', ", + "'compaction.twcs.max_inactive_window_runs' = '5';" + ); + assert_eq!(expected, output); + } + + #[test] + fn test_alter_table_expr_unset_table_options() { + let alter_expr = AlterTableExpr { + table_name: "test".into(), + alter_kinds: AlterTableOperation::UnsetTableOptions { + keys: vec!["ttl".into(), "compaction.twcs.time_window".into()], + }, + }; + + let output = AlterTableExprTranslator.translate(&alter_expr).unwrap(); + let expected = "ALTER TABLE test UNSET 'ttl', 'compaction.twcs.time_window';"; + assert_eq!(expected, output); + } } diff --git a/tests-fuzz/src/validator.rs b/tests-fuzz/src/validator.rs index cf2df9af22..406dd66041 100644 --- a/tests-fuzz/src/validator.rs +++ b/tests-fuzz/src/validator.rs @@ -14,3 +14,4 @@ pub mod column; pub mod row; +pub mod table; diff --git a/tests-fuzz/src/validator/table.rs b/tests-fuzz/src/validator/table.rs new file mode 100644 index 0000000000..406719b2d6 --- /dev/null +++ b/tests-fuzz/src/validator/table.rs @@ -0,0 +1,103 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use snafu::{ensure, ResultExt}; +use sqlx::database::HasArguments; +use sqlx::{ColumnIndex, Database, Decode, Encode, Executor, IntoArguments, Row, Type}; + +use crate::error::{self, Result, UnexpectedSnafu}; +use crate::ir::alter_expr::AlterTableOption; + +/// Parses table options from the result of `SHOW CREATE TABLE` +/// An example of the result of `SHOW CREATE TABLE`: +/// +-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +/// | Table | Create Table | +/// +-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +/// | json | CREATE TABLE IF NOT EXISTS `json` (`ts` TIMESTAMP(3) NOT NULL, `j` JSON NULL, TIME INDEX (`ts`)) ENGINE=mito WITH(compaction.twcs.max_output_file_size = '1M', compaction.type = 'twcs', ttl = '1day') | +/// +-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +fn parse_show_create(show_create: &str) -> Result> { + if let Some(option_start) = show_create.find("WITH(") { + let option_end = { + let remain_str = &show_create[option_start..]; + if let Some(end) = remain_str.find(')') { + end + option_start + } else { + return UnexpectedSnafu { + violated: format!("Cannot find the end of the options in: {}", show_create), + } + .fail(); + } + }; + let options = &show_create[option_start + 5..option_end]; + Ok(AlterTableOption::parse_kv_pairs(options)?) + } else { + Ok(vec![]) + } +} + +/// Fetches table options from the context +pub async fn fetch_table_options<'a, DB, E>(e: E, sql: &'a str) -> Result> +where + DB: Database, + >::Arguments: IntoArguments<'a, DB>, + for<'c> E: 'a + Executor<'c, Database = DB>, + for<'c> String: Decode<'c, DB> + Type, + for<'c> String: Encode<'c, DB> + Type, + usize: ColumnIndex<::Row>, +{ + let fetched_rows = sqlx::query(sql) + .fetch_all(e) + .await + .context(error::ExecuteQuerySnafu { sql })?; + ensure!( + fetched_rows.len() == 1, + error::AssertSnafu { + reason: format!( + "Expected fetched row length: 1, got: {}", + fetched_rows.len(), + ) + } + ); + + let row = fetched_rows.first().unwrap(); + let show_create = row.try_get::(1).unwrap(); + parse_show_create(&show_create) +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use common_base::readable_size::ReadableSize; + use common_time::Duration; + + use super::*; + use crate::ir::alter_expr::Ttl; + use crate::ir::AlterTableOption; + + #[test] + fn test_parse_show_create() { + let show_create = "CREATE TABLE IF NOT EXISTS `json` (`ts` TIMESTAMP(3) NOT NULL, `j` JSON NULL, TIME INDEX (`ts`)) ENGINE=mito WITH(compaction.twcs.max_output_file_size = '1M', compaction.type = 'twcs', ttl = '1day')"; + let options = parse_show_create(show_create).unwrap(); + assert_eq!(options.len(), 2); + assert_eq!( + options[0], + AlterTableOption::TwcsMaxOutputFileSize(ReadableSize::from_str("1MB").unwrap()) + ); + assert_eq!( + options[1], + AlterTableOption::Ttl(Ttl::Duration(Duration::new_second(24 * 60 * 60))) + ); + } +} diff --git a/tests-fuzz/targets/fuzz_alter_logical_table.rs b/tests-fuzz/targets/ddl/fuzz_alter_logical_table.rs similarity index 100% rename from tests-fuzz/targets/fuzz_alter_logical_table.rs rename to tests-fuzz/targets/ddl/fuzz_alter_logical_table.rs diff --git a/tests-fuzz/targets/fuzz_alter_table.rs b/tests-fuzz/targets/ddl/fuzz_alter_table.rs similarity index 72% rename from tests-fuzz/targets/fuzz_alter_table.rs rename to tests-fuzz/targets/ddl/fuzz_alter_table.rs index 7f2a809c9e..247d7632ee 100644 --- a/tests-fuzz/targets/fuzz_alter_table.rs +++ b/tests-fuzz/targets/ddl/fuzz_alter_table.rs @@ -34,10 +34,13 @@ use tests_fuzz::fake::{ use tests_fuzz::generator::alter_expr::{ AlterExprAddColumnGeneratorBuilder, AlterExprDropColumnGeneratorBuilder, AlterExprModifyDataTypeGeneratorBuilder, AlterExprRenameGeneratorBuilder, + AlterExprSetTableOptionsGeneratorBuilder, AlterExprUnsetTableOptionsGeneratorBuilder, }; use tests_fuzz::generator::create_expr::CreateTableExprGeneratorBuilder; use tests_fuzz::generator::Generator; -use tests_fuzz::ir::{droppable_columns, modifiable_columns, AlterTableExpr, CreateTableExpr}; +use tests_fuzz::ir::{ + droppable_columns, modifiable_columns, AlterTableExpr, AlterTableOption, CreateTableExpr, +}; use tests_fuzz::translator::mysql::alter_expr::AlterTableExprTranslator; use tests_fuzz::translator::mysql::create_expr::CreateTableExprTranslator; use tests_fuzz::translator::DslTranslator; @@ -62,11 +65,13 @@ struct FuzzInput { } #[derive(Debug, EnumIter)] -enum AlterTableOption { +enum AlterTableKind { AddColumn, DropColumn, RenameTable, ModifyDataType, + SetTableOptions, + UnsetTableOptions, } fn generate_create_table_expr(rng: &mut R) -> Result { @@ -93,23 +98,23 @@ fn generate_alter_table_expr( table_ctx: TableContextRef, rng: &mut R, ) -> Result { - let options = AlterTableOption::iter().collect::>(); - match options[rng.gen_range(0..options.len())] { - AlterTableOption::DropColumn if !droppable_columns(&table_ctx.columns).is_empty() => { + let kinds = AlterTableKind::iter().collect::>(); + match kinds[rng.gen_range(0..kinds.len())] { + AlterTableKind::DropColumn if !droppable_columns(&table_ctx.columns).is_empty() => { AlterExprDropColumnGeneratorBuilder::default() .table_ctx(table_ctx) .build() .unwrap() .generate(rng) } - AlterTableOption::ModifyDataType if !modifiable_columns(&table_ctx.columns).is_empty() => { + AlterTableKind::ModifyDataType if !modifiable_columns(&table_ctx.columns).is_empty() => { AlterExprModifyDataTypeGeneratorBuilder::default() .table_ctx(table_ctx) .build() .unwrap() .generate(rng) } - AlterTableOption::RenameTable => AlterExprRenameGeneratorBuilder::default() + AlterTableKind::RenameTable => AlterExprRenameGeneratorBuilder::default() .table_ctx(table_ctx) .name_generator(Box::new(MappedGenerator::new( WordGenerator, @@ -118,6 +123,20 @@ fn generate_alter_table_expr( .build() .unwrap() .generate(rng), + AlterTableKind::SetTableOptions => { + let expr_generator = AlterExprSetTableOptionsGeneratorBuilder::default() + .table_ctx(table_ctx) + .build() + .unwrap(); + expr_generator.generate(rng) + } + AlterTableKind::UnsetTableOptions => { + let expr_generator = AlterExprUnsetTableOptionsGeneratorBuilder::default() + .table_ctx(table_ctx) + .build() + .unwrap(); + expr_generator.generate(rng) + } _ => { let location = rng.gen_bool(0.5); let expr_generator = AlterExprAddColumnGeneratorBuilder::default() @@ -179,6 +198,31 @@ async fn execute_alter_table(ctx: FuzzContext, input: FuzzInput) -> Result<()> { let mut columns = table_ctx.columns.clone(); columns.sort_by(|a, b| a.name.value.cmp(&b.name.value)); validator::column::assert_eq(&column_entries, &columns)?; + + // Validates table options + let sql = format!("SHOW CREATE TABLE {}", table_ctx.name); + let mut table_options = validator::table::fetch_table_options(&ctx.greptime, &sql).await?; + table_options.sort_by(|a, b| a.key().cmp(b.key())); + let mut expected_table_options = table_ctx.table_options.clone(); + expected_table_options.sort_by(|a, b| a.key().cmp(b.key())); + table_options + .iter() + .zip(expected_table_options.iter()) + .for_each(|(a, b)| { + if let ( + AlterTableOption::TwcsMaxOutputFileSize(a), + AlterTableOption::TwcsMaxOutputFileSize(b), + ) = (a, b) + { + // to_string loses precision for ReadableSize, so the size in generated SQL is not the same as the size in the table context, + // but the string representation should be the same. For example: + // to_string() from_str() + // ReadableSize(13001360408898724524) ------------> "11547.5PiB" -----------> ReadableSize(13001329174265200640) + assert_eq!(a.to_string(), b.to_string()); + } else { + assert_eq!(a, b); + } + }); } // Cleans up diff --git a/tests-fuzz/targets/fuzz_create_database.rs b/tests-fuzz/targets/ddl/fuzz_create_database.rs similarity index 100% rename from tests-fuzz/targets/fuzz_create_database.rs rename to tests-fuzz/targets/ddl/fuzz_create_database.rs diff --git a/tests-fuzz/targets/fuzz_create_logical_table.rs b/tests-fuzz/targets/ddl/fuzz_create_logical_table.rs similarity index 100% rename from tests-fuzz/targets/fuzz_create_logical_table.rs rename to tests-fuzz/targets/ddl/fuzz_create_logical_table.rs diff --git a/tests-fuzz/targets/fuzz_create_table.rs b/tests-fuzz/targets/ddl/fuzz_create_table.rs similarity index 100% rename from tests-fuzz/targets/fuzz_create_table.rs rename to tests-fuzz/targets/ddl/fuzz_create_table.rs