feat(fuzz): add alter table options for alter fuzzer (#5074)

* feat(fuzz): add set table options to alter fuzzer

* chore: clippy is happy, I'm sad

* chore: happy ci happy

* fix: unit test

* feat(fuzz): add unset table options to alter fuzzer

* fix: unit test

* feat(fuzz): add table option validator

* fix: make clippy happy

* chore: add comments

* chore: apply review comments

* fix: unit test

* feat(fuzz): add more ttl options

* fix: #5108

* chore: add comments

* chore: add comments
This commit is contained in:
Yohan Wal
2024-12-12 12:21:38 +08:00
committed by Yingwen
parent bc5a57f51f
commit 65eabb2a05
20 changed files with 742 additions and 68 deletions

1
Cargo.lock generated
View File

@@ -12197,6 +12197,7 @@ dependencies = [
"arbitrary",
"async-trait",
"chrono",
"common-base",
"common-error",
"common-macro",
"common-query",

View File

@@ -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 {

View File

@@ -72,29 +72,20 @@ pub enum AlterTableOperation {
target_type: DataType,
},
/// `SET <table attrs key> = <table attr value>`
SetTableOptions {
options: Vec<KeyValueOption>,
},
UnsetTableOptions {
keys: Vec<String>,
},
SetTableOptions { options: Vec<KeyValueOption> },
/// `UNSET <table attrs key>`
UnsetTableOptions { keys: Vec<String> },
/// `DROP COLUMN <name>`
DropColumn {
name: Ident,
},
DropColumn { name: Ident },
/// `RENAME <new_table_name>`
RenameTable {
new_table_name: String,
},
RenameTable { new_table_name: String },
/// `MODIFY COLUMN <column_name> SET FULLTEXT [WITH <options>]`
SetColumnFulltext {
column_name: Ident,
options: FulltextOptions,
},
/// `MODIFY COLUMN <column_name> UNSET FULLTEXT`
UnsetColumnFulltext {
column_name: Ident,
},
UnsetColumnFulltext { column_name: Ident },
}
impl Display for AlterTableOperation {

View File

@@ -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

View File

@@ -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<TableContext>;
@@ -35,6 +35,7 @@ pub struct TableContext {
// GreptimeDB specific options
pub partition: Option<PartitionDef>,
pub primary_keys: Vec<usize>,
pub table_options: Vec<AlterTableOption>,
}
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<TableContext> {
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);
}
}

View File

@@ -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<R: Rng + 'static> Generator<AlterTableExpr, R> 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<R: Rng> Generator<AlterTableExpr, R> for AlterExprDropColumnGenerator<R> {
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<R: Rng> Generator<AlterTableExpr, R> for AlterExprRenameGenerator<R> {
.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<R: Rng> Generator<AlterTableExpr, R> for AlterExprModifyDataTypeGenerator<R
Ok(AlterTableExpr {
table_name: self.table_ctx.name.clone(),
alter_options: AlterTableOperation::ModifyDataType {
alter_kinds: AlterTableOperation::ModifyDataType {
column: Column {
name: changed.name,
column_type: to_type,
@@ -191,6 +193,109 @@ impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprModifyDataTypeGenerator<R
}
}
/// Generates the [AlterTableOperation::SetTableOptions] of [AlterTableExpr].
#[derive(Builder)]
#[builder(pattern = "owned")]
pub struct AlterExprSetTableOptionsGenerator<R: Rng> {
table_ctx: TableContextRef,
#[builder(default)]
_phantom: PhantomData<R>,
}
impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprSetTableOptionsGenerator<R> {
type Error = Error;
fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
let all_options = AlterTableOption::iter().collect::<Vec<_>>();
// 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<R: Rng> {
table_ctx: TableContextRef,
#[builder(default)]
_phantom: PhantomData<R>,
}
impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprUnsetTableOptionsGenerator<R> {
type Error = Error;
fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
let all_options = AlterTableOption::iter().collect::<Vec<_>>();
// 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);
}
}

View File

@@ -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};

View File

@@ -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 <column_name> <column_type>`
ModifyDataType { column: Column },
/// `SET <table attrs key> = <table attr value>`
SetTableOptions { options: Vec<AlterTableOption> },
/// `UNSET <table attrs key>`
UnsetTableOptions { keys: Vec<String> },
}
#[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<Self> {
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<Vec<Self>> {
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);
}
}

View File

@@ -55,5 +55,6 @@ pub fn new_test_ctx() -> TableContext {
],
partition: None,
primary_keys: vec![],
table_options: vec![],
}
}

View File

@@ -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;

View File

@@ -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<AlterTableExpr, String> for CommonAlterTableTranslator {
type Error = Error;
fn translate(&self, input: &AlterTableExpr) -> Result<String> {
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::<Vec<_>>()
.join(", ")
)
}
fn format_unset_table_options(name: impl Display, keys: &[String]) -> String {
format!(
"ALTER TABLE {name} UNSET {};",
keys.iter()
.map(|key| format!("'{}'", key))
.collect::<Vec<_>>()
.join(", ")
)
}
}

View File

@@ -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<AlterTableExpr, String> for AlterTableExprTranslator {
type Error = Error;
fn translate(&self, input: &AlterTableExpr) -> Result<String> {
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);
}
}

View File

@@ -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<AlterTableExpr, String> for AlterTableExprTranslator {
type Error = Error;
fn translate(&self, input: &AlterTableExpr) -> Result<String> {
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);
}
}

View File

@@ -14,3 +14,4 @@
pub mod column;
pub mod row;
pub mod table;

View File

@@ -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<Vec<AlterTableOption>> {
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<Vec<AlterTableOption>>
where
DB: Database,
<DB as HasArguments<'a>>::Arguments: IntoArguments<'a, DB>,
for<'c> E: 'a + Executor<'c, Database = DB>,
for<'c> String: Decode<'c, DB> + Type<DB>,
for<'c> String: Encode<'c, DB> + Type<DB>,
usize: ColumnIndex<<DB as Database>::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::<String, usize>(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)))
);
}
}

View File

@@ -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<R: Rng + 'static>(rng: &mut R) -> Result<CreateTableExpr> {
@@ -93,23 +98,23 @@ fn generate_alter_table_expr<R: Rng + 'static>(
table_ctx: TableContextRef,
rng: &mut R,
) -> Result<AlterTableExpr> {
let options = AlterTableOption::iter().collect::<Vec<_>>();
match options[rng.gen_range(0..options.len())] {
AlterTableOption::DropColumn if !droppable_columns(&table_ctx.columns).is_empty() => {
let kinds = AlterTableKind::iter().collect::<Vec<_>>();
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<R: Rng + 'static>(
.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