File Coverage

blib/lib/Tie/Hash/DBD.pm
Criterion Covered Total %
statement 176 215 81.8
branch 95 126 75.4
condition 13 25 52.0
subroutine 22 38 57.8
pod 1 1 100.0
total 307 405 75.8


line stmt bran cond sub pod time code
1             package Tie::Hash::DBD;
2              
3             our $VERSION = "0.24";
4              
5 47     47   877042 use strict;
  47         231  
  47         1388  
6 47     47   245 use warnings;
  47         93  
  47         1099  
7              
8 47     47   249 use Carp;
  47         89  
  47         2853  
9              
10 47     47   78071 use DBI;
  47         856890  
  47         138265  
11              
12             my $dbdx = sprintf "%04d", (time + int rand 10000) % 10000;
13              
14             my %DB = (
15             # k_asc is needed if h_key mush be converted to hex because
16             # where clause is not permitted on binary/BLOB/...
17             Pg => {
18             temp => "temp",
19             t_key => "bytea primary key",
20             t_val => "bytea",
21             clear => "truncate table",
22             autoc => 0,
23             },
24             Unify => {
25             temp => "",
26             t_key => "text",
27             t_val => "binary",
28             clear => "delete from",
29             k_asc => 1,
30             },
31             Oracle => {
32             # Oracle does not allow where clauses on BLOB's nor does it allow
33             # BLOB's to be primary keys
34             temp => "global temporary", # Only as of Ora-9
35             t_key => "varchar2 (4000) primary key",
36             t_val => "blob",
37             clear => "truncate table",
38             autoc => 0,
39             k_asc => 1,
40             },
41             MariaDB => {
42             temp => "temporary",
43             t_key => "blob", # Does not allow binary to be primary key
44             t_val => "blob",
45             clear => "truncate table",
46             autoc => 0,
47             },
48             mysql => {
49             temp => "temporary",
50             t_key => "blob", # Does not allow binary to be primary key
51             t_val => "blob",
52             clear => "truncate table",
53             autoc => 0,
54             },
55             SQLite => {
56             temp => "temporary",
57             t_key => "text primary key",
58             t_val => "blob",
59             clear => "delete from",
60             pbind => 0, # TYPEs in SQLite are text, bind_param () needs int
61             autoc => 0,
62             },
63             CSV => {
64             temp => "temporary",
65             t_key => "text primary key",
66             t_val => "text",
67             clear => "delete from",
68             },
69             Firebird => {
70             temp => "",
71             t_key => "varchar (8192)",
72             t_val => "varchar (8192)",
73             clear => "delete from",
74             },
75             );
76              
77             sub _create_table {
78 21     21   85 my ($cnf, $tmp) = @_;
79 21         46 $cnf->{tmp} = $tmp;
80              
81 21         48 my $dbh = $cnf->{dbh};
82 21         54 my $dbt = $cnf->{dbt};
83              
84 21         36 my $exists = 0;
85 21         43 eval {
86 21         294 local $dbh->{PrintError} = 0;
87 21         796 my $sth = $dbh->prepare ("select $cnf->{f_k}, $cnf->{f_v} from $cnf->{tbl}");
88 11         55414 $sth->execute;
89 2         3960 $cnf->{tmp} = 0;
90 2         57 $exists = 1;
91             };
92 21 100       25531 $exists and return; # Table already exists
93              
94 19         850 my $temp = $DB{$dbt}{temp};
95 19 100       77 $cnf->{tmp} or $temp = "";
96 19 100 66     203 local $dbh->{AutoCommit} = 1 unless $dbt eq "CSV" || $dbt eq "Unify";
97 19         445 $dbh->do (
98             "create $temp table $cnf->{tbl} (".
99             "$cnf->{f_k} $cnf->{ktp},".
100             "$cnf->{f_v} $cnf->{vtp})"
101             );
102 19 50       67684 $dbt eq "Unify" and $dbh->commit;
103             } # create table
104              
105             sub TIEHASH {
106 77     77   1455196 my $pkg = shift;
107 77         294 my $usg = qq{usage: tie %h, "$pkg", \$dbh [, { tbl => "tbl", key => "f_key", fld => "f_value" }];};
108 77 50       351 my $dsn = shift or croak $usg;
109 77         205 my $opt = shift;
110              
111 77 50       763 my $dbh = ref $dsn
    50          
112             ? $dsn->clone
113             : DBI->connect ($dsn, undef, undef, {
114             PrintError => 1,
115             RaiseError => 1,
116             PrintWarn => 0,
117             FetchHashKeyName => "NAME_lc",
118             }) or croak (DBI->errstr);
119              
120 53   50     130162 my $dbt = $dbh->{Driver}{Name} || "no DBI handle";
121 53 50       641 my $cnf = $DB{$dbt} or croak "I don't support database '$dbt'";
122 53         115 my $f_k = "h_key";
123 53         93 my $f_v = "h_value";
124 53         107 my $tmp = 0;
125              
126 53         240 $dbh->{PrintWarn} = 0;
127 53 100       494 $dbh->{AutoCommit} = $cnf->{autoc} if exists $cnf->{autoc};
128 53 50       181 $dbh->{LongReadLen} = 4_194_304 if $dbt eq "Oracle";
129              
130             my $h = {
131             dbt => $dbt,
132             dbh => $dbh,
133             tbl => undef,
134             tmp => $tmp,
135             asc => $cnf->{k_asc} || 0,
136             trh => 0,
137             ktp => $cnf->{t_key},
138             vtp => $cnf->{t_val},
139              
140 53   50     654 _en => undef,
141             _de => undef,
142             };
143              
144 53 100       184 if ($opt) {
145 45 50       154 ref $opt eq "HASH" or croak $usg;
146              
147 45 50       174 $opt->{key} and $f_k = $opt->{key};
148 45 50       139 $opt->{fld} and $f_v = $opt->{fld};
149 45 100       116 $opt->{tbl} and $h->{tbl} = $opt->{tbl};
150 45 50       125 $opt->{trh} and $h->{trh} = $opt->{trh};
151 45 50       132 $opt->{ktp} and $h->{ktp} = $opt->{ktp};
152 45 50       159 $opt->{vtp} and $h->{vtp} = $opt->{vtp};
153              
154 45 100       136 if (my $str = $opt->{str}) {
155 40 100       331 if ($str eq "Sereal") {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
156 4         48 require Sereal::Encoder;
157 4         21 require Sereal::Decoder;
158 4         166 my $se = Sereal::Encoder->new;
159 4         64 my $sd = Sereal::Decoder->new;
160 4     32   34 $h->{_en} = sub { $se->encode ($_[0]) };
  32         480  
161 4     60   25 $h->{_de} = sub { $sd->decode ($_[0]) };
  60         1071  
162             }
163             elsif ($str eq "Storable") {
164 4         60 require Storable;
165 4     32   36 $h->{_en} = sub { Storable::nfreeze ({ val => $_[0] }) };
  32         139  
166 4     60   25 $h->{_de} = sub { Storable::thaw ($_[0])->{val} };
  60         175  
167             }
168             elsif ($str eq "FreezeThaw") {
169 4         945 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         1116 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::Syck") {
180 4         818 require JSON::Syck;
181 0     0   0 $h->{_en} = sub { JSON::Syck::Dump ($_[0]) };
  0         0  
182 0     0   0 $h->{_de} = sub { JSON::Syck::Load ($_[0]) };
  0         0  
183             }
184             elsif ($str eq "YAML") {
185 4         818 require YAML;
186 0     0   0 $h->{_en} = sub { YAML::Dump ($_[0]) };
  0         0  
187 0     0   0 $h->{_de} = sub { YAML::Load ($_[0]) };
  0         0  
188             }
189             elsif ($str eq "YAML::Syck") {
190 4         902 require YAML::Syck;
191 0     0   0 $h->{_en} = sub { YAML::Syck::Dump ($_[0]) };
  0         0  
192 0     0   0 $h->{_de} = sub { YAML::Syck::Load ($_[0]) };
  0         0  
193             }
194             elsif ($str eq "Data::Dumper") {
195 0         0 require Data::Dumper;
196 0     0   0 $h->{_en} = sub { Data::Dumper::Dumper ($_[0]) };
  0         0  
197 0     0   0 $h->{_de} = sub { eval $_[0] };
  0         0  
198             }
199             elsif ($str eq "XML::Dumper") {
200 4         824 require XML::Dumper;
201 0         0 my $xd = XML::Dumper->new;
202 0     0   0 $h->{_en} = sub { $xd->pl2xml ($_[0]) };
  0         0  
203 0     0   0 $h->{_de} = sub { $xd->xml2pl ($_[0]) };
  0         0  
204             }
205             elsif ($str eq "Bencode") {
206 4         960 require Bencode;
207 0     0   0 $h->{_en} = sub { Bencode::bencode ($_[0]) };
  0         0  
208 0     0   0 $h->{_de} = sub { Bencode::bdecode ($_[0]) };
  0         0  
209             }
210             else {
211 4         978 croak "Unsupported serializer: $str\n";
212             }
213             }
214             }
215              
216 21         64 $h->{f_k} = $f_k;
217 21         61 $h->{f_v} = $f_v;
218 21 50       66 $h->{trh} and $dbh->{AutoCommit} = 0;
219              
220 21 100       86 if ($h->{tbl}) { # Used told the table name
221 5 100 66     59 $dbh->{AutoCommit} = 1 unless $h->{trh} || $dbt eq "CSV" || $dbt eq "Unify";
      66        
222             }
223             else { # Create a temporary table
224 16         93 $tmp = ++$dbdx;
225 16         120 $h->{tbl} = "t_tie_dbdh_$$" . "_$tmp";
226             }
227 21         210 _create_table ($h, $tmp);
228              
229 21         103 my $tbl = $h->{tbl};
230              
231 21         217 $h->{ins} = $dbh->prepare ("insert into $tbl ($f_k, $f_v) values (?, ?)");
232 21         18957 $h->{del} = $dbh->prepare ("delete from $tbl where $f_k = ?");
233 21         19969 $h->{upd} = $dbh->prepare ("update $tbl set $f_v = ? where $f_k = ?");
234 21         18114 $h->{sel} = $dbh->prepare ("select $f_v from $tbl where $f_k = ?");
235 21         14510 $h->{cnt} = $dbh->prepare ("select count(*) from $tbl");
236 21         8640 $h->{ctv} = $dbh->prepare ("select count(*) from $tbl where $f_k = ?");
237              
238 21 100 66     13655 unless (exists $cnf->{pbind} && !$cnf->{pbind}) {
239 10         82 my $sth = $dbh->prepare ("select $f_k, $f_v from $tbl where 0 = 1");
240 10         14668 $sth->execute;
241 10         11432 my @typ = @{$sth->{TYPE}};
  10         72  
242              
243 10         9924 $h->{ins}->bind_param (1, undef, $typ[0]);
244 10         150 $h->{ins}->bind_param (2, undef, $typ[1]);
245 10         105 $h->{del}->bind_param (1, undef, $typ[0]);
246 10         114 $h->{upd}->bind_param (1, undef, $typ[1]);
247 10         103 $h->{upd}->bind_param (2, undef, $typ[0]);
248 10         96 $h->{sel}->bind_param (1, undef, $typ[0]);
249 10         99 $h->{ctv}->bind_param (1, undef, $typ[0]);
250             }
251              
252 21         1178 bless $h, $pkg;
253             } # TIEHASH
254              
255             sub _stream {
256 4970     4970   7223 my ($self, $val) = @_;
257 4970 100       8651 defined $val or return undef;
258              
259 4968 100       8463 $self->{_en} and return $self->{_en}->($val);
260 4904         8619 return $val;
261             } # _stream
262              
263             sub _unstream {
264 5066     5066   8677 my ($self, $val) = @_;
265 5066 100       8956 defined $val or return undef;
266              
267 5062 100       9276 $self->{_de} and return $self->{_de}->($val);
268 4942         13698 return $val;
269             } # _unstream
270              
271             sub STORE {
272 4970     4970   43999 my ($self, $key, $value) = @_;
273 4970 50       8692 my $k = $self->{asc} ? unpack "H*", $key : $key;
274 4970         8250 my $v = $self->_stream ($value);
275 4970 100 33     11995 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
276             my $r = $self->EXISTS ($key)
277             ? $self->{upd}->execute ($v, $k)
278 4970 100       8566 : $self->{ins}->execute ($k, $v);
279 4970 50       260155 $self->{trh} and $self->{dbh}->commit;
280 4970         19341 $r;
281             } # STORE
282              
283             sub DELETE {
284 12     12   1449 my ($self, $key) = @_;
285 12 50       62 $self->{asc} and $key = unpack "H*", $key;
286 12 100 33     65 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
287 12         240 $self->{sel}->execute ($key);
288 12         5960 my $r = $self->{sel}->fetch;
289 12 50       296 unless ($r) {
290 0 0       0 $self->{trh} and $self->{dbh}->rollback;
291 0         0 return;
292             }
293              
294 12         15422 $self->{del}->execute ($key);
295 12 50       6774 $self->{trh} and $self->{dbh}->commit;
296 12         63 $self->_unstream ($r->[0]);
297             } # DELETE
298              
299             sub CLEAR {
300 19     19   18863 my $self = shift;
301 19         225 $self->{dbh}->do ("$DB{$self->{dbt}}{clear} $self->{tbl}");
302             } # CLEAR
303              
304             sub EXISTS {
305 9906     9906   81337 my ($self, $key) = @_;
306 9906 50       17361 $self->{asc} and $key = unpack "H*", $key;
307 9906         67549 $self->{sel}->execute ($key);
308 9906 100       9619856 return $self->{sel}->fetch ? 1 : 0;
309             } # EXISTS
310              
311             sub FETCH {
312 5054     5054   41159 my ($self, $key) = @_;
313 5054 50       10116 $self->{asc} and $key = unpack "H*", $key;
314 5054         32874 $self->{sel}->execute ($key);
315 5054 50       6300778 my $r = $self->{sel}->fetch or return;
316 5054         29285 $self->_unstream ($r->[0]);
317             } # FETCH
318              
319             sub FIRSTKEY {
320 51     51   40194 my $self = shift;
321 51 100 33     259 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
322 51         508 $self->{key} = $self->{dbh}->selectcol_arrayref ("select $self->{f_k} from $self->{tbl}");
323 51 50       85891 $self->{trh} and $self->{dbh}->commit;
324 51 100       144 unless (@{$self->{key}}) {
  51         177  
325 12 50       93 $self->{trh} and $self->{dbh}->commit;
326 12         58 return;
327             }
328 39 50       111 if ($self->{asc}) {
329 0         0 $_ = pack "H*", $_ for @{$self->{key}};
  0         0  
330             }
331 39         71 pop @{$self->{key}};
  39         267  
332             } # FIRSTKEY
333              
334             sub NEXTKEY {
335 4956     4956   6125 my $self = shift;
336 4956 100       5595 unless (@{$self->{key}}) {
  4956         8206  
337 39 50       106 $self->{trh} and $self->{dbh}->commit;
338 39         274 return;
339             }
340 4917         5633 pop @{$self->{key}};
  4917         9772  
341             } # FIRSTKEY
342              
343             sub SCALAR {
344 10     10   1698 my $self = shift;
345 10         108 $self->{cnt}->execute;
346 10 50       4715 my $r = $self->{cnt}->fetch or return 0;
347 10         313 $r->[0];
348             } # SCALAR
349              
350             sub drop {
351 2     2 1 8 my $self = shift;
352 2         12 $self->{tmp} = 1;
353             } # drop
354              
355             sub DESTROY {
356 21     21   8287 my $self = shift;
357 21 50       132 my $dbh = $self->{dbh} or return;
358 21         87 for (qw( sel ins upd del cnt ctv )) {
359 126 50       356 $self->{$_} or next;
360 126         518 $self->{$_}->finish;
361 126         1089 undef $self->{$_}; # DESTROY handle
362 126         4713 delete $self->{$_};
363             }
364 21         221 delete $self->{$_} for qw( _de _en );
365 21 100       79 if ($self->{tmp}) {
366 18 100       1733 $dbh->{AutoCommit} or $dbh->rollback;
367 18         338 $dbh->do ("drop table ".$self->{tbl});
368             }
369 21 100       149948 $dbh->{AutoCommit} or $dbh->commit;
370 21         1420 $dbh->disconnect;
371 21         529 undef $dbh;
372 21         947 undef $self->{dbh};
373             } # DESTROY
374              
375             1;
376              
377             __END__