#!/usr/bin/perl # AUTHOR: Clemens Schwaighofer # DATE: 2015/8/6 # DSCRIPTION: # Runs a query from a file or command line and outputs the data to a CSV file # Runs query async/and CURSOR use strict; use warnings; no strict 'refs'; # I need to allow dynamic references in this script use utf8; BEGIN { use POSIX qw(floor); use DBD::Pg ':async'; use Text::CSV_XS; use Getopt::Long; use Time::HiRes qw(time); use Number::Format qw(format_number); # assign a function to INTERRUPT $SIG{INT} = \&tsktsk; } # in case a ^C is cought, cancel all remote running querries before exiting sub tsktsk { # assign to itself again $SIG{INT} = \&tsktsk; warn "\nPGSQL async: ".$main::dbh->{'pg_async_status'}."\n"; # finish all asyncs $main::dbh->pg_cancel() if ($main::dbh->{'pg_async_status'}); # finish all cursors $main::dbh->do($main::close_query) || print "Cursor '".$main::cursor_name."' not yet declared\n"; # close any open cursors # loop cursor close foreach my $my_cur (keys %main::cur) { $main::cur{$my_cur}->finish; } # close read cursor $main::sth_read->finish if ($main::sth_read); # close all DB connections $main::dbh->disconnect(); # output data close($main::CSV_DATA); # die, so the script does not continue die "Exit via ^C\n"; } # METHOD: check_output_file # PARAMS: file name # RETURN: none # DESC : removes the file if not data was written sub check_output_file { my ($file_name) = @_; if (! -s $file_name) { print_out("Delete file $file_name because no data was found", 1); unlink($file_name); } } # METHOD: convert_time # PARAMS: timestamp, 1/0 for micro time output # RETURN: formated string of the input timestamp in days, hours, minutes, seconds and optional micorseconds # DESC : make time string from seconds interval timestamp sub convert_time { my ($timestamp, $show_micro) = @_; my $ms = ''; # cut of the ms, but first round them up to four $timestamp = sprintf("%.4f", $timestamp); ($timestamp, $ms) = split(/\./, $timestamp); my @timegroups = (86400, 3600, 60, 1); my @output = (); for (my $i = 0; $i < @timegroups; $i ++) { push(@output, floor($timestamp / $timegroups[$i])); $timestamp = $timestamp % $timegroups[$i]; } # output has days|hours|min|sec $timestamp = $output[0] ? $output[0].'d ' : ''; # days $timestamp .= ($output[1] || $output[0]) ? $output[1].'h ' : ''; # hours $timestamp .= ($output[2] || $output[1] || $output[0]) ? $output[2].'m ' : ''; # minutes $timestamp .= $output[3].'s'; # seconds $timestamp .= $show_micro ? ' '.(!$ms ? 0 : $ms).'ms' : ''; # microseconds # return string return $timestamp; } # converts bytes to human readable format sub convert_number { my ($number) = @_; my $pos; # the original position in the labels array $number = 0 if (!$number); # divied number until its division would be < 1024. count that position for label usage for ($pos = 0; $number > 1024; $pos ++) { $number = $number / 1024; } # before we return it, we format it [rounded to 2 digits, if has decimals, else just int] # we add the right label to it and return return sprintf(!$pos ? '%d' : '%.2f', $number).qw(B KB MB GB TB PB EB)[$pos]; } # METHOD: print_out # PARAMS: message, verbose level, no line break # RETURN: n/a # DESC : prints out the message based on the global verbose level sub print_out { my ($message, $v_level, $no_lb) = @_; # debug data is only printend, when debug flag is on print $message.(!$no_lb ? "\n" : '') if ($main::verbose >= $v_level); } # no buffering for output $| ++; binmode STDOUT, ":encoding(utf8)"; binmode STDIN, ":encoding(utf8)"; binmode STDERR, ":encoding(utf8)"; my $error = 0; my %opt = (); our $verbose = 0; my $query = ''; my $output_file = ''; my $db_connect_string = ''; my $no_async = 0; my $no_declare = 0; my $wait_time = 10; # wait time for a finish check. defaul it is 10 seconds # add prompt bundeling (eg -qqq Getopt::Long::Configure ("bundling"); # command line my $result = GetOptions(\%opt, 'q|query=s' => \$query, 'o|output=s' => \$output_file, 'd|db=s' => \$db_connect_string, 'w|wait=s' => \$wait_time, 'no-async' => \$no_async, # do not run querys async #'no-declare' => \$no_declare, # do no collect data server side 'verbose|v+' => \$verbose, 'help' # just help ) || exit 1; if ($opt{'help'}) { print "Possible options\n"; print "--query|--q \tCan either be a file that has the query inside or the query itself in a string\n"; print "--output|--o \tThe data read from the query is written into this file including headers\n"; print "--db|-d \tConnection data in the following format: user=;pass=;dbname=;host=;port=\n"; print "--wait|-w