tests_fuzz/generator/
alter_expr.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::marker::PhantomData;
16
17use common_base::readable_size::ReadableSize;
18use common_query::AddColumnLocation;
19use datatypes::data_type::ConcreteDataType;
20use derive_builder::Builder;
21use rand::Rng;
22use snafu::ensure;
23use strum::IntoEnumIterator;
24
25use crate::context::TableContextRef;
26use crate::error::{self, Error, Result};
27use crate::fake::WordGenerator;
28use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Generator, Random};
29use crate::ir::alter_expr::{AlterTableExpr, AlterTableOperation, AlterTableOption, Ttl};
30use crate::ir::create_expr::ColumnOption;
31use crate::ir::{
32    Column, ColumnTypeGenerator, Ident, droppable_columns, generate_columns, generate_random_value,
33    modifiable_columns,
34};
35
36fn add_column_options_generator<R: Rng>(
37    rng: &mut R,
38    column_type: &ConcreteDataType,
39) -> Vec<ColumnOption> {
40    // 0 -> NULL
41    // 1 -> DEFAULT VALUE
42    // 2 -> PRIMARY KEY + DEFAULT VALUE
43    let idx = rng.random_range(0..3);
44    match idx {
45        0 => vec![ColumnOption::Null],
46        1 => {
47            vec![ColumnOption::DefaultValue(generate_random_value(
48                rng,
49                column_type,
50                None,
51            ))]
52        }
53        2 => {
54            vec![
55                ColumnOption::PrimaryKey,
56                ColumnOption::DefaultValue(generate_random_value(rng, column_type, None)),
57            ]
58        }
59        _ => unreachable!(),
60    }
61}
62
63/// Generates the [AlterTableOperation::AddColumn] of [AlterTableExpr].
64#[derive(Builder)]
65#[builder(pattern = "owned")]
66pub struct AlterExprAddColumnGenerator<R: Rng + 'static> {
67    table_ctx: TableContextRef,
68    #[builder(default)]
69    location: bool,
70    #[builder(default = "Box::new(WordGenerator)")]
71    name_generator: Box<dyn Random<Ident, R>>,
72    #[builder(default = "Box::new(add_column_options_generator)")]
73    column_options_generator: ColumnOptionGenerator<R>,
74    #[builder(default = "Box::new(ColumnTypeGenerator)")]
75    column_type_generator: ConcreteDataTypeGenerator<R>,
76}
77
78impl<R: Rng + 'static> Generator<AlterTableExpr, R> for AlterExprAddColumnGenerator<R> {
79    type Error = Error;
80
81    fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
82        let with_location = self.location && rng.random::<bool>();
83        let location = if with_location {
84            let use_first = rng.random::<bool>();
85            let location = if use_first {
86                AddColumnLocation::First
87            } else {
88                AddColumnLocation::After {
89                    column_name: self.table_ctx.columns
90                        [rng.random_range(0..self.table_ctx.columns.len())]
91                    .name
92                    .to_string(),
93                }
94            };
95            Some(location)
96        } else {
97            None
98        };
99
100        let name = self
101            .table_ctx
102            .generate_unique_column_name(rng, self.name_generator.as_ref());
103        let column = generate_columns(
104            rng,
105            vec![name],
106            self.column_type_generator.as_ref(),
107            self.column_options_generator.as_ref(),
108        )
109        .remove(0);
110        Ok(AlterTableExpr {
111            table_name: self.table_ctx.name.clone(),
112            alter_kinds: AlterTableOperation::AddColumn { column, location },
113        })
114    }
115}
116
117/// Generates the [AlterTableOperation::DropColumn] of [AlterTableExpr].
118#[derive(Builder)]
119#[builder(pattern = "owned")]
120pub struct AlterExprDropColumnGenerator<R> {
121    table_ctx: TableContextRef,
122    #[builder(default)]
123    _phantom: PhantomData<R>,
124}
125
126impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprDropColumnGenerator<R> {
127    type Error = Error;
128
129    fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
130        let droppable = droppable_columns(&self.table_ctx.columns);
131        ensure!(!droppable.is_empty(), error::DroppableColumnsSnafu);
132        let name = droppable[rng.random_range(0..droppable.len())].name.clone();
133        Ok(AlterTableExpr {
134            table_name: self.table_ctx.name.clone(),
135            alter_kinds: AlterTableOperation::DropColumn { name },
136        })
137    }
138}
139
140/// Generates the [AlterTableOperation::RenameTable] of [AlterTableExpr].
141#[derive(Builder)]
142#[builder(pattern = "owned")]
143pub struct AlterExprRenameGenerator<R: Rng> {
144    table_ctx: TableContextRef,
145    #[builder(default = "Box::new(WordGenerator)")]
146    name_generator: Box<dyn Random<Ident, R>>,
147}
148
149impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprRenameGenerator<R> {
150    type Error = Error;
151
152    fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
153        let new_table_name = self
154            .table_ctx
155            .generate_unique_table_name(rng, self.name_generator.as_ref());
156        Ok(AlterTableExpr {
157            table_name: self.table_ctx.name.clone(),
158            alter_kinds: AlterTableOperation::RenameTable { new_table_name },
159        })
160    }
161}
162
163/// Generates the [AlterTableOperation::ModifyDataType] of [AlterTableExpr].
164#[derive(Builder)]
165#[builder(pattern = "owned")]
166pub struct AlterExprModifyDataTypeGenerator<R: Rng> {
167    table_ctx: TableContextRef,
168    #[builder(default = "Box::new(ColumnTypeGenerator)")]
169    column_type_generator: ConcreteDataTypeGenerator<R>,
170}
171
172impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprModifyDataTypeGenerator<R> {
173    type Error = Error;
174
175    fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
176        let modifiable = modifiable_columns(&self.table_ctx.columns);
177        let changed = modifiable[rng.random_range(0..modifiable.len())].clone();
178        let mut to_type = self.column_type_generator.generate(rng);
179        while !changed.column_type.can_arrow_type_cast_to(&to_type) {
180            to_type = self.column_type_generator.generate(rng);
181        }
182
183        Ok(AlterTableExpr {
184            table_name: self.table_ctx.name.clone(),
185            alter_kinds: AlterTableOperation::ModifyDataType {
186                column: Column {
187                    name: changed.name,
188                    column_type: to_type,
189                    options: vec![],
190                },
191            },
192        })
193    }
194}
195
196/// Generates the [AlterTableOperation::SetTableOptions] of [AlterTableExpr].
197#[derive(Builder)]
198#[builder(pattern = "owned")]
199pub struct AlterExprSetTableOptionsGenerator<R: Rng> {
200    table_ctx: TableContextRef,
201    #[builder(default)]
202    _phantom: PhantomData<R>,
203}
204
205impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprSetTableOptionsGenerator<R> {
206    type Error = Error;
207
208    fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
209        let all_options = AlterTableOption::iter().collect::<Vec<_>>();
210        // Generate random distinct options
211        let mut option_templates_idx = vec![];
212        for _ in 1..rng.random_range(2..=all_options.len()) {
213            let option = rng.random_range(0..all_options.len());
214            if !option_templates_idx.contains(&option) {
215                option_templates_idx.push(option);
216            }
217        }
218        let options = option_templates_idx
219            .iter()
220            .map(|idx| match all_options[*idx] {
221                AlterTableOption::Ttl(_) => {
222                    // The database purges expired files in background so it's hard to check
223                    // non-forever TTL.
224                    AlterTableOption::Ttl(Ttl::Forever)
225                }
226                AlterTableOption::TwcsTimeWindow(_) => {
227                    let time_window: u32 = rng.random();
228                    AlterTableOption::TwcsTimeWindow((time_window as i64).into())
229                }
230                AlterTableOption::TwcsMaxOutputFileSize(_) => {
231                    let max_output_file_size: u64 = rng.random();
232                    AlterTableOption::TwcsMaxOutputFileSize(ReadableSize(max_output_file_size))
233                }
234                AlterTableOption::TwcsTriggerFileNum(_) => {
235                    let trigger_file_num: u64 = rng.random();
236                    AlterTableOption::TwcsTriggerFileNum(trigger_file_num)
237                }
238            })
239            .collect();
240        Ok(AlterTableExpr {
241            table_name: self.table_ctx.name.clone(),
242            alter_kinds: AlterTableOperation::SetTableOptions { options },
243        })
244    }
245}
246
247/// Generates the [AlterTableOperation::UnsetTableOptions] of [AlterTableExpr].
248#[derive(Builder)]
249#[builder(pattern = "owned")]
250pub struct AlterExprUnsetTableOptionsGenerator<R: Rng> {
251    table_ctx: TableContextRef,
252    #[builder(default)]
253    _phantom: PhantomData<R>,
254}
255
256impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprUnsetTableOptionsGenerator<R> {
257    type Error = Error;
258
259    fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
260        let all_options = AlterTableOption::iter().collect::<Vec<_>>();
261        // Generate random distinct options
262        let mut option_templates_idx = vec![];
263        for _ in 1..rng.random_range(2..=all_options.len()) {
264            let option = rng.random_range(0..all_options.len());
265            if !option_templates_idx.contains(&option) {
266                option_templates_idx.push(option);
267            }
268        }
269        let options = option_templates_idx
270            .iter()
271            .map(|idx| all_options[*idx].key().to_string())
272            .collect();
273        Ok(AlterTableExpr {
274            table_name: self.table_ctx.name.clone(),
275            alter_kinds: AlterTableOperation::UnsetTableOptions { keys: options },
276        })
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use std::sync::Arc;
283
284    use rand::SeedableRng;
285
286    use super::*;
287    use crate::context::TableContext;
288    use crate::generator::Generator;
289    use crate::generator::create_expr::CreateTableExprGeneratorBuilder;
290
291    #[test]
292    fn test_alter_table_expr_generator_deterministic() {
293        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
294        let create_expr = CreateTableExprGeneratorBuilder::default()
295            .columns(10)
296            .build()
297            .unwrap()
298            .generate(&mut rng)
299            .unwrap();
300        let table_ctx = Arc::new(TableContext::from(&create_expr));
301
302        let expr = AlterExprAddColumnGeneratorBuilder::default()
303            .table_ctx(table_ctx.clone())
304            .build()
305            .unwrap()
306            .generate(&mut rng)
307            .unwrap();
308        let serialized = serde_json::to_string(&expr).unwrap();
309        let expected = r#"{"table_name":{"value":"quasi","quote_style":null},"alter_kinds":{"AddColumn":{"column":{"name":{"value":"consequatur","quote_style":null},"column_type":{"Float64":{}},"options":[{"DefaultValue":{"Float64":0.48809950435391647}}]},"location":null}}}"#;
310        assert_eq!(expected, serialized);
311
312        let expr = AlterExprRenameGeneratorBuilder::default()
313            .table_ctx(table_ctx.clone())
314            .build()
315            .unwrap()
316            .generate(&mut rng)
317            .unwrap();
318        let serialized = serde_json::to_string(&expr).unwrap();
319        let expected = r#"{"table_name":{"value":"quasi","quote_style":null},"alter_kinds":{"RenameTable":{"new_table_name":{"value":"voluptates","quote_style":null}}}}"#;
320        assert_eq!(expected, serialized);
321
322        let expr = AlterExprDropColumnGeneratorBuilder::default()
323            .table_ctx(table_ctx.clone())
324            .build()
325            .unwrap()
326            .generate(&mut rng)
327            .unwrap();
328        let serialized = serde_json::to_string(&expr).unwrap();
329        let expected = r#"{"table_name":{"value":"quasi","quote_style":null},"alter_kinds":{"DropColumn":{"name":{"value":"ImPEDiT","quote_style":null}}}}"#;
330        assert_eq!(expected, serialized);
331
332        let expr = AlterExprModifyDataTypeGeneratorBuilder::default()
333            .table_ctx(table_ctx.clone())
334            .build()
335            .unwrap()
336            .generate(&mut rng)
337            .unwrap();
338        let serialized = serde_json::to_string(&expr).unwrap();
339        let expected = r#"{"table_name":{"value":"quasi","quote_style":null},"alter_kinds":{"ModifyDataType":{"column":{"name":{"value":"ADIpisci","quote_style":null},"column_type":{"Int64":{}},"options":[]}}}}"#;
340        assert_eq!(expected, serialized);
341
342        let expr = AlterExprSetTableOptionsGeneratorBuilder::default()
343            .table_ctx(table_ctx.clone())
344            .build()
345            .unwrap()
346            .generate(&mut rng)
347            .unwrap();
348        let serialized = serde_json::to_string(&expr).unwrap();
349        let expected = r#"{"table_name":{"value":"quasi","quote_style":null},"alter_kinds":{"SetTableOptions":{"options":[{"TwcsTimeWindow":{"value":2428665013,"unit":"Millisecond"}}]}}}"#;
350        assert_eq!(expected, serialized);
351
352        let expr = AlterExprUnsetTableOptionsGeneratorBuilder::default()
353            .table_ctx(table_ctx)
354            .build()
355            .unwrap()
356            .generate(&mut rng)
357            .unwrap();
358        let serialized = serde_json::to_string(&expr).unwrap();
359        let expected = r#"{"table_name":{"value":"quasi","quote_style":null},"alter_kinds":{"UnsetTableOptions":{"keys":["compaction.twcs.trigger_file_num","compaction.twcs.time_window"]}}}"#;
360        assert_eq!(expected, serialized);
361    }
362}