@@ -12,6 +12,7 @@ import (
12
12
"regexp"
13
13
"strconv"
14
14
"strings"
15
+ "time"
15
16
)
16
17
17
18
const (
@@ -23,6 +24,9 @@ const (
23
24
seedKeyDelimiter = "__"
24
25
// nullPct is the key for nullability percentage in args maps
25
26
nullPct = "null_pct"
27
+ // maxArg and minArg are keys for range limits in args maps
28
+ maxArg = "max"
29
+ minArg = "min"
26
30
)
27
31
28
32
// GeneratorType is an enum for all the data generator types.
55
59
simpleNumberRe = regexp .MustCompile (`^[+-]?\d+(?:\.\d+)?$` )
56
60
quotedStrRe = regexp .MustCompile (`^'.*'$` )
57
61
booleanLiteralRe = regexp .MustCompile (`^(?i:true|false)$` )
62
+
63
+ // Regexes for simple comparison constraints.
64
+ gtRe = regexp .MustCompile (`(?i)^([A-Za-z_]\w*)\s*>\s*([^\s]+)$` )
65
+ gteRe = regexp .MustCompile (`(?i)^([A-Za-z_]\w*)\s*>=\s*([^\s]+)$` )
66
+ ltRe = regexp .MustCompile (`(?i)^([A-Za-z_]\w*)\s*<\s*([^\s]+)$` )
67
+ lteRe = regexp .MustCompile (`(?i)^([A-Za-z_]\w*)\s*<=\s*([^\s]+)$` )
58
68
)
59
69
60
70
// Schema is the map of TableBlocks, one per table, which is used by all data generators
@@ -285,13 +295,154 @@ func atoi(s string) int {
285
295
286
296
// setArgsRange sets the "min" and "max" keys in the args map to the specified range.
287
297
func setArgsRange (args map [string ]any , min , max int ) {
288
- args ["min" ] = min
289
- args ["max" ] = max
298
+ args [minArg ] = min
299
+ args [maxArg ] = max
290
300
}
291
301
292
302
// canonical replaces "." with "__" to match the legacy YAML format.
293
303
func canonical (name string ) string { return strings .ReplaceAll (name , "." , "__" ) }
294
304
305
+ // bumpTimestampISO returns the given RFC3339Nano timestamp string
306
+ // advanced by one nanosecond, or the original string if parsing fails.
307
+ func bumpTimestampISO (s string ) string {
308
+ if t , err := time .Parse (time .RFC3339Nano , s ); err == nil {
309
+ return t .Add (time .Millisecond ).Format (time .RFC3339Nano )
310
+ }
311
+ // fallback: return original
312
+ return s
313
+ }
314
+
315
+ // applyCheckConstraints updates each ColumnMeta in the Schema
316
+ // to reflect simple comparison CHECK constraints (>, >=, <, <=)
317
+ // for integer, float, and timestamp column types. It reads the
318
+ // raw CheckConstraints from allSchemas and sets min/max/start/end
319
+ // arguments in-place before data generation
320
+ func applyCheckConstraints (blocks Schema , allSchemas map [string ]* TableSchema ) {
321
+ for tbl , blks := range blocks {
322
+ schema := allSchemas [tbl ]
323
+ for i := range blks {
324
+ block := & blks [i ]
325
+ // Iterating over each column in the current TableBlock.
326
+ for colName , cm := range block .Columns {
327
+ // Columns that are not integer, float, or timestamp are skipped.
328
+ switch cm .Type {
329
+ case GenTypeInteger , GenTypeFloat , GenTypeTimestamp :
330
+ default :
331
+ continue
332
+ }
333
+ // Examining each raw CHECK expression for supported patterns.
334
+ for _ , chk := range schema .CheckConstraints {
335
+ chk = strings .TrimSpace (chk )
336
+ // col > val
337
+ applyGreaterThanCheck (chk , colName , cm )
338
+ // col >= val
339
+ applyGreaterThanEqualsCheck (chk , colName , cm )
340
+ // col < val
341
+ applyLessThanCheck (chk , colName , cm )
342
+ // col <= val
343
+ applyLessThanEqualsCheck (chk , colName , cm )
344
+ }
345
+ // The updated ColumnMeta is written back.
346
+ block .Columns [colName ] = cm
347
+ }
348
+ }
349
+ }
350
+ }
351
+
352
+ // applyLessThanEqualsCheck checks if the given check constraint
353
+ // is a "col <= val" expression and updates the ColumnMeta accordingly.
354
+ func applyLessThanEqualsCheck (chk string , colName string , cm ColumnMeta ) {
355
+ if m := lteRe .FindStringSubmatch (chk ); m != nil && m [1 ] == colName {
356
+ lit := stripCast (m [2 ])
357
+ switch cm .Type {
358
+ case GenTypeInteger :
359
+ if v , err := strconv .Atoi (lit ); err == nil {
360
+ cm .Args [maxArg ] = v
361
+ }
362
+ case GenTypeFloat :
363
+ if f , err := strconv .ParseFloat (lit , 64 ); err == nil {
364
+ cm .Args [maxArg ] = f
365
+ }
366
+ case GenTypeTimestamp :
367
+ cm .Args ["end" ] = lit
368
+ }
369
+ }
370
+ }
371
+
372
+ // applyLessThanCheck checks if the given check constraint
373
+ // is a "col < val" expression and updates the ColumnMeta accordingly.
374
+ func applyLessThanCheck (chk string , colName string , cm ColumnMeta ) {
375
+ if m := ltRe .FindStringSubmatch (chk ); m != nil && m [1 ] == colName {
376
+ lit := stripCast (m [2 ])
377
+ switch cm .Type {
378
+ case GenTypeInteger :
379
+ if v , err := strconv .Atoi (lit ); err == nil {
380
+ cm .Args [maxArg ] = v - 1
381
+ }
382
+ case GenTypeFloat :
383
+ if f , err := strconv .ParseFloat (lit , 64 ); err == nil {
384
+ cm .Args [maxArg ] = math .Nextafter (f , math .Inf (- 1 ))
385
+ }
386
+ case GenTypeTimestamp :
387
+ // Subtract one millisecond
388
+ if t , err := time .Parse (time .RFC3339Nano , lit ); err == nil {
389
+ cm .Args ["end" ] = t .Add (- time .Millisecond ).Format (time .RFC3339Nano )
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ // applyLessThanEqualsCheck checks if the given check constraint
396
+ // is a "col <= val" expression and updates the ColumnMeta accordingly.
397
+ func applyGreaterThanEqualsCheck (chk string , colName string , cm ColumnMeta ) {
398
+ if m := gteRe .FindStringSubmatch (chk ); m != nil && m [1 ] == colName {
399
+ lit := stripCast (m [2 ])
400
+ switch cm .Type {
401
+ case GenTypeInteger :
402
+ if v , err := strconv .Atoi (lit ); err == nil {
403
+ cm .Args [minArg ] = v
404
+ }
405
+ case GenTypeFloat :
406
+ if f , err := strconv .ParseFloat (lit , 64 ); err == nil {
407
+ cm .Args [minArg ] = f
408
+ }
409
+ case GenTypeTimestamp :
410
+ cm .Args ["start" ] = lit
411
+ }
412
+ }
413
+ }
414
+
415
+ // applyGreaterThanCheck checks if the given check constraint
416
+ // is a "col > val" expression and updates the ColumnMeta accordingly.
417
+ func applyGreaterThanCheck (chk string , colName string , cm ColumnMeta ) {
418
+ if m := gtRe .FindStringSubmatch (chk ); m != nil && m [1 ] == colName {
419
+ lit := stripCast (m [2 ])
420
+ switch cm .Type {
421
+ case GenTypeInteger :
422
+ if v , err := strconv .Atoi (lit ); err == nil {
423
+ cm .Args [minArg ] = v + 1
424
+ }
425
+ case GenTypeFloat :
426
+ if f , err := strconv .ParseFloat (lit , 64 ); err == nil {
427
+ cm .Args [minArg ] = math .Nextafter (f , math .Inf (1 ))
428
+ }
429
+ case GenTypeTimestamp :
430
+ cm .Args ["start" ] = bumpTimestampISO (lit )
431
+ }
432
+ }
433
+ }
434
+
435
+ // stripCast removes any Cockroach/Postgres cast suffix:
436
+ //
437
+ // “123:::INT8” → “123”
438
+ // “‘2021-01-01’::DATE” → “'2021-01-01'”
439
+ func stripCast (lit string ) string {
440
+ if i := strings .Index (lit , "::" ); i >= 0 {
441
+ return lit [:i ]
442
+ }
443
+ return lit
444
+ }
445
+
295
446
// mapSQLType maps a SQL column type to the workload generator type and
296
447
// argument set expected by cockroach workloads. The returned map may
297
448
// include bounds, formatting information or other hints used by the
@@ -331,15 +482,16 @@ func mapSQLType(sql string, col *Column, rng *rand.Rand) (GeneratorType, map[str
331
482
332
483
case sql == "date" :
333
484
return mapDateType (sql , col , args )
334
-
335
- case sql == "timestamp" || sql == "timestamptz" :
485
+ // We use hasPrefix(timestamp) to match both "timestamp" and "timestamptz"
486
+ // as well as any other variations like "timestamp(6)".
487
+ case strings .HasPrefix (sql , "timestamp" ):
336
488
return mapTimestampType (sql , col , args )
337
489
338
490
case sql == "bool" || sql == "boolean" :
339
491
return GenTypeBool , args
340
492
341
493
case sql == "json" || sql == "jsonb" :
342
- mapJsonType (sql , col , args )
494
+ return mapJsonType (sql , col , args )
343
495
}
344
496
setArgsRange (args , 5 , 30 )
345
497
return GenTypeString , args
@@ -404,13 +556,13 @@ func mapDecimalType(sql string, _ *Column, args map[string]any) (GeneratorType,
404
556
maxVal = 1.0 - fracUnit
405
557
minVal = - maxVal
406
558
}
407
- args ["min" ] = minVal
408
- args ["max" ] = maxVal
559
+ args [minArg ] = minVal
560
+ args [maxArg ] = maxVal
409
561
args ["round" ] = scale
410
562
} else {
411
563
// fallback for DECIMAL without precision
412
- args ["min" ] = 0.0
413
- args ["max" ] = 1.0
564
+ args [minArg ] = 0.0
565
+ args [maxArg ] = 1.0
414
566
args ["round" ] = 2
415
567
}
416
568
return GenTypeFloat , args
0 commit comments