feat: add create alter table expr translator (#3203)

* feat: add create table expr translator

* feat: add alter table expr translator

* refactor: expose mod

* refactor: expr generator

* chore: ignore typos check for lorem_words

* feat: add string map helper functions

* chore: remove unit tests
This commit is contained in:
Weny Xu
2024-01-23 11:53:42 +09:00
committed by GitHub
parent 31787f4bfd
commit 364754afa2
17 changed files with 1231 additions and 262 deletions

25
Cargo.lock generated
View File

@@ -2875,12 +2875,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "deunicode"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae2a35373c5c74340b79ae6780b498b2b183915ec5dacf263aac5a099bf485a"
[[package]]
name = "diff"
version = "0.1.13"
@@ -3154,17 +3148,6 @@ dependencies = [
"rand",
]
[[package]]
name = "faker_rand"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300d2ddbf2245b5b5e723995e0961033121b4fc2be9045fb661af82bd739ffb6"
dependencies = [
"deunicode",
"lazy_static",
"rand",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
@@ -9551,15 +9534,21 @@ dependencies = [
"common-error",
"common-macro",
"common-query",
"common-telemetry",
"datatypes",
"derive_builder 0.12.0",
"faker_rand",
"dotenv",
"lazy_static",
"partition",
"rand",
"rand_chacha",
"serde",
"serde_json",
"snafu",
"sql",
"sqlparser 0.38.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=6a93567ae38d42be5c8d08b13c8ff4dde26502ef)",
"sqlx",
"tokio",
]
[[package]]

View File

@@ -9,12 +9,25 @@ async-trait = { workspace = true }
common-error = { workspace = true }
common-macro = { workspace = true }
common-query = { workspace = true }
common-telemetry = { workspace = true }
datatypes = { workspace = true }
derive_builder = { workspace = true }
faker_rand = "0.1"
lazy_static = { workspace = true }
partition = { workspace = true }
rand = { workspace = true }
rand_chacha = "0.3.1"
serde = { workspace = true }
serde_json = { workspace = true }
snafu = { workspace = true }
sql = { workspace = true }
sqlparser.workspace = true
[dev-dependencies]
dotenv = "0.15"
sqlx = { version = "0.6", features = [
"runtime-tokio-rustls",
"mysql",
"postgres",
"chrono",
] }
tokio = { workspace = true }

View File

@@ -26,7 +26,7 @@ pub struct TableContext {
pub columns: Vec<Column>,
// GreptimeDB specific options
pub partitions: Vec<PartitionDef>,
pub partition: Option<PartitionDef>,
pub primary_keys: Vec<usize>,
}
@@ -35,7 +35,7 @@ impl From<&CreateTableExpr> for TableContext {
CreateTableExpr {
name,
columns,
partitions,
partition,
primary_keys,
..
}: &CreateTableExpr,
@@ -43,7 +43,7 @@ impl From<&CreateTableExpr> for TableContext {
Self {
name: name.to_string(),
columns: columns.clone(),
partitions: partitions.clone(),
partition: partition.clone(),
primary_keys: primary_keys.clone(),
}
}

View File

@@ -0,0 +1,249 @@
alias
consequatur
aut
perferendis
sit
voluptatem
accusantium
doloremque
aperiam
eaque
ipsa
quae
ab
illo
inventore
veritatis
et
quasi
architecto
beatae
vitae
dicta
sunt
explicabo
aspernatur
aut
odit
aut
fugit
sed
quia
consequuntur
magni
dolores
eos
qui
ratione
voluptatem
sequi
nesciunt
neque
dolorem
ipsum
quia
dolor
sit
amet
consectetur
adipisci
velit
sed
quia
non
numquam
eius
modi
tempora
incidunt
ut
labore
et
dolore
magnam
aliquam
quaerat
voluptatem
ut
enim
ad
minima
veniam
quis
nostrum
exercitationem
ullam
corporis
nemo
enim
ipsam
voluptatem
quia
voluptas
sit
suscipit
laboriosam
nisi
ut
aliquid
ex
ea
commodi
consequatur
quis
autem
vel
eum
iure
reprehenderit
qui
in
ea
voluptate
velit
esse
quam
nihil
molestiae
et
iusto
odio
dignissimos
ducimus
qui
blanditiis
praesentium
laudantium
totam
rem
voluptatum
deleniti
atque
corrupti
quos
dolores
et
quas
molestias
excepturi
sint
occaecati
cupiditate
non
provident
sed
ut
perspiciatis
unde
omnis
iste
natus
error
similique
sunt
in
culpa
qui
officia
deserunt
mollitia
animi
id
est
laborum
et
dolorum
fuga
et
harum
quidem
rerum
facilis
est
et
expedita
distinctio
nam
libero
tempore
cum
soluta
nobis
est
eligendi
optio
cumque
nihil
impedit
quo
porro
quisquam
est
qui
minus
id
quod
maxime
placeat
facere
possimus
omnis
voluptas
assumenda
est
omnis
dolor
repellendus
temporibus
autem
quibusdam
et
aut
consequatur
vel
illum
qui
dolorem
eum
fugiat
quo
voluptas
nulla
pariatur
at
vero
eos
et
accusamus
officiis
debitis
aut
rerum
necessitatibus
saepe
eveniet
ut
et
voluptates
repudiandae
sint
et
molestiae
non
recusandae
itaque
earum
rerum
hic
tenetur
a
sapiente
delectus
ut
aut
reiciendis
voluptatibus
maiores
doloribus
asperiores
repellat

174
tests-fuzz/src/fake.rs Normal file
View File

@@ -0,0 +1,174 @@
// 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::collections::HashSet;
use lazy_static::lazy_static;
use rand::seq::{IteratorRandom, SliceRandom};
use rand::Rng;
use crate::generator::Random;
use crate::impl_random;
lazy_static! {
pub static ref LOREM_WORDS: Vec<String> = include_str!("data/lorem_words")
.lines()
.map(String::from)
.collect();
}
/// Modified from https://github.com/ucarion/faker_rand/blob/ea70c660e1ecd7320156eddb31d2830a511f8842/src/lib.rs
macro_rules! faker_impl_from_values {
($name: ident, $values: expr) => {
impl rand::distributions::Distribution<$name> for rand::distributions::Standard {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> $name {
$name($values[rng.gen_range(0..$values.len())].clone())
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
};
}
pub struct Word(String);
faker_impl_from_values!(Word, LOREM_WORDS);
pub struct WordGenerator;
impl_random!(String, WordGenerator, LOREM_WORDS);
pub type WordMapFn<R> = Box<dyn Fn(&mut R, String) -> String>;
pub struct MapWordGenerator<R: Rng> {
base: WordGenerator,
map: WordMapFn<R>,
}
pub fn random_capitalize_map<R: Rng + 'static>(rng: &mut R, s: String) -> String {
let mut v = s.chars().collect::<Vec<_>>();
let select = rng.gen_range(0..s.len());
for idx in (0..s.len()).choose_multiple(rng, select) {
v[idx] = v[idx].to_uppercase().next().unwrap();
}
v.into_iter().collect::<String>()
}
lazy_static! {
static ref KEYWORDS_SET: HashSet<&'static str> = sqlparser::keywords::ALL_KEYWORDS
.iter()
.cloned()
.collect::<HashSet<_>>();
}
/// Returns true if it's a keyword.
pub fn is_keyword(word: impl AsRef<str>) -> bool {
KEYWORDS_SET.contains(word.as_ref())
}
/// Returns true if it contains uppercase char.
pub fn contain_uppercase_char(s: &str) -> bool {
s.chars().any(|c| c.is_uppercase())
}
/// Returns true if it's a keyword or contains uppercase char.
pub fn is_keyword_or_contain_uppercase(s: &str) -> bool {
is_keyword(s.to_uppercase()) || contain_uppercase_char(s)
}
pub fn make_backtick_map<R: Rng + 'static, F: Fn(&str) -> bool>(
f: F,
) -> impl Fn(&mut R, String) -> String {
move |_rng, s| -> String {
let need = f(&s);
if need {
format!("`{s}`")
} else {
s
}
}
}
pub fn make_quote_map<R: Rng + 'static, F: Fn(&str) -> bool>(
f: F,
) -> impl Fn(&mut R, String) -> String {
move |_rng, s| -> String {
let need = f(&s);
if need {
format!("\"{s}\"")
} else {
s
}
}
}
/// Adds backticks if it contains uppercase chars.
pub fn auto_backtick_map<R: Rng + 'static>(_rng: &mut R, s: String) -> String {
let need = s.chars().any(|c| c.is_uppercase());
if need {
format!("`{s}`")
} else {
s
}
}
/// Adds backticks if it contains uppercase chars.
pub fn uppercase_and_keyword_backtick_map<R: Rng + 'static>(rng: &mut R, s: String) -> String {
make_backtick_map(is_keyword_or_contain_uppercase)(rng, s)
}
/// Adds quotes if it contains uppercase chars.
pub fn auto_quote_map<R: Rng + 'static>(rng: &mut R, s: String) -> String {
make_quote_map(contain_uppercase_char)(rng, s)
}
/// Adds quotes if it contains uppercase chars.
pub fn uppercase_and_keyword_quote_map<R: Rng + 'static>(rng: &mut R, s: String) -> String {
make_quote_map(is_keyword_or_contain_uppercase)(rng, s)
}
pub fn merge_two_word_map_fn<R: Rng>(
f1: impl Fn(&mut R, String) -> String,
f2: impl Fn(&mut R, String) -> String,
) -> impl Fn(&mut R, String) -> String {
move |rng, s| -> String {
let s = f1(rng, s);
f2(rng, s)
}
}
impl<R: Rng> MapWordGenerator<R> {
pub fn new(map: WordMapFn<R>) -> Self {
Self {
base: WordGenerator,
map,
}
}
}
impl<R: Rng> Random<String, R> for MapWordGenerator<R> {
fn choose(&self, rng: &mut R, amount: usize) -> Vec<String> {
self.base
.choose(rng, amount)
.into_iter()
.map(|s| (self.map)(rng, s))
.collect()
}
}

