File Coverage

blib/lib/Fsdb/Filter/dbmerge2.pm
Criterion Covered Total %
statement 18 92 19.5
branch 0 20 0.0
condition 0 9 0.0
subroutine 6 20 30.0
pod 5 5 100.0
total 29 146 19.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             #
4             # dbmerge2.pm
5             # Copyright (C) 1991-2017 by John Heidemann
6             # $Id: 66ef27627d3d5cfbc6108d4e1508b3330952c22d $
7             #
8             # This program is distributed under terms of the GNU general
9             # public license, version 2. See the file COPYING
10             # in $dblibdir for details.
11             #
12              
13             package Fsdb::Filter::dbmerge2;
14              
15             =head1 NAME
16              
17             dbmerge2 - merge exactly two inputs in sorted order based on the the specified columns
18              
19             =head1 SYNOPSIS
20              
21             dbmerge2 --input A.fsdb --input B.fsdb [-T TemporaryDirectory] [-nNrR] column [column...]
22              
23             or
24             cat A.fsdb | dbmerge2 --input B.fsdb [-T TemporaryDirectory] [-nNrR] column [column...]
25              
26             =head1 DESCRIPTION
27              
28             Merge exactly two sorted input files, producing one sorted result.
29             Inputs can both be specified with C<--input>, or one can come
30             from standard input and the other from C<--input>.
31              
32             Inputs must have identical schemas (columns, column order,
33             and field separators).
34              
35             Dbmerge2 consumes a fixed amount of memory regardless of input size.
36              
37             Although described above as a command line too, the command line
38             version of dbmerge2 is not installed by default.
39             Dbmerge2 is used primarily internal to perl;
40             L is the command-line tool for user use.
41              
42             Warning: we do not verify that each input is actually sorted.
43             In correct merge results will occur if they are not.
44              
45             =head1 OPTIONS
46              
47             General option:
48              
49             =over 4
50              
51             =item B<--saveoutput $OUT_REF>
52              
53             Save output writer (for integration with other fsdb filters).
54              
55             =item <-T TmpDir>
56              
57             where to put tmp files.
58             Also uses environment variable TMPDIR, if -T is
59             not specified.
60             Default is /tmp.
61              
62             =back
63              
64             Sort specification options (can be interspersed with column names):
65              
66             =over 4
67              
68             =item B<-r> or B<--descending>
69              
70             sort in reverse order (high to low)
71              
72             =item B<-R> or B<--ascending>
73              
74             sort in normal order (low to high)
75              
76             =item B<-n> or B<--numeric>
77              
78             sort numerically
79              
80             =item B<-N> or B<--lexical>
81              
82             sort lexicographically
83              
84             =back
85              
86             =for comment
87             begin_standard_fsdb_options
88              
89             This module also supports the standard fsdb options:
90              
91             =over 4
92              
93             =item B<-d>
94              
95             Enable debugging output.
96              
97             =item B<-i> or B<--input> InputSource
98              
99             Read from InputSource, typically a file name, or C<-> for standard input,
100             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
101              
102             =item B<-o> or B<--output> OutputDestination
103              
104             Write to OutputDestination, typically a file name, or C<-> for standard output,
105             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
106              
107             =item B<--autorun> or B<--noautorun>
108              
109             By default, programs process automatically,
110             but Fsdb::Filter objects in Perl do not run until you invoke
111             the run() method.
112             The C<--(no)autorun> option controls that behavior within Perl.
113              
114             =item B<--help>
115              
116             Show help.
117              
118             =item B<--man>
119              
120             Show full manual.
121              
122             =back
123              
124             =for comment
125             end_standard_fsdb_options
126              
127              
128             =head1 SAMPLE USAGE
129              
130             =head2 Input:
131              
132             File F:
133              
134             #fsdb cid cname
135             11 numanal
136             10 pascal
137              
138             File F:
139              
140             #fsdb cid cname
141             12 os
142             13 statistics
143              
144             =head2 Command:
145              
146             dbmerge2 --input a.fsdb --input b.fsdb cname
147              
148             or
149              
150             cat a.fsdb | dbmerge2 --input b.fsdb cname
151              
152             =head2 Output:
153              
154             #fsdb cid cname
155             11 numanal
156             12 os
157             10 pascal
158             13 statistics
159             # | dbmerge2 --input a.fsdb --input b.fsdb cname
160              
161             =head1 SEE ALSO
162              
163             L,
164             L,
165             L
166              
167             =head1 CLASS FUNCTIONS
168              
169             =cut
170              
171              
172             @ISA = qw(Fsdb::Filter);
173             ($VERSION) = 2.0;
174              
175 1     1   4534 use strict;
  1         2  
  1         30  
176 1     1   6 use Carp qw(croak);
  1         1  
  1         42  
177 1     1   5 use Pod::Usage;
  1         2  
  1         68  
178              
179 1     1   6 use Fsdb::Filter;
  1         1  
  1         17  
180 1     1   4 use Fsdb::IO::Reader;
  1         2  
  1         14  
181 1     1   4 use Fsdb::IO::Writer;
  1         2  
  1         722  
