File Coverage

lib/Fry/Lib/CDBI/Basic.pm
Criterion Covered Total %
statement 4 88 4.5
branch 0 8 0.0
condition n/a
subroutine 2 14 14.2
pod n/a
total 6 110 5.4


line stmt bran cond sub pod time code
1             package Fry::Lib::CDBI::Basic;
2 1     1   1780 use strict;
  1         2  
  1         1519  
3              
4             our $VERSION='0.15';
5             my $sql_count;
6             #our $cdbi_search = "search_abstract";
7             #other possible values are cdbi_search,cdbi_regex and cdbi_search_like
8              
9             #functions
10             sub _default_data {
11 0     0     my $class = shift;
12            
13             return {
14 0           depend=>[':CDBI::Load'],
15             vars=>{
16             editor=>$ENV{EDITOR},
17             splitter=>'=',
18             insert_columns=>'',
19             abstract_opts=>{logic=>'and'},
20             insert_delimiter=>',,',
21             cdbi_search=>'search_abstract',
22             #flags
23             safe_update=>1,
24             only_modified=>1,
25             },
26             subs=>{parseHash=>{qw/a h/},parseHashref=>{qw/a hr/},
27             printTextTable=>{qw/a tt/}
28             #search=>{sub=>'search'},search_like=>{}
29             },
30             cmds=>{
31             print_columns=>{a=>'pc',d=>'Prints columns of current table',u=>''},
32             search_abstract=>{a=>'s',aa=>\&aliasInputAndSql,
33             d=>'Search for results via AbstractSearch'
34             ,u=>'@search_term'},
35             cdbi_search=>{a=>'sn',aa=>\&aliasInputAndSql,u=>'@search_term'},
36             cdbi_search_like=>{a=>'sl',aa=>\&aliasInputAndSql,u=>'@search_term'},
37             cdbi_search_regex=>{a=>'sr',aa=>\&aliasInputAndSql,u=>'@search_term'},
38             cdbi_delete=>{a=>'d',aa=>\&aliasInputAndSql,
39             d=>'Deletes results of given query',u=>'@search_term'},
40             cdbi_create=>{a=>'i',aa=>\&aliasInsert, d=>"Creates a record",
41             u=>'($value$delim)+'},
42             cdbi_find_or_create=>{a=>'fc',aa=>\&aliasInputAndSql, d=>"Find or create a record",
43             u=>'@search_term'},
44             cdbi_multi_insert=>{a=>'mi',arg=>'$file',u=>'$file'},
45             cdbi_update=>{a=>'U',aa=>\&aliasInputAndSql, d=>'Updates records via a text editor',
46             u=>'@search_term'},
47             replace=>{d=>'evals each value of each result row with $operation', a=>'r',
48             u=>'@search_term$operation'},
49             cdbi_delete_obj=>{a=>':d',u=>'@cdbi'},
50             cdbi_update_obj=>{a=>':U',u=>'@cdbi'},
51             verify_no_delim=>{a=>'V',aa=>\&aliasInputAndSql, u=>'@cdbi',
52             d=>"Verify that specified records don't have display delimiter in them"},
53             display_table_list=>{qw/a dpt/, d=>'Displays public tables',u=>''},
54             print_dbi_log=>{a=>'dpl',d=>'Prints the current DBI log',u=>''},
55             clear_dbi_log=>{d=>'Clears the dbi log',u=>'',a=>'dcl',u=>''},
56             set_dbi_log_level=>{a=>'dsl',d=>'Sets the log level of a DBI handler',
57             u=>'$num'},
58             },
59             opts=>{cdbi_search=>{qw/a cs type var noreset 1 default cdbi_search_regex/ }}
60             #retrieve_all retrieve/],
61             #construct,has*,trigger,constrain_column,set_sql
62             #}
63             #subs=>{aliasInputAndSql=>{}},
64             #td: obj-$result(autoupdate,update,delete,set/get,copy,discard_changes,is_changed),$iterator,$col,$relation
65             }
66             }
67             sub _initLib {
68 0     0     my $cls = shift;
69 0           $cls->_set_insert_col;
70 0           $cls->Var('abstract_opts')->{cmp} = $cls->_regex_operator;
71              
72             #ugly, should be in _default_data
73 0           $cls->call(var=>'set','cdbi_search',enum=>[qw/cdbi_search cdbi_search_like cdbi_search_regex search_abstract/]);
74 0           $cls->call(var=>'set','cdbi_search',default=>'cdbi_search_regex');
75             }
76             #note for library use outside of shell
77             #this module depends on external subs: &parse_num
78              
79             ##utils
80             sub uniqueInArrays {
81 0     0     my ($cls,$uniq,$array2) =@_;
82 0           my (@unique,%seen,$i,@num);
83            
84 0           for (@$array2) {$seen{$_}++}
  0            
85 0 0         for (@$uniq) { $i++; do {push(@unique,$_);push(@num,$i) } if (! exists $seen{$_}) }
  0            
  0            
  0            
  0            
86 0           return (\@unique,\@num);
87             }
88             sub file2array {
89 0     0     shift;
90             #local function
91             #d:converts file to @ of lines
92 0           open(FILE,"< $_[0]");
93 0           my @lines; chomp(@lines = );
  0            
94 0           close FILE;
95 0           return @lines;
96             }
97             sub check_for_regex {
98             #d: AoHregexp, could be used as an 'or' search on multiple columns
99 0     0     my ($class,$regex,@records) = @_;
100 0           my @unclean;
101              
102 0           for (@records) {
103 0           for my $col (@{$class->Var('action_columns')}) {
  0            
104 0 0         if ($_->$col =~ /$regex/) {
105 0           push(@unclean,$_);
106 0           last; #break?
107             }
108             }
109             }
110 0           return @unclean;
111             }
112             #internal methods
113             sub _set_insert_col {
114 0     0     my $cls = shift;
115             #set insert_columns
116 0           my @insert_columns = @{$cls->Var('columns')};
  0            
117 0           shift @insert_columns;
118 0           $cls->setVar(insert_columns=>\@insert_columns);
119              
120             }
121             sub regexChangeAoH {
122 0     0     my ($cls,$op,@records2update) = @_;
123 0           for my $rec (@records2update) {
124 0           for (my $j=0; $j < @{$cls->Var('action_columns')}; $j++) {
  0            
125 0           my $col= $cls->Var('action_columns')->[$j];
126 0           $_ = $rec->$col;
127 0 0         eval $op; die($@) if $@;
  0            
128 0           $rec->$col($_);
129             }
130 0           $rec->update;
131             }
132             }
133             sub modify_file {
134 0     0     my ($cls,$tempfile) = @_;
135 0           my $inp;
136              
137 0           system($cls->Var('editor') . " $tempfile");# or die "can't execute command as $<: $@";
138             #?: why does this system always return a fail code
139             #$cls->view("cdbi_update (y/n)? "); chomp($inp = );
140 0           $inp = $cls->Rline->stdin("cdbi_update (y/n)?");
141 0           return ($inp eq "y");
142             }
143             sub update_from_file {
144 0     0     my ($cls,$tempfile,@records) = @_;
145              
146 0           my @lines = $cls->file2array($tempfile);
147              
148             #my $firstline = shift(@lines);
149             #read column order from file
150             #my @fields = split(/$updatedelim/,$firstline);
151             #or not
152 0           my @fields = @{$cls->Var('action_columns')};
  0            
153              
154 0           my $i;
155 0           foreach (@records) { #each row to update
156 0           my @fvalues = split(/${\$cls->Var('field_delimiter')}/,$lines[$i]);
  0            
157 0           for (my $j=0; $j < @fields; $j++) { #each column to update
158              
159 0           my $temp=$fields[$j];
160 0           $_->$temp($fvalues[$j]); # this line = $_->$field($fieldvalue)
161             }
162 0           $_->update;
163             #$_->dbi_commit if ($db = postgres
164 0           $i++;
165             }
166             }
167             sub col2f1 {
168             #d: aliases column names with c and number
169 0     0     my $class = shift;
170 0           my @newterms;
171              
172 0           for (@_) {
173             #if (/c(\d+)=/) { my $col = $col[$1-1];s/c\d+/$col/}
174 0 0         if (/c([-,\d]+)(.*)/) {
  0            
175 0           my @tempcol = $class->sub->parseNum($1,@{$class->Var('columns')});
  0            
176 0           for my $eachcol (@tempcol) {
177 0           push(@newterms,$eachcol.$2);
178             }
179             }
180             else {push (@newterms,$_)}
181             }
182 0           return @newterms;
183             }
184             #sub objects
185             ##print functions,input is objects
186             sub printtofile {
187             #d:prints rows to temporary file
188 0     0     my ($cls,$tempfile,@records) = @_;
189              
190 0           my $output = join($cls->Var('field_delimiter'),@{$cls->Var('action_columns')})."\n";
  0            
191 0           $output .= $cls->View->objAoH_dt(\@records,$cls->Var('action_columns'));
192 0           $cls->View->file($tempfile,$output);
193             }
194             sub printTextTable {
195 0     0     my $cls = shift;
196 0           $cls->print_text_table(\@_,$cls->Var('action_columns'));
197             }
198             sub print_text_table {
199             my $cls = shift;
200             my ($ref1,$ref2) = @_; my @row = @{$ref1}; my @columns = @{$ref2};
201             my (@column_values,@longest);
202              
203             #defaul
204 1     1   387 eval { use Text::Reform}; die $@ if ($@);
  0            
  0            
205              
206             for my $column (@columns) {
207             my @column_value;
208             my $longest = length($column);
209             for (@row) {
210             #find longest string in each column including string
211             my $newlength = length($_->$column);
212             $longest = $newlength if ($newlength > $longest);
213              
214             push(@column_value,$_->$column);
215             }
216             push(@longest,$longest);
217             push(@column_values,\@column_value);
218             }
219              
220             #create format
221             my $line_length = 3 * @columns + 1;
222             my $picture_line = "|";
223              
224             for (@longest) {
225             $line_length += $_ ;
226             $picture_line .= " " . "["x $_ . " |";
227             }
228             my $firstline = "=" x $line_length;
229             #$picture_line .= "\n" . "-" x $line_length;
230              
231             #print column names
232             $cls->view(form $picture_line,@columns);
233             #print body
234             $cls->view(form $firstline,$picture_line, @column_values);
235             }
236             sub print_horizontal_numbered_list {
237             my ($cls,$prompt,$list) = @_;
238             my $a;
239              
240             my $output = $prompt;
241             for (@$list){$a++;$output .= "$a.$_ " };
242             $output .= "\n";
243             $cls->view($output);
244             }
245             ##alias fns
246             sub cdbiDbh { shift->Var('table_class')->db_Main }
247             sub aliasInputAndSql { my $cls = shift;
248             return $cls->aliasSqlAbstract($cls->aliasInput(@_)) }
249             sub aliasInput {
250             my $class = shift;
251             @_ = $class->Var('columns')->[0].$class->Var('splitter').".*" if ($_[0] eq "a"); #all results given
252             @_ = $class->col2f1(@_) if ("@_" =~ /c[-,\d]+=/); #c\d instead of column name
253             return @_;
254             }
255             sub aliasInsert {
256             #d:parses userinput to hashref for &create
257             my $cls = shift;
258             my %chosenf;
259             #die "Nothing given for cdbi_insert" if (not defined @_);
260             my @fields = split(/${\$cls->Var('insert_delimiter')}/,"@_");
261             my @insert_columns = @{$cls->Var('insert_columns')};
262              
263             for (my $i=0;$i< @insert_columns;$i++) {
264             $chosenf{$insert_columns[$i]} = $fields[$i];
265             $cls->view("$insert_columns[$i] = $fields[$i]\n");
266             }
267             return \%chosenf;
268             }
269             sub aliasSqlAbstract {
270             #d:parse to feed to sql::abstract
271             #note: operators hardcoded for now
272             my $class = shift;
273             my @processf;
274             my $splitter = $class->Var('splitter');
275              
276             foreach (@_) {
277             if (/$splitter([>!<])=/) {
278             my $operator = $1;
279             my ($key,$value) = split(/=$operator=/);
280             push(@processf,$key,{"$operator\=",$value});
281             }
282             elsif (/$splitter([><=])/) {
283             my $operator = $1;
284             my ($key,$value) = split (/$splitter$operator/);
285             push(@processf,$key,{$operator,$value});
286             }
287             #embedded sql
288             elsif (/$splitter(.*)$splitter/) {
289             my $literal_sql = $1;
290             $literal_sql =~ s/_/ /g;
291             my ($key,$dump) = split (/$splitter/);
292             push(@processf,$key,\$literal_sql);
293             }
294             #default operator
295             #elsif(/=/)
296             else {
297             my ($key,$value) = split(/$splitter/) or die "error splitting select";
298             push(@processf,$key,$value);
299             }
300             #else { warn "no valid operator specified" };
301             }
302             return @processf;
303             }
304             ##parse functions,input is from commandine
305             sub parseHash {
306             my ($cls,$input) = @_;
307              
308             my @arg = split(/ /,$input);
309             my $cmd = shift @arg;
310             my %results = $cls->parseIndHash($cls->Var('splitter'),@arg);
311             return ($cmd,%results)
312             }
313             sub parseHashref {
314             my ($cls,$input) = @_;
315             my ($cmd,%results) = $cls->parseHash($input);
316             return ($cmd,\%results);
317             }
318             sub parseIndHash {
319             my ($class,$splitter,@chunks) = @_;
320             my %processf;
321              
322             for (@chunks) {
323             my ($key,$value) = split(/$splitter/) or die "error splitting select";
324             $processf{$key} = $value;
325             }
326             return %processf;
327             }
328             #commands
329             sub print_columns {
330             my $cls = shift;
331             $cls->print_horizontal_numbered_list($cls->Var('table')."'s columns are ",$cls->Var('columns'));
332             }
333             sub search_abstract {
334             #d:handles multiple parsing cases and returns search results
335             my $cls = shift;
336             if (@_ ==0 ) {warn("No arguments given to &search_abstract\n");return () }
337             $cls->sub->_require('Class::DBI::AbstractSearch');
338             $cls->sub->useThere('Class::DBI::AbstractSearch',$cls->Var('table_class'));
339              
340             #calling class determines class
341             my @results = $cls->Var('table_class')->Class::DBI::AbstractSearch::search_where(\@_,$cls->Var('abstract_opts'));
342             $cls->saveArray(@results) if ($cls->Flag('menu'));
343             return @results;
344             }
345             sub cdbi_search { shift->Var('table_class')->search(@_) }
346             sub cdbi_search_like { shift->Var('table_class')->search_like(@_) }
347             sub cdbi_search_regex { shift->Var('table_class')->search_regex(@_) }
348             sub cdbi_create { shift->Var('table_class')->create(@_) }
349             sub cdbi_delete {
350             #td: chain
351             my $cls = shift;
352             my @aliasedinput = @_;
353             my @results = $cls->${\$cls->Var('cdbi_search')}(@aliasedinput);
354             #my @results = $cls->sub->subHook(args=>\@aliasedinput,var=>'cdbi_search',default=>'search_abstract',caller=>$cls);
355             $cls->cdbi_delete_obj(@results);
356             }
357             sub cdbi_find_or_create {
358             my ($cls,%dt) = @_;
359             #my $hash = ref $_[0] eq "HASH" ? shift: {@_};
360             my ($exists) = $cls->${\$cls->Var('cdbi_search')}(%dt);
361             return defined($exists) ? $exists : $cls->Var('table_class')->create(\%dt);
362             }
363             sub cdbi_multi_insert {
364             my ($cls,$file) = @_;
365              
366             chomp(my @lines= $cls->file2array($file));
367             for (@lines) {
368             $cls->create($cls->aliasInsert($_));
369             }
370             }
371             sub replace {
372             #td:chain
373             my $cls = shift;
374             my $op = pop(@_);
375              
376             my @records2update = $cls->${\$cls->Var('cdbi_search')}($cls->aliasInputAndSql(@_));
377             $cls->regexChangeAoH($op,@records2update);
378             }
379             sub verify_no_delim {
380             #td:chain
381             my $cls = shift;
382              
383             my @records2update = $cls->${\$cls->Var('cdbi_search')}(@_);
384             my $clean = $cls->verify_no_delim_obj(@records2update);
385             $cls->view("No records containing delimiter found") if ($clean);
386             }
387             sub cdbi_update {
388             #td:chain
389             my $cls = shift;
390             #$cls->cdbi_update_obj($cls->${\$cls->Var('cdbi_search')}(@_));
391             $cls->cdbi_update_obj($cls->search_abstract(@_));
392             }
393             ##$result obj
394             sub cdbi_update_obj {
395             my ($cls,@records2update) = @_;
396             $cls->sub->_require('File::Temp');
397             do {warn("File::Temp"); return} if ($@);
398             my (undef,$tempfile) = File::Temp::tempfile();
399             #$tempfile = 'ya';
400              
401             if ($cls->Flag('safe_update')) {
402             my $clean = $cls->verify_no_delim_obj(@records2update);
403             return if (not $clean);
404             }
405              
406             $cls->printtofile($tempfile,@records2update);
407              
408             #only for changed rec
409             my @original_lines = $cls->file2array($tempfile)
410             if ($cls->Flag('only_modified'));
411              
412             my $modify = $cls->modify_file($tempfile);
413              
414             #only update changed records
415             if ($cls->Flag('only_modified')) {
416             my @new_lines = $cls->file2array($tempfile);
417             #shift off columns line
418             shift(@new_lines); shift(@original_lines);
419              
420             my ($modified_lines,$num) = ([],[]);
421             ($modified_lines,$num) = $cls->uniqueInArrays(\@new_lines,\@original_lines);
422             #exit early if nothing to modify
423             if (@$modified_lines == 0) { $modify = 0; last }
424              
425             #write new file
426             $cls->View->file($tempfile,join("\n",@$modified_lines));
427             @records2update = $cls->sub->parseNum(join(',',@$num),@records2update);
428             }
429              
430             $cls->update_from_file($tempfile,@records2update) if ($modify);
431             }
432             sub verify_no_delim_obj {
433             my ($cls,@records) = @_;
434              
435             my @unclean_records =
436             $cls->check_for_regex($cls->Var('field_delimiter'),@records);
437             #$cls->check_for_regex('a',@records);
438              
439             if (defined @unclean_records) {
440             $cls->view( "The following are records containing the delimiter '",
441             $cls->Var('field_delimiter'),"':\n\n");
442             $cls->View->objAoH(\@unclean_records,$cls->Var('action_columns'));
443             return 0;
444             }
445             #passed successfully
446             return 1;
447             }
448             sub cdbi_delete_obj {
449             my $class = shift;
450             for (@_) { $_->delete; }
451             }
452             #$dbh commands: could be used in DBI
453             sub display_table_list {
454             my ($class,$dbh) = @_;
455             $class->print_horizontal_numbered_list("Database's tables are ",[$class->get_table_list($dbh)]);
456             }
457             sub print_dbi_log {
458             my ($cls) = @_;
459             my $dbh = $cls->cdbiDbh;
460             $cls->view($dbh->{Profile}->format);
461             }
462             sub clear_dbi_log {
463             my ($cls) = @_;
464             my $dbh = $cls->cdbiDbh;
465             $dbh->{Profile}->{Data}=undef;
466             }
467             sub set_dbi_log_level{
468             my ($cls,$num) = @_;
469             my $dbh = $cls->cdbiDbh;
470              
471             if ($num > 15 or $num < -15) {
472             warn" given log level out of -15 to 15 range";
473             }
474             else { $dbh->{Profile} = $num; }
475             }
476             #$dbh = (defined $dbh) ? $cls->idToObj($dbh) : $cls->cdbiDbh;
477             ##other
478             sub t_file {
479             my $cls = shift;
480             #w
481             my $file = shift || do { $cls->view("No file given.\n"); return 0 };
482             if (! -e $file) { $cls->view("File doesn't exist.\n"); return 0};
483             return 1;
484             }
485             sub cmpl_file {
486             }
487             ###internal
488             sub get_table_list {
489             my ($cls,$dbh) = @_;
490             $dbh = (defined $dbh) ? $cls->idToObj($dbh) : $cls->cdbiDbh;
491             my $sth = $cls->get_table_info($dbh);
492             return warn "Driver hasn't implemented the table_info() method" unless (ref $sth);
493             my @tables = map {$_->[2]} @{$sth->fetchall_arrayref};
494             return @tables;
495             }
496             sub get_table_info {
497             #d: displays public tables for postgres, may have to adjust &table_info per database
498             my ($class,$dbh,$table) = @_;
499             my $catalog = undef;
500             my $schema = ($class->Var('db') eq "postgres") ? 'public' : undef;
501             my $type;
502             return $dbh->table_info($catalog,$schema,$table,$type);
503             }
504             1;
505              
506             __END__