File Coverage

blib/lib/Tie/Array/DBD.pm
Criterion Covered Total %
statement 238 301 79.0
branch 126 170 74.1
condition 10 17 58.8
subroutine 31 55 56.3
pod 2 2 100.0
total 407 545 74.6


line stmt bran cond sub pod time code
1             package Tie::Array::DBD;
2              
3             our $VERSION = "0.26";
4              
5 24     24   2763291 use strict;
  24         64  
  24         914  
6 24     24   121 use warnings;
  24         39  
  24         1207  
7              
8 24     24   148 use Carp;
  24         67  
  24         1710  
9              
10 24     24   22253 use DBI;
  24         163910  
  24         124282  
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 _ro_fail {
72 28     28   89 my ($self, $action) = @_;
73 28         66 my $error = "You cannot $action when in read-only mode";
74 28 100       487 $self->{ro} == 1 ? warn $error : die $error;
75             } # ro_fail
76              
77             sub _create_table {
78 16     16   60 my ($cnf, $tmp) = @_;
79 16         41 $cnf->{tmp} = $tmp;
80              
81 16         39 my $dbh = $cnf->{dbh};
82 16         41 my $dbt = $cnf->{dbt};
83              
84 16         32 my $exists = 0;
85 16         51 eval {
86 16         183 local $dbh->{PrintError} = 0;
87 16         530 my $sth = $dbh->prepare ("select $cnf->{f_k}, $cnf->{f_v} from $cnf->{tbl}");
88 8         46323 $sth->execute;
89 0         0 $cnf->{tmp} = 0;
90 0         0 $exists = 1;
91             };
92 16 50       22444 $exists and return; # Table already exists
93              
94 16 50       68 $cnf->{ro} and return _ro_fail ($cnf, "create tables");
95 16         58 my $temp = $DB{$dbt}{temp};
96 16 50       88 $cnf->{tmp} or $temp = "";
97 16 100 66     137 local $dbh->{AutoCommit} = 1 unless $dbt eq "CSV" || $dbt eq "Unify";
98 16         295 $dbh->do (
99             "create $temp table $cnf->{tbl} (".
100             "$cnf->{f_k} $cnf->{ktp},".
101             "$cnf->{f_v} $cnf->{vtp})"
102             );
103 16 50       39231 $dbt eq "Unify" and $dbh->commit;
104             } # create table
105              
106             sub TIEARRAY {
107 60     60   257519 my $pkg = shift;
108 60         163 my $usg = qq{usage: tie \@a, "$pkg", \$dbh [, { tbl => "tbl", key => "f_key", fld => "f_value" }];};
109 60 50       282 my $dsn = shift or croak $usg;
110 60         131 my $opt = shift;
111              
112 60 50       730 my $dbh = ref $dsn
    50          
113             ? $dsn->clone
114             : DBI->connect ($dsn, undef, undef, {
115             PrintError => 1,
116             RaiseError => 1,
117             PrintWarn => 0,
118             FetchHashKeyName => "NAME_lc",
119             }) or croak DBI->errstr;
120              
121 56   50     97517 my $dbt = $dbh->{Driver}{Name} || "no DBI handle";
122 56 50       786 my $cnf = $DB{$dbt} or croak "I don't support database '$dbt'";
123 56         122 my $f_k = "h_key";
124 56         111 my $f_v = "h_value";
125 56         96 my $tmp = 0;
126              
127 56         244 $dbh->{PrintWarn} = 0;
128 56 100       579 $dbh->{AutoCommit} = $cnf->{autoc} if exists $cnf->{autoc};
129 56 50       164 $dbh->{LongReadLen} = 4_194_304 if $dbt eq "Oracle";
130              
131             my $h = {
132             dbt => $dbt,
133             dbh => $dbh,
134             tbl => undef,
135             tmp => $tmp,
136             ktp => $cnf->{t_key},
137             vtp => $cnf->{t_val},
138 56         594 ro => 0,
139              
140             _en => undef,
141             _de => undef,
142             };
143              
144 56 100       184 if ($opt) {
145 54 50       202 ref $opt eq "HASH" or croak $usg;
146              
147 54 50       176 $opt->{key} and $f_k = $opt->{key};
148 54 50       169 $opt->{fld} and $f_v = $opt->{fld};
149 54 50       141 $opt->{tbl} and $h->{tbl} = $opt->{tbl};
150 54 50       154 $opt->{vtp} and $h->{vtp} = $opt->{vtp};
151              
152 54 50       138 exists $opt->{ro} and $h->{ro} = $opt->{ro};
153              
154 54 100       161 if (my $str = $opt->{str}) {
155 52 100       577 if ($str eq "Sereal") {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
156 4         50 require Sereal::Encoder;
157 4         19 require Sereal::Decoder;
158 4         55 my $se = Sereal::Encoder->new;
159 4         76 my $sd = Sereal::Decoder->new;
160 4     316   31 $h->{_en} = sub { $se->encode ($_[0]) };
  316         2212  
161 4     884   22 $h->{_de} = sub { $sd->decode ($_[0]) };
  884         7038  
162             }
163             elsif ($str eq "Storable") {
164 4         33 require Storable;
165 4     316   26 $h->{_en} = sub { Storable::nfreeze ({ val => $_[0] }) };
  316         1685  
166 4     884   66 $h->{_de} = sub { Storable::thaw ($_[0])->{val} };
  884         3170  
167             }
168             elsif ($str eq "FreezeThaw") {
169 4         709 require FreezeThaw;
170 0     0   0 $h->{_en} = sub { FreezeThaw::freeze ($_[0]) };
  0         0  
171 0     0   0 $h->{_de} = sub { (FreezeThaw::thaw ($_[0]))[0] };
  0         0  
172             }
173             elsif ($str eq "JSON") {
174 4         1108 require JSON;
175 0         0 my $j = JSON->new->allow_nonref;
176 0     0   0 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  0         0  
177 0     0   0 $h->{_de} = sub { $j->decode ($_[0]) };
  0         0  
178             }
179             elsif ($str eq "JSON::MaybeXS") {
180 4         43 require JSON::MaybeXS;
181 4         46 my $j = JSON::MaybeXS->new->allow_nonref;
182 4     316   134 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  316         1946  
183 4     884   28 $h->{_de} = sub { $j->decode ($_[0]) };
  884         5357  
184             }
185             elsif ($str eq "JSON::SIMD") {
186 4         825 require JSON::SIMD;
187 0         0 my $j = JSON::SIMD->new->allow_nonref;
188 0     0   0 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  0         0  
189 0     0   0 $h->{_de} = sub { $j->decode ($_[0]) };
  0         0  
190             }
191             elsif ($str eq "JSON::Syck") {
192 4         809 require JSON::Syck;
193 0     0   0 $h->{_en} = sub { JSON::Syck::Dump ($_[0]) };
  0         0  
194 0     0   0 $h->{_de} = sub { JSON::Syck::Load ($_[0]) };
  0         0  
195             }
196             elsif ($str eq "JSON::XS") {
197 4         691 require JSON::XS;
198 0         0 my $j = JSON::XS->new->allow_nonref;
199 0     0   0 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  0         0  
200 0     0   0 $h->{_de} = sub { $j->decode ($_[0]) };
  0         0  
201             }
202             elsif ($str eq "YAML") {
203 4         765 require YAML;
204 0     0   0 $h->{_en} = sub { YAML::Dump ($_[0]) };
  0         0  
205 0     0   0 $h->{_de} = sub { YAML::Load ($_[0]) };
  0         0  
206             }
207             elsif ($str eq "YAML::Syck") {
208 4         638 require YAML::Syck;
209 0     0   0 $h->{_en} = sub { YAML::Syck::Dump ($_[0]) };
  0         0  
210 0     0   0 $h->{_de} = sub { YAML::Syck::Load ($_[0]) };
  0         0  
211             }
212             elsif ($str eq "Data::Dumper") {
213 0         0 require Data::Dumper;
214 0     0   0 $h->{_en} = sub { Data::Dumper::Dumper ($_[0]) };
  0         0  
215 0     0   0 $h->{_de} = sub { eval $_[0] };
  0         0  
216             }
217             elsif ($str eq "XML::Dumper") {
218 4         640 require XML::Dumper;
219 0         0 my $xd = XML::Dumper->new;
220 0     0   0 $h->{_en} = sub { $xd->pl2xml ($_[0]) };
  0         0  
221 0     0   0 $h->{_de} = sub { $xd->xml2pl ($_[0]) };
  0         0  
222             }
223             elsif ($str eq "Bencode") {
224 4         655 require Bencode;
225 0     0   0 $h->{_en} = sub { Bencode::bencode ($_[0]) };
  0         0  
226 0     0   0 $h->{_de} = sub { Bencode::bdecode ($_[0]) };
  0         0  
227             }
228             else {
229 4         1102 croak "Unsupported serializer: $str\n";
230             }
231             }
232             }
233              
234 16         107 $h->{f_k} = $f_k;
235 16         93 $h->{f_v} = $f_v;
236              
237 16 50       63 unless ($h->{tbl}) { # Create a temporary table
238 16         48 $tmp = ++$dbdx;
239 16         177 $h->{tbl} = "t_tie_dbda_$$" . "_$tmp";
240             }
241 16         143 _create_table ($h, $tmp);
242 16         89 _setmax ($h);
243              
244 16         910 my $tbl = $h->{tbl};
245              
246 16 50       184 $h->{ins} = $dbh->prepare ("insert into $tbl ($f_k, $f_v) values (?, ?)") unless $h->{ro};
247 16 50       17194 $h->{del} = $dbh->prepare ("delete from $tbl where $f_k = ?") unless $h->{ro};
248 16 50       20636 $h->{upd} = $dbh->prepare ("update $tbl set $f_v = ? where $f_k = ?") unless $h->{ro};
249 16         17182 $h->{sel} = $dbh->prepare ("select $f_v from $tbl where $f_k = ?");
250 16         13627 $h->{cnt} = $dbh->prepare ("select count(*) from $tbl");
251 16         7897 $h->{ctv} = $dbh->prepare ("select count(*) from $tbl where $f_k = ?");
252 16 50       12590 $h->{uky} = $dbh->prepare ("update $tbl set $f_k = ? where $f_k = ?") unless $h->{ro};
253              
254 16 100 66     19462 unless (exists $cnf->{pbind} && !$cnf->{pbind}) {
255 8         96 my $sth = $dbh->prepare ("select $f_k, $f_v from $tbl where 0 = 1");
256 8         14876 $sth->execute;
257 8         9000 my @typ = @{$sth->{TYPE}};
  8         201  
258              
259 8         4576 $h->{ins}->bind_param (1, undef, $typ[0]);
260 8         148 $h->{ins}->bind_param (2, undef, $typ[1]);
261 8         86 $h->{del}->bind_param (1, undef, $typ[0]);
262 8         89 $h->{upd}->bind_param (1, undef, $typ[1]);
263 8         76 $h->{upd}->bind_param (2, undef, $typ[0]);
264 8         84 $h->{sel}->bind_param (1, undef, $typ[0]);
265 8         78 $h->{ctv}->bind_param (1, undef, $typ[0]);
266 8         116 $h->{uky}->bind_param (1, undef, $typ[0]);
267 8         73 $h->{uky}->bind_param (2, undef, $typ[0]);
268             }
269              
270 16         1421 bless $h, $pkg;
271             } # TIEARRAY
272              
273             sub _stream {
274 1106     1106   2520 my ($self, $val) = @_;
275 1106 50       2550 defined $val or return undef;
276              
277 1106 100       4023 $self->{_en} and return $self->{_en}->($val);
278 158         274 return $val;
279             } # _stream
280              
281             sub _unstream {
282 3094     3094   6937 my ($self, $val) = @_;
283 3094 50       7092 defined $val or return undef;
284              
285 3094 100       11042 $self->{_de} and return $self->{_de}->($val);
286 442         1545 return $val;
287             } # _unstream
288              
289             sub _setmax {
290 30     30   77 my $self = shift;
291 30         323 my $sth = $self->{dbh}->prepare ("select max($self->{f_k}) from $self->{tbl}");
292 30         25885 $sth->execute;
293 30 50       20533 if (my $r = $sth->fetch) {
294 30 100       978 $self->{max} = defined $r->[0] ? $r->[0] : -1;
295             }
296             else {
297 0         0 $self->{max} = -1;
298             }
299 30         406 $self->{max};
300             } # _setmax
301              
302             sub STORE {
303 1134     1134   14933 my ($self, $key, $value) = @_;
304 1134 100       3805 $self->{ro} and return _ro_fail ($self, "store entries");
305 1106         3108 my $v = $self->_stream ($value);
306             $self->EXISTS ($key)
307             ? $self->{upd}->execute ($v, $key)
308 1106 100       18644 : $self->{ins}->execute ($key, $v);
309 1106 100       365056 $key > $self->{max} and $self->{max} = $key;
310             } # STORE
311              
312             sub DELETE {
313 224     224   512 my ($self, $key) = @_;
314 224 50       836 $self->{ro} and return _ro_fail ($self, "delete entries");
315 224         2423 $self->{sel}->execute ($key);
316 224 50       142363 my $r = $self->{sel}->fetch or return;
317 224         6556 $self->{del}->execute ($key);
318 224 100       135425 $key >= $self->{max} and $self->_setmax;
319 224         1819 $self->_unstream ($r->[0]);
320             } # DELETE
321              
322             sub STORESIZE {
323 42     42   21163 my ($self, $size) = @_; # $size = $# + 1
324 42 50       195 $self->{ro} and return _ro_fail ($self, "resize an array");
325 42         88 $size--;
326 42         548 $self->{dbh}->do ("delete from $self->{tbl} where $self->{f_k} > $size");
327 42         72085 $self->{max} = $size;
328             } # STORESIZE
329              
330             sub CLEAR {
331 140     140   20971 my $self = shift;
332 140 50       631 $self->{ro} and return _ro_fail ($self, "clear an array");
333 140         2045 $self->{dbh}->do ("$DB{$self->{dbt}}{clear} $self->{tbl}");
334 140         199088 $self->{max} = -1;
335             } # CLEAR
336              
337             sub EXISTS {
338 1134     1134   3513 my ($self, $key) = @_;
339 1134 100       11493 $key <= $self->{max} or return 0;
340 182         2011 $self->{sel}->execute ($key);
341 182 100       94726 return $self->{sel}->fetch ? 1 : 0;
342             } # EXISTS
343              
344             sub FETCH {
345 2926     2926   26322 my ($self, $key) = @_;
346 2926 100       9052 $key <= $self->{max} or return undef;
347 2898         38163 $self->{sel}->execute ($key);
348 2898 100       1916621 my $r = $self->{sel}->fetch or return;
349 2870         73477 $self->_unstream ($r->[0]);
350             } # STORE
351              
352             sub PUSH {
353 28     28   122 my ($self, @val) = @_;
354 28 50       162 $self->{ro} and return _ro_fail ($self, "push entries");
355 28         146 $self->STORE (++$self->{max}, $_) for @val;
356 28         108 return $self->FETCHSIZE;
357             } # PUSH
358              
359             sub POP {
360 14     14   43 my $self = shift;
361 14 50       83 $self->{ro} and return _ro_fail ($self, "pop entries");
362 14 50       65 $self->{max} >= 0 or return;
363 14         91 $self->DELETE ($self->{max});
364             } # POP
365              
366             sub SHIFT {
367 14     14   63 my $self = shift;
368 14 50       119 $self->{ro} and return _ro_fail ($self, "shift entries");
369 14         62 my $val = $self->DELETE (0);
370 14         593 $self->{uky}->execute ($_ - 1, $_) for 1 .. $self->{max};
371 14         27685 $self->{max}--;
372 14         118 return $val;
373             } # SHIFT
374              
375             sub UNSHIFT {
376 28     28   115 my ($self, @val) = @_;
377 28 50       107 @val or return;
378 28 50       109 $self->{ro} and return _ro_fail ($self, "unshift entries");
379 28         62 my $incr = scalar @val;
380 28         930 $self->{uky}->execute ($_ + $incr, $_) for reverse 0 .. $self->{max};
381 28         49784 $self->{max} += $incr;
382 28         260 $self->STORE ($_, $val[$_]) for 0 .. $#val;
383 28         119 return $self->FETCHSIZE;
384             } # UNSHIFT
385              
386             # splice ARRAY, OFFSET, LENGTH, LIST
387             # splice ARRAY, OFFSET, LENGTH
388             # splice ARRAY, OFFSET
389             # splice ARRAY
390             #
391             # Removes the elements designated by OFFSET and LENGTH from an array, and
392             # replaces them with the elements of LIST, if any.
393             #
394             # In list context, returns the elements removed from the array.
395             # In scalar context, returns the last element removed, or "undef" if
396             # no elements are removed.
397             #
398             # The array grows or shrinks as necessary.
399             #
400             # If OFFSET is negative then it starts that far from the end of the array.
401             # If LENGTH is omitted, removes everything from OFFSET onward.
402             # If LENGTH is negative, removes the elements from OFFSET onward except for
403             # -LENGTH elements at the end of the array.
404             # If both OFFSET and LENGTH are omitted, removes everything.
405             # If OFFSET is past the end of the array, Perl issues a warning, and splices
406             # at the end of the array.
407              
408             sub SPLICE {
409 210     210   583 my $nargs = $#_;
410 210         708 my ($self, $off, $len, @new, @val) = @_;
411              
412 210 50       950 $self->{ro} and return _ro_fail ($self, "splice entries");
413             # splice @array;
414 210 100       674 if ($nargs == 0) {
415 28 100       90 if (wantarray) {
416 14         71 @val = map { $self->FETCH ($_) } 0 .. $self->{max};
  140         1224  
417 14         220 $self->CLEAR;
418 14         170 return @val;
419             }
420 14         65 $val[0] = $self->FETCH ($self->{max});
421 14         167 $self->CLEAR;
422 14         117 return $val[0];
423             }
424              
425             # Take care of negative offset, count from tail
426 182 100       544 $off < 0 and $off = $self->{max} + 1 + $off;
427 182 100       3694 $off < 0 and
428             croak "Modification of non-creatable array value attempted, subscript $_[1]";
429              
430             # splice @array, off;
431 168 100       478 if ($nargs == 1) {
432 42 100       209 $off > $self->{max} and return;
433              
434 28 100       92 if (wantarray) {
435 14         67 @val = map { $self->FETCH ($_) } $off .. $self->{max};
  42         311  
436 14         202 $self->STORESIZE ($off);
437 14         165 return @val;
438             }
439 14         70 $val[0] = $self->FETCH ($self->{max});
440 14         192 $self->STORESIZE ($off);
441 14         116 return $val[0];
442             }
443              
444             # splice @array, off, len;
445 126 100 100     933 $nargs == 2 && $off > $self->{max} and return;
446              
447 84 100       370 my $last = $len < 0 ? $self->{max} + $len : $off + $len - 1;
448 84 50 66     424 $nargs == 2 && $last > $self->{max} and return $self->SPLICE ($off);
449              
450 84         281 @val = map { $self->DELETE ($_) } $off .. $last;
  182         1435  
451 84         773 $len = @val;
452 84         1765 $self->{uky}->execute ($_ - $len, $_) for ($last + 1) .. $self->{max};
453 84         195046 $self->{max} -= $len;
454              
455             # splice @array, off, len, replacement-list;
456 84 100       293 if (@new) {
457 28         64 my $new = @new;
458 28         453 $self->{uky}->execute ($_ + $new, $_) for reverse $off .. $self->{max};
459 28         67807 $self->STORE ($off + $_, $new[$_]) for 0..$#new;
460 28         89 $self->{max} += $new;
461             }
462              
463 84 100       857 return wantarray ? @val : $val[-1];
464             } # SPLICE
465              
466             sub FIRSTKEY {
467 0     0   0 my $self = shift;
468 0 0       0 $self->{max} >= 0 or return;
469 0         0 $self->{min} = 0;
470             } # FIRSTKEY
471              
472             sub NEXTKEY {
473 0     0   0 my $self = shift;
474 0 0 0     0 exists $self->{min} && $self->{min} < $self->{max} and return ++$self->{min};
475 0         0 delete $self->{min};
476 0         0 return;
477             } # FIRSTKEY
478              
479             sub FETCHSIZE {
480 3010     3010   238313 my $self = shift;
481 3010         9345 return $self->{max} + 1;
482             } # FETCHSIZE
483              
484       98     sub EXTEND {
485             # no-op
486             } # EXTEND
487              
488             sub drop {
489 0     0 1 0 my $self = shift;
490 0         0 $self->{tmp} = 1;
491             } # drop
492              
493             sub readonly {
494 56     56 1 26709 my $self = shift;
495 56 100       242 @_ and $self->{ro} = shift;
496 56         275 return $self->{ro};
497             } # readonly
498              
499             sub _dump_table {
500 0     0   0 my $self = shift;
501 0         0 my $sth = $self->{dbh}->prepare ("select $self->{f_k}, $self->{f_v} from $self->{tbl} order by $self->{f_k}");
502 0         0 $sth->execute;
503 0         0 $sth->bind_columns (\my ($k, $v));
504 0         0 while ($sth->fetch) {
505 0         0 printf STDERR "%6d: '%s'\n", $k, $self->_unstream ($v);
506             }
507             } # _dump_table
508              
509             sub DESTROY {
510 16     16   12922 my $self = shift;
511 16 50       118 my $dbh = $self->{dbh} or return;
512 16         66 for (qw( sel ins upd del cnt ctv uky )) {
513 112 50       343 $self->{$_} or next;
514 112         539 $self->{$_}->finish;
515 112         1401 undef $self->{$_}; # DESTROY handle
516 112         6300 delete $self->{$_};
517             }
518 16         188 delete $self->{$_} for qw( _de _en );
519 16 50       73 if ($self->{tmp}) {
520 16 100       3560 $dbh->{AutoCommit} or $dbh->rollback;
521 16 50       197 $self->{ro} and return _ro_fail ($self, "drop tables");
522 16         211 $dbh->do ("drop table ".$self->{tbl});
523             }
524 16 100       232560 $dbh->{AutoCommit} or $dbh->commit;
525 16         1267 $dbh->disconnect;
526 16         595 undef $dbh;
527 16         921 undef $self->{dbh};
528             } # DESTROY
529              
530             1;
531              
532             __END__