~~startperl~~ # $Id: pmsql.in,v 1.1.1.1 1999/07/13 08:14:45 joe Exp $ my $version = '~~nodbd_version~~'; BEGIN {require 5.002;} # use strict; # only for testing. Unfriendly for the user-eval()s # $^W = 1; # too early for readline 0.8 $driver = '~~nodbd_driver~~'; eval "use $driver ()"; use Term::ReadLine; use Carp (); # term my $term = Term::ReadLine->new("$driverPerl Monitor"); # prompt my $prompt = "p" . lc ($driver); my $attribs = $term->Attribs; if ($term->ReadLine eq "Term::ReadLine::Gnu") { $attribs->{'attempted_completion_function'} = \&complete_gnu; $attribs->{'completion_entry_function'} = $attribs->{'list_completion_function'}; } else { $readline::rl_completion_function = 'main::complete'; } $^W = 1; # $SIG{'__WARN__'} = sub { warn Carp::longmess(@_); }; # typelabels my(%typelabel); ~#if#~ ('~~dbd_driver~~' eq 'mysql') @typelabel{ Mysql::FIELD_TYPE_BLOB(), Mysql::FIELD_TYPE_CHAR(), Mysql::FIELD_TYPE_DECIMAL(), Mysql::FIELD_TYPE_DATE(), Mysql::FIELD_TYPE_DATETIME(), Mysql::FIELD_TYPE_DOUBLE(), Mysql::FIELD_TYPE_FLOAT(), Mysql::FIELD_TYPE_INT24(), Mysql::FIELD_TYPE_LONGLONG(), Mysql::FIELD_TYPE_LONG_BLOB(), Mysql::FIELD_TYPE_LONG(), Mysql::FIELD_TYPE_NULL(), Mysql::FIELD_TYPE_SHORT(), Mysql::FIELD_TYPE_STRING(), Mysql::FIELD_TYPE_TINY_BLOB(), Mysql::FIELD_TYPE_TIMESTAMP(), Mysql::FIELD_TYPE_TIME(), Mysql::FIELD_TYPE_VAR_STRING() } = qw( blob char decimal date datetime double float int24 longlong longblob long null short string tinyblob timestamp time varstring ); ~#else#~ my (@typenames); for (qw/INT CHAR REAL IDENT IDX TEXT DATE UINT MONEY TIME SYSVAR/) { my $type = 'Msql::' . $_ . '_TYPE'; push(@typenames, (eval "\&$type") || 999); } @typelabel{ @typenames } = qw( int char real ident index text date uint money time sys ); ~#endif#~ # host my $host = ""; if (@ARGV && $ARGV[0] eq "-h") { shift; $host = shift or die usage(); } # Less my $Less; { my @path = split ":", $ENV{PATH}; if (exists($ENV{P~~uc_dbd_driver~~_PAGER})) { $Less = $ENV{P~~uc_dbd_driver~~_PAGER}; } else { $Less = $ENV{PAGER} || find_exe("less",[@path]) || find_exe("more",[@path]) || ""; } } # database my $database = $ARGV[0] || ""; # fancy output/msqlexport functionality $fancy_output = 1; $sepchar = ','; $quote = '"'; $escape = $quote; # # Greetings # { my ($rl_package) = $term->ReadLine; my $rl_avail; if ($rl_package eq "Term::ReadLine::Perl" || $rl_package eq "Term::ReadLine::readline_pl" || $rl_package eq "Term::ReadLine::Gnu") { $rl_avail = "enabled"; } else { $rl_avail = "available (get Term::ReadKey and" . " Term::ReadLine::[Perl|GNU])"; } ~#if#~ ('~~dbd_driver~~' eq 'mysql') printf ("$prompt -- interactive Mysql monitor version $version\n"); ~#else#~ printf ("$prompt -- interactive mSQL monitor version $version\n"); ~#endif#~ print "Readline support $rl_avail\n\n"; } # # Debugging # my %Debug; #table 1 #complete 2 #table_or_field 4 my $Debug = 0; # 1 | 2 | 4; # # Shell # my($indexarg, $indexdes); ~#if#~ ('~~dbd_driver~~' eq 'mysql') $indexarg = ""; $indexdes = " or tables"; ~#else#~ if (!defined &Msql::IDX_TYPE) { $indexarg = ""; $indexdes = " or tables"; } else { $indexarg = " [index]"; $indexdes = ", tables or indices"; } ~#endif#~ while ( defined ($_ = $term->readline("$prompt> ")) ) { # # Leading blanks? No # s/^\s+//; next if /^$/; # # Let them eval a piece of perl # if (/^\!/) { $term->addhistory($_) if /\S/; s/^\!//; eval($_); warn $@ if $@; print "\n"; next; # # Give some advice # } elsif (/^\?/) { print qq{ ho[st] Set default host (current is "$host") da[tabase] Set default database (current is "$database") re[lshow] [-h host] [database] [table]$indexarg describe databases$indexdes and set default host and database ! eval string in perl ? print this message q[uit] leave $prompt query default database on default host }; next; } # # Look closer what they said # my($command,$arg) = /^(\S+)(.*)/; my(@arg) = split " ", $arg; next unless defined $command; if ($command =~ /^da(t(a(b(a(s(e)?)?)?)?)?)?$/i) { # DATABASE $database = $arg[0] if $arg[0] gt ""; print qq{Database set to "$database"\n}; } elsif ($command =~ /^e(s(c(a(p(e)?)?)?)?)?$/i) { # ESCAPE printf("Escape: %s\n", set_quote_or_separator(\$escape, @arg)); } elsif ($command =~ /^f(a(n(c(y)?)?)?)?$/i) { # FANCY printf("Fancy output is %s.\n", fancy(@arg) ? "on" : "off"); } elsif ($command =~ /^ho(s(t)?)?$/i) { # HOST $host = $arg[0]; print qq{Host set to "$host"\n}; } elsif ($command =~ /^quo(t(e)?)?$/i) { # QUOTE printf("Quote: %s\n", set_quote_or_separator(\$quote, @arg)); } elsif ($command =~ /^re(l(s(h(o(w)?)?)?)?)?$/i) { # RELSHOW print relshow(@arg); } elsif ($command =~ /^sep(a(r(a(t(o(r)?)?)?)?)?)?$/i) { # SEPARATOR printf("Separator: %s\n", set_quote_or_separator(\$sepchar, @arg)); } elsif ($command =~ /^q(u(i(t)?)?)?$/i) { # QUIT print "Goodbye\n"; last; } else { # This is a query unless ($database) { print "No default database defined\n"; next; } my $Db = $driver->connect($host,$database) or next; s/\\[qgp]$//; $::Q = $Db->query($_) or next; if ($fancy_output) { print "Query ok\n"; } if (ref $::Q) { ~#if#~ ('~~dbd_driver~~' ne 'mysql') $::Q->optimize(1); ~#endif#~ if ($Less && ((lc $Less) ne 'stdout')) { open OUT, "| $Less"; } else { open OUT, ">&STDOUT"; } if ($fancy_output) { print OUT $::Q->as_string; } else { print OUT sep_out($sepchar); } close OUT; } } } exit; # # Subroutines # sub complete { my($word,$line,$pos) = @_; $word ||= ""; $line ||= ""; $pos ||= 0; print STDERR "complete line[$line] pos[$pos]" if $Debug & 2; $line =~ s/^\s*//; return $pos == 0 ? grep /^$word/i, ('!', '?', 'create', 'database', 'escape', 'delete from', 'drop table', 'fancy', 'host', 'insert into', 'quit', 'quote', 'relshow', 'separator', 'select', 'update') : $line =~ /^[\!\?qch]/i ? () : # quit, create, host $line =~ /^da/i ? complete_database($word) : # database $line =~ /^de/i ? complete_table_or_field($word,$line) : # delete $line =~ /^dr/i ? complete_table($word,$line) : # drop $line =~ /^e/i ? complete_option($word,$line) : # escape $line =~ /^f/i ? complete_option($word,$line) : # fancy $line =~ /^in/i ? complete_table_or_field($word,$line) : # insert $line =~ /^quo/i ? complete_option($word,$line) : # quote $line =~ /^re/i ? complete_for_relshow($word,$line) : # relshow $line =~ /^se/i ? complete_table_or_field($word,$line) : # select $line =~ /^sep/i ? complete_option($word,$line) : # separator $line =~ /^up/i ? complete_table_or_field($word,$line) : (); # update } sub complete_gnu(@) { my (@poss) = complete(@_); my $attribs = $term->Attribs; $attribs->{'completion_word'} = \@poss; return; } sub complete_database { my($word) = shift; grep /^\Q$word/, $driver->connect($host)->listdbs; } sub complete_option { my($word,$line) = @_; if ($line =~ /^fancy/) { if ($fancy_output) { return "off"; } else { return "on"; } } elsif ($line =~ /^(separator|quote|escape)/) { grep /^\Q$word/, qw(space tab null); } else { ''; } } sub complete_for_relshow { my($word,$line) = @_; my @t = split " ", $line; # system '/usr/sbin/sfplay', '/usr/adm/alarmsnd/woodblock.aiff'; # @::Gl = ([@t]); if (@t==4 && $word eq $t[3] || @t==3 && $word eq "") { my $sth = $driver->connect($host, $t[1])->listfields($t[2]); my(@idx) = $sth->listindices; my(@fitidx) = grep /^\Q$word/, @idx; # push @::Gl, $sth, [@idx], [@fitidx]; # for debugging only return @fitidx; } else { complete_table_or_field($word,$line); } } sub complete_table { my($word,$line) = @_; my($db) = $line =~ /^r\w+\s+(\w+)/; print STDERR "word[$word] line[$line] db[$db]" if $Debug & 1; $db ||= $database; return () unless $db; grep /^\Q$word/, $driver->connect($host, $db)->listtables; } sub complete_table_or_field { my($word,$line) = @_; print STDERR "word[$word] line[$line]" if $Debug & 4; return complete_database($word) if $line =~ /^r\w+\s+\Q$word\E$/; return complete_table($word,$line) if $line =~ /^[ds].*\sfrom\s+\Q$word\E$/ || # delete, select $line =~ /^u\w+\s+\Q$word\E$/ || # update $line =~ /^r\w+\s+\w+\s+\Q$word\E$/ || # relshow $line =~ /^i.*\sinto\s+\Q$word\E$/ # insert ; return () unless $database; my ($table) = $1 if $line =~ /^[ds].*\sfrom\s+(\w+)/ || # delete, select $line =~ /^u\w+\s+(\w+)/ || # update $line =~ /^r\w+\s+\w+\s+(\w+)/ || # relshow $line =~ /^i.*\sinto\s+(\w+)/ # insert ; my(@table) = $table ? $table : $driver->connect($host, $database)->listtables; my($db,%fields,@fields) = $driver->connect($host, $database); for $table (@table) { my $st = $db->listfields($table) or next; @fields = $st->name; @fields{@fields} = (1) x @fields; } return sort grep /^\Q$word/, keys %fields; } sub find_exe { my($exe,$path) = @_; my($dir); for $dir (@$path) { my $abs = "$dir/$exe"; if (-x $abs) { return $abs; } } } sub longest { my $l = 0; for (@_) { $l=length if length > $l } $l; } sub fancy { my $arg = shift || ''; if ($arg eq 'off') { $fancy_output = 0; } elsif ($arg eq 'on') { $fancy_output = 1; } $fancy_output; } sub set_quote_or_separator { my $ref = shift; my ($arg, $ret); if (defined($arg = shift)) { if ($arg eq 'space') { $$ref = " "; } elsif ($arg eq 'tab') { $$ref = "\t"; } elsif ($arg eq 'null' || $arg eq 'off') { undef($$ref); } else { $$ref = join('',$arg,@_); } } if (!defined($$ref)) { $ret = 'off'; } elsif ($$ref eq ' ') { $ret = 'space'; } elsif ($$ref eq '\t') { $ret = 'tab'; } else { $ret = "'$$ref'"; } $ret; } sub relshow { if (@_ && $_[0] eq "-h") { shift @_; $host = shift @_ or die usage(); } if (($indexarg && @_ > 3) || (!$indexarg && @_ > 2)) { return "Usage: relshow [-h host] [database] [table]$indexarg\n"; } my @m; push @m, "Host = $host\n" if $host; my $Dbh = $driver->connect($host) or return; my($table,$bottok,$sorry,$i); if ($_[0]) { $database = shift @_; return "Couldn't connect to $database\n" unless $Dbh->selectdb($database); push @m, "\nDatabase = $database\n"; if ($table = shift @_) { grep /^\Q$table\E$/, $Dbh->ListTables or return join "", @m, qq{Table "$table" not found\n}; my $sth = $Dbh->listfields($table) or return join "", @m, qq{Error reading listfields($table)\n}; push @m, qq{Table = $table\n}; my $index; if ($index = shift @_) { # # relshow database table index # return "Too many arguments to relshow\n" unless $Dbh->getserverinfo ge 2; #warn join ":", grep //, $sth->name; ~#if#~ ('~~dbd_driver~~' ne 'mysql') if ($index eq "_seq") { my(@seq) = $Dbh->getsequenceinfo($table); push(@m, "Sequence Step = $seq[0]\n" . "Sequence Value = $seq[1]\n"); return join "", @m; } ~#endif#~ grep(/^\Q$index\E$/, $sth->name) or return join "", @m, qq{Index "$index" not found\n}; push @m, qq{Index = $index\n}; my $idxhandle = $Dbh->listindex($table,$index) or return join "", @m, qq{Error reading listindex($table,$index)\n}; my @row; @row = $idxhandle->fetchrow; # chop off avl or whatever push @m, qq{Index Type = $row[0]\n}; my $border = " +" . ("-"x21) . "+\n"; push @m, $border; push @m, sprintf " | %-19s |\n", " Field"; push @m, $border; while (@row = $idxhandle->fetchrow) { push @m, sprintf " | %-19s |\n", $row[0]; } push @m, $border; return join "", @m; } # # relshow database table # my $fieldwidth = longest($sth->name,"Field") || 15; my ($keywidth, $keytitle); ~#if#~ ('~~dbd_driver~~' eq 'mysql') $keywidth = 3; $keytitle = "Key"; ~#else#~ if ($Dbh->getserverinfo lt 2) { $keywidth = 3; $keytitle = "Key"; } else { $keywidth = 12; $keytitle = "Unique Index"; } ~#endif#~ my $border = " +-".("-"x$fieldwidth)."-+-----------+--------+----------+-".("-"x$keywidth)."-+\n"; push @m, $border; push @m, sprintf " | %-".$fieldwidth."s | Type | Length | Not Null | %-".$keywidth."s |\n", "Field", $keytitle; push @m, $border; my $max = $sth->numfields; for ($i=0;$i<$max;$i++){ my $keyNO; ~#if#~ ('~~dbd_driver~~' eq 'mysql') $keyNO = "N"; ~#else#~ if ($Dbh->getserverinfo lt 2 || $sth->type->[$i] == &Msql::IDX_TYPE()) { $keyNO = "N"; } else { $keyNO = "N/A"; } ~#endif#~ push @m, sprintf " | %-".$fieldwidth."s | %-9s | %6s | %-3s | %-".$keywidth."s |\n", $sth->name->[$i], $typelabel{$sth->type->[$i]} || ("unknown-".$sth->type->[$i]), ($sth->length->[$i] || "N/A"), ~#if#~ ('~~dbd_driver~~' eq 'mysql') $sth->is_not_null->[$i] ? " Y " : "N", ~#else#~ $sth->is_not_null->[$i] ? " Y " : (defined &Msql::IDX_TYPE && $sth->type->[$i]!=Msql::IDX_TYPE()) ? " N " : "N/A", ~#endif#~ $sth->is_pri_key->[$i] ? "Y" : $keyNO; } push @m, "$border\n"; } else { # # relshow database # my @l = $Dbh->ListTables; if (@l) { my $border = " +---------------------+\n"; push @m, qq{ $border | Table |\n$border}; my $elem; for $elem (@l) { push @m, sprintf " | %-19s |\n", $elem; } push @m, "$border\n"; } else { push @m, "No tables in database\n"; } } } else { # # relshow # my @l = $Dbh->ListDBs; if (@l) { my $border = " +------------------+\n"; push @m, qq{ $border | Databases |\n$border} ; my $elem; for $elem (@l) { push @m, sprintf " | %-16s |\n", $elem; } push @m, "$border\n"; } else { push @m, "No databases found\n"; } } return join "", @m; } sub sep_out { my($sep) = shift; my(@arr, @res); my($epattern, $qpattern, $null); if (defined($escape) && $escape ne '') { $epattern = $escape; $epattern =~ s/(.)/\\$1/g; if (defined($quote) && $quote ne '') { $qpattern = $quote; $qpattern =~ s/(.)/\\$1/g; if ($epattern) { $epattern = "$epattern|$qpattern"; } else { $epattern = $qpattern; } } $epattern = "($epattern|\\0)"; } else { $epattern = ''; } $null = 0; while (@arr = $::Q->fetchrow()) { foreach $word (@arr) { if ($epattern) { $word =~ s/($epattern)/$escape$1/g; } if ($quote) { $word = "$quote$word$quote"; } } push(@res, join($sepchar, @arr) . "\n"); } return @res; } sub usage () {"Usage: $0 [-h host] database";} __END__ =head1 NAME pmsql, pmysql - interactive shells with readline for msql and mysql =head1 SYNOPSIS C or C =head1 DESCRIPTION pmsql and pmysql let you talk to a running msql or mysql daemon sending either SQL queries or relshow (mysqlshow) commands. The output is formatted much in the same way as by the msql or mysql monitor (see below), the msqlexport command and the relshow (mysqlshow) program, which are coming with msql or mysql. The additional capability is a connection to a readline interface (if available) and a pipe to your favorite pager. Additionally you may switch between hosts and databases within one session and you don't have to type the nasty C<\g> or C<;> (a trailing C<\g>, C<\q>, and C<\p> will be ignored). If a command starts with one of the following reserved words, it's treated specially, otherwise it is passed on verbatim to the mSQL or mysql daemon. Output from the daemon is piped to your pager specified by either the PMSQL_PAGER (PMYSQL_PAGER) or the PAGER environment variable. If both are undefined, the PATH is searched for either "less" or "more" and the first program found is taken. If no pager can be determined, or your pager variable contains the word C, the program writes to unfiltered STDOUT. =over 2 =item C print usage summary and current host and database =item C Set default database to "database" =item C Set the escape character which is used when I mode is off. Defaults to C<">. =item C Set the output format of I