File Coverage

lib/SQL/Shell.pm
Criterion Covered Total %
statement 216 679 31.8
branch 88 340 25.8
condition 30 100 30.0
subroutine 48 92 52.1
pod 37 39 94.8
total 419 1250 33.5


line stmt bran cond sub pod time code
1             ##############################################################################
2             # Purpose : SQL Shell API
3             # Author : John Alden
4             # Created : Jul 2006 (refactored from sqlsh.pl)
5             # CVS : $Header: /home/cvs/software/cvsroot/db_utils/lib/SQL/Shell.pm,v 1.14 2006/12/05 14:31:33 andreww Exp $
6             ###############################################################################
7              
8             package SQL::Shell;
9              
10 1     1   18416 use strict;
  1         1  
  1         31  
11              
12 1     1   3 use Carp;
  1         1  
  1         39  
13 1     1   1195 use DBI;
  1         12007  
  1         46  
14 1     1   6 use File::Path;
  1         1  
  1         40  
15 1     1   460 use IO::File;
  1         6246  
  1         83  
16 1     1   390 use URI::Escape;
  1         921  
  1         49  
17              
18 1     1   4 use vars qw($VERSION);
  1         1  
  1         86  
19             $VERSION = ('$Revision: 1.14_02 $' =~ /([\d\._]+)/)[0];
20              
21 1   50 1   4 use constant HISTORY_SIZE => $ENV{HISTSIZE} || $ENV{HISTFILESIZE} || 50;
  1         1  
  1         75  
22 1     1   4 use vars qw(%Renderers %Commands %Settings);
  1         0  
  1         4548  
