File Coverage

blib/lib/Tie/Array/DBD.pm
Criterion Covered Total %
statement 213 266 80.0
branch 98 128 76.5
condition 10 17 58.8
subroutine 27 47 57.4
pod 1 1 100.0
total 349 459 76.0


line stmt bran cond sub pod time code
1             package Tie::Array::DBD;
2              
3             our $VERSION = "0.22";
4              
5 24     24   339560 use strict;
  24         54  
  24         686  
6 24     24   169 use warnings;
  24         49  
  24         553  
7              
8 24     24   127 use Carp;
  24         64  
  24         1413  
9              
10 24     24   12891 use DBI;
  24         143395  
  24         87820  
11              
12             my $dbdx = sprintf "%04d", (time + int rand 10000) % 10000;
13              
14             my %DB = (
15             Pg => {
16             temp => "temp",
17             t_key => "bigint not null primary key",
18             t_val => "bytea",
19             clear => "truncate table",
20             autoc => 0,
21             },
22             Unify => {
23             temp => "",
24             t_key => "numeric (9) not null primary key",
25             t_val => "binary",
26             clear => "delete from",
27             },
28             Oracle => {
29             temp => "global temporary", # Only as of Ora-9
30             t_key => "number (38) not null primary key",
31             t_val => "blob",
32             clear => "truncate table",
33             autoc => 0,
34             },
35             MariaDB => {
36             temp => "temporary",
37             t_key => "bigint not null primary key",
38             t_val => "blob",
39             clear => "truncate table",
40             autoc => 0,
41             },
42             mysql => {
43             temp => "temporary",
44             t_key => "bigint not null primary key",
45             t_val => "blob",
46             clear => "truncate table",
47             autoc => 0,
48             },
49             SQLite => {
50             temp => "temporary",
51             t_key => "integer not null primary key",
52             t_val => "blob",
53             clear => "delete from",
54             pbind => 0, # TYPEs in SQLite are text, bind_param () needs int
55             autoc => 0,
56             },
57             CSV => {
58             temp => "temporary",
59             t_key => "integer not null primary key",
60             t_val => "text",
61             clear => "delete from",
62             },
63             Firebird => {
64             temp => "",
65             t_key => "integer primary key",
66             t_val => "varchar (8192)",
67             clear => "delete from",
68             },
69             );
70              
71             sub _create_table {
72 12     12   88 my ($cnf, $tmp) = @_;
73 12         38 $cnf->{tmp} = $tmp;
74              
75 12         32 my $dbh = $cnf->{dbh};
76 12         25 my $dbt = $cnf->{dbt};
77              
78 12         21 my $exists = 0;
79 12         24 eval {
80 12         124 local $dbh->{PrintError} = 0;
81 12         422 my $sth = $dbh->prepare ("select $cnf->{f_k}, $cnf->{f_v} from $cnf->{tbl}");
82 6         30472 $sth->execute;
83 0         0 $cnf->{tmp} = 0;
84 0         0 $exists = 1;
85             };
86 12 50       15859 $exists and return; # Table already exists
87              
88 12         555 my $temp = $DB{$dbt}{temp};
89 12 50       48 $cnf->{tmp} or $temp = "";
90 12 100 66     97 local $dbh->{AutoCommit} = 1 unless $dbt eq "CSV" || $dbt eq "Unify";
91 12         226 $dbh->do (
92             "create $temp table $cnf->{tbl} (".
93             "$cnf->{f_k} $cnf->{ktp},".
94             "$cnf->{f_v} $cnf->{vtp})"
95             );
96 12 50       24581 $dbt eq "Unify" and $dbh->commit;
97             } # create table
98              
99             sub TIEARRAY {
100 48     48   356937 my $pkg = shift;
101 48         220 my $usg = qq{usage: tie \@a, "$pkg", \$dbh [, { tbl => "tbl", key => "f_key", fld => "f_value" }];};
102 48 50       236 my $dsn = shift or croak $usg;
103 48         95 my $opt = shift;
104              
105 48 50       482 my $dbh = ref $dsn
    50          
106             ? $dsn->clone
107             : DBI->connect ($dsn, undef, undef, {
108             PrintError => 1,
109             RaiseError => 1,
110             PrintWarn => 0,
111             FetchHashKeyName => "NAME_lc",
112             }) or croak DBI->errstr;
113              
114 44   50     55601 my $dbt = $dbh->{Driver}{Name} || "no DBI handle";
115 44 50       518 my $cnf = $DB{$dbt} or croak "I don't support database '$dbt'";
116 44         99 my $f_k = "h_key";
117 44         92 my $f_v = "h_value";
118 44         83 my $tmp = 0;
119              
120 44         220 $dbh->{PrintWarn} = 0;
121 44 100       464 $dbh->{AutoCommit} = $cnf->{autoc} if exists $cnf->{autoc};
122 44 50       174 $dbh->{LongReadLen} = 4_194_304 if $dbt eq "Oracle";
123              
124             my $h = {
125             dbt => $dbt,
126             dbh => $dbh,
127             tbl => undef,
128             tmp => $tmp,
129             ktp => $cnf->{t_key},
130             vtp => $cnf->{t_val},
131              
132 44         322 _en => undef,
133             _de => undef,
134             };
135              
136 44 100       130 if ($opt) {
137 42 50       145 ref $opt eq "HASH" or croak $usg;
138              
139 42 50       124 $opt->{key} and $f_k = $opt->{key};
140 42 50       116 $opt->{fld} and $f_v = $opt->{fld};
141 42 50       133 $opt->{tbl} and $h->{tbl} = $opt->{tbl};
142 42 50       101 $opt->{vtp} and $h->{vtp} = $opt->{vtp};
143              
144 42 100       113 if (my $str = $opt->{str}) {
145 40 100       312 if ($str eq "Sereal") {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
146 4         47 require Sereal::Encoder;
147 4         32 require Sereal::Decoder;
148 4         52 my $se = Sereal::Encoder->new;
149 4         41 my $sd = Sereal::Decoder->new;
150 4     312   29 $h->{_en} = sub { $se->encode ($_[0]) };
  312         1482  
151 4     880   24 $h->{_de} = sub { $sd->decode ($_[0]) };
  880         4893  
152             }
153             elsif ($str eq "Storable") {
154 4         36 require Storable;
155 4     312   36 $h->{_en} = sub { Storable::nfreeze ({ val => $_[0] }) };
  312         1116  
156 4     880   21 $h->{_de} = sub { Storable::thaw ($_[0])->{val} };
  880         2247  
157             }
158             elsif ($str eq "FreezeThaw") {
159 4         863 require FreezeThaw;
160 0     0   0 $h->{_en} = sub { FreezeThaw::freeze ($_[0]) };
  0         0  
161 0     0   0 $h->{_de} = sub { (FreezeThaw::thaw ($_[0]))[0] };
  0         0  
162             }
163             elsif ($str eq "JSON") {
164 4         1049 require JSON;
165 0         0 my $j = JSON->new->allow_nonref;
166 0     0   0 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  0         0  
167 0     0   0 $h->{_de} = sub { $j->decode ($_[0]) };
  0         0  
168             }
169             elsif ($str eq "JSON::Syck") {
170 4         976 require JSON::Syck;
171 0     0   0 $h->{_en} = sub { JSON::Syck::Dump ($_[0]) };
  0         0  
172 0     0   0 $h->{_de} = sub { JSON::Syck::Load ($_[0]) };
  0         0  
173             }
174             elsif ($str eq "YAML") {
175 4         881 require YAML;
176 0     0   0 $h->{_en} = sub { YAML::Dump ($_[0]) };
  0         0  
177 0     0   0 $h->{_de} = sub { YAML::Load ($_[0]) };
  0         0  
178             }
179             elsif ($str eq "YAML::Syck") {
180 4         875 require YAML::Syck;
181 0     0   0 $h->{_en} = sub { YAML::Syck::Dump ($_[0]) };
  0         0  
182 0     0   0 $h->{_de} = sub { YAML::Syck::Load ($_[0]) };
  0         0  
183             }
184             elsif ($str eq "Data::Dumper") {
185 0         0 require Data::Dumper;
186 0     0   0 $h->{_en} = sub { Data::Dumper::Dumper ($_[0]) };
  0         0  
187 0     0   0 $h->{_de} = sub { eval $_[0] };
  0         0  
188             }
189             elsif ($str eq "XML::Dumper") {
190 4         935 require XML::Dumper;
191 0         0 my $xd = XML::Dumper->new;
192 0     0   0 $h->{_en} = sub { $xd->pl2xml ($_[0]) };
  0         0  
193 0     0   0 $h->{_de} = sub { $xd->xml2pl ($_[0]) };
  0         0  
194             }
195             elsif ($str eq "Bencode") {
196 4         867 require Bencode;
197 0     0   0 $h->{_en} = sub { Bencode::bencode ($_[0]) };
  0         0  
198 0     0   0 $h->{_de} = sub { Bencode::bdecode ($_[0]) };
  0         0  
199             }
200             else {
201 4         812 croak "Unsupported serializer: $str\n";
202             }
203             }
204             }
205              
206 12         39 $h->{f_k} = $f_k;
207 12         36 $h->{f_v} = $f_v;
208              
209 12 50       61 unless ($h->{tbl}) { # Create a temporary table
210 12         47 $tmp = ++$dbdx;
211 12         86 $h->{tbl} = "t_tie_dbda_$$" . "_$tmp";
212             }
213 12         64 _create_table ($h, $tmp);
214 12         72 _setmax ($h);
215              
216 12         504 my $tbl = $h->{tbl};
217              
218 12         92 $h->{ins} = $dbh->prepare ("insert into $tbl values (?, ?)");
219 12         9136 $h->{del} = $dbh->prepare ("delete from $tbl where $f_k = ?");
220 12         12058 $h->{upd} = $dbh->prepare ("update $tbl set $f_v = ? where $f_k = ?");
221 12         10842 $h->{sel} = $dbh->prepare ("select $f_v from $tbl where $f_k = ?");
222 12         8383 $h->{cnt} = $dbh->prepare ("select count(*) from $tbl");
223 12         5074 $h->{ctv} = $dbh->prepare ("select count(*) from $tbl where $f_k = ?");
224 12         8147 $h->{uky} = $dbh->prepare ("update $tbl set $f_k = ? where $f_k = ?");
225              
226 12 100 66     10281 unless (exists $cnf->{pbind} && !$cnf->{pbind}) {
227 6         53 my $sth = $dbh->prepare ("select $f_k, $f_v from $tbl where 0 = 1");
228 6         9108 $sth->execute;
229 6         5481 my @typ = @{$sth->{TYPE}};
  6         45  
230              
231 6         3685 $h->{ins}->bind_param (1, undef, $typ[0]);
232 6         95 $h->{ins}->bind_param (2, undef, $typ[1]);
233 6         64 $h->{del}->bind_param (1, undef, $typ[0]);
234 6         71 $h->{upd}->bind_param (1, undef, $typ[1]);
235 6         71 $h->{upd}->bind_param (2, undef, $typ[0]);
236 6         63 $h->{sel}->bind_param (1, undef, $typ[0]);
237 6         73 $h->{ctv}->bind_param (1, undef, $typ[0]);
238 6         64 $h->{uky}->bind_param (1, undef, $typ[0]);
239 6         61 $h->{uky}->bind_param (2, undef, $typ[0]);
240             }
241              
242 12         680 bless $h, $pkg;
243             } # TIEARRAY
244              
245             sub _stream {
246 780     780   1374 my ($self, $val) = @_;
247 780 50       1621 defined $val or return undef;
248              
249 780 100       2043 $self->{_en} and return $self->{_en}->($val);
250 156         236 return $val;
251             } # _stream
252              
253             sub _unstream {
254 2200     2200   4053 my ($self, $val) = @_;
255 2200 50       4180 defined $val or return undef;
256              
257 2200 100       5290 $self->{_de} and return $self->{_de}->($val);
258 440         1255 return $val;
259             } # _unstream
260              
261             sub _setmax {
262 22     22   58 my $self = shift;
263 22         213 my $sth = $self->{dbh}->prepare ("select max($self->{f_k}) from $self->{tbl}");
264 22         13854 $sth->execute;
265 22 50       11358 if (my $r = $sth->fetch) {
266 22 100       545 $self->{max} = defined $r->[0] ? $r->[0] : -1;
267             }
268             else {
269 0         0 $self->{max} = -1;
270             }
271 22         218 $self->{max};
272             } # _setmax
273              
274             sub STORE {
275 780     780   8505 my ($self, $key, $value) = @_;
276 780         1603 my $v = $self->_stream ($value);
277             $self->EXISTS ($key)
278             ? $self->{upd}->execute ($v, $key)
279 780 100       12884 : $self->{ins}->execute ($key, $v);
280 780 100       195076 $key > $self->{max} and $self->{max} = $key;
281             } # STORE
282              
283             sub DELETE {
284 160     160   314 my ($self, $key) = @_;
285 160         1384 $self->{sel}->execute ($key);
286 160 50       76814 my $r = $self->{sel}->fetch or return;
287 160         4257 $self->{del}->execute ($key);
288 160 100       74896 $key >= $self->{max} and $self->_setmax;
289 160         871 $self->_unstream ($r->[0]);
290             } # DELETE
291              
292             sub STORESIZE {
293 30     30   7010 my ($self, $size) = @_; # $size = $# + 1
294 30         68 $size--;
295 30         300 $self->{dbh}->do ("delete from $self->{tbl} where $self->{f_k} > $size");
296 30         38172 $self->{max} = $size;
297             } # STORESIZE
298              
299             sub CLEAR {
300 100     100   11747 my $self = shift;
301 100         945 $self->{dbh}->do ("$DB{$self->{dbt}}{clear} $self->{tbl}");
302 100         84873 $self->{max} = -1;
303             } # CLEAR
304              
305             sub EXISTS {
306 800     800   1399 my ($self, $key) = @_;
307 800 100       6513 $key <= $self->{max} or return 0;
308 130         1160 $self->{sel}->execute ($key);
309 130 100       53639 return $self->{sel}->fetch ? 1 : 0;
310             } # EXISTS
311              
312             sub FETCH {
313 2060     2060   8427 my ($self, $key) = @_;
314 2060 50       4555 $key <= $self->{max} or return undef;
315 2060         14216 $self->{sel}->execute ($key);
316 2060 100       992015 my $r = $self->{sel}->fetch or return;
317 2040         41336 $self->_unstream ($r->[0]);
318             } # STORE
319              
320             sub PUSH {
321 20     20   72 my ($self, @val) = @_;
322 20         54 for (@val) {
323 30         95 $self->STORE (++$self->{max}, $_);
324             }
325 20         71 return $self->FETCHSIZE;
326             } # PUSH
327              
328             sub POP {
329 10     10   42 my $self = shift;
330 10 50       52 $self->{max} >= 0 or return;
331 10         54 $self->DELETE ($self->{max});
332             } # POP
333              
334             sub SHIFT {
335 10     10   31 my $self = shift;
336 10         40 my $val = $self->DELETE (0);
337 10         273 $self->{uky}->execute ($_ - 1, $_) for 1 .. $self->{max};
338 10         11942 $self->{max}--;
339 10         59 return $val;
340             } # SHIFT
341              
342             sub UNSHIFT {
343 20     20   98 my ($self, @val) = @_;
344 20 50       72 @val or return;
345 20         42 my $incr = scalar @val;
346 20         466 $self->{uky}->execute ($_ + $incr, $_) for reverse 0 .. $self->{max};
347 20         27675 $self->{max} += $incr;
348 20         112 $self->STORE ($_, $val[$_]) for 0 .. $#val;
349 20         78 return $self->FETCHSIZE;
350             } # UNSHIFT
351              
352             # splice ARRAY, OFFSET, LENGTH, LIST
353             # splice ARRAY, OFFSET, LENGTH
354             # splice ARRAY, OFFSET
355             # splice ARRAY
356             #
357             # Removes the elements designated by OFFSET and LENGTH from an array, and
358             # replaces them with the elements of LIST, if any.
359             #
360             # In list context, returns the elements removed from the array.
361             # In scalar context, returns the last element removed, or "undef" if
362             # no elements are removed.
363             #
364             # The array grows or shrinks as necessary.
365             #
366             # If OFFSET is negative then it starts that far from the end of the array.
367             # If LENGTH is omitted, removes everything from OFFSET onward.
368             # If LENGTH is negative, removes the elements from OFFSET onward except for
369             # -LENGTH elements at the end of the array.
370             # If both OFFSET and LENGTH are omitted, removes everything.
371             # If OFFSET is past the end of the array, Perl issues a warning, and splices
372             # at the end of the array.
373              
374             sub SPLICE {
375 150     150   356 my $nargs = $#_;
376 150         391 my ($self, $off, $len, @new, @val) = @_;
377              
378             # splice @array;
379 150 100       422 if ($nargs == 0) {
380 20 100       71 if (wantarray) {
381 10         53 @val = map { $self->FETCH ($_) } 0 .. $self->{max};
  100         779  
382 10         140 $self->CLEAR;
383 10         119 return @val;
384             }
385 10         45 $val[0] = $self->FETCH ($self->{max});
386 10         124 $self->CLEAR;
387 10         99 return $val[0];
388             }
389              
390             # Take care of negative offset, count from tail
391 130 100       337 $off < 0 and $off = $self->{max} + 1 + $off;
392 130 100       2051 $off < 0 and
393             croak "Modification of non-creatable array value attempted, subscript $_[1]";
394              
395             # splice @array, off;
396 120 100       302 if ($nargs == 1) {
397 30 100       140 $off > $self->{max} and return;
398              
399 20 100       63 if (wantarray) {
400 10         44 @val = map { $self->FETCH ($_) } $off .. $self->{max};
  30         208  
401 10         145 $self->STORESIZE ($off);
402 10         98 return @val;
403             }
404 10         49 $val[0] = $self->FETCH ($self->{max});
405 10         141 $self->STORESIZE ($off);
406 10         87 return $val[0];
407             }
408              
409             # splice @array, off, len;
410 90 100 100     557 $nargs == 2 && $off > $self->{max} and return;
411              
412 60 100       242 my $last = $len < 0 ? $self->{max} + $len : $off + $len - 1;
413 60 50 66     227 $nargs == 2 && $last > $self->{max} and return $self->SPLICE ($off);
414              
415 60         178 @val = map { $self->DELETE ($_) } $off .. $last;
  130         886  
416 60         501 $len = @val;
417 60         1292 $self->{uky}->execute ($_ - $len, $_) for ($last + 1) .. $self->{max};
418 60         109104 $self->{max} -= $len;
419              
420             # splice @array, off, len, replacement-list;
421 60 100       186 if (@new) {
422 20         43 my $new = @new;
423 20         331 $self->{uky}->execute ($_ + $new, $_) for reverse $off .. $self->{max};
424 20         37195 $self->STORE ($off + $_, $new[$_]) for 0..$#new;
425 20         65 $self->{max} += $new;
426             }
427              
428 60 100       628 return wantarray ? @val : $val[-1];
429             } # SPLICE
430              
431             sub FIRSTKEY {
432 0     0   0 my $self = shift;
433 0 0       0 $self->{max} >= 0 or return;
434 0         0 $self->{min} = 0;
435             } # FIRSTKEY
436              
437             sub NEXTKEY {
438 0     0   0 my $self = shift;
439 0 0 0     0 exists $self->{min} && $self->{min} < $self->{max} and return ++$self->{min};
440 0         0 delete $self->{min};
441 0         0 return;
442             } # FIRSTKEY
443              
444             sub FETCHSIZE {
445 2150     2150   123270 my $self = shift;
446 2150         5239 return $self->{max} + 1;
447             } # FETCHSIZE
448              
449       70     sub EXTEND {
450             # no-op
451             } # EXTEND
452              
453             sub drop {
454 0     0 1 0 my $self = shift;
455 0         0 $self->{tmp} = 1;
456             } # drop
457              
458             sub _dump_table {
459 0     0   0 my $self = shift;
460 0         0 my $sth = $self->{dbh}->prepare ("select $self->{f_k}, $self->{f_v} from $self->{tbl} order by $self->{f_k}");
461 0         0 $sth->execute;
462 0         0 $sth->bind_columns (\my ($k, $v));
463 0         0 while ($sth->fetch) {
464 0         0 printf STDERR "%6d: '%s'\n", $k, $self->_unstream ($v);
465             }
466             } # _dump_table
467              
468             sub DESTROY {
469 12     12   104 my $self = shift;
470 12 50       150 my $dbh = $self->{dbh} or return;
471 12         87 for (qw( sel ins upd del cnt ctv uky )) {
472 84 50       222 $self->{$_} or next;
473 84         315 $self->{$_}->finish;
474 84         789 undef $self->{$_}; # DESTROY handle
475 84         3624 delete $self->{$_};
476             }
477 12         122 delete $self->{$_} for qw( _de _en );
478 12 50       56 if ($self->{tmp}) {
479 12 100       1406 $dbh->{AutoCommit} or $dbh->rollback;
480 12         213 $dbh->do ("drop table ".$self->{tbl});
481             }
482 12 100       86365 $dbh->{AutoCommit} or $dbh->commit;
483 12         797 $dbh->disconnect;
484 12         372 undef $dbh;
485 12         598 undef $self->{dbh};
486             } # DESTROY
487              
488             1;
489              
490             __END__