1
package GenTest::App::Gendata;
8
use GenTest::Constants;
10
use GenTest::Executor;
14
use constant FIELD_TYPE => 0;
15
use constant FIELD_CHARSET => 1;
16
use constant FIELD_COLLATION => 2;
17
use constant FIELD_SIGN => 3;
18
use constant FIELD_NULLABILITY => 4;
19
use constant FIELD_INDEX => 5;
20
use constant FIELD_AUTO_INCREMENT => 6;
21
use constant FIELD_SQL => 7;
22
use constant FIELD_INDEX_SQL => 8;
23
use constant FIELD_NAME => 9;
25
use constant TABLE_ROW => 0;
26
use constant TABLE_ENGINE => 1;
27
use constant TABLE_CHARSET => 2;
28
use constant TABLE_COLLATION => 3;
29
use constant TABLE_ROW_FORMAT => 4;
30
use constant TABLE_PARTITION => 5;
31
use constant TABLE_PK => 6;
32
use constant TABLE_SQL => 7;
33
use constant TABLE_NAME => 8;
34
use constant TABLE_VIEWS => 9;
35
use constant TABLE_MERGES => 10;
36
use constant TABLE_NAMES => 11;
38
use constant DATA_NUMBER => 0;
39
use constant DATA_STRING => 1;
40
use constant DATA_BLOB => 2;
41
use constant DATA_TEMPORAL => 3;
42
use constant DATA_ENUM => 4;
45
use constant GD_CONFIG => 0;
46
use constant GD_DEBUG => 1;
47
use constant GD_DSN => 2;
48
use constant GD_SEED => 3;
49
use constant GD_ENGINE => 4;
50
use constant GD_ROWS => 5;
51
use constant GD_VIEWS => 6;
52
use constant GD_VARCHAR_LENGTH => 7;
53
use constant GD_SERVER_ID => 8;
58
my $self = $class->SUPER::new({
59
'config_file' => GD_CONFIG,
63
'engine' => GD_ENGINE,
66
'varchar_length' => GD_VARCHAR_LENGTH,
67
'server_id' => GD_SERVER_ID},@_);
69
if (not defined $self->[GD_SEED]) {
78
return $_[0]->[GD_CONFIG];
83
return $_[0]->[GD_DEBUG];
88
return $_[0]->[GD_DSN];
93
return $_[0]->[GD_SEED];
98
return $_[0]->[GD_ENGINE];
103
return $_[0]->[GD_ROWS];
108
return $_[0]->[GD_VIEWS];
113
return $_[0]->[GD_VARCHAR_LENGTH];
118
return $_[0]->[GD_SERVER_ID];
126
my $config_file = $self->config_file();
128
my $prng = GenTest::Random->new(
129
seed => $self->seed() eq 'time' ? time() : $self->seed(),
130
varchar_length => $self->varchar_length()
133
my $executor = GenTest::Executor->newFromDSN($self->dsn());
137
# The configuration file is actually a perl script, so we read it by eval()-ing it
140
my ($tables, $fields, $data); # Configuration as read from the config file.
141
my (@table_perms, @field_perms, @data_perms); # Configuration after defaults have been substituted
143
if ($config_file ne '') {
144
open(CONF , $config_file) or die "unable to open config file '$config_file': $!";
145
read(CONF, my $config_text, -s $config_file);
147
die "Unable to load $config_file: $@" if $@;
150
$executor->execute("SET SQL_MODE= 'NO_ENGINE_SUBSTITUTION'") if $executor->type == DB_MYSQL;
151
$executor->execute("SET STORAGE_ENGINE='".$self->engine()."'") if $self->engine() ne '';
153
$table_perms[TABLE_ROW] = $tables->{rows} || (defined $self->rows() ? [ $self->rows() ] : undef ) || [0, 1, 2, 10, 100];
154
$table_perms[TABLE_ENGINE] = $tables->{engines} || [ $self->engine() ];
155
$table_perms[TABLE_CHARSET] = $tables->{charsets} || [ undef ];
156
$table_perms[TABLE_COLLATION] = $tables->{collations} || [ undef ];
157
$table_perms[TABLE_PARTITION] = $tables->{partitions} || [ undef ];
158
$table_perms[TABLE_PK] = $tables->{pk} || $tables->{primary_key} || [ 'integer auto_increment' ];
159
$table_perms[TABLE_ROW_FORMAT] = $tables->{row_formats} || [ undef ];
161
$table_perms[TABLE_VIEWS] = $tables->{views} || (defined $self->views() ? [ "" ] : undef );
162
$table_perms[TABLE_MERGES] = $tables->{merges} || undef ;
164
$table_perms[TABLE_NAMES] = $tables->{names} || [ ];
166
$field_perms[FIELD_TYPE] = $fields->{types} || [ 'int', 'varchar', 'date', 'time', 'datetime' ];
167
$field_perms[FIELD_NULLABILITY] = $fields->{null} || $fields->{nullability} || [ undef ];
168
$field_perms[FIELD_SIGN] = $fields->{sign} || [ undef ];
169
$field_perms[FIELD_INDEX] = $fields->{indexes} || $fields->{keys} || [ undef, 'KEY' ];
170
$field_perms[FIELD_CHARSET] = $fields->{charsets} || [ undef ];
171
$field_perms[FIELD_COLLATION] = $fields->{collations} || [ undef ];
173
$data_perms[DATA_NUMBER] = $data->{numbers} || ['digit', 'digit', 'digit', 'digit', 'null' ]; # 20% NULL values
174
$data_perms[DATA_STRING] = $data->{strings} || ['letter', 'letter', 'letter', 'letter', 'null' ];
175
$data_perms[DATA_BLOB] = $data->{blobs} || [ 'data', 'data', 'data', 'data', 'null' ];
176
$data_perms[DATA_TEMPORAL] = $data->{temporals} || [ 'date', 'time', 'datetime', 'year', 'timestamp', 'null' ];
177
$data_perms[DATA_ENUM] = $data->{enum} || ['letter', 'letter', 'letter', 'letter', 'null' ];
179
my @tables = (undef);
182
foreach my $cycle (TABLE_ROW, TABLE_ENGINE, TABLE_CHARSET, TABLE_COLLATION, TABLE_PARTITION, TABLE_PK, TABLE_ROW_FORMAT) {
185
if (not defined $table_perms[$cycle]) {
186
$old_table; # Retain old table, no permutations at this stage.
188
# Create several new tables, one for each allowed value in the current $cycle
191
my @new_table = defined $old_table ? @$old_table : [];
192
$new_table[$cycle] = lc($new_perm);
194
} @{$table_perms[$cycle]};
200
# Iteratively build the array of tables. We start with an empty array, and on each iteration
201
# we increase the size of the array to contain more combinations.
203
# Then we do the same for fields.
206
my @fields = (undef);
208
foreach my $cycle (FIELD_TYPE, FIELD_NULLABILITY, FIELD_SIGN, FIELD_INDEX, FIELD_CHARSET, FIELD_COLLATION) {
211
if (not defined $field_perms[$cycle]) {
212
$old_field; # Retain old field, no permutations at this stage.
214
($cycle == FIELD_SIGN) &&
215
($old_field->[FIELD_TYPE] !~ m{int|float|double|dec|numeric|fixed}sio)
217
$old_field; # Retain old field, sign does not apply to non-integer types
219
($cycle == FIELD_CHARSET) &&
220
($old_field->[FIELD_TYPE] =~ m{bit|int|bool|float|double|dec|numeric|fixed|blob|date|time|year|binary}sio)
222
$old_field; # Retain old field, charset does not apply to integer types
224
# Create several new fields, one for each allowed value in the current $cycle
227
my @new_field = defined $old_field ? @$old_field : [];
228
$new_field[$cycle] = lc($new_perm);
230
} @{$field_perms[$cycle]};
235
# If no fields were defined, continue with just the primary key.
236
@fields = () if ($#fields == 0) && ($fields[0]->[FIELD_TYPE] eq '');
238
foreach my $field_id (0..$#fields) {
239
my $field = $fields[$field_id];
240
next if not defined $field;
241
my @field_copy = @$field;
243
# $field_copy[FIELD_INDEX] = 'nokey' if $field_copy[FIELD_INDEX] eq '';
246
$field_name = join('_', grep { $_ ne '' } @field_copy)."_f";
247
$field_name =~ s{[^A-Za-z0-9]}{_}sgio;
248
$field_name =~ s{ }{_}sgio;
249
$field_name =~ s{_+}{_}sgio;
250
$field_name =~ s{_+$}{}sgio;
252
$field->[FIELD_NAME] = $field_name;
255
($field_copy[FIELD_TYPE] =~ m{set|enum}sio) &&
256
($field_copy[FIELD_TYPE] !~ m{\(}sio )
258
$field_copy[FIELD_TYPE] .= " (".join(',', map { "'$_'" } ('a'..'z') ).")";
262
($field_copy[FIELD_TYPE] =~ m{char}sio) &&
263
($field_copy[FIELD_TYPE] !~ m{\(}sio)
265
$field_copy[FIELD_TYPE] .= ' (1)';
268
$field_copy[FIELD_CHARSET] = "CHARACTER SET ".$field_copy[FIELD_CHARSET] if $field_copy[FIELD_CHARSET] ne '';
269
$field_copy[FIELD_COLLATION] = "COLLATE ".$field_copy[FIELD_COLLATION] if $field_copy[FIELD_COLLATION] ne '';
274
($field_copy[FIELD_TYPE] =~ m{blob|text|binary}sio ) &&
275
($field_copy[FIELD_TYPE] !~ m{\(}sio )
281
($field_copy[FIELD_INDEX] ne 'nokey') &&
282
($field_copy[FIELD_INDEX] ne '')
284
$field->[FIELD_INDEX_SQL] = $field_copy[FIELD_INDEX]." (`$field_name` $key_len)";
287
delete $field_copy[FIELD_INDEX]; # do not include FIELD_INDEX in the field description
289
$fields[$field_id]->[FIELD_SQL] = "`$field_name` ". join(' ' , grep { $_ ne '' } @field_copy);
291
if ($field_copy[FIELD_TYPE] =~ m{timestamp}sio ) {
292
$field->[FIELD_SQL] .= ' NULL DEFAULT 0';
296
foreach my $table_id (0..$#tables) {
297
my $table = $tables[$table_id];
298
my @table_copy = @$table;
300
if ($#{$table_perms[TABLE_NAMES]} > -1) {
301
$table->[TABLE_NAME] = shift @{$table_perms[TABLE_NAMES]};
304
$table_name = "table".join('_', grep { $_ ne '' } @table_copy);
305
$table_name =~ s{[^A-Za-z0-9]}{_}sgio;
306
$table_name =~ s{ }{_}sgio;
307
$table_name =~ s{_+}{_}sgio;
308
$table_name =~ s{auto_increment}{autoinc}siog;
309
$table_name =~ s{partition_by}{part_by}siog;
310
$table_name =~ s{partition}{part}siog;
311
$table_name =~ s{partitions}{parts}siog;
312
$table_name =~ s{values_less_than}{}siog;
313
$table_name =~ s{integer}{int}siog;
316
(uc($table_copy[TABLE_ENGINE]) eq 'MYISAM') ||
317
($table_copy[TABLE_ENGINE] eq '')
319
push @myisam_tables, $table_name;
322
$table->[TABLE_NAME] = $table_name;
325
$table_copy[TABLE_ENGINE] = "ENGINE=".$table_copy[TABLE_ENGINE] if $table_copy[TABLE_ENGINE] ne '';
326
$table_copy[TABLE_ROW_FORMAT] = "ROW_FORMAT=".$table_copy[TABLE_ROW_FORMAT] if $table_copy[TABLE_ROW_FORMAT] ne '';
327
$table_copy[TABLE_CHARSET] = "CHARACTER SET ".$table_copy[TABLE_CHARSET] if $table_copy[TABLE_CHARSET] ne '';
328
$table_copy[TABLE_COLLATION] = "COLLATE ".$table_copy[TABLE_COLLATION] if $table_copy[TABLE_COLLATION] ne '';
329
$table_copy[TABLE_PARTITION] = "/*!50100 PARTITION BY ".$table_copy[TABLE_PARTITION]." */" if $table_copy[TABLE_PARTITION] ne '';
331
delete $table_copy[TABLE_ROW]; # Do not include number of rows in the CREATE TABLE
332
delete $table_copy[TABLE_PK]; # Do not include PK definition at the end of CREATE TABLE
334
$table->[TABLE_SQL] = join(' ' , grep { $_ ne '' } @table_copy);
337
foreach my $table_id (0..$#tables) {
338
my $table = $tables[$table_id];
339
my @table_copy = @$table;
340
my @fields_copy = @fields;
342
if (uc($table->[TABLE_ENGINE]) eq 'FALCON') {
343
@fields_copy = grep {
344
!($_->[FIELD_TYPE] =~ m{blob|text}io && $_->[FIELD_INDEX] ne '')
348
say("# Creating table $table_copy[TABLE_NAME] .");
350
if ($table_copy[TABLE_PK] ne '') {
352
$pk_field->[FIELD_NAME] = 'pk';
353
$pk_field->[FIELD_TYPE] = $table_copy[TABLE_PK];
354
$pk_field->[FIELD_INDEX] = 'primary key';
355
$pk_field->[FIELD_INDEX_SQL] = 'primary key (pk)';
356
$pk_field->[FIELD_SQL] = 'pk '.$table_copy[TABLE_PK];
357
push @fields_copy, $pk_field;
360
# Make field ordering in every table different.
361
# This exposes bugs caused by different physical field placement
363
$prng->shuffleArray(\@fields_copy);
365
$executor->execute("DROP TABLE /*! IF EXISTS*/ $table->[TABLE_NAME]");
367
# Compose the CREATE TABLE statement by joining all fields and indexes and appending the table options
369
my @field_sqls = join(",\n", map { $_->[FIELD_SQL] } @fields_copy);
372
if ($executor->type() == DB_MYSQL || $executor->type() == DB_DRIZZLE) {
373
@index_fields = grep { $_->[FIELD_INDEX_SQL] ne '' } @fields_copy;
375
## Just keep the primary keys.....
376
@index_fields = grep { $_->[FIELD_INDEX_SQL] =~ m/primary/ } @fields_copy;
379
my $index_sqls = $#index_fields > -1 ? join(",\n", map { $_->[FIELD_INDEX_SQL] } @index_fields) : undef;
381
$executor->execute("CREATE TABLE `$table->[TABLE_NAME]` (\n".join(",\n\t", grep { defined $_ } (@field_sqls, $index_sqls) ).") $table->[TABLE_SQL] ");
386
if (defined $table_perms[TABLE_VIEWS]) {
387
foreach my $view_id (0..$#{$table_perms[TABLE_VIEWS]}) {
388
my $view_name = 'v'.$table->[TABLE_NAME]."_$view_id";
389
$executor->execute("CREATE OR REPLACE ".uc($table_perms[TABLE_VIEWS]->[$view_id])." VIEW `$view_name` AS SELECT * FROM `$table->[TABLE_NAME]`");
393
if ($table->[TABLE_ROW] > 1000) {
394
$executor->execute("SET AUTOCOMMIT=OFF");
395
$executor->execute("START TRANSACTION");
399
foreach my $row_id (1..$table->[TABLE_ROW]) {
401
foreach my $field (@fields_copy) {
404
if ($field->[FIELD_INDEX] eq 'primary key') {
405
if ($field->[FIELD_TYPE] =~ m{auto_increment}sio) {
406
$value = undef; # Trigger auto-increment by inserting NULLS for PK
408
$value = $row_id; # Otherwise, insert sequential numbers
411
my (@possible_values, $value_type);
413
if ($field->[FIELD_TYPE] =~ m{date|time|year}sio) {
414
$value_type = DATA_TEMPORAL;
415
} elsif ($field->[FIELD_TYPE] =~ m{blob|text|binary}sio) {
416
$value_type = DATA_BLOB;
417
} elsif ($field->[FIELD_TYPE] =~ m{int|float|double|dec|numeric|fixed|bool|bit}sio) {
418
$value_type = DATA_NUMBER;
419
} elsif ($field->[FIELD_TYPE] eq 'enum') {
420
$value_type = DATA_ENUM;
422
$value_type = DATA_STRING;
425
if ($field->[FIELD_NULLABILITY] eq 'not null') {
426
# Remove NULL from the list of allowed values
427
@possible_values = grep { lc($_) ne 'null' } @{$data_perms[$value_type]};
429
@possible_values = @{$data_perms[$value_type]};
432
die("# Unable to generate data for field '$field->[FIELD_TYPE] $field->[FIELD_NULLABILITY]'") if $#possible_values == -1;
434
my $possible_value = $prng->arrayElement(\@possible_values);
435
$possible_value = $field->[FIELD_TYPE] if not defined $possible_value;
437
if ($prng->isFieldType($possible_value)) {
438
$value = $prng->fieldType($possible_value);
440
$value = $possible_value; # A simple string literal as specified
444
# Blob values are generated as LOAD_FILE , so do not quote them.
445
if ($value =~ m{load_file}sio) {
446
push @data, defined $value ? $value : "NULL";
448
$value =~ s{'}{\\'}sgio;
449
push @data, defined $value ? "'$value'" : "NULL";
453
push @row_buffer, " (".join(', ', @data).") ";
456
(($row_id % 10) == 0) ||
457
($row_id == $table->[TABLE_ROW])
459
$executor->execute("INSERT /*! IGNORE */ INTO $table->[TABLE_NAME] VALUES ".join(', ', @row_buffer));
463
if (($row_id % 10000) == 0) {
464
$executor->execute("COMMIT");
465
say("# Progress: loaded $row_id out of $table->[TABLE_ROW] rows");
468
$executor->execute("COMMIT");
471
$executor->execute("COMMIT");
474
(defined $table_perms[TABLE_MERGES]) &&
475
($#myisam_tables > -1)
477
foreach my $merge_id (0..$#{$table_perms[TABLE_MERGES]}) {
478
my $merge_name = 'merge_'.$merge_id;
479
$executor->execute("CREATE TABLE `$merge_name` LIKE `".$myisam_tables[0]."`");
480
$executor->execute("ALTER TABLE `$merge_name` ENGINE=MERGE UNION(".join(',',@myisam_tables).") ".uc($table_perms[TABLE_MERGES]->[$merge_id]));