| 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 |  |  |  |