File Coverage

blib/lib/DBIx/TableLoader.pm
Criterion Covered Total %
statement 137 137 100.0
branch 41 44 93.1
condition 29 38 76.3
subroutine 40 40 100.0
pod 28 28 100.0
total 275 287 95.8


line stmt bran cond sub pod time code
1             # vim: set ts=2 sts=2 sw=2 expandtab smarttab:
2             #
3             # This file is part of DBIx-TableLoader
4             #
5             # This software is copyright (c) 2011 by Randy Stauner.
6             #
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             #
10 6     6   68454 use strict;
  6         20  
  6         175  
11 6     6   31 use warnings;
  6         11  
  6         312  
12              
13             package DBIx::TableLoader;
14             # git description: v1.100-1-g8e5d9b8
15              
16             our $AUTHORITY = 'cpan:RWSTAUNER';
17             # ABSTRACT: Easily load a database table from a data set
18             $DBIx::TableLoader::VERSION = '1.101';
19 6     6   34 use Carp qw(croak);
  6         19  
  6         306  
20             #use DBI 1.13 (); # oldest DBI on CPAN as of 2011-02-15; Has SQL_LONGVARCHAR
21 6     6   1376 use Try::Tiny 0.09;
  6         6095  
  6         11733  
22              
23              
24             sub new {
25 44     44 1 27424 my $class = shift;
26 44         87 my $self = bless {}, $class;
27              
28 44 100       153 my %opts = @_ == 1 ? %{$_[0]} : @_;
  16         63  
29              
30 44         78 my %defaults = (%{ $self->base_defaults }, %{ $self->defaults });
  44         91  
  44         94  
31 44         334 while( my ($key, $value) = each %defaults ){
32             $self->{$key} = exists($opts{$key})
33 1102 100       3222 ? delete $opts{$key}
34             : $value;
35             }
36              
37             # be loud about typos
38 44 100       95 croak("Unknown options: ${\join(', ', keys %opts)}")
  1         210  
39             if %opts;
40              
41             # custom routine to handle type of input data (hook for subclasses)
42 43         116 $self->prepare_data();
43              
44             # normalize 'columns' attribute
45 43         106 $self->determine_column_types();
46              
47 41         187 return $self;
48             }
49              
50              
51             sub base_defaults {
52             return {
53 44     44 1 582 catalog => undef,
54             columns => undef,
55             create => 1,
56             create_prefix => '',
57             create_sql => '',
58             create_suffix => '',
59             # 'data' attribute may not be useful in subclasses
60             data => undef,
61             dbh => undef,
62             default_column_type => '',
63             default_sql_data_type => '',
64             drop => 0,
65             drop_prefix => '',
66             drop_sql => '',
67             drop_suffix => '',
68             get_row => undef,
69             grep_rows => undef,
70             handle_invalid_row => undef,
71             map_rows => undef,
72             # default_name() method will default to 'data' if 'name' is blank
73             # this way subclasses don't have to override this value in defaults()
74             name => '',
75             name_prefix => '',
76             name_suffix => '',
77             quoted_name => undef,
78             schema => undef,
79             table_type => '', # TEMP, TEMPORARY, VIRTUAL?
80             transaction => 1,
81             };
82             }
83              
84              
85             sub defaults {
86 42     42 1 277 return {};
87             }
88              
89              
90             sub columns {
91 146     146 1 1653 my ($self) = @_;
92             # by default the column names are found in the first row of the data
93             # (but circumvent get_row() to avoid any grep or map subs)
94 146   100     499 return $self->{columns} ||= $self->_get_custom_or_raw_row();
95             }
96              
97              
98             sub column_names {
99 29     29 1 3195 my ($self) = @_;
100             # return the first element of each arrayref
101 29         47 return [ map { $$_[0] } @{ $self->columns } ];
  74         209  
  29         52  
102             }
103              
104              
105             sub create {
106 10     10 1 17 my ($self) = @_;
107 10         23 $self->{dbh}->do($self->create_sql);
108             }
109              
110              
111             sub create_prefix {
112 16     16 1 2545 my ($self) = @_;
113             return $self->{create_prefix} ||=
114 16   66     80 "CREATE $self->{table_type} TABLE " .
115             $self->quoted_name . " (";
116             }
117              
118              
119             sub create_sql {
120 16     16 1 28 my ($self) = @_;
121             $self->{create_sql} ||=
122             join(' ',
123             $self->create_prefix,
124              
125             # column definitions (each element is: [name, data_type])
126             join(', ', map {
127 20         116 $self->{dbh}->quote_identifier($_->[0]) . ' ' . $_->[1]
128 16   66     63 } @{ $self->columns }),
  10         76  
129              
130             $self->create_suffix
131             );
132             }
133              
134              
135             sub create_suffix {
136 16     16 1 773 my ($self) = @_;
137             return $self->{create_suffix} ||=
138 16   100     382 ')';
139             }
140              
141             # ask the driver what data type it uses for the desired SQL standard type
142              
143             sub _data_type_from_driver {
144 27     27   399 my ($self, $data_type) = @_;
145 27 100       121 if( my $type = $self->{dbh}->type_info($data_type) ){
146 17         164 return $type->{TYPE_NAME};
147             }
148 3         29 return;
149             }
150              
151              
152             sub default_name {
153 16     16 1 126 return 'data';
154             }
155              
156              
157             sub default_column_type {
158 46     46 1 1135 my ($self) = @_;
159             return $self->{default_column_type} ||= try {
160 27     27   731 $self->_data_type_from_driver($self->default_sql_data_type);
161             }
162             # outside the eval in case there was an error
163 46   100     220 || 'text';
      66        
164             }
165              
166              
167             sub default_sql_data_type {
168 28     28 1 428 my ($self) = @_;
169             $self->{default_sql_data_type} ||= try {
170             # if this doesn't work default_column_type will just use 'text'
171 27     27   5721 require DBI;
172 27         53694 DBI::SQL_LONGVARCHAR();
173 28   66     165 };
174             }
175              
176              
177             sub determine_column_types {
178 43     43 1 63 my ($self) = @_;
179 43         84 my ($columns, $type) = ($self->columns, $self->default_column_type);
180              
181 43 100 100     874 croak("Unable to determine columns!")
182             unless $columns && @$columns;
183              
184             # break reference
185 41         93 $columns = [@$columns];
186              
187             # reset each element to an arrayref if it isn't already
188 41         84 foreach my $column ( @$columns ){
189             # upgrade lone string to arrayref otherwise break reference
190 84 100       177 $column = ref $column ? [@$column] : [$column];
191             # append column type if missing
192 84 100       226 push(@$column, $type)
193             unless @$column > 1;
194             }
195              
196             # restore changes
197 41         76 $self->{columns} = $columns;
198 41         70 return;
199             }
200              
201              
202             sub drop {
203 8     8 1 15 my ($self) = @_;
204 8         24 $self->{dbh}->do($self->drop_sql);
205             }
206              
207              
208             sub drop_prefix {
209 16     16 1 773 my ($self) = @_;
210             # default to "DROP TABLE" since SQLite, PostgreSQL, and MySQL
211             # all accept it (rather than "DROP $table_type TABLE")
212 16   100     192 $self->{drop_prefix} ||= 'DROP TABLE';
213             }
214              
215              
216             sub drop_sql {
217 16     16 1 26 my ($self) = @_;
218 16   66     64 return $self->{drop_sql} ||= join(' ',
219             $self->drop_prefix,
220             $self->quoted_name,
221             $self->drop_suffix,
222             );
223             }
224              
225              
226             sub drop_suffix {
227 16     16 1 48 my ($self) = @_;
228             # default is blank
229 16         342 return $self->{drop_suffix};
230             }
231              
232             # call get_raw_row unless a custom 'get_row' is defined
233             # (this is the essence of get_row() but without the grep/map subs)
234              
235             sub _get_custom_or_raw_row {
236 88     88   139 my ($self) = @_;
237             # considered { $self->{get_row} ||= $self->can('get_raw_row'); } in new()
238             # but it just seemed a little strange... this is more normal/clear
239             return $self->{get_row}
240 88 100       247 ? $self->{get_row}->($self)
241             : $self->get_raw_row();
242             }
243              
244              
245             sub get_raw_row {
246 67     67 1 93 my ($self) = @_;
247             # It would be simpler to shift the data but I don't think it actually
248             # gains us anything. This way we're not modifying anything unexpectedly.
249             # Besides subclasses will likely be more useful than this one.
250 67         268 return $self->{data}->[ $self->{row_index}++ ];
251             }
252              
253              
254             sub get_row {
255 69     69 1 17838 my ($self) = @_;
256 69         115 my $row;
257              
258             GETROW: {
259 69 100       102 $row = $self->_get_custom_or_raw_row()
  72         834  
260             or last GETROW;
261              
262             # call grep_rows with the same semantics as map_rows (below)
263 55 100       306 if( $self->{grep_rows} ){
264 6         10 local $_ = $row;
265             # if grep returns false try the block again
266             redo GETROW
267 6 100       12 unless $self->{grep_rows}->($row, $self);
268             }
269              
270             # Send the row first since it's the important part.
271             # This isn't a method call, and $self will likely be seldom used.
272 53 100       125 if( $self->{map_rows} ){
273             # localize $_ to the $row for consistency with the built in map()
274 11         18 local $_ = $row;
275             # also pass row as the first argument to simulate a normal function call
276 11         23 $row = $self->{map_rows}->($row, $self);
277             }
278              
279             # validate the row before passing a bad value to the DBI
280             $row = try {
281 53     53   2159 $self->validate_row($row);
282             }
283             catch {
284             # file/line position is unhelpful, but so is the newline
285 12     12   189 chomp(my $e = $_[0]);
286             # if there was an error, pass it through the handler
287             # the handler should die, return a row, or return false to skip
288 12         29 $self->handle_invalid_row($e, $row);
289             }
290 53 100       412 or redo GETROW;
291             }
292              
293 65         1697 return $row;
294             }
295              
296              
297             sub handle_invalid_row {
298 15     15 1 1761 my ($self, $error, $row) = @_;
299              
300 15 100       41 if( my $handler = $self->{handle_invalid_row} ){
301             # should this be croak/carp?
302 14 100       46 if( $handler eq 'die' ){
    100          
303 3         18 die $error . "\n";
304             }
305             elsif( $handler eq 'warn' ){
306 1         13 warn $error . "\n";
307 1         10 return $row;
308             }
309             # otherwise it should be a coderef (or a method name (for a subclass maybe))
310             else {
311 10         26 return $self->$handler($error, $row);
312             }
313             }
314              
315             # pass through if no handler was defined
316 1         5 return $row;
317             }
318              
319              
320             sub insert_sql {
321 12     12 1 2705 my ($self) = @_;
322             join(' ',
323             'INSERT INTO',
324             $self->quoted_name,
325             '(',
326 12         69 join(', ', @{ $self->quoted_column_names } ),
327             ')',
328             'VALUES(',
329 12         27 join(', ', ('?') x @{ $self->columns }),
  12         127  
330             ')'
331             );
332             }
333              
334              
335             sub insert_all {
336 7     7 1 1126 my ($self) = @_;
337              
338 7         13 my $rows = 0;
339 7         15 my $sth = $self->{dbh}->prepare($self->insert_sql);
340 7         37 while( my $row = $self->get_row() ){
341 8         36 $sth->execute(@$row);
342 8         57 ++$rows;
343             }
344              
345 6         17 return $rows;
346             }
347              
348              
349             sub load {
350 4     4 1 1848 my ($self) = @_;
351 4         7 my $rows;
352              
353             # is it appropriate/sufficient to call prepare_data() from new()?
354              
355             try {
356              
357             $self->{dbh}->begin_work()
358 4 100   4   177 if $self->{transaction};
359              
360             $self->drop()
361 4 50       22 if $self->{drop};
362              
363             $self->create()
364 4 50       15 if $self->{create};
365              
366 4         19 $rows = $self->insert_all();
367              
368             $self->{dbh}->commit()
369 3 100       14 if $self->{transaction};
370              
371             }
372             catch {
373             # explicitly end the transaction that we started
374             # in case this isn't the last thing being done with the dbh
375             $self->{dbh}->rollback()
376 1 50   1   20 if $self->{transaction};
377              
378             # propagate the exception
379 1         8 die $_[0];
380 4         27 };
381              
382 3         54 return $rows;
383             }
384              
385              
386             sub name {
387 24     24 1 2480 my ($self) = @_;
388             return $self->{_name} ||=
389             $self->{name_prefix} .
390             ($self->{name} || $self->default_name) .
391 24   66     114 $self->{name_suffix};
      66        
392             }
393              
394              
395             sub prepare_data {
396 42     42 1 70 my ($self) = @_;
397 42         73 $self->{row_index} = 0;
398             }
399              
400              
401             sub quoted_name {
402 33     33 1 55 my ($self) = @_;
403             # allow quoted name to be passed in to handle edge cases
404             return $self->{quoted_name} ||=
405             $self->{dbh}->quote_identifier(
406 33   66     107 $self->{catalog}, $self->{schema}, $self->name);
407             }
408              
409              
410             sub quoted_column_names {
411 16     16 1 29 my ($self) = @_;
412             return $self->{quoted_column_names} ||= [
413 36         196 map { $self->{dbh}->quote_identifier($_) }
414 16   50     45 @{ $self->column_names }
  16         34  
415             ];
416             }
417              
418              
419             sub validate_row {
420 48     48 1 139 my ($self, $row) = @_;
421              
422             # DBI will croak if exec'd with different numbers
423 48         63 my $num_columns = @{ $self->columns };
  48         104  
424              
425 48 100       167 die 'Row has ' . @$row . ' fields when ' . $num_columns . " are expected\n"
426             if @$row != $num_columns;
427              
428             # are there other validation checks we can do?
429              
430 36         78 return $row;
431             }
432              
433             1;
434              
435             __END__