File Coverage

blib/lib/Data/HandyGen/mysql.pm
Criterion Covered Total %
statement 93 328 28.3
branch 38 134 28.3
condition 32 107 29.9
subroutine 19 43 44.1
pod 2 8 25.0
total 184 620 29.6


line stmt bran cond sub pod time code
1             package Data::HandyGen::mysql;
2              
3 26     26   4059214 use strict;
  26         316  
  26         780  
4 26     26   158 use warnings;
  26         53  
  26         660  
5              
6 26     26   485 use 5.008;
  26         123  
7             our $VERSION = '0.0.5';
8             $VERSION = eval $VERSION;
9              
10              
11             # precision and scale of float value.
12             # They may be changed from outside this module.
13             our $FLOAT_PRECISION = 4;
14             our $FLOAT_SCALE = 2;
15              
16             our $DISTINCT_VAL_FETCH_LIMIT = 100;
17              
18             our $RANGE_YEAR_YEAR = 20;
19             our $RANGE_YEAR_DATETIME = 2;
20              
21 26     26   3482 use DBI;
  26         35842  
  26         1603  
22 26     26   23681 use DateTime;
  26         13190719  
  26         1400  
23 26     26   310 use Carp;
  26         72  
  26         6438  
24 26     26   30505 use SQL::Maker;
  26         310738  
  26         909  
25 26     26   219 use DateTime;
  26         65  
  26         538  
26 26     26   16689 use Data::Dumper;
  26         185240  
  26         2089  
27 26     26   13494 use String::Random;
  26         88490  
  26         2017  
28             use Class::Accessor::Lite (
29 26         242 new => 1,
30             rw => [
31             'dbh', # Database handle
32             'fk', # 1: Creates record on other table referenced by main table
33             'debug' # debug mode
34             ],
35             ro => [
36             'inserted', # All inserted ids
37             'defs', # Table definitions
38             # $self->defs->{ $table_name } = (Data::HandyGen::mysql::TableDef object)
39             ],
40 26     26   239 );
  26         56  
41              
42 26     26   16876 use Data::HandyGen::mysql::TableDef;
  26         87  
  26         120662  
