The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

# Copyright 2014 - present 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
#
#
# 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 utf8;
use Test::More 0.88;
use Test::Deep 0.111 qw/!blessed/;
use Scalar::Util qw/refaddr/;
use boolean;
use MongoDB;
use lib "t/lib";
use MongoDBTest qw/skip_unless_mongod build_client get_test_db server_version/;
skip_unless_mongod();
my $conn = build_client();
my $testdb = get_test_db($conn);
my $coll = $testdb->get_collection("test_collection");
my $server_version = server_version($conn);
my $ismaster = $testdb->run_command( { ismaster => 1 } );
my $server_status = $testdb->run_command( { serverStatus => 1 } );
# Standalone in "--master" mode will have serverStatus.repl, but ordinary
# standalone won't
my $is_standalone = $conn->topology_type eq 'Single' && ! exists $server_status->{repl};
my $server_does_bulk = $server_version >= v2.5.5;
my $supports_collation = $server_version >= v3.3.9;
my $case_insensitive_collation = { locale => "en_US", strength => 2 };
sub _truncate {
return( length($_[0]) > 1600 ? (substr($_[0],0,1600)."...") : $_[0] );
}
sub _bulk_write_result {
return MongoDB::BulkWriteResult->new(
acknowledged => 1,
write_errors => [],
write_concern_errors => [],
modified_count => 0,
inserted_count => 0,
upserted_count => 0,
matched_count => 0,
deleted_count => 0,
upserted => [],
inserted => [],
batch_count => 0,
op_count => 0,
@_,
);
}
sub _number_of_servers {
return 0 unless $ismaster->{hosts};
return @{ $ismaster->{hosts} } + @{ $ismaster->{passives} // [] };
}
subtest "constructors" => sub {
my @constructors = qw(
initialize_ordered_bulk_op initialize_unordered_bulk_op
ordered_bulk unordered_bulk
);
for my $method (@constructors) {
my $bulk = $coll->$method;
isa_ok( $bulk, 'MongoDB::BulkWrite', $method );
if ( $method =~ /unordered/ ) {
ok( !$bulk->ordered, "ordered attr is false" );
}
else {
ok( $bulk->ordered, "ordered attr is true" );
}
is(
refaddr $bulk->collection,
refaddr $coll,
"MongoDB::BulkWrite holds ref to originating Collection"
);
}
};
note("QA-477 INSERT");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: insert errors" => sub {
my $bulk = $coll->$method;
# raise errors on wrong arg types
my %bad_args = (
LIST => [ {}, {} ],
EMPTY => [],
);
for my $k ( sort keys %bad_args ) {
like(
exception { $bulk->insert_one( @{ $bad_args{$k} } ) },
qr/reference/,
"insert( $k ) throws an error"
);
}
like(
exception { $bulk->insert_one( 'foo' ) },
qr/reference/,
"insert( 'foo' ) throws an error"
);
like(
exception { $bulk->insert_one( ['foo'] ) },
qr{must have key/value pairs},
"insert( ['foo'] ) throws an error",
);
like(
exception { $bulk->find( {} )->insert_one( {} ) },
qr/^Can't locate object method "insert_one"/,
"find({})->insert_one({}) throws an error",
);
is( exception { $bulk->insert_one( { '$key' => 1 } ) },
undef, "queuing insertion of document with \$key is allowed" );
my $err = exception { $bulk->execute };
isa_ok( $err, 'MongoDB::WriteError', "executing insertion with \$key" );
};
subtest "$method: successful insert" => sub {
$coll->drop;
my $bulk = $coll->$method;
is( $coll->count_documents({}), 0, "no docs in collection" );
$bulk->insert_one( { _id => 1 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on insert" ) or diag _truncate explain $err;
is( $coll->count_documents({}), 1, "one doc in collection" );
# test empty superclass
isa_ok( $result, 'MongoDB::WriteResult', "result object" );
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
inserted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
op_count => 1,
batch_count => 1,
inserted => [ { index => 0, _id => 1 } ],
),
"result object correct"
) or diag _truncate explain $result;
};
subtest "$method insert without _id" => sub {
$coll->drop;
my $bulk = $coll->$method;
is( $coll->count_documents({}), 0, "no docs in collection" );
my $doc = {};
$bulk->insert_one( $doc );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on insert" ) or diag _truncate explain $err;
is( $coll->count_documents({}), 1, "one doc in collection" );
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
inserted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
op_count => 1,
batch_count => 1,
inserted => [ { index => 0, _id => obj_isa("BSON::OID") } ],
),
"result object correct"
);
my $id = $coll->find_one()->{_id};
# OID PIDs are the low 16 bits
is( $id->_get_pid, $$ & 0xffff, "generated ID has our PID" );
};
}
note("QA-477 FIND");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: find" => sub {
my $bulk = $coll->$method;
like(
exception { $bulk->find },
qr/find requires a criteria document/,
"find without doc selector throws exception"
);
};
}
note("QA-477 UPDATE and UPDATE_ONE");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "update and update_one errors with $method" => sub {
my $bulk;
# raise errors on wrong arg types
my %bad_args = (
SCALAR => ['foo'],
EMPTY => [], # not in QA test
);
for my $update (qw/update_many update_one/) {
$bulk = $coll->$method;
for my $k ( sort keys %bad_args ) {
like(
exception { $bulk->find( {} )->$update( @{ $bad_args{$k} } ) },
qr/argument to $update must be a single hashref, arrayref, Tie::IxHash or BSON::Array/,
"$update( $k ) throws an error"
);
}
$bulk = $coll->$method;
like(
exception { $bulk->$update( { '$set' => { x => 1 } } ) },
qr/^Can't locate object method "$update"/,
"$update on bulk object (without find) throws an error",
);
$bulk = $coll->$method;
$bulk->find( {} )->$update( { key => 1 } );
like(
exception { $bulk->execute },
qr/update document must only contain update operators/,
"single non-op key in $update doc throws exception"
);
$bulk = $coll->$method;
$bulk->find( {} )->$update( [ key => 1, '$key' => 1 ]);
like(
exception { $bulk->execute },
qr/update document must only contain update operators/,
"first non-op key in $update doc throws exception"
);
}
};
subtest "update all docs with $method" => sub {
$coll->drop;
my $bulk = $coll->$method;
$coll->insert_one($_) for map { { key => $_ } } 1, 2;
my @docs = $coll->find( {} )->all;
$bulk->find( {} )->update_many( { '$set' => { x => 3 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
matched_count => 2,
modified_count => ( $server_does_bulk ? 2 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
if ( $server_does_bulk ) {
ok( $result->has_modified_count, "newer server has_modified_count" );
}
else {
ok( ! $result->has_modified_count, "older server has_modified_count" );
}
# check expected values
$_->{x} = num(3) for @docs;
cmp_deeply( [ $coll->find( {} )->all ], \@docs, "all documents updated" );
};
subtest "update only matching docs with $method" => sub {
$coll->drop;
my $bulk = $coll->$method;
$coll->insert_one($_) for map { { key => $_ } } 1, 2;
my @docs = $coll->find( {} )->all;
$bulk->find( { key => 1 } )->update_many( { '$set' => { x => 1 } } );
$bulk->find( { key => 2 } )->update_many( { '$set' => { x => 2 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
is_deeply(
$result,
_bulk_write_result(
matched_count => 2,
modified_count => ( $server_does_bulk ? 2 : undef ),
op_count => 2,
batch_count => $server_does_bulk ? 1 : 2,
),
"result object correct"
);
# check expected values
$_->{x} = $_->{key} for @docs;
cmp_deeply( [ $coll->find( {} )->all ], \@docs, "all documents updated" );
};
subtest "update_one with $method" => sub {
$coll->drop;
my $bulk = $coll->$method;
$coll->insert_one($_) for map { { key => $_ } } 1, 2;
$bulk->find( {} )->update_one( { '$set' => { key => 3 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
is_deeply(
$result,
_bulk_write_result(
matched_count => 1,
modified_count => ( $server_does_bulk ? 1 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
);
# check expected values
is( $coll->count_documents( { key => 3 } ), 1, "one document updated" );
};
subtest "update and update_one with collation, using $method" => sub {
for my $update (qw/update_many update_one/) {
$coll->drop;
$coll->insert_one( { key => "a" } );
my $bulk = $coll->$method;
$bulk->insert_one( { key => "b" } );
$bulk->find( { key => "A" } )->collation($case_insensitive_collation)
->$update( { '$set' => { key => "b" } } );
my $err = exception { $bulk->execute };
if ($supports_collation) {
is( $err, undef, "bulk update_one w/ collation" );
is( $coll->count_documents( { key => "b" } ), 2, "collection updated" );
}
else {
like(
$err,
qr/MongoDB host '.*:\d+' doesn't support collation/,
"bulk update_one w/ collation returns error if unsupported"
);
is( $coll->count_documents( { key => "b" } ), 0, "collection not updated" );
}
}
};
}
note("QA-477 REPLACE_ONE");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "replace_one errors with $method" => sub {
my $bulk;
# raise errors on wrong arg types
my %bad_args = (
SCALAR => ['foo'],
EMPTY => [], # not in QA test
);
$bulk = $coll->$method;
for my $k ( sort keys %bad_args ) {
like(
exception { $bulk->find( {} )->replace_one( @{ $bad_args{$k} } ) },
qr/argument to replace_one must be a single hashref, arrayref, Tie::IxHash or BSON::Array/,
"replace_one( $k ) throws an error"
);
}
like(
exception { $bulk->replace_one( { '$set' => { x => 1 } } ) },
qr/^Can't locate object method "replace_one"/,
"replace_one on bulk object (without find) throws an error",
);
$bulk = $coll->$method;
$bulk->find( {} )->replace_one( { '$key' => 1 } );
like(
exception { $bulk->execute },
qr/replacement document must not contain update operators/,
"single op key in replace_one doc throws exception"
);
$bulk = $coll->$method;
$bulk->find( {} )->replace_one( [ '$key' => 1, key => 1 ] );
like(
exception { $bulk->execute },
qr/replacement document must not contain update operators/,
"mixed op and non-op key in replace_one doc throws exception"
);
};
subtest "replace_one with $method" => sub {
$coll->drop;
my $bulk = $coll->$method;
$coll->insert_one( { key => 1 } ) for 1 .. 2;
$bulk->find( {} )->replace_one( { key => 3 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
is_deeply(
$result,
_bulk_write_result(
matched_count => 1,
modified_count => ( $server_does_bulk ? 1 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
);
# check expected values
my $distinct = [ $coll->distinct("key")->all ];
cmp_deeply( $distinct, bag( num(1), num(3) ), "only one document replaced" );
};
subtest "replace_one with collation, using $method" => sub {
$coll->drop;
$coll->insert_one( { key => "a" } );
my $bulk = $coll->$method;
$bulk->insert_one( { key => "b" } );
$bulk->find( { key => "A" } )->collation($case_insensitive_collation)
->replace_one( { key => "b" } );
my $err = exception { $bulk->execute };
if ($supports_collation) {
is( $err, undef, "bulk replace_one w/ collation" );
is( $coll->count_documents( { key => "b" } ), 2, "collection updated" );
}
else {
like(
$err,
qr/MongoDB host '.*:\d+' doesn't support collation/,
"bulk replace_one w/ collation returns error if unsupported"
);
is( $coll->count_documents( { key => "b" } ), 0, "collection not updated" );
}
};
}
note("QA-477 UPSERT-UPDATE");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "upsert errors with $method" => sub {
my $bulk = $coll->$method;
like(
exception { $bulk->upsert() },
qr/^Can't locate object method "upsert"/,
"upsert on bulk object (without find) throws an error",
);
like(
exception { $bulk->find( {} )->upsert( {} ) },
qr/the upsert method takes no arguments/,
"upsert( NONEMPTY ) throws an error"
);
};
subtest "upsert-update insertion with $method" => sub {
$coll->drop;
my $bulk = $coll->$method;
$bulk->find( { key => 1 } )->update_many( { '$set' => { x => 1 } } );
$bulk->find( { key => 2 } )->upsert->update_many( { '$set' => { x => 2 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on upsert-update" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
upserted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
upserted => [ { index => 1, _id => ignore() } ],
op_count => 2,
batch_count => $server_does_bulk ? 1 : 2,
),
"result object correct"
) or diag _truncate explain $result;
cmp_deeply(
[ $coll->find( {} )->all ],
[ { _id => ignore(), key => num(2), x => num(2) } ],
"upserted document correct"
);
$bulk = $coll->$method;
$bulk->find( { key => 1 } )->update_many( { '$set' => { x => 1 } } );
$bulk->find( { key => 2 } )->upsert->update_many( { '$set' => { x => 2 } } );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on second upsert-update" ) or diag _truncate explain $err;
cmp_deeply(
$result,
_bulk_write_result(
matched_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
op_count => 2,
batch_count => $server_does_bulk ? 1 : 2,
),
"result object correct"
) or diag _truncate explain $result;
};
subtest "upsert-update updates with $method" => sub {
$coll->drop;
$coll->insert_one( { key => 1 } ) for 1 .. 2;
my @docs = $coll->find( {} )->all;
my $bulk = $coll->$method;
$bulk->find( { key => 1 } )->upsert->update_many( { '$set' => { x => 1 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on upsert-update" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
matched_count => 2,
modified_count => ( $server_does_bulk ? 2 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
$_->{x} = num(1) for @docs;
cmp_deeply( [ $coll->find( {} )->all ], \@docs, "all documents updated" );
};
subtest "upsert-update large doc with $method" => sub {
$coll->drop;
# QA test says big_string should be 16MiB - 31 long, but { _id => $oid,
# key => 1, x => $big_string } exceeds 16MiB when BSON encoded unless
# the bigstring is 16MiB - 41. This may be a peculiarity of Perl's
# BSON type encoding.
#
# Using legacy API, the bigstring must be 16MiB - 97 for some reason.
my $big_string = "a" x ( 16 * 1024 * 1024 - $server_does_bulk ? 41 : 97 );
my $bulk = $coll->$method;
$bulk->find( { key => "1" } )->upsert->update_many( { '$set' => { x => $big_string } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on upsert-update" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
upserted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
upserted => [ { index => 0, _id => ignore() } ],
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
};
}
note("QA-477 UPSERT-UPDATE_ONE");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "upsert-update_one insertion with $method" => sub {
$coll->drop;
my $bulk = $coll->$method;
$bulk->find( { key => 1 } )->update_one( { '$set' => { x => 1 } } ); # not upsert
$bulk->find( { key => 2 } )->upsert->update_one( { '$set' => { x => 2 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on upsert-update_one" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
upserted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
upserted => [ { index => 1, _id => ignore() } ],
op_count => 2,
batch_count => $server_does_bulk ? 1 : 2,
),
"result object correct"
) or diag _truncate explain $result;
cmp_deeply(
[ $coll->find( {} )->all ],
[ { _id => ignore(), key => num(2), x => num(2) } ],
"upserted document correct"
);
};
subtest "upsert-update_one (no insert) with $method" => sub {
$coll->drop;
$coll->insert_one( { key => 1 } ) for 1 .. 2;
my @docs = $coll->find( {} )->all;
my $bulk = $coll->$method;
$bulk->find( { key => 1 } )->upsert->update_one( { '$set' => { x => 2 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on upsert-update_one" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
matched_count => 1,
modified_count => ( $server_does_bulk ? 1 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
# add expected key to one document only
$docs[0]{x} = num(2);
my @got = $coll->find( {} )->all;
cmp_deeply( \@got, bag(@docs), "updated document correct" )
or diag _truncate explain \@got;
};
}
note("QA-477 UPSERT-REPLACE_ONE");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "upsert-replace_one insertion with $method" => sub {
$coll->drop;
my $bulk = $coll->$method;
$bulk->find( { key => 1 } )->replace_one( { x => 1 } ); # not upsert
$bulk->find( { key => 2 } )->upsert->replace_one( { x => 2 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on upsert-replace_one" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
upserted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
upserted => [ { index => 1, _id => ignore() } ],
op_count => 2,
batch_count => $server_does_bulk ? 1 : 2,
),
"result object correct"
) or diag _truncate explain $result;
cmp_deeply(
[ $coll->find( {} )->all ],
[ { _id => ignore(), x => num(2) } ],
"upserted document correct"
);
};
subtest "upsert-replace_one (no insert) with $method" => sub {
$coll->drop;
$coll->insert_one( { key => 1 } ) for 1 .. 2;
my @docs = $coll->find( {} )->all;
my $bulk = $coll->$method;
$bulk->find( { key => 1 } )->upsert->replace_one( { x => 2 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on upsert-replace_one" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
matched_count => 1,
modified_count => ( $server_does_bulk ? 1 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
# change one expected doc only
$docs[0]{x} = num(2);
delete $docs[0]{key};
my @got = $coll->find( {} )->all;
cmp_deeply( \@got, bag(@docs), "updated document correct" )
or diag _truncate explain \@got;
};
}
note("QA-477 delete_many");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "delete_many errors with $method" => sub {
my $bulk = $coll->$method;
like(
exception { $bulk->delete_many() },
qr/^Can't locate object method "delete_many"/,
"delete_many on bulk object (without find) throws an error",
);
};
subtest "delete_many all with $method" => sub {
$coll->drop;
$coll->insert_one( { key => 1 } ) for 1 .. 2;
my $bulk = $coll->$method;
$bulk->find( {} )->delete_many;
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on delete_many" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
deleted_count => 2,
modified_count => ( $server_does_bulk ? 0 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
is( $coll->count_documents({}), 0, "all documents removed" );
};
subtest "delete_many matching with $method" => sub {
$coll->drop;
$coll->insert_one( { key => $_ } ) for 1 .. 2;
my $bulk = $coll->$method;
$bulk->find( { key => 1 } )->delete_many;
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on delete_many" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
deleted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
cmp_deeply(
[ $coll->find( {} )->all ],
[ { _id => ignore(), key => num(2) } ],
"correct object remains"
);
};
subtest "delete_many with collation, using $method" => sub {
$coll->drop;
$coll->insert_one( { key => "a" } );
$coll->insert_one( { key => "a" } );
my $bulk = $coll->$method;
$bulk->insert_one( { key => "b" } );
$bulk->find( { key => "A" } )->collation($case_insensitive_collation)
->delete_many;
my $err = exception { $bulk->execute };
if ($supports_collation) {
is( $err, undef, "bulk delete_many w/ collation" );
cmp_deeply(
[ $coll->find( {} )->all ],
bag( { _id => ignore(), key => str("b") } ),
"collection updated"
);
}
else {
like(
$err,
qr/MongoDB host '.*:\d+' doesn't support collation/,
"bulk delete_many w/ collation returns error if unsupported"
);
cmp_deeply(
[ $coll->find( {} )->all ],
bag( { _id => ignore(), key => str("a") }, { _id => ignore(), key => str("a") } ),
"collection not updated"
);
}
};
}
note("QA-477 delete_one");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "delete_one errors with $method" => sub {
my $bulk = $coll->$method;
like(
exception { $bulk->delete_one() },
qr/^Can't locate object method "delete_one"/,
"delete_one on bulk object (without find) throws an error",
);
};
subtest "delete_one with $method" => sub {
$coll->drop;
$coll->insert_one( { key => 1 } ) for 1 .. 2;
my $bulk = $coll->$method;
$bulk->find( {} )->delete_one;
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on delete_one" ) or diag _truncate explain $err;
isa_ok( $result, 'MongoDB::BulkWriteResult', "result object" );
cmp_deeply(
$result,
_bulk_write_result(
deleted_count => 1,
modified_count => ( $server_does_bulk ? 0 : undef ),
op_count => 1,
batch_count => 1,
),
"result object correct"
) or diag _truncate explain $result;
is( $coll->count_documents({}), 1, "only one doc removed" );
};
subtest "delete_one with collation, using $method" => sub {
$coll->drop;
$coll->insert_one( { key => "a" } );
my $bulk = $coll->$method;
$bulk->insert_one( { key => "b" } );
$bulk->find( { key => "A" } )->collation($case_insensitive_collation)
->delete_one;
my $err = exception { $bulk->execute };
if ($supports_collation) {
is( $err, undef, "bulk delete_one w/ collation" );
cmp_deeply(
[ $coll->find( {} )->all ],
bag( { _id => ignore(), key => str("b") } ),
"collection updated"
);
}
else {
like(
$err,
qr/MongoDB host '.*:\d+' doesn't support collation/,
"bulk delete_one w/ collation returns error if unsupported"
);
cmp_deeply(
[ $coll->find( {} )->all ],
bag( { _id => ignore(), key => str("a") } ),
"collection not updated"
);
}
};
}
note("QA-477 MIXED OPERATIONS, UNORDERED");
subtest "mixed operations, unordered" => sub {
$coll->drop;
$coll->insert_one( { a => $_ } ) for 1 .. 2;
my $bulk = $coll->initialize_unordered_bulk_op;
$bulk->find( { a => 1 } )->update_many( { '$set' => { b => 1 } } );
$bulk->find( { a => 2 } )->delete_many;
$bulk->insert_one( { a => 3 } );
$bulk->find( { a => 4 } )->upsert->update_one( { '$set' => { b => 4 } } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on mixed operations" ) or diag _truncate explain $err;
cmp_deeply(
$result,
_bulk_write_result(
inserted_count => 1,
matched_count => 1,
modified_count => ( $server_does_bulk ? 1 : undef ),
upserted_count => 1,
deleted_count => 1,
op_count => 4,
batch_count => $server_does_bulk ? 3 : 4,
# XXX QA Test says index should be 3, but with unordered, that's
# not guaranteed, so we ignore the value
upserted => [ { index => ignore(), _id => obj_isa("BSON::OID") } ],
inserted => [ { index => ignore(), _id => obj_isa("BSON::OID") } ],
),
"result object correct"
) or diag _truncate explain $result;
};
note("QA-477 MIXED OPERATIONS, ORDERED");
subtest "mixed operations, ordered" => sub {
$coll->drop;
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->insert_one( { a => 1 } );
$bulk->find( { a => 1 } )->update_one( { '$set' => { b => 1 } } );
$bulk->find( { a => 2 } )->upsert->update_one( { '$set' => { b => 2 } } );
$bulk->insert_one( { a => 3 } );
$bulk->find( { a => 3 } )->delete_many;
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on mixed operations" ) or diag _truncate explain $err;
cmp_deeply(
$result,
_bulk_write_result(
inserted_count => 2,
upserted_count => 1,
matched_count => 1,
modified_count => ( $server_does_bulk ? 1 : undef ),
deleted_count => 1,
op_count => 5,
batch_count => $server_does_bulk ? 4 : 5,
upserted => [ { index => 2, _id => obj_isa("BSON::OID") } ],
inserted => [
{ index => 0, _id => obj_isa("BSON::OID") },
{ index => 3, _id => obj_isa("BSON::OID") },
],
),
"result object correct"
) or diag _truncate explain $result;
};
note("QA-477 UNORDERED BATCH WITH ERRORS");
subtest "unordered batch with errors" => sub {
$coll->drop;
$coll->indexes->create_one( [ a => 1 ], { unique => 1 } );
my $bulk = $coll->initialize_unordered_bulk_op;
$bulk->insert_one( { b => 1, a => 1 } );
$bulk->find( { b => 2 } )->upsert->update_one( { '$set' => { a => 1 } } );
$bulk->find( { b => 3 } )->upsert->update_one( { '$set' => { a => 2 } } );
$bulk->find( { b => 2 } )->upsert->update_one( { '$set' => { a => 1 } } );
$bulk->insert_one( { b => 4, a => 3 } );
$bulk->insert_one( { b => 5, a => 1 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
isa_ok( $err, 'MongoDB::DuplicateKeyError', 'caught error' )
or diag _truncate explain $err;
my $details = $err->result;
# Check if all ops ran in two batches (unless we're on a legacy server)
is( $details->op_count, 6, "op_count" );
is( $details->batch_count, $server_does_bulk ? 2 : 6, "batch_count" );
# XXX QA 477 doesn't cover *both* possible orders. Either the inserts go
# first or the upsert/update_ones goes first and different result states
# are possible for each case.
if ( $details->inserted_count == 2 ) {
note("inserts went first");
is( $details->inserted_count, 2, "inserted_count" );
is( $details->upserted_count, 1, "upserted_count" );
is( $details->deleted_count, 0, "deleted_count" );
is( $details->matched_count, 0, "matched_count" );
is( $details->modified_count, ( $server_does_bulk ? 0 : undef ), "modified_count" );
is( $details->count_write_errors, 3, "writeError count" )
or diag _truncate explain $details;
cmp_deeply( $details->upserted, [ { index => 2, _id => obj_isa("BSON::OID") }, ],
"upsert list" );
}
else {
note("updates went first");
is( $details->inserted_count, 1, "inserted_count" );
is( $details->upserted_count, 2, "upserted_count" );
is( $details->deleted_count, 0, "deleted_count" );
is( $details->matched_count, 1, "matched_count" );
is( $details->modified_count, ( $server_does_bulk ? 0 : undef ), "modified_count" );
is( $details->count_write_errors, 2, "writeError count" )
or diag _truncate explain $details;
cmp_deeply(
$details->upserted,
[
{ index => 1, _id => obj_isa("BSON::OID") },
{ index => 2, _id => obj_isa("BSON::OID") },
],
"upsert list"
);
}
my $distinct = [ $coll->distinct("a")->all ];
cmp_deeply( $distinct, bag( map { num($_) } 1 .. 3 ), "distinct keys" );
};
note("QA-477 ORDERED BATCH WITH ERRORS");
subtest "ordered batch with errors" => sub {
$coll->drop;
$coll->indexes->create_one( [ a => 1 ], { unique => 1 } );
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->insert_one( { b => 1, a => 1 } );
$bulk->find( { b => 2 } )->upsert->update_one( { '$set' => { a => 1 } } );
$bulk->find( { b => 3 } )->upsert->update_one( { '$set' => { a => 2 } } );
$bulk->find( { b => 2 } )->upsert->update_one( { '$set' => { a => 1 } } ); # fail
$bulk->insert_one( { b => 4, a => 3 } );
$bulk->insert_one( { b => 5, a => 1 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
isa_ok( $err, 'MongoDB::DuplicateKeyError', 'caught error' );
my $details = $err->result;
is( $details->upserted_count, 0, "upserted_count" );
is( $details->matched_count, 0, "matched_count" );
is( $details->deleted_count, 0, "deleted_count" );
is( $details->modified_count, ( $server_does_bulk ? 0 : undef ), "modified_count" );
is( $details->inserted_count, 1, "inserted_count" );
# on 2.6+, 4 ops run in two batches; but on legacy, we get an error on
# the first update_one, so we only have two ops, still in two batches
is( $details->op_count, $server_does_bulk ? 4 : 2, "op_count" );
is( $details->batch_count, 2, "op_count" );
is( $details->count_write_errors, 1, "writeError count" );
is( $details->write_errors->[0]{code}, 11000, "error code" );
is( $details->write_errors->[0]{index}, 1, "error index" );
ok( length $details->write_errors->[0]{errmsg}, "error string" );
cmp_deeply(
$details->write_errors->[0]{op},
{
q => methods(['FETCH','b'] => 3 ),
u => obj_isa( $server_does_bulk ? 'BSON::Raw' : 'Tie::IxHash' ),
multi => false,
upsert => true,
},
"error op"
) or diag _truncate explain $details->write_errors->[0]{op};
is( $coll->count_documents({}), 1, "subsequent inserts did not run" );
};
note("QA-477 BATCH SPLITTING: maxBsonObjectSize");
subtest "ordered batch split on size" => sub {
$coll->drop;
my $bulk = $coll->initialize_ordered_bulk_op;
my $big_string = "a" x ( 4 * 1024 * 1024 );
$bulk->insert_one( { _id => $_, a => $big_string } ) for 0 .. 5;
$bulk->insert_one( { _id => 0 } ); # will fail
$bulk->insert_one( { _id => 100 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
isa_ok( $err, 'MongoDB::DuplicateKeyError', 'caught error' )
or diag "CAUGHT ERROR: $err";
my $details = $err->result;
my $errdoc = $details->write_errors->[0];
is( $details->inserted_count, 6, "inserted_count" );
cmp_deeply(
$details->inserted_ids,
{ map { $_ => $_ } 0 .. 5 },
"inserted_ids correct"
);
is( $details->count_write_errors, 1, "count_write_errors" );
is( $errdoc->{code}, 11000, "error code" ) or diag _truncate explain $errdoc;
is( $errdoc->{index}, 6, "error index" );
ok( length( $errdoc->{errmsg} ), "error message" );
is( $coll->count_documents({}), 6, "collection count" );
};
subtest "unordered batch split on size" => sub {
$coll->drop;
my $bulk = $coll->initialize_unordered_bulk_op;
my $big_string = "a" x ( 4 * 1024 * 1024 );
$bulk->insert_one( { _id => $_, a => $big_string } ) for 0 .. 5;
$bulk->insert_one( { _id => 0 } ); # will fail
$bulk->insert_one( { _id => 100 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
isa_ok( $err, 'MongoDB::DuplicateKeyError', 'caught error' )
or diag $err;
my $details = $err->result;
my $errdoc = $details->write_errors->[0];
is( $details->inserted_count, 7, "inserted_count" );
is( $details->count_write_errors, 1, "count_write_errors" );
is( $errdoc->{code}, 11000, "error code" ) or diag _truncate explain $errdoc;
is( $errdoc->{index}, 6, "error index" );
ok( length( $errdoc->{errmsg} ), "error message" );
is( $coll->count_documents({}), 7, "collection count" );
};
note("QA-477 BATCH SPLITTING: maxWriteBatchSize");
subtest "ordered batch split on number of ops" => sub {
$coll->drop;
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->insert_one( { _id => $_ } ) for 0 .. 1999;
$bulk->insert_one( { _id => 0 } ); # will fail
$bulk->insert_one( { _id => 10000 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
isa_ok( $err, 'MongoDB::DuplicateKeyError', 'caught error' )
or diag $err;
my $details = $err->result;
my $errdoc = $details->write_errors->[0];
is( $details->inserted_count, 2000, "inserted_count" );
cmp_deeply(
$details->inserted_ids,
{ map { $_ => $_ } 0 .. 1999 },
"inserted_ids correct"
);
is( $details->count_write_errors, 1, "count_write_errors" );
is( $errdoc->{code}, 11000, "error code" );
is( $errdoc->{index}, 2000, "error index" );
ok( length( $errdoc->{errmsg} ), "error message" );
is( $coll->count_documents({}), 2000, "collection count" );
};
subtest "unordered batch split on number of ops" => sub {
$coll->drop;
my $bulk = $coll->initialize_unordered_bulk_op;
$bulk->insert_one( { _id => $_ } ) for 0 .. 1999;
$bulk->insert_one( { _id => 0 } ); # will fail
$bulk->insert_one( { _id => 10000 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
isa_ok( $err, 'MongoDB::DuplicateKeyError', 'caught error' )
or diag $err;
my $details = $err->result;
my $errdoc = $details->write_errors->[0];
is( $details->inserted_count, 2001, "inserted_count" );
is( $details->count_write_errors, 1, "count_write_errors" );
is( $errdoc->{code}, 11000, "error code" );
is( $errdoc->{index}, 2000, "error index" );
ok( length( $errdoc->{errmsg} ), "error message" );
is( $coll->count_documents({}), 2001, "collection count" );
};
note("QA-477 RE-RUNNING A BATCH");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: rerun a bulk operation" => sub {
$coll->drop;
my $bulk = $coll->$method;
$bulk->insert_one( {} );
my $err = exception { $bulk->execute };
is( $err, undef, "first execute succeeds" );
$err = exception { $bulk->execute };
isa_ok( $err, 'MongoDB::Error', "re-running a bulk op throws exception" );
like( $err->message, qr/bulk op execute called more than once/, "error message" )
or diag _truncate explain $err;
};
}
note("QA-477 EMPTY BATCH");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: empty bulk operation" => sub {
my $bulk = $coll->$method;
my $err = exception { $bulk->execute };
isa_ok( $err, 'MongoDB::Error', "empty bulk op throws exception" );
like( $err->message, qr/no bulk ops to execute/, "error message" )
or diag _truncate explain $err;
};
}
note("QA-477 W>1 AGAINST STANDALONE");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: w > 1 against standalone (explicit)" => sub {
plan skip_all => 'needs a standalone server'
unless $is_standalone;
$coll->drop;
my $bulk = $coll->$method;
$bulk->insert_one( {} );
my $err = exception { $bulk->execute( { w => 2 } ) };
isa_ok( $err, 'MongoDB::DatabaseError',
"executing write concern w > 1 throws error" );
like( $err->message, qr/replica/, "error message mentions replication" );
};
subtest "$method: w > 1 against standalone (implicit)" => sub {
plan skip_all => 'needs a standalone server'
unless $is_standalone;
$coll->drop;
my $coll2 = $coll->clone( write_concern => { w => 2 } );
my $bulk = $coll2->$method;
$bulk->insert_one( {} );
my $err = exception { $bulk->execute() };
isa_ok( $err, 'MongoDB::DatabaseError',
"executing write concern w > 1 throws error" );
like( $err->message, qr/replica/, "error message mentions replication" );
};
}
note("QA-477 WTIMEOUT PLUS DUPLICATE KEY ERROR");
subtest "initialize_unordered_bulk_op: wtimeout plus duplicate keys" => sub {
plan skip_all => 'needs a replica set'
unless _number_of_servers > 1;
# asking for w more than N hosts will trigger the error we need
my $W = _number_of_servers() + 1;
$coll->drop;
my $bulk = $coll->initialize_unordered_bulk_op;
$bulk->insert_one( { _id => 1 } );
$bulk->insert_one( { _id => 1 } );
my $err = exception { $bulk->execute( { w => $W, wtimeout => 100 } ) };
isa_ok( $err, 'MongoDB::DuplicateKeyError', "executing throws error" );
my $details = $err->result;
is( $details->inserted_count, 1, "inserted_count == 1" );
is( $details->count_write_errors, 1, "one write error" );
is( $details->count_write_concern_errors, 1, "one write concern error" );
};
note("QA-477 W = 0");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: w = 0" => sub {
$coll->drop;
my $bulk = $coll->$method;
$bulk->insert_one( { _id => 1 } );
$bulk->insert_one( { _id => 1 } );
$bulk->insert_one( { _id => 2 } ); # ensure success after failure
my ( $result, $err );
$err = exception { $result = $bulk->execute( { w => 0 } ) };
is( $err, undef, "execute with w = 0 doesn't throw error" )
or diag _truncate explain $err;
my $expect = $method eq 'initialize_ordered_bulk_op' ? 1 : 2;
is( $coll->count_documents({}), $expect, "document count ($expect)" );
};
}
# This test was not included in the QA-477 test plan; it ensures that
# write concerns are applied only after all operations finish
note("WRITE CONCERN ERRORS");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: write concern errors" => sub {
plan skip_all => 'needs a replica set'
unless _number_of_servers > 1;
# asking for w more than N hosts will trigger the error we need
my $W = _number_of_servers() + 1;
$coll->drop;
my $bulk = $coll->$method;
$bulk->insert_one( { _id => 1 } );
$bulk->insert_one( { _id => 2 } );
$bulk->find( { id => 3 } )->upsert->update_many( { '$set' => { x => 2 } } );
$bulk->insert_one( { _id => 4 } );
my $err = exception { $bulk->execute( { w => $W, wtimeout => 100 } ) };
isa_ok( $err, 'MongoDB::WriteConcernError', "executing throws error" );
my $details = $err->result;
is( $details->inserted_count, 3, "inserted_count" );
is( $details->upserted_count, 1, "upserted_count" );
is( $details->count_write_errors, 0, "no write errors" );
ok( $details->count_write_concern_errors, "got write concern errors" );
};
}
# Not in QA-477 -- Many methods take hashrefs, arrayrefs or Tie::IxHash
# objects. The following tests check that arrayrefs and Tie::IxHash are legal
# arguments to find, insert, update, update_one and replace_one. The
# delete_many and delete_one methods take no arguments and don't need tests
note("ARRAY REFS"); # Not in QA-477 -- this is perl driver specific
subtest "insert (ARRAY)" => sub {
$coll->drop;
my $bulk = $coll->initialize_ordered_bulk_op;
is( $coll->count_documents({}), 0, "no docs in collection" );
$bulk->insert_one( [ _id => 1 ] );
$bulk->insert_one( [] );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on insert" ) or diag _truncate explain $err;
is( $coll->count_documents({}), 2, "doc count" );
};
subtest "update (ARRAY)" => sub {
$coll->drop;
$coll->insert_one( { _id => 1 } );
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->find( [] )->update_many( [ '$set' => { x => 2 } ] );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update" ) or diag _truncate explain $err;
is( $coll->find_one( {} )->{x}, 2, "document updated" );
};
subtest "update_one (ARRAY)" => sub {
$coll->drop;
$coll->insert_one( { _id => $_ } ) for 1 .. 2;
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->find( [] )->update_one( [ '$set' => { x => 2 } ] );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update_one" ) or diag _truncate explain $err;
is( $coll->count_documents( { x => 2 } ), 1, "only one doc updated" );
};
subtest "replace_one (ARRAY)" => sub {
$coll->drop;
$coll->insert_one( { key => $_ } ) for 1 .. 2;
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->find( [] )->replace_one( [ key => 3 ] );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on replace" ) or diag _truncate explain $err;
is( $coll->count_documents( { key => 3 } ), 1, "only one doc replaced" );
};
note("Tie::IxHash");
subtest "insert (Tie::IxHash)" => sub {
$coll->drop;
my $bulk = $coll->initialize_ordered_bulk_op;
is( $coll->count_documents({}), 0, "no docs in collection" );
$bulk->insert_one( Tie::IxHash->new( _id => 1 ) );
my $doc = Tie::IxHash->new();
$bulk->insert_one( $doc );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on insert" ) or diag _truncate explain $err;
is( $coll->count_documents({}), 2, "doc count" );
};
subtest "update (Tie::IxHash)" => sub {
$coll->drop;
$coll->insert_one( { _id => 1 } );
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->find( Tie::IxHash->new() )
->update_many( Tie::IxHash->new( '$set' => { x => 2 } ) );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update" ) or diag _truncate explain $err;
is( $coll->find_one( {} )->{x}, 2, "document updated" );
};
subtest "update_one (Tie::IxHash)" => sub {
$coll->drop;
$coll->insert_one( { _id => $_ } ) for 1 .. 2;
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->find( Tie::IxHash->new() )
->update_one( Tie::IxHash->new( '$set' => { x => 2 } ) );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on update" ) or diag _truncate explain $err;
is( $coll->count_documents( { x => 2 } ), 1, "only one doc updated" );
};
subtest "replace_one (Tie::IxHash)" => sub {
$coll->drop;
$coll->insert_one( { key => $_ } ) for 1 .. 2;
my $bulk = $coll->initialize_ordered_bulk_op;
$bulk->find( Tie::IxHash->new() )->replace_one( Tie::IxHash->new( key => 3 ) );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "no error on replace" ) or diag _truncate explain $err;
is( $coll->count_documents( { key => 3 } ), 1, "only one doc replaced" );
};
# not in QA-477
note("W = 0 IGNORES ERRORS");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: w = 0" => sub {
$coll->drop;
my $bulk = $coll->$method;
$bulk->insert_one( { _id => 1 } );
$bulk->insert_one( { _id => 3, '$bad' => 1 } );
$bulk->insert_one( { _id => 4 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute( { w => 0 } ) };
is( $err, undef, "execute with w = 0 doesn't throw error" )
or diag _truncate explain $err;
my $expect = $method eq 'initialize_ordered_bulk_op' ? 1 : 2;
is( $coll->count_documents({}), $expect, "document count ($expect)" );
};
}
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "collation w/ w:0 bulk writes return error, using $method" => sub {
plan skip_all => "Collation tests for MongoDB 3.4+" unless $supports_collation;
$coll->drop;
my $bulk = $coll->$method;
$bulk->find( { x => "foo" } )->collation($case_insensitive_collation)->delete_one;
like(
exception { $bulk->execute( { w => 0 } ) },
qr/Unacknowledged bulk writes that specify a collation are not allowed/,
"bulk write w/ collation returns error if write is unacknowledged"
);
};
}
# DRIVERS-151 Handle edge case for pre-2.6 when upserted _id not returned
note("UPSERT _ID NOT RETURNED");
for my $method (qw/initialize_ordered_bulk_op initialize_unordered_bulk_op/) {
subtest "$method: upsert with non OID _ids" => sub {
$coll->drop;
my $bulk = $coll->$method;
$bulk->find( { _id => 0 } )->upsert->update_one( { '$set' => { a => 0 } } );
$bulk->find( { a => 1 } )->upsert->replace_one( { _id => 1 } );
# 2.6 doesn't allow changing _id, but previously that's OK, so we try it both ways
# to ensure we use the right _id from the replace doc on older servers
$bulk->find( { _id => $server_does_bulk ? 2 : 3 } )->upsert->replace_one( { _id => 2 } );
my ( $result, $err );
$err = exception { $result = $bulk->execute };
is( $err, undef, "execute doesn't throw error" )
or diag _truncate explain $err;
cmp_deeply(
$result,
_bulk_write_result(
upserted_count => 3,
modified_count => ( $server_does_bulk ? 0 : undef ),
upserted =>
[ { index => num(0), _id => num(0) }, { index => num(1), _id => num(1) }, { index => num(2), _id => num(2) }, ],
op_count => 3,
batch_count => $server_does_bulk ? 1 : 3,
),
"result object correct"
) or diag _truncate explain $result;
};
}
subtest "replace with custom op_char" => sub {
$coll->drop;
my $coll2 = $coll->with_codec( op_char => '-' );
my $bulk = $coll2->ordered_bulk;
$bulk->insert_one( { _id => 0 } );
$bulk->find( { _id => 0 } )->replace_one( { '-set' => { key => 1} } );
like(
exception { $bulk->execute },
qr/replacement document must not contain update operators/,
"single non-op key in update doc throws exception"
);
};
# XXX QA-477 tests not covered herein:
# MIXED OPERATIONS, AUTH
# FAILOVER WITH MIXED VERSIONS
done_testing;