1use std::collections::HashMap;
16
17use datatypes::data_type::ConcreteDataType;
18use datatypes::value::Value;
19use derive_builder::Builder;
20use partition::expr::{Operand, PartitionExpr, RestrictedOp};
21use rand::Rng;
22use rand::seq::SliceRandom;
23use snafu::{ResultExt, ensure};
24
25use super::Generator;
26use crate::context::TableContextRef;
27use crate::error::{self, Error, Result};
28use crate::fake::{MappedGenerator, WordGenerator, random_capitalize_map};
29use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Random};
30use crate::ir::create_expr::{
31 ColumnOption, CreateDatabaseExprBuilder, CreateTableExprBuilder, PartitionDef,
32};
33use crate::ir::{
34 Column, ColumnTypeGenerator, CreateDatabaseExpr, CreateTableExpr, Ident,
35 PartibleColumnTypeGenerator, StringColumnTypeGenerator, TsColumnTypeGenerator,
36 column_options_generator, generate_columns, generate_partition_bounds,
37 partible_column_options_generator, primary_key_options_generator, ts_column_options_generator,
38};
39
40#[derive(Builder)]
41#[builder(default, pattern = "owned")]
42pub struct CreateTableExprGenerator<R: Rng + 'static> {
43 columns: usize,
44 #[builder(setter(into))]
45 engine: String,
46 partition: usize,
47 if_not_exists: bool,
48 #[builder(setter(into))]
49 name: Ident,
50 #[builder(setter(into))]
51 with_clause: HashMap<String, String>,
52 name_generator: Box<dyn Random<Ident, R>>,
53 ts_column_type_generator: ConcreteDataTypeGenerator<R>,
54 column_type_generator: ConcreteDataTypeGenerator<R>,
55 partible_column_type_generator: ConcreteDataTypeGenerator<R>,
56 partible_column_options_generator: ColumnOptionGenerator<R>,
57 column_options_generator: ColumnOptionGenerator<R>,
58 ts_column_options_generator: ColumnOptionGenerator<R>,
59}
60
61const DEFAULT_ENGINE: &str = "mito";
62
63impl<R: Rng + 'static> Default for CreateTableExprGenerator<R> {
64 fn default() -> Self {
65 Self {
66 columns: 0,
67 engine: DEFAULT_ENGINE.to_string(),
68 if_not_exists: false,
69 partition: 0,
70 name: Ident::new(""),
71 with_clause: HashMap::default(),
72 name_generator: Box::new(MappedGenerator::new(WordGenerator, random_capitalize_map)),
73 ts_column_type_generator: Box::new(TsColumnTypeGenerator),
74 column_type_generator: Box::new(ColumnTypeGenerator),
75 partible_column_type_generator: Box::new(PartibleColumnTypeGenerator),
76 partible_column_options_generator: Box::new(partible_column_options_generator),
77 column_options_generator: Box::new(column_options_generator),
78 ts_column_options_generator: Box::new(ts_column_options_generator),
79 }
80 }
81}
82
83impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreateTableExprGenerator<R> {
84 type Error = Error;
85
86 fn generate(&self, rng: &mut R) -> Result<CreateTableExpr> {
88 ensure!(
89 self.columns != 0,
90 error::UnexpectedSnafu {
91 violated: "The columns must larger than zero"
92 }
93 );
94
95 let mut builder = CreateTableExprBuilder::default();
96 let mut columns = Vec::with_capacity(self.columns);
97 let mut primary_keys = vec![];
98 let need_partible_column = self.partition > 1;
99 let mut column_names = self.name_generator.choose(rng, self.columns);
100
101 if self.columns == 1 {
102 let name = column_names.pop().unwrap();
105 let column = generate_columns(
106 rng,
107 vec![name.clone()],
108 self.ts_column_type_generator.as_ref(),
109 self.ts_column_options_generator.as_ref(),
110 )
111 .remove(0);
112 columns.push(column);
113 } else {
114 if need_partible_column {
116 let name = column_names.pop().unwrap();
118 let column = generate_columns(
119 rng,
120 vec![name.clone()],
121 self.partible_column_type_generator.as_ref(),
122 self.partible_column_options_generator.as_ref(),
123 )
124 .remove(0);
125
126 let partition_def = generate_partition_def(
128 self.partition,
129 column.column_type.clone(),
130 name.clone(),
131 );
132 builder.partition(partition_def);
133 columns.push(column);
134 }
135 let name = column_names.pop().unwrap();
138 columns.extend(generate_columns(
139 rng,
140 vec![name],
141 self.ts_column_type_generator.as_ref(),
142 self.ts_column_options_generator.as_ref(),
143 ));
144 columns.extend(generate_columns(
146 rng,
147 column_names,
148 self.column_type_generator.as_ref(),
149 self.column_options_generator.as_ref(),
150 ));
151 }
152
153 for (idx, column) in columns.iter().enumerate() {
154 if column.is_primary_key() {
155 primary_keys.push(idx);
156 }
157 }
158 primary_keys.shuffle(rng);
160
161 builder.columns(columns);
162 builder.primary_keys(primary_keys);
163 builder.engine(self.engine.to_string());
164 builder.if_not_exists(self.if_not_exists);
165 if self.name.is_empty() {
166 builder.table_name(self.name_generator.generate(rng));
167 } else {
168 builder.table_name(self.name.clone());
169 }
170 if !self.with_clause.is_empty() {
171 let mut options = HashMap::new();
172 for (key, value) in &self.with_clause {
173 options.insert(key.to_string(), Value::from(value.to_string()));
174 }
175 builder.options(options);
176 }
177 builder.build().context(error::BuildCreateTableExprSnafu)
178 }
179}
180
181fn generate_partition_def(
182 partitions: usize,
183 column_type: ConcreteDataType,
184 column_name: Ident,
185) -> PartitionDef {
186 let bounds = generate_partition_bounds(&column_type, partitions - 1);
187 let mut partition_exprs = Vec::with_capacity(partitions);
188
189 let first_bound = bounds[0].clone();
190 partition_exprs.push(PartitionExpr::new(
191 Operand::Column(column_name.to_string()),
192 RestrictedOp::Lt,
193 Operand::Value(first_bound),
194 ));
195 for bound_idx in 1..bounds.len() {
196 partition_exprs.push(PartitionExpr::new(
197 Operand::Expr(PartitionExpr::new(
198 Operand::Column(column_name.to_string()),
199 RestrictedOp::GtEq,
200 Operand::Value(bounds[bound_idx - 1].clone()),
201 )),
202 RestrictedOp::And,
203 Operand::Expr(PartitionExpr::new(
204 Operand::Column(column_name.to_string()),
205 RestrictedOp::Lt,
206 Operand::Value(bounds[bound_idx].clone()),
207 )),
208 ));
209 }
210 let last_bound = bounds.last().unwrap().clone();
211 partition_exprs.push(PartitionExpr::new(
212 Operand::Column(column_name.to_string()),
213 RestrictedOp::GtEq,
214 Operand::Value(last_bound),
215 ));
216
217 PartitionDef {
218 columns: vec![column_name.to_string()],
219 exprs: partition_exprs,
220 }
221}
222
223#[derive(Builder)]
225#[builder(pattern = "owned")]
226pub struct CreatePhysicalTableExprGenerator<R: Rng + 'static> {
227 #[builder(default = "Box::new(WordGenerator)")]
228 name_generator: Box<dyn Random<Ident, R>>,
229 #[builder(default = "false")]
230 if_not_exists: bool,
231 #[builder(default, setter(into))]
232 with_clause: HashMap<String, String>,
233}
234
235impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreatePhysicalTableExprGenerator<R> {
236 type Error = Error;
237
238 fn generate(&self, rng: &mut R) -> Result<CreateTableExpr> {
239 let mut options = HashMap::with_capacity(self.with_clause.len() + 1);
240 options.insert("physical_metric_table".to_string(), Value::from(""));
241 for (key, value) in &self.with_clause {
242 options.insert(key.to_string(), Value::from(value.to_string()));
243 }
244
245 Ok(CreateTableExpr {
246 table_name: self.name_generator.generate(rng),
247 columns: vec![
248 Column {
249 name: Ident::new("ts"),
250 column_type: ConcreteDataType::timestamp_millisecond_datatype(),
251 options: vec![ColumnOption::TimeIndex],
252 },
253 Column {
254 name: Ident::new("val"),
255 column_type: ConcreteDataType::float64_datatype(),
256 options: vec![],
257 },
258 ],
259 if_not_exists: self.if_not_exists,
260 partition: None,
261 engine: "metric".to_string(),
262 options,
263 primary_keys: vec![],
264 })
265 }
266}
267
268#[derive(Builder)]
270#[builder(pattern = "owned")]
271pub struct CreateLogicalTableExprGenerator<R: Rng + 'static> {
272 physical_table_ctx: TableContextRef,
273 labels: usize,
274 if_not_exists: bool,
275 #[builder(default = "Box::new(WordGenerator)")]
276 name_generator: Box<dyn Random<Ident, R>>,
277}
278
279impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreateLogicalTableExprGenerator<R> {
280 type Error = Error;
281
282 fn generate(&self, rng: &mut R) -> Result<CreateTableExpr> {
283 ensure!(
285 self.physical_table_ctx.columns.len() == 2,
286 error::UnexpectedSnafu {
287 violated: "The physical table must have two columns"
288 }
289 );
290
291 let logical_table_name = self
293 .physical_table_ctx
294 .generate_unique_table_name(rng, self.name_generator.as_ref());
295 let mut logical_table = CreateTableExpr {
296 table_name: logical_table_name,
297 columns: self.physical_table_ctx.columns.clone(),
298 if_not_exists: self.if_not_exists,
299 partition: None,
300 engine: "metric".to_string(),
301 options: [(
302 "on_physical_table".to_string(),
303 self.physical_table_ctx.name.value.clone().into(),
304 )]
305 .into(),
306 primary_keys: vec![],
307 };
308
309 let column_names = self.name_generator.choose(rng, self.labels);
310 logical_table.columns.extend(generate_columns(
311 rng,
312 column_names,
313 &StringColumnTypeGenerator,
314 Box::new(primary_key_options_generator),
315 ));
316
317 let mut primary_keys = vec![];
320 for (idx, column) in logical_table.columns.iter().enumerate() {
321 if column.is_primary_key() {
322 primary_keys.push(idx);
323 }
324 }
325 primary_keys.shuffle(rng);
326 logical_table.primary_keys = primary_keys;
327
328 Ok(logical_table)
329 }
330}
331
332#[derive(Builder)]
333#[builder(default, pattern = "owned")]
334pub struct CreateDatabaseExprGenerator<R: Rng + 'static> {
335 #[builder(setter(into))]
336 database_name: String,
337 name_generator: Box<dyn Random<Ident, R>>,
338 if_not_exists: bool,
339}
340
341impl<R: Rng + 'static> Default for CreateDatabaseExprGenerator<R> {
342 fn default() -> Self {
343 Self {
344 database_name: String::new(),
345 name_generator: Box::new(MappedGenerator::new(WordGenerator, random_capitalize_map)),
346 if_not_exists: false,
347 }
348 }
349}
350
351impl<R: Rng + 'static> Generator<CreateDatabaseExpr, R> for CreateDatabaseExprGenerator<R> {
352 type Error = Error;
353
354 fn generate(&self, rng: &mut R) -> Result<CreateDatabaseExpr> {
355 let mut builder = CreateDatabaseExprBuilder::default();
356 builder.if_not_exists(self.if_not_exists);
357 if self.database_name.is_empty() {
358 builder.database_name(self.name_generator.generate(rng));
359 } else {
360 builder.database_name(self.database_name.to_string());
361 }
362 builder.build().context(error::BuildCreateDatabaseExprSnafu)
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use std::sync::Arc;
369
370 use datatypes::data_type::ConcreteDataType;
371 use datatypes::value::Value;
372 use rand::SeedableRng;
373
374 use super::*;
375 use crate::context::TableContext;
376
377 #[test]
378 fn test_float64() {
379 let value = Value::from(0.047318541668048164);
380 assert_eq!("0.047318541668048164", value.to_string());
381 let value: f64 = "0.047318541668048164".parse().unwrap();
382 assert_eq!("0.047318541668048164", value.to_string());
383 }
384
385 #[test]
386 fn test_create_table_expr_generator() {
387 let mut rng = rand::rng();
388
389 let expr = CreateTableExprGeneratorBuilder::default()
390 .columns(10)
391 .partition(3)
392 .if_not_exists(true)
393 .engine("mito2")
394 .build()
395 .unwrap()
396 .generate(&mut rng)
397 .unwrap();
398 assert_eq!(expr.engine, "mito2");
399 assert!(expr.if_not_exists);
400 assert_eq!(expr.columns.len(), 10);
401 assert_eq!(expr.partition.unwrap().exprs.len(), 3);
402
403 let expr = CreateTableExprGeneratorBuilder::default()
404 .columns(10)
405 .partition(1)
406 .build()
407 .unwrap()
408 .generate(&mut rng)
409 .unwrap();
410 assert_eq!(expr.columns.len(), 10);
411 assert!(expr.partition.is_none());
412 }
413
414 #[test]
415 fn test_create_table_expr_generator_deterministic() {
416 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
417 let expr = CreateTableExprGeneratorBuilder::default()
418 .columns(10)
419 .partition(3)
420 .if_not_exists(true)
421 .engine("mito2")
422 .build()
423 .unwrap()
424 .generate(&mut rng)
425 .unwrap();
426
427 let serialized = serde_json::to_string(&expr).unwrap();
428 let expected = r#"{"table_name":{"value":"quasi","quote_style":null},"columns":[{"name":{"value":"mOLEsTIAs","quote_style":null},"column_type":{"Float64":{}},"options":["PrimaryKey","Null"]},{"name":{"value":"CUMQUe","quote_style":null},"column_type":{"Timestamp":{"Second":null}},"options":["TimeIndex"]},{"name":{"value":"NaTus","quote_style":null},"column_type":{"Int64":{}},"options":[]},{"name":{"value":"EXPeDITA","quote_style":null},"column_type":{"Float64":{}},"options":[]},{"name":{"value":"ImPEDiT","quote_style":null},"column_type":{"Float32":{}},"options":[{"DefaultValue":{"Float32":0.56425774}}]},{"name":{"value":"ADIpisci","quote_style":null},"column_type":{"Float32":{}},"options":["PrimaryKey"]},{"name":{"value":"deBITIs","quote_style":null},"column_type":{"Float32":{}},"options":[{"DefaultValue":{"Float32":0.31315368}}]},{"name":{"value":"toTaM","quote_style":null},"column_type":{"Int32":{}},"options":["NotNull"]},{"name":{"value":"QuI","quote_style":null},"column_type":{"Float32":{}},"options":[{"DefaultValue":{"Float32":0.39941502}}]},{"name":{"value":"INVeNtOre","quote_style":null},"column_type":{"Boolean":null},"options":["PrimaryKey"]}],"if_not_exists":true,"partition":{"columns":["mOLEsTIAs"],"exprs":[{"lhs":{"Column":"mOLEsTIAs"},"op":"Lt","rhs":{"Value":{"Float64":5.992310449541053e307}}},{"lhs":{"Expr":{"lhs":{"Column":"mOLEsTIAs"},"op":"GtEq","rhs":{"Value":{"Float64":5.992310449541053e307}}}},"op":"And","rhs":{"Expr":{"lhs":{"Column":"mOLEsTIAs"},"op":"Lt","rhs":{"Value":{"Float64":1.1984620899082105e308}}}}},{"lhs":{"Column":"mOLEsTIAs"},"op":"GtEq","rhs":{"Value":{"Float64":1.1984620899082105e308}}}]},"engine":"mito2","options":{},"primary_keys":[0,5,9]}"#;
429 assert_eq!(expected, serialized);
430 }
431
432 #[test]
433 fn test_create_logical_table_expr_generator() {
434 let mut rng = rand::rng();
435
436 let physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
437 .if_not_exists(false)
438 .build()
439 .unwrap()
440 .generate(&mut rng)
441 .unwrap();
442 assert_eq!(physical_table_expr.engine, "metric");
443 assert_eq!(physical_table_expr.columns.len(), 2);
444
445 let physical_ts = physical_table_expr.columns.iter().position(|column| {
446 column
447 .options
448 .iter()
449 .any(|option| option == &ColumnOption::TimeIndex)
450 });
451 let physical_ts_name = physical_table_expr.columns[physical_ts.unwrap()]
452 .name
453 .value
454 .to_string();
455
456 let physical_table_ctx = Arc::new(TableContext::from(&physical_table_expr));
457
458 let logical_table_expr = CreateLogicalTableExprGeneratorBuilder::default()
459 .physical_table_ctx(physical_table_ctx)
460 .labels(5)
461 .if_not_exists(false)
462 .build()
463 .unwrap()
464 .generate(&mut rng)
465 .unwrap();
466 let logical_ts = logical_table_expr.columns.iter().position(|column| {
467 column
468 .options
469 .iter()
470 .any(|option| option == &ColumnOption::TimeIndex)
471 });
472 let logical_ts_name = logical_table_expr.columns[logical_ts.unwrap()]
473 .name
474 .value
475 .to_string();
476
477 assert_eq!(logical_table_expr.engine, "metric");
478 assert_eq!(logical_table_expr.columns.len(), 7);
479 assert_eq!(logical_ts_name, physical_ts_name);
480 assert!(logical_table_expr.columns.iter().all(|column| {
481 column.column_type != ConcreteDataType::string_datatype()
482 || column
483 .options
484 .iter()
485 .any(|option| option == &ColumnOption::PrimaryKey)
486 }));
487 }
488
489 #[test]
490 fn test_create_logical_table_expr_generator_deterministic() {
491 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
492 let physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
493 .if_not_exists(false)
494 .build()
495 .unwrap()
496 .generate(&mut rng)
497 .unwrap();
498 let physical_table_serialized = serde_json::to_string(&physical_table_expr).unwrap();
499 let physical_table_expected = r#"{"table_name":{"value":"expedita","quote_style":null},"columns":[{"name":{"value":"ts","quote_style":null},"column_type":{"Timestamp":{"Millisecond":null}},"options":["TimeIndex"]},{"name":{"value":"val","quote_style":null},"column_type":{"Float64":{}},"options":[]}],"if_not_exists":false,"partition":null,"engine":"metric","options":{"physical_metric_table":{"String":""}},"primary_keys":[]}"#;
500 assert_eq!(physical_table_expected, physical_table_serialized);
501
502 let physical_table_ctx = Arc::new(TableContext::from(&physical_table_expr));
503
504 let logical_table_expr = CreateLogicalTableExprGeneratorBuilder::default()
505 .physical_table_ctx(physical_table_ctx)
506 .labels(5)
507 .if_not_exists(false)
508 .build()
509 .unwrap()
510 .generate(&mut rng)
511 .unwrap();
512
513 let logical_table_serialized = serde_json::to_string(&logical_table_expr).unwrap();
514 let logical_table_expected = r#"{"table_name":{"value":"impedit","quote_style":null},"columns":[{"name":{"value":"ts","quote_style":null},"column_type":{"Timestamp":{"Millisecond":null}},"options":["TimeIndex"]},{"name":{"value":"val","quote_style":null},"column_type":{"Float64":{}},"options":[]},{"name":{"value":"totam","quote_style":null},"column_type":{"String":null},"options":["PrimaryKey"]},{"name":{"value":"cumque","quote_style":null},"column_type":{"String":null},"options":["PrimaryKey"]},{"name":{"value":"natus","quote_style":null},"column_type":{"String":null},"options":["PrimaryKey"]},{"name":{"value":"molestias","quote_style":null},"column_type":{"String":null},"options":["PrimaryKey"]},{"name":{"value":"qui","quote_style":null},"column_type":{"String":null},"options":["PrimaryKey"]}],"if_not_exists":false,"partition":null,"engine":"metric","options":{"on_physical_table":{"String":"expedita"}},"primary_keys":[4,2,3,6,5]}"#;
515 assert_eq!(logical_table_expected, logical_table_serialized);
516 }
517
518 #[test]
519 fn test_create_database_expr_generator() {
520 let mut rng = rand::rng();
521
522 let expr = CreateDatabaseExprGeneratorBuilder::default()
523 .if_not_exists(true)
524 .build()
525 .unwrap()
526 .generate(&mut rng)
527 .unwrap();
528 assert!(expr.if_not_exists);
529 }
530
531 #[test]
532 fn test_create_database_expr_generator_deterministic() {
533 let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
534 let expr = CreateDatabaseExprGeneratorBuilder::default()
535 .if_not_exists(true)
536 .build()
537 .unwrap()
538 .generate(&mut rng)
539 .unwrap();
540
541 let serialized = serde_json::to_string(&expr).unwrap();
542 let expected =
543 r#"{"database_name":{"value":"EXPediTA","quote_style":null},"if_not_exists":true}"#;
544 assert_eq!(expected, serialized);
545 }
546}