mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-03 20:02:54 +00:00
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:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -12197,6 +12197,7 @@ dependencies = [
|
||||
"arbitrary",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"common-base",
|
||||
"common-error",
|
||||
"common-macro",
|
||||
"common-query",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,5 +55,6 @@ pub fn new_test_ctx() -> TableContext {
|
||||
],
|
||||
partition: None,
|
||||
primary_keys: vec![],
|
||||
table_options: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
67
tests-fuzz/src/translator/common.rs
Normal file
67
tests-fuzz/src/translator/common.rs
Normal 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(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +14,4 @@
|
||||
|
||||
pub mod column;
|
||||
pub mod row;
|
||||
pub mod table;
|
||||
|
||||
103
tests-fuzz/src/validator/table.rs
Normal file
103
tests-fuzz/src/validator/table.rs
Normal 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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user