43              
44              
45             ###############
46             #
47             # Constants
48             #
49             ###############
50              
51             my $ONE_YEAR_SEC = 86400 * 365;
52              
53             my @VARCHAR_LIST = ( 0..9, 'a'..'z', 'A'..'Z', '_' );
54             my $COUNT_VARCHAR_LIST = scalar @VARCHAR_LIST;
55              
56             my $MAX_TINYINT_SIGNED = 127;
57             my $MAX_TINYINT_UNSIGNED = 255;
58             my $MAX_SMALLINT_SIGNED = 32767;
59             my $MAX_SMALLINT_UNSIGNED = 65535;
60             my $MAX_INT_SIGNED = 2147483647;
61             my $MAX_INT_UNSIGNED = 4294967295;
62              
63             my $LENGTH_LIMIT_VARCHAR = 20;
64              
65             my %VALUE_DEF_FUNC = (
66             char => \&_val_varchar,
67             varchar => \&_val_varchar,
68             text => \&_val_varchar,
69             tinyint => \&_val_tinyint,
70             smallint => \&_val_smallint,
71             int => \&_val_int,
72             integer => \&_val_int,
73             bigint => \&_val_int,
74             numeric => \&_val_numeric,
75             decimal => \&_val_numeric,
76             float => \&_val_float,
77             double => \&_val_float,
78             datetime => \&_val_datetime,
79             timestamp => \&_val_datetime,
80             date => \&_val_datetime,
81             year => \&_val_year,
82             );
83              
84             # When convert regex into string, some prefix and postfix is added to pattern
85             # like (?^:AAAAA)
86             # These are used to remove them from string converted from regex.
87             my ($REGEX_TO_STRING_PREFIX, $REGEX_TO_STRING_POSTFIX) = (scalar qr/AAAAA/) =~ /^(.*)AAAAA(.*)$/;
88              
89              
90             =head1 NAME
91              
92             Data::HandyGen::mysql - Generates test data for mysql easily.
93              
94              
95             =head1 VERSION
96              
97             This documentation refers to Data::HandyGen::mysql version 0.0.5
98              
99              
100             =head1 SYNOPSIS
101              
102             use DBI;
103             use Data::HandyGen::mysql;
104              
105             my $dbh = DBI->connect('dbi:mysql:test', 'user', 'pass');
106              
107             my $hd = Data::HandyGen::mysql->new( fk => 1 );
108             $hd->dbh($dbh);
109              
110              
111             # -- table definitions --
112             #
113             # create table category (
114             # id integer primary key,
115             # name varchar(20) not null
116             # );
117             #
118             # create table item (
119             # id integer primary key auto_increment,
120             # category_id interger not null,
121             # name varchar(20) not null,
122             # price integer not null,
123             # constraint foreign key (category_id) references category(id)
124             # );
125              
126              
127             # 1.
128             # Insert one row to 'item'.
129             # 'category_id', 'name' and 'price' will be random values.
130             # category_id refers to category.id, so the value will be selected one of values in category.id.
131             # If table 'category' has no record, new record will be added to 'category'.
132              
133             my $id = $hd->insert('item');
134              
135             # Result example:
136             # [item]
137             # id: 1
138             # category_id: 497364651
139             # name: name_1
140             # price: 597348646
141             #
142             # [category]
143             # id: 497364651
144             # name: name_497364651
145             #
146              
147             print "ID: $id\n"; # 'ID: 1'
148              
149              
150             # 2.
151             # Insert one row to 'item' with name = 'Banana'.
152             # category_id and price will be random values.
153              
154             $id = $hd->insert('item', { name => 'Banana' }); # Maybe $id == 2
155              
156             # Result example:
157             # [item]
158             # id: 2
159             # category_id: 497364651
160             # name: Banana
161             # price: 337640949
162             #
163             # [category]
164             # id: 497364651
165             # name: name_497364651
166              
167              
168             # 3.
169             # Insert one row to 'item' with category_id one of 10, 20 or 30 (selected randomly).
170             # If table 'category' has no record with id = 10, 20 nor 30,
171             # a record having one of those ids will be generated on 'category'.
172              
173             $hd->insert('item', { category_id => [ 10, 20, 30 ] });
174              
175             # Result example:
176             # [item]
177             # id: 3
178             # category_id: 20
179             # name: name_3
180             # price: 587323402
181             #
182             # [category]
183             # id: 20
184             # name: name_20
185              
186              
187             # 4.
188             # If you're interested also in category name, do this.
189              
190             $cat_id = $hd->insert('category', { name => 'Fruit' });
191             $item_id = $hd->insert('item', { category_id => $cat_id, name => 'Coconut' });
192              
193              
194             # Delete all records inserted by $hd
195             $hd->delete_all();
196              
197             # ...Or retrieve all IDs for later deletion.
198             my $ids = $hd->inserted();
199              
200              
201             =head1 DESCRIPTION
202              
203             This module generates test data and insert it into mysql tables. You only have to specify values of columns you're really interested in. Other necessary values are generated automatically.
204              
205             When we test our product, sometimes we need to create test records, but generating them is a tedious task. We should consider many constraints (not null, foreign key, etc.) and set values to many columns in many tables, even if we want to do small tests, are interested in only a few columns and don't want to care about others. Maybe this module get rid of much of those unnecessary task.
206              
207              
208             =head1 METHODS
209              
210              
211             =head2 new(dbh => $dbh, fk => $fk)
212              
213             Constructor. C<dbh> is required to be specified at here, or by calling C<< $obj->dbh($dbh) >> later. C<fk> is optional.
214              
215              
216             =head2 dbh($dbh)
217              
218             set a database handle
219              
220              
221             =head2 fk($flag)
222              
223             If specified 1, it also creates records on other tables referred by foreign key columns in main table, if necessary.
224              
225             Default is 0 (doesn't add records to other tables), so if you want to use this functionality, you need to specify 1 explicitly.
226              
227              
228             =cut
229              
230             sub _sql_maker {
231 0     0   0 my ($self) = @_;
232 0   0     0 $self->{_sql_maker} ||= SQL::Maker->new( driver => 'mysql' );
233 0         0 return $self->{_sql_maker};
234             }
235              
236              
237             # distinct values for each referenced tables/columns
238             # $self->{_distinct_val}{$table}{$column} = {
239             # 'value1' => 1,
240             # 'value2' => 1,
241             # }
242             sub _distinct_val {
243 0     0   0 my ($self) = @_;
244              
245 0   0     0 $self->{_distinct_val} ||= {};
246              
247 0         0 return $self->{_distinct_val};
248             }
249              
250              
251             =head2 insert($table_name, $valspec)
252              
253             Inserts a record to a table named $table_name.
254              
255             You can specify values of each column(s) with $valspec, a hashref which keys are columns' names in $table_name.
256              
257             $hd->insert('table1', {
258             id => 5,
259             price => 300
260             });
261              
262             =head3 format
263              
264             =over 4
265              
266             =item * colname => $scalar
267              
268             specifies a value of 'colname'
269              
270             $hd->insert('table1', { id => 5 }); # id will become 5
271              
272              
273             =item * colname => [ $val1, $val2, ... ]
274              
275             value of 'colname' will be randomly chosen from $val1, $val2, ...
276              
277             $hd->insert('table1', { id => [ 10, 20, 30 ] }) # id will become one of 10, 20 or 30
278              
279              
280             =item * colname => { random => [ $val1, $val2, ... ] }
281              
282             verbose expression of above
283              
284             =item * colname => qr/$pattern/
285              
286             value of 'colname' is determined by $pattern.
287              
288             NOTE: This function uses randregex of C<String::Random>, which does not handles real regular expression.
289              
290             $hd->insert('table1', { filename => qr/[0-9a-f]{8}\.jpg/ }); # 'a1b2c3d4.jpg'
291              
292             =item * colname => { random => qr/$pattern/ }
293              
294             verbose expression of above
295              
296             =item * colname => { range => [ $min, $max ] }
297              
298             value of 'colname' is determined between $min and $max ($min inclusive, $max exclusive). Can be used only for number(int, double, numeric, etc.).
299              
300             =item * colname => { dt_range => [ $start_datetime, $end_datetime ] }
301              
302             value of 'colname' is determined between $start_datetime and $end_datetime ($start_datetime inclusive, $end_datetime exclusive). Can be used only for date or datetime type.
303              
304             $hd->insert('table1', {
305             purchase_datetime => { dt_range => [ '2013-07-20 12:00:00', '2013-7-21 14:00:00' ] }
306             });
307              
308             $hd->insert('table2', {
309             exec_datetime => { dt_range => [ '2013-08-01', '2013-08-05' ] } # time can be ommitted
310             });
311              
312              
313             =back
314              
315             =head3 return value
316              
317             Returns a value of primary key. (Only when primary key exists and it contains only a single column. Otherwise returns undef.)
318              
319             =cut
320              
321             # XXX: I commented out lines below, because this function does not work properly.
322             #
323             #=head3 column name in other tables
324             #
325             #If you want to specify values of other tables (maybe referenced by foreign key), join table name and column name with dot(.)
326             #
327             # $valspec = {
328             # column1 => 50, # Column in the same table
329             # 'another_table.column2' => [10, 20, 30] # Column in referenced table
330             # }
331              
332             sub insert {
333 0     0 1 0 my ($self, $table_name, $table_valspec) = @_;
334              
335 0 0       0 $table_valspec
336             and $self->_set_user_valspec($table_name, $table_valspec);
337              
338 0         0 return $self->process_table($table_name);
339             }
340              
341              
342              
343             sub process_table {
344 0     0 0 0 my ($self, $table, $tmpl_valspec) = @_;
345 0         0 my $dbh = $self->dbh();
346              
347             # Reads an additional spec
348 0 0       0 $tmpl_valspec
349             and $self->_add_user_valspec($table, $tmpl_valspec);
350 0         0 $self->_print_debug("tmpl_valspec : " . Dumper($self->_valspec()));
351              
352 0         0 my $table_def = $self->_table_def($table);
353              
354             # Determines ID value.
355             # $exp_id : Expected ID. User specified value if specified, or auto_increment value if auto_increment column.
356             # $real_id : User specified value if specified. Otherwise undef.
357 0         0 my ($exp_id, $real_id) = $self->get_id($table, $tmpl_valspec);
358 0   0     0 $self->_print_debug("id is (" . ($exp_id || '(undef)') . ", " . ($real_id || '(undef)') . ")");
      0        
359              
360              
361             # columns to which we need to specify values.
362 0         0 my @colnames = $self->get_cols_requiring_value($table, $table_def->def);
363              
364              
365 0         0 my %values = ();
366              
367 0         0 for my $col (@colnames) {
368              
369 0         0 my $value;
370              
371             # (1)Primary key, and a value is specified by user.
372 0 0 0     0 if ( $table_def->is_pk($col) and defined($real_id) ) {
373 0         0 $values{$col} = $real_id;
374 0         0 next;
375             }
376              
377 0 0       0 my $col_def = $table_def->column_def($col)
378             or confess "No column def found. $col";
379              
380              
381             # (2)If $self->fk = 1 and the column is a foreign key.
382 0 0       0 if ( $self->fk ) {
383 0 0       0 if ( my $referenced_table_col = $table_def->is_fk($col) ) { # ret = { table => 'table name, column => 'column name' }
384 0 0       0 if ( ref $referenced_table_col eq 'HASH' ) {
385 0         0 $value = $self->determine_fk_value($table, $col, $referenced_table_col);
386             }
387             else {
388 0         0 warn "Currently only one foreign key per column is supported.";
389             }
390             }
391             }
392              
393             # (3)If user specified a value, use it.
394 0 0 0     0 if ( !defined($value) and defined( my $valspec_col = $self->_valspec()->{$table}{$col} ) ) {
395 0         0 $value = $self->determine_value( $valspec_col );
396             }
397              
398             # (3.5)If column default is available, use it.
399 0 0 0     0 if ( !defined($value) and defined($col_def->column_default) ) {
400 0         0 $value = $col_def->column_default;
401             }
402              
403             # (4)Otherwise, decide a value randomly.
404 0 0       0 if ( !defined($value) ) {
405              
406 0         0 my $type = $col_def->data_type;
407 0         0 my $func = $VALUE_DEF_FUNC{$type};
408              
409             # Die if the data type is not supported.
410 0 0       0 unless ($func) {
411 0         0 die "Type $type for $col is not supported.";
412             }
413              
414 0         0 $value = $self->$func($col_def, $exp_id);
415 0         0 $self->_print_debug("No rule found. Generates random value.($value)");
416              
417             }
418              
419 0         0 $values{$col} = $value;
420              
421 0 0       0 if ( $table_def->is_pk($col) ) {
422 0         0 $real_id = $value;
423             }
424             }
425              
426 0         0 eval {
427 0         0 my ($sql, @bind) = $self->_sql_maker->insert($table, \%values);
428 0         0 $self->_print_debug($sql . ", binds [" . (join ', ', @bind) . "]");
429              
430 0         0 my $sth = $dbh->prepare($sql);
431 0         0 $sth->execute(@bind);
432 0         0 $sth->finish;
433             };
434 0 0       0 if ($@) {
435 0         0 confess $@
436             }
437              
438 0         0 my $inserted_id = undef;
439              
440              
441             # Handles PK value only when the table has single pk column.
442 0 0       0 if ( @{ $table_def->pk_columns() } == 1 ) {
  0         0  
443 0   0     0 $inserted_id = $real_id || $dbh->{'mysql_insertid'};
444 0         0 $self->add_inserted_id($table, $inserted_id);
445              
446 0         0 $self->_print_debug("Inserted. table = $table, id = $inserted_id");
447             }
448              
449 0         0 return $inserted_id;
450             }
451              
452              
453             sub _valspec {
454 55     55   463 my ($self, $_valspec) = @_;
455              
456 55 100       127 if ( defined $_valspec ) {
457 8 100       30 if ( ref $_valspec eq 'HASH' ) {
458 5         18 $self->{_valspec} = $_valspec;
459             }
460             else {
461 3         38 confess "Invalid valspec.";
462             }
463             }
464              
465 52   100     126 $self->{_valspec} ||= {};
466 52         237 return $self->{_valspec};
467             }
468              
469              
470             # Records an ID of inserted record.
471             sub add_inserted_id {
472 7     7 0 6470 my ($self, $table, $id) = @_;
473              
474 7 100       36 $table or confess "Missing table name";
475 6 100       24 defined $id or confess "Missing ID. table = $table";
476              
477 5   100     34 $self->{inserted}{$table} ||= [];
478 5         8 push @{ $self->{inserted}{$table} }, $id;
  5         25  
479             }
480              
481              
482              
483             # Determine a value of column according to (user-specified) rules.
484             sub determine_value {
485 21     21 0 36056 my ($self, $valspec_col) = @_;
486              
487 21 100       98 ref $valspec_col eq 'HASH'
488             or confess "Invalid valspec type." . ref($valspec_col);
489              
490 18         30 my $value;
491              
492 18 100       56 if ( exists($valspec_col->{random}) ) {
    100          
    50          
    50          
    50          
493 13         25 my $values = $valspec_col->{random};
494              
495 13 100       40 if (ref $values eq 'ARRAY') {
    100          
496 9 100       24 if (scalar(@$values) == 0) {
497 1         10 confess "Value of 'random' is an empty arrayref";
498             }
499              
500 8         60 my $ind = rand() * scalar(@$values);
501 8         21 $value = $values->[$ind];
502             }
503             elsif (ref $values eq 'Regexp') {
504 1         2 my $pattern = scalar $values;
505 1         15 $pattern =~ s/^\Q$REGEX_TO_STRING_PREFIX\E//;
506 1         10 $pattern =~ s/\Q$REGEX_TO_STRING_POSTFIX\E$//;
507 1         18 $value = String::Random::random_regex($pattern);
508             }
509             else {
510 3         43 confess "Value of 'random' is invalid. type = " . (ref $values);
511             }
512             }
513             elsif ( exists($valspec_col->{fixval}) ) {
514 4         9 my $fixval = $valspec_col->{fixval};
515 4 100       27 ref $fixval eq ''
516             or confess "Value of 'fixval' is invalid";
517              
518 2         3 $value = $fixval;
519             }
520             elsif ( exists($valspec_col->{any}) ) {
521             # Leave it null. Value will be assigned later.
522 0         0 return undef;
523             }
524             elsif ( exists($valspec_col->{range} ) ) {
525 0         0 my $spec = $valspec_col->{range};
526 0 0 0     0 ref $spec eq 'ARRAY' and @$spec == 2
527             or confess "Value of 'range' must be an arrayref with (begin, end) values";
528 0         0 $value = _get_random_range(@$spec);
529             }
530             elsif ( exists($valspec_col->{dt_range}) ) {
531 0         0 my $spec = $valspec_col->{dt_range};
532 0 0 0     0 ref $spec eq 'ARRAY' and @$spec == 2
533             or confess "Value of 'dt_range' must be an arrayref with (start, end) values";
534 0         0 $value = _get_random_dt_range(@$spec);
535             }
536              
537 12         455 return $value;
538             }
539              
540              
541             sub _get_random_range {
542 0     0   0 my ($begin, $end) = @_;
543              
544 0         0 my $value = $begin + rand($end - $begin);
545 0         0 return $value;
546             }
547              
548              
549             sub _get_random_dt_range {
550 5     5   1561 my ($start, $end) = @_;
551              
552 5         13 my $start_epoch = _get_epoch($start);
553 5         63 my $end_epoch = _get_epoch($end);
554              
555 5         102 my $value = DateTime
556             ->from_epoch( epoch => $start_epoch + rand($end_epoch - $start_epoch) )
557             ->strftime("%Y-%m-%d %H:%M:%S");
558              
559 5         1964 return $value;
560             }
561              
562              
563             sub _get_epoch {
564 10     10   22 my ($timestr) = @_;
565              
566             # time format is expected to 'yyyy-mm-dd hh:mm:ss'
567 10         53 my @ymdhms = split /\D/, $timestr;
568 10   100     100 my $dt = DateTime->new(
      100        
      100        
      100        
      100        
569             year => $ymdhms[0],
570             month => $ymdhms[1] || 1,
571             day => $ymdhms[2] || 1,
572             hour => $ymdhms[3] || 0,
573             minute => $ymdhms[4] || 0,
574             second => $ymdhms[5] || 0,
575             );
576              
577 10         3032 return $dt->epoch();
578             }
579              
580              
581              
582             # Check if a record with specified column value exists.
583             # Return value is a count of record(s).
584             sub _value_exists_in_table_col {
585 0     0   0 my ($self, $table, $col, $value) = @_;
586              
587 0 0 0     0 defined($table) and defined($col) and defined($value)
      0        
588             or confess "Invalid args (requires 3 arg)";
589              
590 0         0 my ($sql, @binds) = $self->_sql_maker->select( $table, [\'count(*)'], { $col => $value } );
591 0         0 my $sth = $self->dbh()->prepare($sql);
592 0         0 $sth->execute(@binds);
593 0         0 my $row = $sth->fetchrow_arrayref();
594              
595 0         0 $self->_print_debug("Record count : $row->[0]");
596 0         0 return $row->[0]; # count(*)
597             }
598              
599              
600             sub determine_fk_value {
601 0     0 0 0 my ($self, $table, $col, $ref) = @_;
602              
603 0         0 my $value = undef;
604              
605 0         0 my $ref_table = $ref->{table};
606 0         0 my $ref_col = $ref->{column};
607              
608 0 0 0     0 $table and $col and $ref_table and $ref_col
      0        
      0        
609             or confess "Invalid args. (requires 3 args)";
610              
611 0         0 $self->_print_debug("Column $col is a foreign key references $ref_table.$ref_col.");
612              
613 0 0 0     0 if ( my $valspec_col = $self->_valspec()->{$table}{$col} || $self->_valspec()->{$ref_table}{$ref_col} ) {
    0          
614 0         0 $self->_print_debug("Value is specified.");
615              
616             #
617             # (1)If a rule of determining the value is specified by user, apply the rule.
618             #
619 0         0 $value = $self->determine_value( $valspec_col );
620              
621             # If a referenced record does not exist in a referenced table,
622             # insert a record having the value at first.
623             #
624             # * I haven't thought it would be efficient to query every time which values
625             # in a given column in a referenced table exist. At first I used to believe
626             # it would be a good idea to query only for the first time, and cache those values
627             # for later use. But I suspected it wouldn't be a good idea. Sometimes the number of values
628             # becomes very huge, requiring big memory space. Furthermore, those values may change.
629             # So I've changed my mind to query current values every time.
630 0         0 $self->_add_record_if_not_exist($ref_table, $ref_col, $value);
631              
632             }
633             elsif ( defined( my $column_default = $self->_table_def($table)->column_def($col)->column_default ) ) {
634 0         0 $self->_print_debug("Column default is specified. value = $column_default");
635 0         0 $value = $column_default;
636 0         0 $self->_add_record_if_not_exist($ref_table, $ref_col, $value);
637              
638             }
639             else {
640 0         0 $self->_print_debug("No value is specified. Trying to retrieve list of ids from $ref_table");
641              
642             #
643             # (2)Case when no rule for the value definition specified by user
644             #
645              
646             # Retrieve values of primary key in the referenced table.
647             # Its result is like...
648             # $ref_ids => { (id1) => 1, (id2) => 1, ... }
649             #
650 0         0 my $ref_ids = $self->_get_current_distinct_values($ref_table, $ref_col);
651              
652              
653             # Pick up one of referenced values randomly, if at least one record exists.
654 0         0 my @_ref_ids = keys %$ref_ids;
655 0 0       0 if ( @_ref_ids ) {
656 0         0 $value = $_ref_ids[ int(rand() * scalar(@_ref_ids)) ];
657 0         0 $self->_print_debug("Referenced record id = $value");
658              
659             }
660             else {
661             # No record found in the referenced table, so insert here.
662 0         0 $value = $self->process_table($ref_table); # ID value would be determined randomly.
663 0         0 $self->_distinct_val()->{$ref_table}{$ref_col}{$value} = 1; # Add the ID value
664 0         0 $self->_print_debug("Referenced record created. id = $value");
665              
666             }
667             }
668              
669 0         0 return $value;
670              
671             }
672              
673              
674             # Determines ID value.
675             # Returns 2 values. One if exp_id(expected ID), which is used to determine column values
676             # other than primary key (for example, when expected id is 4001, values of column named 'foo'
677             # will be 'foo_4001' if possible.
678             # Another is real_id, which is a final value of ID column. It may be undef if no value is
679             # specified by user.
680             #
681             # TODO: Currently it works properly only when primary key consists of one column,
682             # and its type is integer.
683             sub get_id {
684 0     0 0 0 my ($self, $table) = @_;
685              
686 0         0 my $table_def = $self->_table_def($table);
687 0         0 my $pks = $table_def->pk_columns();
688              
689 0         0 my ($exp_id, $real_id);
690 0         0 for my $col (@$pks) { # for each pk columns
691              
692 0         0 my $col_def = $table_def->column_def($col);
693              
694              
695             # Verifies if PK value can be determined by the user-specified rule.
696             # If possible, $real_id will be a value determined by the rule.
697 0 0 0     0 if ( $self->_valspec()->{$table}
      0        
698             and defined( $self->_valspec()->{$table}{$col} )
699             and defined( $real_id = $self->determine_value( $self->_valspec()->{$table}{$col} ) )
700             )
701             {
702              
703             # exp_id will be the same of real_id when user-specified rule exists.
704 0         0 $exp_id = $real_id;
705              
706             }
707             else {
708              
709             # When no user-rule specified
710 0         0 $self->_print_debug("user value is not specified");
711              
712 0 0       0 if ( $col_def->is_auto_increment() ) {
713              
714             # If the PK has auto_increment attribute, retrieve a value from it.
715 0         0 $self->_print_debug("Column $col is an auto_increment");
716 0         0 $exp_id = $table_def->get_auto_increment_value();
717              
718             # real_id won't be determined until insert operation executes, so leaves it undef.
719              
720             }
721             else {
722             # There's no auto_increment attribute, so generates random value and uses it as a value of primary key.
723 0         0 $self->_print_debug("Column $col is not an auto_increment");
724 0         0 my $type = $col_def->data_type;
725 0         0 my $size = $col_def->character_maximum_length;
726 0 0       0 my $func = $VALUE_DEF_FUNC{$type}
727             or die "Type $type for $col not supported";
728              
729 0         0 $exp_id = $real_id = $self->$func($col_def);
730              
731             }
732             }
733             }
734              
735 0         0 return ($exp_id, $real_id);
736             }
737              
738              
739              
740             # Make a list of columns which need a value at an insert operation.
741             sub get_cols_requiring_value {
742 0     0 0 0 my ($self, $table) = @_;
743              
744 0         0 my $table_def = $self->_table_def($table);
745              
746 0         0 my @cols = ();
747 0         0 for my $col ( $table_def->colnames ) {
748              
749             # When user specifies a rule of determining value, uses it every time.
750             # If not, checks if any column definition (like 'auto_increment') can be used
751             # as a rule.
752 0 0       0 if ( defined( $self->_valspec()->{$table}{$col} ) ) {
753 0         0 $self->_print_debug("column $col has a valspec, so value is needed");
754 0         0 push @cols, $col;
755             }
756             else {
757              
758 0         0 my $col_def = $table_def->column_def($col);
759              
760             # we do not need to specify a value of auto_increment column. Skip it.
761 0 0       0 if ( $col_def->is_auto_increment ) {
762 0         0 $self->_print_debug("column $col is auto_increment, so no need to assign value.");
763 0         0 next;
764             }
765              
766             #
767             # I used to believe that DEFAULT value could be used if exists, so
768             # I should skip the column having DEFAULT value.
769             # But I found it wouldn't work properly when the column has
770             # foreign key constraint too, because it seemes there would be
771             # no way to add a record to referenced table.
772             # So I've changed the way assuming the user rule would be specified
773             # as the DEFAULT value.
774             #
775             # Skip only when the column isn't a foreign key and has default value.
776 0 0 0     0 if ( defined($col_def->column_default) and not $table_def->is_fk($col) ) {
777 0         0 $self->_print_debug("column $col has default value and not FK, so no need to assign value");
778 0         0 next;
779             }
780              
781             # When NULL value is accetable, skip the column.
782 0 0       0 if ( $col_def->is_nullable eq 'YES' ) {
783 0         0 $self->_print_debug("column $col is nullable, so no need to assign a value");
784 0         0 next;
785             }
786              
787 0         0 $self->_print_debug("column $col needs a value");
788 0         0 push @cols, $col;
789             }
790              
791             }
792              
793 0 0       0 return wantarray ? @cols : [ @cols ];
794             }
795              
796              
797             sub _table_def {
798 0     0   0 my ($self, $table) = @_;
799              
800 0   0     0 $self->{_table_def}{$table}
801             ||= Data::HandyGen::mysql::TableDef->new( dbh => $self->dbh, table_name => $table );
802              
803 0         0 return $self->{_table_def}{$table};
804             }
805              
806              
807              
808             # _val_varchar($col_def, $exp_id)
809             #
810             # Creates a new varchar value.
811             #
812             # $col_def : ColumnDef object.
813             # $exp_id : an expected value of primary key.
814             #
815             sub _val_varchar {
816 0     0   0 my ($self, $col_def, $exp_id) = @_;
817              
818 0         0 my $maxlen = $col_def->character_maximum_length;
819 0         0 $self->_print_debug("Maxlen is $maxlen");
820              
821 0 0       0 if ( defined $exp_id ) {
822 0         0 my $pk_length = length($exp_id);
823 0         0 my $colname = $col_def->name;
824 0         0 my $colname_length = length($colname);
825              
826 0 0       0 if ( $colname_length + $pk_length + 1 <= $maxlen ) { # (colname)_(num)
    0          
    0          
827 0         0 return sprintf("%s_%d", $colname, $exp_id);
828             }
829             elsif ( $pk_length + 1 <= $maxlen ) { # (part_of_colname)_(num)
830 0         0 my $part_of_colname = substr($colname, 0, $maxlen - $pk_length - 1);
831 0         0 return sprintf("%s_%d", $part_of_colname, $exp_id);
832             }
833             elsif ( $pk_length == $maxlen ) {
834 0         0 return $exp_id;
835             }
836             }
837              
838 0 0       0 $maxlen > $LENGTH_LIMIT_VARCHAR
839             and $maxlen = $LENGTH_LIMIT_VARCHAR;
840 0         0 $self->_print_debug("Maxlen is $maxlen");
841              
842 0         0 my $string = '';
843 0         0 for (1 .. $maxlen) {
844 0         0 $string .= $VARCHAR_LIST[ int( rand() * $COUNT_VARCHAR_LIST ) ];
845             }
846 0         0 $self->_print_debug("Result string is $string");
847              
848 0         0 return $string;
849              
850             }
851              
852              
853             sub _val_tinyint {
854 0     0   0 my ($self, $col_def) = @_;
855              
856 0         0 my $type = $col_def->column_type;
857              
858 0 0 0     0 return (($type || '') =~ /unsigned/) ? int(rand() * $MAX_TINYINT_UNSIGNED) : int(rand() * $MAX_TINYINT_SIGNED);
859             }
860              
861              
862             sub _val_smallint {
863 0     0   0 my ($self, $col_def) = @_;
864              
865 0         0 my $type = $col_def->column_type;
866              
867 0 0 0     0 return (($type || '') =~ /unsigned/) ? int(rand() * $MAX_SMALLINT_UNSIGNED) : int(rand() * $MAX_SMALLINT_SIGNED);
868             }
869              
870             sub _val_int {
871 0     0   0 my ($self, $col_def) = @_;
872              
873 0         0 my $type = $col_def->column_type;
874              
875 0 0 0     0 return (($type || '') =~ /unsigned/) ? int(rand() * $MAX_INT_UNSIGNED) : int(rand() * $MAX_INT_SIGNED);
876             }
877              
878              
879             sub _make_float {
880 0     0   0 my ($precision, $scale) = @_;
881              
882 0         0 my $num = '';
883 0         0 $num .= int(rand() * 10) for 1 .. $precision - $scale;
884 0 0       0 if ( $num =~ /^0+$/ ) {
885 0         0 $num = '0'
886             }
887             else {
888 0         0 $num =~ s/^0+//;
889             }
890              
891 0 0       0 if ( $scale > 0 ) {
892 0         0 $num .= '.';
893 0         0 my $frac = '';
894 0         0 $frac .= int(rand() * 10) for 1 .. $scale;
895 0 0       0 if ( $frac =~ /^0+$/ ) {
896 0         0 $frac = '0';
897             }
898             else {
899 0         0 $frac =~ s/0+$//;
900             }
901              
902 0         0 $num .= $frac;
903             }
904              
905 0         0 return $num;
906             }
907              
908              
909             sub _val_numeric {
910 0     0   0 my ($self, $col_def) = @_;
911              
912 0         0 my $precision = $col_def->numeric_precision;
913 0         0 my $scale = $col_def->numeric_scale;
914              
915 0         0 return _make_float($precision, $scale);
916             }
917              
918              
919             sub _val_float {
920 0     0   0 my ($self, $col_def) = @_;
921              
922 0         0 my $type = $col_def->column_type;
923              
924 0         0 return _make_float($FLOAT_PRECISION, $FLOAT_SCALE);
925             }
926              
927              
928              
929             sub _val_datetime {
930 0     0   0 my ($self, $col_def) = @_;
931              
932 0         0 my $dt = DateTime->from_epoch( epoch => time + rand() * $RANGE_YEAR_DATETIME * $ONE_YEAR_SEC - $ONE_YEAR_SEC );
933              
934 0 0       0 if ($col_def->data_type eq 'date') {
935 0         0 return $dt->ymd('-');
936             }
937             else {
938 0         0 return $dt->ymd('-') . ' ' . $dt->hms(':');
939             }
940             }
941              
942              
943             sub _val_year {
944 0     0   0 my $dt = DateTime->from_epoch( epoch => time + rand() * $RANGE_YEAR_YEAR * $ONE_YEAR_SEC - $ONE_YEAR_SEC );
945              
946 0         0 return $dt->year();
947             }
948              
949              
950             #
951             # _get_current_distinct_values($table, $col)
952             #
953             # Returns some distinct values in the specified $table and specified $col.
954             #
955             sub _get_current_distinct_values {
956 0     0   0 my ($self, $table, $col) = @_;
957              
958 0         0 my $current;
959              
960             # At first, I tried to cache distinct values, but when user delete records,
961             # those cached values are incorrect, and this module has no idea
962             # which records have been already deleted.
963             # So I decide not to cache distinct values and query them every time.
964              
965             #my $current = $self->_distinct_val()->{$table}{$col};
966             #if ( !defined $current or keys %$current == 0 ) {
967              
968             # SELECT DISTINCT $col FROM $table LIMIT $DISTINCT_VAL_FETCH_LIMIT;
969 0         0 my $select = $self->_sql_maker->new_select(distinct => 1);
970 0         0 my ($sql, @bind) = $select->add_select($col)
971             ->add_from($table)
972             ->limit($DISTINCT_VAL_FETCH_LIMIT)
973             ->as_sql();
974              
975 0         0 my $res = $self->dbh()->selectall_arrayref($sql, undef, @bind);
976              
977 0         0 my %values = map { $_->[0] => 1 } @$res;
  0         0  
978              
979 0         0 $current = $self->_distinct_val()->{$table}{$col} = { %values };
980             #}
981              
982 0         0 return $current;
983             }
984              
985              
986             #
987             # _set_user_valspec($table_name, $valspec)
988             #
989             # Specifies user-defined rules for determining values of columns.
990             # Previous rules will be cleared.
991             #
992             sub _set_user_valspec {
993 3     3   18 my ($self, $table, $table_valspec) = @_;
994              
995             # Clear previous valspec
996 3         14 $self->_valspec({});
997              
998 3         10 $self->_add_user_valspec($table, $table_valspec);
999             }
1000              
1001              
1002             #
1003             # _add_user_valspec($table, $table_valspec)
1004             #
1005             # Specifies user-defined rules for determining values of columns.
1006             # Previous rules will remain and new rules will be added.
1007             #
1008             sub _add_user_valspec {
1009 17     17   7143 my ($self, $table, $table_valspec) = @_;
1010              
1011 17 100 66     110 defined $table and length($table) > 0
1012             or confess "Missing table name";
1013              
1014 16 100 100     82 defined $table_valspec and ref $table_valspec eq 'HASH'
1015             or confess "Invalid user valspec.";
1016              
1017              
1018 14         49 for my $col (keys %$table_valspec) {
1019              
1020 14         29 my $_table = $table;
1021 14         19 my $_col = $col;
1022              
1023 14 100       51 if ( $col =~ /\./ ) {
1024 5         24 ($_table, $_col, my @_dummy) = split '\.', $col;
1025              
1026             # column name may include only one dot.
1027 5 100 66     64 defined($_table) and length($_table) > 0
      100        
      66        
      100        
1028             and defined($_col) and length($_col) > 0
1029             and @_dummy == 0
1030             or confess "Invalid column name : $col";
1031             }
1032              
1033 11         25 my $val = $table_valspec->{$col};
1034              
1035             # At first, clear all values with the same key.
1036 11         27 delete $self->_valspec()->{$_table}{$_col};
1037              
1038 11 100 66     66 if ( ref $val eq 'ARRAY' or ref $val eq 'Regexp' ) {
    50 33        
    50          
    50          
1039             # arrayref : select one from the list randomly.
1040 5         14 $self->_valspec()->{$_table}{$_col}{random} = $val;
1041              
1042             }
1043             elsif ( ref $val eq 'HASH' ) {
1044             # hash :
1045             # currently { random => [ ... ] } or { fixval => $scalar }
1046             # may be specified.
1047 0         0 for (keys %$val) {
1048 0         0 $self->_valspec()->{$_table}{$_col}{$_} = $val->{$_};
1049             }
1050              
1051             }
1052             elsif ( ref $val eq 'SCALAR' and $$val eq 'any' ) {
1053             # scalarref to string 'any'
1054             # determine value randomly.
1055 0         0 $self->_valspec()->{$_table}{$_col}{any} = 1;
1056             }
1057             elsif ( ref $val eq '' ) {
1058             # scalar : fix value
1059 6         15 $self->_valspec()->{$_table}{$_col}{fixval} = $val;
1060              
1061             }
1062             else {
1063 0           confess "Invalid spec of column. Column name = [$col]";
1064             }
1065              
1066             }
1067              
1068             }
1069              
1070              
1071             =head2 inserted()
1072              
1073             Returns all primary keys of inserted records by this instance. Returned value is a hashref like this:
1074              
1075             my $ret = $hd->inserted();
1076              
1077             # $ret = {
1078             # 'table_name1' => [ 10, 11 ],
1079             # 'table_name2' => [ 100, 110, 120 ],
1080             # };
1081              
1082             CAUTION: inserted() ignores records with no primary key, or primary key with multiple columns.
1083              
1084             =cut
1085              
1086              
1087              
1088             =head2 delete_all()
1089              
1090             deletes all rows inserted by this instance.
1091              
1092             CAUTION: delete_all() won't delete rows in tables which don't have primary key, or which have primary key with multiple columns.
1093              
1094             =cut
1095              
1096             sub delete_all {
1097 0     0 1   my ($self) = @_;
1098              
1099 0           my $dbh = $self->dbh();
1100              
1101 0           my $fk_check = $self->_check_fk_check_status();
1102              
1103 0 0 0       if ( $fk_check eq 'ON' or $fk_check == 1 ) {
1104 0           $dbh->do('SET FOREIGN_KEY_CHECKS = 0');
1105             }
1106              
1107 0           for my $table ( keys %{ $self->inserted() } ) {
  0            
1108 0           my $pk_name = $self->_table_def($table)->pk_columns()->[0];
1109              
1110 0           for my $val ( @{ $self->inserted->{$table} } ) {
  0            
1111 0           my ($sql, @bind) = $self->_sql_maker->delete($table, { $pk_name => $val });
1112 0           $dbh->do($sql, undef, @bind);
1113 0           $self->_print_debug(qq{DELETE FROM `$table` WHERE `$pk_name` = "$val"});
1114             }
1115             }
1116              
1117 0 0 0       if ( $fk_check eq 'ON' or $fk_check == 1 ) {
1118 0           $dbh->do('SET FOREIGN_KEY_CHECKS = 1');
1119             }
1120             }
1121              
1122              
1123             sub _check_fk_check_status {
1124 0     0     my ($self) = @_;
1125              
1126 0           my @rows = $self->dbh->selectrow_array(q{SHOW VARIABLES LIKE '%foreign_key_checks%'});
1127              
1128 0           return $rows[1];
1129             }
1130              
1131              
1132             #
1133             # _add_record_if_not_exist($table, $col, $value)
1134             #
1135             # Inserts a record only if record(s) which value of column $col is $value doesn't exist.
1136             #
1137             sub _add_record_if_not_exist {
1138 0     0     my ($self, $table, $col, $value) = @_;
1139              
1140 0 0         if ( 0 == $self->_value_exists_in_table_col($table, $col, $value) ) { # No record exists
1141 0           $self->process_table($table, { $col => $value });
1142 0           $self->_print_debug("A referenced record created. id = $value");
1143             }
1144             }
1145              
1146              
1147              
1148             sub _print_debug {
1149 0     0     my ($self, $message) = @_;
1150              
1151 0 0         if ( $self->debug ) {
1152 0           print "$message\n";
1153             }
1154             }
1155              
1156              
1157             1;
1158              
1159              
1160              
1161             __END__
1162              
1163              
1164             =head1 BUGS AND LIMITATIONS
1165              
1166             There are still many limitations with this module. I'll fix them later.
1167              
1168             Please report problems to Egawata C<< (egawa.takashi at gmail com) >>
1169             Patches are welcome.
1170              
1171             =head3 Only primary key with single column is supported.
1172              
1173             Although it works when inserting a record into a table which primary key consists of multiple columns, C<< insert() >> won't return a value of primary key just inserted.
1174              
1175              
1176             =head3 Foreign key constraint which has multiple columns is not supported.
1177              
1178             For now, if you want to use this module with such a table, specify those values explicitly.
1179              
1180              
1181             =head3 Multiple foreign key constraints to the same column are not supported.
1182              
1183             For now, if you want to use this module with such a table, specify those values explicitly.
1184              
1185              
1186             =head3 Some data types are not supported.
1187              
1188             For example, C<< blob >> or C<< set >> aren't supported. The values of those columns won't be auto-generated.
1189              
1190              
1191             =head1 AUTHOR
1192              
1193             Takashi Egawa (C<< egawa.takashi at gmail com >>)
1194              
1195              
1196             =head1 LICENCE AND COPYRIGHT
1197              
1198             Copyright (c)2012-2018 Takashi Egawa (C<< egawa.takashi at gmail com >>). All rights reserved.
1199              
1200             This module is free software; you can redistribute it and/or
1201             modify it under the same terms as Perl itself. See L<perlartistic>.
1202              
1203             This program is distributed in the hope that it will be useful,
1204             but WITHOUT ANY WARRANTY; without even the implied warranty of
1205             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.