File Coverage

blib/lib/Mnet/Report/Table.pm
Criterion Covered Total %
statement 110 276 39.8
branch 40 118 33.9
condition 15 81 18.5
subroutine 13 21 61.9
pod 3 3 100.0
total 181 499 36.2


line stmt bran cond sub pod time code
1             package Mnet::Report::Table;
2              
3             =head1 NAME
4              
5             Mnet::Report::Table - Output rows of report data
6              
7             =head1 SYNOPSIS
8              
9             # create an example new table object, with csv file output
10             my $table = Mnet::Report::Table->new({
11             output => "csv:file.csv",
12             columns => [
13             device => "string",
14             time => "time",
15             data => "Integer",
16             error => "error"
17             ],
18             });
19              
20             # output error row if script aborts with unreported errors
21             $table->row_on_error({ device => $device });
22              
23             # output a normal report row, call again to output more
24             $table->row({ device => $device, data => $data });
25              
26             =head1 DESCRIPTION
27              
28             Mnet::Report::Table can be used to create new report table objects, with a row
29             method call to add data, a row_on_error method to ensure errors always appear,
30             with various output options including csv, json, sql, and perl L
31             formats.
32              
33             =head1 METHODS
34              
35             Mnet::Report::Table implements the methods listed below.
36              
37             =cut
38              
39             # required modules
40 1     1   563 use warnings;
  1         8  
  1         32  
41 1     1   5 use strict;
  1         2  
  1         23  
42 1     1   453 use parent qw( Mnet::Log::Conditional );
  1         363  
  1         5  
43 1     1   45 use Carp;
  1         2  
  1         49  
44 1     1   420 use Mnet::Dump;
  1         3  
  1         76  
45 1     1   9 use Mnet::Log::Conditional qw( DEBUG INFO WARN FATAL NOTICE );
  1         3  
  1         67  
46 1     1   7 use Mnet::Opts::Cli::Cache;
  1         2  
  1         175  
