File Coverage

blib/lib/Data/Direct.pm
Criterion Covered Total %
statement 9 190 4.7
branch 0 54 0.0
condition 0 13 0.0
subroutine 3 29 10.3
pod 18 23 78.2
total 30 309 9.7


line stmt bran cond sub pod time code
1             package Data::Direct;
2              
3 1     1   591 use strict qw(vars subs);
  1         2  
  1         31  
4 1         180 use vars qw($VERSION @EXPORT @ISA $opt_u $opt_p $table $opt_w $opt_a
5 1     1   5 $gen_unique);
  1         2  
6              
7             $VERSION = 0.05;
8              
9             require Exporter;
10             @EXPORT = qw(edit);
11             @ISA = qw(Exporter);
12              
13 1     1   2417 use DBI;
  1         25911  
  1         2095  
14              
15             sub new {
16 0     0 1   my ($class, $dsn, $user, $pass, $table, $filter, $add) = @_;
17 0           my $self = {};
18 0           bless $self, $class;
19 0           $self->{' dsn'} = $dsn;
20 0           my $dbh;
21              
22             ####
23             ## Try to connect with transactions first; otherwise just connect
24              
25 0           eval '$dbh = DBI->connect($dsn, $user, $pass, {AutoCommit => 0});';
26 0 0         $dbh = DBI->connect($dsn, $user, $pass) unless ($dbh);
27 0 0         return undef unless ($dbh);
28              
29 0           $self->{' dbh'} = $dbh;
30 0 0         my $sql = "SELECT * FROM $table" . ($filter ? " WHERE $filter" : "")
    0          
31             . ($add ? " $add" : "");
32 0           my $sth = $dbh->prepare($sql);
33 0 0         return undef unless ($sth);
34              
35 0           $self->{' table'} = $table;
36 0           $self->{' filter'} = $filter;
37 0           $sth->execute();
38              
39             ####
40             ## Find field names
41              
42 0           my $fields = $sth->{NAME};
43 0           $self->{' fields'} = $fields;
44              
45             ####
46             ## Fetch rows
47              
48 0           my ($r, @rs);
49 0           while ($r = $sth->fetchrow_arrayref) {
50             ####
51             ## Recreate array ref. Could I use while (my $r = ?
52              
53 0           push(@rs, [@$r]);
54             }
55 0           $self->{' recs'} = \@rs;
56 0           undef $sth;
57              
58 0           $self->fetch(0);
59 0           $self->{' bookmarks'} = {};
60 0           $self->{' zap'} = [];
61 0           $self;
62             }
63              
64             sub bind {
65 0     0 1   my $self = shift;
66 0           my %hash = @_;
67 0           $self->{' binding'} = \%hash;
68 0           $self->fetch;
69             }
70              
71             sub simplebind {
72 0     0 0   my ($self, $pkg) = @_;
73 0           my @fields = @{$self->{' fields'}};
  0            
74              
75             ####
76             ## Create tuples 'var', \$var
77              
78 0           my @ary = map {($_, \${"${pkg}::$_"})} @fields;
  0            
  0            
79 0           $self->bind(@ary);
80             }
81              
82             sub flush {
83 0     0 0   my $self = shift;
84 0           my $param = shift;
85 0           my ($table, $filter, $fields, $rs, $dbh) =
86             @$self{(' table', ' filter', ' fields', ' recs', ' dbh')};
87              
88             ####
89             ## Delete records before inserting everything back
90             ## Can be hazardous if there are no transactions and
91             ## somebody added data meanwhile!
92              
93 0 0         my $sql = "DELETE FROM $table" . ($filter ? " WHERE $filter" : "");
94 0 0         $dbh->do($sql) || die $DBI::errstr;
95              
96             ####
97             ## Not sure why I wrote this:
98              
99 0 0         return if ($param eq 'pseudo');
100              
101             ####
102             ## Prepare an INSERT statement
103              
104 0           $sql = "INSERT INTO $table (" . join(", ", @$fields) . ") VALUES ("
105 0           . join(", ", map {"?";} @$fields) . ")";
106 0   0       my $sth = $dbh->prepare($sql) || die $DBI::errstr;
107 0           my $i;
108 0           foreach (@$rs) {
109 0 0 0       ($sth->execute(@$_) || die $DBI::errstr)
110             unless ($self->{' zap'}->[$i++]);
111             }
112 0           undef $sth;
113 0 0         eval '$dbh->commit;' unless ($dbh->{AutoCommit});
114 0           $dbh->disconnect;
115             }
116              
117             sub recs {
118 0     0 1   my $self = shift;
119 0           scalar(@{$self->{' recs'}});
  0            
120             }
121              
122             sub rows {
123 0     0 1   my $self = shift;
124 0           $self->recs - $self->{' dels'};
125             }
126              
127             sub cursor {
128 0     0 1   my $self = shift;
129 0           $self->{' cursor'};
130             }
131              
132             sub fetch {
133 0     0 1   my $self = shift;
134              
135             ####
136             ## Find cursor
137              
138 0           my $rs = $self->{' recs'};
139 0           my $rec;
140              
141             ####
142             ## Did we have a parameter?
143              
144 0 0         if (defined($_[0])) {
145 0           $rec = shift;
146 0 0 0       return undef if ($rec < 0 || $rec > @$rs);
147 0           $self->{' cursor'} = $rec;
148 0 0         return undef if ($rec == @$rs);
149             } else {
150 0           $rec = $self->{' cursor'};
151             }
152              
153             ####
154             ## Take row
155              
156 0           my $ref = $rs->[$rec];
157 0           my @fields = @{$self->{' fields'}};
  0            
158 0           my $bind = $self->{' binding'};
159              
160             ####
161             ## Iterate over fields
162              
163 0           foreach (@$ref) {
164 0           my $col = shift @fields;
165             ####
166             ## Bind variable
167              
168 0           my $ref = $bind->{$col};
169 0 0         $$ref = $_ if (ref($ref));
170              
171             ####
172             ## Load self
173              
174 0           $self->{$col} = $_;
175             }
176 0           1;
177             }
178              
179             sub addnew {
180 0     0 1   my $self = shift;
181 0           my $rs = $self->{' recs'};
182 0           my $fields = $self->{' fields'};
183 0           my $cursor = $self->{' cursor'};
184              
185             ####
186             ## Create an empty record
187              
188 0           my $new = [map {undef;} @$fields];
  0            
189              
190             ####
191             ## Add it
192              
193 0           splice(@$rs, $cursor, 0, $new);
194 0           $self->fetch($cursor);
195             }
196              
197             sub setbookmark {
198 0     0 1   my ($self, $name) = @_;
199 0           $self->{' bookmarks'}->{$name} = $self->cursor;
200             }
201              
202             sub gotobookmark {
203 0     0 1   my ($self, $name) = @_;
204 0           $self->fetch($self->{' bookmarks'}->{$name});
205             }
206              
207             sub delete {
208 0     0 1   my $self = shift;
209 0           my $where = $self->cursor;
210 0 0         return if ($self->{' zap'}->[$where]);
211 0           $self->{' zap'}->[$where] = 1;
212 0           $self->{' dels'}++;
213             }
214              
215             sub undelete {
216 0     0 1   my $self = shift;
217 0           my $where = $self->cursor;
218 0 0         return unless ($self->{' zap'}->[$where]);
219 0           $self->{' zap'}->[$where] = undef;
220 0           $self->{' dels'}--;
221             }
222              
223             sub isdeleted {
224 0     0 1   my $self = shift;
225 0           $self->{' zap'}->[$self->cursor];
226             }
227              
228             sub update {
229 0     0 1   my $self = shift;
230 0           my $fields = $self->{' fields'};
231 0           my @ary;
232 0           my $bind = $self->{' binding'};
233             ####
234             ## Retrieve bound variables
235              
236 0           foreach (keys %$bind) {
237 0           $self->{$_} = ${$bind->{$_}};
  0            
238             }
239              
240             ####
241             ## Retrieve row
242              
243 0           foreach (@$fields) {
244 0           push(@ary, $self->{$_});
245             }
246              
247             ####
248             ## Put
249              
250 0           my $rs = $self->{' recs'};
251 0           $rs->[$self->cursor] = \@ary;
252             }
253              
254             sub next {
255 0     0 1   my $self = shift;
256 0           $self->fetch($self->cursor + 1);
257             }
258              
259             sub back {
260 0     0 1   my $self = shift;
261 0           $self->fetch($self->cursor - 1);
262             }
263              
264             sub bof {
265 0     0 1   my $self = shift;
266 0           $self->cursor <= 0;
267             }
268              
269             sub eof {
270 0     0 1   my $self = shift;
271 0           $self->cursor >= $self->recs;
272             }
273              
274             sub fields {
275 0     0 0   my $self = shift;
276 0           my $ref = $self->{' fields'};
277 0           @$ref;
278             }
279              
280             sub spawn {
281 0     0 1   require Text::ParseWords;
282 0           my ($self, $cmd, $pack, $unpack) = @_;
283              
284             ####
285             ## Find editor, unless a different command requested
286              
287 0 0 0       $cmd = $ENV{'EDITOR'} || 'vi' unless ($cmd);
288             ####
289             ## Default delimiter is comma
290              
291 0 0         $pack = "," unless ($pack);
292              
293             ####
294             ## If pack information is a string and not a routine, pack line by quoting
295             ## tokens and adding delmiters
296 0           my $packc = !UNIVERSAL::isa($pack, 'CODE') ?
297 0 0   0     sub {join($pack, (map {qq!"$_"!} @_)) . "\n";} : $pack;
  0            
298              
299             ####
300             ## Assume unpack routine to be supplied only if pack routine was supplied.
301             ## Otherwise, unpacking is done by parsing the delimited line
302              
303             ## NOTE:
304             ## Packing function recieves a list; Unpacking function gets a stream to
305             ## read from.
306              
307             my $unpackc = ref($pack) !~ /CODE/ ?
308 0     0     sub { my $l = scalar(<$_>); chop $l;
  0            
309 0 0         Text::ParseWords::parse_line($pack, undef, $l);} : $unpack;
  0            
310              
311             ####
312             ## Save bookmark
313              
314 0           my $save = $self->cursor;
315              
316             ####
317             ## Create file
318              
319 0           my $fn = &gentemp;
320 0 0         open(O, ">$fn") || die "Can't open $fn for write: $!";
321 0           my $rs = $self->{' recs'};
322              
323             ####
324             ## Iterate
325              
326 0           foreach (@$rs) {
327 0           print O &$packc(@$_);
328             }
329 0           close(O);
330              
331             ####
332             ## Take file stamp to figure if it was changed
333              
334 0           my @st = stat($fn);
335 0           splice(@st, 8); # Access time obviously changes
336 0           my $s = join(":", @st);
337              
338             ####
339             ## Invoke editor
340 0 0         $cmd .= " %1" unless ($cmd =~ /[\$\%]1/);
341 0           $cmd =~ s/[\$\%]1/$fn/g;
342 0           system $cmd;
343              
344             ####
345             ## Recreate file stamp
346              
347 0           @st = stat($fn);
348 0           splice(@st, 8);
349 0           my $ss = join(":", @st);
350              
351 0           my $ret = undef;
352              
353             ####
354             ## If there were changes
355              
356 0 0         if ($s ne $ss) {
357 0           @$rs = ();
358 0 0         open(I, $fn) || die "Can't open $fn for read: $!";
359 0           while (!CORE::eof(I)) {
360 0           $_ = \*I;
361 0           push(@$rs, [ &$unpackc($_) ]);
362             }
363 0           close(I);
364 0           $ret = 1;
365             }
366 0   0       unlink $fn || die "Can't remove $fn: $!";
367 0           $ret;
368             }
369              
370             sub DESTROY {
371 0     0     my $self = shift;
372 0           $self->{' dbh'}->disconnect;
373             }
374              
375             sub edit {
376              
377             #####
378             ## Front end for spawn() to be called from command line
379              
380 0     0 0   require Getopt::Std;
381 0           import Getopt::Std;
382              
383             ####
384             ## Change slashes to dashes. Dashes would have been parsed by perl istelf.
385              
386 0           my @dummy = map {s|^/|-|;} @ARGV;
  0            
387 0           getopt("u:p:w:a:");
388              
389 0           my ($dsn, $table) = @ARGV;
390              
391 0   0       my $d = new Data::Direct($dsn, $opt_u, $opt_p, $table, $opt_w,
392             $opt_a) || die "Connection failed";
393              
394             ####
395             ## Updated database only if changes were detected
396              
397 0 0         $d->flush if ($d->spawn);
398             }
399              
400             sub gentemp {
401 0     0 0   my $fn;
402 0           eval {
403             ####
404             ## Make POSIX do the hard job
405              
406 0           require POSIX;
407 0           $fn = &POSIX::tmpnam;
408             };
409 0 0         return $fn if ($fn);
410 0           $fn = join("-", "data_direct", $$, $0, time, localtime, rand,
411             $gen_unique++);
412 0           $fn =~ s/[^a-zA-Z0-9]/_/g;
413             ####
414             ## Ultra safety check - run the function again if that weird filename
415             ## already exists.
416              
417 0 0         return &gentemp if (-e $fn);
418 0           $fn;
419             }
420              
421             1;
422              
423             __END__