File Coverage

blib/lib/MongoDB/BulkWriteResult.pm
Criterion Covered Total %
statement 30 116 25.8
branch 0 36 0.0
condition 0 11 0.0
subroutine 10 16 62.5
pod 0 1 0.0
total 40 180 22.2


line stmt bran cond sub pod time code
1             # Copyright 2014 - present MongoDB, Inc.
2             #
3             # Licensed under the Apache License, Version 2.0 (the "License");
4             # you may not use this file except in compliance with the License.
5             # You may obtain a copy of the License at
6             #
7             # http://www.apache.org/licenses/LICENSE-2.0
8             #
9             # Unless required by applicable law or agreed to in writing, software
10             # distributed under the License is distributed on an "AS IS" BASIS,
11             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12             # See the License for the specific language governing permissions and
13             # limitations under the License.
14              
15 59     59   1156 use strict;
  59         141  
  59         1805  
16 59     59   338 use warnings;
  59         147  
  59         2006  
17             package MongoDB::BulkWriteResult;
18              
19             # ABSTRACT: MongoDB bulk write result document
20              
21 59     59   347 use version;
  59         176  
  59         352  
22             our $VERSION = 'v2.2.0';
23              
24             # empty superclass for backcompatibility; add a variable to the
25             # package namespace so Perl thinks it's a real package
26             $MongoDB::WriteResult::VERSION = $VERSION;
27              
28 59     59   5695 use Moo;
  59         140  
  59         398  
29 59     59   18631 use MongoDB::Error;
  59         227  
  59         6192  
30 59     59   450 use MongoDB::_Constants;
  59         152  
  59         7669  
31 59         517 use MongoDB::_Types qw(
32             ArrayOfHashRef
33             Numish
34 59     59   481 );
  59         149  
35 59         438 use Types::Standard qw(
36             HashRef
37             Undef
38 59     59   70180 );
  59         167  
39 59     59   47256 use namespace::clean;
  59         147  
  59         432  
