1pub mod numeric;
5pub mod special;
6pub mod string;
7pub mod temporal;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum SeedValue {
12 Default,
14 Null,
16 Integer(i64),
19 Float(f64),
21 Text(String),
23 Bool(bool),
25 Blob(Vec<u8>),
27 CurrentTime,
29}
30
31pub trait Generator: Send + Sync {
58 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue;
61
62 fn name(&self) -> &'static str {
64 core::any::type_name::<Self>()
65 }
66}
67
68impl<G: Generator + ?Sized> Generator for Box<G> {
69 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue {
70 (**self).generate(rng, index, sql_type)
71 }
72
73 fn name(&self) -> &'static str {
74 (**self).name()
75 }
76}
77
78impl<G: Generator + ?Sized> Generator for std::sync::Arc<G> {
79 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue {
80 (**self).generate(rng, index, sql_type)
81 }
82
83 fn name(&self) -> &'static str {
84 (**self).name()
85 }
86}
87
88impl Generator for GeneratorKind {
91 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue {
92 self.into_generator().generate(rng, index, sql_type)
93 }
94
95 fn name(&self) -> &'static str {
96 self.into_generator().name()
97 }
98}
99
100macro_rules! seed_value_from {
101 ($variant:ident: $($ty:ty),*) => {$(
102 impl From<$ty> for SeedValue {
103 fn from(value: $ty) -> Self {
104 Self::$variant(value.into())
105 }
106 }
107 )*};
108}
109
110seed_value_from!(Integer: i8, i16, i32, i64, u8, u16, u32);
111seed_value_from!(Float: f32, f64);
112seed_value_from!(Text: String, &str, char);
113seed_value_from!(Bool: bool);
114seed_value_from!(Blob: Vec<u8>, &[u8]);
115
116impl<T: Into<SeedValue>> From<Option<T>> for SeedValue {
117 fn from(value: Option<T>) -> Self {
119 value.map_or(Self::Null, Into::into)
120 }
121}
122
123pub use rand::RngCore;
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132#[non_exhaustive]
133pub enum GeneratorKind {
134 IntPrimaryKey,
136 Int,
138 Float,
140 Bool,
142 Text,
144 FirstName,
146 LastName,
148 FullName,
150 Email,
153 Phone,
155 City,
157 Country,
159 Address,
161 JobTitle,
163 Title,
165 Url,
168 Username,
170 Slug,
172 Company,
174 LoremIpsum,
176 Uuid,
178 Json,
180 Date,
183 Timestamp,
186 Time,
188 TimeTz,
190 Interval,
192 Blob,
194 PgInet,
196 PgCidr,
198 PgMacAddr,
200 PgMacAddr8,
202 PgPoint,
204 PgLine,
206 PgLseg,
208 PgBox,
210 PgPath,
212 PgPolygon,
214 PgCircle,
216 PgBit,
218 PgVarBit,
220 PgArray,
222}
223
224impl GeneratorKind {
225 #[must_use]
227 pub fn into_generator(self) -> Box<dyn Generator> {
228 match self {
229 Self::IntPrimaryKey => Box::new(numeric::IntPrimaryKeyGen),
230 Self::Int => Box::new(numeric::IntGen {
231 min: 0,
232 max: 10_000,
233 }),
234 Self::Float => Box::new(numeric::FloatGen {
235 min: 0.0,
236 max: 10_000.0,
237 }),
238 Self::Bool => Box::new(numeric::BoolGen),
239 Self::Text => Box::new(string::TextGen {
240 min_len: 5,
241 max_len: 50,
242 }),
243 Self::FirstName => Box::new(string::FirstNameGen),
244 Self::LastName => Box::new(string::LastNameGen),
245 Self::FullName => Box::new(string::FullNameGen),
246 Self::Email => Box::new(string::EmailGen),
247 Self::Phone => Box::new(string::PhoneGen),
248 Self::City => Box::new(string::CityGen),
249 Self::Country => Box::new(string::CountryGen),
250 Self::Address => Box::new(string::AddressGen),
251 Self::JobTitle => Box::new(string::JobTitleGen),
252 Self::Title => Box::new(string::TitleGen),
253 Self::Url => Box::new(string::UrlGen),
254 Self::Username => Box::new(string::UsernameGen),
255 Self::Slug => Box::new(string::SlugGen),
256 Self::Company => Box::new(string::CompanyGen),
257 Self::LoremIpsum => Box::new(string::LoremGen { words: 10 }),
258 Self::Uuid => Box::new(special::UuidGen),
259 Self::Json => Box::new(special::JsonGen),
260 Self::Date => Box::new(temporal::DateGen),
261 Self::Timestamp => Box::new(temporal::TimestampGen),
262 Self::Time => Box::new(temporal::TimeGen),
263 Self::TimeTz => Box::new(temporal::TimeTzGen),
264 Self::Interval => Box::new(temporal::IntervalGen),
265 Self::Blob => Box::new(special::BlobGen { size: 32 }),
266 Self::PgInet => Box::new(special::InetGen),
267 Self::PgCidr => Box::new(special::CidrGen),
268 Self::PgMacAddr => Box::new(special::MacAddrGen),
269 Self::PgMacAddr8 => Box::new(special::MacAddr8Gen),
270 Self::PgPoint => Box::new(special::PointGen),
271 Self::PgLine => Box::new(special::LineGen),
272 Self::PgLseg => Box::new(special::LsegGen),
273 Self::PgBox => Box::new(special::BoxGen),
274 Self::PgPath => Box::new(special::PathGen),
275 Self::PgPolygon => Box::new(special::PolygonGen),
276 Self::PgCircle => Box::new(special::CircleGen),
277 Self::PgBit => Box::new(special::BitGen {
278 min_len: 8,
279 max_len: 8,
280 }),
281 Self::PgVarBit => Box::new(special::BitGen {
282 min_len: 1,
283 max_len: 32,
284 }),
285 Self::PgArray => Box::new(special::ArrayGen),
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use rand::SeedableRng;
294 use rand::rngs::StdRng;
295
296 #[test]
298 fn every_kind_is_deterministic() {
299 let kinds = [
300 GeneratorKind::IntPrimaryKey,
301 GeneratorKind::Int,
302 GeneratorKind::Float,
303 GeneratorKind::Bool,
304 GeneratorKind::Text,
305 GeneratorKind::FirstName,
306 GeneratorKind::LastName,
307 GeneratorKind::FullName,
308 GeneratorKind::Email,
309 GeneratorKind::Phone,
310 GeneratorKind::City,
311 GeneratorKind::Country,
312 GeneratorKind::Address,
313 GeneratorKind::JobTitle,
314 GeneratorKind::Title,
315 GeneratorKind::Url,
316 GeneratorKind::Username,
317 GeneratorKind::Slug,
318 GeneratorKind::Company,
319 GeneratorKind::LoremIpsum,
320 GeneratorKind::Uuid,
321 GeneratorKind::Json,
322 GeneratorKind::Date,
323 GeneratorKind::Timestamp,
324 GeneratorKind::Time,
325 GeneratorKind::TimeTz,
326 GeneratorKind::Interval,
327 GeneratorKind::Blob,
328 GeneratorKind::PgInet,
329 GeneratorKind::PgCidr,
330 GeneratorKind::PgMacAddr,
331 GeneratorKind::PgMacAddr8,
332 GeneratorKind::PgPoint,
333 GeneratorKind::PgLine,
334 GeneratorKind::PgLseg,
335 GeneratorKind::PgBox,
336 GeneratorKind::PgPath,
337 GeneratorKind::PgPolygon,
338 GeneratorKind::PgCircle,
339 GeneratorKind::PgBit,
340 GeneratorKind::PgVarBit,
341 GeneratorKind::PgArray,
342 ];
343
344 for seed in [0u64, 42, 999, u64::MAX] {
345 for kind in &kinds {
346 let g1 = kind.into_generator();
347 let g2 = kind.into_generator();
348
349 let seq1: Vec<SeedValue> = (0..20)
351 .map(|i| {
352 let mut rng = StdRng::seed_from_u64(seed.wrapping_add(i as u64));
353 g1.generate(&mut rng, i, "TEXT")
354 })
355 .collect();
356 let seq2: Vec<SeedValue> = (0..20)
357 .map(|i| {
358 let mut rng = StdRng::seed_from_u64(seed.wrapping_add(i as u64));
359 g2.generate(&mut rng, i, "TEXT")
360 })
361 .collect();
362
363 assert_eq!(
364 seq1, seq2,
365 "Determinism violation for {:?} with seed {seed}",
366 kind
367 );
368 }
369 }
370 }
371
372 #[test]
375 fn date_timestamp_deterministic_for_integer_columns() {
376 for sql_type in ["INTEGER", "BIGINT"] {
377 for seed in [0u64, 42, 12345] {
378 let date_seq1: Vec<SeedValue> = (0..50)
379 .map(|i| {
380 let mut rng = StdRng::seed_from_u64(seed.wrapping_add(i as u64));
381 temporal::DateGen.generate(&mut rng, i, sql_type)
382 })
383 .collect();
384 let date_seq2: Vec<SeedValue> = (0..50)
385 .map(|i| {
386 let mut rng = StdRng::seed_from_u64(seed.wrapping_add(i as u64));
387 temporal::DateGen.generate(&mut rng, i, sql_type)
388 })
389 .collect();
390 assert_eq!(
391 date_seq1, date_seq2,
392 "DateGen not deterministic for {sql_type} seed={seed}"
393 );
394 assert!(
396 matches!(date_seq1[0], SeedValue::Integer(_)),
397 "DateGen should produce Integer for {sql_type}"
398 );
399
400 let ts_seq1: Vec<SeedValue> = (0..50)
401 .map(|i| {
402 let mut rng = StdRng::seed_from_u64(seed.wrapping_add(i as u64));
403 temporal::TimestampGen.generate(&mut rng, i, sql_type)
404 })
405 .collect();
406 let ts_seq2: Vec<SeedValue> = (0..50)
407 .map(|i| {
408 let mut rng = StdRng::seed_from_u64(seed.wrapping_add(i as u64));
409 temporal::TimestampGen.generate(&mut rng, i, sql_type)
410 })
411 .collect();
412 assert_eq!(
413 ts_seq1, ts_seq2,
414 "TimestampGen not deterministic for {sql_type} seed={seed}"
415 );
416 assert!(
417 matches!(ts_seq1[0], SeedValue::Integer(_)),
418 "TimestampGen should produce Integer for {sql_type}"
419 );
420 }
421 }
422 }
423
424 #[test]
426 fn different_seeds_produce_different_output() {
427 let kinds = [
428 GeneratorKind::Int,
429 GeneratorKind::Text,
430 GeneratorKind::Email,
431 GeneratorKind::Date,
432 GeneratorKind::Timestamp,
433 GeneratorKind::Uuid,
434 ];
435
436 for kind in &kinds {
437 let g = kind.into_generator();
438 let mut rng_a = StdRng::seed_from_u64(1);
439 let mut rng_b = StdRng::seed_from_u64(2);
440
441 let seq_a: Vec<SeedValue> =
442 (0..10).map(|i| g.generate(&mut rng_a, i, "TEXT")).collect();
443 let seq_b: Vec<SeedValue> =
444 (0..10).map(|i| g.generate(&mut rng_b, i, "TEXT")).collect();
445
446 assert_ne!(
447 seq_a, seq_b,
448 "{:?} produced identical output for different seeds",
449 kind
450 );
451 }
452 }
453
454 #[test]
456 fn continuous_rng_stream_is_deterministic() {
457 let kinds = [
458 GeneratorKind::Int,
459 GeneratorKind::Float,
460 GeneratorKind::Text,
461 GeneratorKind::Email,
462 GeneratorKind::Date,
463 GeneratorKind::Timestamp,
464 ];
465
466 for kind in &kinds {
467 let g1 = kind.into_generator();
468 let g2 = kind.into_generator();
469 let mut rng1 = StdRng::seed_from_u64(42);
470 let mut rng2 = StdRng::seed_from_u64(42);
471
472 for i in 0..100 {
473 assert_eq!(
474 g1.generate(&mut rng1, i, "TEXT"),
475 g2.generate(&mut rng2, i, "TEXT"),
476 "Stream diverged at row {i} for {:?}",
477 kind
478 );
479 }
480 }
481 }
482}