#
# Copyright 2014 MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use strict;
use warnings;
package MongoDB::Role::_SingleBatchDocWrite;
# MongoDB interface for database insert/update/delete operations
use version;
our $VERSION = 'v1.6.0';
use Moo::Role;
use MongoDB::CommandResult;
use MongoDB::Error;
use MongoDB::UnacknowledgedResult;
use MongoDB::_Constants;
use MongoDB::_Protocol;
use MongoDB::_Types qw(
WriteConcern
);
use namespace::clean;
with $_ for qw(
MongoDB::Role::_WriteOp
);
requires qw/db_name write_concern _parse_cmd _parse_gle/;
sub _send_legacy_op_with_gle {
my ( $self, $link, $op_bson, $op_doc, $result_class ) = @_;
my $wc_args = $self->write_concern->as_args();
my @write_concern = scalar @$wc_args ? %{ $wc_args->[1] } : ();
my $gle = $self->bson_codec->encode_one( [ getlasterror => 1, @write_concern ] );
my ( $gle_bson, $request_id ) =
MongoDB::_Protocol::write_query( $self->db_name . '.$cmd', $gle, undef, 0, -1 );
# write op sent as a unit with GLE command to ensure GLE applies to the
# operation without other operations in between
$op_bson .= $gle_bson;
if ( length($op_bson) > MAX_BSON_WIRE_SIZE ) {
# XXX should this become public?
MongoDB::_CommandSizeError->throw(
message => "database command too large",
size => length $op_bson,
);
}
$link->write( $op_bson ),
( my $result = MongoDB::_Protocol::parse_reply( $link->read, $request_id ) );
my $res = $self->bson_codec->decode_one( $result->{docs} );
# errors in the command itself get handled as normal CommandResult
if ( !$res->{ok} && ( $res->{errmsg} || $res->{'$err'} ) ) {
return MongoDB::CommandResult->_new(
output => $res,
address => $link->address,
);
}
# 'ok' false means GLE itself failed
# usually we shouldn't check wnote or jnote, but the Bulk API QA test says we should
# detect no journal or replication not enabled, so we check for special strings.
# These strings were checked back to MongoDB 1.8.5.
my $got_error =
( exists( $res->{jnote} ) && $res->{jnote} =~ NO_JOURNAL_RE ) ? $res->{jnote}
: ( exists( $res->{wnote} ) && $res->{wnote} =~ NO_REPLICATION_RE ) ? $res->{wnote}
: undef;
if ($got_error) {
MongoDB::DatabaseError->throw(
message => $got_error,
result => MongoDB::CommandResult->_new(
output => $res,
address => $link->address,
),
);
}
# otherwise, construct the desired result object, calling back
# on class-specific parser to generate additional attributes
my ( $write_concern_error, $write_error );
my $errmsg = $res->{err};
my $wtimeout = $res->{wtimeout};
if ($wtimeout) {
$write_concern_error = {
errmsg => $errmsg,
errInfo => { wtimeout => $wtimeout },
code => $res->{code} || WRITE_CONCERN_ERROR,
};
}
elsif ($errmsg) {
$write_error = {
errmsg => $errmsg,
code => $res->{code} || UNKNOWN_ERROR,
index => 0,
op => $op_doc,
};
}
return $result_class->_new(
acknowledged => 1,
write_errors => ( $write_error ? [$write_error] : [] ),
write_concern_errors => ( $write_concern_error ? [$write_concern_error] : [] ),
$self->_parse_gle( $res, $op_doc ),
);
}
sub _send_legacy_op_noreply {
my ( $self, $link, $op_bson, $op_doc, $result_class ) = @_;
$link->write($op_bson);
return MongoDB::UnacknowledgedResult->_new(
$self->_parse_gle( {}, $op_doc ),
acknowledged => 0,
write_errors => [],
write_concern_errors => [],
);
}
sub _send_write_command {
my ( $self, $link, $cmd, $op_doc, $result_class ) = @_;
# send command and get response document
my $command = $self->bson_codec->encode_one( $cmd );
my ( $op_bson, $request_id ) =
MongoDB::_Protocol::write_query( $self->db_name . '.$cmd',
$command, undef, 0, -1, undef );
if ( length($op_bson) > MAX_BSON_WIRE_SIZE ) {
# XXX should this become public?
MongoDB::_CommandSizeError->throw(
message => "database command too large",
size => length $op_bson,
);
}
$link->write( $op_bson ),
( my $result = MongoDB::_Protocol::parse_reply( $link->read, $request_id ) );
my $res = $self->bson_codec->decode_one( $result->{docs} );
# Error checking depends on write concern
if ( $self->write_concern->is_acknowledged ) {
# errors in the command itself get handled as normal CommandResult
if ( !$res->{ok} && ( $res->{errmsg} || $res->{'$err'} ) ) {
return MongoDB::CommandResult->_new(
output => $res,
address => $link->address,
);
}
# if an error occurred, add the op document involved
if ( exists($res->{writeErrors}) && @{$res->{writeErrors}} ) {
$res->{writeErrors}[0]{op} = $op_doc;
}
# otherwise, construct the desired result object, calling back
# on class-specific parser to generate additional attributes
return $result_class->_new(
write_errors => ( $res->{writeErrors} ? $res->{writeErrors} : [] ),
write_concern_errors =>
( $res->{writeConcernError} ? [ $res->{writeConcernError} ] : [] ),
$self->_parse_cmd($res),
);
}
else {
return MongoDB::UnacknowledgedResult->_new(
write_errors => [],
write_concern_errors => [],
);
}
}
1;