40              
41             # fake empty superclass for backcompat
42             our @ISA;
43             push @ISA, 'MongoDB::WriteResult';
44              
45             with $_ for qw(
46             MongoDB::Role::_PrivateConstructor
47             MongoDB::Role::_WriteResult
48             );
49              
50             has [qw/upserted inserted/] => (
51             is => 'ro',
52             required => 1,
53             isa => ArrayOfHashRef,
54             );
55              
56             has inserted_ids => (
57             is => 'lazy',
58             builder => '_build_inserted_ids',
59             init_arg => undef,
60             isa => HashRef,
61             );
62              
63             sub _build_inserted_ids {
64 0     0     my ($self) = @_;
65 0           return { map { $_->{index}, $_->{_id} } @{ $self->inserted } };
  0            
  0            
66             }
67              
68             has upserted_ids => (
69             is => 'lazy',
70             builder => '_build_upserted_ids',
71             init_arg => undef,
72             isa => HashRef,
73             );
74              
75             sub _build_upserted_ids {
76 0     0     my ($self) = @_;
77 0           return { map { $_->{index}, $_->{_id} } @{ $self->upserted } };
  0            
  0            
78             }
79              
80             for my $attr (qw/inserted_count upserted_count matched_count deleted_count/) {
81             has $attr => (
82             is => 'ro',
83             writer => "_set_$attr",
84             required => 1,
85             isa => Numish,
86             );
87             }
88              
89             # This should always be initialized either as a number or as undef so that
90             # merges accumulate correctly. It should be undef if talking to a server < 2.6
91             # or if talking to a mongos and not getting the field back from an update. The
92             # default is undef, which will be sticky and ensure this field stays undef.
93              
94             has modified_count => (
95             is => 'ro',
96             writer => '_set_modified_count',
97             required => 1,
98             isa => (Numish|Undef),
99             );
100              
101             sub has_modified_count {
102 0     0 0   my ($self) = @_;
103 0           return defined( $self->modified_count );
104             }
105              
106             has op_count => (
107             is => 'ro',
108             writer => '_set_op_count',
109             required => 1,
110             isa => Numish,
111             );
112              
113             has batch_count => (
114             is => 'ro',
115             writer => '_set_batch_count',
116             required => 1,
117             isa => Numish,
118             );
119              
120             #--------------------------------------------------------------------------#
121             # emulate old API
122             #--------------------------------------------------------------------------#
123              
124             my %OLD_API_ALIASING = (
125             nInserted => 'inserted_count',
126             nUpserted => 'upserted_count',
127             nMatched => 'matched_count',
128             nModified => 'modified_count',
129             nRemoved => 'deleted_count',
130             writeErrors => 'write_errors',
131             writeConcernErrors => 'write_concern_errors',
132             count_writeErrors => 'count_write_errors',
133             count_writeConcernErrors => 'count_write_concern_errors',
134             );
135              
136             while ( my ( $old, $new ) = each %OLD_API_ALIASING ) {
137 59     59   75463 no strict 'refs';
  59         156  
  59         66864  
138             *{$old} = \&{$new};
139             }
140              
141             #--------------------------------------------------------------------------#
142             # private functions
143             #--------------------------------------------------------------------------#
144              
145             # defines how an logical operation type gets mapped to a result
146             # field from the actual command result
147             my %op_map = (
148             insert => [ inserted_count => sub { $_[0]->{n} } ],
149             delete => [ deleted_count => sub { $_[0]->{n} } ],
150             update => [ matched_count => sub { $_[0]->{n} } ],
151             upsert => [ matched_count => sub { $_[0]->{n} - @{ $_[0]->{upserted} || [] } } ],
152             );
153              
154             my @op_map_keys = sort keys %op_map;
155              
156             sub _parse_cmd_result {
157 0     0     my $class = shift;
158 0 0         my $args = ref $_[0] eq 'HASH' ? shift : {@_};
159              
160 0 0         unless ( 2 == grep { exists $args->{$_} } qw/op result/ ) {
  0            
161 0           MongoDB::UsageError->throw("parse requires 'op' and 'result' arguments");
162             }
163              
164             my ( $op, $op_count, $batch_count, $result, $cmd_doc ) =
165 0           @{$args}{qw/op op_count batch_count result cmd_doc/};
  0            
166              
167             $result = $result->output
168 0 0         if eval { $result->isa("MongoDB::CommandResult") };
  0            
169              
170             MongoDB::UsageError->throw("op argument to parse must be one of: @op_map_keys")
171 0 0         unless grep { $op eq $_ } @op_map_keys;
  0            
172 0 0         MongoDB::UsageError->throw("results argument to parse must be a hash reference")
173             unless ref $result eq 'HASH';
174              
175 0 0 0       my %attrs = (
176             batch_count => $batch_count || 1,
177             $op_count ? ( op_count => $op_count ) : (),
178             inserted_count => 0,
179             upserted_count => 0,
180             matched_count => 0,
181             deleted_count => 0,
182             upserted => [],
183             inserted => [],
184             );
185              
186 0 0         $attrs{write_errors} = $result->{writeErrors} ? $result->{writeErrors} : [];
187              
188             # rename writeConcernError -> write_concern_errors; coerce it to arrayref
189              
190             $attrs{write_concern_errors} =
191 0 0         $result->{writeConcernError} ? [ $result->{writeConcernError} ] : [];
192              
193             # if we have upserts, change type to calculate differently
194 0 0         if ( $result->{upserted} ) {
195 0           $op = 'upsert';
196 0           $attrs{upserted} = $result->{upserted};
197 0           $attrs{upserted_count} = @{ $result->{upserted} };
  0            
198             }
199              
200             # recover _ids from documents
201 0 0 0       if ( exists($result->{n}) && $op eq 'insert' ) {
202 0           my @pairs;
203 0           my $docs = {@$cmd_doc}->{documents};
204 0           for my $i ( 0 .. $result->{n}-1 ) {
205 0           push @pairs, { index => $i, _id => $docs->[$i]{metadata}{_id} };
206             }
207 0           $attrs{inserted} = \@pairs;
208             }
209              
210             # change 'n' into an op-specific count
211 0 0         if ( exists $result->{n} ) {
212 0           my ( $key, $builder ) = @{ $op_map{$op} };
  0            
213 0           $attrs{$key} = $builder->($result);
214             }
215              
216             # for an update/upsert we want the exact response whether numeric or undef
217             # so that new undef responses become sticky; for all other updates, we
218             # consider it 0 and let it get sorted out in the merging
219              
220             $attrs{modified_count} = ( $op eq 'update' || $op eq 'upsert' ) ?
221 0 0 0       $result->{nModified} : 0;
222              
223 0           return $class->_new(%attrs);
224             }
225              
226             # these are for single results only
227             sub _parse_write_op {
228 0     0     my $class = shift;
229 0           my $op = shift;
230              
231 0           my %attrs = (
232             batch_count => 1,
233             op_count => 1,
234             write_errors => $op->write_errors,
235             write_concern_errors => $op->write_concern_errors,
236             inserted_count => 0,
237             upserted_count => 0,
238             matched_count => 0,
239             modified_count => undef,
240             deleted_count => 0,
241             upserted => [],
242             inserted => [],
243             );
244              
245 0           my $has_write_error = @{ $attrs{write_errors} };
  0            
246              
247             # parse by type
248 0           my $type = ref($op);
249 0 0         if ( $type eq 'MongoDB::InsertOneResult' ) {
    0          
    0          
250 0 0         if ( $has_write_error ) {
251 0           $attrs{inserted_count} = 0;
252 0           $attrs{inserted} = [];
253             }
254             else {
255 0           $attrs{inserted_count} = 1;
256 0           $attrs{inserted} = [ { index => 0, _id => $op->inserted_id } ];
257             }
258             }
259             elsif ( $type eq 'MongoDB::DeleteResult' ) {
260 0           $attrs{deleted_count} = $op->deleted_count;
261             }
262             elsif ( $type eq 'MongoDB::UpdateResult' ) {
263 0 0         if ( defined $op->upserted_id ) {
264 0           my $upsert = { index => 0, _id => $op->upserted_id };
265 0           $attrs{upserted} = [$upsert];
266 0           $attrs{upserted_count} = 1;
267             # modified_count *must* always be defined for 2.6+ servers
268             # matched_count is here for clarity and consistency
269 0           $attrs{matched_count} = 0;
270 0           $attrs{modified_count} = 0;
271             }
272             else {
273 0           $attrs{matched_count} = $op->matched_count;
274 0           $attrs{modified_count} = $op->modified_count;
275             }
276             }
277             else {
278 0           MongoDB::InternalError->throw("can't parse unknown result class $op");
279             }
280              
281 0           return $class->_new(%attrs);
282             }
283              
284             sub _merge_result {
285 0     0     my ( $self, $result ) = @_;
286              
287             # Add simple counters
288 0           for my $attr (qw/inserted_count upserted_count matched_count deleted_count/) {
289 0           my $setter = "_set_$attr";
290 0           $self->$setter( $self->$attr + $result->$attr );
291             }
292              
293             # If modified_count is defined in both results we're merging, then we're
294             # talking to a 2.6+ mongod or we're talking to a 2.6+ mongos and have only
295             # seen responses with modified_count. In any other case, we set
296             # modified_count to undef, which then becomes "sticky"
297              
298 0 0 0       if ( defined $self->modified_count && defined $result->modified_count ) {
299 0           $self->_set_modified_count( $self->modified_count + $result->modified_count );
300             }
301             else {
302 0           $self->_set_modified_count(undef);
303             }
304              
305             # Append error and upsert docs, but modify index based on op count
306 0           my $op_count = $self->op_count;
307 0           for my $attr (qw/write_errors upserted inserted/) {
308 0           for my $doc ( @{ $result->$attr } ) {
  0            
309 0           $doc->{index} += $op_count;
310             }
311 0           push @{ $self->$attr }, @{ $result->$attr };
  0            
  0            
312             }
313              
314             # Append write concern errors without modification (they have no index)
315 0           push @{ $self->write_concern_errors }, @{ $result->write_concern_errors };
  0            
  0            
316              
317 0           $self->_set_op_count( $op_count + $result->op_count );
318 0           $self->_set_batch_count( $self->batch_count + $result->batch_count );
319              
320 0           return 1;
321             }
322              
323             1;
324              
325             __END__