App-AltSQL
view release on metacpan or search on metacpan
lib/App/AltSQL/View.pm view on Meta::CPAN
package App::AltSQL::View;
=head1 NAME
App::AltSQL::View
=head1 DESCRIPTION
This is an internal class used by L<App::AltSQL> to capture the output of a DBI statement handler and express it to the user somehow. It does this mainly with L<Text::UnicodeBox::Table>, and is currently MySQL specific.
=cut
use Moose;
use Data::Dumper;
use Text::CharWidth qw(mbswidth);
use Time::HiRes qw(gettimeofday);
use Params::Validate;
use List::Util qw(sum max);
with 'App::AltSQL::Role';
with 'MooseX::Object::Pluggable';
has 'timing' => ( is => 'rw' );
has 'verb' => ( is => 'rw' );
has 'buffer' => ( is => 'rw' );
has 'table_data' => ( is => 'rw' );
has 'footer' => ( is => 'rw' );
sub args_spec {
return (
);
}
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
my %args = validate(@_, {
app => 1,
timing => 1,
verb => 1,
sth => 1,
});
my $sth = delete $args{sth};
if ($args{verb} eq 'use') {
$args{buffer} = 'Database changed';
return $class->$orig(\%args);
}
if (! $sth->{NUM_OF_FIELDS}) {
$args{buffer} = sprintf "Query OK, %d row%s affected (%s)\n", $sth->rows, ($sth->rows > 1 ? 's' : ''), _describe_timing($args{timing});
if ($args{verb} ne 'insert') {
$args{buffer} .= sprintf "Records: %d Warnings: %d\n", $sth->rows, $sth->{mysql_warning_count};
}
$args{buffer} .= "\n";
return $class->$orig(\%args);
}
my %table_data = (
columns => [],
rows => [],
);
$args{table_data} = \%table_data;
# Populate table_data{columns}
my %mysql_meta = (
map { my $key = $_; $key =~ s/^mysql_//; +($key => $sth->{$_}) }
qw(mysql_is_blob mysql_is_key mysql_is_num mysql_is_pri_key mysql_is_auto_increment mysql_length mysql_max_length)
);
foreach my $i (0..$sth->{NUM_OF_FIELDS} - 1) {
push @{ $table_data{columns} }, {
name => $sth->{NAME}[$i],
type => $sth->{TYPE}[$i],
precision => $sth->{PRECISION}[$i],
scale => $sth->{SCALE}[$i],
nullable => $sth->{NULLABLE}[$i] || undef,
map { $_ => $mysql_meta{$_}[$i] } keys %mysql_meta
};
}
# Populate table_data{rows}
my $t0 = gettimeofday;
$table_data{rows} = $sth->fetchall_arrayref;
$args{timing}{fetchall} = gettimeofday - $t0;
# Return if no rows in result
if (int @{ $table_data{rows} } == 0) {
$args{buffer} = sprintf "Empty set (%s)\n\n", _describe_timing($args{timing});
return $class->$orig(\%args);
}
$args{footer} = sprintf "%d rows in set (%s)\n\n", int @{ $table_data{rows} }, _describe_timing($args{timing});
return $class->$orig(\%args);
};
=head2 render %args
Optionally pass 'no_pager' or 'one_row_per_column'. Will render the stored buffer by either printing it to the screen or piping it to a pager.
=cut
sub render {
my $self = shift;
my %args = validate(@_, {
no_pager => 0,
one_row_per_column => 0,
});
# Buffer will be unset unless there is a static result
my $buffer = $self->buffer;
if ($buffer) {
print $buffer;
return;
}
# Otherwise, construct the buffer from rendering the table_data with footer
if ($args{one_row_per_column}) {
$buffer = $self->render_one_row_per_column();
}
else {
$buffer = $self->render_table();
}
if ($self->footer) {
$buffer .= $self->footer;
}
## Possibly page the output
my $pager;
my ($buffer_width, $buffer_height) = _buffer_dimensions(\$buffer);
# less args are:
# -F quit if one screen
# -R support color
# -X don't send termcap init
# -S chop long lines; don't wrap long lines
if ($buffer_width > $self->app->term->get_term_width) {
$pager = 'less -FRXS';
}
elsif ($buffer_height > $self->app->term->get_term_height) {
$pager = 'less -FRX';
}
if ($pager && ! $args{no_pager}) {
open my $out, "| $pager" or die "Can't open $pager for pipe: $!";
binmode $out, ':utf8';
print $out $buffer;
close $out;
}
else {
print $buffer;
}
}
=head2 render_table
Given the table data that was extracted from the statement handler, compose a nicely formatted table for showing to the user.
Optionally pass in the table data to be used.
=cut
( run in 1.228 second using v1.01-cache-2.11-cpan-56fb94df46f )