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.23";
4              
5 47     47   876039 use strict;
  47         179  
  47         1323  
6 47     47   272 use warnings;
  47         84  
  47         1099  
7              
8 47     47   221 use Carp;
  47         80  
  47         2788  
9              
10 47     47   72512 use DBI;
  47         824745  
  47         134867  
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   81 my ($cnf, $tmp) = @_;
79 21         56 $cnf->{tmp} = $tmp;
80              
81 21         47 my $dbh = $cnf->{dbh};
82 21         49 my $dbt = $cnf->{dbt};
83              
84 21         41 my $exists = 0;
85 21         38 eval {
86 21         297 local $dbh->{PrintError} = 0;
87 21         779 my $sth = $dbh->prepare ("select $cnf->{f_k}, $cnf->{f_v} from $cnf->{tbl}");
88 11         58767 $sth->execute;
89 2         3677 $cnf->{tmp} = 0;
90 2         40 $exists = 1;
91             };
92 21 100       27193 $exists and return; # Table already exists
93              
94 19         931 my $temp = $DB{$dbt}{temp};
95 19 100       74 $cnf->{tmp} or $temp = "";
96 19 100 66     175 local $dbh->{AutoCommit} = 1 unless $dbt eq "CSV" || $dbt eq "Unify";
97 19         454 $dbh->do (
98             "create $temp table $cnf->{tbl} (".
99             "$cnf->{f_k} $cnf->{ktp},".
100             "$cnf->{f_v} $cnf->{vtp})"
101             );
102 19 50       81166 $dbt eq "Unify" and $dbh->commit;
103             } # create table
104              
105             sub TIEHASH {
106 77     77   1484993 my $pkg = shift;
107 77         307 my $usg = qq{usage: tie %h, "$pkg", \$dbh [, { tbl => "tbl", key => "f_key", fld => "f_value" }];};
108 77 50       343 my $dsn = shift or croak $usg;
109 77         216 my $opt = shift;
110              
111 77 50       927 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     134622 my $dbt = $dbh->{Driver}{Name} || "no DBI handle";
121 53 50       719 my $cnf = $DB{$dbt} or croak "I don't support database '$dbt'";
122 53         130 my $f_k = "h_key";
123 53         109 my $f_v = "h_value";
124 53         92 my $tmp = 0;
125              
126 53         247 $dbh->{PrintWarn} = 0;
127 53 100       483 $dbh->{AutoCommit} = $cnf->{autoc} if exists $cnf->{autoc};
128 53 50       208 $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     820 _en => undef,
141             _de => undef,
142             };
143              
144 53 100       185 if ($opt) {
145 45 50       170 ref $opt eq "HASH" or croak $usg;
146              
147 45 50       166 $opt->{key} and $f_k = $opt->{key};
148 45 50       191 $opt->{fld} and $f_v = $opt->{fld};
149 45 100       150 $opt->{tbl} and $h->{tbl} = $opt->{tbl};
150 45 50       129 $opt->{trh} and $h->{trh} = $opt->{trh};
151 45 50       122 $opt->{ktp} and $h->{ktp} = $opt->{ktp};
152 45 50       187 $opt->{vtp} and $h->{vtp} = $opt->{vtp};
153              
154 45 100       148 if (my $str = $opt->{str}) {
155 40 100       366 if ($str eq "Sereal") {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
156 4         51 require Sereal::Encoder;
157 4         19 require Sereal::Decoder;
158 4         175 my $se = Sereal::Encoder->new;
159 4         54 my $sd = Sereal::Decoder->new;
160 4     32   32 $h->{_en} = sub { $se->encode ($_[0]) };
  32         606  
161 4     60   24 $h->{_de} = sub { $sd->decode ($_[0]) };
  60         1114  
162             }
163             elsif ($str eq "Storable") {
164 4         65 require Storable;
165 4     32   32 $h->{_en} = sub { Storable::nfreeze ({ val => $_[0] }) };
  32         160  
166 4     60   22 $h->{_de} = sub { Storable::thaw ($_[0])->{val} };
  60         211  
167             }
168             elsif ($str eq "FreezeThaw") {
169 4         1033 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         1195 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         954 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         956 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         955 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         954 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         1057 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         911 croak "Unsupported serializer: $str\n";
212             }
213             }
214             }
215              
216 21         70 $h->{f_k} = $f_k;
217 21         82 $h->{f_v} = $f_v;
218 21 50       81 $h->{trh} and $dbh->{AutoCommit} = 0;
219              
220 21 100       114 if ($h->{tbl}) { # Used told the table name
221 5 100 66     63 $dbh->{AutoCommit} = 1 unless $h->{trh} || $dbt eq "CSV" || $dbt eq "Unify";
      66        
222             }
223             else { # Create a temporary table
224 16         106 $tmp = ++$dbdx;
225 16         124 $h->{tbl} = "t_tie_dbdh_$$" . "_$tmp";
226             }
227 21         172 _create_table ($h, $tmp);
228              
229 21         100 my $tbl = $h->{tbl};
230              
231 21         204 $h->{ins} = $dbh->prepare ("insert into $tbl ($f_k, $f_v) values (?, ?)");
232 21         19318 $h->{del} = $dbh->prepare ("delete from $tbl where $f_k = ?");
233 21         20868 $h->{upd} = $dbh->prepare ("update $tbl set $f_v = ? where $f_k = ?");
234 21         18171 $h->{sel} = $dbh->prepare ("select $f_v from $tbl where $f_k = ?");
235 21         15482 $h->{cnt} = $dbh->prepare ("select count(*) from $tbl");
236 21         8906 $h->{ctv} = $dbh->prepare ("select count(*) from $tbl where $f_k = ?");
237              
238 21 100 66     13995 unless (exists $cnf->{pbind} && !$cnf->{pbind}) {
239 10         95 my $sth = $dbh->prepare ("select $f_k, $f_v from $tbl where 0 = 1");
240 10         15305 $sth->execute;
241 10         11667 my @typ = @{$sth->{TYPE}};
  10         89  
242              
243 10         10346 $h->{ins}->bind_param (1, undef, $typ[0]);
244 10         162 $h->{ins}->bind_param (2, undef, $typ[1]);
245 10         105 $h->{del}->bind_param (1, undef, $typ[0]);
246 10         109 $h->{upd}->bind_param (1, undef, $typ[1]);
247 10         101 $h->{upd}->bind_param (2, undef, $typ[0]);
248 10         101 $h->{sel}->bind_param (1, undef, $typ[0]);
249 10         99 $h->{ctv}->bind_param (1, undef, $typ[0]);
250             }
251              
252 21         1291 bless $h, $pkg;
253             } # TIEHASH
254              
255             sub _stream {
256 4970     4970   7288 my ($self, $val) = @_;
257 4970 100       9114 defined $val or return undef;
258              
259 4968 100       9309 $self->{_en} and return $self->{_en}->($val);
260 4904         8368 return $val;
261             } # _stream
262              
263             sub _unstream {
264 5066     5066   8979 my ($self, $val) = @_;
265 5066 100       9018 defined $val or return undef;
266              
267 5062 100       9494 $self->{_de} and return $self->{_de}->($val);
268 4942         15260 return $val;
269             } # _unstream
270              
271             sub STORE {
272 4970     4970   37103 my ($self, $key, $value) = @_;
273 4970 50       9578 my $k = $self->{asc} ? unpack "H*", $key : $key;
274 4970         8436 my $v = $self->_stream ($value);
275 4970 100 33     12691 $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       8562 : $self->{ins}->execute ($k, $v);
279 4970 50       270362 $self->{trh} and $self->{dbh}->commit;
280 4970         19148 $r;
281             } # STORE
282              
283             sub DELETE {
284 12     12   993 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         199 $self->{sel}->execute ($key);
288 12         5973 my $r = $self->{sel}->fetch;
289 12 50       275 unless ($r) {
290 0 0       0 $self->{trh} and $self->{dbh}->rollback;
291 0         0 return;
292             }
293              
294 12         13211 $self->{del}->execute ($key);
295 12 50       7042 $self->{trh} and $self->{dbh}->commit;
296 12         56 $self->_unstream ($r->[0]);
297             } # DELETE
298              
299             sub CLEAR {
300 19     19   18052 my $self = shift;
301 19         278 $self->{dbh}->do ("$DB{$self->{dbt}}{clear} $self->{tbl}");
302             } # CLEAR
303              
304             sub EXISTS {
305 9906     9906   87132 my ($self, $key) = @_;
306 9906 50       18017 $self->{asc} and $key = unpack "H*", $key;
307 9906         73842 $self->{sel}->execute ($key);
308 9906 100       9347841 return $self->{sel}->fetch ? 1 : 0;
309             } # EXISTS
310              
311             sub FETCH {
312 5054     5054   48979 my ($self, $key) = @_;
313 5054 50       10455 $self->{asc} and $key = unpack "H*", $key;
314 5054         34897 $self->{sel}->execute ($key);
315 5054 50       6271634 my $r = $self->{sel}->fetch or return;
316 5054         37119 $self->_unstream ($r->[0]);
317             } # FETCH
318              
319             sub FIRSTKEY {
320 51     51   35785 my $self = shift;
321 51 100 33     313 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
322 51         603 $self->{key} = $self->{dbh}->selectcol_arrayref ("select $self->{f_k} from $self->{tbl}");
323 51 50       86351 $self->{trh} and $self->{dbh}->commit;
324 51 100       160 unless (@{$self->{key}}) {
  51         175  
325 12 50       81 $self->{trh} and $self->{dbh}->commit;
326 12         69 return;
327             }
328 39 50       123 if ($self->{asc}) {
329 0         0 $_ = pack "H*", $_ for @{$self->{key}};
  0         0  
330             }
331 39         66 pop @{$self->{key}};
  39         255  
332             } # FIRSTKEY
333              
334             sub NEXTKEY {
335 4956     4956   6375 my $self = shift;
336 4956 100       5469 unless (@{$self->{key}}) {
  4956         8207  
337 39 50       112 $self->{trh} and $self->{dbh}->commit;
338 39         286 return;
339             }
340 4917         6031 pop @{$self->{key}};
  4917         10063  
341             } # FIRSTKEY
342              
343             sub SCALAR {
344 10     10   1752 my $self = shift;
345 10         139 $self->{cnt}->execute;
346 10 50       5085 my $r = $self->{cnt}->fetch or return 0;
347 10         261 $r->[0];
348             } # SCALAR
349              
350             sub drop {
351 2     2 1 6 my $self = shift;
352 2         10 $self->{tmp} = 1;
353             } # drop
354              
355             sub DESTROY {
356 21     21   8082 my $self = shift;
357 21 50       108 my $dbh = $self->{dbh} or return;
358 21         103 for (qw( sel ins upd del cnt ctv )) {
359 126 50       379 $self->{$_} or next;
360 126         497 $self->{$_}->finish;
361 126         1133 undef $self->{$_}; # DESTROY handle
362 126         5004 delete $self->{$_};
363             }
364 21         236 delete $self->{$_} for qw( _de _en );
365 21 100       89 if ($self->{tmp}) {
366 18 100       1897 $dbh->{AutoCommit} or $dbh->rollback;
367 18         346 $dbh->do ("drop table ".$self->{tbl});
368             }
369 21 100       123365 $dbh->{AutoCommit} or $dbh->commit;
370 21         1361 $dbh->disconnect;
371 21         564 undef $dbh;
372 21         867 undef $self->{dbh};
373             } # DESTROY
374              
375             1;
376              
377             __END__