diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 90514f203f..58d56e1b94 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -339,7 +339,9 @@ def capped? # inserted or updated documents where the clustered index key value # matches an existing value in the index. # - *:name* -- Optional. A name that uniquely identifies the clustered index. - # @option opts [ Hash ] :collation The collation to use. + # @option opts [ Hash ] :collation The collation to use when creating the + # collection. This option will not be sent to the server when calling + # collection methods. # @option opts [ Hash ] :encrypted_fields Hash describing encrypted fields # for queryable encryption. # @option opts [ Integer ] :expire_after Number indicating diff --git a/lib/mongo/collection/view/iterable.rb b/lib/mongo/collection/view/iterable.rb index f9d7b1603a..159719bcb1 100644 --- a/lib/mongo/collection/view/iterable.rb +++ b/lib/mongo/collection/view/iterable.rb @@ -162,6 +162,7 @@ def initial_query_op(session) let: options[:let], limit: limit, allow_disk_use: options[:allow_disk_use], + allow_partial_results: options[:allow_partial_results], read: read, read_concern: options[:read_concern] || read_concern, batch_size: batch_size, diff --git a/spec/integration/find_options_spec.rb b/spec/integration/find_options_spec.rb new file mode 100644 index 0000000000..e5a822a3ea --- /dev/null +++ b/spec/integration/find_options_spec.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Find operation options' do + require_mri + require_no_auth + min_server_fcv '4.4' + + let(:subscriber) { Mrss::EventSubscriber.new } + + let(:seeds) do + [ SpecConfig.instance.addresses.first ] + end + + let(:client) do + ClientRegistry.instance.new_local_client( + seeds, + SpecConfig.instance.test_options.merge(client_options) + ).tap do |client| + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + end + end + + let(:collection) do + client['find_options', collection_options] + end + + let(:find_command) do + subscriber.started_events.find { |cmd| cmd.command_name == 'find' } + end + + before do + ClientRegistry.instance.global_client('authorized')['find_options'].drop + collection.insert_many([ { a: 1 }, { a: 2 }, { a: 3 } ]) + end + + describe 'collation' do + let(:client_options) do + {} + end + + let(:collation) do + { 'locale' => 'en_US' } + end + + context 'when defined on the collection' do + let(:collection_options) do + { collation: collation } + end + + it 'uses the collation defined on the collection' do + collection.find.to_a + expect(find_command.command['collation']).to be_nil + end + end + + context 'when defined on the operation' do + let(:collection_options) do + {} + end + + it 'uses the collation defined on the collection' do + collection.find({}, collation: collation).to_a + expect(find_command.command['collation']).to eq(collation) + end + end + + context 'when defined on both collection and operation' do + let(:collection_options) do + { 'locale' => 'de_AT' } + end + + it 'uses the collation defined on the collection' do + collection.find({}, collation: collation).to_a + expect(find_command.command['collation']).to eq(collation) + end + end + end + + describe 'read concern' do + context 'when defined on the client' do + let(:client_options) do + { read_concern: { level: :local } } + end + + let(:collection_options) do + {} + end + + it 'uses the read concern defined on the client' do + collection.find.to_a + expect(find_command.command['readConcern']).to eq('level' => 'local') + end + + context 'when defined on the collection' do + let(:collection_options) do + { read_concern: { level: :majority } } + end + + it 'uses the read concern defined on the collection' do + collection.find.to_a + expect(find_command.command['readConcern']).to eq('level' => 'majority') + end + + context 'when defined on the operation' do + let(:operation_read_concern) do + { level: :available } + end + + it 'uses the read concern defined on the operation' do + collection.find({}, read_concern: operation_read_concern).to_a + expect(find_command.command['readConcern']).to eq('level' => 'available') + end + end + end + + context 'when defined on the operation' do + let(:collection_options) do + {} + end + + let(:operation_read_concern) do + { level: :available } + end + + it 'uses the read concern defined on the operation' do + collection.find({}, read_concern: operation_read_concern).to_a + expect(find_command.command['readConcern']).to eq('level' => 'available') + end + end + end + + context 'when defined on the collection' do + let(:client_options) do + {} + end + + let(:collection_options) do + { read_concern: { level: :majority } } + end + + it 'uses the read concern defined on the collection' do + collection.find.to_a + expect(find_command.command['readConcern']).to eq('level' => 'majority') + end + + context 'when defined on the operation' do + let(:operation_read_concern) do + { level: :available } + end + + it 'uses the read concern defined on the operation' do + collection.find({}, read_concern: operation_read_concern).to_a + expect(find_command.command['readConcern']).to eq('level' => 'available') + end + end + end + end + + describe 'read preference' do + require_topology :replica_set + + context 'when defined on the client' do + let(:client_options) do + { read: { mode: :secondary } } + end + + let(:collection_options) do + {} + end + + it 'uses the read preference defined on the client' do + collection.find.to_a + expect(find_command.command['$readPreference']).to eq('mode' => 'secondary') + end + + context 'when defined on the collection' do + let(:collection_options) do + { read: { mode: :secondary_preferred } } + end + + it 'uses the read concern defined on the collection' do + collection.find.to_a + expect(find_command.command['$readPreference']).to eq('mode' => 'secondaryPreferred') + end + end + end + end +end diff --git a/spec/runners/unified/crud_operations.rb b/spec/runners/unified/crud_operations.rb index 9274bf62df..69e35513a8 100644 --- a/spec/runners/unified/crud_operations.rb +++ b/spec/runners/unified/crud_operations.rb @@ -32,6 +32,18 @@ def get_find_view(op) if session = args.use('session') opts[:session] = entities.get(:session, session) end + if collation = args.use('collation') + opts[:collation] = collation + end + if args.key?('noCursorTimeout') + opts[:no_cursor_timeout] = args.use('noCursorTimeout') + end + if args.key?('oplogReplay') + opts[:oplog_replay] = args.use('oplogReplay') + end + if args.key?('allowPartialResults') + opts[:allow_partial_results] = args.use('allowPartialResults') + end req = collection.find(args.use!('filter'), **opts) if batch_size = args.use('batchSize') req = req.batch_size(batch_size) diff --git a/spec/spec_tests/data/crud_unified/find-test-all-options.yml b/spec/spec_tests/data/crud_unified/find-test-all-options.yml new file mode 100644 index 0000000000..24be5ee08d --- /dev/null +++ b/spec/spec_tests/data/crud_unified/find-test-all-options.yml @@ -0,0 +1,345 @@ +description: "find options" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name find-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +tests: + - description: "sort" + operations: + - name: find + arguments: + filter: &filter { _name: "John" } + sort: &sort { _id: 1 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + sort: *sort + commandName: find + + - description: "projection" + operations: + - name: find + arguments: + filter: *filter + projection: &projection { _id: 1 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + projection: *projection + commandName: find + databaseName: *database0Name + + - description: "hint" + operations: + - name: find + arguments: + filter: *filter + hint: &hint { _id: 1 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + hint: *hint + commandName: find + databaseName: *database0Name + + - description: "skip" + operations: + - name: find + arguments: + filter: *filter + skip: &skip 10 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + skip: *skip + commandName: find + databaseName: *database0Name + + - description: "limit" + operations: + - name: find + arguments: + filter: *filter + limit: &limit 10 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + limit: *limit + commandName: find + databaseName: *database0Name + + - description: "batchSize" + operations: + - name: find + arguments: + filter: *filter + batchSize: &batchSize 10 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + batchSize: *batchSize + commandName: find + databaseName: *database0Name + + - description: "comment" + operations: + - name: find + arguments: + filter: *filter + comment: &comment 'comment' + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + comment: *comment + commandName: find + databaseName: *database0Name + + - description: "maxTimeMS" + operations: + - name: find + arguments: + filter: *filter + maxTimeMS: &maxTimeMS 1000 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + maxTimeMS: *maxTimeMS + commandName: find + databaseName: *database0Name + + - description: "max" + operations: + - name: find + arguments: + filter: *filter + max: &max { _id: 10 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + max: *max + commandName: find + databaseName: *database0Name + + - description: "min" + operations: + - name: find + arguments: + filter: *filter + hint: { name: 1 } + min: &min { name: 'John' } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + min: *min + commandName: find + databaseName: *database0Name + + - description: "returnKey" + operations: + - name: find + arguments: + filter: *filter + returnKey: &returnKey false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + returnKey: *returnKey + commandName: find + databaseName: *database0Name + + - description: "showRecordId" + operations: + - name: find + arguments: + filter: *filter + showRecordId: &showRecordId false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + showRecordId: *showRecordId + commandName: find + databaseName: *database0Name + + - description: "oplogReplay" + operations: + - name: find + arguments: + filter: *filter + oplogReplay: &oplogReplay false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + oplogReplay: *oplogReplay + commandName: find + databaseName: *database0Name + + - description: "noCursorTimeout" + operations: + - name: find + arguments: + filter: *filter + noCursorTimeout: &noCursorTimeout false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + noCursorTimeout: *noCursorTimeout + commandName: find + databaseName: *database0Name + + - description: "allowPartialResults" + operations: + - name: find + arguments: + filter: *filter + allowPartialResults: &allowPartialResults false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + allowPartialResults: *allowPartialResults + commandName: find + databaseName: *database0Name + + - description: "collation" + operations: + - name: find + arguments: + filter: *filter + collation: &collation { locale: "en" } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + collation: *collation + commandName: find + databaseName: *database0Name + + - description: "allowDiskUse" + runOnRequirements: + - minServerVersion: 4.4 + operations: + - name: find + arguments: + filter: *filter + allowDiskUse: &allowDiskUse true + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + allowDiskUse: *allowDiskUse + commandName: find + databaseName: *database0Name + + - description: "let" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: find + arguments: + filter: *filter + let: &let { name: "Mary" } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + let: *let + commandName: find + databaseName: *database0Name