File Coverage

blib/lib/Tie/RDBM.pm
Criterion Covered Total %
statement 126 155 81.2
branch 47 106 44.3
condition 12 36 33.3
subroutine 17 19 89.4
pod 2 2 100.0
total 204 318 64.1


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