File Coverage

blib/lib/Data/Model/Driver/DBI.pm
Criterion Covered Total %
statement 301 353 85.2
branch 76 120 63.3
condition 13 23 56.5
subroutine 40 42 95.2
pod 0 25 0.0
total 430 563 76.3


line stmt bran cond sub pod time code
1             package Data::Model::Driver::DBI;
2 94     94   38190173 use strict;
  94         249  
  94         4071  
3 94     94   800 use warnings;
  94         200  
  94         3720  
4 94     94   549 use base 'Data::Model::Driver';
  94         263  
  94         53479  
5              
6 94     94   595 use Carp ();
  94         179  
  94         2430  
7             $Carp::Internal{(__PACKAGE__)}++;
8 94     94   8196 use DBI;
  94         64728  
  94         4821  
9              
10 94     94   66952 use Data::Model::SQL;
  94         332  
  94         3990  
11 94     94   65947 use Data::Model::Driver::DBI::DBD;
  94         243  
  94         335243  
12              
13 2768     2768 0 15856 sub dbd { $_[0]->{dbd} }
14              
15             sub dbi_config {
16 962     962 0 2084 my($self, $name) = @_;
17 962 50       5867 $self->{dbi_config}->{$name}
18             or Carp::croak "has not dbi_config name '$name'";
19             }
20              
21             sub init {
22 49     49 0 372 my $self = shift;
23 49 50       894 if (my($type) = $self->{dsn} =~ /^dbi:(\w*)/i) {
24 49         812 $self->{dbd} = Data::Model::Driver::DBI::DBD->new($type);
25             }
26 49         569 $self->{dbi_config} = +{
27             rw => +{
28             dsn => delete $self->{dsn},
29             username => delete $self->{username},
30             password => delete $self->{password},
31             connect_options => delete $self->{connect_options},
32             dbh => undef,
33             },
34             };
35             }
36              
37             my %reuse_handles;
38             sub init_db {
39 36     36 0 105 my($self, $name, %args) = @_;
40 36         124 my $dbi_config = $self->dbi_config($name);
41 36         121 my $dsn = $dbi_config->{dsn};
42 36         77 my $dbh;
43 36 100       168 if ($self->{reuse_dbh}) {
44 2         5 $dbh = $reuse_handles{$dsn};
45             }
46 36 100 33     209 unless ($dbh && ($args{no_ping} || $dbh->ping)) {
      66        
47 35 50       673 $dbh = DBI->connect(
48             $dsn, $dbi_config->{username}, $dbi_config->{password},
49 35 50       138 { RaiseError => 1, PrintError => 0, AutoCommit => 1, %{ $dbi_config->{connect_options} || {} } },
50             ) or Carp::croak("Connection error: " . $DBI::errstr);
51 35 100       49723 if ($self->{reuse_dbh}) {
52 1         5 $reuse_handles{$dsn} = $dbh;
53             }
54             }
55 36         142 $self->{__dbh_init_by_driver} = 1;
56 36         348 $dbh;
57             }
58              
59             sub _get_dbh {
60 926     926   1767 my $self = shift;
61 926   50     3239 my $name = shift || 'rw';
62 926         2848 my %args = @_; # this option is experimental
63 926         3827 my $dbi_config = $self->dbi_config($name);
64 926 50       4996 unless ($args{no_ping}) {
65 926 50 66     22179 $dbi_config->{dbh} = undef if $dbi_config->{dbh} and !$dbi_config->{dbh}->ping;
66             }
67 926 100 66     66934 unless ($dbi_config->{dbh} || $args{cannot_reconnect}) {
68 36 50       174 if (my $getter = $self->{get_dbh}) {
69 0         0 $dbi_config->{dbh} = $getter->();
70             } else {
71 36 50       202 $dbi_config->{dbh} = $self->init_db($name, %args) or Carp::croak $self->last_error;
72             }
73             }
74 926         4218 $dbi_config->{dbh};
75             }
76              
77 890     890 0 4331 sub rw_handle { shift->_get_dbh('rw', @_) };
78 448     448 0 1990 sub r_handle { shift->rw_handle(@_) }
79              
80 0     0 0 0 sub last_error {}
81              
82             sub add_key_to_where {
83 441     441 0 1309 my($self, $stmt, $columns, $key) = @_;
84 441 50       1656 if ($key) {
85             # add where
86 441         721 my $i = 0;
87 441         816 for my $i (0..( scalar(@{ $key }) - 1 )) {
  441         1906  
88 489         2659 $stmt->add_where( $columns->[$i] => $key->[$i] );
89             }
90             }
91             }
92              
93             sub add_index_to_where {
94 61     61 0 154 my($self, $schema, $stmt, $index_obj) = @_;
95 61 50       131 return unless my($index, $index_key) = (%{ $index_obj });
  61         468  
96 61 100       596 $index_key = [ $index_key ] unless ref($index_key) eq 'ARRAY';
97 61         163 for my $index_type (qw/ unique index /) {
98 98 100       480 if (exists $schema->$index_type->{$index}) {
99 61         222 $self->add_key_to_where($stmt, $schema->$index_type->{$index}, $index_key);
100 61         209 last;
101             }
102             }
103             }
104              
105             sub bind_params {
106 837     837 0 3474 my($self, $schema, $columns, $sth) = @_;
107 837         3923 my $i = 1;
108 837         1485 for my $column (@{ $columns }) {
  837         2818  
109 1302         1961 my($col, $val) = @{ $column };
  1302         8078  
110 1302         6922 my $type = $schema->column_type($col);
111 1302         4925 my $attr = $self->dbd->bind_param_attributes($type, $columns, $col);
112 1302   100     20362 $sth->bind_param($i++, $val, $attr || undef);
113             }
114             }
115              
116             sub fetch {
117 456     456 0 1214 my($self, $rec, $schema, $key, $columns, %args) = @_;
118              
119 456 100       1457 $columns = +{} unless $columns;
120              
121 456   50     4494 $columns->{select} ||= [ $schema->column_names ];
122 456   50     3715 $columns->{from} ||= [];
123 456         833 unshift @{ $columns->{from} }, $schema->model;
  456         2738  
124              
125 456         1514 my $index_query = delete $columns->{index};
126 456         1035 my $stmt = Data::Model::SQL->new(%{ $columns });
  456         4378  
127 456 100       3765 $self->add_key_to_where($stmt, $schema->key, $key) if $key;
128 456 100       1692 $self->add_index_to_where($schema, $stmt, $index_query) if $index_query;
129 456         8838 my $sql = $stmt->as_sql;
130              
131             # bind_params
132 456         948 my @params;
133 456         933 for my $i (1..scalar(@{ $stmt->bind })) {
  456         2437  
134 530         1806 push @params, [ $stmt->bind_column->[$i - 1], $stmt->bind->[$i - 1] ];
135             }
136              
137 456         961 my @bind;
138 456         4163 my $map = $stmt->select_map;
139 456         1011 for my $col (@{ $stmt->select }) {
  456         2095  
140 1056 50       5094 push @bind, \$rec->{ exists $map->{$col} ? $map->{$col} : $col };
141             }
142              
143 456         1547 my $sth;
144 456         952 eval {
145 456         3146 my $dbh = $self->r_handle;
146 456         3895 $self->start_query($sql, $stmt->bind);
147 456 100       7081 $sth = $args{no_cached_prepare} ? $dbh->prepare($sql) : $dbh->prepare_cached($sql);
148 456         63865 $self->bind_params($schema, \@params, $sth);
149 456         75725 $sth->execute;
150 456         5246 $sth->bind_columns(undef, @bind);
151             };
152 456 50       22575 if ($@) {
153 0         0 $self->_stack_trace($sth, $sql, $stmt->bind, $@);
154             }
155 456         8728 $sth;
156             }
157              
158             sub lookup {
159 106     106 0 6572 my($self, $schema, $id, %args) = @_;
160              
161 106         275 my $rec = +{};
162 106         620 my $sth = $self->fetch($rec, $schema, $id, {}, %args);
163              
164 106         2623 my $rv = $sth->fetch;
165 106         663 $sth->finish;
166 106         427 $self->end_query($sth);
167 106         228 undef $sth;
168 106 100       392 return unless $rv;
169 92         535 return $rec;
170             }
171              
172             sub lookup_multi {
173 23     23 0 71 my($self, $schema, $ids, %args) = @_;
174              
175 23         60 my @keys = @{ $schema->key };
  23         85  
176 23         60 my $query = {};
177 23 100       84 if (@keys == 1) {
178 19         32 my @id_list = map { $_->[0] } @{ $ids };
  49         336  
  19         49  
179 19         103 $query = { where => [ $keys[0] => \@id_list ] };
180             } else {
181 4         10 my @queries;
182 4         9 for my $id (@{ $ids }) {
  4         12  
183 7         12 my %query;
184 7         10 @query{@keys} = @{ $id };
  7         25  
185 7         43 push @queries, '-and' => [ %query ];
186             }
187 4         27 $query = { where => [ -or => \@queries ] };
188             }
189              
190 23         61 my $rec = +{};
191 23         90 local $args{no_cached_prepare} = 1;
192 23         149 my $sth = $self->fetch($rec, $schema, undef, $query, %args);
193              
194 23         58 my %resultlist;
195 23         424 while ($sth->fetch) {
196 42         325 my $key = $schema->get_key_array_by_hash($rec);
197 42         63 $resultlist{join "\0", @{ $key }} = +{ %{ $rec } };
  42         783  
  42         231  
198             }
199              
200 23         112 $sth->finish;
201 23         86 $self->end_query($sth);
202 23         46 undef $sth;
203              
204 23         658 \%resultlist;
205             }
206              
207             sub get {
208 327     327 0 1091 my($self, $schema, $key, $columns, %args) = @_;
209              
210 327         1146 my $rec = +{};
211 327         1920 my $sth = $self->fetch($rec, $schema, $key, $columns, %args);
212              
213 327         688 my $i = 0;
214             my $iterator = sub {
215 1033 100   1033   4779 return unless $sth;
216 1029 100       4872 return $rec if $i++ eq 1;
217 759 100       13588 unless ($sth->fetch) {
218 323         1589 $sth->finish;
219 323         1151 $self->end_query($sth);
220 323         563 undef $sth;
221 323         1699 return;
222             }
223 436         1342 $rec;
224 327         2260 };
225              
226             # pre load
227 327 100       1003 return unless $iterator->();
228             return $iterator, +{
229 274 100   274   4261 end => sub { if ($sth) { $sth->finish; $self->end_query($sth); undef $sth; } },
  4         133  
  4         21  
  4         86  
230 274         3137 };
231             }
232              
233             # insert or replace
234             sub set {
235 277     277 0 674 my $self = shift;
236 277         1271 $self->_insert_or_replace(0, @_);
237             }
238              
239             sub replace {
240 4     4 0 14 my($self, $schema, $key, $columns, %args) = @_;
241 4 50       21 if ($self->dbd->can_replace) {
242 4         33 return $self->_insert_or_replace(1, $schema, $key, $columns, %args);
243             } else {
244             # $self->thx(sub {
245 0         0 $self->delete($schema, $key, +{}, %args);
246 0         0 return $self->set($schema, $key, $columns, %args);
247             # });
248             }
249             }
250              
251             sub _on_duplicate_key_update {
252 0     0   0 my($self, $schema, $columns, $args, $sql, $column_list) = @_;
253 0         0 my $table = $schema->model;
254              
255             # check unique keys
256 0         0 my $keys = $schema->key;
257 0         0 my $unique = $schema->unique;
258 0         0 my $key_columns = [];
259 0 0       0 if (scalar(@{ $keys }) >= 1) {
  0 0       0  
  0 0       0  
260 0 0       0 if (scalar(keys %{ $unique }) >= 1) {
  0         0  
261 0         0 Carp::croak "on_duplicate_key_update support: $table has multi unique key";
262             }
263             # OK
264 0         0 $key_columns = $keys;
265 0         0 } elsif (scalar(keys %{ $unique }) > 1) {
266 0         0 Carp::croak "on_duplicate_key_update support: $table has multi unique key";
267             } elsif (scalar(keys %{ $unique }) == 1) {
268             # OK
269 0         0 while (my($k, $v) = each %{ $unique }) {
  0         0  
270 0         0 $key_columns = $v;
271             }
272             } else {
273 0         0 Carp::croak "on_duplicate_key_update support: $table not has key or unique index";
274             }
275              
276             # check key num
277 0         0 my $has_keys = 1;
278 0         0 for my $k (@{ $key_columns }) {
  0         0  
279 0 0       0 $has_keys = 0 unless defined $columns->{$k};
280             }
281 0 0       0 Carp::croak "on_duplicate_key_update support: $table is insufficient keys" unless $has_keys;
282              
283             # append sql
284 0         0 my @set;
285 0         0 for my $column (keys %{ $args }) {
  0         0  
286 0         0 my $val = $args->{$column};
287 0 0       0 if (ref($val) eq 'SCALAR') {
    0          
288 0         0 push @set, "$column = " . ${ $val };
  0         0  
289             } elsif (!ref($val)) {
290 0         0 push @set, "$column = ?";
291 0         0 push @{ $column_list }, [ $column => $val ];
  0         0  
292             } else {
293 0         0 Carp::confess 'No references other than a SCALAR reference can use a update column';
294             }
295             }
296 0         0 ${ $sql } .= ' ON DUPLICATE KEY UPDATE ' . join(', ', @set) . "\n";
  0         0  
297             }
298              
299             sub _insert_or_replace {
300 281     281   1096 my($self, $is_replace, $schema, $key, $columns, %args) = @_;
301 281 100       998 my $select_or_replace = $is_replace ? 'REPLACE' : 'INSERT';
302              
303 281         1658 my $table = $schema->model;
304 281         622 my $cols = [ keys %{ $columns } ];
  281         1369  
305 606         2858 my @column_list = map {
306 281         718 [ $_ => $columns->{$_} ]
307 281         771 } @{ $cols };
308 281         1377 my $sql = "$select_or_replace INTO $table\n";
309 281         1434 $sql .= '(' . join(', ', @{ $cols }) . ')' . "\n" .
  281         1578  
310 281         744 'VALUES (' . join(', ', ('?') x @{ $cols }) . ')' . "\n";
311              
312             # ON DUPLICATE KEY UPDATE support for MySQL
313 281 50 33     1520 if ($args{on_duplicate_key_update} && $self->dbd->has_support('on_duplicate_key_update')) {
314 0         0 $self->_on_duplicate_key_update($schema, $columns, $args{on_duplicate_key_update}, \$sql, \@column_list);
315             }
316              
317 281         1049 my $sth;
318 281         554 eval {
319 281         1400 my $dbh = $self->rw_handle;
320 281         1971 $self->start_query($sql, $columns);
321 281         3242 $sth = $dbh->prepare_cached($sql);
322 281         40500 $self->bind_params($schema, \@column_list, $sth);
323 281         17631803 $sth->execute;
324 277         7803 $sth->finish;
325 277         3358 $self->end_query($sth);
326              
327             # set autoincrement key
328 277     92   10124 $self->_set_auto_increment($schema, $columns, sub { $self->dbd->fetch_last_id( $schema, $columns, $dbh, $sth ) });
  92         741  
329             };
330 281 100       2883 if ($@) {
331 4         23 $self->_stack_trace($sth, $sql, \@column_list, $@);
332             }
333              
334 277         1323 undef $sth;
335 277         9229 $columns;
336             }
337              
338             # update
339             sub _update {
340 47     47   143 my($self, $schema, $changed_columns, $columns, $where_sql, $pre_bind, $pre_bind_column) = @_;
341              
342 47         99 my @bind;
343             my @bind_column;
344 0         0 my @set;
345 47         186 for my $column (keys %{ $changed_columns }) {
  47         318  
346 57         131 my $val = $columns->{$column};
347 57 100       282 if (ref($val) eq 'SCALAR') {
    50          
348 1         2 push @set, "$column = " . ${ $val };
  1         5  
349             } elsif (!ref($val)) {
350 56         160 push @set, "$column = ?";
351 56         108 push @bind, $val;
352 56         265 push @bind_column, $column;
353             } else {
354 0         0 Carp::confess 'No references other than a SCALAR reference can use a update column';
355             }
356             }
357 47         108 push @bind, @{ $pre_bind };
  47         106  
358 47         78 push @bind_column, @{ $pre_bind_column };
  47         106  
359              
360             # bind_params
361 47         95 my @params;
362 47         159 for my $i (1..scalar(@bind)) {
363 107         477 push @params, [ $bind_column[$i - 1], $bind[$i - 1] ];
364             }
365              
366 47         243 my $sql = 'UPDATE ' . $schema->model . ' SET ' . join(', ', @set) . ' ' . $where_sql;
367 47         103 my $sth;
368 47         101 eval {
369 47         173 my $dbh = $self->rw_handle;
370 47         309 $self->start_query($sql, \@bind);
371 47         1857 $sth = $dbh->prepare_cached($sql);
372 47         11038 $self->bind_params($schema, \@params, $sth);
373 47         2169035 $sth->execute;
374 47         2680 $sth->finish;
375 47         748 $self->end_query($sth);
376             };
377 47 50       351 if ($@) {
378 0         0 $self->_stack_trace($sth, $sql, \@params, $@);
379             }
380              
381 47 50       294 if (wantarray) {
382 0         0 my @ret = $sth->rows;
383 0         0 undef $sth;
384 0         0 return @ret;
385             } else {
386 47         420 my $ret = $sth->rows;
387 47         161 undef $sth;
388 47         2534 return $ret;
389             }
390             }
391              
392             sub update {
393 23     23 0 83 my($self, $schema, $old_key, $key, $old_columns, $columns, $changed_columns, %args) = @_;
394              
395 23         149 my $stmt = Data::Model::SQL->new;
396 23         115 $self->add_key_to_where($stmt, $schema->key, $old_key);
397              
398 23         124 my $where_sql = $stmt->as_sql_where;
399 23 50       102 return unless $where_sql;
400              
401 23         105 return $self->_update($schema, $changed_columns, $columns, $where_sql, $stmt->bind, $stmt->bind_column);
402             }
403              
404             sub update_direct {
405 24     24 0 300 my($self, $schema, $key, $query, $columns, %args) = @_;
406              
407 24         70 my $index_query = delete $query->{index};
408 24         49 my $stmt = Data::Model::SQL->new(%{ $query });
  24         167  
409 24 100       1338 $self->add_key_to_where($stmt, $schema->key, $key) if $key;
410 24 100       100 $self->add_index_to_where($schema, $stmt, $index_query) if $index_query;
411              
412 24         116 my $where_sql = $stmt->as_sql_where;
413 24 50       3761 return unless $where_sql;
414              
415 24         111 return $self->_update($schema, $columns, $columns, $where_sql, $stmt->bind, $stmt->bind_column);
416             }
417              
418             # delete
419             sub delete {
420 53     53 0 304 my($self, $schema, $key, $columns, %args) = @_;
421              
422 53         228 $columns->{from} = [ $schema->model ];
423 53         191 my $index_query = delete $columns->{index};
424 53         115 my $stmt = Data::Model::SQL->new(%{ $columns });
  53         402  
425 53 100       392 $self->add_key_to_where($stmt, $schema->key, $key) if $key;
426 53 50       203 $self->add_index_to_where($schema, $stmt, $index_query) if $index_query;
427              
428             # bind_params
429 53         98 my @params;
430 53         99 for my $i (1..scalar(@{ $stmt->bind })) {
  53         469  
431 59         232 push @params, [ $stmt->bind_column->[$i - 1], $stmt->bind->[$i - 1] ];
432             }
433              
434 53         238 my $sql = "DELETE " . $stmt->as_sql;
435 53         112 my $sth;
436 53         106 eval {
437 53         183 my $dbh = $self->rw_handle;
438 53         234 $self->start_query($sql, $stmt->bind);
439 53         582 $sth = $dbh->prepare_cached($sql);
440 53         8666 $self->bind_params($schema, \@params, $sth);
441 53         1878751 $sth->execute;
442 53         1046 $sth->finish;
443 53         417 $self->end_query($sth);
444             };
445 53 50       485 if ($@) {
446 0         0 $self->_stack_trace($sth, $sql, $stmt->bind, $@);
447             }
448              
449 53 50       441 if (wantarray) {
450 0         0 my @ret = $sth->rows;
451 0         0 undef $sth;
452 0         0 return @ret;
453             } else {
454 53         1466 my $ret = $sth->rows;
455 53         151 undef $sth;
456 53         2868 return $ret;
457             }
458             }
459              
460             # for schema
461             sub _as_sql_hook {
462 1370     1370   1704 my $self = shift;
463 1370         2647 $self->dbd->_as_sql_hook(@_);
464             }
465              
466             # stack trace
467             sub _stack_trace {
468 4     4   12 my($self, $sth, $sql, $binds, $reason) = @_;
469 4         4374 require Data::Dumper;
470              
471 4 50       14128 if ($sth) {
472             # finalize sth handle
473 4         43 $sth->finish;
474 4         18 $self->end_query($sth);
475             }
476              
477 4         27 $sql =~ s/\n/\n /gm;
478 4         19 Carp::croak sprintf <<"TRACE", $reason, $sql, Data::Dumper::Dumper($binds);
479             **** { Data::Model::Driver::DBI 's Exception ****
480             Reason : %s
481             SQL : %s
482             **** BINDS DUMP ****
483             %s
484             **** Data::Model::Driver::DBI 's Exception } ****
485             TRACE
486             }
487              
488             # profile
489 837     837 0 1495 sub start_query {}
490 837     837 0 2975 sub end_query {}
491              
492             sub DESTROY {
493 17     17   16960 my $self = shift;
494 17 100       1207 return unless $self->{__dbh_init_by_driver};
495              
496             # if (my $dbh = $self->dbh) {
497             # $dbh->disconnect if $dbh;
498             # }
499             }
500              
501             # for transactions
502             sub txn_begin {
503 33     33 0 72 my $self = shift;
504 33         87 $self->{active_transaction} = 1;
505 33         132 my $dbh = $self->rw_handle;
506 33 50       228 eval { $dbh->begin_work } or Carp::croak $@;
  33         920  
507             }
508              
509             sub txn_rollback {
510 22     22 0 40 my $self = shift;
511 22 50       75 return unless $self->{active_transaction};
512 22         73 my $dbh = $self->rw_handle;
513 22 50       48 eval { $dbh->rollback } or Carp::croak $@;
  22         8198  
514             }
515              
516             sub txn_commit {
517 11     11 0 23 my $self = shift;
518 11 50       42 return unless $self->{active_transaction};
519 11         39 my $dbh = $self->rw_handle;
520 11 50       26 eval { $dbh->commit } or Carp::croak $@;
  11         1241159  
521             }
522              
523             sub txn_end {
524 33     33 0 122 $_[0]->{active_transaction} = 0;
525             }
526              
527             1;
528              
529             =head1 NAME
530              
531             Data::Model::Driver::DBI - storage driver for DBI
532              
533             =head1 SYNOPSIS
534              
535             package MyDB;
536             use base 'Data::Model';
537             use Data::Model::Schema;
538             use Data::Model::Driver::DBI;
539            
540             my $dbi_connect_options = {};
541             my $driver = Data::Model::Driver::DBI->new(
542             dsn => 'dbi:mysql:host=localhost:database=test',
543             username => 'user',
544             password => 'password',
545             connect_options => $dbi_connect_options,
546             reuse_dbh => 1, # sharing dbh (experimental option)
547             # When you use by MySQL, please set up
548             # connect_options => { mysql_auto_reconnect => 1 },
549             # simultaneously. but mysql_auto_reconnect is very unsettled.
550             );
551            
552             base_driver $driver;
553             install_model model_name => schema {
554             ....
555             };
556              
557             =head1 DESCRIPTION
558              
559             DBD that is working now is only mysql and SQLite.
560              
561             =head1 SEE ALSO
562              
563             L,
564             L
565              
566             =head1 AUTHOR
567              
568             Kazuhiro Osawa Eyappo shibuya plE
569              
570             =head1 LICENSE
571              
572             This library is free software; you can redistribute it and/or modify
573             it under the same terms as Perl itself.
574              
575             =cut