View File

@@ -17,16 +17,46 @@ pub mod create_expr;
use std::fmt;
use datatypes::data_type::ConcreteDataType;
use rand::Rng;
use crate::error::Error;
use crate::ir::create_expr::ColumnOption;
use crate::ir::{AlterTableExpr, CreateTableExpr};
pub type CreateTableExprGenerator =
Box<dyn Generator<CreateTableExpr, Error = Error> + Sync + Send>;
pub type CreateTableExprGenerator<R> =
Box<dyn Generator<CreateTableExpr, R, Error = Error> + Sync + Send>;
pub type AlterTableExprGenerator = Box<dyn Generator<AlterTableExpr, Error = Error> + Sync + Send>;
pub type AlterTableExprGenerator<R> =
Box<dyn Generator<AlterTableExpr, R, Error = Error> + Sync + Send>;
pub(crate) trait Generator<T> {
pub type ColumnOptionGenerator<R> = Box<dyn Fn(&mut R, &ConcreteDataType) -> Vec<ColumnOption>>;
pub type ConcreteDataTypeGenerator<R> = Box<dyn Random<ConcreteDataType, R>>;
pub trait Generator<T, R: Rng> {
type Error: Sync + Send + fmt::Debug;
fn generate(&self) -> Result<T, Self::Error>;
fn generate(&self, rng: &mut R) -> Result<T, Self::Error>;
}
pub trait Random<T, R: Rng> {
/// Generates a random element.
fn gen(&self, rng: &mut R) -> T {
self.choose(rng, 1).remove(0)
}
/// Uniformly sample `amount` distinct elements.
fn choose(&self, rng: &mut R, amount: usize) -> Vec<T>;
}
#[macro_export]
macro_rules! impl_random {
($type: ident, $value:ident, $values: ident) => {
impl<R: Rng> Random<$type, R> for $value {
fn choose(&self, rng: &mut R, amount: usize) -> Vec<$type> {
$values.choose_multiple(rng, amount).cloned().collect()
}
}
};
}

View File

@@ -12,47 +12,41 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::f64::consts::E;
use std::sync::Arc;
use std::marker::PhantomData;
use common_query::AddColumnLocation;
use faker_rand::lorem::Word;
use rand::{random, Rng};
use derive_builder::Builder;
use rand::Rng;
use snafu::ensure;
use crate::context::TableContextRef;
use crate::error::{self, Error, Result};
use crate::generator::Generator;
use crate::fake::WordGenerator;
use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Generator, Random};
use crate::ir::alter_expr::{AlterTableExpr, AlterTableOperation};
use crate::ir::{droppable_columns, Column};
use crate::ir::{
column_options_generator, droppable_columns, generate_columns, ColumnTypeGenerator,
};
/// Generates the [AlterTableOperation::AddColumn] of [AlterTableExpr].
pub struct AlterExprAddColumnGenerator {
#[derive(Builder)]
#[builder(pattern = "owned")]
pub struct AlterExprAddColumnGenerator<R: Rng + 'static> {
table_ctx: TableContextRef,
#[builder(default)]
location: bool,
#[builder(default = "Box::new(WordGenerator)")]
name_generator: Box<dyn Random<String, R>>,
#[builder(default = "Box::new(column_options_generator)")]
column_options_generator: ColumnOptionGenerator<R>,
#[builder(default = "Box::new(ColumnTypeGenerator)")]
column_type_generator: ConcreteDataTypeGenerator<R>,
}
impl AlterExprAddColumnGenerator {
/// Returns an [AlterExprAddColumnGenerator].
pub fn new(table_ctx: &TableContextRef) -> Self {
Self {
table_ctx: table_ctx.clone(),
location: false,
}
}
/// Sets true to generate alter expr with a specific location.
pub fn with_location(mut self, v: bool) -> Self {
self.location = v;
self
}
}
impl Generator<AlterTableExpr> for AlterExprAddColumnGenerator {
impl<R: Rng + 'static> Generator<AlterTableExpr, R> for AlterExprAddColumnGenerator<R> {
type Error = Error;
fn generate(&self) -> Result<AlterTableExpr> {
let mut rng = rand::thread_rng();
fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
let with_location = self.location && rng.gen::<bool>();
let location = if with_location {
let use_first = rng.gen::<bool>();
@@ -71,7 +65,14 @@ impl Generator<AlterTableExpr> for AlterExprAddColumnGenerator {
None
};
let column = rng.gen::<Column>();
let name = self.name_generator.gen(rng);
let column = generate_columns(
rng,
vec![name],
self.column_type_generator.as_ref(),
self.column_options_generator.as_ref(),
)
.remove(0);
Ok(AlterTableExpr {
name: self.table_ctx.name.to_string(),
alter_options: AlterTableOperation::AddColumn { column, location },
@@ -80,24 +81,18 @@ impl Generator<AlterTableExpr> for AlterExprAddColumnGenerator {
}
/// Generates the [AlterTableOperation::DropColumn] of [AlterTableExpr].
pub struct AlterExprDropColumnGenerator {
#[derive(Builder)]
#[builder(pattern = "owned")]
pub struct AlterExprDropColumnGenerator<R> {
table_ctx: TableContextRef,
#[builder(default)]
_phantom: PhantomData<R>,
}
impl AlterExprDropColumnGenerator {
/// Returns an [AlterExprDropColumnGenerator].
pub fn new(table_ctx: &TableContextRef) -> Self {
Self {
table_ctx: table_ctx.clone(),
}
}
}
impl Generator<AlterTableExpr> for AlterExprDropColumnGenerator {
impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprDropColumnGenerator<R> {
type Error = Error;
fn generate(&self) -> Result<AlterTableExpr> {
let mut rng = rand::thread_rng();
fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
let droppable = droppable_columns(&self.table_ctx.columns);
ensure!(!droppable.is_empty(), error::DroppableColumnsSnafu);
let name = droppable[rng.gen_range(0..droppable.len())]
@@ -110,25 +105,20 @@ impl Generator<AlterTableExpr> for AlterExprDropColumnGenerator {
}
}
pub struct AlterExprRenameGenerator {
/// Generates the [AlterTableOperation::RenameTable] of [AlterTableExpr].
#[derive(Builder)]
#[builder(pattern = "owned")]
pub struct AlterExprRenameGenerator<R: Rng> {
table_ctx: TableContextRef,
#[builder(default = "Box::new(WordGenerator)")]
name_generator: Box<dyn Random<String, R>>,
}
impl AlterExprRenameGenerator {
/// Returns an [AlterExprRenameGenerator].
pub fn new(table_ctx: &TableContextRef) -> Self {
Self {
table_ctx: table_ctx.clone(),
}
}
}
impl Generator<AlterTableExpr> for AlterExprRenameGenerator {
impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprRenameGenerator<R> {
type Error = Error;
fn generate(&self) -> Result<AlterTableExpr> {
let mut rng = rand::thread_rng();
let mut new_table_name = rng.gen::<Word>().to_string();
fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
let new_table_name = self.name_generator.gen(rng);
Ok(AlterTableExpr {
name: self.table_ctx.name.to_string(),
alter_options: AlterTableOperation::RenameTable { new_table_name },
@@ -140,29 +130,53 @@ impl Generator<AlterTableExpr> for AlterExprRenameGenerator {
mod tests {
use std::sync::Arc;
use rand::SeedableRng;
use super::*;
use crate::context::TableContext;
use crate::generator::create_expr::CreateTableExprGenerator;
use crate::generator::create_expr::CreateTableExprGeneratorBuilder;
use crate::generator::Generator;
#[test]
fn test_alter_table_expr_generator() {
let create_expr = CreateTableExprGenerator::default()
fn test_alter_table_expr_generator_deterministic() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let create_expr = CreateTableExprGeneratorBuilder::default()
.columns(10)
.generate()
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let table_ctx = Arc::new(TableContext::from(&create_expr));
let alter_expr = AlterExprAddColumnGenerator::new(&table_ctx)
.generate()
let expr = AlterExprAddColumnGeneratorBuilder::default()
.table_ctx(table_ctx.clone())
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let serialized = serde_json::to_string(&expr).unwrap();
let expected = r#"{"name":"DigNissIMOS","alter_options":{"AddColumn":{"column":{"name":"sit","column_type":{"Boolean":null},"options":["PrimaryKey"]},"location":null}}}"#;
assert_eq!(expected, serialized);
let alter_expr = AlterExprRenameGenerator::new(&table_ctx)
.generate()
let expr = AlterExprRenameGeneratorBuilder::default()
.table_ctx(table_ctx.clone())
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let serialized = serde_json::to_string(&expr).unwrap();
let expected = r#"{"name":"DigNissIMOS","alter_options":{"RenameTable":{"new_table_name":"excepturi"}}}"#;
assert_eq!(expected, serialized);
let alter_expr = AlterExprDropColumnGenerator::new(&table_ctx)
.generate()
let expr = AlterExprDropColumnGeneratorBuilder::default()
.table_ctx(table_ctx)
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let serialized = serde_json::to_string(&expr).unwrap();
let expected =
r#"{"name":"DigNissIMOS","alter_options":{"DropColumn":{"name":"INVentORE"}}}"#;
assert_eq!(expected, serialized);
}
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use faker_rand::lorem::Word;
use derive_builder::Builder;
use partition::partition::{PartitionBound, PartitionDef};
use rand::seq::SliceRandom;
use rand::Rng;
@@ -20,61 +20,60 @@ use snafu::{ensure, ResultExt};
use super::Generator;
use crate::error::{self, Error, Result};
use crate::ir::create_expr::{ColumnOption, CreateTableExprBuilder};
use crate::fake::{random_capitalize_map, MapWordGenerator};
use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Random};
use crate::ir::create_expr::CreateTableExprBuilder;
use crate::ir::{
self, generate_random_value, Column, CreateTableExpr, PartibleColumn, TsColumn,
PARTIBLE_DATA_TYPES,
column_options_generator, generate_columns, generate_random_value,
partible_column_options_generator, ts_column_options_generator, ColumnTypeGenerator,
CreateTableExpr, PartibleColumnTypeGenerator, TsColumnTypeGenerator,
};
pub struct CreateTableExprGenerator {
#[derive(Builder)]
#[builder(default, pattern = "owned")]
pub struct CreateTableExprGenerator<R: Rng + 'static> {
columns: usize,
#[builder(setter(into))]
engine: String,
partition: usize,
if_not_exists: bool,
#[builder(setter(into))]
name: String,
name_generator: Box<dyn Random<String, R>>,
ts_column_type_generator: ConcreteDataTypeGenerator<R>,
column_type_generator: ConcreteDataTypeGenerator<R>,
partible_column_type_generator: ConcreteDataTypeGenerator<R>,
partible_column_options_generator: ColumnOptionGenerator<R>,
column_options_generator: ColumnOptionGenerator<R>,
ts_column_options_generator: ColumnOptionGenerator<R>,
}
const DEFAULT_ENGINE: &str = "mito";
impl Default for CreateTableExprGenerator {
impl<R: Rng + 'static> Default for CreateTableExprGenerator<R> {
fn default() -> Self {
Self {
columns: 0,
engine: DEFAULT_ENGINE.to_string(),
if_not_exists: false,
partition: 0,
name: String::new(),
name_generator: Box::new(MapWordGenerator::new(Box::new(random_capitalize_map))),
ts_column_type_generator: Box::new(TsColumnTypeGenerator),
column_type_generator: Box::new(ColumnTypeGenerator),
partible_column_type_generator: Box::new(PartibleColumnTypeGenerator),
partible_column_options_generator: Box::new(partible_column_options_generator),
column_options_generator: Box::new(column_options_generator),
ts_column_options_generator: Box::new(ts_column_options_generator),
}
}
}
impl CreateTableExprGenerator {
/// Sets column num.
pub fn columns(mut self, columns: usize) -> Self {
self.columns = columns;
self
}
/// Sets the `if_not_exists`.
pub fn create_is_not_exists(mut self, v: bool) -> Self {
self.if_not_exists = v;
self
}
/// Sets the `engine`.
pub fn engine(mut self, engine: &str) -> Self {
self.engine = engine.to_string();
self
}
/// Sets the partition num.
/// If there is no primary key column,
/// it appends a primary key atomically.
pub fn partitions(mut self, partition: usize) -> Self {
self.partition = partition;
self
}
impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreateTableExprGenerator<R> {
type Error = Error;
/// Generates the [CreateTableExpr].
pub fn generate(&self) -> Result<CreateTableExpr> {
fn generate(&self, rng: &mut R) -> Result<CreateTableExpr> {
ensure!(
self.columns != 0,
error::UnexpectedSnafu {
@@ -83,100 +82,153 @@ impl CreateTableExprGenerator {
);
let mut builder = CreateTableExprBuilder::default();
let mut columns = Vec::with_capacity(self.columns + 1);
let mut columns = Vec::with_capacity(self.columns);
let mut primary_keys = vec![];
let mut rng = rand::thread_rng();
let need_partible_column = self.partition > 1;
let mut column_names = self.name_generator.choose(rng, self.columns);
// Generates columns.
for i in 0..self.columns {
let mut column = rng.gen::<Column>();
let is_primary_key = column
.options
.iter()
.any(|option| option == &ColumnOption::PrimaryKey);
if is_primary_key {
primary_keys.push(i);
if self.columns == 1 {
// Generates the ts column.
// Safety: columns must large than 0.
let name = column_names.pop().unwrap();
let column = generate_columns(
rng,
vec![name.to_string()],
self.ts_column_type_generator.as_ref(),
self.ts_column_options_generator.as_ref(),
)
.remove(0);
if need_partible_column {
// Generates partition bounds.
let mut partition_bounds = Vec::with_capacity(self.partition);
for _ in 0..self.partition - 1 {
partition_bounds.push(PartitionBound::Value(generate_random_value(
rng,
&column.column_type,
)));
partition_bounds.sort();
}
partition_bounds.push(PartitionBound::MaxValue);
builder.partition(PartitionDef::new(vec![name], partition_bounds));
}
columns.push(column);
} else {
// Generates the partible column.
if need_partible_column {
// Safety: columns must large than 0.
let name = column_names.pop().unwrap();
let column = generate_columns(
rng,
vec![name.to_string()],
self.partible_column_type_generator.as_ref(),
self.partible_column_options_generator.as_ref(),
)
.remove(0);
// Generates partition bounds.
let mut partition_bounds = Vec::with_capacity(self.partition);
for _ in 0..self.partition - 1 {
partition_bounds.push(PartitionBound::Value(generate_random_value(
rng,
&column.column_type,
)));
partition_bounds.sort();
}
partition_bounds.push(PartitionBound::MaxValue);
builder.partition(PartitionDef::new(vec![name], partition_bounds));
columns.push(column);
}
// Generates the ts column.
// Safety: columns must large than 1.
let name = column_names.pop().unwrap();
columns.extend(generate_columns(
rng,
vec![name],
self.ts_column_type_generator.as_ref(),
self.ts_column_options_generator.as_ref(),
));
// Generates rest columns
columns.extend(generate_columns(
rng,
column_names,
self.column_type_generator.as_ref(),
self.column_options_generator.as_ref(),
));
}
for (idx, column) in columns.iter().enumerate() {
if column.is_primary_key() {
primary_keys.push(idx);
}
}
// Shuffles the primary keys.
primary_keys.shuffle(&mut rng);
let partitions = if self.partition > 1 {
// Finds a partible primary keys.
let partible_primary_keys = primary_keys
.iter()
.flat_map(|i| {
if PARTIBLE_DATA_TYPES.contains(&columns[*i].column_type) {
Some(*i)
} else {
None
}
})
.collect::<Vec<_>>();
// Generates the partitions.
if partible_primary_keys.is_empty() {
columns.push(rng.gen::<PartibleColumn>().0);
primary_keys.push(columns.len() - 1);
}
let primary_key_idx = primary_keys[rng.gen_range(0..primary_keys.len())];
let primary_column = &columns[primary_key_idx];
// Generates partition bounds.
let mut partition_bounds = Vec::with_capacity(self.partition);
for _ in 0..self.partition - 1 {
partition_bounds.push(PartitionBound::Value(generate_random_value(
&primary_column.column_type,
)));
partition_bounds.sort();
}
partition_bounds.push(PartitionBound::MaxValue);
vec![PartitionDef::new(
vec![primary_column.name.to_string()],
partition_bounds,
)]
} else {
vec![]
};
// Generates ts column.
columns.push(rng.gen::<TsColumn>().0);
primary_keys.shuffle(rng);
builder.columns(columns);
builder.primary_keys(primary_keys);
builder.engine(self.engine.to_string());
builder.if_not_exists(self.if_not_exists);
builder.name(rng.gen::<Word>().to_string());
builder.partitions(partitions);
if self.name.is_empty() {
builder.name(self.name_generator.gen(rng));
} else {
builder.name(self.name.to_string());
}
builder.build().context(error::BuildCreateTableExprSnafu)
}
}
#[cfg(test)]
mod tests {
use rand::SeedableRng;
use super::*;
#[test]
fn test_create_table_expr_generator() {
let expr = CreateTableExprGenerator::default()
let mut rng = rand::thread_rng();
let expr = CreateTableExprGeneratorBuilder::default()
.columns(10)
.partitions(3)
.create_is_not_exists(true)
.partition(3)
.if_not_exists(true)
.engine("mito2")
.generate()
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
assert_eq!(expr.engine, "mito2");
assert!(expr.if_not_exists);
assert!(expr.columns.len() >= 11);
assert_eq!(expr.partitions[0].partition_bounds().len(), 3);
assert_eq!(expr.columns.len(), 10);
assert_eq!(expr.partition.unwrap().partition_bounds().len(), 3);
let expr = CreateTableExprGenerator::default()
let expr = CreateTableExprGeneratorBuilder::default()
.columns(10)
.partitions(1)
.generate()
.partition(1)
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
assert_eq!(expr.columns.len(), 11);
assert_eq!(expr.partitions.len(), 0);
assert_eq!(expr.columns.len(), 10);
assert!(expr.partition.is_none());
}
#[test]
fn test_create_table_expr_generator_deterministic() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let expr = CreateTableExprGeneratorBuilder::default()
.columns(10)
.partition(3)
.if_not_exists(true)
.engine("mito2")
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let serialized = serde_json::to_string(&expr).unwrap();
let expected = r#"{"name":"iN","columns":[{"name":"CUlpa","column_type":{"Int16":{}},"options":["PrimaryKey","NotNull"]},{"name":"dEBiTiS","column_type":{"Timestamp":{"Second":null}},"options":["TimeIndex"]},{"name":"HArum","column_type":{"Int16":{}},"options":["NotNull"]},{"name":"NObIS","column_type":{"Int32":{}},"options":["PrimaryKey"]},{"name":"IMPEDiT","column_type":{"Int16":{}},"options":[{"DefaultValue":{"Int16":-25151}}]},{"name":"bLanDITIis","column_type":{"Boolean":null},"options":[{"DefaultValue":{"Boolean":true}}]},{"name":"Dolores","column_type":{"Float32":{}},"options":["PrimaryKey"]},{"name":"eSt","column_type":{"Float32":{}},"options":[{"DefaultValue":{"Float32":0.9152612}}]},{"name":"INVentORE","column_type":{"Int64":{}},"options":["PrimaryKey"]},{"name":"aDIpiSci","column_type":{"Float64":{}},"options":["Null"]}],"if_not_exists":true,"partition":{"partition_columns":["CUlpa"],"partition_bounds":[{"Value":{"Int16":15966}},{"Value":{"Int16":31925}},"MaxValue"]},"engine":"mito2","options":{},"primary_keys":[6,0,8,3]}"#;
assert_eq!(expected, serialized);
}
}

View File

@@ -22,15 +22,17 @@ pub use create_expr::CreateTableExpr;
use datatypes::data_type::ConcreteDataType;
use datatypes::value::Value;
use derive_builder::Builder;
use faker_rand::lorem::Word;
use lazy_static::lazy_static;
use rand::distributions::{Distribution, Standard};
use rand::seq::SliceRandom;
use rand::Rng;
use serde::{Deserialize, Serialize};
use crate::generator::Random;
use crate::impl_random;
use crate::ir::create_expr::ColumnOption;
lazy_static! {
static ref DATA_TYPES: Vec<ConcreteDataType> = vec![
pub static ref DATA_TYPES: Vec<ConcreteDataType> = vec![
ConcreteDataType::boolean_datatype(),
ConcreteDataType::int16_datatype(),
ConcreteDataType::int32_datatype(),
@@ -38,7 +40,7 @@ lazy_static! {
ConcreteDataType::float32_datatype(),
ConcreteDataType::float64_datatype(),
];
static ref TS_DATA_TYPES: Vec<ConcreteDataType> = vec![
pub static ref TS_DATA_TYPES: Vec<ConcreteDataType> = vec![
ConcreteDataType::timestamp_nanosecond_datatype(),
ConcreteDataType::timestamp_microsecond_datatype(),
ConcreteDataType::timestamp_millisecond_datatype(),
@@ -56,25 +58,37 @@ lazy_static! {
];
}
impl_random!(ConcreteDataType, ColumnTypeGenerator, DATA_TYPES);
impl_random!(ConcreteDataType, TsColumnTypeGenerator, TS_DATA_TYPES);
impl_random!(
ConcreteDataType,
PartibleColumnTypeGenerator,
PARTIBLE_DATA_TYPES
);
pub struct ColumnTypeGenerator;
pub struct TsColumnTypeGenerator;
pub struct PartibleColumnTypeGenerator;
/// Generates a random [Value].
pub fn generate_random_value(datatype: &ConcreteDataType) -> Value {
pub fn generate_random_value<R: Rng>(rng: &mut R, datatype: &ConcreteDataType) -> Value {
match datatype {
&ConcreteDataType::Boolean(_) => Value::from(rand::random::<bool>()),
ConcreteDataType::Int16(_) => Value::from(rand::random::<i16>()),
ConcreteDataType::Int32(_) => Value::from(rand::random::<i32>()),
ConcreteDataType::Int64(_) => Value::from(rand::random::<i64>()),
ConcreteDataType::Float32(_) => Value::from(rand::random::<f32>()),
ConcreteDataType::Float64(_) => Value::from(rand::random::<f64>()),
ConcreteDataType::String(_) => Value::from(rand::random::<char>().to_string()),
ConcreteDataType::Date(_) => Value::from(rand::random::<i32>()),
ConcreteDataType::DateTime(_) => Value::from(rand::random::<i64>()),
&ConcreteDataType::Boolean(_) => Value::from(rng.gen::<bool>()),
ConcreteDataType::Int16(_) => Value::from(rng.gen::<i16>()),
ConcreteDataType::Int32(_) => Value::from(rng.gen::<i32>()),
ConcreteDataType::Int64(_) => Value::from(rng.gen::<i64>()),
ConcreteDataType::Float32(_) => Value::from(rng.gen::<f32>()),
ConcreteDataType::Float64(_) => Value::from(rng.gen::<f64>()),
ConcreteDataType::String(_) => Value::from(rng.gen::<char>().to_string()),
ConcreteDataType::Date(_) => Value::from(rng.gen::<i32>()),
ConcreteDataType::DateTime(_) => Value::from(rng.gen::<i64>()),
_ => unimplemented!("unsupported type: {datatype}"),
}
}
/// The IR column.
#[derive(Debug, Builder, Clone, Serialize, Deserialize)]
#[derive(Debug, Builder, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Column {
#[builder(setter(into))]
pub name: String,
@@ -83,6 +97,22 @@ pub struct Column {
pub options: Vec<ColumnOption>,
}
impl Column {
/// Returns true if it's [ColumnOption::TimeIndex] [Column].
pub fn is_time_index(&self) -> bool {
self.options
.iter()
.any(|opt| opt == &ColumnOption::TimeIndex)
}
/// Returns true if it's the [ColumnOption::PrimaryKey] [Column].
pub fn is_primary_key(&self) -> bool {
self.options
.iter()
.any(|opt| opt == &ColumnOption::PrimaryKey)
}
}
/// Returns droppable columns. i.e., non-primary key columns, non-ts columns.
pub fn droppable_columns(columns: &[Column]) -> Vec<&Column> {
columns
@@ -95,59 +125,78 @@ pub fn droppable_columns(columns: &[Column]) -> Vec<&Column> {
.collect::<Vec<_>>()
}
impl Distribution<Column> for Standard {
fn sample<R: rand::prelude::Rng + ?Sized>(&self, rng: &mut R) -> Column {
let column_type = DATA_TYPES[rng.gen_range(0..DATA_TYPES.len())].clone();
// 0 -> NULL
// 1 -> NOT NULL
// 2 -> DEFAULT VALUE
// 3 -> PRIMARY KEY
// 4 -> EMPTY
let option_idx = rng.gen_range(0..5);
let options = match option_idx {
0 => vec![ColumnOption::Null],
1 => vec![ColumnOption::NotNull],
2 => vec![ColumnOption::DefaultValue(generate_random_value(
&column_type,
))],
3 => vec![ColumnOption::PrimaryKey],
_ => vec![],
};
Column {
name: rng.gen::<Word>().to_string(),
/// Generates [ColumnOption] for [Column].
pub fn column_options_generator<R: Rng>(
rng: &mut R,
column_type: &ConcreteDataType,
) -> Vec<ColumnOption> {
// 0 -> NULL
// 1 -> NOT NULL
// 2 -> DEFAULT VALUE
// 3 -> PRIMARY KEY
// 4 -> EMPTY
let option_idx = rng.gen_range(0..5);
match option_idx {
0 => vec![ColumnOption::Null],
1 => vec![ColumnOption::NotNull],
2 => vec![ColumnOption::DefaultValue(generate_random_value(
rng,
column_type,
options,
}
))],
3 => vec![ColumnOption::PrimaryKey],
_ => vec![],
}
}
/// The IR ts column.
pub struct TsColumn(pub Column);
impl Distribution<TsColumn> for Standard {
fn sample<R: rand::prelude::Rng + ?Sized>(&self, rng: &mut R) -> TsColumn {
let column_type = TS_DATA_TYPES[rng.gen_range(0..TS_DATA_TYPES.len())].clone();
TsColumn(Column {
name: rng.gen::<Word>().to_string(),
column_type,
options: vec![],
})
/// Generates [ColumnOption] for Partible [Column].
pub fn partible_column_options_generator<R: Rng + 'static>(
rng: &mut R,
column_type: &ConcreteDataType,
) -> Vec<ColumnOption> {
// 0 -> NULL
// 1 -> NOT NULL
// 2 -> DEFAULT VALUE
// 3 -> PRIMARY KEY
let option_idx = rng.gen_range(0..4);
match option_idx {
0 => vec![ColumnOption::PrimaryKey, ColumnOption::Null],
1 => vec![ColumnOption::PrimaryKey, ColumnOption::NotNull],
2 => vec![
ColumnOption::PrimaryKey,
ColumnOption::DefaultValue(generate_random_value(rng, column_type)),
],
3 => vec![ColumnOption::PrimaryKey],
_ => unreachable!(),
}
}
/// The IR partible column.
pub struct PartibleColumn(pub Column);
/// Generates [ColumnOption] for ts [Column].
pub fn ts_column_options_generator<R: Rng + 'static>(
_: &mut R,
_: &ConcreteDataType,
) -> Vec<ColumnOption> {
vec![ColumnOption::TimeIndex]
}
impl Distribution<PartibleColumn> for Standard {
fn sample<R: rand::prelude::Rng + ?Sized>(&self, rng: &mut R) -> PartibleColumn {
let column_type = PARTIBLE_DATA_TYPES[rng.gen_range(0..PARTIBLE_DATA_TYPES.len())].clone();
PartibleColumn(Column {
name: rng.gen::<Word>().to_string(),
column_type,
options: vec![],
/// Generates columns with given `names`.
pub fn generate_columns<R: Rng + 'static>(
rng: &mut R,
names: impl IntoIterator<Item = String>,
types: &(impl Random<ConcreteDataType, R> + ?Sized),
options: impl Fn(&mut R, &ConcreteDataType) -> Vec<ColumnOption>,
) -> Vec<Column> {
names
.into_iter()
.map(|name| {
let column_type = types.gen(rng);
let options = options(rng, &column_type);
Column {
name,
options,
column_type,
}
})
}
.collect()
}
#[cfg(test)]

View File

@@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize};
use crate::ir::Column;
#[derive(Debug, Builder, Clone)]
#[derive(Debug, Builder, Clone, Serialize, Deserialize)]
pub struct AlterTableExpr {
pub name: String,
pub alter_options: AlterTableOperation,

View File

@@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
use crate::ir::Column;
// The column options
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum ColumnOption {
Null,
NotNull,
@@ -49,6 +49,7 @@ impl Display for ColumnOption {
/// A naive create table expr builder.
#[derive(Debug, Builder, Clone, Serialize, Deserialize)]
pub struct CreateTableExpr {
#[builder(setter(into))]
pub name: String,
pub columns: Vec<Column>,
#[builder(default)]
@@ -56,7 +57,8 @@ pub struct CreateTableExpr {
// GreptimeDB specific options
#[builder(default, setter(into))]
pub partitions: Vec<PartitionDef>,
pub partition: Option<PartitionDef>,
#[builder(default, setter(into))]
pub engine: String,
#[builder(default, setter(into))]
pub options: HashMap<String, Value>,

View File

@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub(crate) mod context;
pub(crate) mod error;
pub(crate) mod executor;
// TODO(weny): removes it.
#[allow(unused)]
pub(crate) mod generator;
pub(crate) mod ir;
pub(crate) mod table_creator;
pub(crate) mod translator;
#![feature(associated_type_bounds)]
pub mod context;
pub mod error;
pub mod executor;
pub mod fake;
pub mod generator;
pub mod ir;
pub mod translator;

View File

@@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod greptime;
use std::fmt;
pub(crate) trait DslTranslator<T, U> {
pub trait DslTranslator<T, U> {
type Error: Sync + Send + fmt::Debug;
fn translate(&self, input: &T) -> Result<U, Self::Error>;

View File

@@ -11,3 +11,6 @@
// 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.
pub mod alter_expr;
pub mod create_expr;

View File

@@ -0,0 +1,159 @@
// 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 common_query::AddColumnLocation;
use datatypes::data_type::ConcreteDataType;
use sql::statements::concrete_data_type_to_sql_data_type;
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::DslTranslator;
pub struct AlterTableExprTranslator;
impl DslTranslator<AlterTableExpr, String> for AlterTableExprTranslator {
type Error = Error;
fn translate(&self, input: &AlterTableExpr) -> Result<String> {
Ok(match &input.alter_options {
AlterTableOperation::AddColumn { column, location } => {
Self::format_add_column(&input.name, column, location)
}
AlterTableOperation::DropColumn { name } => Self::format_drop(&input.name, name),
AlterTableOperation::RenameTable { new_table_name } => {
Self::format_rename(&input.name, new_table_name)
}
})
}
}
impl AlterTableExprTranslator {
fn format_drop(name: &str, column: &str) -> String {
format!("ALTER TABLE {name} DROP COLUMN {column};")
}
fn format_rename(name: &str, new_name: &str) -> String {
format!("ALTER TABLE {name} RENAME {new_name};")
}
fn format_add_column(
name: &str,
column: &Column,
location: &Option<AddColumnLocation>,
) -> String {
format!(
"{};",
vec![
format!(
"ALTER TABLE {name} ADD COLUMN {}",
Self::format_column(column)
),
Self::format_location(location).unwrap_or_default(),
]
.into_iter()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
)
}
fn format_location(location: &Option<AddColumnLocation>) -> Option<String> {
location.as_ref().map(|location| match location {
AddColumnLocation::First => "FIRST".to_string(),
AddColumnLocation::After { column_name } => format!("AFTER {column_name}"),
})
}
fn format_column(column: &Column) -> String {
vec![
column.name.to_string(),
Self::format_column_type(&column.column_type),
Self::format_column_options(&column.options),
]
.into_iter()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
}
fn format_column_type(column_type: &ConcreteDataType) -> String {
// Safety: We don't use the `Dictionary` type
concrete_data_type_to_sql_data_type(column_type)
.unwrap()
.to_string()
}
fn format_column_options(options: &[ColumnOption]) -> String {
options
.iter()
.map(|option| option.to_string())
.collect::<Vec<_>>()
.join(" ")
}
}
#[cfg(test)]
mod tests {
use common_query::AddColumnLocation;
use datatypes::data_type::ConcreteDataType;
use super::AlterTableExprTranslator;
use crate::ir::alter_expr::AlterTableOperation;
use crate::ir::create_expr::ColumnOption;
use crate::ir::{AlterTableExpr, Column};
use crate::translator::DslTranslator;
#[test]
fn test_alter_table_expr() {
let alter_expr = AlterTableExpr {
name: "test".to_string(),
alter_options: AlterTableOperation::AddColumn {
column: Column {
name: "host".to_string(),
column_type: ConcreteDataType::string_datatype(),
options: vec![ColumnOption::PrimaryKey],
},
location: Some(AddColumnLocation::First),
},
};
let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
assert_eq!(
"ALTER TABLE test ADD COLUMN host STRING PRIMARY KEY FIRST;",
output
);
let alter_expr = AlterTableExpr {
name: "test".to_string(),
alter_options: AlterTableOperation::RenameTable {
new_table_name: "foo".to_string(),
},
};
let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
assert_eq!("ALTER TABLE test RENAME foo;", output);
let alter_expr = AlterTableExpr {
name: "test".to_string(),
alter_options: AlterTableOperation::DropColumn {
name: "foo".to_string(),
},
};
let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
assert_eq!("ALTER TABLE test DROP COLUMN foo;", output);
}
}

View File

@@ -0,0 +1,233 @@
// 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 datatypes::data_type::ConcreteDataType;
use datatypes::value::Value;
use partition::partition::PartitionBound;
use sql::statements::concrete_data_type_to_sql_data_type;
use crate::error::{Error, Result};
use crate::ir::create_expr::ColumnOption;
use crate::ir::{Column, CreateTableExpr};
use crate::translator::DslTranslator;
pub struct CreateTableExprTranslator;
impl DslTranslator<CreateTableExpr, String> for CreateTableExprTranslator {
type Error = Error;
fn translate(&self, input: &CreateTableExpr) -> Result<String> {
Ok(format!(
"CREATE TABLE{}{}(\n{}\n)\n{};",
Self::create_if_not_exists(input),
input.name,
Self::format_columns(input),
Self::format_table_options(input)
))
}
}
impl CreateTableExprTranslator {
fn create_if_not_exists(input: &CreateTableExpr) -> &str {
if input.if_not_exists {
" IF NOT EXISTS "
} else {
" "
}
}
fn format_columns(input: &CreateTableExpr) -> String {
let mut output =
Vec::with_capacity(input.columns.len() + (!input.primary_keys.is_empty()) as usize);
for column in &input.columns {
output.push(Self::format_column(column));
}
if let Some(primary_keys) = Self::format_primary_keys(input) {
output.push(primary_keys);
}
output.join(",\n")
}
fn format_column(column: &Column) -> String {
vec![
column.name.to_string(),
Self::format_column_type(&column.column_type),
Self::format_column_options(&column.options),
]
.into_iter()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
}
fn format_partition(input: &CreateTableExpr) -> Option<String> {
input.partition.as_ref().map(|partition| {
format!(
"PARTITION BY RANGE COLUMNS({}) (\n{}\n)",
partition.partition_columns().join(", "),
partition
.partition_bounds()
.iter()
.enumerate()
.map(|(i, bound)| format!(
"PARTITION r{} VALUES LESS THAN ({})",
i,
Self::format_partition_bound(bound)
))
.collect::<Vec<_>>()
.join(",\n")
)
})
}
fn format_partition_bound(bound: &PartitionBound) -> String {
match bound {
PartitionBound::Value(v) => match v {
Value::String(v) => format!("'{}'", v.as_utf8()),
_ => format!("{v}"),
},
PartitionBound::MaxValue => "MAXVALUE".to_string(),
}
}
fn format_column_type(column_type: &ConcreteDataType) -> String {
// Safety: We don't use the `Dictionary` type
concrete_data_type_to_sql_data_type(column_type)
.unwrap()
.to_string()
}
fn format_column_options(options: &[ColumnOption]) -> String {
let mut output = Vec::with_capacity(options.len());
for option in options {
if option != &ColumnOption::PrimaryKey {
output.push(option.to_string());
}
}
output.join(" ")
}
fn format_primary_keys(input: &CreateTableExpr) -> Option<String> {
if input.primary_keys.is_empty() {
None
} else {
Some(format!(
"PRIMARY KEY({})",
input
.primary_keys
.iter()
.map(|idx| input.columns[*idx].name.to_string())
.collect::<Vec<_>>()
.join(", ")
))
}
}
fn format_table_options(input: &CreateTableExpr) -> String {
let mut output = vec![];
if !input.engine.is_empty() {
output.push(format!("ENGINE={}", input.engine));
}
if let Some(partition) = Self::format_partition(input) {
output.push(partition);
}
output.join("\n")
}
}
#[cfg(test)]
mod tests {
use datatypes::data_type::ConcreteDataType;
use datatypes::value::Value;
use partition::partition::{PartitionBound, PartitionDef};
use super::CreateTableExprTranslator;
use crate::ir::create_expr::{ColumnOption, CreateTableExprBuilder};
use crate::ir::Column;
use crate::translator::DslTranslator;
#[test]
fn test_create_table_expr_translator() {
let create_table_expr = CreateTableExprBuilder::default()
.columns(vec![
Column {
name: "host".to_string(),
column_type: ConcreteDataType::string_datatype(),
options: vec![ColumnOption::PrimaryKey],
},
Column {
name: "idc".to_string(),
column_type: ConcreteDataType::string_datatype(),
options: vec![ColumnOption::PrimaryKey],
},
Column {
name: "cpu_util".to_string(),
column_type: ConcreteDataType::float64_datatype(),
options: vec![],
},
Column {
name: "memory_util".to_string(),
column_type: ConcreteDataType::float64_datatype(),
options: vec![],
},
Column {
name: "disk_util".to_string(),
column_type: ConcreteDataType::float64_datatype(),
options: vec![],
},
Column {
name: "ts".to_string(),
column_type: ConcreteDataType::timestamp_millisecond_datatype(),
options: vec![ColumnOption::TimeIndex],
},
])
.name("system_metrics")
.engine("mito")
.primary_keys(vec![0, 1])
.partition(PartitionDef::new(
vec!["idc".to_string()],
vec![
PartitionBound::Value(Value::String("a".into())),
PartitionBound::Value(Value::String("f".into())),
PartitionBound::MaxValue,
],
))
.build()
.unwrap();
let output = CreateTableExprTranslator
.translate(&create_table_expr)
.unwrap();
assert_eq!(
"CREATE TABLE system_metrics(
host STRING,
idc STRING,
cpu_util DOUBLE,
memory_util DOUBLE,
disk_util DOUBLE,
ts TIMESTAMP(3) TIME INDEX,
PRIMARY KEY(host, idc)
)
ENGINE=mito
PARTITION BY RANGE COLUMNS(idc) (
PARTITION r0 VALUES LESS THAN ('a'),
PARTITION r1 VALUES LESS THAN ('f'),
PARTITION r2 VALUES LESS THAN (MAXVALUE)
);",
output
);
}
}

View File

@@ -2,4 +2,4 @@
ue = "ue"
datas = "datas"
[files]
extend-exclude = ["corrupted"]
extend-exclude = ["corrupted","tests-fuzz/src/data/lorem_words"]