File Coverage

blib/lib/Tie/RDBM.pm
Criterion Covered Total %
statement 137 157 87.2
branch 53 106 50.0
condition 14 36 38.8
subroutine 18 20 90.0
pod 2 2 100.0
total 224 321 69.7


line stmt bran cond sub pod time code
1             package Tie::RDBM;
2              
3 1     1   85647 use strict;
  1         12  
  1         30  
4 1     1   5 use warnings;
  1         2  
  1         28  
5 1     1   16 use 5.006;
  1         3  
6 1     1   4 use Carp;
  1         3  
  1         63  
7 1     1   6 use DBI;
  1         2  
  1         2463  
8             our $VERSION = '0.74';
9              
10             # %Types is used for creating the data table if it doesn't exist already.
11             # You may want to edit this.
12             our %Types = ( # key value frozen freeze keyless
13             'mysql' => [qw/ varchar(127) longblob tinyint 1 0 /],
14             'mSQL' => [qw/ char(255) char(255) int 0 0 /],
15             'Pg' => [qw/ varchar(127) varchar(2000) int 0 0 /],
16             'Sybase' => [qw/ varchar(255) varbinary(255) tinyint 1 0 /],
17             'Oracle' => [qw/ varchar(255) varchar2(2000) integer 1 0 /],
18             'CSV' => [qw/ varchar(255) varchar(255) integer 1 1 /],
19             'Informix' => [qw/ nchar(120) nchar(2000) integer 0 0 /],
20             'Solid' => [qw/ varchar(255) varbinary(2000) integer 1 0 /],
21             'ODBC' => [qw/ varchar(255) varbinary(2000) integer 1 0 /],
22             'default' => [qw/ varchar(255) varchar(255) integer 0 0 /], #others
23             );
24              
25             # list drivers that do run-time binding correctly
26             my %CAN_BIND = (
27             'mysql' => 1,
28             'mSQL' => 1,
29             'Oracle' => 1,
30             'Pg' => 1,
31             'Informix' => 1,
32             'Solid' => 1,
33             'ODBC' => 1,
34             );
35              
36             # Default options for the module
37             my %DefaultOptions = (
38             'table' => 'pdata',
39             'key' => 'pkey',
40             'value' => 'pvalue',
41             'frozen' => 'pfrozen',
42             'user' => '',
43             'password' => '',
44             'autocommit' => 1,
45             'create' => 0,
46             'drop' => 0,
47             'DEBUG' => 0,
48             );
49              
50             sub TIEHASH {
51 2     2   1117 my $class = shift;
52 2 50       10 my ( $dsn, $opt ) = ref( $_[0] ) ? ( undef, $_[0] ) : @_;
53 2   33     6 $dsn ||= $opt->{'db'};
54              
55 2 50       6 croak "Usage tie(%h,Tie::RDBM,,\%options)" unless $dsn;
56 2 50       6 if ($opt) {
57 2         21 foreach ( keys %DefaultOptions ) {
58 20 100       48 $opt->{$_} = $DefaultOptions{$_} unless exists( $opt->{$_} );
59             }
60             }
61             else {
62 0         0 $opt = \%DefaultOptions;
63             }
64              
65 2         6 my ( $dbh, $driver );
66              
67 2 50       17 if ( UNIVERSAL::isa( $dsn, 'DBI::db' ) ) {
68 0         0 $dbh = $dsn;
69 0         0 $driver = $dsn->{Driver}{Name};
70             }
71             else {
72 2 50       12 $dsn = "dbi:$dsn" unless $dsn =~ /^dbi/;
73 2         13 ($driver) = $dsn =~ /\w+:(\w+)/;
74              
75             # Try to establish connection with data source.
76 2 50       8 delete $ENV{NLS_LANG} if $driver eq 'Oracle'; # allow 8 bit connections?
77             $dbh = DBI->connect(
78             $dsn,
79             $opt->{user},
80             $opt->{password},
81             {
82             AutoCommit => $opt->{autocommit},
83 2         22 PrintError => 0,
84             ChopBlanks => 1,
85             Warn => 0
86             }
87             );
88 2 50       13931 croak "TIEHASH: Can't open $dsn, $DBI::errstr" unless $dbh;
89             }
90              
91             # A variety of shinanegans to handle freeze/thaw option.
92             # We will serialize references if:
93             # 1. The database driver supports binary types.
94             # 2. The database table has a boolean field to indicate that a value is frozen.
95             # 3. The Storable module is available.
96             # we also check that "primary key" is recognized
97 2   33     26 my $db_features = $Types{$driver} || $Types{'default'};
98 2         7 my ($canfreeze) = $db_features->[3];
99 2         5 my ($keyless) = $db_features->[4];
100 2         147 my ($haveStorable) = eval 'require Storable;';
101 2 50       3305 Storable->import(qw/nfreeze thaw/) if $haveStorable;
102 2   33     9 $canfreeze &&= $haveStorable;
103              
104             # Check that the indicated table exists. If it doesn't,
105             # try to create it....
106              
107             # This query tests that a table with the correct fields is present.
108             # I would prefer to use a where clause of 1=0 but some dumb drivers (mSQL)
109             # treat this as a syntax error!!!
110 2         10 my $q = "select * from $opt->{table} where $opt->{key}=''";
111 2         16 my $sth = $dbh->prepare($q);
112 2         669 my $structure_ok = 0;
113 2         13 local ($^W) = 0; # uninitialized variable problem
114 2 100 66     71 if ( defined($sth) && $sth->execute() ne '' ) {
115              
116             # At least the key field exists. Check whether the others do too.
117 1         4 my (%field_names);
118 1         2 grep( $field_names{ lc($_) }++, @{ $sth->{NAME} } );
  1         24  
119 1 50       7 $structure_ok++ if $field_names{ $opt->{'value'} };
120 1   33     5 $canfreeze &&= $field_names{ $opt->{'frozen'} };
121             }
122              
123 2 100       7 unless ($structure_ok) {
124              
125 1 0 33     4 unless ( $opt->{'create'} || $opt->{'drop'} ) {
126 0         0 my $err = $DBI::errstr;
127 0         0 $dbh->disconnect;
128 0         0 croak "Table $opt->{table} does not have expected structure and creation forbidden: $err";
129             }
130              
131 1 50       10 $dbh->do("drop table $opt->{table}") if $opt->{'drop'};
132              
133 1         79 my ( $keytype, $valuetype, $frozentype ) = @{$db_features};
  1         5  
134 1 50       8 my (@fields) = (
135             $keyless ? "$opt->{key} $keytype" : "$opt->{key} $keytype primary key",
136             "$opt->{value} $valuetype"
137             );
138 1 0       4 push( @fields, ( $keyless ? "$opt->{frozen} $frozentype" : "$opt->{frozen} $frozentype not null" ) )
    50          
139             if $canfreeze;
140 1         6 $q = "create table $opt->{table} (" . join( ',', @fields ) . ")";
141 1 50       4 warn "$q\n" if $opt->{DEBUG};
142 1 50       6 $dbh->do($q) || do {
143 0         0 my $err = $DBI::errstr;
144 0         0 $dbh->disconnect;
145 0         0 croak("Can't initialize data table: $err");
146             }
147             }
148              
149             return bless {
150             'dbh' => $dbh,
151             'table' => $opt->{'table'},
152             'key' => $opt->{'key'},
153             'value' => $opt->{'value'},
154             'frozen' => $opt->{'frozen'},
155             'canfreeze' => $canfreeze,
156             'brokenselect' => $driver eq 'mSQL' || $driver eq 'mysql',
157             'canbind' => $CAN_BIND{$driver},
158             'DEBUG' => $opt->{DEBUG},
159 2   33     12388 }, $class;
160             }
161              
162             sub FETCH {
163 8     8   248 my ( $self, $key ) = @_;
164              
165             # this is a hack to avoid doing an unnecessary SQL select
166             # during an each() loop.
167             return $self->{'cached_value'}->{$key}
168 8 50       31 if exists $self->{'cached_value'}->{$key};
169              
170             # create statement handler if it doesn't already exist.
171 8 50       25 my $cols = $self->{'canfreeze'} ? "$self->{'value'},$self->{'frozen'}" : $self->{'value'};
172 8         49 my $sth = $self->_run_query( 'fetch', <
173             select $cols from $self->{table} where $self->{key}=?
174             END
175 8         131 my $result = $sth->fetchrow_arrayref();
176 8         62 $sth->finish;
177 8 50       22 return undef unless $result;
178 8 50 33     186 $self->{'canfreeze'} && $result->[1] ? thaw( $result->[0] ) : $result->[0];
179             }
180              
181             sub STORE {
182 5     5   666 my ( $self, $key, $value ) = @_;
183              
184 5         8 my $frozen = 0;
185 5 0 33     15 if ( ref($value) && $self->{'canfreeze'} ) {
186 0         0 $frozen++;
187 0         0 $value = nfreeze($value);
188             }
189              
190             # Yes, this is ugly. It is designed to minimize the number of SQL statements
191             # for both database whose update statements return the number of rows updated,
192             # and those (like mSQL) whose update statements don't.
193 5         6 my ($r);
194 5 50       13 if ( $self->{'brokenselect'} ) {
195 0 0       0 return $self->EXISTS($key)
196             ? $self->_update( $key, $value, $frozen )
197             : $self->_insert( $key, $value, $frozen );
198             }
199              
200 5   66     14 return $self->_update( $key, $value, $frozen ) || $self->_insert( $key, $value, $frozen );
201             }
202              
203             sub DELETE {
204 1     1   4 my ( $self, $key ) = @_;
205 1         6 my $sth = $self->_run_query( 'delete', <
206             delete from $self->{table} where $self->{key}=?
207             END
208 1 50       24 croak "Database delete statement failed: $DBI::errstr" if $sth->err;
209 1         8 $sth->finish;
210 1         16 1;
211             }
212              
213             sub CLEAR {
214 1     1   4 my $self = shift;
215 1         4 my $dbh = $self->{'dbh'};
216 1         7 my $sth = $self->_prepare( 'clear', "delete from $self->{table}" );
217 1         11433 $sth->execute();
218 1 50       26 croak "Database delete all statement failed: $DBI::errstr" if $dbh->err;
219 1         11 $sth->finish;
220             }
221              
222             sub EXISTS {
223 2     2   5 my ( $self, $key ) = @_;
224 2         12 my $sth = $self->_run_query( 'exists', <
225             select $self->{key} from $self->{table} where $self->{key}=?
226             END
227 2 50       9 croak "Database select statement failed: $DBI::errstr" unless $sth;
228 2         21 $sth->fetch;
229 2         15 my $rows = $sth->rows;
230 2         8 $sth->finish;
231 2         33 $rows >= 1;
232             }
233              
234             sub FIRSTKEY {
235 4     4   1002 my $self = shift;
236              
237 4         11 delete $self->{'cached_value'};
238 4 100       19 if ( $self->{'fetchkeys'} ) {
239 2         15 $self->{'fetchkeys'}->finish(); # to prevent truncation in ODBC driver
240 2         26 delete $self->{'fetchkeys'};
241             }
242              
243 4 50       33 my $sth = $self->_prepare( 'fetchkeys', $self->{'canfreeze'} ? <
244             select $self->{'key'},$self->{'value'},$self->{'frozen'} from $self->{'table'}
245             END1
246             select $self->{'key'},$self->{'value'} from $self->{'table'}
247             END2
248              
249 4 50       238 $sth->execute() || croak "Can't execute select statement: $DBI::errstr";
250 4         41 my $ref = $sth->fetch();
251 4 100       31 return defined($ref) ? $ref->[0] : undef;
252             }
253              
254             sub NEXTKEY {
255 8     8   14 my $self = shift;
256              
257             # no statement handler defined, so nothing to iterate over
258 8 0       20 return wantarray ? () : undef unless my $sth = $self->{'fetchkeys'};
    50          
259 8         99 my $r = $sth->fetch();
260 8 100       33 if ( !$r ) {
261 3         10 $sth->finish;
262 3         8 delete $self->{'cached_value'};
263 3 50       40 return wantarray ? () : undef;
264             }
265 5 50       20 my ( $key, $value ) = ( $r->[0], $r->[2] ? thaw( $r->[1] ) : $r->[1] );
266 5         17 $self->{'cached_value'}->{$key} = $value;
267 5 50       23 return wantarray ? ( $key, $value ) : $key;
268             }
269              
270             sub DESTROY {
271 2     2   557 my $self = shift;
272 2         5 foreach (qw/fetch update insert delete clear exists fetchkeys/) {
273 14 100       41 $self->{$_}->finish if $self->{$_};
274             }
275 2 50       392 $self->{'dbh'}->disconnect() if $self->{'dbh'};
276             }
277              
278             sub commit {
279 0     0 1 0 $_[0]->{'dbh'}->commit();
280             }
281              
282             sub rollback {
283 0     0 1 0 $_[0]->{'dbh'}->rollback();
284             }
285              
286             # utility routines
287             sub _update {
288 5     5   13 my ( $self, $key, $value, $frozen ) = @_;
289 5         7 my ($sth);
290 5 50       15 if ( $self->{'canfreeze'} ) {
291 0         0 $sth = $self->_run_query(
292             'update',
293             "update $self->{table} set $self->{value}=?,$self->{frozen}=? where $self->{key}=?",
294             $value, $frozen, $key
295             );
296             }
297             else {
298 5         29 $sth = $self->_run_query(
299             'update',
300             "update $self->{table} set $self->{value}=? where $self->{key}=?",
301             $value, $key
302             );
303             }
304 5 50       15 croak "Update: $DBI::errstr" unless $sth;
305 5         117 $sth->rows > 0;
306             }
307              
308             sub _insert {
309 4     4   15 my ( $self, $key, $value, $frozen ) = @_;
310 4         7 my ($sth);
311 4 50       13 if ( $self->{'canfreeze'} ) {
312 0         0 $sth = $self->_run_query(
313             'insert',
314             "insert into $self->{table} ($self->{key},$self->{value},$self->{frozen}) values (?,?,?)",
315             $key, $value, $frozen
316             );
317             }
318             else {
319 4         20 $sth = $self->_run_query(
320             'insert',
321             "insert into $self->{table} ($self->{key},$self->{value}) values (?,?)",
322             $key, $value
323             );
324             }
325 4 50 33     155 ( $sth && $sth->rows ) || croak "Update: $DBI::errstr";
326             }
327              
328             sub _run_query {
329 20     20   32 my $self = shift;
330 20         48 my ( $tag, $query, @bind_variables ) = @_;
331 20 50       48 if ( $self->{canbind} ) {
332 0         0 my $sth = $self->_prepare( $tag, $query );
333 0 0       0 return undef unless $sth->execute(@bind_variables);
334 0         0 return $sth;
335             }
336              
337             # if we get here, then we can't bind, so we replace ? with escaped parameters
338 20         103 $query =~ s/\?/$self->{'dbh'}->quote(shift(@bind_variables))/eg;
  29         225  
339 20         292 my $sth = $self->{'dbh'}->prepare($query);
340 20 50 33     64599 return undef unless $sth && $sth->execute;
341 20         150 return $sth;
342             }
343              
344             sub _prepare ($$$) {
345 5     5   15 my ( $self, $tag, $q ) = @_;
346 5 50       16 unless ( exists( $self->{$tag} ) ) {
347 5 50       14 return undef unless $q;
348 5 50       14 warn $q, "\n" if $self->{DEBUG};
349 5         28 my $sth = $self->{'dbh'}->prepare($q);
350 5 50       364 croak qq/Problems preparing statement "$q": $DBI::errstr/ unless $sth;
351 5         16 $self->{$tag} = $sth;
352             }
353             else {
354 0 0       0 $self->{$tag}->finish if $q; # in case we forget
355             }
356 5         12 $self->{$tag};
357             }
358              
359             1;
360             __END__