File Coverage

blib/lib/Tie/Hash/DBD.pm
Criterion Covered Total %
statement 196 245 80.0
branch 116 154 75.3
condition 13 25 52.0
subroutine 26 46 56.5
pod 2 2 100.0
total 353 472 74.7


line stmt bran cond sub pod time code
1             package Tie::Hash::DBD;
2              
3             our $VERSION = "0.26";
4              
5 47     47   13248291 use strict;
  47         198  
  47         1898  
6 47     47   298 use warnings;
  47         93  
  47         2598  
7              
8 47     47   342 use Carp;
  47         120  
  47         3314  
9              
10 47     47   76028 use DBI;
  47         1105512  
  47         200028  
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 _ro_fail {
78 24     24   56 my ($self, $action) = @_;
79 24         63 my $error = "You cannot $action when in read-only mode";
80 24 100       377 $self->{ro} == 1 ? warn $error : die $error;
81             } # ro_fail
82              
83             sub _create_table {
84 25     25   74 my ($cnf, $tmp) = @_;
85 25         104 $cnf->{tmp} = $tmp;
86              
87 25         65 my $dbh = $cnf->{dbh};
88 25         77 my $dbt = $cnf->{dbt};
89              
90 25         59 my $exists = 0;
91 25         66 eval {
92 25         391 local $dbh->{PrintError} = 0;
93 25         884 my $sth = $dbh->prepare ("select $cnf->{f_k}, $cnf->{f_v} from $cnf->{tbl}");
94 13         71847 $sth->execute;
95 2         2370 $cnf->{tmp} = 0;
96 2         25 $exists = 1;
97             };
98 25 100       30578 $exists and return; # Table already exists
99              
100 23 50       130 $cnf->{ro} and return _ro_fail ($cnf, "create tables");
101 23         104 my $temp = $DB{$dbt}{temp};
102 23 100       80 $cnf->{tmp} or $temp = "";
103 23 100 66     202 local $dbh->{AutoCommit} = 1 unless $dbt eq "CSV" || $dbt eq "Unify";
104 23         406 $dbh->do (
105             "create $temp table $cnf->{tbl} (".
106             "$cnf->{f_k} $cnf->{ktp},".
107             "$cnf->{f_v} $cnf->{vtp})"
108             );
109 23 50       79967 $dbt eq "Unify" and $dbh->commit;
110             } # create table
111              
112             sub TIEHASH {
113 89     89   2366062 my $pkg = shift;
114 89         268 my $usg = qq{usage: tie %h, "$pkg", \$dbh [, { tbl => "tbl", key => "f_key", fld => "f_value" }];};
115 89 50       509 my $dsn = shift or croak $usg;
116 89         202 my $opt = shift;
117              
118 89 50       1101 my $dbh = ref $dsn
    50          
119             ? $dsn->clone
120             : DBI->connect ($dsn, undef, undef, {
121             PrintError => 1,
122             RaiseError => 1,
123             PrintWarn => 0,
124             FetchHashKeyName => "NAME_lc",
125             }) or croak (DBI->errstr);
126              
127 65   50     187666 my $dbt = $dbh->{Driver}{Name} || "no DBI handle";
128 65 50       923 my $cnf = $DB{$dbt} or croak "I don't support database '$dbt'";
129 65         147 my $f_k = "h_key";
130 65         121 my $f_v = "h_value";
131 65         105 my $tmp = 0;
132              
133 65         320 $dbh->{PrintWarn} = 0;
134 65 100       761 $dbh->{AutoCommit} = $cnf->{autoc} if exists $cnf->{autoc};
135 65 50       230 $dbh->{LongReadLen} = 4_194_304 if $dbt eq "Oracle";
136              
137             my $h = {
138             dbt => $dbt,
139             dbh => $dbh,
140             tbl => undef,
141             tmp => $tmp,
142             asc => $cnf->{k_asc} || 0,
143             trh => 0,
144             ktp => $cnf->{t_key},
145             vtp => $cnf->{t_val},
146 65   50     1044 ro => 0,
147              
148             _en => undef,
149             _de => undef,
150             };
151              
152 65 100       187 if ($opt) {
153 57 50       229 ref $opt eq "HASH" or croak $usg;
154              
155 57 50       160 $opt->{key} and $f_k = $opt->{key};
156 57 50       165 $opt->{fld} and $f_v = $opt->{fld};
157 57 100       168 $opt->{tbl} and $h->{tbl} = $opt->{tbl};
158 57 50       148 $opt->{trh} and $h->{trh} = $opt->{trh};
159 57 50       153 $opt->{ktp} and $h->{ktp} = $opt->{ktp};
160 57 50       139 $opt->{vtp} and $h->{vtp} = $opt->{vtp};
161              
162 57 50       131 exists $opt->{ro} and $h->{ro} = $opt->{ro};
163              
164 57 100       199 if (my $str = $opt->{str}) {
165 52 100       474 if ($str eq "Sereal") {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
166 4         40 require Sereal::Encoder;
167 4         14 require Sereal::Decoder;
168 4         153 my $se = Sereal::Encoder->new;
169 4         46 my $sd = Sereal::Decoder->new;
170 4     36   25 $h->{_en} = sub { $se->encode ($_[0]) };
  36         3038  
171 4     64   20 $h->{_de} = sub { $sd->decode ($_[0]) };
  64         1146  
172             }
173             elsif ($str eq "Storable") {
174 4         40 require Storable;
175 4     36   30 $h->{_en} = sub { Storable::nfreeze ({ val => $_[0] }) };
  36         5990  
176 4     64   21 $h->{_de} = sub { Storable::thaw ($_[0])->{val} };
  64         249  
177             }
178             elsif ($str eq "FreezeThaw") {
179 4         1535 require FreezeThaw;
180 0     0   0 $h->{_en} = sub { FreezeThaw::freeze ($_[0]) };
  0         0  
181 0     0   0 $h->{_de} = sub { (FreezeThaw::thaw ($_[0]))[0] };
  0         0  
182             }
183             elsif ($str eq "JSON") {
184 4         1145 require JSON;
185 0         0 my $j = JSON->new->allow_nonref;
186 0     0   0 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  0         0  
187 0     0   0 $h->{_de} = sub { $j->decode ($_[0]) };
  0         0  
188             }
189             elsif ($str eq "JSON::MaybeXS") {
190 4         2081 require JSON::MaybeXS;
191 4         41204 my $j = JSON::MaybeXS->new->allow_nonref;
192 4     36   142 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  36         458  
193 4     64   22 $h->{_de} = sub { $j->decode ($_[0]) };
  64         738  
194             }
195             elsif ($str eq "JSON::SIMD") {
196 4         987 require JSON::SIMD;
197 0         0 my $j = JSON::SIMD->new->allow_nonref;
198 0     0   0 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  0         0  
199 0     0   0 $h->{_de} = sub { $j->decode ($_[0]) };
  0         0  
200             }
201             elsif ($str eq "JSON::Syck") {
202 4         787 require JSON::Syck;
203 0     0   0 $h->{_en} = sub { JSON::Syck::Dump ($_[0]) };
  0         0  
204 0     0   0 $h->{_de} = sub { JSON::Syck::Load ($_[0]) };
  0         0  
205             }
206             elsif ($str eq "JSON::XS") {
207 4         688 require JSON::XS;
208 0         0 my $j = JSON::XS->new->allow_nonref;
209 0     0   0 $h->{_en} = sub { $j->utf8->encode ($_[0]) };
  0         0  
210 0     0   0 $h->{_de} = sub { $j->decode ($_[0]) };
  0         0  
211             }
212             elsif ($str eq "YAML") {
213 4         992 require YAML;
214 0     0   0 $h->{_en} = sub { YAML::Dump ($_[0]) };
  0         0  
215 0     0   0 $h->{_de} = sub { YAML::Load ($_[0]) };
  0         0  
216             }
217             elsif ($str eq "YAML::Syck") {
218 4         930 require YAML::Syck;
219 0     0   0 $h->{_en} = sub { YAML::Syck::Dump ($_[0]) };
  0         0  
220 0     0   0 $h->{_de} = sub { YAML::Syck::Load ($_[0]) };
  0         0  
221             }
222             elsif ($str eq "Data::Dumper") {
223 0         0 require Data::Dumper;
224 0     0   0 $h->{_en} = sub { Data::Dumper::Dumper ($_[0]) };
  0         0  
225 0     0   0 $h->{_de} = sub { eval $_[0] };
  0         0  
226             }
227             elsif ($str eq "XML::Dumper") {
228 4         887 require XML::Dumper;
229 0         0 my $xd = XML::Dumper->new;
230 0     0   0 $h->{_en} = sub { $xd->pl2xml ($_[0]) };
  0         0  
231 0     0   0 $h->{_de} = sub { $xd->xml2pl ($_[0]) };
  0         0  
232             }
233             elsif ($str eq "Bencode") {
234 4         1173 require Bencode;
235 0     0   0 $h->{_en} = sub { Bencode::bencode ($_[0]) };
  0         0  
236 0     0   0 $h->{_de} = sub { Bencode::bdecode ($_[0]) };
  0         0  
237             }
238             else {
239 4         1228 croak "Unsupported serializer: $str\n";
240             }
241             }
242             }
243              
244 25         88 $h->{f_k} = $f_k;
245 25         84 $h->{f_v} = $f_v;
246 25 50       89 $h->{trh} and $dbh->{AutoCommit} = 0;
247              
248 25 100       100 if ($h->{tbl}) { # Used told the table name
249 5 100 66     60 $dbh->{AutoCommit} = 1 unless $h->{trh} || $dbt eq "CSV" || $dbt eq "Unify";
      66        
250             }
251             else { # Create a temporary table
252 20         88 $tmp = ++$dbdx;
253 20         209 $h->{tbl} = "t_tie_dbdh_$$" . "_$tmp";
254             }
255 25         121 _create_table ($h, $tmp);
256              
257 25         85 my $tbl = $h->{tbl};
258              
259 25 50       269 $h->{ins} = $dbh->prepare ("insert into $tbl ($f_k, $f_v) values (?, ?)") unless $h->{ro};
260 25 50       25325 $h->{del} = $dbh->prepare ("delete from $tbl where $f_k = ?") unless $h->{ro};
261 25 50       36220 $h->{upd} = $dbh->prepare ("update $tbl set $f_v = ? where $f_k = ?") unless $h->{ro};
262 25         24177 $h->{sel} = $dbh->prepare ("select $f_v from $tbl where $f_k = ?");
263 25         20143 $h->{cnt} = $dbh->prepare ("select count(*) from $tbl");
264 25         12393 $h->{ctv} = $dbh->prepare ("select count(*) from $tbl where $f_k = ?");
265              
266 25 100 66     28983 unless (exists $cnf->{pbind} && !$cnf->{pbind}) {
267 12         99 my $sth = $dbh->prepare ("select $f_k, $f_v from $tbl where 0 = 1");
268 12         21918 $sth->execute;
269 12         14534 my @typ = @{$sth->{TYPE}};
  12         107  
270              
271 12         12318 $h->{ins}->bind_param (1, undef, $typ[0]);
272 12         209 $h->{ins}->bind_param (2, undef, $typ[1]);
273 12         121 $h->{del}->bind_param (1, undef, $typ[0]);
274 12         153 $h->{upd}->bind_param (1, undef, $typ[1]);
275 12         116 $h->{upd}->bind_param (2, undef, $typ[0]);
276 12         131 $h->{sel}->bind_param (1, undef, $typ[0]);
277 12         112 $h->{ctv}->bind_param (1, undef, $typ[0]);
278             }
279              
280 25         1820 bless $h, $pkg;
281             } # TIEHASH
282              
283             sub _stream {
284 5014     5014   7710 my ($self, $val) = @_;
285 5014 100       9117 defined $val or return undef;
286              
287 5012 100       11067 $self->{_en} and return $self->{_en}->($val);
288 4904         9363 return $val;
289             } # _stream
290              
291             sub _unstream {
292 5138     5138   10387 my ($self, $val) = @_;
293 5138 100       10983 defined $val or return undef;
294              
295 5134 100       11515 $self->{_de} and return $self->{_de}->($val);
296 4942         16448 return $val;
297             } # _unstream
298              
299             sub STORE {
300 5038     5038   42970 my ($self, $key, $value) = @_;
301 5038 100       9910 $self->{ro} and return _ro_fail ($self, "store entries");
302 5014 50       9154 my $k = $self->{asc} ? unpack "H*", $key : $key;
303 5014         9417 my $v = $self->_stream ($value);
304 5014 100 33     14673 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
305             my $r = $self->EXISTS ($key)
306             ? $self->{upd}->execute ($v, $k)
307 5014 100       9637 : $self->{ins}->execute ($k, $v);
308 5014 50       313651 $self->{trh} and $self->{dbh}->commit;
309 5014         19867 $r;
310             } # STORE
311              
312             sub DELETE {
313 16     16   750 my ($self, $key) = @_;
314 16 50       70 $self->{ro} and return _ro_fail ($self, "delete entries");
315 16 50       51 $self->{asc} and $key = unpack "H*", $key;
316 16 100 33     75 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
317 16         237 $self->{sel}->execute ($key);
318 16         8591 my $r = $self->{sel}->fetch;
319 16 50       401 unless ($r) {
320 0 0       0 $self->{trh} and $self->{dbh}->rollback;
321 0         0 return;
322             }
323              
324 16         14038 $self->{del}->execute ($key);
325 16 50       13224 $self->{trh} and $self->{dbh}->commit;
326 16         69 $self->_unstream ($r->[0]);
327             } # DELETE
328              
329             sub CLEAR {
330 23     23   42060 my $self = shift;
331 23 50       105 $self->{ro} and return _ro_fail ($self, "clear entries");
332 23         335 $self->{dbh}->do ("$DB{$self->{dbt}}{clear} $self->{tbl}");
333             } # CLEAR
334              
335             sub EXISTS {
336 9966     9966   96310 my ($self, $key) = @_;
337 9966 50       20804 $self->{asc} and $key = unpack "H*", $key;
338 9966         80594 $self->{sel}->execute ($key);
339 9966 100       10319556 return $self->{sel}->fetch ? 1 : 0;
340             } # EXISTS
341              
342             sub FETCH {
343 5146     5146   64875 my ($self, $key) = @_;
344 5146 50       11998 $self->{asc} and $key = unpack "H*", $key;
345 5146         35581 $self->{sel}->execute ($key);
346 5146 100       6808868 my $r = $self->{sel}->fetch or return;
347 5122         41368 $self->_unstream ($r->[0]);
348             } # FETCH
349              
350             sub FIRSTKEY {
351 67     67   50671 my $self = shift;
352 67 100 33     361 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
353 67         880 $self->{key} = $self->{dbh}->selectcol_arrayref ("select $self->{f_k} from $self->{tbl}");
354 67 50       141385 $self->{trh} and $self->{dbh}->commit;
355 67 100       156 unless (@{$self->{key}}) {
  67         264  
356 16 50       62 $self->{trh} and $self->{dbh}->commit;
357 16         82 return;
358             }
359 51 50       153 if ($self->{asc}) {
360 0         0 $_ = pack "H*", $_ for @{$self->{key}};
  0         0  
361             }
362 51         80 pop @{$self->{key}};
  51         333  
363             } # FIRSTKEY
364              
365             sub NEXTKEY {
366 4980     4980   5047 my $self = shift;
367 4980 100       4535 unless (@{$self->{key}}) {
  4980         6742  
368 51 50       177 $self->{trh} and $self->{dbh}->commit;
369 51         339 return;
370             }
371 4929         4644 pop @{$self->{key}};
  4929         8335  
372             } # FIRSTKEY
373              
374             sub SCALAR {
375 14     14   1583 my $self = shift;
376 14         159 $self->{cnt}->execute;
377 14 50       9108 my $r = $self->{cnt}->fetch or return 0;
378 14         341 $r->[0];
379             } # SCALAR
380              
381             sub drop {
382 2     2 1 9 my $self = shift;
383 2         14 $self->{tmp} = 1;
384             } # drop
385              
386             sub readonly {
387 48     48 1 27837 my $self = shift;
388 48 100       192 @_ and $self->{ro} = shift;
389 48         224 return $self->{ro};
390             } # readonly
391              
392             sub DESTROY {
393 25     25   14337 my $self = shift;
394 25 50       130 my $dbh = $self->{dbh} or return;
395 25         95 for (qw( sel ins upd del cnt ctv )) {
396 150 50       487 $self->{$_} or next;
397 150         626 $self->{$_}->finish;
398 150         1388 undef $self->{$_}; # DESTROY handle
399 150         7770 delete $self->{$_};
400             }
401 25         287 delete $self->{$_} for qw( _de _en );
402 25 100       124 if ($self->{tmp}) {
403 22 100       3680 $dbh->{AutoCommit} or $dbh->rollback;
404 22 50       283 $self->{ro} and return _ro_fail ($self, "drop tables");
405 22         196 $dbh->do ("drop table ".$self->{tbl});
406             }
407 25 100       290657 $dbh->{AutoCommit} or $dbh->commit;
408 25         1855 $dbh->disconnect;
409 25         722 undef $dbh;
410 25         1210 undef $self->{dbh};
411             } # DESTROY
412              
413             1;
414              
415             __END__