tests_fuzz/ir/
string_value.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 datatypes::value::Value;
16use rand::Rng;
17
18use crate::error::{self, Result};
19use crate::generator::Random;
20use crate::ir::Ident;
21
22const READABLE_CHARSET: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
23
24fn readable_token(index: usize) -> String {
25    let base = READABLE_CHARSET.len();
26    let mut n = index + 1;
27    let mut buf = Vec::new();
28
29    while n > 0 {
30        let rem = (n - 1) % base;
31        buf.push(READABLE_CHARSET[rem] as char);
32        n = (n - 1) / base;
33    }
34
35    buf.iter().rev().collect()
36}
37
38pub fn generate_data_string_value<R: Rng>(
39    rng: &mut R,
40    random_str: Option<&dyn Random<Ident, R>>,
41) -> Value {
42    match random_str {
43        Some(random) => Value::from(random.generate(rng).value),
44        None => {
45            let idx = rng.random_range(0..(READABLE_CHARSET.len() * READABLE_CHARSET.len() * 4));
46            Value::from(readable_token(idx))
47        }
48    }
49}
50
51/// Generates ordered readable string bounds for partition expressions.
52pub fn generate_partition_bounds(bounds: usize) -> Vec<Value> {
53    let token_space = READABLE_CHARSET.len() * READABLE_CHARSET.len() * 1024;
54    (1..=bounds)
55        .map(|i| {
56            let idx = i * token_space / (bounds + 1);
57            Value::from(readable_token(idx))
58        })
59        .collect()
60}
61
62/// Picks a representative string value for the target partition range.
63pub fn generate_partition_value(bounds: &[Value], bound_idx: usize) -> Value {
64    let first = bounds.first().unwrap();
65    let last = bounds.last().unwrap();
66    let upper = match first {
67        Value::String(v) => v.as_utf8(),
68        _ => "",
69    };
70
71    if bound_idx == 0 {
72        if upper <= "0" {
73            Value::from("")
74        } else {
75            Value::from("0")
76        }
77    } else if bound_idx < bounds.len() {
78        bounds[bound_idx - 1].clone()
79    } else {
80        last.clone()
81    }
82}
83
84/// Generates a unique readable bound not present in existing bounds.
85pub fn generate_unique_partition_bound<R: Rng>(rng: &mut R, bounds: &[Value]) -> Result<Value> {
86    let search_space = READABLE_CHARSET.len() * READABLE_CHARSET.len() * 1024;
87    let start = rng.random_range(0..search_space);
88    for offset in 0..search_space {
89        let idx = start + offset;
90        let candidate = Value::from(readable_token(idx));
91        if !bounds.contains(&candidate) {
92            return Ok(candidate);
93        }
94    }
95
96    error::UnexpectedSnafu {
97        violated: "unable to generate unique string partition bound".to_string(),
98    }
99    .fail()
100}
101
102#[cfg(test)]
103mod tests {
104    use rand::SeedableRng;
105    use rand_chacha::ChaCha8Rng;
106
107    use super::*;
108
109    #[test]
110    fn test_readable_token_grows_length() {
111        assert_eq!("0", readable_token(0));
112        assert_eq!("9", readable_token(9));
113        assert_eq!("A", readable_token(10));
114        assert_eq!("z", readable_token(61));
115        assert_eq!("00", readable_token(62));
116    }
117
118    #[test]
119    fn test_generate_partition_bounds_are_readable_and_unique() {
120        let bounds = generate_partition_bounds(8);
121        assert_eq!(8, bounds.len());
122
123        let mut values = bounds
124            .iter()
125            .map(|v| match v {
126                Value::String(s) => s.as_utf8().to_string(),
127                _ => panic!("expected string value"),
128            })
129            .collect::<Vec<_>>();
130        let mut dedup = values.clone();
131        dedup.sort();
132        dedup.dedup();
133        assert_eq!(values.len(), dedup.len());
134
135        for s in values.drain(..) {
136            assert!(s.chars().all(|c| c.is_ascii_alphanumeric()));
137        }
138    }
139
140    #[test]
141    fn test_generate_partition_value_for_string_bounds() {
142        let bounds = vec![Value::from("A"), Value::from("M")];
143        assert_eq!(Value::from("0"), generate_partition_value(&bounds, 0));
144        assert_eq!(Value::from("A"), generate_partition_value(&bounds, 1));
145        assert_eq!(Value::from("M"), generate_partition_value(&bounds, 2));
146    }
147
148    #[test]
149    fn test_generate_unique_partition_bound_not_in_existing() {
150        let mut rng = ChaCha8Rng::seed_from_u64(42);
151        let bounds = vec![Value::from("0"), Value::from("1"), Value::from("2")];
152        let candidate = generate_unique_partition_bound(&mut rng, &bounds).unwrap();
153        assert!(!bounds.contains(&candidate));
154        match candidate {
155            Value::String(s) => {
156                assert!(!s.as_utf8().is_empty());
157                assert!(s.as_utf8().chars().all(|c| c.is_ascii_alphanumeric()));
158            }
159            _ => panic!("expected string value"),
160        }
161    }
162}