File Coverage

blib/lib/Test/DataLoader/MySQL.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Test::DataLoader::MySQL;
2 5     5   345735 use strict;
  5         16  
  5         255  
3 5     5   28 use warnings;
  5         10  
  5         163  
4 5     5   13920 use DBI;
  5         36122  
  5         214  
5 5     5   7922 use DBD::mysql;
  0            
  0            
6             use Carp;
7             use base qw(Exporter);
8             our $VERSION = '0.1.0';
9             use 5.008;
10              
11             =head1 NAME
12              
13             Test::DataLoader::MySQL - Load testdata into MySQL database
14              
15             =head1 SYNOPSIS
16              
17             my $data = Test::DataLoader::MySQL->new($dbh);
18             $data->add('foo', #table name
19             1, # data id
20             {# data_href: column => value
21             id => 1,
22             name => 'aaa',
23             },
24             ['id']); # primary keys
25             $data->add('foo', 2,
26             {
27             id => 2,
28             name => 'bbb',
29             },
30             ['id']);
31             $data->load('foo', 1); #load data into database
32             # ... tests using database
33             $data->clear;# when finished
34              
35             if table has auto_increment
36              
37             $data->add('foo', 1,
38             {
39             name => 'aaa',
40             },
41             ['id']);
42             my $keys = $data->load('foo', 1);#load data and get auto_increment
43             is( $keys->{id}, 2); # get key value(generated by auto_increment)
44             # ... tests using database
45             $data->clear;# when finished
46              
47             read from external file
48              
49             # data.pm
50             my $data = Test::DataLoader::MySQL->init(); # use init(not new)
51             $data->add('foo', 1,
52             {
53             id => 1,
54             name => 'aaa',
55             },
56             ['id']);
57             # in your testcode
58             my $data = Test::DataLoader::MySQL->new($dbh);
59             $data->load('foo', 1);
60             # ... tests using database
61             $data->clear;# when finished
62              
63             =head1 DESCRIPTION
64              
65             Load testdata into MySQL database.
66              
67             =cut
68              
69             =head1 methods
70              
71             =cut
72              
73             my $singleton; #instance object is shared for reading data from external file.
74             END {#delete data if test code is abort
75             if (defined $singleton && @{ $singleton->{loaded} } ) {
76             $singleton->clear;
77             }
78             }
79              
80             =head2 new($dbh, %options)
81              
82             create new instance
83             parameter $dbh(provided by DBI) is required;
84             If Keep option is NOT specified(default), loaded data is deleted when instance is destroyed, otherwise(specified Keep option) loaded data is remain.
85              
86             #$dbh = DBI->connect(...);
87              
88             my $data = Test::DataLoader::MySQL->new($dbh); # loaded data is deleted when $data is DESTROYed
89             # or
90             my $data = Test::DataLoader::MySQL->new($dbh, Keep => 1); # loaded data is remain
91             # or
92             my $data = Test::DataLoader::MySQL->new($dbh, DeleteBeforeInsert => 1); # delete data which has same keys before load
93              
94             if you want to use external file and in external file, use init() instead of new().
95              
96             =cut
97              
98             sub new {
99             my $class = shift;
100             my ($dbh, %options) = @_;
101             my $self = defined $singleton ? $singleton : {};
102              
103             $self = {
104             dbh => $dbh,
105             loaded => [],
106             keynames => undef,#keys set in set_keys()
107             Keep => !!$options{Keep},
108             DeleteBeforeInsert => !!$options{DeleteBeforeInsert},
109             };
110              
111             bless $self, $class;
112             $singleton = $self;
113             return $self;
114             }
115              
116             =head2 add($table_name, $data_id, $data_href, $keynames_aref)
117              
118             add testdata into this modules (not loading testdata)
119              
120             $data->add('foo', # table_name
121             1, # data_id,
122             { # data which you want to load into database. specified by hash_ref
123             id => 1,
124             name => 'aaa',
125             },
126             ['id'] #key(s), specified by array_ref, this is important.
127             );
128              
129             table_name and data_id is like a database's key. For example, table_name is 'foo' and data_id is 1 and 'foo' and 2 is dealt with defferent data even if contained data is equal( ex id=>1, name=>'aaa').
130              
131             Key is important, because when $data is DESTROYed, this module delete all data which had been loaded and deleted data is found by specified key(s) in this method.
132              
133             if set_keys() was called before, $keynames_aref is ommittable.
134              
135             =cut
136              
137             sub add {
138             my $self = shift;
139             my ($table_name, $data_id, $data_href, $key_aref) = @_;
140              
141             carp "already exists $table_name : $data_id" if ( exists $self->{data} &&
142             exists $self->{data}->{$table_name}->{$data_id} );
143             $self->{data}->{$table_name}->{$data_id} = { data => $data_href, key => $key_aref };
144             }
145              
146             =head2 load
147              
148             load testdata from this module into database.
149              
150             $data->load('foo', 1);
151              
152             first parameter is table_name, second parameter is data_id. meaning of them are same as specified in add-method.
153             third parameter is option href, if you want to alter data with add method. for example,
154              
155             $data->add('foo', 1, { id=>1, name=>'aaa' }); #registered name is 'aaa'
156             $data->load('foo', 1, { name=>'bbb' }); #but loaded name is 'bbb' because option href is specified.
157              
158             return hash_ref. it contains database key and value. this is useful for AUTO_INCREMENT key.
159              
160             my $key = $data->load('foo', 1);
161             my $id = $key->{id};
162              
163             =cut
164              
165             sub load {
166             my $self = shift;
167             my ($table_name, $data_id, $option_href) = @_;
168              
169             my %data = $self->_data_with_option($table_name, $data_id, $option_href);
170             my $keynames_aref = $self->_get_keys($table_name, $data_id);
171              
172             return $self->_load($table_name, $keynames_aref, %data);
173             }
174              
175             sub _get_keys {
176             my $self = shift;
177             my ($table_name, $data_id) = @_;
178             my $keynames_aref = $self->{data}->{$table_name}->{$data_id}->{key};
179             if ( $self->_aref_is_empty($keynames_aref) ) {
180             $keynames_aref = $self->{keynames}->{$table_name};
181             }
182             return $keynames_aref;
183             }
184              
185             sub _data_with_option {
186             my $self = shift;
187             my ($table_name, $data_id, $option_href) = @_;
188             my %data = %{$self->_data($table_name, $data_id)};
189              
190             if ( defined $option_href ) {
191             for my $key ( keys %{$option_href} ) {
192             $data{$key} = $option_href->{$key};
193             }
194             }
195             return %data;
196             }
197              
198              
199              
200             =head2 load_direct($table_name, $data_href, $keynames_aref)
201              
202             load testdata from this module into database directly.
203              
204             $data->load_direct('foo', { id=>1, name=>'aaa' }, ['id']);
205              
206             first parameter is table_name, second parameter is hashref for data.
207              
208             for example,
209              
210             my $key = $data->load_direct('foo', { id=>1, name=>'aaa' }, ['id']);
211              
212             is equivalent to
213              
214             $data->add('foo', 1, { id=>1, name=>'aaa' }, ['id']);
215             my $key = $data->load('foo', 1);
216              
217             if set_keys() was called before, $keynames_aref is ommittable.
218              
219             =cut
220              
221              
222             sub load_direct {
223             my $self = shift;
224             my ($table_name, $data_href, $keynames_aref) = @_;
225             my %data = %{ $data_href };
226             if ( $self->_aref_is_empty($keynames_aref) ) {
227             $keynames_aref = $self->{keynames}->{$table_name};
228             }
229              
230             return $self->_load($table_name, $keynames_aref, %data);
231             }
232              
233             sub _load {
234             my $self = shift;
235             my ($table_name, $keynames_aref, %data) = @_;
236              
237             croak "primary keys are not defined\n" if ( $self->_aref_is_empty($keynames_aref) );
238             if ( $self->{DeleteBeforeInsert} && $self->_data_for_key_exists($keynames_aref, %data) ) {
239             $self->_delete($table_name, \%data, $keynames_aref);
240             }
241             $self->_do_insert($table_name, %data);
242             my $keys = $self->_primary_keys($keynames_aref, \%data);
243             $self->{dbh}->do('commit');
244              
245             push @{$self->{loaded}}, [$table_name, \%data, $keynames_aref];
246              
247             return $keys;
248              
249             }
250              
251             sub _aref_is_empty {
252             my $self = shift;
253             my ($keynames_aref) = @_;
254             return !defined $keynames_aref || !@{ $keynames_aref };
255             }
256              
257             sub _data_for_key_exists {
258             my $self = shift;
259             my ($keynames_aref, %data) = @_;
260             for my $key ( @{ $keynames_aref } ) {
261             return 0 if ( !exists $data{$key} );
262             }
263             return 1;
264             }
265              
266              
267              
268             sub _do_insert {
269             my $self = shift;
270             my ($table_name, %data) = @_;
271             my $dbh = $self->{dbh};
272             my $sth = $dbh->prepare($self->_insert_sql($table_name, %data)) || croak $dbh->errstr;
273             my $i=1;
274             for my $column ( sort keys %data ) {
275             $sth->bind_param($i++, $data{$column});
276             }
277             $sth->execute() || croak $dbh->errstr;
278             $sth->finish;
279             }
280              
281             =head2 load_file
282              
283             add data from external file
284              
285             $data->load_file('data.pm');
286              
287             parameter is filename.
288              
289             =cut
290              
291             sub load_file {
292             my $self = shift;
293             my ( $filename ) = @_;
294             require $filename;
295             croak("can't read $filename") if ( $@ );
296             }
297              
298             =head2 init
299              
300             create new instance for external file
301              
302             my $data = Test::DataLoader::MySQL->init();
303             #$data->add(...
304              
305             =cut
306              
307             sub init {
308             my $class = shift;
309             my $self = {};
310             if ( defined $singleton ) {
311             $self = $singleton;
312             }
313             else {
314             bless $self, $class;
315             $singleton = $self;
316             }
317             return $self;
318             }
319              
320             =head2 set_keys($table_name, $keynames_aref)
321             set primary key information
322              
323             =cut
324             sub set_keys {
325             my $self = shift;
326             my($table_name, $keynames_aref) = @_;
327             $self->{keynames}->{$table_name} = $keynames_aref;
328             }
329              
330             sub _primary_keys {
331             my $self = shift;
332             my ($keynames_aref, $data_href) = @_;
333              
334             my $result;
335             for my $key ( @{ $keynames_aref } ) {
336             if ( !defined $data_href->{$key} ) { #for auto_increment
337             $data_href->{$key} = $self->_last_insert_id() || undef; #if LAST_INSERT_ID returns '0' its not auto_increment
338             }
339             $result->{$key} = $data_href->{$key}
340              
341             }
342             return $result;
343             }
344              
345             sub _last_insert_id {
346             my $self = shift;
347              
348             my $dbh = $self->{dbh};
349             my $sth = $dbh->prepare("select LAST_INSERT_ID() from dual") || croak $dbh->errstr;
350             $sth->execute() || croak $dbh->errstr;
351             if ( my @id = $sth->fetchrow_array ) {
352             return $id[0];
353             }
354             return;
355             }
356              
357              
358             =head2 do_select
359              
360             do select statement
361              
362             $data->do_select('foo', "id=1");
363              
364             first parameter is table_name which you want to select. second parameter is where closure. Omitting second parameter is not allowed, if you want to use all data, use condition which is aloways true such as "1=1".
365              
366             =cut
367              
368             sub do_select {
369             my $self = shift;
370             my ($table, $condition) = @_;
371             my $dbh = $self->{dbh};
372             croak( "Error: condition undefined" ) if !defined $condition;
373              
374             my $sth = $dbh->prepare("select * from $table where $condition");
375             $sth->execute();
376              
377             my @result;
378             while( my $item = $sth->fetchrow_hashref ) {
379             push @result, $item;
380             }
381             $sth->finish();
382              
383             return @result if wantarray;
384             return $result[0];
385             }
386              
387             sub _insert_sql {
388             my $self = shift;
389             my ($table_name, %data) = @_;
390             my $sql = sprintf("insert into %s set ", $table_name);
391             $sql .= join(',', map { "$_=?" } sort keys %data);
392             return $sql;
393             }
394              
395             sub _data {
396             my $self = shift;
397             my ($table_name, $data_id) = @_;
398             croak "$table_name not found" if ( !exists $self->{data}->{$table_name} );
399             croak "$data_id for $table_name not found" if ( !exists $self->{data}->{$table_name}->{$data_id} );
400             return $self->{data}->{$table_name}->{$data_id}->{data};
401             }
402              
403              
404             sub DESTROY {
405             my $self = shift;
406             if ( @{ $self->{loaded} } ) {
407             carp "clear was not called in $0";
408             $self->clear;
409             }
410              
411             }
412              
413             =head2 clear
414              
415             clear all loaded data from database;
416              
417             =cut
418              
419             sub clear {
420             my $self = shift;
421             my $dbh = $self->{dbh};
422             if ( $self->{Keep} || !defined $dbh ) {
423             $self->{loaded} = [];
424             return;
425             }
426              
427             for my $loaded ( reverse @{ $self->{loaded} } ) {
428             $self->_delete_loaded($loaded);
429             }
430             $dbh->do('commit');
431             $self->{loaded} = [];
432             }
433              
434             sub _delete_loaded {
435             my $self = shift;
436             my ($loaded) = @_;
437              
438             my( $table_name, $data_href, $keynames_aref ) = @{ $loaded };
439             $self->_delete($table_name, $data_href, $keynames_aref);
440             }
441              
442             sub _delete {
443             my $self = shift;
444             my ($table_name, $data_href, $keynames_aref) = @_;
445             my $dbh = $self->{dbh};
446             my %data = %{ $data_href };
447             my @keys = @{ $keynames_aref };
448             my $condition = join(' And ', map {
449             defined $data{$_} ? "$_=?" : "$_ IS NULL"
450             } @keys);
451             my $sth = $dbh->prepare("delete from $table_name where $condition");
452             my $i=1;
453             for my $key ( @keys ) {
454             $sth->bind_param($i++, $data{$key});
455             }
456             $sth->execute() || croak $dbh->errstr;
457             }
458              
459             1;
460             __END__