require 'spec_helper' describe Mongo::BulkWrite do before do authorized_collection.delete_many end after do authorized_collection.delete_many collection_with_validator.drop end let(:collection_with_validator) do begin; authorized_client[:validating].drop; rescue; end authorized_client[:validating, :validator => { :a => { '$exists' => true } }].tap do |c| c.create end end let(:collection_invalid_write_concern) do authorized_collection.client.with(write: INVALID_WRITE_CONCERN)[authorized_collection.name] end let(:collation) do { locale: 'en_US', strength: 2 } end let(:array_filters) do [{ 'i.y' => 3}] end let(:collection) do authorized_collection end let(:client) do authorized_client end describe '#execute' do shared_examples_for 'an executable bulk write' do context 'when providing a bad operation' do let(:requests) do [{ not_an_operation: { _id: 0 }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_error(Mongo::Error::InvalidBulkOperationType) end end context 'when the operations do not need to be split' do context 'when a write error occurs' do let(:requests) do [ { insert_one: { _id: 0 }}, { insert_one: { _id: 1 }}, { insert_one: { _id: 0 }}, { insert_one: { _id: 1 }} ] end let(:error) do begin bulk_write.execute rescue => e e end end it 'raises an exception' do expect { bulk_write.execute }.to raise_error(Mongo::Error::BulkWriteError) end it 'sets the document index on the error' do expect(error.result[Mongo::Error::WRITE_ERRORS].first['index']).to eq(2) end context 'when a session is provided' do let(:extra_options) do { session: session } end let(:client) do authorized_client end let(:failed_operation) do bulk_write.execute end it_behaves_like 'a failed operation using a session' end end context 'when provided a single insert one' do let(:requests) do [{ insert_one: { _id: 0 }}] end let(:result) do bulk_write.execute end it 'inserts the document' do expect(result.inserted_count).to eq(1) expect(authorized_collection.find(_id: 0).count).to eq(1) end it 'only inserts that document' do result expect(authorized_collection.find.first['_id']).to eq(0) end context 'when a session is provided' do let(:operation) do result end let(:extra_options) do { session: session } end let(:client) do authorized_client end it_behaves_like 'an operation using a session' end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:extra_options) do {session: session} end let(:client) do collection_invalid_write_concern.client end let(:failed_operation) do bulk_write_invalid_write_concern.execute end it_behaves_like 'a failed operation using a session' end end end context 'when provided multiple insert ones' do let(:requests) do [ { insert_one: { _id: 0 }}, { insert_one: { _id: 1 }}, { insert_one: { _id: 2 }} ] end let(:result) do bulk_write.execute end it 'inserts the documents' do expect(result.inserted_count).to eq(3) expect(authorized_collection.find(_id: { '$in'=> [ 0, 1, 2 ]}).count).to eq(3) end context 'when there is a write failure' do let(:requests) do [{ insert_one: { _id: 1 }}, { insert_one: { _id: 1 }}] end it 'raises a BulkWriteError' do expect { bulk_write.execute }.to raise_error(Mongo::Error::BulkWriteError) end end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:extra_options) do {session: session} end let(:client) do collection_invalid_write_concern.client end let(:failed_operation) do bulk_write_invalid_write_concern.execute end it_behaves_like 'a failed operation using a session' end end end context 'when provided a single delete one' do let(:requests) do [{ delete_one: { filter: { _id: 0 }}}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_one({ _id: 0 }) end it 'deletes the document' do expect(result.deleted_count).to eq(1) expect(authorized_collection.find(_id: 0).count).to eq(0) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:extra_options) do {session: session} end let(:client) do collection_invalid_write_concern.client end let(:failed_operation) do bulk_write_invalid_write_concern.execute end it_behaves_like 'a failed operation using a session' end context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ delete_one: { filter: { name: 'BANG' }, collation: collation } }] end context 'when the server selected supports collations', if: collation_enabled? do let!(:result) do bulk_write.execute end it 'applies the collation' do expect(authorized_collection.find(name: 'bang').count).to eq(0) end it 'reports the deleted count' do expect(result.deleted_count).to eq(1) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ delete_one: { filter: { name: 'BANG' }, 'collation' => collation } }] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when a collation is not specified' do before do authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ delete_one: { filter: { name: 'BANG' }}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(name: 'bang').count).to eq(1) end it 'reports the deleted count' do expect(result.deleted_count).to eq(0) end end end context 'when bulk executing update_one' do context 'when the write has specified arrayFilters' do before do authorized_collection.insert_one(_id: 1, x: [{ y: 1 }, { y: 2 }, { y: 3 }]) end let(:requests) do [{ update_one: { filter: { _id: 1 }, update: { '$set' => { 'x.$[i].y' => 5 } }, array_filters: array_filters, } }] end context 'when the server selected supports arrayFilters', if: array_filters_enabled? do let!(:result) do bulk_write.execute end it 'applies the arrayFilters' do expect(result.matched_count).to eq(1) expect(result.modified_count).to eq(1) expect(authorized_collection.find(_id: 1).first['x'].last['y']).to eq(5) end end context 'when the server selected does not support arrayFilters', unless: array_filters_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedArrayFilters) end end end end context 'when bulk executing update_many' do context 'when the write has specified arrayFilters' do before do authorized_collection.insert_many([{ _id: 1, x: [ { y: 1 }, { y: 2 }, { y: 3 } ] }, { _id: 2, x: [ { y: 3 }, { y: 2 }, { y: 1 } ] }]) end let(:selector) do { '$or' => [{ _id: 1 }, { _id: 2 }]} end let(:requests) do [{ update_many: { filter: { '$or' => [{ _id: 1 }, { _id: 2 }]}, update: { '$set' => { 'x.$[i].y' => 5 } }, array_filters: array_filters, } }] end context 'when the server selected supports arrayFilters', if: array_filters_enabled? do let!(:result) do bulk_write.execute end it 'applies the arrayFilters' do expect(result.matched_count).to eq(2) expect(result.modified_count).to eq(2) docs = authorized_collection.find(selector, sort: { _id: 1 }).to_a expect(docs[0]['x']).to eq ([{ 'y' => 1 }, { 'y' => 2 }, { 'y' => 5}]) expect(docs[1]['x']).to eq ([{ 'y' => 5 }, { 'y' => 2 }, { 'y' => 1}]) end end context 'when the server selected does not support arrayFilters', unless: array_filters_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedArrayFilters) end end end end context 'when multiple documents match delete selector' do before do authorized_collection.insert_many([{ a: 1 }, { a: 1 }]) end let(:requests) do [{ delete_one: { filter: { a: 1 }}}] end it 'reports n_removed correctly' do expect(bulk_write.execute.deleted_count).to eq(1) end it 'deletes only matching documents' do bulk_write.execute expect(authorized_collection.find(a: 1).count).to eq(1) end end end context 'when provided multiple delete ones' do let(:requests) do [ { delete_one: { filter: { _id: 0 }}}, { delete_one: { filter: { _id: 1 }}}, { delete_one: { filter: { _id: 2 }}} ] end let(:result) do bulk_write.execute end before do authorized_collection.insert_many([ { _id: 0 }, { _id: 1 }, { _id: 2 } ]) end it 'deletes the documents' do expect(result.deleted_count).to eq(3) expect(authorized_collection.find(_id: { '$in'=> [ 0, 1, 2 ]}).count).to eq(0) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:extra_options) do {session: session} end let(:client) do collection_invalid_write_concern.client end let(:failed_operation) do bulk_write_invalid_write_concern.execute end it_behaves_like 'a failed operation using a session' end end context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'doink') end let(:requests) do [{ delete_one: { filter: { name: 'BANG' }, collation: collation }}, { delete_one: { filter: { name: 'DOINK' }, collation: collation }}] end context 'when the server selected supports collations', if: collation_enabled? do let!(:result) do bulk_write.execute end it 'applies the collation' do expect(authorized_collection.find(name: { '$in' => ['bang', 'doink']}).count).to eq(0) end it 'reports the deleted count' do expect(result.deleted_count).to eq(2) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ delete_one: { filter: { name: 'BANG' }, 'collation' => collation }}, { delete_one: { filter: { name: 'DOINK' }, 'collation' => collation }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when the write does not have a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'doink') end let(:requests) do [{ delete_one: { filter: { name: 'BANG' }}}, { delete_one: { filter: { name: 'DOINK' }}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(name: { '$in' => ['bang', 'doink']}).count).to eq(2) end it 'reports the deleted count' do expect(result.deleted_count).to eq(0) end end end context 'when provided a single delete many' do let(:requests) do [{ delete_many: { filter: { _id: 0 }}}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_one({ _id: 0 }) end it 'deletes the documents' do expect(result.deleted_count).to eq(1) expect(authorized_collection.find(_id: 0).count).to eq(0) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:extra_options) do {session: session} end let(:client) do collection_invalid_write_concern.client end let(:failed_operation) do bulk_write_invalid_write_concern.execute end it_behaves_like 'a failed operation using a session' end end context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ delete_many: { filter: { name: 'BANG' }, collation: collation }}] end context 'when the server selected supports collations', if: collation_enabled? do let!(:result) do bulk_write.execute end it 'applies the collation' do expect(authorized_collection.find(name: 'bang').count).to eq(0) end it 'reports the deleted count' do expect(result.deleted_count).to eq(2) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ delete_many: { filter: { name: 'BANG' }, 'collation' => collation }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when a collation is not specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ delete_many: { filter: { name: 'BANG' }}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(name: 'bang').count).to eq(2) end it 'reports the deleted count' do expect(result.deleted_count).to eq(0) end end end context 'when provided multiple delete many ops' do let(:requests) do [ { delete_many: { filter: { _id: 0 }}}, { delete_many: { filter: { _id: 1 }}}, { delete_many: { filter: { _id: 2 }}} ] end let(:result) do bulk_write.execute end before do authorized_collection.insert_many([ { _id: 0 }, { _id: 1 }, { _id: 2 } ]) end it 'deletes the documents' do expect(result.deleted_count).to eq(3) expect(authorized_collection.find(_id: { '$in'=> [ 0, 1, 2 ]}).count).to eq(0) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do {session: session} end it_behaves_like 'an operation using a session' end end context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'doink') end let(:requests) do [{ delete_many: { filter: { name: 'BANG' }, collation: collation }}, { delete_many: { filter: { name: 'DOINK' }, collation: collation }}] end context 'when the server selected supports collations', if: collation_enabled? do let!(:result) do bulk_write.execute end it 'applies the collation' do expect(authorized_collection.find(name: { '$in' => ['bang', 'doink'] }).count).to eq(0) end it 'reports the deleted count' do expect(result.deleted_count).to eq(3) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ delete_many: { filter: { name: 'BANG' }, 'collation' => collation }}, { delete_many: { filter: { name: 'DOINK' }, 'collation' => collation }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when a collation is not specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'doink') end let(:requests) do [{ delete_many: { filter: { name: 'BANG' }}}, { delete_many: { filter: { name: 'DOINK' }}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(name: { '$in' => ['bang', 'doink'] }).count).to eq(3) end it 'reports the deleted count' do expect(result.deleted_count).to eq(0) end end end context 'when providing a single replace one' do let(:requests) do [{ replace_one: { filter: { _id: 0 }, replacement: { name: 'test' }}}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_one({ _id: 0 }) end it 'replaces the document' do expect(result.modified_count).to eq(1) expect(authorized_collection.find(_id: 0).first[:name]).to eq('test') end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:extra_options) do {session: session} end let(:client) do collection_invalid_write_concern.client end let(:failed_operation) do bulk_write_invalid_write_concern.execute end it_behaves_like 'a failed operation using a session' end end context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ replace_one: { filter: { name: 'BANG' }, replacement: { other: 'pong' }, collation: collation }}] end context 'when the server selected supports collations' do let!(:result) do bulk_write.execute end it 'applies the collation', if: collation_enabled? do expect(authorized_collection.find(other: 'pong').count).to eq(1) end it 'reports the upserted id', if: collation_enabled? do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count', if: collation_enabled? do expect(result.upserted_count).to eq(0) end it 'reports the modified count', if: collation_enabled? do expect(result.modified_count).to eq(1) end it 'reports the matched count', if: collation_enabled? do expect(result.matched_count).to eq(1) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ replace_one: { filter: { name: 'BANG' }, replacement: { other: 'pong' }, 'collation' => collation }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when the write does not have a collation specified' do before do authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ replace_one: { filter: { name: 'BANG' }, replacement: { other: 'pong' }}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(other: 'pong').count).to eq(0) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(0) end it 'reports the matched count' do expect(result.matched_count).to eq(0) end end end context 'when providing a single update one' do context 'when upsert is false' do let(:requests) do [{ update_one: { filter: { _id: 0 }, update: { "$set" => { name: 'test' }}}}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_one({ _id: 0 }) end it 'updates the document' do result expect(authorized_collection.find(_id: 0).first[:name]).to eq('test') end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(1) end it 'reports the matched count' do expect(result.matched_count).to eq(1) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end context 'when documents match but are not modified' do before do authorized_collection.insert_one({ a: 0 }) end let(:requests) do [{ update_one: { filter: { a: 0 }, update: { "$set" => { a: 0 }}}}] end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(0) end it 'reports the matched count' do expect(result.matched_count).to eq(1) end end context 'when the number of updates exceeds the max batch size' do let(:batch_size) do 11 end before do allow(client.cluster.next_primary).to receive(:max_write_batch_size).and_return(batch_size - 1) end let(:requests) do batch_size.times.collect do |i| { update_one: { filter: { a: i }, update: { "$set" => { a: i, b: 3 }}, upsert: true }} end end it 'updates the documents and reports the correct number of upserted ids' do expect(result.upserted_ids.size).to eq(batch_size) expect(authorized_collection.find(b: 3).count).to eq(batch_size) end end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end context 'when a session is provided' do let(:extra_options) do {session: session} end let(:client) do collection_invalid_write_concern.client end let(:failed_operation) do bulk_write_invalid_write_concern.execute end it_behaves_like 'a failed operation using a session' end end end context 'when upsert is true' do let(:requests) do [{ update_one: { filter: { _id: 0 }, update: { "$set" => { name: 'test' } }, upsert: true }}] end let(:result) do bulk_write.execute end it 'updates the document' do result expect(authorized_collection.find(_id: 0).first[:name]).to eq('test') end it 'reports the upserted count' do expect(result.upserted_count).to eq(1) end it 'reports the modified_count count' do expect(result.modified_count).to eq(0) end it 'reports the matched count' do expect(result.matched_count).to eq(0) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([0]) end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end end end context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ update_one: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}, collation: collation }}] end context 'when the server selected supports collations' do let!(:result) do bulk_write.execute end it 'applies the collation', if: collation_enabled? do expect(authorized_collection.find(name: 'pong').count).to eq(1) end it 'reports the upserted id', if: collation_enabled? do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count', if: collation_enabled? do expect(result.upserted_count).to eq(0) end it 'reports the modified count', if: collation_enabled? do expect(result.modified_count).to eq(1) end it 'reports the matched count', if: collation_enabled? do expect(result.matched_count).to eq(1) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ update_one: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}, 'collation' => collation }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when the write does not have a collation specified' do before do authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ update_one: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(name: 'pong').count).to eq(0) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(0) end it 'reports the matched count' do expect(result.matched_count).to eq(0) end end end context 'when providing multiple update ones' do context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'doink') end let(:requests) do [{ update_one: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}, collation: collation }}, { update_one: { filter: { name: 'DOINK' }, update: { "$set" => { name: 'pong' }}, collation: collation }}] end context 'when the server selected supports collations' do let!(:result) do bulk_write.execute end it 'applies the collation', if: collation_enabled? do expect(authorized_collection.find(name: 'pong').count).to eq(2) end it 'reports the upserted id', if: collation_enabled? do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count', if: collation_enabled? do expect(result.upserted_count).to eq(0) end it 'reports the modified count', if: collation_enabled? do expect(result.modified_count).to eq(2) end it 'reports the matched count', if: collation_enabled? do expect(result.matched_count).to eq(2) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ update_one: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}, 'collation' => collation }}, { update_one: { filter: { name: 'DOINK' }, update: { "$set" => { name: 'pong' }}, 'collation' => collation }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when the write does not have a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'doink') end let(:requests) do [{ update_one: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}}}, { update_one: { filter: { name: 'DOINK' }, update: { "$set" => { name: 'pong' }}}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(name: 'pong').count).to eq(0) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(0) end it 'reports the matched count' do expect(result.matched_count).to eq(0) end end context 'when upsert is false' do let(:requests) do [{ update_one: { filter: { _id: 0 }, update: { "$set" => { name: 'test' }}}}, { update_one: { filter: { _id: 1 }, update: { "$set" => { name: 'test' }}}}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_many([{ _id: 0 }, { _id: 1 }]) end it 'updates the document' do result expect(authorized_collection.find(name: 'test').count).to eq(2) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(2) end it 'reports the matched count' do expect(result.matched_count).to eq(2) end context 'when there is a mix of updates and matched without an update' do let(:requests) do [{ update_one: { filter: { a: 0 }, update: { "$set" => { a: 1 }}}}, { update_one: { filter: { a: 2 }, update: { "$set" => { a: 2 }}}}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_many([{ a: 0 }, { a: 2 }]) end it 'updates the document' do result expect(authorized_collection.find(a: { '$lt' => 3 }).count).to eq(2) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(1) end it 'reports the matched count' do expect(result.matched_count).to eq(2) end end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end end end context 'when upsert is true' do let(:requests) do [{ update_one: { filter: { _id: 0 }, update: { "$set" => { name: 'test' }}, upsert: true }}, { update_one: { filter: { _id: 1 }, update: { "$set" => { name: 'test1' }}, upsert: true }}] end let(:result) do bulk_write.execute end it 'updates the document' do expect(result.modified_count).to eq(0) expect(authorized_collection.find(name: { '$in' => ['test', 'test1'] }).count).to eq(2) end it 'reports the upserted count' do expect(result.upserted_count).to eq(2) end it 'reports the modified count' do expect(result.modified_count).to eq(0) end it 'reports the matched count' do expect(result.matched_count).to eq(0) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([0, 1]) end context 'when there is a mix of updates, upsert, and matched without an update' do let(:requests) do [{ update_one: { filter: { a: 0 }, update: { "$set" => { a: 1 }}}}, { update_one: { filter: { a: 2 }, update: { "$set" => { a: 2 }}}}, { update_one: { filter: { _id: 3 }, update: { "$set" => { a: 4 }}, upsert: true }}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_many([{ a: 0 }, { a: 2 }]) end it 'updates the documents' do result expect(authorized_collection.find(a: { '$lt' => 3 }).count).to eq(2) expect(authorized_collection.find(a: 4).count).to eq(1) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([3]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(1) end it 'reports the modified count' do expect(result.modified_count).to eq(1) end it 'reports the matched count' do expect(result.matched_count).to eq(2) end end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end end end end context 'when providing a single update many' do context 'when the write has a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ update_many: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}, collation: collation }}] end context 'when the server selected supports collations' do let!(:result) do bulk_write.execute end it 'applies the collation', if: collation_enabled? do expect(authorized_collection.find(name: 'pong').count).to eq(2) end it 'reports the upserted id', if: collation_enabled? do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count', if: collation_enabled? do expect(result.upserted_count).to eq(0) end it 'reports the modified count', if: collation_enabled? do expect(result.modified_count).to eq(2) end it 'reports the matched count', if: collation_enabled? do expect(result.matched_count).to eq(2) end end context 'when the server selected does not support collations', unless: collation_enabled? do it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end context 'when a String key is used' do let(:requests) do [{ update_many: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}, 'collation' => collation }}] end it 'raises an exception' do expect { bulk_write.execute }.to raise_exception(Mongo::Error::UnsupportedCollation) end end end end context 'when the write does not have a collation specified' do before do authorized_collection.insert_one(name: 'bang') authorized_collection.insert_one(name: 'bang') end let(:requests) do [{ update_many: { filter: { name: 'BANG' }, update: { "$set" => { name: 'pong' }}}}] end let!(:result) do bulk_write.execute end it 'does not apply the collation' do expect(authorized_collection.find(name: 'pong').count).to eq(0) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(0) end it 'reports the matched count' do expect(result.matched_count).to be(0) end end context 'when upsert is false' do let(:requests) do [{ update_many: { filter: { a: 0 }, update: { "$set" => { name: 'test' }}}}] end let(:result) do bulk_write.execute end before do authorized_collection.insert_many([{ a: 0 }, { a: 0 }]) end it 'updates the documents' do expect(authorized_collection.find(a: 0).count).to eq(2) end it 'reports the upserted ids' do expect(result.upserted_ids).to eq([]) end it 'reports the upserted count' do expect(result.upserted_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(2) end it 'reports the matched count' do expect(result.matched_count).to eq(2) end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end end end context 'when upsert is true' do let(:requests) do [{ update_many: { filter: { _id: 0 }, update: { "$set" => { name: 'test' }}, upsert: true }}] end let(:result) do bulk_write.execute end it 'updates the document' do result expect(authorized_collection.find(name: 'test').count).to eq(1) end it 'reports the upserted count' do expect(result.upserted_count).to eq(1) end it 'reports the matched count' do expect(result.matched_count).to eq(0) end it 'reports the modified count' do expect(result.modified_count).to eq(0) end it 'reports the upserted id' do expect(result.upserted_ids).to eq([0]) end context 'when there is a write concern error' do it 'raises an OperationFailure', if: standalone? do expect { bulk_write_invalid_write_concern.execute }.to raise_error(Mongo::Error::OperationFailure) end end end end end context 'when the operations need to be split' do let(:batch_size) do 11 end before do allow(client.cluster.next_primary).to receive(:max_write_batch_size).and_return(batch_size - 1) end context 'when a write error occurs' do let(:requests) do batch_size.times.map do |i| { insert_one: { _id: i }} end end let(:error) do begin bulk_write.execute rescue => e e end end it 'raises an exception' do expect { requests.push({ insert_one: { _id: 5 }}) bulk_write.execute }.to raise_error(Mongo::Error::BulkWriteError) end it 'sets the document index on the error' do requests.push({ insert_one: { _id: 5 }}) expect(error.result[Mongo::Error::WRITE_ERRORS].first['index']).to eq(batch_size) end end context 'when no write errors occur' do let(:requests) do batch_size.times.map do |i| { insert_one: { _id: i }} end end let(:result) do bulk_write.execute end it 'inserts the documents' do expect(result.inserted_count).to eq(batch_size) end it 'combines the inserted ids' do expect(result.inserted_ids.size).to eq(batch_size) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end context 'when retryable writes are supported', if: test_sessions? do let(:client) do authorized_client_with_retry_writes end let(:collection) do client[authorized_collection.name] end let!(:result) do bulk_write.execute end let(:first_txn_number) do EventSubscriber.started_events[-2].command['txnNumber'].instance_variable_get(:@integer) end let(:second_txn_number) do EventSubscriber.started_events[-1].command['txnNumber'].instance_variable_get(:@integer) end it 'inserts the documents' do expect(result.inserted_count).to eq(batch_size) end it 'combines the inserted ids' do expect(result.inserted_ids.size).to eq(batch_size) end it 'increments the transaction number' do expect(first_txn_number + 1). to eq(second_txn_number) end end end end context 'when an operation exceeds the max bson size' do let(:requests) do 5.times.map do |i| { insert_one: { _id: i, x: 'y' * 4000000 }} end end let(:result) do bulk_write.execute end it 'inserts the documents' do expect(result.inserted_count).to eq(5) end context 'when a session is provided' do let(:operation) do result end let(:client) do authorized_client end let(:extra_options) do { session: session } end it_behaves_like 'an operation using a session' end end end context 'when the bulk write is unordered' do let(:bulk_write) do described_class.new(collection, requests, options) end let(:options) do { ordered: false }.merge(extra_options) end let(:extra_options) do {} end let(:bulk_write_invalid_write_concern) do described_class.new(collection_invalid_write_concern, requests, options) end it_behaves_like 'an executable bulk write' end context 'when the bulk write is ordered' do let(:bulk_write) do described_class.new(collection, requests, options) end let(:options) do { ordered: true }.merge(extra_options) end let(:extra_options) do {} end let(:bulk_write_invalid_write_concern) do described_class.new(collection_invalid_write_concern, requests, options) end it_behaves_like 'an executable bulk write' end end describe '#initialize' do let(:requests) do [{ insert_one: { _id: 0 }}] end shared_examples_for 'a bulk write initializer' do it 'sets the collection' do expect(bulk_write.collection).to eq(authorized_collection) end it 'sets the requests' do expect(bulk_write.requests).to eq(requests) end end context 'when no options are provided' do let(:bulk_write) do described_class.new(authorized_collection, requests) end it 'sets empty options' do expect(bulk_write.options).to be_empty end it_behaves_like 'a bulk write initializer' end context 'when options are provided' do let(:bulk_write) do described_class.new(authorized_collection, requests, ordered: true) end it 'sets the options' do expect(bulk_write.options).to eq(ordered: true) end end context 'when nil options are provided' do let(:bulk_write) do described_class.new(authorized_collection, requests, nil) end it 'sets empty options' do expect(bulk_write.options).to be_empty end end end describe '#ordered?' do context 'when no option provided' do let(:bulk_write) do described_class.new(authorized_collection, []) end it 'returns true' do expect(bulk_write).to be_ordered end end context 'when the option is provided' do context 'when the option is true' do let(:bulk_write) do described_class.new(authorized_collection, [], ordered: true) end it 'returns true' do expect(bulk_write).to be_ordered end end context 'when the option is false' do let(:bulk_write) do described_class.new(authorized_collection, [], ordered: false) end it 'returns false' do expect(bulk_write).to_not be_ordered end end end end describe 'when the collection has a validator', if: find_command_enabled? do before do collection_with_validator.insert_many([{ :a => 1 }, { :a => 2 }]) end after do collection_with_validator.delete_many end context 'when the documents are invalid' do let(:ops) do [ { insert_one: { :x => 1 } }, { update_one: { filter: { :a => 1 }, update: { '$unset' => { :a => '' } } } }, { replace_one: { filter: { :a => 2 }, replacement: { :x => 2 } } } ] end context 'when bypass_document_validation is not set' do let(:result) do collection_with_validator.bulk_write(ops) end it 'raises BulkWriteError' do expect { result }.to raise_exception(Mongo::Error::BulkWriteError) end end context 'when bypass_document_validation is true' do let(:result2) do collection_with_validator.bulk_write( ops, :bypass_document_validation => true) end it 'executes successfully' do expect(result2.modified_count).to eq(2) expect(result2.inserted_count).to eq(1) end end end end end