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