File Coverage

blib/lib/DBIx/Brev.pm
Criterion Covered Total %
statement 66 184 35.8
branch 18 108 16.6
condition 8 39 20.5
subroutine 14 30 46.6
pod 9 16 56.2
total 115 377 30.5


line stmt bran cond sub pod time code
1             package DBIx::Brev;
2              
3 1     1   30836 use strict;
  1         3  
  1         41  
4 1     1   7 use warnings;
  1         2  
  1         43  
5              
6 1     1   6241 use DBI;
  1         26720  
  1         121  
7              
8 1     1   1468 our $use_config = eval q{use Config::General;1};
  1         31333  
  1         43  
9 1     1   2210 our $use_sqlsplit = eval q{use SQL::SplitStatement;1};
  1         31137  
  1         11  
10 1     1   435 our $use_connector = eval q{use DBIx::Connector;1};
  0         0  
  0         0  
11              
12 1     1   11 use Scalar::Util qw(looks_like_number);
  1         2  
  1         117  
13              
14             our $VERSION = '0.02';
15              
16 1     1   6 use base 'Exporter';
  1         2  
  1         2363  
17              
18             our @EXPORT = our @EXPORT_OK = qw(
19             sql_exec
20             sql_value
21             sql_query
22             sql_map
23             sql_hash
24             sql_query_hash
25             sql_in
26             inserts
27             db_use
28             dbc
29             quote
30             );
31              
32             my ($dbh,$dbc);
33              
34             sub dbc {
35 1 50   1 0 4 $dbc = $_[0] if @_;
36 1 50       4 $dbh = $dbc unless $use_connector;
37 1         8 $dbc;
38             }
39              
40             sub dbh {
41 0 0   0 0 0 $dbh = $_[0] if @_;
42 0 0       0 $dbc = $dbh unless $use_connector;
43 0         0 $dbh;
44             }
45              
46             sub shift_params(&\@) {
47 3     3 0 6 my ($predicate,$params) = @_;
48 3         6 local $_ = shift(@$params);
49 3         4 my $p = $_;
50 3 50       5 if (eval {$predicate->($_) && 1}) {
  3 50       8  
51 0         0 return $p;
52             } else {
53 3         7 unshift @$params,$p;
54 3         7 return;
55             }
56             }
57              
58             my %config;
59             {
60             my $config_loaded;
61             sub load_config {
62             #return if $config_loaded; $config_loaded = 1;
63 3 50   3 0 4766 return unless $use_config;
64 3     3   19 my $config = shift_params {ref($_) eq 'HASH'} @_;
  3         16  
65 3 50       15 if ($config) {
66 0         0 %config = %$config;
67 0         0 return;
68             }
69 3         7 my $mswin = $^O eq 'MSWin32';
70 3 0       17 my @path = $mswin?map( {exists $ENV{$_}?$ENV{$_}:()} qw(
  0 50       0  
71             USERPROFILE HOME ALLUSERSPROFILE APPDATA ProgramData SYSTEMROOT WINDIR
72             )) : ($ENV{HOME},'/etc');
73 3 50       8 my $fd = $mswin?q{\\}:q{/};
74 3   100     386 my ($config_file) = grep defined && -f,@_,$ENV{DBI_CONF},map $_.$fd.q{dbi.conf},@path;
75 3 50       34 %config = Config::General->new($config_file)->getall if $config_file;
76             }
77             }
78              
79             my %dbc; # cache of dbx connections for fast switching between handles
80             sub db_use {
81 1     1 1 2599 my ($db_alias,%options) = @_;
82 1         5 my @connect = ($db_alias,@options{qw(username password)});
83 1         3 my $options = $options{options};
84 1   50     10 $options ||= {RaiseError => 1,AutoCommit => 1};
85             # subroutine changes default dbc if it is called in void context or $dbc is undefined
86 1   33     7 my $keep_default = $dbc && defined(wantarray);
87 1   50     9 my $connection_mode = delete $options->{connection_mode} || 'fixup';
88 1         2 my ($local_dbc) = $dbc{$db_alias};
89 1 50       4 unless ($local_dbc) {
90 1 50 33     11 load_config() if $use_config && keys(%config)==0;
91 1         4 my ($alias,$mode) = split /:/,$db_alias;
92 1         3 my $databases = $config{database};
93 1 50       3 die "wrong config" unless $databases;
94 1         5 my @keys = qw(data_source username password);
95 1 50 33     8 @connect = @{$databases->{$alias}}{map $mode."_$_",@keys} if exists $databases->{$alias} && $mode;
  0         0  
96 1 50       4 @connect = @{$databases->{$db_alias}}{@keys} if exists $databases->{$db_alias};
  1         12  
97 1         2 push @connect,$options;
98 1 50       35 $local_dbc = $use_connector?DBIx::Connector->new(@connect):DBI->connect(@connect);
99 1 50       13730 $local_dbc->mode($connection_mode) if $use_connector;
100 1         6 $dbc{$db_alias} = $dbc;
101             }
102 1 50       4 return $local_dbc if $keep_default;
103 1         5 dbc($local_dbc);
104             }
105              
106             sub quote {
107 0 0   0 0 0 die "connect to db first!" unless $dbc;
108 0 0       0 ($use_connector?$dbc->dbh():$dbh)->quote(@_);
109             }
110              
111 1     1   15 sub import { my ($class,$db_alias,@options) = @_;
112 1 50       7 db_use($db_alias,@options) if $db_alias;
113 1         150 $class->export_to_level(1);
114             }
115              
116             sub shift_connection(\@) {
117 0     0 0   my $c = shift;
118 0 0   0     my $local_dbc = shift_params {UNIVERSAL::isa($_,$use_connector?'DBIx::Connector':'DBI::db')} @$c;
  0            
119 0 0         unless ($local_dbc) {
120 0   0 0     my $db_alias = shift_params {!ref($_) && !m{\s}} @$c;
  0            
121 0 0         if ($db_alias) {
122 0     0     my $db_options = shift_params {ref($_) eq 'HASH'} @$c;
  0            
123 0           $local_dbc = db_use($db_alias,$db_options);
124             }
125             }
126 0   0       return $local_dbc || $dbc;
127             }
128              
129             sub get_sth
130             {
131 0     0 0   my $dbc = shift_connection(@_);
132 0           my ($sql,@bind_values) = @_;
133 0           my $executed;
134             my $sth;
135 0 0         if ($use_connector) {
136 0   0 0     $dbc->run(sub {$executed = ($sth = $_->prepare($sql)) && $sth->execute(@bind_values);});
  0            
137             } else {
138 0   0       $executed = ($sth = $dbc->prepare($sql)) && $sth->execute(@bind_values);
139             }
140 0 0         my $err = $@ or $sth->errstr;
141 0 0         die "$err\n[$sql]" if $err;
142 0 0         unless ($executed) {
143 0           die "Error:$DBI::errstr\nSQL::$sql\n";
144             };
145 0           return $sth;
146             }
147              
148             sub sql_query
149             {
150 0     0 1   my $sth = &get_sth;
151 0           my $r = $sth->fetchall_arrayref;
152 0 0 0       if (@$r && (@{$r->[0]}==1)) {
  0            
153 0           $_ = $_->[0] for @$r; #scalarize rows if row is single-dimension array
154             }
155 0 0         return wantarray?@$r:$r;
156             }
157              
158             sub sql_in {
159 0     0 1   my $dbc = shift_connection(@_);
160 0 0         my $dbh = $use_connector?$dbc->dbh():$dbc;
161 0 0         my $list = join ",",map {looks_like_number($_)?$_:$dbh->quote($_)} sql_query($dbc,@_);
  0            
162 0   0       sprintf(" in (%s)",$list || 'NULL');
163             }
164              
165             sub sql_value
166             {
167 0     0 1   my $sth = &get_sth;
168 0           my @row_ary = $sth->fetchrow_array;
169 0 0         wantarray()?@row_ary:$row_ary[0];
170             }
171              
172             sub sql_hash
173             {
174 0     0 1   my $sth = &get_sth;
175 0           my $hash = $sth->fetchrow_hashref;
176 0 0         return undef unless $hash;
177 0 0         wantarray()?%$hash:$hash;
178             }
179              
180             sub sql_query_hash
181             {
182 0     0 1   my $sth = &get_sth;
183 0           my @result;
184 0           while (my $row = $sth->fetchrow_hashref) { push @result,$row; }
  0            
185 0 0         wantarray()?@result:\@result;
186             }
187              
188             sub sql_map(&@)
189             {
190 0     0 1   my $callback = shift;
191 0           my $sth = get_sth(@_);
192 0           my @result = ();
193 0           my $wantresult = defined wantarray;
194 0           local $_;
195 0           while (defined($_ = $sth->fetch)) {
196             # do copy because fetch uses the same buffer
197 0 0         $_ = @$_>1?[@$_]:$_->[0];
198 0           my @r = $callback->($_);
199 0 0         last unless @r;
200 0 0         if ($wantresult) {
201 0           push @result, @r;
202             }
203             }
204 0 0         return unless $wantresult;
205 0 0         return wantarray?@result:\@result;
206             }
207              
208             sub sql_exec
209             {
210 0     0 1   my $dbc = shift_connection(@_);
211 0           my ($attr) = grep ref($_) eq 'HASH', @_,{};
212 0           my ($sql_code,@bind_values) = grep ref($_) ne 'HASH', @_;
213 0           my $rows_affected = 0;
214 0 0         my $dbh = $use_connector?$dbc->dbh():$dbc;
215 0 0         if ($use_sqlsplit) {
216 0   0       my $splitter_options = delete $attr->{splitter_options}||{};
217 0           my $no_commit = delete $attr->{no_commit};
218 0           my $splitter = SQL::SplitStatement->new($splitter_options);
219 0           my ( $statements, $placeholders ) = $splitter->split_with_placeholders( $sql_code );
220 0           $dbh->{AutoCommit} = 0; # enable transactions, if possible
221 0           $dbh->{RaiseError} = 1;
222 0 0         die $@ unless eval {
223 0           for my $statement (@$statements) {
224 0           my $placeholders_count = shift(@$placeholders);
225 0           my @sbind_values = splice @bind_values, 0, $placeholders_count;
226 0           $rows_affected += $dbh->do($statement,$attr,@sbind_values);
227             }
228 0 0         $dbh->commit unless $no_commit;
229 0           1;
230             };
231             } else {
232 0           $rows_affected += $dbh->do($sql_code,$attr,@bind_values);
233             }
234 0           return $rows_affected;
235             }
236            
237              
238             sub inserts {
239 0     0 1   my $dbc = shift_connection(@_);
240 0           my ($sprintf_sql,$data,%opts) = @_;
241 0 0         $sprintf_sql .= ' %s' unless $sprintf_sql =~ m{%s};
242             # set default step 500 for smooth sqlite experience (see http://stackoverflow.com/questions/15858466/limit-on-multiple-rows-insert)
243 0   0       my $step = $opts{step} || 500; # split batch into bunches of $step records
244 0   0       my $delay = ($opts{delay} || 0) / 1000; # delay between statements in milliseconds
245 0           my $last = $#$data;
246 0           my $offset = 0;
247 0           my $updated = 0;
248             # make fixup for dbh
249             #$dbc->run(fixup => sub {$_->do(q{SELECT 1});});
250             # do all inserts in one transaction
251 0 0         my $dbh = $use_connector?$dbc->dbh():$dbc;
252 0           $dbh->{AutoCommit} = 0; # enable transactions, if possible
253 0           $dbh->{RaiseError} = 1;
254 0 0         die $@ unless eval {
255 0           while ($offset <= $last) {
256 0           my $limit = $offset + $step - 1;
257 0 0         $limit = $last if $limit > $last;
258 0           my $records = join "\nunion all\n",
259             map sprintf("select %s",
260             ref($_) eq 'ARRAY'?
261             join(",", map $dbh->quote($_), @$_)
262             :
263             $_
264 0 0         ), @{$data}[$offset..$limit];
265 0           $offset += $step;
266 0 0         sleep($delay) if $updated;
267 0           my $time = time;
268 0           my $sql = sprintf($sprintf_sql,$records);
269 0           $updated += $dbh->do($sql);
270             # make next delay twice time of sql_exec if it was not explicitly specified
271 0 0         $delay = (time - $time) * 2 unless exists $opts{delay};
272             }
273 0           $dbh->commit;
274 0           1;
275             };
276 0           return $updated;
277             }
278              
279              
280             1;
281             __END__