23              
24             #Available rendering routines
25             %Renderers = (
26             'delimited' => \&_render_delimited,
27             'box' => \&_render_box,
28             'spaced' => \&_render_spaced,
29             'record' => \&_render_record,
30             'sql' => \&_render_sql,
31             'xml' => \&_render_xml,
32             );
33              
34             #Commands available by default
35             %Commands = (
36             qr/^(list|show) +drivers$/i => \&show_drivers,
37             qr/^(?:list|show) datasources (\w+)$/i => \&show_datasources,
38             qr/^(show )?history$/i => \&show_history,
39             qr/^clear history$/i => \&clear_history,
40             qr/^load history from ([\w\-\.\/~]+)$/i => \&load_history,
41             qr/^save history to ([\w\-\.\/~]+)$/i => \&save_history,
42             qr/^connect (\S+) ?(\S+)? ?(\S+)?/i => \&connect,
43             qr/^disconnect$/i => \&disconnect,
44             qr/^show +\$dbh +(.*)/i => \&show_dbh,
45             qr/^(list|show) +schema$/i => \&show_schema,
46             qr/^(list|show) +tables$/i => \&show_tables,
47             qr/^(list|show) +charsets$/i => \&show_charsets,
48             qr/^(?:desc|describe) +(.*)/i => \&describe,
49             qr/^((?:select|explain)\s+.*)/is => \&run_query,
50             qr/^((?:create|alter|drop|insert|replace|update|delete|grant|revoke) .*)/is => \&do_sql,
51             qr/^begin work/i => \&begin_work,
52             qr/^rollback/i => \&rollback,
53             qr/^commit/i => \&commit,
54             qr/^wipe(?: all)? tables$/i => \&wipe_tables,
55             qr/^load ([^\s]+) into ([\w\-\.\/]+)(?: delimited by (\S+))?(?: (uri-decode))?(?: from (\S+))?(?: to (\S+))?/i => \&load_data,
56             qr/^dump (.+) into ([\w\-\.\/~]+)(?: delimited by (\S+))?/i => \&dump_data,
57             qr/^set +(.*?)\s+(.*)/i => \&set_param,
58             qr/^(?:execute|source) +(.*)/i => \&run_script,
59             qr/^no log$/i => \&disable_logging,
60             qr/^log +(.*?) +(?:(?:to|into) +)?(.*)/i => \&enable_logging,
61             );
62              
63             %Settings = map {$_ => 1} qw(GetHistory SetHistory AddHistory MaxHistory Interactive Verbose NULL Renderer Logger Delimiter Width LogLevel EscapeStrategy AutoCommit LongTruncOk LongReadLen MultiLine);
64              
65             #######################################################################
66             #
67             # Public methods - these should croak on error
68             #
69             #######################################################################
70              
71             sub new
72             {
73 1     1 1 343 my ($class, $overrides) = @_;
74              
75             #Default storage for history information (used by closures)
76 1         2 my @history;
77              
78             #Default settings
79             my $settings = {
80             Interactive => $overrides->{Interactive} || 0,
81             Verbose => $overrides->{Verbose} || 0,
82             Renderer => _renderer($overrides->{Renderer}) || \&_render_box,
83             Logger => _renderer($overrides->{Logger}) || \&_render_delimited,
84             Delimiter => $overrides->{Delimiter} || "\t",
85             Width => $overrides->{Width} || 80,
86             MaxHistory => $overrides->{MaxHistory} || HISTORY_SIZE,
87             LogLevel => $overrides->{LogLevel},
88             AutoCommit => $overrides->{AutoCommit} || 0,
89             LongTruncOk => exists $overrides->{LongTruncOk}? $overrides->{LongTruncOk} : 1,
90             LongReadLen => $overrides->{LongReadLen} || 512,
91             MultiLine => $overrides->{MultiLine} || 0,
92 3     3   192 GetHistory => $overrides->{GetHistory} || sub {return \@history},
93 3     3   189 SetHistory => $overrides->{SetHistory} || sub {my $n = shift; @history = @$n},
  3         8  
94 8     8   13 AddHistory => $overrides->{AddHistory} || sub {push @history, shift()},
95 1 50 50     13 NULL => exists $overrides->{NULL}? $overrides->{NULL} : 'NULL',
    50 50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
96             };
97              
98 1         18 my %commands = %Commands;
99 1         5 my %renderers = %Renderers;
100              
101 1         5 my $self = {
102             'settings' => $settings,
103             'commands' => \%commands,
104             'renderers' => \%renderers,
105             'current_statement' => ''
106             };
107 1         4 return bless($self, $class);
108             }
109              
110             sub DESTROY
111             {
112 1     1   229 my $self = shift;
113 1 50       2 if(_is_connected($self->{dbh})) {
114 0         0 $self->{dbh}->disconnect();
115             }
116             }
117              
118             sub set
119             {
120 0     0 1 0 my ($self, $key, $value) = @_;
121 0 0       0 croak("Unknown setting: $key") unless $Settings{$key};
122 0         0 $self->{settings}{$key} = $value;
123             }
124              
125             sub get
126             {
127 2     2 1 366 my ($self, $key) = @_;
128 2 50       6 croak("Unknown setting: $key") unless $Settings{$key};
129 2         4 return $self->{settings}{$key};
130             }
131              
132             sub install_renderers
133             {
134 0     0 1 0 my ($self, $renderers) = @_;
135 0 0       0 croak "install_renderers method should be passed a hashref" unless(ref $renderers eq 'HASH');
136 0         0 foreach my $k (keys %$renderers) {
137 0         0 $self->{renderers}{$k} = $renderers->{$k};
138             }
139             }
140              
141             sub uninstall_renderers
142             {
143 0     0 1 0 my ($self, $renderers) = @_;
144 0 0       0 $renderers = $self->{renderers} unless defined ($renderers);
145 0 0       0 croak "uninstall_renderers method should be passed an arrayref" unless(ref $renderers eq 'ARRAY');
146 0         0 for(@$renderers) {
147 0 0       0 delete $self->{renderers}{$_} or carp("$_ not found in list of renderers");
148             }
149             }
150              
151             sub install_cmds
152             {
153 1     1 1 235 my ($self, $cmds) = @_;
154 1 50       5 croak "install_commands method should be passed a hashref" unless(ref $cmds eq 'HASH');
155 1         4 foreach my $rx(keys %$cmds) {
156 1         4 $self->{commands}{$rx} = $cmds->{$rx};
157             }
158             }
159              
160             sub uninstall_cmds
161             {
162 1     1 1 273 my ($self, $cmds) = @_;
163 1 50       3 $cmds = $self->{commands} unless defined ($cmds);
164 1 50       3 croak "uninstall_commands method should be passed an arrayref" unless(ref $cmds eq 'ARRAY');
165 1         3 for(@$cmds) {
166 1 50       7 delete $self->{commands}{$_} or carp("$_ not found in list of commands");
167             }
168             }
169              
170             sub execute_cmd
171             {
172 31     31 1 35424 my $self = shift;
173 31         79 return $self->_execute(@_);
174             }
175              
176             sub is_connected
177             {
178 0     0 1 0 my $self = shift;
179 0         0 return _is_connected($self->{dbh});
180             }
181              
182             sub dsn
183             {
184 0     0 1 0 my $self = shift;
185 0 0       0 return undef unless _is_connected($self->{dbh});
186 0         0 return sprintf "DBI:%s:%s", $self->{dbh}{Driver}{Name}, $self->{dbh}{Name};
187             }
188              
189             sub render_rowset {
190 0     0 1 0 my $self = shift;
191 0         0 $self->{settings}{Renderer}->($self, \*STDOUT, @_);
192             }
193              
194             sub log_rowset {
195 0     0 1 0 my $self = shift;
196 0         0 $self->{settings}{Logger}->($self, $self->{LogFH}, @_);
197             }
198              
199             ###############################################
200             #
201             # Commands - these should die with /n on error
202             #
203             ###############################################
204              
205             sub run_script
206             {
207 1     1 1 3 my ($self, $script) = @_;
208 1 50       23 print "Executing $script\n" if ($self->{settings}{Verbose});
209 1         3 $script = _expand_filename($script);
210 1 50       8 my $file = new IO::File "$script" or die("Unable to open file $script - $!");
211 1         85 my @cmds = map {chomp; $_} <$file>;
  2         3  
  2         5  
212 1         3 foreach(@cmds)
213             {
214 2 100       5 $self->execute_cmd($_) or die("Command '$_' failed - aborting $script");
215             }
216 0         0 return 1;
217             }
218              
219             sub load_history
220             {
221 1     1 1 2 my $self = shift;
222 1   50     4 my $filename = shift || die("You must specify a file to load the history from");
223              
224 1         3 TRACE("Loading history from $filename");
225 1         4 my $history = _load_history($filename);
226 1 50       5 $self->{settings}{SetHistory}->($history) if(defined $history);
227 1         5 return $history;
228             }
229              
230             sub clear_history
231             {
232 1     1 1 2 my $self = shift;
233 1         3 TRACE("Clearing history");
234 1         3 $self->{settings}{SetHistory}->([]);
235 1         3 return 1;
236             }
237              
238             sub save_history
239             {
240 1     1 1 1 my $self = shift;
241 1   50     4 my $filename = shift || die("You must specify a file to save the history to");
242 1   33     16 my $max_size = shift || $self->{settings}{MaxHistory};
243              
244 1         5 my $history = $self->{settings}{GetHistory}->();
245 1         7 TRACE("Saving history to $filename (contains ".(scalar @$history)." items)");
246 1         4 _save_history($history, $filename, $max_size);
247 1         5 return 1;
248             }
249              
250             sub show_history
251             {
252 1     1 1 1 my $self = shift;
253 1         4 my $history = $self->{settings}{GetHistory}->();
254 1         3 print "\n",(map {" ".$_."\n"} @$history),"\n";
  3         25  
255 1         6 return 1;
256             }
257              
258             sub enable_logging
259             {
260 0     0 1 0 my ($self, $level, $file) = @_;
261 0 0       0 die("Unrecognised logging level: $level\n") unless($level =~ /^(commands|queries|all)$/);
262 0         0 my $settings = $self->{settings};
263 0         0 $file = _expand_filename($file);
264 0 0       0 $self->{LogFH} = new IO::File ">> $file" or die("Unable to open $file for logging - $!\n");
265 0         0 $settings->{LogLevel} = $level;
266 0 0       0 print "Logging $level to $file\n" if($settings->{Verbose});
267 0         0 return 1;
268             }
269              
270             sub disable_logging
271             {
272 0     0 1 0 my ($self) = @_;
273 0         0 my $settings = $self->{settings};
274 0 0 0     0 print "Stopped logging $settings->{LogLevel}\n" if($settings->{Verbose} && defined $self->{LogFH});
275 0         0 $self->{LogFH} = undef;
276 0         0 $settings->{LogLevel} = undef;
277 0         0 return 1;
278             }
279              
280             sub connect
281             {
282 0     0 1 0 my($self, $dsn, $username, $password) = @_;
283 0         0 my $settings = $self->{settings};
284              
285             my $dbh = DBI->connect($dsn, $username, $password,
286             {PrintError => 0, RaiseError => 1, LongTruncOk => $settings->{LongTruncOk},
287 0         0 LongReadLen => $settings->{LongReadLen}});
288              
289 0         0 eval { $dbh->{AutoCommit} = $settings->{AutoCommit} };
  0         0  
290 0 0 0     0 if ($@ && !$settings->{AutoCommit}) {
291 0         0 warn "WARNING: $dsn doesn't appear to support transactions\n";
292             }
293              
294 0         0 $self->{dbh} = $dbh;
295 0         0 return $dbh;
296             }
297              
298             sub disconnect
299             {
300 52     52 1 41 my $self = shift;
301 52 50       52 $self->{dbh}->disconnect if _is_connected($self->{dbh});
302 52         43 $self->{dbh} = undef;
303 52         44 return 1;
304             }
305              
306             sub show_charsets
307             {
308 0     0 1 0 my ($self) = @_;
309 0         0 eval {require Locale::Recode};
  0         0  
310 0 0       0 die "Locale::Recode is not available. Please install it if you want character set support.\n" if($@);
311 0         0 my $charsets = Locale::Recode->getSupported();
312 0         0 print "\n",(map {" ".$_."\n"} sort @$charsets),"\n";
  0         0  
313 0         0 return 1;
314             }
315              
316             sub show_drivers
317             {
318 2     2 1 15 print "\n",(map {" ".$_."\n"} DBI->available_drivers()),"\n";
  12         580  
319 2         12 return 1;
320             }
321              
322             sub show_datasources
323             {
324 0     0 1 0 my ($self, $driver) = @_;
325 0         0 print "\n",(map {" ".$_."\n"} DBI->data_sources($driver)),"\n";
  0         0  
326 0         0 return 1;
327             }
328              
329             sub show_dbh
330             {
331 1     1 1 2 my ($self, $property) = @_;
332 1 50       2 my $dbh = $self->_dbh() or die "Not connected to database.\n";
333 0         0 $self->render_rowset([$property], [[$dbh->{$property}]]);
334 0         0 return 1;
335             }
336              
337             sub show_schema
338             {
339 0     0 1 0 my $self = shift;
340 0 0       0 my $dbh = $self->_dbh() or die "Not connected to database.\n";
341              
342             #Banner
343 0         0 my($driver, $db, $user) = ($dbh->{Driver}{Name}, $dbh->{Name}, $dbh->{Username});
344 0         0 my $header = ["Schema dump"];
345 0         0 my @data = (
346             ["$driver database $db"],
347             ["connected as $user"],
348             ["on ".localtime()],
349             );
350 0         0 $self->_render_box(\*STDOUT, $header, \@data);
351            
352             #Each table
353 0         0 foreach(_list_tables($dbh))
354             {
355 0         0 print "\n";
356 0         0 $self->_desc_table($_);
357             }
358              
359 0         0 return 1;
360             }
361              
362             sub show_tables
363             {
364 1     1 1 2 my $self = shift;
365 1 50       4 my $dbh = $self->_dbh() or die "Not connected to database.\n";
366 0         0 $self->render_rowset([qw(table rows)], _summarise_tables($dbh));
367 0         0 return 1;
368             }
369              
370             sub describe
371             {
372 1     1 1 2 my ($self, $table) = @_;
373 1 50       3 my $dbh = $self->_dbh() or die "Not connected to database.\n";
374              
375 0         0 $self->_desc_table($table);
376 0         0 return 1;
377             }
378              
379             sub run_query
380             {
381 1     1 1 2 my ($self, $query) = @_;
382 1 50       3 my $dbh = $self->_dbh() or die "Not connected to database.\n";
383            
384 0         0 my $settings = $self->{settings};
385 0         0 my($headers, $data) = $self->_execute_query($query);
386 0         0 $self->render_rowset($headers, $data);
387 0 0 0     0 if (defined $settings->{LogLevel} && ($settings->{LogLevel} eq 'queries' || $settings->{LogLevel} eq 'all')) {
      0        
388 0         0 $self->log_rowset($headers, $data);
389             }
390 0         0 return 1;
391             }
392              
393             sub do_sql
394             {
395 0     0 1 0 my ($self, $statement) = @_;
396 0 0       0 my $dbh = $self->_dbh() or die "Not connected to database.\n";
397              
398 0         0 my $rows = $dbh->do($statement);
399 0 0       0 $rows = 0 if $rows eq '0E0';
400              
401 0         0 my $cmd = (split /\s+/, $statement)[0];
402 0 0       0 my $obj =
    0          
    0          
    0          
403             scalar $cmd =~ /(create|alter|drop)/? ($statement =~ /$1\s+(\S+\s+\S+?)\b/i)[0]
404             : $cmd eq 'insert' ? ($statement =~ /into\s+(\S+?)\b/)[0]
405             : $cmd eq 'update' ? ($statement =~/\s+(\S+?)\b/)[0]
406             : $cmd eq 'delete' ? ($statement =~/from\s+(\S+?)\b/)[0]
407             : ''
408             ;
409              
410 0 0 0     0 print "\U$cmd\E $obj: $rows rows affected\n\n" unless($rows == -1 && !$self->{settings}{Verbose});
411 0         0 return 1;
412             }
413              
414             sub begin_work
415             {
416 1     1 1 24 my $self = shift;
417 1 50       2 my $dbh = $self->_dbh() or die "Not connected to database.\n";
418 0         0 $dbh->begin_work;
419 0         0 return 1;
420             }
421              
422             sub commit
423             {
424 1     1 1 2 my $self = shift;
425 1 50       3 my $dbh = $self->_dbh() or die "Not connected to database.\n";
426 0         0 $dbh->commit;
427 0         0 return 1;
428             }
429              
430             sub rollback
431             {
432 1     1 1 3 my $self = shift;
433 1 50       3 my $dbh = $self->_dbh() or die "Not connected to database.\n";
434 0         0 $dbh->rollback;
435 0         0 return 1;
436             }
437              
438             sub wipe_tables
439             {
440 0     0 1 0 my $self = shift;
441 0 0       0 my $dbh = $self->_dbh() or die "Not connected to database.\n";
442 0         0 my @tables = _list_tables($dbh);
443              
444 0 0       0 if($self->{settings}{Interactive}) {
445 0         0 print "Wipe all data from:\n\n",(map {" ".$_."\n"} @tables),"\nAre you sure you want to do this? (type 'yes' if you are) ";
  0         0  
446 0         0 my $response = ;
447 0         0 chomp $response;
448 0 0       0 return 0 unless ($response eq 'yes');
449             }
450              
451 0         0 foreach(@tables)
452             {
453 0         0 $dbh->do("delete from $_");
454             }
455 0 0       0 print "\nWiped all data in database\n\n" if($self->{settings}{Verbose});
456 0         0 return 1;
457             }
458              
459             sub load_data
460             {
461 1     1 1 3 my ($self,$filename, $table, $delimiter, $uri_decode, $cf, $ct) = @_;
462 1   50     3 $uri_decode &&= 1; #Force to boolean (concession to command regex)
463 1 50       5 $delimiter = $self->{settings}{Delimiter} unless(defined $delimiter);
464 1 50 33     3 die "You must supply a character set to recode into!\n" if ($cf && !$ct);
465 1 50 33     7 die "You must supply a source character set for recoding\n" if (!$cf && $ct);
466 1 50 33     5 if($cf && $ct) {
467 0         0 require Locale::Recode;
468 0 0       0 die "Unrecognised character set '$cf'\n" if(not Locale::Recode->resolveAlias($cf));
469 0 0       0 die "Unrecognised character set '$ct'\n" if(not Locale::Recode->resolveAlias($ct));
470             }
471 1 50       3 my $dbh = $self->_dbh() or die "Not connected to database.\n";
472            
473 0 0 0     0 print "Using URI::Decode\n" if ($uri_decode && $self->{settings}{Verbose});
474 0         0 my $recoder;
475 0 0       0 if ($cf) {
476 0 0       0 print "Recoding characters from $cf to $ct\n" if ($self->{settings}{Verbose});
477 0         0 require Locale::Recode;
478 0         0 $recoder = new Locale::Recode('from' => $cf, 'to' => $ct);
479             }
480              
481             #Open file
482 0         0 my $file = new IO::File $filename;
483              
484             #Read headers
485 0         0 my $headers = <$file>; chomp $headers;
  0         0  
486 0         0 my @headers = split($delimiter, $headers);
487              
488             #Build SQL from headers
489 0         0 my $sql = "INSERT into $table (".join(",", @headers).") VALUES (".join(",", map{"?"} @headers).")";
  0         0  
490 0         0 my $sth = $dbh->prepare_cached($sql);
491              
492             #Load data from file
493 0         0 my $counter = 0;
494 0         0 while(<$file>)
495             {
496 0         0 chomp;
497 0         0 my @row = split($delimiter, $_);
498 0 0       0 die "Error: more values in row ".join(",",@row)." than there are headers (".join(",",@headers)."). Aborting load\n" if(scalar @row > scalar @headers);
499              
500             #Fill in short rows with nulls
501 0         0 while(scalar @row < scalar @headers) {
502 0         0 push @row, undef;
503             }
504              
505             #Perform encoding conversions
506 0 0       0 @row = _recode($recoder, @row) if ($recoder);
507 0 0       0 @row = map {uri_unescape($_)} @row if ($uri_decode);
  0         0  
508              
509             #Insert data
510 0         0 eval {
511 0         0 $sth->execute(@row);
512             };
513 0 0       0 die("Error executing $sql with params (" . join(",", @row) . ") at line $. in $filename - $@") if($@);
514              
515 0         0 $counter++;
516             }
517              
518 0 0       0 print "Loaded $counter rows into $table from $filename\n" if($self->{settings}{Verbose});
519 0         0 return 1;
520             }
521              
522             sub dump_data
523             {
524 1     1 1 3 my ($self, $source, $filename, $delimiter) = @_;
525 1 50       6 my $dbh = $self->_dbh() or die "Not connected to database.\n";
526 0         0 $source =~ s/^\s+//g; $source =~ s/\s+$//g; #Trim any whitespace
  0         0  
527 0 0       0 print "Dumping $source into $filename\n" if($self->{settings}{Verbose});
528 0 0       0 if(lc($source) eq 'all tables')
529             {
530 0         0 my $files = $self->_dump_tables($filename, $delimiter);
531 0 0       0 print "Dumped ".scalar(@$files)." tables into $filename:\n" if($self->{settings}{Verbose});
532 0         0 print map {" - $_\n"} @$files;
  0         0  
533             }
534             else
535             {
536 0         0 my $count = $self->_dump_data($source, $filename, $delimiter);
537 0 0       0 print "Dumped $count rows into $filename\n" if($self->{settings}{Verbose});
538             }
539 0         0 return 1;
540             }
541              
542             sub set_param
543             {
544 12     12 1 13 my ($self,$param, $mode) = @_;
545 12         28 TRACE("set $param=$mode");
546 12         11 my $settings = $self->{settings};
547 12         16 my $dbh = $self->_dbh;
548            
549 12         10 my $valid = 1;
550 12 100       58 if($param eq 'display-mode')
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
551             {
552 2 100       10 die sprintf "'$mode' is an invalid value for display-mode. Valid values are %s\n", join(", ", sort keys %{$self->{renderers}}) unless (exists $self->{renderers}{$mode});
  1         13  
553 1         2 $settings->{Renderer} = $self->{renderers}{$mode};
554             }
555             elsif($param eq 'log-mode')
556             {
557 1 50       6 die sprintf "'$mode' is an invalid value for log-mode. Valid values are %s\n", join(", ", sort keys %{$self->{renderers}}) unless(exists $self->{renderers}{$mode});
  1         9  
558 0         0 $settings->{Logger} = $self->{renderers}{$mode};
559             }
560             elsif($param eq 'escape')
561             {
562 1 50       14 die("'$mode' is an invalid value for escape should be (off, uri-escape, show-whitespace or escape-whitespace)") unless $mode =~ /(uri-escape|show-whitespace|escape-whitespace|off)/;
563 0         0 my $mapping = {
564             'show-whitespace' => 'ShowWhitespace',
565             'uri-escape' => 'UriEscape',
566             'escape-whitespace' => 'EscapeWhitespace',
567             'off' => undef
568             };
569 0         0 $settings->{EscapeStrategy} = $mapping->{$mode};
570 0 0       0 print "Escape set to $mode\n" if($settings->{Verbose});
571             }
572             elsif($param eq 'enter-whitespace')
573             {
574 1 50       5 my $_onoff = ($mode =~ /^on$/i) ? 1 : ($mode =~ /^off$/i) ? 0 : undef;
    50          
575 1 50       6 die "'$mode' is an invalid value for enter-whitespace (should be 'on' or 'off')\n" unless(defined $_onoff);
576 0         0 $settings->{EnterWhitespace} = $_onoff;
577 0 0       0 print "Whitespace ".($settings->{EnterWhitespace}?"may":"may not")." be entered as \\n, \\r and \\t\n" if($settings->{Verbose});
    0          
578             }
579             elsif($param eq 'delimiter')
580             {
581 0         0 $settings->{Delimiter} = $mode;
582 0 0       0 print "Delimiter is now '$settings->{Delimiter}'\n" if($settings->{Verbose});
583             }
584             elsif($param eq 'width')
585             {
586 1 50       7 die "'$mode' is an invalid value for width (should be an integer)\n" unless($mode =~ /^\d+$/);
587 0         0 $settings->{Width} = $mode;
588 0 0       0 print "Width is now '$settings->{Width}'\n" if($settings->{Verbose});
589             }
590             elsif($param eq 'auto-commit')
591             {
592 1 50       5 my $_onoff = ($mode =~ /^on$/i) ? 1 : ($mode =~ /^off$/i) ? 0 : undef;
    50          
593 1 50       7 die "'$mode' is an invalid value for auto-commit (should be 'on' or 'off')\n" unless (defined $_onoff);
594 0 0       0 eval {$dbh->{AutoCommit} = $_onoff if _is_connected($dbh) };
  0         0  
595 0 0       0 die "Couldn't set AutoCommit to '$mode' - $@\n" if($@);
596 0 0       0 print "AutoCommit is now '\U$mode\E'\n" if($settings->{Verbose});
597 0         0 $settings->{AutoCommit} = $_onoff;
598             }
599             elsif($param eq 'longreadlen')
600             {
601 1 50       12 die "'$mode' is an invalid value for longreadlen (should be an integer)\n" unless($mode =~ /^\d+$/);
602 0 0       0 eval { $dbh->{LongReadLen} = $mode if _is_connected($dbh) };
  0         0  
603 0 0       0 die "Couldn't set LongReadLen to '$mode' - $@\n" if($@);
604 0 0       0 print "LongReadLen set to '$mode'\n" if($settings->{Verbose});
605 0         0 $settings->{LongReadLen} = $mode;
606             }
607             elsif($param eq 'longtruncok')
608             {
609 1 50       7 my $_onoff = ($mode =~ /^on$/i) ? 1 : ($mode =~ /^off$/i) ? 0 : undef;
    50          
610 1 50       7 die "'$mode' is an invalid value for longtruncok (should be 'on' or 'off')\n" unless (defined $_onoff);
611 0 0       0 eval { $dbh->{LongTruncOk} = $_onoff if _is_connected($dbh) };
  0         0  
612 0 0       0 die "Couldn't set LongTruncOk to '\U$mode\E'\n - $@" if($@);
613 0 0       0 print "LongTruncOk set to '\U$mode\E'\n" if($settings->{Verbose});
614 0         0 $settings->{LongTruncOk} = $_onoff;
615             }
616             elsif($param eq 'multiline')
617             {
618 1 50       5 my $_onoff = ($mode =~ /^on$/i) ? 1 : ($mode =~ /^off$/i) ? 0 : undef;
    50          
619 1 50       7 die "'$mode' is an invalid value for multiline (should be 'on' or 'off')\n" unless (defined $_onoff);
620 0         0 $settings->{MultiLine} = $_onoff;
621             }
622             elsif($param eq 'tracing')
623             {
624 1 50       8 if ($mode =~ /^on$/i) {
    50          
    50          
625 0         0 import Log::Trace("print");
626 0 0       0 print "Log::Trace enabled\n" if($settings->{Verbose});
627             }
628             elsif ($mode =~ /^off$/i) {
629 0         0 import Log::Trace();
630 0 0       0 print "Log::Trace disabled\n" if($settings->{Verbose});
631             }
632             elsif ($mode =~ /^deep$/i) {
633 0         0 import Log::Trace("print" => {Deep => 1});
634 0 0       0 print "Log::Trace enabled with deep import into modules\n" if($settings->{Verbose});
635             }
636             else {
637 1         5 die "'$mode' is an invalid value for tracing (should be 'on', 'deep' or 'off')\n";
638             }
639             }
640             else
641             {
642 1         5 die "Unknown parameter '$param' for set command\n";
643             }
644            
645 1         5 return $valid;
646             }
647              
648              
649             #######################################################################
650             #
651             # Private methods
652             #
653             #######################################################################
654              
655             #
656             # Main worker
657             #
658             sub _execute
659             {
660 31     31   38 my($self, $cmd) = @_;
661 31         26 my $valid = 1;
662            
663             #Convenience vars
664 31         45 my $dbh = $self->_dbh;
665 31         30 my $settings = $self->{settings};
666            
667 31 0 0     48 if (defined $settings->{LogLevel} && ($settings->{LogLevel} eq 'all' || $settings->{LogLevel} eq 'commands'))
      33        
668             {
669 0         0 my $log = $self->{LogFH};
670 0         0 my $dont_log = 0; #May want to extend to allow a list of command regexes to be specified "unsuitable for logging"
671 0 0       0 print $log "$cmd\n" unless($dont_log);
672             }
673              
674 31 50       37 if ($settings->{MultiLine})
675             {
676 0         0 $self->{current_statement} .= $cmd."\n";
677 0 0       0 return 1 unless $self->{current_statement} =~ /;\s*$/s;
678 0         0 $cmd = $self->{current_statement};
679 0         0 $cmd =~ s/\n/ /sg;
680             }
681 31         29 $self->{current_statement} = '';
682              
683 31         292 $cmd =~ s/(?:^\s*|\s*;?\s*$)//g;
684 31 50       52 if($settings->{EnterWhitespace})
685             {
686 0         0 $cmd =~ s/\\n/\n/g;
687 0         0 $cmd =~ s/\\r/\r/g;
688 0         0 $cmd =~ s/\\t/\t/g;
689             }
690              
691             #Command recognition
692 31 50       38 if($cmd)
693             {
694             #Look for command in command table
695 31         25 my $found = 0;
696 31         25 foreach my $regex (keys %{$self->{commands}}) {
  31         124  
697 496         7444 my @args = ($cmd =~ $regex);
698 496 100       851 if(@args) {
699             eval
700 29         27 {
701             #Execute command and convert any true return value to 1
702 29   50     80 $valid = $self->{commands}{$regex}->($self, @args) && 1;
703             };
704 29 100       55 if($@) {
705 21         402 print $@;
706 21         22 $valid = 0;
707             }
708 29         20 $found = 1;
709 29         44 last;
710             }
711             }
712            
713 31 100       81 if(not $found) {
714 2 50       6 my $s = length($cmd)>20? substr($cmd,0,20)."..." : $cmd;
715 2         46 warn "Unrecognised command '$s'\n";
716 2         4 $valid = 0;
717             }
718             }
719            
720 31 100 66     162 $settings->{AddHistory}->($cmd) if($cmd =~ /\S/ && $valid); #Add command to history
721 31         122 return $valid;
722             }
723              
724              
725              
726             #######################################################################
727             #
728             # Renderers
729             #
730             #######################################################################
731              
732             sub _render_delimited
733             {
734 0     0   0 my ($self, $fh, $headers, $data) = @_;
735 0         0 my $delim = $self->{settings}{Delimiter};
736 0         0 print $fh join($delim, @$headers)."\n";
737 0         0 foreach(@$data)
738             {
739 0         0 print $fh join($delim, @$_)."\n";
740             }
741 0         0 print $fh "\n";
742             }
743              
744             sub _render_sql
745             {
746 0     0   0 my ($self, $fh, $headers, $data, $table) = @_;
747 0   0     0 $table ||= '$table';
748 0         0 my $sql = "INSERT into $table (".join("," , @$headers).") VALUES (%s);\n";
749 0         0 my $settings = $self->{settings};
750 0         0 my $dbh = $self->_dbh;
751 0 0       0 local $settings->{NULL} = 'NULL' unless -t $fh;
752 0         0 foreach(@$data)
753             {
754             my @fields = map{
755 0         0 defined() ?
756             DBI::looks_like_number($_) ? $_ : $dbh->quote($_)
757             : $settings->{NULL}
758 0 0       0 } @$_;
    0          
759 0         0 printf $fh $sql, join(",", @fields);
760             }
761 0         0 print $fh "\n";
762             }
763              
764             sub _render_xml
765             {
766 0     0   0 my ($self, $fh, $headers, $data) = @_;
767 0         0 require CGI; #For its markup escaping routine
768 0         0 print $fh "\n";
769 0         0 foreach my $record (@$data)
770             {
771 0         0 print $fh "\t\n";
772             print $fh map {
773 0         0 my $val = shift @$record;
  0         0  
774 0         0 $val = CGI::escapeHTML($val);
775 0         0 "\t\t<$_>$val\n"
776             } @$headers;
777 0         0 print $fh "\t\n";
778             }
779 0         0 print $fh "\n";
780 0         0 print $fh "\n";
781             }
782              
783             sub _render_box
784             {
785 0     0   0 my ($self, $fh, $headers, $data, $table) = @_;
786 0         0 my $settings = $self->{settings};
787 0         0 my $widths = _compute_widths($headers,$data);
788 1     1   12 use constant LD_H => '-';
  1         1  
  1         53  
789 1     1   3 use constant LD_V => '|';
  1         2  
  1         34  
790 1     1   3 use constant LD_X => '+';
  1         1  
  1         2011  
791 0         0 my $line = join(LD_X, map{LD_H x ($_+2)} @$widths);
  0         0  
792 0 0       0 local $settings->{NULL} = 'NULL' unless -t $fh;
793              
794             #Table
795 0 0       0 if($table) {
796 0         0 print $fh LD_X . LD_H x (length $line) . LD_X . "\n";
797 0         0 my $str = " " x int(0.5 * (length($line) - length($table)));
798 0         0 $str .= $table;
799 0         0 $str .= " " x (length($line) - length($str));
800 0         0 print LD_V . $str . LD_V . "\n";
801             }
802              
803             #Headers
804 0         0 print $fh LD_X . $line . LD_X . "\n";
805 0         0 my $str = LD_V;
806 0         0 for(my $l = 0; $l<=$#$headers; $l++)
807             {
808 0         0 $str .= " " . $headers->[$l] . " " x ($widths->[$l] - length($headers->[$l])) . " " . LD_V;
809             }
810 0         0 print $fh $str."\n";
811            
812 0         0 print $fh LD_X . $line . LD_X . "\n";
813              
814             #Data
815 0         0 foreach my $row (@$data)
816             {
817 0         0 my $str = LD_V;
818 0         0 for(my $l = 0; $l<=$#$headers; $l++)
819             {
820 0         0 my $value = $row->[$l];
821 0         0 my $len_val;
822 0 0       0 unless (defined $value) {
823 0         0 $value = $settings->{NULL};
824 0         0 $len_val = 4;
825             } else {
826 0         0 $len_val = length $value;
827             }
828 0         0 $str .= " " . $value . " " x ($widths->[$l] - $len_val) . " " . LD_V;
829             }
830 0         0 print $fh $str."\n";
831             }
832              
833 0         0 print $fh LD_X . $line . LD_X . "\n";
834             }
835              
836             sub _render_spaced
837             {
838 0     0   0 my ($self, $fh, $headers, $data) = @_;
839 0         0 my $widths = _compute_widths($headers,$data);
840 0         0 my $format = join($self->{settings}{Delimiter}, map{"%".$_."s"} @$widths)."\n";
  0         0  
841 0         0 TRACE($format);
842 0         0 printf $fh ($format, @$headers);
843 0         0 foreach(@$data)
844             {
845 0 0       0 printf $fh ($format, map {defined() ? $_ : 'NULL'} @$_);
  0         0  
846             }
847 0         0 print $fh "\n";
848             }
849              
850             sub _render_record
851             {
852 0     0   0 my ($self, $fh, $headers, $data) = @_;
853 0         0 my $settings = $self->{settings};
854 0         0 my $header_width = _max_width($headers);
855 0         0 my $line = (LD_H x $settings->{Width})."\n";
856 0 0       0 local $settings->{NULL} = 'NULL' unless -t $fh;
857 0         0 foreach my $record (@$data)
858             {
859 0         0 print $fh $line;
860 0         0 for(my $l = 0; $l<=$#$headers; $l++)
861             {
862 0         0 my $heading = $headers->[$l] . " " x ($header_width - length($headers->[$l])) . " " . LD_V . " ";
863 0         0 my $str;
864 0 0       0 if($settings->{Width} > length($heading))
865             {
866 0         0 my $room = $settings->{Width} - length($heading);
867 0 0       0 my $text = defined $record->[$l] ? $record->[$l] : $settings->{NULL};
868 0         0 my $segments = length($text)/$room;
869 0         0 for(my $i=0; $i<$segments; $i++)
870             {
871 0         0 $str .= $heading . substr($text,$i*$room,$room) . "\n"
872             }
873             }
874             else
875             {
876 0         0 $str="Terminal too narrow\n";
877             }
878 0         0 print $fh $str;
879             }
880 0         0 print $fh $line."\n";
881             }
882             }
883              
884             #######################################################################
885             #
886             # Misc private methods
887             #
888             #######################################################################
889              
890             #Dump data to a logfile
891             sub _dump_data
892             {
893 0     0   0 my($self, $sql, $filename, $delimiter) = @_;
894 0         0 my $table;
895 0 0       0 unless($sql=~/ /) #If it's just one word treat it as a table name
896             {
897 0         0 $table = $sql;
898 0         0 $sql = "select * from $table"; #Allow just table name to be passed
899             }
900 0         0 my ($headers, $data) = $self->_execute_query($sql);
901 0         0 $filename = _expand_filename($filename);
902 0 0       0 my $fh = new IO::File ">$filename" or die ("Unable to write to $filename - $!");
903 0         0 my $settings = $self->{settings};
904 0         0 my $old_delim = $self->{settings}{Delimiter};
905 0         0 eval {
906 0 0       0 $self->{settings}{Delimiter} = $delimiter if($delimiter);
907 0         0 $settings->{Logger}->($self, $fh, $headers, $data, $table);
908             };
909 0         0 $self->{settings}{Delimiter} = $old_delim; #restore before raising exception
910 0 0       0 die($@) if($@); #Rethrow exception
911 0         0 return scalar(@$data);
912             }
913              
914             #Dump all tables to a directory
915             sub _dump_tables
916             {
917 0     0   0 my($self, $dir, $delimiter) = @_;
918 0         0 $dir = _expand_filename($dir);
919 0 0       0 mkpath($dir) if(! -e $dir);
920 0         0 my @files;
921 0         0 foreach(_list_tables($self->_dbh))
922             {
923 0         0 my $filename = $dir."/".$_.".dat";
924 0         0 push @files, $filename;
925 0         0 $self->_dump_data($_, $filename, $delimiter);
926             }
927 0         0 return \@files;
928             }
929              
930             sub _execute_query
931             {
932 0     0   0 my ($self, $sql) = @_;
933              
934             #Place to hang future logic for memory-saving database cursors
935 0         0 my $class = "Tie::Rowset::InMemory";
936 0         0 TRACE("Executing $sql using $class");
937              
938             #Get a handle onto the data that looks like an array of arrays
939 0         0 my @data;
940 0         0 my $dbh = $self->_dbh;
941 0         0 tie @data, $class, $dbh, $sql, {Type => 'Array'};
942 0         0 my $object = tied @data;
943 0         0 my $headers = $object->column_names();
944              
945             #Attach filter for escaping data as it's accessed
946 0         0 my $settings = $self->{settings};
947 0 0       0 if($settings->{EscapeStrategy} eq "EscapeWhitespace")
948             {
949 0         0 _escape_whitespace($headers);
950 0         0 $object->filter(\&_escape_whitespace); #install a filter on the tied rowset
951             }
952 0 0       0 if($settings->{EscapeStrategy} eq "ShowWhitespace")
    0          
953             {
954 0         0 _show_whitespace($headers);
955 0         0 $object->filter(\&_show_whitespace); #install a filter on the tied rowset
956             }
957             elsif($settings->{EscapeStrategy} eq "UriEscape")
958             {
959 0         0 _uri_escape($headers);
960 0         0 $object->filter(\&_uri_escape); #install a filter on the tied rowset
961             }
962              
963 0         0 return($headers, \@data);
964             }
965              
966             sub _desc_table
967             {
968 0     0   0 my ($self, $table) = @_;
969 0         0 my $dbh = $self->_dbh;
970 0         0 my $driver = $dbh->{Driver}->{Name};
971 0         0 my ($headers, $data);
972 0 0       0 if($driver eq 'mysql')
973             {
974 0         0 ($headers, $data) = $self->_execute_query("desc $table");
975             }
976             else
977             {
978 0         0 $data = _deduce_columns($dbh,$table);
979 0         0 $headers=['Field','Type','Null'];
980             }
981 0         0 my $settings = $self->{settings};
982 0         0 $self->render_rowset($headers, $data, $table);
983 0 0 0     0 $self->log_rowset($headers, $data, $table) if($settings->{LogLevel} eq 'queries' || $settings->{LogLevel} eq 'all');
984             }
985              
986             sub _dbh
987             {
988 52     52   39 my $self = shift;
989 52 50       82 if(_is_connected($self->{dbh})) {
990 0         0 return $self->{dbh};
991             } else {
992 52         68 $self->disconnect();
993 52         91 return undef;
994             }
995             }
996              
997             #######################################################################
998             #
999             # Private routines
1000             #
1001             #######################################################################
1002              
1003             sub _renderer {
1004 2     2   5 my $renderer = shift;
1005 2 50 33     5 if(defined $renderer && ref $renderer ne 'CODE') {
1006 0   0     0 $renderer = $Renderers{$renderer} || die("Unrecognised renderer: $renderer\n");
1007             }
1008 2         50 return $renderer;
1009             }
1010              
1011             sub _is_connected
1012             {
1013 105 50 33 105   219 if(defined $_[0] && ref $_[0] && UNIVERSAL::isa($_[0], 'DBI::db') && $_[0]->ping) {
      33        
      0        
1014 0         0 return 1;
1015             } else {
1016 105         182 return 0;
1017             }
1018             }
1019              
1020             #
1021             # Table manipulation
1022             #
1023              
1024             #List tables and their size
1025             sub _summarise_tables
1026             {
1027 0     0   0 my($dbh) = @_;
1028 0         0 my @results;
1029 0         0 foreach my $table(_list_tables($dbh))
1030             {
1031 0         0 my $sth = $dbh->prepare("select count(*) from $table");
1032 0         0 $sth->execute();
1033 0         0 my ($rows) = $sth->fetchrow_array();
1034 0         0 push @results,[$table, $rows];
1035             }
1036 0         0 return \@results;
1037             }
1038              
1039             sub _list_tables
1040             {
1041 0     0   0 my($dbh) = @_;
1042 0         0 my $driver = $dbh->{Driver}->{Name};
1043 0 0       0 if($driver eq 'Oracle')
1044             {
1045 0         0 my $sth = $dbh->prepare("select table_name from cat where table_type=?");
1046 0         0 $sth->execute('TABLE');
1047 0         0 my $tables = $sth->fetchall_arrayref();
1048 0         0 return map {$_->[0]} @$tables;
  0         0  
1049             }
1050             else
1051             {
1052             #Generic DBI function
1053 0         0 return $dbh->tables();
1054             }
1055             }
1056              
1057              
1058             sub _deduce_columns
1059             {
1060 0     0   0 my ($dbh,$table) = @_;
1061 0         0 my $sth = $dbh->prepare("select * from $table where 0=1");
1062 0         0 $sth->execute();
1063 0         0 my @names = @{$sth->{NAME}};
  0         0  
1064 0         0 my (@types, @nullable);
1065             eval
1066 0         0 {
1067 0         0 my @null = ("NO","YES","");
1068 0         0 my @type_codes = @{$sth->{TYPE}};
  0         0  
1069 0         0 my @precision = @{$sth->{PRECISION}};
  0         0  
1070 0         0 @nullable = map{$null[$_]} @{$sth->{NULLABLE}};
  0         0  
  0         0  
1071 0         0 $sth->finish;
1072              
1073 0         0 foreach(@type_codes)
1074             {
1075 0         0 my $info = $dbh->type_info($_);
1076 0         0 my $type = $info->{TYPE_NAME};
1077 0         0 my $precision = shift @precision;
1078 0 0       0 $type.="($precision)" if(defined $precision);
1079 0         0 push @types, $type;
1080             }
1081             };
1082 0         0 my @data = map {[$_, shift @types, shift @nullable]} @names;
  0         0  
1083 0         0 return \@data;
1084             }
1085              
1086             #
1087             # History
1088             #
1089             sub _load_history {
1090 1     1   1 my $filename = shift;
1091 1         3 local *FH;
1092 1         1 my @hist;
1093 1 50       2 open (FH, _expand_filename($filename)) or die("Unable to load history from $filename - $!");
1094 1         9 while () {
1095 2         3 chomp; push @hist, $_;
  2         6  
1096             }
1097 1         6 close FH;
1098 1         5 TRACE("Loaded ".scalar @hist." items from $filename");
1099 1         3 return \@hist;
1100             }
1101              
1102             sub _save_history {
1103 1     1   1 my $history = shift;
1104 1   50     3 my $filename = shift || die("You must specify a file to save the history to");
1105 1   50     3 my $max_size = shift || HISTORY_SIZE;
1106 1 50       3 my $max_hist = scalar @$history >= $max_size ? $max_size : scalar @$history;
1107 1         3 TRACE("Saving $max_hist items to $filename");
1108 1         4 my @hist = @$history[-$max_hist..-1];
1109 1         2 local *FH;
1110 1 50       3 open (FH, "> " . _expand_filename($filename)) or die("Unable to save history to $filename - $!");
1111 1         7 print FH $_, $/ for @hist;
1112 1         23 close FH;
1113             }
1114              
1115             sub _recode
1116             {
1117 0     0   0 my ($recoder, @rows) = @_;
1118 0         0 foreach (@rows)
1119             {
1120 0         0 my $init = $_;
1121 0 0       0 die $recoder->getError if $recoder->getError;
1122 0 0       0 $recoder->recode($_) or die $recoder->getError;
1123 0         0 TRACE("recoded FROM [$init] to [$_]");
1124             }
1125 0         0 return @rows;
1126             }
1127              
1128             sub _escape_whitespace
1129             {
1130 0     0   0 my $row = shift;
1131 0         0 foreach(@$row)
1132             {
1133 0         0 s/\r/\\r/g;
1134 0         0 s/\n/\\n/g;
1135 0         0 s/\t/\\t/g;
1136             }
1137 0         0 return $row;
1138             }
1139              
1140             sub _show_whitespace
1141             {
1142 0     0   0 my $row = shift;
1143 0         0 $row = _escape_whitespace($row);
1144 0         0 foreach(@$row)
1145             {
1146 0         0 s/ /./g; #Also convert spaces to dots
1147             }
1148 0         0 return $row;
1149             }
1150              
1151             sub _uri_escape
1152             {
1153 0     0   0 my $row = shift;
1154 0         0 my @new = map {uri_escape($_)} @$row;
  0         0  
1155 0         0 return \@new;
1156             }
1157              
1158             sub _compute_widths
1159             {
1160 0     0   0 my ($headers,$data) = @_;
1161 0         0 my @widths = map {length $_} @$headers;
  0         0  
1162 0         0 foreach my $row(@$data)
1163             {
1164 0         0 for(0..$#widths)
1165             {
1166 0 0       0 my $len = defined $row->[$_] ? length($row->[$_]) : length 'NULL';
1167 0 0       0 $widths[$_] = $len if($len > $widths[$_]);
1168             }
1169             }
1170 0         0 return \@widths;
1171             }
1172              
1173             sub _max_width
1174             {
1175 0     0   0 my ($list) = @_;
1176 0         0 my $width = 0;
1177 0         0 foreach (@$list)
1178             {
1179 0         0 my $len = length($_);
1180 0 0       0 $width = $len if($len > $width);
1181             }
1182 0         0 return $width;
1183             }
1184              
1185              
1186             sub _expand_filename {
1187 3     3   4 my $file = shift;
1188 3 50       9 if ($file =~ s/^~([^\/]*)//)
1189             {
1190 0 0       0 my $home = $1 ? ((getpwnam ($1)) [7]) : $ENV{HOME};
1191 0         0 $file = $home . $file;
1192             }
1193 3         79 return $file;
1194             }
1195              
1196             # stubs for Log::Trace
1197       17 0   sub TRACE{}
1198       0 0   sub DUMP{}
1199              
1200             ############################################################################################
1201             #
1202             # Inlined package for the time being whilst Tie::Rowset is being worked on
1203             #
1204             ############################################################################################
1205              
1206             package Tie::Rowset::InMemory;
1207              
1208 1     1   7 use strict;
  1         1  
  1         23  
1209 1     1   3 use Carp;
  1         1  
  1         609  
1210              
1211             ##############################################
1212             # TIE interface
1213             ##############################################
1214              
1215             sub TIEARRAY
1216             {
1217 0     0     my ($class, $dbh, $sql, $options) = @_;
1218 0 0         $options = {} unless defined $options;
1219 0           my $params = $options->{params};
1220             my $self = {
1221             'dbh' => $dbh,
1222             'sql' => $sql,
1223             'params' => defined $params? $params : [],
1224             'type' => $options->{Type} || 'Hash',
1225             'filter' => $options->{Filter},
1226 0 0 0       'count' => undef,
1227             };
1228 0           bless $self, $class;
1229 0           TRACE(__PACKAGE__." constructor");
1230 0           return $self;
1231             }
1232              
1233             sub DESTROY
1234             {
1235 0     0     my $self = shift;
1236 0 0         $self->{sth}->finish() if defined($self->{sth});
1237             }
1238              
1239             sub FETCH
1240             {
1241 0     0     my ($self, $index) = @_;
1242 0           TRACE("FETCH $index");
1243 0 0         $self->_execute_query() unless $self->{data};
1244 0 0         croak("index $index is out of bounds - rowset only has " . scalar @{$self->{data}}." elements") if($index+1 > scalar @{$self->{data}});
  0            
  0            
1245 0           my $rv = $self->{data}->[$index];
1246 0 0         $rv = $self->{filter}->($rv) if defined $self->{filter}; #optionally filter
1247 0           DUMP("Fetch $index", $rv);
1248 0           return $rv;
1249             }
1250              
1251             sub FETCHSIZE
1252             {
1253 0     0     my $self = shift;
1254 0 0         $self->_execute_query() unless $self->{data};
1255 0           TRACE("Fetch size - " . scalar @{$self->{data}});
  0            
1256 0           return scalar @{$self->{data}};
  0            
1257             }
1258              
1259             ##############################################
1260             # Non-tied OO interface (access via tied)
1261             ##############################################
1262              
1263             sub column_names
1264             {
1265 0     0     my $self = shift;
1266 0 0         $self->_execute_query() unless $self->{headers};
1267 0           return $self->{headers};
1268             }
1269              
1270             sub filter
1271             {
1272 0     0     my ($self, $filter) = @_;
1273 0 0         $self->{filter} = $filter if defined($filter);
1274 0           return $self->{filter};
1275             }
1276              
1277             ##############################################
1278             # private methods
1279             ##############################################
1280              
1281             sub _execute_query
1282             {
1283 0     0     my $self = shift;
1284             eval
1285 0           {
1286 0           my $sth = $self->{dbh}->prepare($self->{sql});
1287 0           $sth->execute(@{$self->{params}});
  0            
1288 0           $self->{headers} = $sth->{NAME};
1289 0 0         if($self->{type} eq 'Array') {
1290 0           $self->{data} = $sth->fetchall_arrayref();
1291             } else {
1292 0           my @loh;
1293 0           while(my $hashref = $sth->fetchrow_hashref)
1294             {
1295 0           push @loh, { %$hashref };
1296             }
1297 0           $self->{data} = \@loh;
1298             }
1299             };
1300 0 0         if($@)
1301             {
1302 0           $@ =~ s/\n$//;
1303 0           die("$@ sql=$self->{sql}"); #Decorate error messages with SQL
1304             }
1305             }
1306              
1307             # stubs for Log::Trace
1308       0     sub TRACE{}
1309       0     sub DUMP{}
1310              
1311              
1312             =head1 NAME
1313              
1314             SQL::Shell - command interpreter for DBI shells
1315              
1316             =head1 SYNOPSIS
1317              
1318             use SQL::Shell;
1319            
1320             #Initialise and configure
1321             my $sqlsh = new SQL::Shell(\%settings);
1322             $sqlsh->set($setting, $new_value);
1323             $value = $sqlsh->get($setting);
1324            
1325             #Interpret commands
1326             $sqlsh->execute_command($command);
1327             $sqlsh->run_script($filename);
1328              
1329             =head1 DESCRIPTION
1330              
1331             SQL::Shell is a command-interpreter API for building shells and batch scripts.
1332             A command-line interface with readline support - sqlsh.pl - is included as part of the CPAN distribution. See for a user guide.
1333              
1334             SQL::Shell offers features similar to the mysql or sql*plus client programs but is database independent.
1335             The default command syntax is arguably more user-friendly than dbish not requiring any go, do or slashes to fire SQL statements at the database.
1336              
1337             Features include:
1338              
1339             =over 4
1340              
1341             =item * issuing common SQL statements by simply typing them
1342              
1343             =item * command history
1344              
1345             =item * listing drivers, datasources, tables
1346              
1347             =item * describing a table or the entire schema
1348              
1349             =item * dumping and loading data to/from delimited text files
1350              
1351             =item * character set conversion when loading data
1352              
1353             =item * logging of queries, results or all commands to file
1354              
1355             =item * a number of formats for display/logging data (sql, xml, delimited, boxed)
1356              
1357             =item * executing a series of commands from a file
1358              
1359             =back
1360              
1361             You can also install custom commands, rendering formats and command history mechanisms.
1362             All the commands run by the interpreter are available via the API so if you don't like the default command syntax you can replace the command regexes with your own.
1363            
1364             It's been developed and used in anger with Oracle and mysql but should work with any database with a DBD:: driver.
1365              
1366             =head1 METHODS
1367              
1368             =over 4
1369              
1370             =item $sqlsh = new SQL::Shell(\%settings);
1371              
1372             Constructs a new object and initialises it with a set of settings.
1373             See L for a complete list.
1374              
1375             =item $sqlsh->set($setting, $new_value)
1376              
1377             Changes a setting once the object has been constructed.
1378             See L for a complete list.
1379              
1380             =item $value = $sqlsh->get($setting)
1381              
1382             Fetches a setting.
1383             See L for a complete list.
1384              
1385             =back
1386              
1387             =head2 Commands
1388              
1389             =over 4
1390              
1391             =item $sqlsh->execute_cmd($command)
1392              
1393             Executes a command ($command is a string).
1394              
1395             Returns 1 if the command was successful.
1396             Returns 0 if the command was unsuccessful.
1397              
1398             =item $sqlsh->run_script($filename)
1399              
1400             Executes a sequence of commands in a file.
1401             Dies if there is a problem.
1402              
1403             =item $sqlsh->install_cmds(\%additional_commands)
1404              
1405             %additional_commands should contain a mapping of regex to coderef.
1406             See L for more information.
1407              
1408             =item $sqlsh->uninstall_cmds(\@commands)
1409              
1410             @additional_commands should contain a list of regexes to remove.
1411             If uninstall_cmds is called with no arguments, all commands will be uninstalled.
1412              
1413             =item $sqlsh->set_param($param, $value)
1414              
1415             Equivalent to the "set " command.
1416             In many cases this will affect the internal settings accessible through the C and C methods.
1417              
1418             =back
1419              
1420             =head2 Renderers
1421              
1422             =over 4
1423              
1424             =item $sqlsh->install_renderers(\%additional_renderers)
1425              
1426             %additional_renderers should contain a mapping of renderer name to coderef.
1427             See L for more information.
1428              
1429             =item $sqlsh->uninstall_renderers(\@renderers)
1430              
1431             @renderers should contain a list of renderer names to remove.
1432             If uninstall_renderers is called with no arguments, all renderers will be uninstalled.
1433              
1434             =item $sqlsh->render_rowset(\@headers, \@data, $table)
1435              
1436             Calls the current renderer (writes to STDOUT)
1437              
1438             =item $sqlsh->log_rowset(\@headers, \@data, $table)
1439              
1440             Calls the current logger
1441              
1442             =back
1443              
1444             =head2 Database connection
1445              
1446             =over 4
1447              
1448             =item $dsn = $sqlsh->connect($dsn, $user, $pass)
1449              
1450             Connects to a DBI datasource.
1451             Equivalent to issuing the "connect $dsn $user $pass" command.
1452              
1453             =item $sqlsh->disconnect()
1454              
1455             Disconnects if connected.
1456             Equivalent to issuing the "disconnect" command.
1457              
1458             =item $bool = $sqlsh->is_connected()
1459              
1460             Check if we're connected to the database.
1461              
1462             =item $string = $sqlsh->dsn()
1463              
1464             The datasource we're currently connected as - undef if not connected.
1465              
1466             =back
1467              
1468             =head2 History manipulation
1469              
1470             =over 4
1471              
1472             =item $arrayref = $sqlsh->load_history($filename)
1473              
1474             Loads a sequence of commands from a file into the command history.
1475             Equivalent to "load history from $filename".
1476              
1477             =item $sqlsh->clear_history()
1478              
1479             Clears the command history.
1480             Equivalent to "clear history".
1481              
1482             =item $sqlsh->save_history($filename, $size)
1483              
1484             Saves the command history to a file in a format suitable for C and C.
1485             Equivalent to "save history to $filename", except the maximum number of items can be specified.
1486             $size is optional - if not specified defaults to the MaxHistory setting.
1487              
1488             =item $sqlsh->show_history()
1489              
1490             Displays the command history.
1491             Equivalent to "show history".
1492              
1493             =back
1494              
1495             =head2 Logging
1496              
1497             =over 4
1498              
1499             =item $sqlsh->enable_logging($level, $file)
1500              
1501             Enables logging to a file.
1502             $level should be all, queries or commands.
1503             Equivalent to "log $level $file".
1504              
1505             =item $sqlsh->disable_logging()
1506              
1507             Disables logging to a file.
1508             Equivalent to "no log".
1509              
1510             =back
1511              
1512             =head2 Querying
1513              
1514             =over 4
1515              
1516             =item $sqlsh->show_drivers()
1517              
1518             Outputs a list of database drivers. Equivalent to "show drivers".
1519              
1520             =item $sqlsh->show_datasources($driver)
1521              
1522             Outputs a list of datasources for a driver. Equivalent to "show datasources $driver".
1523              
1524             =item $sqlsh->show_dbh($property)
1525              
1526             Outputs a property of a database handle. Equivalent to "show \$dbh $property".
1527              
1528             =item $sqlsh->show_schema()
1529              
1530             Equivalent to "show schema".
1531              
1532             =item $sqlsh->show_tables()
1533              
1534             Displays a list of tables with row counts. Equivalent to "show tables".
1535              
1536             =item $sqlsh->describe($table)
1537              
1538             Displays the columns in the table. Equivalent to "describe $table".
1539              
1540             =item $sqlsh->run_query($sql)
1541              
1542             Displays the rowset returned by the query. Equivalent to execute_cmd with a select or explain statement.
1543              
1544             =back
1545              
1546             =head2 Modifying data
1547              
1548             =over 4
1549              
1550             =item $sqlsh->do_sql($sql)
1551              
1552             Executes a SQL statement that modifies the database. Equivalent to execute_cmd with a DML or DDL statement.
1553              
1554             =item $sqlsh->begin_work()
1555              
1556             Starts a transaction. Equivalent to "begin work".
1557              
1558             =item $sqlsh->commit()
1559              
1560             Commits a transaction. Equivalent to "commit".
1561              
1562             =item $sqlsh->rollback()
1563              
1564             Rolls back a transaction. Equivalent to "rollback".
1565              
1566             =item $sqlsh->wipe_tables()
1567              
1568             Blanks all the tables in the database.
1569             Will prompt for confirmation if the Interactive setting is enabled.
1570             Equivalent to "wipe tables".
1571              
1572             =back
1573              
1574             =head2 Loading and dumping data
1575              
1576             =over 4
1577              
1578             =item $sqlsh->dump_data($source, $filename, $delimiter)
1579              
1580             Dumps data from a table or query into a delimited file.
1581             $source should either be a table name or a select query.
1582             This is equivalent to the "dump data" command.
1583              
1584             =item $sqlsh->load_data($filename, $table, $delimiter, $uri_decode, $charset_from, $charset_to)
1585              
1586             Loads data from a delimited file into a database table.
1587             $uri_decode is a boolean value - if true the data will be URI-decoded before being inserted.
1588             $charset_from and $charset_to are character set names understood by Locale::Recode.
1589             This is equivalent to the "load data" command.
1590              
1591             =item $sqlsh->show_charsets()
1592              
1593             Lists the character sets supported by the recoding feature of "load data". Equivalent to "show charsets".
1594              
1595             =back
1596              
1597             =head1 CUSTOMISING
1598              
1599             =head2 INSTALLING CUSTOM COMMANDS
1600              
1601             The coderef will be passed the $sqlsh object followed by each argument captured by the regex.
1602              
1603             my %additional_commands = (
1604             qr/^hello from (\.*)/ => sub {
1605             my ($self, $name) = @_;
1606             print "hi there $name\n";
1607             });
1608              
1609             To install this:
1610              
1611             $sqlsh->install_cmds(\%additional_commands)
1612              
1613             Then in sqlsh:
1614              
1615             > hello from John
1616             hi there John
1617              
1618             =head2 INSTALLING CUSTOM RENDERERS
1619              
1620             Renderers are coderefs which are passed the following arguments:
1621              
1622             $sqlsh - the SQL::Shell object
1623             $fh - the filehandle to render to
1624             $headers - an arrayref of column headings
1625             $data - an arrayref of arrays containing the data (row major)
1626             $table - the name of the table being rendered (not defined in all contexts)
1627              
1628             Here's an example to render data in CSV format:
1629              
1630             sub my_renderer {
1631             my ($sqlsh, $fh, $headers, $data, $table) = @_;
1632             my $delim = ",";
1633             print $fh "#Dump of $table" if($table); #Assuming our CSV format support #-style comments
1634             print $fh join($delim, @$headers)."\n";
1635             foreach my $row (@$data)
1636             {
1637             print $fh join($delim, @$row)."\n";
1638             }
1639             }
1640              
1641             To install this:
1642              
1643             $sqlsh->install_renderers({'csv' => \&my_renderer});
1644              
1645             Then in sqlsh:
1646              
1647             > set display-mode csv
1648              
1649             =head2 INSTALLING A CUSTOM HISTORY MECHANISM
1650              
1651             You can install a custom history recording mechanism by overriding the GetHistory, SetHistory and AddHistory callbacks which should take the following arguments and return values:
1652              
1653             =over 4
1654              
1655             =item $arrayref = $GetHistorySub->()
1656              
1657             =item $SetHistorySub->($arrayref)
1658              
1659             =item $AddHistorySub->($string)
1660              
1661             =back
1662              
1663             An example:
1664              
1665             my $term = new Term::ReadLine "My Shell";
1666             my $autohistory = $term->Features()->{autohistory};
1667             my $sqlsh = new SQL::Shell({
1668             'GetHistory' => sub {[$term->GetHistory()]});
1669             'SetHistory' => sub {my $history = shift; $term->SetHistory(@$history)});
1670             'AddHistory' => sub {my $cmd = shift; $term->addhistory($cmd) if !$autohistory});
1671             });
1672              
1673             =head1 SETTINGS
1674              
1675             The following settings can only be set through the constructor or the C method:
1676              
1677             NAME DESCRIPTION DEFAULT
1678             GetHistory Callback to fetch history sub {return \@history}
1679             SetHistory Callback to set history sub {my $n = shift; @history = @$n}
1680             AddHistory Callback to add cmd to history sub {push @history, shift()}
1681             MaxHistory Maximum length of history to save $ENV{HISTSIZE} || $ENV{HISTFILESIZE} || 50
1682             Interactive Should SQL::Shell ask questions? 0
1683             Verbose Should SQL::Shell print messages? 0
1684             NULL How to display null values NULL
1685              
1686             The following are also affected by the C method or the "set" command:
1687              
1688             NAME DESCRIPTION DEFAULT
1689             Renderer Current renderer for screen \&_render_box
1690             Logger Current renderer for logfile \&_render_delimited
1691             Delimiter Delimiter for delimited format \t
1692             Width Width used for record display 80
1693             LogLevel Log what? all|commands|queries undef
1694             EscapeStrategy UriEscape|EscapeWhitespace|ShowWhitespace undef
1695             AutoCommit Commit each statement 0
1696             LongTruncOk OK to truncate LONG datatypes? 1
1697             LongReadLen Amount read from LONG datatypes 512
1698             MultiLine Allows multiline sql statements 0
1699              
1700             =head1 COMMANDS
1701            
1702             show drivers
1703             show datasources
1704             connect [ ] - connect to DBI DSN
1705             disconnect - disconnect from the DB
1706            
1707             show tables - display a list of tables
1708             show schema - display the entire schema
1709             desc - display schema of table
1710            
1711             show $dbh - show a database handle object.
1712             some examples:
1713             show $dbh Name
1714             show $dbh LongReadLen
1715             show $dbh mysql_serverinfo (mysql only)
1716            
1717             set display-mode delimited|spaced|box|record|sql|xml - query display mode
1718             set log-mode delimited|spaced|box|record|sql|xml - set the query log mode
1719             set delimiter - set the column delimiter (default is tab)
1720             set escape show-whitespace|escape-whitespace|uri-escape|off
1721             - show-whitespace is just for looking at
1722             - escape-whitespace is compatible with enter-whitespace
1723             - uri-escape is compatible with uri-decode (load command)
1724             set enter-whitespace on|off - allow \r \n and \t in SQL statements
1725             set uri-encode on|off - allow all non ascii characters to be escaped
1726             set auto-commit on|off - commit after every statement (default is OFF)
1727             set longtruncok on|off - See DBI/LongTruncOk (default is on)
1728             set longreadlen - See DBI/LongReadLen (default is 512)
1729             set multiline on|off - multiline statements ending in ; (default is off)
1730             set tracing on|off|deep - debug sqlsh using Log::Trace (default is off)
1731            
1732             log (queries|commands|all) - start logging to
1733             no log - stop logging
1734            
1735             select ...
1736             insert ...
1737             update ...
1738             create ...
1739             alter ...
1740             drop ...
1741             grant ...
1742             revoke ...
1743             begin_work
1744             commit
1745             rollback
1746            
1747             load into (delimited by foo) (uri-decode) (from bar to baz)
1748             - load delimited data from a file
1749             - use uri-decode if file includes uri-encoded data
1750             - from, to can take character set to recode data e.g. from CP1252 to UTF-8
1751             show charsets - display available character sets
1752             dump into (delimited by foo) - dump delimited data
1753             dump into (delimited by foo) - dump delimited data
1754             dump all tables into (delimited by foo) - dump delimited data
1755             wipe tables - remove all data from DB (leaving tables empty)
1756            
1757             show history - display command history
1758             clear history - erases the command history
1759             save history to - saves the command history
1760             load history from - loads the command history
1761             execute - run a set of SQL or sqlsh commands from a file
1762            
1763             =head1 VERSION
1764              
1765             $Revision: 1.14 $ on $Date: 2006/12/05 14:31:33 $ by $Author: andreww $
1766              
1767             =head1 AUTHOR
1768              
1769             John Alden with contributions by Simon Flack and Simon Stevenson
1770              
1771             =head1 COPYRIGHT
1772              
1773             (c) BBC 2006. This program is free software; you can redistribute it and/or modify it under the GNU GPL.
1774              
1775             See the file COPYING in this distribution, or http://www.gnu.org/licenses/gpl.txt
1776            
1777             =cut