182              
183             =head2 new
184              
185             $filter = new Fsdb::Filter::dbmerge2(@arguments);
186              
187             Create a new object, taking command-line arguments.
188              
189             =cut
190              
191             sub new ($@) {
192 0     0 1   my $class = shift @_;
193 0           my $self = $class->SUPER::new(@_);
194 0           bless $self, $class;
195 0           $self->set_defaults;
196 0           $self->parse_options(@_);
197 0           $self->SUPER::post_new();
198 0           return $self;
199             }
200              
201              
202             =head2 set_defaults
203              
204             $filter->set_defaults();
205              
206             Internal: set up defaults.
207              
208             =cut
209              
210             sub set_defaults ($) {
211 0     0 1   my $self = shift @_;
212 0           $self->SUPER::set_defaults();
213 0           $self->{_info}{input_count} = 2;
214 0           $self->{_sort_argv} = [];
215 0 0         $self->{_tmpdir} = defined($ENV{'TMPDIR'}) ? $ENV{'TMPDIR'} : "/tmp";
216             }
217              
218             =head2 parse_options
219              
220             $filter->parse_options(@ARGV);
221              
222             Internal: parse command-line arguments.
223              
224             =cut
225              
226             sub parse_options ($@) {
227 0     0 1   my $self = shift @_;
228              
229 0           my(@argv) = @_;
230             $self->get_options(
231             \@argv,
232 0     0     'help|?' => sub { pod2usage(1); },
233 0     0     'man' => sub { pod2usage(-verbose => 2); },
234             'autorun!' => \$self->{_autorun},
235             'close!' => \$self->{_close},
236             'd|debug+' => \$self->{_debug},
237 0     0     'i|input=s@' => sub { $self->parse_io_option('inputs', @_); },
238             'log!' => \$self->{_logprog},
239 0     0     'o|output=s' => sub { $self->parse_io_option('output', @_); },
240             'saveoutput=s' => \$self->{_save_output},
241             'T|tmpdir|tempdir=s' => \$self->{_tmpdir},
242             # sort key options:
243 0     0     'n|numeric' => sub { $self->parse_sort_option(@_); },
244 0     0     'N|lexical' => sub { $self->parse_sort_option(@_); },
245 0     0     'r|descending' => sub { $self->parse_sort_option(@_); },
246 0     0     'R|ascending' => sub { $self->parse_sort_option(@_); },
247 0     0     '<>' => sub { $self->parse_sort_option('<>', @_); },
248 0 0         ) or pod2usage(2);
249             }
250              
251              
252             =head2 setup
253              
254             $filter->setup();
255              
256             Internal: setup, parse headers.
257              
258             =cut
259              
260             sub setup ($) {
261 0     0 1   my($self) = @_;
262              
263             croak $self->{_prog} . ": no sorting key specified.\n"
264 0 0         if ($#{$self->{_sort_argv}} == -1);
  0            
265              
266             #
267             # setup final IO
268             #
269 0           $self->setup_exactly_two_inputs;
270 0           $self->finish_io_option('inputs', -comment_handler => $self->create_pass_comments_sub);
271 0           $self->finish_io_option('output', -clone => $self->{_ins}[0]);
272              
273             croak $self->{_prog} . ": input streams have different schemas; cannot merge\n"
274 0 0         if ($self->{_ins}[0]->compare($self->{_ins}[1]) ne 'identical');
275              
276 0           $self->{_compare_code} = $self->create_compare_code(@{$self->{_ins}});;
  0            
277             croak $self->{_prog} . ": no merge field specified.\n"
278 0 0         if (!defined($self->{_compare_code}));
279 0 0 0       print "COMPARE CODE:\n\t" . $self->{_compare_code} . "\n" if ($self->{_debug} && $self->{_debug} > 1);
280             }
281              
282              
283             =head2 run
284              
285             $filter->run();
286              
287             Internal: run over each rows.
288              
289             =cut
290             sub run ($) {
291 0     0 1   my($self) = @_;
292              
293             # Override the usual package globals for $a, $b (!),
294             # then eval compare_sub in this lexical context.
295             # We're Having Fun Now.
296 0           my($a, $b);
297 0           my $compare_sub;
298 0           eval '$compare_sub = ' . $self->{_compare_code};
299 0 0         $@ && croak $self->{_prog} . ": internal eval error in compare code: $@.\n";
300 0           my @fastpath_subs;
301 0           foreach (0..1) {
302 0           $fastpath_subs[$_] = $self->{_ins}[$_]->fastpath_sub();
303             };
304 0           my $out_fastpath_sub = $self->{_out}->fastpath_sub();
305              
306             # prime the pump
307 0           $a = &{$fastpath_subs[0]}();
  0            
308 0           $b = &{$fastpath_subs[1]}();
  0            
309 0           for (;;) {
310 0 0 0       last if (!defined($a) || !defined($b)); # eof on one
311 0           my $cmp = &{$compare_sub}(); # compare $a vs $b
  0            
312 0 0         if ($cmp <= 0) {
313 0           &{$out_fastpath_sub}($a);
  0            
314 0           $a = &{$fastpath_subs[0]}();
  0            
315             } else {
316 0           &{$out_fastpath_sub}($b);
  0            
317 0           $b = &{$fastpath_subs[1]}();
  0            
318             };
319             };
320             # one should be done
321 0 0 0       die if (defined($a) && defined($b)); # assert
322             # drain the one that's still full
323 0           while (defined($a)) {
324 0           &{$out_fastpath_sub}($a);
  0            
325 0           $a = &{$fastpath_subs[0]}();
  0            
326             };
327 0           while (defined($b)) {
328 0           &{$out_fastpath_sub}($b);
  0            
329 0           $b = &{$fastpath_subs[1]}();
  0            
330             };
331             # print "# dbmerge2: both inputs done\n" if ($self->{_debug});
332 0           foreach (0..1) {
333 0           $self->{_ins}[$_]->close;
334             };
335             };
336            
337              
338             =head1 AUTHOR and COPYRIGHT
339              
340             Copyright (C) 1991-2017 by John Heidemann
341              
342             This program is distributed under terms of the GNU general
343             public license, version 2. See the file COPYING
344             with the distribution for details.
345              
346             =cut
347              
348             1;