Skip to main content

tests_fuzz/generator/
create_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::collections::HashMap;
16
17use datatypes::data_type::ConcreteDataType;
18use datatypes::value::Value;
19use derive_builder::Builder;
20use rand::Rng;
21use rand::seq::SliceRandom;
22use snafu::{ResultExt, ensure};
23
24use super::Generator;
25use crate::context::TableContextRef;
26use crate::error::{self, Error, Result};
27use crate::fake::{MappedGenerator, WordGenerator, random_capitalize_map};
28use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Random};
29use crate::ir::create_expr::{
30    ColumnOption, CreateDatabaseExprBuilder, CreateTableExprBuilder, PartitionDef,
31};
32use crate::ir::partition_expr::SimplePartitions;
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    partition_column: bool,
48    if_not_exists: bool,
49    #[builder(setter(into))]
50    name: Ident,
51    #[builder(setter(into))]
52    with_clause: HashMap<String, String>,
53    name_generator: Box<dyn Random<Ident, R>>,
54    ts_column_type_generator: ConcreteDataTypeGenerator<R>,
55    column_type_generator: ConcreteDataTypeGenerator<R>,
56    partible_column_type_generator: ConcreteDataTypeGenerator<R>,
57    partible_column_options_generator: ColumnOptionGenerator<R>,
58    column_options_generator: ColumnOptionGenerator<R>,
59    ts_column_options_generator: ColumnOptionGenerator<R>,
60}
61
62const DEFAULT_ENGINE: &str = "mito";
63
64impl<R: Rng + 'static> Default for CreateTableExprGenerator<R> {
65    fn default() -> Self {
66        Self {
67            columns: 0,
68            engine: DEFAULT_ENGINE.to_string(),
69            if_not_exists: false,
70            partition: 0,
71            partition_column: false,
72            name: Ident::new(""),
73            with_clause: HashMap::default(),
74            name_generator: Box::new(MappedGenerator::new(WordGenerator, random_capitalize_map)),
75            ts_column_type_generator: Box::new(TsColumnTypeGenerator),
76            column_type_generator: Box::new(ColumnTypeGenerator),
77            partible_column_type_generator: Box::new(PartibleColumnTypeGenerator),
78            partible_column_options_generator: Box::new(partible_column_options_generator),
79            column_options_generator: Box::new(column_options_generator),
80            ts_column_options_generator: Box::new(ts_column_options_generator),
81        }
82    }
83}
84
85impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreateTableExprGenerator<R> {
86    type Error = Error;
87
88    /// Generates the [CreateTableExpr].
89    fn generate(&self, rng: &mut R) -> Result<CreateTableExpr> {
90        ensure!(
91            self.columns != 0,
92            error::UnexpectedSnafu {
93                violated: "The columns must larger than zero"
94            }
95        );
96
97        let mut builder = CreateTableExprBuilder::default();
98        let mut columns = Vec::with_capacity(self.columns);
99        let mut primary_keys = vec![];
100        let need_partible_column = self.partition > 1 || self.partition_column;
101        let mut column_names = self.name_generator.choose(rng, self.columns);
102
103        if self.columns == 1 {
104            // Generates the ts column.
105            // Safety: columns must large than 0.
106            let name = column_names.pop().unwrap();
107            let column = generate_columns(
108                rng,
109                vec![name.clone()],
110                self.ts_column_type_generator.as_ref(),
111                self.ts_column_options_generator.as_ref(),
112            )
113            .remove(0);
114            columns.push(column);
115        } else {
116            // Generates the partible column.
117            if need_partible_column {
118                // Safety: columns must large than 0.
119                let name = column_names.pop().unwrap();
120                let column = generate_columns(
121                    rng,
122                    vec![name.clone()],
123                    self.partible_column_type_generator.as_ref(),
124                    self.partible_column_options_generator.as_ref(),
125                )
126                .remove(0);
127
128                if self.partition > 1 {
129                    // Generates partition bounds.
130                    let partition_def = generate_partition_def(
131                        self.partition,
132                        column.column_type.clone(),
133                        name.clone(),
134                    );
135                    builder.partition(partition_def);
136                }
137                columns.push(column);
138            }
139            // Generates the ts column.
140            // Safety: columns must large than 1.
141            let name = column_names.pop().unwrap();
142            columns.extend(generate_columns(
143                rng,
144                vec![name],
145                self.ts_column_type_generator.as_ref(),
146                self.ts_column_options_generator.as_ref(),
147            ));
148            // Generates rest columns
149            columns.extend(generate_columns(
150                rng,
151                column_names,
152                self.column_type_generator.as_ref(),
153                self.column_options_generator.as_ref(),
154            ));
155        }
156
157        for (idx, column) in columns.iter().enumerate() {
158            if column.is_primary_key() {
159                primary_keys.push(idx);
160            }
161        }
162        // Shuffles the primary keys.
163        primary_keys.shuffle(rng);
164
165        builder.columns(columns);
166        builder.primary_keys(primary_keys);
167        builder.engine(self.engine.clone());
168        builder.if_not_exists(self.if_not_exists);
169        if self.name.is_empty() {
170            builder.table_name(self.name_generator.generate(rng));
171        } else {
172            builder.table_name(self.name.clone());
173        }
174        if !self.with_clause.is_empty() {
175            let mut options = HashMap::new();
176            for (key, value) in &self.with_clause {
177                options.insert(key.clone(), Value::from(value.clone()));
178            }
179            builder.options(options);
180        }
181        builder.build().context(error::BuildCreateTableExprSnafu)
182    }
183}
184
185pub fn generate_partition_def(
186    partitions: usize,
187    column_type: ConcreteDataType,
188    column_name: Ident,
189) -> PartitionDef {
190    assert!(partitions > 1, "partitions must be greater than 1");
191    let bounds = generate_partition_bounds(&column_type, partitions - 1);
192    let partitions = SimplePartitions::new(column_name.clone(), bounds);
193    let partition_exprs = partitions.generate().unwrap();
194
195    PartitionDef {
196        columns: vec![column_name.clone()],
197        exprs: partition_exprs,
198    }
199}
200
201fn metric_partition_column() -> Column {
202    Column {
203        name: Ident::new("host"),
204        column_type: ConcreteDataType::string_datatype(),
205        options: vec![ColumnOption::PrimaryKey],
206    }
207}
208
209pub fn generate_metric_partition_def(partitions: usize) -> PartitionDef {
210    assert!(partitions > 1, "partitions must be greater than 1");
211    let partition_column = metric_partition_column();
212    let bounds = generate_partition_bounds(&partition_column.column_type, partitions - 1);
213    let partitions = SimplePartitions::new(partition_column.name.clone(), bounds);
214    PartitionDef {
215        columns: vec![partitions.column_name.clone()],
216        exprs: partitions.generate().unwrap(),
217    }
218}
219
220/// Generate a physical table with 2 columns: ts of TimestampType::Millisecond as time index and val of Float64Type.
221#[derive(Builder)]
222#[builder(pattern = "owned")]
223pub struct CreatePhysicalTableExprGenerator<R: Rng + 'static> {
224    #[builder(default = "Box::new(WordGenerator)")]
225    name_generator: Box<dyn Random<Ident, R>>,
226    #[builder(default = "false")]
227    if_not_exists: bool,
228    #[builder(default = "0")]
229    partition: usize,
230    #[builder(default = "false")]
231    partition_column: bool,
232    #[builder(default, setter(into))]
233    with_clause: HashMap<String, String>,
234}
235
236impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreatePhysicalTableExprGenerator<R> {
237    type Error = Error;
238
239    fn generate(&self, rng: &mut R) -> Result<CreateTableExpr> {
240        let mut options = HashMap::with_capacity(self.with_clause.len() + 1);
241        options.insert("physical_metric_table".to_string(), Value::from(""));
242        for (key, value) in &self.with_clause {
243            options.insert(key.clone(), Value::from(value.clone()));
244        }
245
246        let mut columns = vec![
247            Column {
248                name: Ident::new("ts"),
249                column_type: ConcreteDataType::timestamp_millisecond_datatype(),
250                options: vec![ColumnOption::TimeIndex],
251            },
252            Column {
253                name: Ident::new("val"),
254                column_type: ConcreteDataType::float64_datatype(),
255                options: vec![],
256            },
257        ];
258
259        let mut partition = None;
260        let mut primary_keys = vec![];
261        if self.partition > 1 || self.partition_column {
262            columns.push(metric_partition_column());
263            primary_keys.push(columns.len() - 1);
264        }
265        if self.partition > 1 {
266            partition = Some(generate_metric_partition_def(self.partition));
267        }
268
269        Ok(CreateTableExpr {
270            table_name: self.name_generator.generate(rng),
271            columns,
272            if_not_exists: self.if_not_exists,
273            partition,
274            engine: "metric".to_string(),
275            options,
276            primary_keys,
277        })
278    }
279}
280
281/// Generate a logical table based on an existing physical table.
282#[derive(Builder)]
283#[builder(pattern = "owned")]
284pub struct CreateLogicalTableExprGenerator<R: Rng + 'static> {
285    physical_table_ctx: TableContextRef,
286    labels: usize,
287    if_not_exists: bool,
288    #[builder(default = "true")]
289    include_partition_column: bool,
290    #[builder(default = "Box::new(WordGenerator)")]
291    name_generator: Box<dyn Random<Ident, R>>,
292}
293
294impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreateLogicalTableExprGenerator<R> {
295    type Error = Error;
296
297    fn generate(&self, rng: &mut R) -> Result<CreateTableExpr> {
298        // Currently we mock the usage of GreptimeDB as Prometheus' backend, the physical table must have ts and val.
299        ensure!(
300            self.physical_table_ctx.columns.len() >= 2,
301            error::UnexpectedSnafu {
302                violated: "The physical table must have at least two columns"
303            }
304        );
305
306        // Generates the logical table columns based on the physical table.
307        let logical_table_name = self
308            .physical_table_ctx
309            .generate_unique_table_name(rng, self.name_generator.as_ref());
310        let mut physical_columns = self.physical_table_ctx.columns.clone();
311        if !self.include_partition_column
312            && let Some(partition_def) = &self.physical_table_ctx.partition
313        {
314            physical_columns.retain(|column| !partition_def.columns.contains(&column.name));
315        }
316
317        let mut logical_table = CreateTableExpr {
318            table_name: logical_table_name,
319            columns: physical_columns,
320            if_not_exists: self.if_not_exists,
321            partition: None,
322            engine: "metric".to_string(),
323            options: [(
324                "on_physical_table".to_string(),
325                self.physical_table_ctx.name.value.clone().into(),
326            )]
327            .into(),
328            primary_keys: vec![],
329        };
330
331        let column_names = self.name_generator.choose(rng, self.labels);
332        logical_table.columns.extend(generate_columns(
333            rng,
334            column_names,
335            &StringColumnTypeGenerator,
336            Box::new(primary_key_options_generator),
337        ));
338
339        // Currently only the `primary key` option is kept in physical table,
340        // so we only keep the `primary key` option in the logical table for fuzz test.
341        let mut primary_keys = vec![];
342        for (idx, column) in logical_table.columns.iter().enumerate() {
343            if column.is_primary_key() {
344                primary_keys.push(idx);
345            }
346        }
347        primary_keys.shuffle(rng);
348        logical_table.primary_keys = primary_keys;
349
350        Ok(logical_table)
351    }
352}
353
354#[derive(Builder)]
355#[builder(default, pattern = "owned")]
356pub struct CreateDatabaseExprGenerator<R: Rng + 'static> {
357    #[builder(setter(into))]
358    database_name: String,
359    name_generator: Box<dyn Random<Ident, R>>,
360    if_not_exists: bool,
361}
362
363impl<R: Rng + 'static> Default for CreateDatabaseExprGenerator<R> {
364    fn default() -> Self {
365        Self {
366            database_name: String::new(),
367            name_generator: Box::new(MappedGenerator::new(WordGenerator, random_capitalize_map)),
368            if_not_exists: false,
369        }
370    }
371}
372
373impl<R: Rng + 'static> Generator<CreateDatabaseExpr, R> for CreateDatabaseExprGenerator<R> {
374    type Error = Error;
375
376    fn generate(&self, rng: &mut R) -> Result<CreateDatabaseExpr> {
377        let mut builder = CreateDatabaseExprBuilder::default();
378        builder.if_not_exists(self.if_not_exists);
379        if self.database_name.is_empty() {
380            builder.database_name(self.name_generator.generate(rng));
381        } else {
382            builder.database_name(self.database_name.clone());
383        }
384        builder.build().context(error::BuildCreateDatabaseExprSnafu)
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use std::sync::Arc;
391
392    use datatypes::data_type::ConcreteDataType;
393    use datatypes::value::Value;
394    use rand::SeedableRng;
395
396    use super::*;
397    use crate::context::TableContext;
398    use crate::ir::PARTIBLE_DATA_TYPES;
399
400    #[test]
401    fn test_float64() {
402        let value = Value::from(0.047318541668048164);
403        assert_eq!("0.047318541668048164", value.to_string());
404        let value: f64 = "0.047318541668048164".parse().unwrap();
405        assert_eq!("0.047318541668048164", value.to_string());
406    }
407
408    #[test]
409    fn test_create_table_expr_generator() {
410        let mut rng = rand::rng();
411
412        let expr = CreateTableExprGeneratorBuilder::default()
413            .columns(10)
414            .partition(3)
415            .if_not_exists(true)
416            .engine("mito2")
417            .build()
418            .unwrap()
419            .generate(&mut rng)
420            .unwrap();
421        assert_eq!(expr.engine, "mito2");
422        assert!(expr.if_not_exists);
423        assert_eq!(expr.columns.len(), 10);
424        assert_eq!(expr.partition.unwrap().exprs.len(), 3);
425
426        let expr = CreateTableExprGeneratorBuilder::default()
427            .columns(10)
428            .partition(1)
429            .build()
430            .unwrap()
431            .generate(&mut rng)
432            .unwrap();
433        assert_eq!(expr.columns.len(), 10);
434        assert!(expr.partition.is_none());
435
436        let expr = CreateTableExprGeneratorBuilder::default()
437            .columns(10)
438            .partition(1)
439            .partition_column(true)
440            .build()
441            .unwrap()
442            .generate(&mut rng)
443            .unwrap();
444        assert_eq!(expr.columns.len(), 10);
445        assert!(expr.partition.is_none());
446        assert!(PARTIBLE_DATA_TYPES.contains(&expr.columns[0].column_type));
447    }
448
449    #[test]
450    fn test_create_table_expr_generator_deterministic() {
451        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
452        let expr = CreateTableExprGeneratorBuilder::default()
453            .columns(10)
454            .partition(3)
455            .if_not_exists(true)
456            .engine("mito2")
457            .build()
458            .unwrap()
459            .generate(&mut rng)
460            .unwrap();
461
462        let serialized = serde_json::to_string(&expr).unwrap();
463        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":[{"value":"mOLEsTIAs","quote_style":null}],"exprs":[{"lhs":{"Column":"mOLEsTIAs"},"op":"Lt","rhs":{"Value":{"Float64":5.992310449541053e+307}}},{"lhs":{"Expr":{"lhs":{"Column":"mOLEsTIAs"},"op":"GtEq","rhs":{"Value":{"Float64":5.992310449541053e+307}}}},"op":"And","rhs":{"Expr":{"lhs":{"Column":"mOLEsTIAs"},"op":"Lt","rhs":{"Value":{"Float64":1.1984620899082105e+308}}}}},{"lhs":{"Column":"mOLEsTIAs"},"op":"GtEq","rhs":{"Value":{"Float64":1.1984620899082105e+308}}}]},"engine":"mito2","options":{},"primary_keys":[0,5,9]}"#;
464        assert_eq!(expected, serialized);
465    }
466
467    #[test]
468    fn test_create_logical_table_expr_generator() {
469        let mut rng = rand::rng();
470
471        let physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
472            .if_not_exists(false)
473            .build()
474            .unwrap()
475            .generate(&mut rng)
476            .unwrap();
477        assert_eq!(physical_table_expr.engine, "metric");
478        assert_eq!(physical_table_expr.columns.len(), 2);
479
480        let physical_ts = physical_table_expr.columns.iter().position(|column| {
481            column
482                .options
483                .iter()
484                .any(|option| option == &ColumnOption::TimeIndex)
485        });
486        let physical_ts_name = physical_table_expr.columns[physical_ts.unwrap()]
487            .name
488            .value
489            .clone();
490
491        let physical_table_ctx = Arc::new(TableContext::from(&physical_table_expr));
492
493        let logical_table_expr = CreateLogicalTableExprGeneratorBuilder::default()
494            .physical_table_ctx(physical_table_ctx)
495            .labels(5)
496            .if_not_exists(false)
497            .build()
498            .unwrap()
499            .generate(&mut rng)
500            .unwrap();
501        let logical_ts = logical_table_expr.columns.iter().position(|column| {
502            column
503                .options
504                .iter()
505                .any(|option| option == &ColumnOption::TimeIndex)
506        });
507        let logical_ts_name = logical_table_expr.columns[logical_ts.unwrap()]
508            .name
509            .value
510            .clone();
511
512        assert_eq!(logical_table_expr.engine, "metric");
513        assert_eq!(logical_table_expr.columns.len(), 7);
514        assert_eq!(logical_ts_name, physical_ts_name);
515        assert!(logical_table_expr.columns.iter().all(|column| {
516            column.column_type != ConcreteDataType::string_datatype()
517                || column
518                    .options
519                    .iter()
520                    .any(|option| option == &ColumnOption::PrimaryKey)
521        }));
522    }
523
524    #[test]
525    fn test_create_physical_table_expr_generator_with_partition() {
526        let mut rng = rand::rng();
527        let physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
528            .partition(3)
529            .if_not_exists(false)
530            .build()
531            .unwrap()
532            .generate(&mut rng)
533            .unwrap();
534
535        assert_eq!(physical_table_expr.engine, "metric");
536        assert!(physical_table_expr.partition.is_some());
537        assert_eq!(physical_table_expr.partition.unwrap().exprs.len(), 3);
538    }
539
540    #[test]
541    fn test_create_physical_table_expr_generator_with_partition_column() {
542        let mut rng = rand::rng();
543        let physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
544            .partition(1)
545            .partition_column(true)
546            .if_not_exists(false)
547            .build()
548            .unwrap()
549            .generate(&mut rng)
550            .unwrap();
551
552        assert_eq!(physical_table_expr.engine, "metric");
553        assert!(physical_table_expr.partition.is_none());
554        assert_eq!(physical_table_expr.columns.len(), 3);
555        assert_eq!(physical_table_expr.columns[2].name, Ident::new("host"));
556        assert_eq!(physical_table_expr.primary_keys, vec![2]);
557    }
558
559    #[test]
560    fn test_create_logical_table_expr_generator_without_partition_column() {
561        let mut rng = rand::rng();
562        let physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
563            .partition(3)
564            .if_not_exists(false)
565            .build()
566            .unwrap()
567            .generate(&mut rng)
568            .unwrap();
569        let partition_columns = physical_table_expr
570            .partition
571            .as_ref()
572            .unwrap()
573            .columns
574            .clone();
575        let physical_table_ctx = Arc::new(TableContext::from(&physical_table_expr));
576
577        let logical_table_expr = CreateLogicalTableExprGeneratorBuilder::default()
578            .physical_table_ctx(physical_table_ctx)
579            .labels(3)
580            .include_partition_column(false)
581            .if_not_exists(false)
582            .build()
583            .unwrap()
584            .generate(&mut rng)
585            .unwrap();
586
587        assert!(
588            logical_table_expr
589                .columns
590                .iter()
591                .all(|column| !partition_columns.contains(&column.name))
592        );
593    }
594
595    #[test]
596    fn test_create_logical_table_expr_generator_deterministic() {
597        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
598        let physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
599            .if_not_exists(false)
600            .build()
601            .unwrap()
602            .generate(&mut rng)
603            .unwrap();
604        let physical_table_serialized = serde_json::to_string(&physical_table_expr).unwrap();
605        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":[]}"#;
606        assert_eq!(physical_table_expected, physical_table_serialized);
607
608        let physical_table_ctx = Arc::new(TableContext::from(&physical_table_expr));
609
610        let logical_table_expr = CreateLogicalTableExprGeneratorBuilder::default()
611            .physical_table_ctx(physical_table_ctx)
612            .labels(5)
613            .if_not_exists(false)
614            .build()
615            .unwrap()
616            .generate(&mut rng)
617            .unwrap();
618
619        let logical_table_serialized = serde_json::to_string(&logical_table_expr).unwrap();
620        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":{"size_type":"Utf8"}},"options":["PrimaryKey"]},{"name":{"value":"cumque","quote_style":null},"column_type":{"String":{"size_type":"Utf8"}},"options":["PrimaryKey"]},{"name":{"value":"natus","quote_style":null},"column_type":{"String":{"size_type":"Utf8"}},"options":["PrimaryKey"]},{"name":{"value":"molestias","quote_style":null},"column_type":{"String":{"size_type":"Utf8"}},"options":["PrimaryKey"]},{"name":{"value":"qui","quote_style":null},"column_type":{"String":{"size_type":"Utf8"}},"options":["PrimaryKey"]}],"if_not_exists":false,"partition":null,"engine":"metric","options":{"on_physical_table":{"String":"expedita"}},"primary_keys":[4,2,3,6,5]}"#;
621        assert_eq!(logical_table_expected, logical_table_serialized);
622    }
623
624    #[test]
625    fn test_create_database_expr_generator() {
626        let mut rng = rand::rng();
627
628        let expr = CreateDatabaseExprGeneratorBuilder::default()
629            .if_not_exists(true)
630            .build()
631            .unwrap()
632            .generate(&mut rng)
633            .unwrap();
634        assert!(expr.if_not_exists);
635    }
636
637    #[test]
638    fn test_create_database_expr_generator_deterministic() {
639        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
640        let expr = CreateDatabaseExprGeneratorBuilder::default()
641            .if_not_exists(true)
642            .build()
643            .unwrap()
644            .generate(&mut rng)
645            .unwrap();
646
647        let serialized = serde_json::to_string(&expr).unwrap();
648        let expected =
649            r#"{"database_name":{"value":"EXPediTA","quote_style":null},"if_not_exists":true}"#;
650        assert_eq!(expected, serialized);
651    }
652}