| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package App::RecordStream::Operation::join; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
our $VERSION = "4.0.24"; |
|
4
|
|
|
|
|
|
|
|
|
5
|
2
|
|
|
2
|
|
1008
|
use strict; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
66
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
2
|
|
|
2
|
|
11
|
use base qw(App::RecordStream::Operation); |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
152
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
14
|
use App::RecordStream::Executor; |
|
|
2
|
|
|
|
|
11
|
|
|
|
2
|
|
|
|
|
43
|
|
|
10
|
2
|
|
|
2
|
|
10
|
use App::RecordStream::InputStream; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
44
|
|
|
11
|
2
|
|
|
2
|
|
11
|
use App::RecordStream::Record; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
1111
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
sub init { |
|
14
|
5
|
|
|
5
|
0
|
8
|
my $this = shift; |
|
15
|
5
|
|
|
|
|
9
|
my $args = shift; |
|
16
|
|
|
|
|
|
|
|
|
17
|
5
|
|
|
|
|
7
|
my $left = 0; |
|
18
|
5
|
|
|
|
|
8
|
my $right = 0; |
|
19
|
5
|
|
|
|
|
8
|
my $inner = 0; |
|
20
|
5
|
|
|
|
|
9
|
my $outer = 0; |
|
21
|
5
|
|
|
|
|
8
|
my $operation = ""; |
|
22
|
5
|
|
|
|
|
6
|
my $accumulate_right = 0; |
|
23
|
|
|
|
|
|
|
|
|
24
|
5
|
|
|
|
|
21
|
my $spec = { |
|
25
|
|
|
|
|
|
|
"left" => \$left, |
|
26
|
|
|
|
|
|
|
"right" => \$right, |
|
27
|
|
|
|
|
|
|
"inner" => \$inner, |
|
28
|
|
|
|
|
|
|
"outer" => \$outer, |
|
29
|
|
|
|
|
|
|
"operation=s" => \$operation, |
|
30
|
|
|
|
|
|
|
"accumulate-right" => \$accumulate_right, |
|
31
|
|
|
|
|
|
|
}; |
|
32
|
|
|
|
|
|
|
|
|
33
|
5
|
|
|
|
|
21
|
$this->parse_options($args, $spec); |
|
34
|
|
|
|
|
|
|
|
|
35
|
5
|
50
|
|
|
|
16
|
my $inputkey = shift @$args |
|
36
|
|
|
|
|
|
|
or die "You must provide inputkey\n"; |
|
37
|
|
|
|
|
|
|
|
|
38
|
5
|
50
|
|
|
|
12
|
my $dbkey = shift @$args |
|
39
|
|
|
|
|
|
|
or die "You must provide dbkey\n"; |
|
40
|
|
|
|
|
|
|
|
|
41
|
5
|
50
|
|
|
|
14
|
my $dbfile = shift @$args |
|
42
|
|
|
|
|
|
|
or die "You must provide dbfile\n"; |
|
43
|
|
|
|
|
|
|
|
|
44
|
5
|
|
|
|
|
13
|
$this->{'ACCUMULATE_RIGHT'} = $accumulate_right; |
|
45
|
5
|
|
|
|
|
11
|
$this->{'DB_KEY'} = $dbkey; |
|
46
|
5
|
|
|
|
|
9
|
$this->{'INPUT_KEY'} = $inputkey; |
|
47
|
5
|
|
66
|
|
|
19
|
$this->{'KEEP_LEFT'} = $left || $outer; |
|
48
|
5
|
|
66
|
|
|
22
|
$this->{'KEEP_RIGHT'} = $right || $outer; |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
51
|
5
|
100
|
|
|
|
12
|
if ( $operation ) { |
|
52
|
1
|
|
|
|
|
8
|
$this->{'OPERATION'} = App::RecordStream::Executor->transform_code($operation); |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
|
|
55
|
5
|
|
|
|
|
18
|
$this->create_db($dbfile, $dbkey); |
|
56
|
|
|
|
|
|
|
|
|
57
|
5
|
|
|
|
|
59
|
$this->{'KEYS_PRINTED'} = {}; |
|
58
|
|
|
|
|
|
|
} |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
sub create_db { |
|
61
|
5
|
|
|
5
|
0
|
7
|
my $this = shift; |
|
62
|
5
|
|
|
|
|
9
|
my $file = shift; |
|
63
|
5
|
|
|
|
|
9
|
my $key = shift; |
|
64
|
|
|
|
|
|
|
|
|
65
|
5
|
|
|
|
|
31
|
my $db_stream = App::RecordStream::InputStream->new('FILE' => $file); |
|
66
|
5
|
|
|
|
|
11
|
my %db; |
|
67
|
|
|
|
|
|
|
my $record; |
|
68
|
|
|
|
|
|
|
|
|
69
|
5
|
|
|
|
|
17
|
while($record = $db_stream->get_record()) { |
|
70
|
21
|
|
|
|
|
47
|
my $value = $this->value_for_key($record, $key); |
|
71
|
|
|
|
|
|
|
|
|
72
|
21
|
50
|
|
|
|
84
|
$db{$value} = [] unless ( $db{$value} ); |
|
73
|
21
|
|
|
|
|
34
|
push @{$db{$value}}, $record; |
|
|
21
|
|
|
|
|
88
|
|
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
|
|
76
|
5
|
|
|
|
|
35
|
$this->{'DB'} = \%db; |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub value_for_key { |
|
80
|
50
|
|
|
50
|
0
|
80
|
my $this = shift; |
|
81
|
50
|
|
|
|
|
65
|
my $record = shift; |
|
82
|
50
|
|
|
|
|
69
|
my $key = shift; |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
return join "\x1E", # ASCII record separator (RS) |
|
85
|
50
|
|
|
|
|
125
|
map { ${$record->guess_key_from_spec($_, 0)} } |
|
|
60
|
|
|
|
|
76
|
|
|
|
60
|
|
|
|
|
138
|
|
|
86
|
|
|
|
|
|
|
split /,/, $key; |
|
87
|
|
|
|
|
|
|
} |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
sub accept_record { |
|
90
|
25
|
|
|
25
|
0
|
36
|
my $this = shift; |
|
91
|
25
|
|
|
|
|
29
|
my $record = shift; |
|
92
|
|
|
|
|
|
|
|
|
93
|
25
|
|
|
|
|
47
|
my $value = $this->value_for_key($record, $this->{'INPUT_KEY'}); |
|
94
|
|
|
|
|
|
|
|
|
95
|
25
|
|
|
|
|
44
|
my $db = $this->{'DB'}; |
|
96
|
|
|
|
|
|
|
|
|
97
|
25
|
100
|
|
|
|
61
|
if(my $db_records = $db->{$value}) { |
|
|
|
100
|
|
|
|
|
|
|
98
|
14
|
|
|
|
|
25
|
foreach my $db_record (@$db_records) { |
|
99
|
14
|
50
|
|
|
|
28
|
if ($this->{'ACCUMULATE_RIGHT'}) { |
|
100
|
0
|
0
|
|
|
|
0
|
if ($this->{'OPERATION'}) { |
|
101
|
0
|
|
|
|
|
0
|
$this->run_expression($db_record, $record); |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
else { |
|
104
|
0
|
|
|
|
|
0
|
foreach my $this_key (keys %$record) { |
|
105
|
0
|
0
|
|
|
|
0
|
if (!exists($db_record->{$this_key})) { |
|
106
|
0
|
|
|
|
|
0
|
$db_record->{$this_key} = $record->{$this_key}; |
|
107
|
|
|
|
|
|
|
} |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
} |
|
110
|
|
|
|
|
|
|
} |
|
111
|
|
|
|
|
|
|
else { |
|
112
|
14
|
100
|
|
|
|
27
|
if ($this->{'OPERATION'}) { |
|
113
|
3
|
|
|
|
|
14
|
my $output_record = App::RecordStream::Record->new(%$db_record); |
|
114
|
3
|
|
|
|
|
10
|
$this->run_expression($output_record, $record); |
|
115
|
3
|
|
|
|
|
10
|
$this->push_record($output_record); |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
else { |
|
118
|
11
|
|
|
|
|
52
|
$this->push_record(App::RecordStream::Record->new(%$record, %$db_record)); |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
|
|
121
|
14
|
100
|
|
|
|
34
|
if ($this->{'KEEP_LEFT'}) { |
|
122
|
3
|
|
|
|
|
7
|
$this->{'KEYS_PRINTED'}->{$value} = 1; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
} |
|
126
|
|
|
|
|
|
|
} |
|
127
|
|
|
|
|
|
|
elsif ($this->{'KEEP_RIGHT'}) { |
|
128
|
2
|
|
|
|
|
6
|
$this->push_record($record); |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
|
|
131
|
25
|
|
|
|
|
114
|
return 1; |
|
132
|
|
|
|
|
|
|
} |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# TODO: shove down into executor |
|
135
|
|
|
|
|
|
|
sub run_expression { |
|
136
|
3
|
|
|
3
|
0
|
4
|
my $__MY__this = shift; |
|
137
|
3
|
|
|
|
|
5
|
my $d = shift; |
|
138
|
3
|
|
|
|
|
4
|
my $i = shift; |
|
139
|
|
|
|
|
|
|
|
|
140
|
2
|
|
|
2
|
|
16
|
no strict; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
75
|
|
|
141
|
2
|
|
|
2
|
|
13
|
no warnings; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
674
|
|
|
142
|
3
|
|
|
|
|
143
|
eval $__MY__this->{'OPERATION'}; |
|
143
|
|
|
|
|
|
|
|
|
144
|
3
|
50
|
|
|
|
16
|
if ( $@ ) { |
|
145
|
0
|
|
|
|
|
0
|
warn "Code died with $@\n"; |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
} |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub stream_done { |
|
150
|
5
|
|
|
5
|
0
|
9
|
my $this = shift; |
|
151
|
5
|
100
|
|
|
|
14
|
if ($this->{'KEEP_LEFT'}) { |
|
152
|
1
|
|
|
|
|
2
|
foreach my $db_records (values %{$this->{'DB'}}) { |
|
|
1
|
|
|
|
|
4
|
|
|
153
|
4
|
|
|
|
|
8
|
foreach my $db_record (@$db_records) { |
|
154
|
4
|
|
|
|
|
9
|
my $value = $this->value_for_key($db_record, $this->{'DB_KEY'}); |
|
155
|
4
|
100
|
|
|
|
12
|
if (!exists($this->{'KEYS_PRINTED'}->{$value})) { |
|
156
|
1
|
|
|
|
|
3
|
$this->push_record($db_record); |
|
157
|
|
|
|
|
|
|
} |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
} |
|
161
|
|
|
|
|
|
|
} |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
sub add_help_types { |
|
164
|
5
|
|
|
5
|
0
|
10
|
my $this = shift; |
|
165
|
5
|
|
|
|
|
18
|
$this->use_help_type('keyspecs'); |
|
166
|
5
|
|
|
|
|
13
|
$this->use_help_type('snippet'); |
|
167
|
5
|
|
|
|
|
19
|
$this->add_help_type( |
|
168
|
|
|
|
|
|
|
'full', |
|
169
|
|
|
|
|
|
|
\&full_help, |
|
170
|
|
|
|
|
|
|
'Help on join types and accumulate-right' |
|
171
|
|
|
|
|
|
|
); |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub full_help { |
|
175
|
0
|
|
|
0
|
0
|
|
print <
|
|
176
|
|
|
|
|
|
|
Join Types |
|
177
|
|
|
|
|
|
|
For instance, if you did: |
|
178
|
|
|
|
|
|
|
recs-join type typeName dbfile fromfile |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
with a db file like: |
|
181
|
|
|
|
|
|
|
{ 'typeName': 'foo', 'hasSetting': 1 } |
|
182
|
|
|
|
|
|
|
{ 'typeName': 'bar', 'hasSetting': 0 } |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
and joined that with |
|
185
|
|
|
|
|
|
|
{ 'name': 'something', 'type': 'foo'} |
|
186
|
|
|
|
|
|
|
{ 'name': 'blarg', 'type': 'hip'} |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
for an inner (default) join, you would get |
|
189
|
|
|
|
|
|
|
{ 'name': 'something', 'type': 'foo', 'typeName': 'foo', 'hasSetting': 1} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
for an outer join, you would get |
|
192
|
|
|
|
|
|
|
{ 'name': 'something', 'type': 'foo', 'typeName': 'foo', 'hasSetting': 1} |
|
193
|
|
|
|
|
|
|
{ 'name': 'blarg', 'type': 'hip'} |
|
194
|
|
|
|
|
|
|
{ 'typeName': 'bar', 'hasSetting': 0 } |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
for a left join, you would get |
|
197
|
|
|
|
|
|
|
{ 'name': 'something', 'type': 'foo', 'typeName': 'foo', 'hasSetting': 1} |
|
198
|
|
|
|
|
|
|
{ 'typeName': 'bar', 'hasSetting': 0 } |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
for a right join, you would get |
|
201
|
|
|
|
|
|
|
{ 'name': 'something', 'type': 'foo', 'typeName': 'foo', 'hasSetting': 1} |
|
202
|
|
|
|
|
|
|
{ 'name': 'blarg', 'type': 'hip'} |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
Accumulate Right: |
|
205
|
|
|
|
|
|
|
Accumulate all input records with the same key onto each db record matching |
|
206
|
|
|
|
|
|
|
that key. This means that a db record can have multiple input records merged |
|
207
|
|
|
|
|
|
|
into it. If no operation is provided, any fields in second or later records |
|
208
|
|
|
|
|
|
|
will be lost due to them being discarded. This option is most useful with a |
|
209
|
|
|
|
|
|
|
user defined operation to handle collisions. For example, one could provide |
|
210
|
|
|
|
|
|
|
an operation to add fields together: |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
recs-join --left --operation ' |
|
213
|
|
|
|
|
|
|
foreach \$k (keys \%\$i) { |
|
214
|
|
|
|
|
|
|
if (exists(\$d->{\$k})) { |
|
215
|
|
|
|
|
|
|
if (\$k =~ /^value/) {\$d->{\$k} = \$d->{\$k} + \$i->{\$k};} |
|
216
|
|
|
|
|
|
|
} else { |
|
217
|
|
|
|
|
|
|
\$d->{\$k} = \$i->{\$k}; |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
}' --accumulate-right name name dbfile inputfile |
|
220
|
|
|
|
|
|
|
HELP_FULL |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub usage { |
|
224
|
0
|
|
|
0
|
0
|
|
my $this = shift; |
|
225
|
0
|
|
|
|
|
|
my $message = shift; |
|
226
|
|
|
|
|
|
|
|
|
227
|
0
|
|
|
|
|
|
my $options = [ |
|
228
|
|
|
|
|
|
|
['left', 'Do a left join'], |
|
229
|
|
|
|
|
|
|
['right', 'Do a right join'], |
|
230
|
|
|
|
|
|
|
['inner', 'Do an inner join (This is the default)'], |
|
231
|
|
|
|
|
|
|
['outer', 'Do an outer join'], |
|
232
|
|
|
|
|
|
|
['operation', 'An perl expression to evaluate for merging two records together, in place of the default behavior of db fields overwriting input fields. See "Operation" below.'], |
|
233
|
|
|
|
|
|
|
['accumulate-right', 'Accumulate all input records with the same key onto each db record matching that key. See "Accumulate Right" below.'], |
|
234
|
|
|
|
|
|
|
]; |
|
235
|
|
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
my $args_string = $this->options_string($options); |
|
237
|
|
|
|
|
|
|
|
|
238
|
0
|
|
|
|
|
|
return <
|
|
239
|
|
|
|
|
|
|
$message |
|
240
|
|
|
|
|
|
|
Usage: recs-join [] |
|
241
|
|
|
|
|
|
|
__FORMAT_TEXT__ |
|
242
|
|
|
|
|
|
|
Records of input (or records from ) are joined against records in |
|
243
|
|
|
|
|
|
|
, using field from input and field from . |
|
244
|
|
|
|
|
|
|
Each record from input may match 0, 1, or more records from . Each |
|
245
|
|
|
|
|
|
|
pair of matches will be combined to form a larger record, with fields from |
|
246
|
|
|
|
|
|
|
the dbfile overwriting fields from the input stream. If the join is a left |
|
247
|
|
|
|
|
|
|
join or inner join, any inputs that do not match a dbfile record are |
|
248
|
|
|
|
|
|
|
discarded. If the join is a right join or inner join, any db records that do |
|
249
|
|
|
|
|
|
|
not match an input record are discarded. |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
dbkey and inputkey may be key specs, see '--help-keyspecs' for more |
|
252
|
|
|
|
|
|
|
information. Separate multiple keyspecs with commas to use a composite key. |
|
253
|
|
|
|
|
|
|
__FORMAT_TEXT__ |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
Arguments: |
|
256
|
|
|
|
|
|
|
$args_string |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
Operation: |
|
259
|
|
|
|
|
|
|
__FORMAT_TEXT__ |
|
260
|
|
|
|
|
|
|
The expression provided is evaluated for every pair of db record and input |
|
261
|
|
|
|
|
|
|
record that have matching keys, in place of the default operation to |
|
262
|
|
|
|
|
|
|
overwrite input fields with db fields. The variable \$d is set to a |
|
263
|
|
|
|
|
|
|
App::RecordStream::Record object for the db record, and \$i is set to a |
|
264
|
|
|
|
|
|
|
App::RecordStream::Record object for the input record. The \$d record is |
|
265
|
|
|
|
|
|
|
used for the result. Thus, if you provide an empty operation, the result |
|
266
|
|
|
|
|
|
|
will contain only fields from the db record. Note that an empty operation is |
|
267
|
|
|
|
|
|
|
different from no --operation at all. |
|
268
|
|
|
|
|
|
|
__FORMAT_TEXT__ |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
Examples: |
|
271
|
|
|
|
|
|
|
Join type from STDIN and typeName from dbfile |
|
272
|
|
|
|
|
|
|
cat recs | recs-join type typeName dbfile |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
Join host name from a mapping file to machines to get IPs |
|
275
|
|
|
|
|
|
|
recs-join host host hostIpMapping machines |
|
276
|
|
|
|
|
|
|
USAGE |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
1; |