47              
48             # init autoflush, global variables, and sig handlers
49             # autoflush is set so multi-process syswrite lines don't clobber each other
50             # @selves tracks report objects until deferred/row_on_error output end block
51             # $error and sig handlers used to track first error, if Mnet::Log not loaded
52             BEGIN {
53 1     1   6 $| = 1;
54 1         2 my @selves = ();
55 1         2 my $error = undef;
56 1 50       4 if (not $INC{'Mnet/Log.pm'}) {
57             $SIG{__DIE__} = sub {
58 11 100       353 if (not defined $Mnet::Report::Table::error) {
59 1         5 chomp($Mnet::Report::Table::error = "@_");
60             }
61 11         62 die @_;
62 1         7 };
63             $SIG{__WARN__} = sub {
64 0 0       0 if (not defined $Mnet::Report::Table::error) {
65 0         0 chomp($Mnet::Report::Table::error = "@_");
66             }
67             warn @_
68 1         3099 };
  0         0  
69             }
70             }
71              
72              
73              
74             sub new {
75              
76             =head2 new
77              
78             $table = Mnet::Report::Table->new(\%opts)
79              
80             A new Mnet::Report::Table object can be created with the options showm in
81             the example below:
82              
83             my $table = Mnet::Report::Table->new({
84             columns => [ # ordered column names and types
85             device => "string", # strips eol chars in csv output
86             count => "integer", # +/- integer numbers
87             error => "error", # first error, see row_on_error()
88             time => "time", # time as yyyy/mm/dd hh:mm:ss
89             unix => "epoch", # unix time, see perldoc -f time
90             ],
91             output => "csv:$file", # see this module's OUTPUT section
92             append => $boolean, # set to append output to file
93             log_id => $string, # see perldoc Mnet::Log new method
94             nodefer => $boolean, # set to output rows immediately
95             });
96              
97             The columns option is required, and is an array reference containing an ordered
98             list of hashed column names of type string, integer, error, time, or epoch.
99              
100             Columns of type string and integer are set by the user for new rows using the
101             row method in this module. Columns of type error, time, and epoch are set
102             automatically for each row of output.
103              
104             The output option should be used to specify an output format and filename, as
105             in the example above. Refer to the OUTPUT section below for more information.
106              
107             The append option opens the output file for appending and doesn't write a
108             heading row, Otherwise the default is to overwrite the output file when the
109             new table object is created.
110              
111             The nodefer option can be used so that report data rows are output immediately
112             when the row method is called. Otherwise row data is output when the script
113             exits. This can affect the reporting of errors, refer to the row_on_error
114             method below for more information.
115              
116             Note that new Mnet::Report::Table objects should be created before forking
117             batch children if the L module is being used.
118              
119             Errors are issued for invalid options.
120              
121             =cut
122              
123             # read input class and options hash ref merged with cli options
124 11   33 11 1 6100 my $class = shift // croak("missing class arg");
125 11   50     42 my $opts = Mnet::Opts::Cli::Cache::get(shift // {});
126              
127             # bless new object created from input opts hash
128             # this allows log_id and other Mnet::Log options to be in effect
129             # the following keys start with underscore and are used internally:
130             # _column_error => set if error column is present, for _row_on_error
131             # _column_order => array ref listing column names in sort order
132             # _column_types => hash ref keyed by column names with value as type
133             # _output_rows => list of row hashes, output t end unless nodefer
134             # _output_fh => filehandle for row outputs, opened from new method
135             # _row_on_error => set with row hash to ensure output if any errors
136             # in addition refer to perldoc for input opts and Mnet::Log0->new opts
137 11         20 my $self = $opts;
138 11         18 bless $self, $class;
139 11         15 push @{$Mnet::Report::Table::selves}, $self;
  11         24  
140 11         43 $self->debug("new starting");
141              
142             # abort if we were called before batch fork if Mnet::Batch was loaded
143             # avoids problems with first row method call from new sub to init output
144             # for example: _output_csv batch parent must create file and heading row
145             # we don't want every batch child creating duplicate heading rows
146             croak("new Mnet::Report::Table must be created before Mnet::Batch::fork")
147 11 50 33     28 if $INC{"Mnet/Batch.pm"} and Mnet::Batch::fork_called();
148              
149             # abort if opts columns array ref is not set
150             # create _column_types hash ref and _column_order array ref in new object
151             # croak for invalid column types and error type if Mnet::Log not loaded
152 11 100       256 croak("missing opts input columns key") if not $opts->{columns};
153 10 100       133 croak("invalid opts input columns key") if ref $opts->{columns} ne "ARRAY";
154 9 100       14 croak("missing opts input column data") if not scalar(@{$opts->{columns}});
  9         160  
155 8         17 $self->{_column_types} = {};
156 8         16 $self->{_column_order} = [];
157 8         11 while (@{$opts->{columns}}) {
  22         46  
158 17   66     23 my $column = shift @{$opts->{columns}} // croak("missing column name");
  17         147  
159 16   66     22 my $type = shift @{$opts->{columns}} // croak("missing column type");
  16         132  
160 15 50       39 croak("invalid column name $column") if $column =~ /["\r\n]/;
161 15         52 $self->debug("new column = $column ($type)");
162 15         32 $self->{_column_types}->{$column} = $type;
163 15         18 push @{$self->{_column_order}}, $column;
  15         30  
164 15 100       27 $self->{_column_error} = 1 if $type eq "error";
165 15 100       67 if ($type !~ /^(epoch|error|integer|string|time)$/) {
166 1         104 croak("column type $type is invalid");
167             }
168             }
169              
170             # debug calls to display output option set for this object
171 5         21 $self->debug("new output = ".Mnet::Dump::line($self->{output}));
172              
173             # call _output method with no row arg to init output
174             # allows batch parent or non-batch proc to open file and output headings
175 5         16 $self->debug("new init _output call");
176 5         13 $self->_output;
177              
178             # finished new method, return Mnet::Report::Table object
179 4         17 $self->debug("new finished, returning $self");
180 4         11 return $self;
181             }
182              
183              
184              
185             sub row {
186              
187             =head2 row
188              
189             $table->row(\%data)
190              
191             This method will add a row of specified data to the current report table
192             object, as in the following example:
193              
194             $table->row({
195             device => $string,
196             sample => $integer,
197             })
198              
199             Note that an error is issued if any keys in the data were not defined as string
200             or integer columns when the new method was used to create the current object.
201              
202             =cut
203              
204             # read input object
205 4   33 4 1 21 my $self = shift // croak("missing self arg");
206 4   66     118 my $data = shift // croak("missing data arg");
207              
208             # init hash ref to hold output row data
209 3         9 my $row = $self->_row_data($data);
210              
211             # output or store row data
212 0 0       0 if ($self->{nodefer}) {
213 0         0 $self->_output($row);
214             } else {
215 0         0 push @{$self->{_output_rows}}, $row;
  0         0  
216             }
217              
218             # finished row method
219 0         0 return;
220             }
221              
222              
223              
224             sub _row_data {
225              
226             # \%row = $self->_row_data(\%data)
227             # purpose: set keys in output row hash form input data hash, with error refs
228              
229             # read input object
230 3   50 3   7 my $self = shift // die "missing self arg";
231 3   50     22 my $data = shift // die "missing data arg";
232 3         28 $self->debug("_row_data starting");
233              
234             # init hash ref to hold output row data
235 3         4 my $row = {};
236              
237             # loop through all columns in the current object
238 3         7 foreach my $column (sort keys %{$self->{_column_types}}) {
  3         17  
239 6         12 my $type = $self->{_column_types}->{$column};
240 6         7 my $value = $data->{$column};
241              
242             # set epoch column to unix time, refer to perldoc -f time
243             # on most systems is non-leap seconds since 00:00:00 jan 1, 1970 utc
244             # this is simplest way to agnostically store time for various uses
245 6 50       24 if ($type eq "epoch") {
    100          
    100          
    50          
    50          
246 0         0 $row->{$column} = time;
247 0 0       0 $row->{$column} = Mnet::Test::time() if $INC{'Mnet/Test.pm'};
248 0 0       0 croak("invalid time column $column") if exists $data->{$column};
249              
250             # set error column type as reference to global first error variable
251             # update global error ref, so all rows show errors from end block
252             # croak if the user supplied data for an error column
253             } elsif ($type eq "error") {
254 3         8 $row->{$column} = \$Mnet::Report::Table::error;
255 3 100       101 croak("invalid error column $column") if exists $data->{$column};
256              
257             # set integer column type, croak on bad integer
258             } elsif ($type eq "integer") {
259 2 100       6 if (defined $value) {
260 1         5 $value =~ s/(^\s+|\s+$)//;
261 1 50       7 if ($value =~ /^(\+|\-)?\d+$/) {
262 0         0 $row->{$column} = $value;
263             } else {
264 1         4 $value = Mnet::Dump::line($value);
265 1         97 croak("invalid integer column $column value $value");
266             }
267             }
268              
269             # set string column type
270             } elsif ($type eq "string") {
271 0         0 $row->{$column} = $value;
272              
273             # set time column types to yyyy/mm/dd hh:mm:ss
274             } elsif ($type eq "time") {
275 1         2 my $time = time;
276 1 50       3 $time = Mnet::Test::time() if $INC{'Mnet/Test.pm'};
277 1         61 my ($sec, $min, $hour, $date, $month, $year) = localtime($time);
278 1         4 $month++; $year += 1900;
  1         4  
279 1         3 my @fields = ($year, $month, $date, $hour, $min, $sec);
280 1         8 $row->{$column} = sprintf("%04s/%02s/%02s %02s:%02s:%02s", @fields);
281 1 50       105 croak("invalid time column $column") if exists $data->{$column};
282              
283             # abort on unknown column type
284             } else {
285 0         0 die "invalid column type $type";
286             }
287              
288             # continue loop through columns in the currect object
289             }
290              
291             # croak if any input data columns were not declared for current object
292 0         0 foreach my $column (sort keys %$data) {
293 0 0       0 next if exists $self->{_column_types}->{$column};
294 0         0 croak("column $column was not defined for $self->{output}");
295             }
296              
297             # finished row method, return row hash ref
298 0         0 $self->debug("_row_data finished");
299 0         0 return $row;
300             }
301              
302              
303              
304             sub row_on_error {
305              
306             =head2 row_on_error
307              
308             $table->row_on_error(\%data)
309              
310             This method can be used to ensure that an Mnet::Report::Table object with an
311             error column outputs an error row when the script exits if no prior output row
312             reflected that there was an error, as in the example below:
313              
314             # declare report object as a global
315             use Mnet::Report::Table;
316             my $table = Mnet::Report::Table->new({
317             output => "json:file.json",
318             columns => [
319             input => "text",
320             error => "error",
321             ttl => "integer"
322             ],
323             });
324              
325             # call Mnet::Batch::fork here, if using Mnet::Batch module
326              
327             # output error row at exit if there was an unreported error
328             $table->row_on_error({ input => "error" });
329              
330             # output first row, no error, always present in output
331             $table->row({ input => "first" });
332              
333             # lots of code could go here, with possibility of errors...
334             die if int(rand) > .5;
335              
336             # output second row, no error, present if die did not occur
337             $table->row({ input => "second" });
338              
339             # row_on_error output at exit for unpreported errors
340             exit;
341              
342             This ensures that a script does not die after the row_on_error call without
343             any indication of an error in the report output.
344              
345             The default is to output all report data rows when the script exits. At this
346             time all error columns for all rows will be set with the first of any prior
347             script errors. In this case row_on_error will output an error row if there
348             was an error and the row method was never called.
349              
350             If the nodefer option was set when a new Mnet::Report::Table object was created
351             then row data is output immediately each time the row method is called, with
352             the error column set only if there was an error before the row method call. Any
353             errors afterward could go unreported. In this case row_on_error will output an
354             extra row at script exit if there was an error after the last row method call,
355             or the row method was never called.
356              
357             =cut
358              
359             # read inputs, store row_on_error row data as object _row_on_error
360             # this is output from module end block if there were unreported errors
361 0   0 0 1 0 my $self = shift // croak("missing self arg");
362 0   0     0 my $data = shift // croak("missing data arg");
363 0 0       0 croak("row_on_error requires error column") if not $self->{_column_error};
364 0         0 $self->{_row_on_error} = $self->_row_data($data);
365 0         0 return;
366             }
367              
368              
369              
370             =head1 OUTPUT OPTIONS
371              
372             When a new Mnet::Report::Table object is created the output option can be set
373             to any of the output format types listed in the documentation sections below.
374              
375             If the L module is loaded report rows are always logged with the
376             info method.
377              
378             Note the L module --test command line option silently overrides all
379             other report output options, outputting report data using the L
380             module if loaded or sending report output to stdout in L format,
381             for consistant test results.
382              
383             Output files are opened when an Mnet::Report object is created, with a heading
384             row if necessary. Refer to the new method in this documentation for information
385             on the append and nodefer options that control how the output file is opened
386             and when row data is written.
387              
388             Output options below can use /dev/stdout as the output file, which works nicely
389             with the L --silent option used with the L --batch
390             option, allowing report output from all concurrently executing batch children
391             to be easily piped or redirected in aggregate as necessary. However be aware
392             thet /dev/stdout report output is not captured by the L module.
393              
394             Note the L module --test command line option silently overrides
395             all other report output options, outputting report data using the L
396             module if loaded or sending report output to stdout in L format,
397             for consistant test results.
398              
399             =cut
400              
401             sub _output {
402              
403             # $self->_output(\$row)
404             # purpose: called from new to open file and output headings, called from row
405             # \%row: row data, or undef for init call from new method w/Mnet::Batch loaded
406             # $self->{output} object property is parsed to determin output type
407             # $self->{append} clear by default, output overwrites file, heading rows output
408             # $self->{append} set will append to output file, headng rows are suppressed
409              
410             # read input object and row data hash reference
411 5   50 5   13 my $self = shift // die "missing self arg";
412 5         8 my $row = shift;
413 5         12 $self->debug("_output starting");
414              
415             # init file parsed from output option and row output line
416 5         10 my ($file, $output) = (undef, undef);
417              
418             # handle --test output, skipped for undef heading row
419 5         11 my $cli = Mnet::Opts::Cli::Cache::get({});
420 5 50       16 if ($cli->{test}) {
421 0 0       0 if (defined $row) {
422 0         0 $self->debug("_output calling _output_test");
423 0         0 $output = $self->_output_test($row);
424             }
425              
426             # handle non-test output
427             } else {
428              
429             # log report row output, skipped for undef heading row
430 5 50       12 if (defined $row) {
431 0         0 $self->debug("_output calling _output_log");
432 0         0 $output = $self->_output_log($row);
433             }
434              
435             # note that no output option was set
436 5 100       18 if (not defined $self->{output}) {
    50          
    50          
    50          
    50          
437 4         10 $self->debug("_output skipped, output option not set");
438              
439             # handle csv output
440             } elsif ($self->{output} =~ /^csv:(.+)/) {
441 0         0 $self->debug("_output calling _output_csv");
442 0         0 $output = $self->_output_csv($row);
443 0         0 $file = $1;
444              
445             # handle dump output, call with var name arg
446             } elsif ($self->{output} =~ /^dump:([a-zA-Z]\w*):(.+)/) {
447 0         0 $self->debug("_output calling _output_dump");
448 0         0 $output = $self->_output_dump($row, $1);
449 0         0 $file = $2;
450              
451             # handle json output, call with var name arg
452             } elsif ($self->{output} =~ /^json:([a-zA-Z]\w*):(.+)/) {
453 0         0 $self->debug("_output calling _output_json");
454 0         0 $output = $self->_output_json($row, $1);
455 0         0 $file = $2;
456              
457             # handle sql output, call with table name arg
458             } elsif ($self->{output} =~ /^sql:"?([^"]+)"?:(.+)/) {
459 0         0 $self->debug("_output calling _output_sql");
460 0         0 $output = $self->_output_sql($row, $1);
461 0         0 $file = $2;
462              
463             # error on invalid output option
464             } else {
465 1         10 $self->fatal("invalid output option $self->{output}");
466             }
467              
468             # finished handling non-test output
469             }
470              
471             # open output filehandle, honor object append option
472             # open output file for first heading row call so we know we can open it
473             # so we don't continue running script when report file won't work
474 4 50 33     10 if ($file and not $self->{_output_fh}) {
475 0         0 my $mode = ">";
476 0 0       0 $mode = ">>" if $self->{append};
477 0         0 $self->debug("_output opening ${mode}$file");
478 0 0       0 open($self->{_output_fh}, $mode, $file)
479             or $self->fatal("unable to open ${mode}$file, $!");
480             }
481              
482             # output row
483             # note that for heading row the input row value is undefined
484             # normal rows are always output, heading row output only if append not set
485 4 50       10 if ($output) {
486 0 0 0     0 if ($row or not $self->{append}) {
487 0         0 syswrite $self->{_output_fh}, "$output\n";
488             }
489             }
490              
491             # finished _output method
492 4         9 $self->debug("_output finished");
493 4         8 return;
494             }
495              
496              
497              
498             sub _output_csv {
499              
500             # $output = $self->_output_csv($row)
501             # purpose: return output row data in csv format, or heading row
502             # \%row: row data, undef for heading row which returns heading row
503             # $output: single line of row output, or heading row if input row was undef
504              
505             =head2 output csv
506              
507             csv:$file
508              
509             The csv output option can be used to create a csv file.
510              
511             All csv outputs are doule quoted. Double quotes in the outut data are escaped
512             with an extra double quote.
513              
514             All end of line carraige return and linefeed characters are replaced with
515             spaces in the csv output. Multiline csv output data is not supported.
516              
517             The output csv file will be created with a heading row when the new method is
518             called unless the append option was set when the new method was called.
519              
520             Refer to the OUTPUT OPTIONS section of this module for more info.
521              
522             =cut
523              
524             # read input object and row data hash reference
525 0   0 0     my $self = shift // die "missing self arg";
526 0           my $row = shift;
527 0           $self->debug("_output_csv starting");
528              
529             # init csv row output sting, will be heading row if input row is undef
530 0           my $output = undef;
531              
532             # declare sub to quote and escape csv value
533             # eol chars removed so concurrent batch outputs klines don't intermix
534             # double quotes are escaped with an extra double quote
535             # value is prefixed and suffixed with double quotes
536             sub _output_csv_escaped {
537 0   0 0     my $value = shift // "";
538 0           $value =~ s/(\r|\n)/ /g;
539 0           $value =~ s/"/""/g;
540 0           $value = '"'.$value.'"';
541 0           return $value;
542             }
543              
544             # determine if headings row is needed
545             # headings are needed if current script is not a batch script
546             # headings are needed for parent process of batch executions
547             # headings are not needed if the append option is set for table
548 0           my $headings_needed = 0;
549 0 0 0       if (not $INC{"Mnet/Batch.pm"} or not $MNet::Batch::fork_called) {
550 0 0         if (not $self->{append}) {
551 0 0         $headings_needed = 1 if not defined $row;
552             }
553             }
554              
555             # output heading row, if needed
556 0 0         if ($headings_needed) {
557 0           $self->debug("_output_csv generating heading row");
558 0           my @headings = ();
559 0           foreach my $column (@{$self->{_column_order}}) {
  0            
560 0           push @headings, _output_csv_escaped($column);
561             }
562 0           $output = join(",", @headings);
563             }
564              
565             # output data row, if defined
566 0 0         if (defined $row) {
567 0           my @data = ();
568 0           foreach my $column (@{$self->{_column_order}}) {
  0            
569 0           my $column_data = $row->{$column};
570 0 0         $column_data = ${$row->{$column}} if ref $row->{$column};
  0            
571 0           push @data, _output_csv_escaped($column_data);
572             }
573 0           $output = join(",", @data);
574             }
575              
576             # finished _output_csv method, return output line
577 0           $self->debug("_output_csv finished");
578 0           return $output;
579             }
580              
581              
582              
583             sub _output_dump {
584              
585             # $output = $self->_output_dump($row, $var)
586             # purpose: return output row data in perl Data::Dumper format
587             # \%row: row data, undef for heading row which returns undef (no heading row)
588             # $var: var name parsed from object output option used in Data::Dumper output
589             # $output: single line of row output, or undef if input row was undef
590              
591             =head2 output dump
592              
593             dump:$var:$file
594              
595             The dump output option writes one row per line in L format
596             prefixed by the specified variable name.
597              
598             This dump output can be read back into a perl script as follows:
599              
600             use Data::Dumper;
601             while () {
602             my ($line, $var) = ($_, undef);
603             my $table = $1 if $line =~ s/^\$(\S+)/\$var/ or die;
604             eval "$line";
605             print Dumper($var);
606             }
607              
608             Refer to the OUTPUT OPTIONS section of this module for more info.
609              
610             =cut
611              
612             # read input object and row data hash reference
613 0   0 0     my $self = shift // die "missing self arg";
614 0   0       my $row = shift // return;
615 0   0       my $var = shift // die "missing var arg";
616 0           $self->debug("_output_dump starting");
617              
618             # dereference error columns
619 0           foreach my $column (keys %$row) {
620 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
621             }
622              
623             # create output row string, singl line dump
624 0           my $output = "\$$var = ".Mnet::Dump::line($row).";";
625              
626             # finished _output_dump method, return output line
627 0           $self->debug("_output_dump finished");
628 0           return $output;
629             }
630              
631              
632              
633             sub _output_json {
634              
635             # $output = $self->_output_json($row, $var)
636             # purpose: return output row data in json format
637             # \%row: row data, undef for heading row which returns undef (no heading row)
638             # $var: var name parsed from object output option used in json output
639             # $output: single line of row output, or undef if input row was undef
640              
641             =head2 output json
642              
643             json:$var:$file
644              
645             The json output option writes one row per line in json format prefixed by the
646             specified $var name. This requires that the L module is available.
647              
648             The output json looks something like the example below:
649              
650             var = {"device":"test","error":null};
651              
652             This json output can be read back into a perl script as follows:
653              
654             use JSON;
655             use Data::Dumper;
656             while () {
657             my ($line, $var) = ($_, undef);
658             my $table = $1 if $line =~ s/^(\S+) = // or die;
659             $var = decode_json($line);
660             print Dumper($var);
661             }
662              
663             Refer to the OUTPUT OPTIONS section of this module for more info.
664              
665             =cut
666              
667             # read input object and row data hash reference
668 0   0 0     my $self = shift // die "missing self arg";
669 0   0       my $row = shift // return;
670 0   0       my $var = shift // die "missing var arg";
671 0           $self->debug("_output_json starting");
672              
673             # abort with an error if JSON module is not available
674             croak("Mnet::Report::Table json requires perl JSON module is installed")
675 0 0 0       if not $INC{'JSON.pm'} and not eval("require JSON; 1");
676              
677             # dereference error columns
678 0           foreach my $column (keys %$row) {
679 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
680             }
681              
682             # create output data row
683             # json is sorted so that test output doesn't vary
684             # this will be undefined if called from new method
685 0           my $output = "$var = ".JSON->new->canonical->encode($row).";";
686              
687             # finished _output_json method, return output line
688 0           $self->debug("_output_json finished");
689 0           return $output;
690             }
691              
692              
693              
694             sub _output_log {
695              
696             # $self->_output_log
697             # purpose: output report row as info log entries
698              
699             # read input object and row data hash reference
700 0   0 0     my $self = shift // die "missing self arg";
701 0           my $row = shift;
702 0           $self->debug("_output_log starting");
703              
704             # dereference error columns
705 0           foreach my $column (keys %$row) {
706 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
707             }
708              
709             # determine width of widest column, for formatting
710 0           my $width = 0;
711 0           foreach my $column (@{$self->{_column_order}}) {
  0            
712 0 0         $width = length($column) if length($column) > $width;
713             }
714              
715             # output data row to Mnet::Log
716             # row will be undefined if called from new method
717 0 0         if (defined $row) {
718 0           my $prefix = "row";
719 0           $self->info("$prefix {");
720 0           foreach my $column (@{$self->{_column_order}}) {
  0            
721 0           my $value = Mnet::Dump::line($row->{$column});
722 0           $self->info(sprintf("$prefix %-${width}s => $value", $column));
723             }
724 0           $self->info("$prefix }");
725             }
726              
727             # finished _output_log method
728 0           $self->debug("_output_log finished");
729 0           return;
730             }
731              
732              
733              
734             sub _output_sql {
735              
736             # $output = $self->_output_sql($row, $var)
737             # purpose: return output row data in sql format, as an insert statement
738             # \%row: row data, undef for heading row which returns undef (no heading row)
739             # $table: table name parsed from object output option used in sql output
740             # $output: single line of row output, or undef if input row was undef
741              
742             =head2 output sql
743              
744             sql:$table:$file
745             or sql:"$table":$file
746              
747             The sql output option writes one row per line as sql insert statements in
748             the following format:
749              
750             INSERT INTO (, ...) VALUES (, ...);
751              
752             Table and column names are double quoted, and values are single quoted. Single
753             quotes in values are escaped with an extra single quote character, LF and CR
754             characters are escaped as '+CHAR(10)+' and '+CHAR(13)+' respectively.
755              
756             Refer to the OUTPUT OPTIONS section of this module for more info.
757              
758             =cut
759              
760             # read input object and row data hash reference
761 0   0 0     my $self = shift // die "missing self arg";
762 0   0       my $row = shift // return;
763 0   0       my $table = shift // die "missing table arg";
764 0           $self->debug("_output_sql starting");
765              
766             # init sql row output sting, will be heading row if input row is undef
767 0           my $output = undef;
768              
769             # dereference error columns
770 0           foreach my $column (keys %$row) {
771 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
772             }
773              
774             # output data row
775             # this will be undefined if called from new method
776             # double quote column names to handle unusual column names
777             # escape multiline outputs which concurrent batch procs can clobber
778 0 0         if (defined $row) {
779 0           my @sql_columns = ();
780 0           my @sql_values = ();
781 0           foreach my $column (@{$self->{_column_order}}) {
  0            
782 0           push @sql_columns, '"' . $column . '"';
783 0   0       my $value = $row->{$column} // "";
784 0           $value =~ s/'/''/g;
785 0           $value =~ s/\r/'+CHAR(10)+'/g;
786 0           $value =~ s/\n/'+CHAR(13)+'/g;
787 0           push @sql_values, "'" . $value . "'";
788             }
789 0           $output = "INSERT INTO \"$table\" ";
790 0           $output .= "(" . join(",", @sql_columns) . ") ";
791 0           $output .= "VALUES (" . join(",", @sql_values) . ");";
792             }
793              
794             # finished _output_sql method, return output line
795 0           $self->debug("_output_sql finished");
796 0           return $output;
797             }
798              
799              
800              
801             sub _output_test {
802              
803             # $self->_output_test(\%row)
804             # purpose: output test row data to stdout in Data::Dumper for when --test set
805             # \%row: row data, or undef for init call from new method w/Mnet::Batch loaded
806              
807             # read input object and row data hash reference
808 0   0 0     my $self = shift // die "missing self arg";
809 0           my $row = shift;
810 0           $self->debug("_output_test starting");
811              
812             # dereference error columns
813 0           foreach my $column (keys %$row) {
814 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
815             }
816              
817             # determine width of widest column, for formatting
818 0           my $width = 0;
819 0           foreach my $column (@{$self->{_column_order}}) {
  0            
820 0 0         $width = length($column) if length($column) > $width;
821             }
822              
823             # output data row to Mnet::Log
824             # row will be undefined if called from new method
825 0 0 0       if (defined $row and $INC{"Mnet/Log.pm"}) {
    0          
826 0           $self->debug("_output_test calling _output_log");
827 0           $self->_output_log($row);
828              
829             # otherwise output data row to standard output
830             # row will be undefined if called from new method
831             } elsif (defined $row) {
832 0           syswrite STDOUT, "\nMnet::Report::Table row = {\n";
833 0           foreach my $column (@{$self->{_column_order}}) {
  0            
834 0           my $value = Mnet::Dump::line($row->{$column});
835 0           syswrite STDOUT, sprintf(" %-${width}s => $value\n", $column);
836             }
837 0           syswrite STDOUT, "}\n";
838             }
839              
840             # finished _output_test method
841 0           $self->debug("_output_test finished");
842 0           return;
843             }
844              
845              
846              
847             # ensure that row data and error for all report objects has been output
848             # update global error var if Mnet::Log is loaded, ref used for error columns
849             # output rows for report objects that stored rows for end (nodefer not set)
850             # output row_on_error if there were unreported errors or nodefer was set
851             sub END {
852 1 50   1   1104 $Mnet::Report::Table::error = Mnet::Log::error() if $INC{'Mnet/Log.pm'};
853 1         2 foreach my $self (@{$Mnet::Report::Table::selves}) {
  1         4  
854 11         15 $self->_output($_) foreach @{$self->{_output_rows}};
  11         19  
855 11 0 33     19 if ($self->{_row_on_error} and $Mnet::Report::Table::error) {
856 0 0 0     0 if (not $self->{_row_on_error} or $self->{nodefer}) {
857 0         0 $self->_output($self->{_row_on_error});
858             }
859             }
860             }
861             }
862              
863              
864              
865             =head1 TESTING
866              
867             Mnet::Report::Table supports the L module test, record, and replay
868             functionality, tracking report data so it can be included in test results.
869              
870             =head1 SEE ALSO
871              
872             L
873              
874             L
875              
876             L
877              
878             L
879              
880             L
881              
882             L
883              
884             =cut
885              
886             # normal package return
887             1;
888