File Coverage

blib/lib/DBIx/MoCo/DataBase.pm
Criterion Covered Total %
statement 162 193 83.9
branch 67 118 56.7
condition 18 28 64.2
subroutine 24 26 92.3
pod 1 13 7.6
total 272 378 71.9


line stmt bran cond sub pod time code
1             package DBIx::MoCo::DataBase;
2 16     16   111358 use strict;
  16         38  
  16         592  
3 16     16   94 use warnings;
  16         41  
  16         426  
4 16     16   88 use Carp;
  16         28  
  16         2056  
5 16     16   167 use base qw (Class::Data::Inheritable);
  16         44  
  16         2986  
6 16     16   53623 use DBI;
  16         342276  
  16         1344  
7 16     16   22129 use SQL::Abstract;
  16         199604  
  16         1801  
8              
9             __PACKAGE__->mk_classdata($_) for qw(username password
10             cache_connection last_insert_id);
11             __PACKAGE__->cache_connection(1);
12              
13             our $DEBUG = 0;
14             our $SQL_COUNT = 0;
15              
16             # $Carp::CarpLevel = 2;
17             my $sqla = SQL::Abstract->new;
18              
19             sub insert {
20 26     26 0 1030 my $class = shift;
21 26         68 my ($table, $args) = @_;
22 26         592 my ($sql, @binds) = $sqla->insert($table,$args);
23 26         8925 $class->execute($sql,undef,\@binds);
24             }
25              
26             sub delete {
27 11     11 0 244 my $class = shift;
28 11         25 my ($table, $where) = @_;
29 11 50       49 $where or croak "where is not specified to delete from $table";
30 11 50 33     154 (ref $where eq 'HASH' && %$where) or croak "where is not specified to delete from $table";
31 11         160 my ($sql, @binds) = $sqla->delete($table,$where);
32 11 50       2947 $sql =~ /WHERE/io or croak "where is not specified to delete from $table";
33 11         104 $class->execute($sql,undef,\@binds);
34             }
35              
36             sub update {
37 14     14 0 362 my $class = shift;
38 14         45 my ($table, $args, $where) = @_;
39 14 50       56 $where or croak "where is not specified to update $table";
40 14 50 33     184 (ref $where eq 'HASH' && %$where) or croak "where is not specified to update $table";
41 14         121 my ($sql, @binds) = $sqla->update($table,$args,$where);
42 14 50       6024 $sql =~ /WHERE/io or croak "where is not specified to update $table";
43 14         86 $class->execute($sql,undef,\@binds);
44             }
45              
46             sub select {
47 51     51 0 1537 my $class = shift;
48 51         283 my ($table, $args, $where, $order, $limit) = @_;
49 51         387 my ($sql, @binds) = $sqla->select($table,$args,$where,$order);
50 51 50       17605 $sql .= $class->_parse_limit($limit) if $limit;
51 51         92 my $data;
52 51 50       309 $class->execute($sql,\$data,\@binds) or return;
53 51         276 return $data;
54             }
55              
56             sub search {
57 49     49 0 3446 my $class = shift;
58 49         597 my %args = @_;
59 49         402 my ($sql, @binds) = $class->_search_sql(\%args);
60 49         92 my $data;
61 49 50       462 $class->execute($sql,\$data,\@binds) or return;
62 49         395 return $data;
63             }
64              
65             sub _search_sql {
66 49     49   100 my $class = shift;
67 49         191 my $args = shift;
68 49   100     273 my $field = $args->{field} || "*";
69 49         237 my $sql = "SELECT $field FROM " . $args->{table};
70 49 50       216 $sql .= " USE INDEX ($args->{use_index})" if $args->{use_index};
71 49         279 my ($where,@binds) = $class->_parse_where($args->{where});
72 49 100       7168 $sql .= $where if $where;
73 49 50       181 $sql .= " GROUP BY $args->{group}" if $args->{group};
74 49 100       165 $sql .= " ORDER BY $args->{order}" if $args->{order};
75 49         258 $sql .= $class->_parse_limit($args);
76 49         311 return ($sql,@binds);
77             }
78              
79             sub _parse_where {
80 49     49   110 my ($class, $where) = @_;
81 49         466 my $binds = [];
82 49 100       337 if (ref $where eq 'ARRAY') {
    100          
    100          
83 23         49 my $sql = shift @$where;
84 23 100       98 if ($sql =~ m!\s*:[A-Za-z_][A-Za-z0-9_]+\s*!o) {
85 1 50       6 @$where % 2 and croak "You gave me an odd number of parameters to 'where'!";
86 1         4 my %named_values = @$where;
87 1         2 my @values;
88 1         5 $sql =~ s{:([A-Za-z_][A-Za-z0-9_]*)}{
89 1 50       5 croak "$1 is not exists in hash" if !exists $named_values{$1};
90 1         2 my $value = $named_values{$1};
91 1 50       9 if (ref $value eq 'ARRAY') {
92 0         0 push @values, $_ for @$value;
93 0         0 join ',', map('?', 1..@$value);
94             } else {
95 1         3 push @values, $value;
96 1         3 '?'
97             }
98             }ge;
99 1         3 $binds = \@values;
100             } else {
101 22         42 $binds = $where;
102             }
103 23         136 return (' WHERE ' . $sql, @$binds);
104             } elsif (ref $where eq 'HASH') {
105 16         159 return $sqla->where($where);
106             } elsif ($where) {
107 3         16 return ' WHERE ' . $where;
108             }
109 7         33 return $where;
110             }
111              
112             sub _parse_limit {
113 49     49   130 my ($class, $args) = @_;
114 49         139 my $sql = '';
115 49 100 100     357 if ($args->{offset} || $args->{limit}) {
116 10         28 $sql .= " LIMIT ";
117 10 100 66     50 if ($args->{offset} && $args->{offset} =~ m/^\d+$/o) {
118 1         5 $sql .= $args->{offset}.",";
119             }
120 10 50       85 $sql .= $args->{limit} =~ /^\d+$/o ? $args->{limit} : '1';
121             }
122 49         137 return $sql;
123             }
124              
125             sub dsn {
126 23     23 1 470574 my $class = shift;
127 23         52 my ($master_dsn, $slave_dsn);
128 23 100 66     249 if ($_[0] && ref($_[0]) eq 'HASH') {
129 1         3 @_ = (%{$_[0]});
  1         5  
130             }
131 23 100       132 if ($_[1]) {
    50          
132 3         10 my %args = @_;
133 3 50       12 my $master = $args{master} or croak "master dsn is not specified";
134 3 100       14 $master_dsn = ref($master) eq 'ARRAY' ? $master : [$master];
135 3   66     11 my $slave = $args{slave} || $master;
136 3 50       15 $slave_dsn = ref($slave) eq 'ARRAY' ? $slave : [$slave];
137             } elsif ($_[0]) {
138 20 100       124 $slave_dsn = $master_dsn = ref($_[0]) eq 'ARRAY' ? $_[0] : [$_[0]];
139             } else {
140 0         0 croak "Please specify your dsn.";
141             }
142             # $dsn->{$class} = {
143             # master => $master_dsn,
144             # slave => $slave_dsn,
145             # };
146 23         81 my $getter = $class . '::get_dsn';
147             {
148 16     16   23600 no strict 'refs';
  16         44  
  16         711  
  23         94  
149 16     16   105 no warnings 'redefine';
  16         38  
  16         21773  
150 23         187 *{$getter} = sub {
151 527     527   1294 my $class = shift;
152 527         1461 my $sql = shift;
153 527         1232 my $list = $master_dsn;
154 527 100 100     4877 if ($sql && $sql =~ /^SELECT/io) { $list = $slave_dsn }
  112         230  
155 527         1654 my $dsn = shift @$list;
156 527         2820 push @$list, $dsn;
157 527         2170 return $dsn;
158             }
159 23         162 }
160             }
161              
162 0     0 0 0 sub get_dsn { croak "You must set up your dsn first" }
163              
164             sub dbh {
165 504     504 0 17720 my $class = shift;
166 504         1822 my $sql = shift;
167 504 50       3899 my $connect = $class->cache_connection ? 'connect_cached' : 'connect';
168 504         7589 my $dsn = $class->get_dsn($sql);
169 504         2624 my $opt = {RaiseError => 1};
170 504         3193 DBI->$connect($dsn, $class->username, $class->password, $opt);
171             }
172              
173             sub execute {
174 428     428 0 13498 my $class = shift;
175 428         1681 my ($sql, $data, $binds) = @_;
176 428 50       1637 $sql or return;
177 428 100       3338 my @bind_values = ref $binds eq 'ARRAY' ? @$binds : ();
178 428         3420 my $dbh = $class->dbh(substr($sql,0,8));
179 428 100       402804 my $sth = @bind_values ? $dbh->prepare_cached($sql,undef,1) :
180             $dbh->prepare($sql);
181 428 0       59421 unless ($sth) { carp $dbh->errstr and return; }
  0 50       0  
182 428 50       1914 if ($DEBUG) {
183 0 0       0 my @binds = map { defined $_ ? "'$_'" : "'NULL'" } @bind_values;
  0         0  
184 0         0 carp $sql . '->execute(' . join(',', @binds) . ')';
185 0         0 $SQL_COUNT++;
186             }
187              
188             my $sql_error = sub {
189 0     0   0 my ($sql, $sth) = @_;
190 0 0       0 defined $data
191             ? sprintf('SQL Error: "%s" (%s)', $sql, $sth->errstr)
192             : sprintf('SQL Error "%s"', $sql);
193 428         5321 };
194              
195 428         1110 eval {
196 428 100       1292 if (defined $data) {
197 105 50 0     12981 $sth->execute(@bind_values) or carp $sql_error->($sth, $sql) and return;
198 105         1816 $$data = $sth->fetchall_arrayref({});
199             } else {
200 323 50       16488357 unless ($sth->execute(@bind_values)) {
201 0         0 $sql_error->($sql, $sth);
202 0         0 return;
203             }
204             }
205             };
206 428 50       19528 if ($@) {
207 0         0 confess $sql_error->($sql, $sth);
208             }
209              
210 428 100       15673 if ($sql =~ /^insert/io) {
211 218   66     7858 $class->last_insert_id($dbh->last_insert_id(undef,undef,undef,undef) ||
212             $dbh->{'mysql_insertid'});
213             }
214 428         29662 return !$sth->err;
215             }
216              
217             sub vendor {
218 18     18 0 71 my $class = shift;
219 18         64 $class->dbh->get_info(17); # SQL_DBMS_NAME
220             }
221              
222             sub primary_keys {
223 13     13 0 272 my $class = shift;
224 13 50       65 my $table = shift or return;
225 13         76 my $dbh = $class->dbh;
226 13 50       4749 if ($class->vendor eq 'MySQL') {
227 0 0       0 my $sth = $dbh->column_info(undef,undef,$table,'%') or
228             croak $dbh->errstr;
229 0 0       0 $dbh->err and croak $dbh->errstr;
230 0 0       0 my @cols = @{$sth->fetchall_arrayref({})} or
  0         0  
231             croak "couldnt get primary keys";
232             return [
233 0         0 map {$_->{COLUMN_NAME}}
  0         0  
234 0         0 grep {$_->{mysql_is_pri_key}}
235             @cols
236             ];
237             } else {
238 13         3802 return [$dbh->primary_key(undef,undef,$table)];
239             }
240             }
241              
242             sub unique_keys {
243 3     3 0 234 my $class = shift;
244 3 50       19 my $table = shift or return;
245 3 50       15 if ($class->vendor eq 'MySQL') {
246 0         0 my $sql = "SHOW INDEX FROM $table";
247 0         0 my $data;
248 0 0       0 $class->execute($sql,\$data) or
249             croak "couldnt get unique keys";
250 0 0       0 @$data or croak "couldnt get unique keys";
251             return [
252 0         0 map {$_->{Column_name}}
  0         0  
253 0         0 grep {!$_->{Non_unique}}
254             @$data
255             ];
256             } else {
257 3         816 return $class->primary_keys($table);
258             }
259             }
260              
261             sub columns {
262 18     18 0 394 my $class = shift;
263 18 50       101 my $table = shift or return;
264 18         134 my $dbh = $class->dbh;
265 18 50       7531 if (my $sth = $class->dbh->column_info(undef,undef,$table,'%')) {
266 18 50       77166 croak $dbh->errstr if $dbh->err;
267 18 50       58 my @cols = @{$sth->fetchall_arrayref({})} or
  18         380  
268             croak "couldnt get primary keys";
269             return [
270 18         3822 map {$_->{COLUMN_NAME}}
  60         544  
271             @cols
272             ];
273             } else {
274 0 0         my $d = $class->select($table,'*',undef,'',{limit => 1}) or return;
275 0           return [keys %{$d->[0]}];
  0            
276             }
277             }
278              
279             1;
280              
281             =head1 NAME
282              
283             DBIx::MoCo::DataBase - Data Base Handler for DBIx::MoCo
284              
285             =head1 SYNOPSIS
286              
287             package MyDataBase;
288             use base qw(DBIx::MoCo::DataBase);
289              
290             __PACKAGE__->dsn('dbi:mysql:myapp');
291             __PACKAGE__->username('test');
292             __PACKAGE__->password('test');
293              
294             1;
295              
296             # In your scripts
297             MyDataBase->execute('select 1');
298              
299             # Configure your replication databases
300             __PACKAGE__->dsn(
301             master => 'dbi:mysql:dbname=test;host=db1',
302             slave => ['dbi:mysql:dbname=test;host=db2','dbi:mysql:dbname=test;host=db3'],
303             );
304              
305             =head1 METHODS
306              
307             =over 4
308              
309             =item cache_connection
310              
311             Controlls cache behavior for dbh connection. (default 1)
312             If its set to 0, DBIx::MoCo::DataBase uses DBI->connect instead of
313             DBI->connect_cached.
314              
315             DBIx::MoCo::DataBase->cache_connection(0);
316              
317             =item dsn
318              
319             Configures dsn(s). You can specify single dsn as string, multiple dsns as an array,
320             master/slave dsns as hash.
321              
322             If you specify multiple dsns, they will be rotated automatically in round-robin.
323             MoCo will use slave dsns when the sql begins with C
324              
325             MyDataBase->dsn('dbi:mysql:dbname=test');
326             MyDataBase->dsn(['dbi:mysql:dbname=test;host=db1','dbi:mysql:dbname=test;host=db2']);
327             MyDataBase->dsn(
328             master => ['dbi:mysql:dbname=test;host=db1','dbi:mysql:dbname=test;host=db2'],
329             );
330             MyDataBase->dsn(
331             master => 'dbi:mysql:dbname=test;host=db1',
332             slave => ['dbi:mysql:dbname=test;host=db2','dbi:mysql:dbname=test;host=db3'],
333             );
334              
335             =back
336              
337             =head1 SEE ALSO
338              
339             L, L
340              
341             =head1 AUTHOR
342              
343             Junya Kondo, Ejkondo@hatena.comE
344              
345             =head1 COPYRIGHT AND LICENSE
346              
347             Copyright (C) Hatena Inc. All Rights Reserved.
348              
349             This library is free software; you may redistribute it and/or modify
350             it under the same terms as Perl itself.
351              
352             =cut