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.22";
4              
5 47     47   883663 use strict;
  47         185  
  47         1330  
6 47     47   254 use warnings;
  47         84  
  47         1085  
7              
8 47     47   227 use Carp;
  47         84  
  47         2695  
9              
10 47     47   75456 use DBI;
  47         842826  
  47         135735  
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   69 my ($cnf, $tmp) = @_;
79 21         59 $cnf->{tmp} = $tmp;
80              
81 21         51 my $dbh = $cnf->{dbh};
82 21         46 my $dbt = $cnf->{dbt};
83              
84 21         59 my $exists = 0;
85 21         42 eval {
86 21         320 local $dbh->{PrintError} = 0;
87 21         743 my $sth = $dbh->prepare ("select $cnf->{f_k}, $cnf->{f_v} from $cnf->{tbl}");
88 11         56814 $sth->execute;
89 2         3297 $cnf->{tmp} = 0;
90 2         39 $exists = 1;
91             };
92 21 100       25903 $exists and return; # Table already exists
93              
94 19         882 my $temp = $DB{$dbt}{temp};
95 19 100       74 $cnf->{tmp} or $temp = "";
96 19 100 66     162 local $dbh->{AutoCommit} = 1 unless $dbt eq "CSV" || $dbt eq "Unify";
97 19         409 $dbh->do (
98             "create $temp table $cnf->{tbl} (".
99             "$cnf->{f_k} $cnf->{ktp},".
100             "$cnf->{f_v} $cnf->{vtp})"
101             );
102 19 50       65724 $dbt eq "Unify" and $dbh->commit;
103             } # create table
104              
105             sub TIEHASH {
106 77     77   1478604 my $pkg = shift;
107 77         280 my $usg = qq{usage: tie %h, "$pkg", \$dbh [, { tbl => "tbl", key => "f_key", fld => "f_value" }];};
108 77 50       327 my $dsn = shift or croak $usg;
109 77         202 my $opt = shift;
110              
111 77 50       906 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     127219 my $dbt = $dbh->{Driver}{Name} || "no DBI handle";
121 53 50       701 my $cnf = $DB{$dbt} or croak "I don't support database '$dbt'";
122 53         112 my $f_k = "h_key";
123 53         115 my $f_v = "h_value";
124 53         86 my $tmp = 0;
125              
126 53         221 $dbh->{PrintWarn} = 0;
127 53 100       478 $dbh->{AutoCommit} = $cnf->{autoc} if exists $cnf->{autoc};
128 53 50       179 $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     665 _en => undef,
141             _de => undef,
142             };
143              
144 53 100       176 if ($opt) {
145 45 50       150 ref $opt eq "HASH" or croak $usg;
146              
147 45 50       135 $opt->{key} and $f_k = $opt->{key};
148 45 50       109 $opt->{fld} and $f_v = $opt->{fld};
149 45 100       105 $opt->{tbl} and $h->{tbl} = $opt->{tbl};
150 45 50       148 $opt->{trh} and $h->{trh} = $opt->{trh};
151 45 50       106 $opt->{ktp} and $h->{ktp} = $opt->{ktp};
152 45 50       143 $opt->{vtp} and $h->{vtp} = $opt->{vtp};
153              
154 45 100       114 if (my $str = $opt->{str}) {
155 40 100       290 if ($str eq "Sereal") {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
156 4         37 require Sereal::Encoder;
157 4         17 require Sereal::Decoder;
158 4         139 my $se = Sereal::Encoder->new;
159 4         51 my $sd = Sereal::Decoder->new;
160 4     32   29 $h->{_en} = sub { $se->encode ($_[0]) };
  32         478  
161 4     60   23 $h->{_de} = sub { $sd->decode ($_[0]) };
  60         941  
162             }
163             elsif ($str eq "Storable") {
164 4         39 require Storable;
165 4     32   25 $h->{_en} = sub { Storable::nfreeze ({ val => $_[0] }) };
  32         134  
166 4     60   22 $h->{_de} = sub { Storable::thaw ($_[0])->{val} };
  60         180  
167             }
168             elsif ($str eq "FreezeThaw") {
169 4         1060 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         1247 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         984 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         1001 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         954 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         935 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         961 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         845 croak "Unsupported serializer: $str\n";
212             }
213             }
214             }
215              
216 21         60 $h->{f_k} = $f_k;
217 21         64 $h->{f_v} = $f_v;
218 21 50       81 $h->{trh} and $dbh->{AutoCommit} = 0;
219              
220 21 100       65 if ($h->{tbl}) { # Used told the table name
221 5 100 66     73 $dbh->{AutoCommit} = 1 unless $h->{trh} || $dbt eq "CSV" || $dbt eq "Unify";
      66        
222             }
223             else { # Create a temporary table
224 16         94 $tmp = ++$dbdx;
225 16         99 $h->{tbl} = "t_tie_dbdh_$$" . "_$tmp";
226             }
227 21         171 _create_table ($h, $tmp);
228              
229 21         83 my $tbl = $h->{tbl};
230              
231 21         163 $h->{ins} = $dbh->prepare ("insert into $tbl values (?, ?)");
232 21         15276 $h->{del} = $dbh->prepare ("delete from $tbl where $f_k = ?");
233 21         20456 $h->{upd} = $dbh->prepare ("update $tbl set $f_v = ? where $f_k = ?");
234 21         17858 $h->{sel} = $dbh->prepare ("select $f_v from $tbl where $f_k = ?");
235 21         14666 $h->{cnt} = $dbh->prepare ("select count(*) from $tbl");
236 21         8679 $h->{ctv} = $dbh->prepare ("select count(*) from $tbl where $f_k = ?");
237              
238 21 100 66     13638 unless (exists $cnf->{pbind} && !$cnf->{pbind}) {
239 10         97 my $sth = $dbh->prepare ("select $f_k, $f_v from $tbl where 0 = 1");
240 10         14988 $sth->execute;
241 10         12563 my @typ = @{$sth->{TYPE}};
  10         102  
242              
243 10         10902 $h->{ins}->bind_param (1, undef, $typ[0]);
244 10         164 $h->{ins}->bind_param (2, undef, $typ[1]);
245 10         126 $h->{del}->bind_param (1, undef, $typ[0]);
246 10         103 $h->{upd}->bind_param (1, undef, $typ[1]);
247 10         104 $h->{upd}->bind_param (2, undef, $typ[0]);
248 10         102 $h->{sel}->bind_param (1, undef, $typ[0]);
249 10         100 $h->{ctv}->bind_param (1, undef, $typ[0]);
250             }
251              
252 21         1248 bless $h, $pkg;
253             } # TIEHASH
254              
255             sub _stream {
256 4970     4970   7624 my ($self, $val) = @_;
257 4970 100       9337 defined $val or return undef;
258              
259 4968 100       8767 $self->{_en} and return $self->{_en}->($val);
260 4904         8442 return $val;
261             } # _stream
262              
263             sub _unstream {
264 5066     5066   8703 my ($self, $val) = @_;
265 5066 100       9132 defined $val or return undef;
266              
267 5062 100       9271 $self->{_de} and return $self->{_de}->($val);
268 4942         13796 return $val;
269             } # _unstream
270              
271             sub STORE {
272 4970     4970   37476 my ($self, $key, $value) = @_;
273 4970 50       8589 my $k = $self->{asc} ? unpack "H*", $key : $key;
274 4970         8357 my $v = $self->_stream ($value);
275 4970 100 33     12235 $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       8337 : $self->{ins}->execute ($k, $v);
279 4970 50       266204 $self->{trh} and $self->{dbh}->commit;
280 4970         18706 $r;
281             } # STORE
282              
283             sub DELETE {
284 12     12   794 my ($self, $key) = @_;
285 12 50       54 $self->{asc} and $key = unpack "H*", $key;
286 12 100 33     64 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
287 12         193 $self->{sel}->execute ($key);
288 12         5816 my $r = $self->{sel}->fetch;
289 12 50       279 unless ($r) {
290 0 0       0 $self->{trh} and $self->{dbh}->rollback;
291 0         0 return;
292             }
293              
294 12         10698 $self->{del}->execute ($key);
295 12 50       6955 $self->{trh} and $self->{dbh}->commit;
296 12         55 $self->_unstream ($r->[0]);
297             } # DELETE
298              
299             sub CLEAR {
300 19     19   17985 my $self = shift;
301 19         241 $self->{dbh}->do ("$DB{$self->{dbt}}{clear} $self->{tbl}");
302             } # CLEAR
303              
304             sub EXISTS {
305 9906     9906   83920 my ($self, $key) = @_;
306 9906 50       17556 $self->{asc} and $key = unpack "H*", $key;
307 9906         70023 $self->{sel}->execute ($key);
308 9906 100       9517746 return $self->{sel}->fetch ? 1 : 0;
309             } # EXISTS
310              
311             sub FETCH {
312 5054     5054   42979 my ($self, $key) = @_;
313 5054 50       10034 $self->{asc} and $key = unpack "H*", $key;
314 5054         33290 $self->{sel}->execute ($key);
315 5054 50       6250254 my $r = $self->{sel}->fetch or return;
316 5054         30861 $self->_unstream ($r->[0]);
317             } # FETCH
318              
319             sub FIRSTKEY {
320 51     51   36058 my $self = shift;
321 51 100 33     270 $self->{trh} and $self->{dbh}->begin_work unless $self->{dbt} eq "SQLite";
322 51         559 $self->{key} = $self->{dbh}->selectcol_arrayref ("select $self->{f_k} from $self->{tbl}");
323 51 50       85727 $self->{trh} and $self->{dbh}->commit;
324 51 100       140 unless (@{$self->{key}}) {
  51         164  
325 12 50       79 $self->{trh} and $self->{dbh}->commit;
326 12         62 return;
327             }
328 39 50       106 if ($self->{asc}) {
329 0         0 $_ = pack "H*", $_ for @{$self->{key}};
  0         0  
330             }
331 39         61 pop @{$self->{key}};
  39         230  
332             } # FIRSTKEY
333              
334             sub NEXTKEY {
335 4956     4956   6290 my $self = shift;
336 4956 100       5547 unless (@{$self->{key}}) {
  4956         8237  
337 39 50       106 $self->{trh} and $self->{dbh}->commit;
338 39         244 return;
339             }
340 4917         5770 pop @{$self->{key}};
  4917         9640  
341             } # FIRSTKEY
342              
343             sub SCALAR {
344 10     10   1685 my $self = shift;
345 10         109 $self->{cnt}->execute;
346 10 50       4945 my $r = $self->{cnt}->fetch or return 0;
347 10         255 $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   7861 my $self = shift;
357 21 50       116 my $dbh = $self->{dbh} or return;
358 21         88 for (qw( sel ins upd del cnt ctv )) {
359 126 50       320 $self->{$_} or next;
360 126         485 $self->{$_}->finish;
361 126         1068 undef $self->{$_}; # DESTROY handle
362 126         4763 delete $self->{$_};
363             }
364 21         194 delete $self->{$_} for qw( _de _en );
365 21 100       85 if ($self->{tmp}) {
366 18 100       1432 $dbh->{AutoCommit} or $dbh->rollback;
367 18         330 $dbh->do ("drop table ".$self->{tbl});
368             }
369 21 100       124539 $dbh->{AutoCommit} or $dbh->commit;
370 21         1121 $dbh->disconnect;
371 21         604 undef $dbh;
372 21         756 undef $self->{dbh};
373             } # DESTROY
374              
375             1;
376              
377             __END__