├── tasks ├── docs.rake ├── release.rake └── testing.rake ├── .yardopts ├── .rspec ├── .coveralls.yml ├── NOTICE ├── lib ├── jsasl.jar ├── mongo │ ├── errors.rb │ ├── protocol.rb │ ├── version.rb │ ├── operation │ │ ├── read.rb │ │ ├── write │ │ │ ├── command.rb │ │ │ ├── command │ │ │ │ ├── remove_user.rb │ │ │ │ ├── create_user.rb │ │ │ │ ├── drop_index.rb │ │ │ │ ├── ensure_index.rb │ │ │ │ ├── insert.rb │ │ │ │ ├── delete.rb │ │ │ │ └── update.rb │ │ │ ├── drop_index.rb │ │ │ ├── remove_user.rb │ │ │ ├── create_user.rb │ │ │ └── ensure_index.rb │ │ ├── read │ │ │ ├── indexes.rb │ │ │ └── query.rb │ │ ├── kill_cursors.rb │ │ ├── slicable.rb │ │ └── aggregate │ │ │ └── result.rb │ ├── server │ │ ├── description │ │ │ ├── inspection.rb │ │ │ └── inspection │ │ │ │ ├── server_added.rb │ │ │ │ └── server_removed.rb │ │ ├── address │ │ │ ├── resolvable.rb │ │ │ ├── unix.rb │ │ │ ├── ipv4.rb │ │ │ └── ipv6.rb │ │ └── context.rb │ ├── event.rb │ ├── grid.rb │ ├── collection │ │ └── view │ │ │ ├── immutable.rb │ │ │ └── explainable.rb │ ├── write_concern │ │ ├── acknowledged.rb │ │ └── unacknowledged.rb │ ├── cluster │ │ ├── mode │ │ │ ├── sharded.rb │ │ │ ├── standalone.rb │ │ │ └── replica_set.rb │ │ └── mode.rb │ ├── event │ │ ├── subscriber.rb │ │ ├── server_added.rb │ │ ├── server_removed.rb │ │ └── publisher.rb │ ├── operation.rb │ ├── index.rb │ ├── auth │ │ ├── executable.rb │ │ ├── x509.rb │ │ ├── ldap.rb │ │ ├── cr.rb │ │ └── kerberos.rb │ ├── pool │ │ └── socket │ │ │ ├── unix.rb │ │ │ ├── tcp.rb │ │ │ ├── connectable.rb │ │ │ └── ssl │ │ │ └── context.rb │ ├── loggable.rb │ ├── protocol │ │ ├── kill_cursors.rb │ │ ├── bit_vector.rb │ │ ├── reply.rb │ │ ├── delete.rb │ │ └── get_more.rb │ ├── socket │ │ ├── unix.rb │ │ └── tcp.rb │ ├── server_preference.rb │ └── server_preference │ │ ├── primary.rb │ │ ├── secondary.rb │ │ └── primary_preferred.rb └── mongo.rb ├── bin └── mongo_console ├── .gitignore ├── spec ├── mongo │ ├── write_concern │ │ ├── unacknowledged_spec.rb │ │ └── acknowledged_spec.rb │ ├── collection │ │ └── view │ │ │ └── explainable_spec.rb │ ├── event │ │ ├── subscriber_spec.rb │ │ └── publisher_spec.rb │ ├── operation │ │ ├── read │ │ │ ├── indexes_spec.rb │ │ │ ├── get_more_spec.rb │ │ │ └── query_spec.rb │ │ ├── write │ │ │ ├── create_user_spec.rb │ │ │ ├── drop_index_spec.rb │ │ │ ├── remove_user_spec.rb │ │ │ ├── ensure_index_spec.rb │ │ │ └── response_spec.rb │ │ ├── kill_cursors_spec.rb │ │ ├── aggregate │ │ │ └── result_spec.rb │ │ └── command_spec.rb │ ├── auth │ │ ├── ldap_spec.rb │ │ ├── x509_spec.rb │ │ ├── cr_spec.rb │ │ └── user │ │ │ └── view_spec.rb │ ├── server │ │ ├── address │ │ │ ├── unix_spec.rb │ │ │ ├── ipv6_spec.rb │ │ │ └── ipv4_spec.rb │ │ ├── description │ │ │ └── inspection │ │ │ │ ├── server_added_spec.rb │ │ │ │ └── server_removed_spec.rb │ │ └── monitor_spec.rb │ ├── pool │ │ └── queue_spec.rb │ ├── cluster │ │ ├── mode │ │ │ ├── sharded_spec.rb │ │ │ ├── standalone_spec.rb │ │ │ └── replica_set_spec.rb │ │ └── mode_spec.rb │ ├── auth_spec.rb │ ├── loggable_spec.rb │ ├── logger_spec.rb │ ├── server_spec.rb │ ├── protocol │ │ └── kill_cursors_spec.rb │ └── server_preference_spec.rb ├── quality_spec.rb ├── support │ ├── shared │ │ ├── protocol.rb │ │ ├── cursor.rb │ │ ├── operation.rb │ │ └── socket.rb │ ├── matchers.rb │ └── monitoring.rb └── spec_helper.rb ├── Rakefile ├── Guardfile ├── gem-public_cert.pem ├── Gemfile ├── mongo.gemspec ├── .travis.yml ├── .rubocop.yml └── CONTRIBUTING.md /tasks/docs.rake: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tasks/release.rake: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | lib/**/*.rb -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --tty 2 | --colour 3 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | MongoDB Ruby Driver 2 | Copyright (C) 2009-2013 MongoDB, Inc. 3 | -------------------------------------------------------------------------------- /lib/jsasl.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sferik/mongo-ruby-driver/master/lib/jsasl.jar -------------------------------------------------------------------------------- /bin/mongo_console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib') 4 | require 'pry' 5 | require 'mongo' 6 | 7 | # include the mongo namespace 8 | include Mongo 9 | 10 | Pry.config.prompt_name = 'mongo' 11 | Pry.start 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *#* 2 | *.bundle 3 | *.class 4 | *.gem 5 | *.log 6 | *.o 7 | *.pid 8 | *.so 9 | *.swp 10 | *~ 11 | .DS_Store 12 | .idea/* 13 | .yardoc 14 | coverage 15 | data 16 | doc 17 | Gemfile.lock 18 | .ruby-gemset 19 | .ruby-version 20 | gem-private_key.pem 21 | nbproject 22 | tmp 23 | -------------------------------------------------------------------------------- /spec/mongo/write_concern/unacknowledged_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::WriteConcern::Unacknowledged do 4 | 5 | let(:concern) do 6 | described_class.new(:w => 0) 7 | end 8 | 9 | describe '#get_last_error' do 10 | 11 | it 'returns nil' do 12 | expect(concern.get_last_error).to be_nil 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/mongo/errors.rb: -------------------------------------------------------------------------------- 1 | module Mongo 2 | # Base error class for all Mongo related errors. 3 | class MongoError < StandardError; end 4 | 5 | # Base error class for all errors coming from the driver. 6 | class DriverError < MongoError; end 7 | 8 | # Base error class for all errors coming from the server. 9 | class OperationError < MongoError; end 10 | end 11 | -------------------------------------------------------------------------------- /tasks/testing.rake: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | RSpec::Core::RakeTask.new(:spec) 4 | task :default => :spec 5 | 6 | namespace :spec do 7 | if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.2' 8 | require 'coveralls/rake/task' 9 | Coveralls::RakeTask.new 10 | task :ci => [:spec, 'coveralls:push'] 11 | else 12 | task :ci => [:spec] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | 5 | begin 6 | require 'bundler' 7 | rescue LoadError 8 | raise '[FAIL] Bundler not found! Install it with ' + 9 | '`gem install bundler; bundle install`.' 10 | end 11 | 12 | default_groups = [:default, :testing] 13 | default_groups << :release unless ENV['TEST'] 14 | Bundler.require(*default_groups) 15 | 16 | Dir.glob('tasks/**/*.rake').sort.each { |r| load r } 17 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # supresses issues with pry hooks on jruby 2 | interactor :simple if RUBY_PLATFORM =~ /java/ 3 | 4 | guard 'bundler' do 5 | watch('Gemfile') 6 | watch(/^.+\.gemspec/) 7 | end 8 | 9 | guard 'rspec', :all_after_pass => false, :cmd => 'bundle exec rspec -t ~style' do 10 | watch(%r{^spec/.+_spec\.rb$}) 11 | watch(%r{^lib/(.+)\.rb$}) { |match| 'spec/#{match[1]}_spec.rb' } 12 | watch('spec/spec_helper.rb') { 'spec' } 13 | end 14 | -------------------------------------------------------------------------------- /lib/mongo/protocol.rb: -------------------------------------------------------------------------------- 1 | # Wire Protocol Base 2 | require 'mongo/protocol/serializers' 3 | require 'mongo/protocol/bit_vector' 4 | require 'mongo/protocol/message' 5 | 6 | # Client Requests 7 | require 'mongo/protocol/delete' 8 | require 'mongo/protocol/get_more' 9 | require 'mongo/protocol/insert' 10 | require 'mongo/protocol/kill_cursors' 11 | require 'mongo/protocol/query' 12 | require 'mongo/protocol/update' 13 | 14 | # Server Responses 15 | require 'mongo/protocol/reply' 16 | -------------------------------------------------------------------------------- /spec/mongo/collection/view/explainable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Collection::View::Explainable do 4 | 5 | let(:selector) do 6 | {} 7 | end 8 | 9 | let(:options) do 10 | {} 11 | end 12 | 13 | let(:view) do 14 | Mongo::Collection::View.new(authorized_collection, selector, options) 15 | end 16 | 17 | after do 18 | authorized_collection.find.remove_many 19 | end 20 | 21 | describe '#explain' do 22 | 23 | let(:explain) do 24 | view.explain 25 | end 26 | 27 | it 'executes an explain' do 28 | expect(explain[:cursor]).to eq('BasicCursor') 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/quality_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Code Quality', :quality do 4 | 5 | if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.2' 6 | pending 'has no style-guide violations', :style do 7 | require 'rubocop' 8 | result = silence { Rubocop::CLI.new.run } 9 | puts '[STYLE] style violations found. ' + 10 | 'Please run \'rubocop\' for a full report.' if result == 1 11 | expect(result).to eq(0) 12 | end 13 | 14 | unless RUBY_PLATFORM =~ /java/ 15 | it 'has required minimum test coverage' do 16 | expect(SimpleCov.result.covered_percent).to be >= COVERAGE_MIN 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/mongo/event/subscriber_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Event::Subscriber do 4 | 5 | let(:klass) do 6 | Class.new do 7 | include Mongo::Event::Subscriber 8 | end 9 | end 10 | 11 | describe '#subscribe_to' do 12 | 13 | let(:publisher) do 14 | double('publisher') 15 | end 16 | 17 | let(:listener) do 18 | double('listener') 19 | end 20 | 21 | let(:subscriber) do 22 | klass.new 23 | end 24 | 25 | it 'adds subscribes the listener to the publisher' do 26 | expect(publisher).to receive(:add_listener).with('test', listener) 27 | subscriber.subscribe_to(publisher, 'test', listener) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/mongo/version.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | VERSION = '2.0.0.alpha' 18 | end 19 | -------------------------------------------------------------------------------- /spec/mongo/operation/read/indexes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Read::Indexes do 4 | 5 | describe '#execute' do 6 | 7 | let(:spec) do 8 | { name: 1 } 9 | end 10 | 11 | before do 12 | authorized_collection.indexes.ensure(spec, unique: true) 13 | end 14 | 15 | after do 16 | authorized_collection.indexes.drop(spec) 17 | end 18 | 19 | let(:operation) do 20 | described_class.new(db_name: TEST_DB, coll_name: TEST_COLL) 21 | end 22 | 23 | let(:indexes) do 24 | operation.execute(authorized_primary.context) 25 | end 26 | 27 | it 'returns the indexes for the collection' do 28 | expect(indexes.documents.size).to eq(2) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/mongo/operation/read.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/operation/read/query' 16 | require 'mongo/operation/read/get_more' 17 | require 'mongo/operation/read/indexes' 18 | -------------------------------------------------------------------------------- /lib/mongo/server/description/inspection.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/server/description/inspection/server_added' 16 | require 'mongo/server/description/inspection/server_removed' 17 | -------------------------------------------------------------------------------- /spec/mongo/auth/ldap_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Auth::LDAP do 4 | 5 | let(:server) do 6 | Mongo::Server.new('127.0.0.1:27017') 7 | end 8 | 9 | let(:connection) do 10 | Mongo::Connection.new(server) 11 | end 12 | 13 | let(:user) do 14 | Mongo::Auth::User.new(database: TEST_DB, user: 'driver', password: 'password') 15 | end 16 | 17 | describe '#login' do 18 | 19 | context 'when the user is not authorized for the database' do 20 | 21 | let(:cr) do 22 | described_class.new(user) 23 | end 24 | 25 | let(:login) do 26 | cr.login(connection).documents[0] 27 | end 28 | 29 | it 'logs the user into the connection' do 30 | expect { 31 | cr.login(connection) 32 | }.to raise_error(Mongo::Auth::Unauthorized) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/mongo/auth/x509_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Auth::X509 do 4 | 5 | let(:server) do 6 | Mongo::Server.new('127.0.0.1:27017') 7 | end 8 | 9 | let(:connection) do 10 | Mongo::Connection.new(server) 11 | end 12 | 13 | let(:user) do 14 | Mongo::Auth::User.new(database: TEST_DB, user: 'driver', password: 'password') 15 | end 16 | 17 | describe '#login' do 18 | 19 | context 'when the user is not authorized for the database' do 20 | 21 | let(:cr) do 22 | described_class.new(user) 23 | end 24 | 25 | let(:login) do 26 | cr.login(connection).documents[0] 27 | end 28 | 29 | it 'logs the user into the connection' do 30 | expect { 31 | cr.login(connection) 32 | }.to raise_error(Mongo::Auth::Unauthorized) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/mongo/server/address/unix_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Server::Address::Unix do 4 | 5 | describe '#initialize' do 6 | 7 | let(:resolver) do 8 | described_class.new('/path/to/socket.sock') 9 | end 10 | 11 | it 'sets the ip to nil' do 12 | expect(resolver.ip).to be_nil 13 | end 14 | 15 | it 'sets the port to nil' do 16 | expect(resolver.port).to be_nil 17 | end 18 | 19 | it 'sets the host' do 20 | expect(resolver.host).to eq('/path/to/socket.sock') 21 | end 22 | end 23 | 24 | describe '#socket' do 25 | 26 | let(:resolver) do 27 | described_class.new('/path/to/socket.sock') 28 | end 29 | 30 | let(:socket) do 31 | resolver.socket(5) 32 | end 33 | 34 | it 'returns a unix socket' do 35 | expect(socket).to be_a(Mongo::Socket::Unix) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/support/shared/protocol.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'message with a header' do 2 | describe 'header' do 3 | describe 'length' do 4 | let(:field) { bytes[0..3] } 5 | it 'serializes the length' do 6 | expect(field).to be_int32(bytes.size) 7 | end 8 | end 9 | 10 | describe 'request id' do 11 | let(:field) { bytes[4..7] } 12 | it 'serializes the request id' do 13 | expect(field).to be_int32(message.request_id) 14 | end 15 | end 16 | 17 | describe 'response to' do 18 | let(:field) { bytes[8..11] } 19 | it 'serializes the response to' do 20 | expect(field).to be_int32(0) 21 | end 22 | end 23 | 24 | describe 'op code' do 25 | let(:field) { bytes[12..15] } 26 | it 'serializes the op code' do 27 | expect(field).to be_int32(opcode) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :be_int32 do |num| 2 | match do |actual| 3 | actual == [num].pack('l<') 4 | end 5 | end 6 | 7 | RSpec::Matchers.define :be_int64 do |num| 8 | match do |actual| 9 | actual == [num].pack('q<') 10 | end 11 | end 12 | 13 | RSpec::Matchers.define :be_int64_sequence do |array| 14 | match do |actual| 15 | actual == array.reduce(String.new) do |buffer, num| 16 | buffer << [num].pack('q<') 17 | end 18 | end 19 | end 20 | 21 | RSpec::Matchers.define :be_cstring do |string| 22 | match do |actual| 23 | actual == "#{string}\0" 24 | end 25 | end 26 | 27 | RSpec::Matchers.define :be_bson do |hash| 28 | match do |actual| 29 | actual == hash.to_bson 30 | end 31 | end 32 | 33 | RSpec::Matchers.define :be_bson_sequence do |array| 34 | match do |actual| 35 | actual == array.map(&:to_bson).join 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/command.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/operation/write/command/writable' 16 | require 'mongo/operation/write/command/delete' 17 | require 'mongo/operation/write/command/insert' 18 | require 'mongo/operation/write/command/update' 19 | require 'mongo/operation/write/command/drop_index' 20 | require 'mongo/operation/write/command/ensure_index' 21 | require 'mongo/operation/write/command/create_user' 22 | require 'mongo/operation/write/command/remove_user' 23 | -------------------------------------------------------------------------------- /lib/mongo/event.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/event/publisher' 16 | require 'mongo/event/subscriber' 17 | require 'mongo/event/server_added' 18 | require 'mongo/event/server_removed' 19 | 20 | module Mongo 21 | 22 | module Event 23 | 24 | # When a server is to be added to a cluster. 25 | # 26 | # @since 2.0.0 27 | SERVER_ADDED = 'server_added'.freeze 28 | 29 | # When a server is to be removed from a cluster. 30 | # 31 | # @since 2.0.0 32 | SERVER_REMOVED = 'server_removed'.freeze 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/mongo/write_concern/acknowledged_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::WriteConcern::Acknowledged do 4 | 5 | describe '#get_last_error' do 6 | 7 | let(:get_last_error) do 8 | concern.get_last_error 9 | end 10 | 11 | context 'when the options are symbols' do 12 | 13 | let(:concern) do 14 | described_class.new(:w => :majority) 15 | end 16 | 17 | it 'converts the values to strings' do 18 | expect(get_last_error).to eq(:getlasterror => 1, :w => 'majority') 19 | end 20 | end 21 | 22 | context 'when the options are strings' do 23 | 24 | let(:concern) do 25 | described_class.new(:w => 'majority') 26 | end 27 | 28 | it 'keeps the values as strings' do 29 | expect(get_last_error).to eq(:getlasterror => 1, :w => 'majority') 30 | end 31 | end 32 | 33 | context 'when the options are numbers' do 34 | 35 | let(:concern) do 36 | described_class.new(:w => 3) 37 | end 38 | 39 | it 'keeps the values as numbers' do 40 | expect(get_last_error).to eq(:getlasterror => 1, :w => 3) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/support/shared/cursor.rb: -------------------------------------------------------------------------------- 1 | shared_context 'shared cursor' do 2 | 3 | let(:client) do 4 | double('client').tap do |client| 5 | allow(client).to receive(:mongos?).and_return(false) 6 | allow(client).to receive(:execute).and_return(*get_mores) 7 | end 8 | end 9 | 10 | let(:db) { Mongo::Database.new(client, TEST_DB) } 11 | let(:collection) do 12 | db[TEST_COLL].tap do |collection| 13 | allow(collection).to receive(:full_namespace) do 14 | "#{db.name}.#{collection.name}" 15 | end 16 | allow(collection).to receive(:client) { client } 17 | end 18 | end 19 | 20 | let(:view_options) { {} } 21 | let(:view) { Mongo::View::Collection.new(collection, {}, view_options) } 22 | 23 | let(:nonzero) { 1 } 24 | let(:b) { proc { |d| d } } 25 | 26 | let(:response) { make_response(1, 3) } 27 | 28 | def make_response(cursor_id = 0, nreturned = 5) 29 | double('response').tap do |response| 30 | allow(response).to receive(:documents) { (0...nreturned).to_a } 31 | allow(response).to receive(:cursor_id) { cursor_id } 32 | end 33 | end 34 | 35 | def get_mores 36 | [ make_response ] 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/mongo/operation/write/create_user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Write::CreateUser do 4 | 5 | describe '#execute' do 6 | 7 | let(:user) do 8 | Mongo::Auth::User.new( 9 | user: 'durran', 10 | password: 'password', 11 | roles: [ Mongo::Auth::Roles::READ_WRITE ] 12 | ) 13 | end 14 | 15 | let(:operation) do 16 | described_class.new(user: user, db_name: TEST_DB) 17 | end 18 | 19 | after do 20 | root_authorized_client.database.users.remove('durran') 21 | end 22 | 23 | context 'when user creation was successful' do 24 | 25 | let!(:response) do 26 | operation.execute(root_authorized_primary.context) 27 | end 28 | 29 | it 'saves the user in the database' do 30 | expect(response).to be_successful 31 | end 32 | end 33 | 34 | context 'when creation was not successful' do 35 | 36 | it 'raises an exception' do 37 | expect { 38 | operation.execute(root_authorized_primary.context) 39 | operation.execute(root_authorized_primary.context) 40 | }.to raise_error(Mongo::Operation::Write::Failure) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /gem-public_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDODCCAiCgAwIBAgIBADANBgkqhkiG9w0BAQUFADBCMRQwEgYDVQQDDAtkcml2 3 | ZXItcnVieTEVMBMGCgmSJomT8ixkARkWBTEwZ2VuMRMwEQYKCZImiZPyLGQBGRYD 4 | Y29tMB4XDTEzMDIwMTE0MTEzN1oXDTE0MDIwMTE0MTEzN1owQjEUMBIGA1UEAwwL 5 | ZHJpdmVyLXJ1YnkxFTATBgoJkiaJk/IsZAEZFgUxMGdlbjETMBEGCgmSJomT8ixk 6 | ARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANFdSAa8fRm1 7 | bAM9za6Z0fAH4g02bqM1NGnw8zJQrE/PFrFfY6IFCT2AsLfOwr1maVm7iU1+kdVI 8 | IQ+iI/9+E+ArJ+rbGV3dDPQ+SLl3mLT+vXjfjcxMqI2IW6UuVtt2U3Rxd4QU0kdT 9 | JxmcPYs5fDN6BgYc6XXgUjy3m+Kwha2pGctdciUOwEfOZ4RmNRlEZKCMLRHdFP8j 10 | 4WTnJSGfXDiuoXICJb5yOPOZPuaapPSNXp93QkUdsqdKC32I+KMpKKYGBQ6yisfA 11 | 5MyVPPCzLR1lP5qXVGJPnOqUAkvEUfCahg7EP9tI20qxiXrR6TSEraYhIFXL0EGY 12 | u8KAcPHm5KkCAwEAAaM5MDcwCQYDVR0TBAIwADAdBgNVHQ4EFgQUW3dZsX70mlSM 13 | CiPrZxAGA1vwfNcwCwYDVR0PBAQDAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQCIa/Y6 14 | xS7YWBxkn9WP0EMnJ3pY9vef9DTmLSi/2jz8PzwlKQ89zNTrqSUD8LoQZmBqCJBt 15 | dKSQ/RUnaHJuxh8HWvWubP8EBYTuf+I1DFnRv648IF3MR1tCQumVL0XcYMvZcxBj 16 | a/p+8DomWTQqUdNbNoGywwjtVBWfDdwFV8Po1XcN/AtpILOJQd9J77INIGGCHxZo 17 | 6SOHHaNknlE9H0w6q0SVxZKZI8/+2c447V0NrHIw1Qhe0tAGJ9V1u3ky8gyxe0SM 18 | 8v7zLF2XliYbfurYIwkcXs8yPn8ggApBIy9bX6VJxRs/l2+UvqzaHIFaFy/F8/GP 19 | RNTuXsVG5NDACo7Q 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /lib/mongo/grid.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/grid/file_store' 16 | require 'mongo/grid/file' 17 | 18 | module Mongo 19 | 20 | class GridError < StandardError; end 21 | 22 | module Grid 23 | 24 | # Default prefix for the 'files' and 'chunks' collections 25 | # 26 | # @since 2.0.0 27 | DEFAULT_FS_NAME = 'fs'.freeze 28 | 29 | # Default size for chunks of data. 30 | # 31 | # @since 2.0.0 32 | DEFAULT_CHUNK_SIZE = (255 * 1024).freeze 33 | 34 | # Default content type for stored files. 35 | # 36 | # @since 2.0.0 37 | DEFAULT_CONTENT_TYPE = 'binary/octet-stream'.freeze 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/mongo.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'forwardable' 16 | require 'bson' 17 | require 'mongo/loggable' 18 | require 'mongo/logger' 19 | require 'mongo/errors' 20 | require 'mongo/event' 21 | require 'mongo/auth' 22 | require 'mongo/client' 23 | require 'mongo/cluster' 24 | require 'mongo/collection' 25 | require 'mongo/connection' 26 | require 'mongo/cursor' 27 | require 'mongo/database' 28 | require 'mongo/grid' 29 | require 'mongo/index' 30 | require 'mongo/operation' 31 | require 'mongo/pool' 32 | require 'mongo/protocol' 33 | require 'mongo/server' 34 | require 'mongo/server_preference' 35 | require 'mongo/socket' 36 | require 'mongo/uri' 37 | require 'mongo/version' 38 | -------------------------------------------------------------------------------- /spec/mongo/auth/cr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Auth::CR do 4 | 5 | let(:server) do 6 | Mongo::Server.new('127.0.0.1:27017') 7 | end 8 | 9 | let(:connection) do 10 | Mongo::Connection.new(server) 11 | end 12 | 13 | describe '#login' do 14 | 15 | context 'when the user is not authorized' do 16 | 17 | let(:user) do 18 | Mongo::Auth::User.new( 19 | database: 'driver', 20 | user: 'notauser', 21 | password: 'password' 22 | ) 23 | end 24 | 25 | let(:cr) do 26 | described_class.new(user) 27 | end 28 | 29 | let(:login) do 30 | cr.login(connection).documents[0] 31 | end 32 | 33 | it 'raises an exception' do 34 | expect { 35 | cr.login(connection) 36 | }.to raise_error(Mongo::Auth::Unauthorized) 37 | end 38 | end 39 | end 40 | 41 | context 'when the user is authorized for the database' do 42 | 43 | let(:cr) do 44 | described_class.new(root_user) 45 | end 46 | 47 | let(:login) do 48 | cr.login(connection).documents[0] 49 | end 50 | 51 | it 'logs the user into the connection' do 52 | expect(cr.login(connection).documents[0]['ok']).to eq(1) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | gem 'rake' 5 | 6 | # group :release do 7 | # gem 'git' 8 | # gem 'kramdown' 9 | # gem 'yard' 10 | # end 11 | 12 | platforms :rbx do 13 | gem 'racc' 14 | gem 'rubysl', '~> 2.0' 15 | gem 'psych' 16 | gem 'rubinius-coverage', github: 'rubinius/rubinius-coverage' 17 | end 18 | 19 | group :development, :testing do 20 | gem 'json', :platforms => [ :jruby ] 21 | gem 'rspec', '~> 3.0' 22 | gem 'mime-types', '~> 1.25' 23 | 24 | platforms :ruby_19, :ruby_20, :ruby_21, :jruby do 25 | gem 'coveralls', :require => false 26 | gem 'rubocop', '0.15.0' if RUBY_VERSION > '1.9' 27 | end 28 | end 29 | 30 | group :development do 31 | if RUBY_VERSION > '1.9' 32 | gem 'rb-fchange', :require => false # Windows 33 | gem 'rb-fsevent', :require => false # OS X 34 | gem 'rb-inotify', :require => false # Linux 35 | gem 'terminal-notifier-guard' 36 | 37 | gem 'guard-bundler' 38 | gem 'guard-rspec', :platforms => :mri 39 | gem 'guard-jruby-rspec', :platforms => :jruby 40 | gem 'guard-rubocop', :platforms => [:ruby_19, :ruby_20, :jruby] 41 | gem 'guard-yard', :platforms => [:mri_19, :mri_20] 42 | end 43 | 44 | gem 'ruby-prof', :platforms => :mri 45 | gem 'pry-rescue' 46 | gem 'pry-nav' 47 | end 48 | -------------------------------------------------------------------------------- /spec/mongo/operation/write/drop_index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Write::DropIndex do 4 | 5 | describe '#execute' do 6 | 7 | context 'when the index exists' do 8 | 9 | let(:spec) do 10 | { another: -1 } 11 | end 12 | 13 | before do 14 | authorized_collection.indexes.ensure(spec, unique: true) 15 | end 16 | 17 | let(:operation) do 18 | described_class.new( 19 | db_name: TEST_DB, 20 | coll_name: TEST_COLL, 21 | index_name: 'another_-1' 22 | ) 23 | end 24 | 25 | let(:response) do 26 | operation.execute(authorized_primary.context) 27 | end 28 | 29 | it 'removes the index' do 30 | expect(response).to be_successful 31 | end 32 | end 33 | 34 | context 'when the index does not exist' do 35 | 36 | let(:operation) do 37 | described_class.new( 38 | db_name: TEST_DB, 39 | coll_name: TEST_COLL, 40 | index_name: 'another_blah' 41 | ) 42 | end 43 | 44 | it 'raises an exception' do 45 | expect { 46 | operation.execute(authorized_primary.context) 47 | }.to raise_error(Mongo::Operation::Write::Failure) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/mongo/pool/queue_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Pool::Queue do 4 | 5 | describe '#dequeue' do 6 | 7 | let(:queue) do 8 | described_class.new { double('connection') } 9 | end 10 | 11 | context 'when the queue is empty' do 12 | 13 | it 'raises a timeout error' do 14 | expect { 15 | queue.dequeue 16 | }.to raise_error(Timeout::Error) 17 | end 18 | end 19 | 20 | context 'when waiting for a connection to be enqueued' do 21 | 22 | let(:connection) do 23 | double('connection') 24 | end 25 | 26 | before do 27 | Thread.new do 28 | sleep(0.5) 29 | queue.enqueue(connection) 30 | end.join 31 | end 32 | 33 | it 'returns the enqueued connection' do 34 | expect(queue.dequeue(1)).to eq(connection) 35 | end 36 | end 37 | end 38 | 39 | describe '#enqueue' do 40 | 41 | let(:queue) do 42 | described_class.new { double('connection') } 43 | end 44 | 45 | let(:connection) do 46 | double('connection') 47 | end 48 | 49 | before do 50 | queue.enqueue(connection) 51 | end 52 | 53 | it 'adds the connection to the queue' do 54 | expect(queue.dequeue).to eq(connection) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/mongo/collection/view/immutable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Collection 17 | class View 18 | module Immutable 19 | 20 | # @return [ Hash ] options The additional query options. 21 | attr_reader :options 22 | 23 | private 24 | 25 | # @api private 26 | # 27 | # @note In the including class, the method #immutable needs to be 28 | # implemented in order to define how a new class of that type needs to 29 | # be instantiated. 30 | def configure(field, value) 31 | return options[field] if value.nil? 32 | new(options.merge(field => value)) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/mongo/write_concern/acknowledged.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module WriteConcern 17 | 18 | # An acknowledged write concern provides a get last error command with the 19 | # appropriate options on each write operation. 20 | # 21 | # @since 2.0.0 22 | class Acknowledged < Mode 23 | 24 | # Get the get last error command for the concern. 25 | # 26 | # @example Get the gle command. 27 | # acknowledged.get_last_error 28 | # 29 | # @return [ Hash ] The gle command. 30 | # 31 | # @since 2.0.0 32 | def get_last_error 33 | @get_last_error ||= { :getlasterror => 1 }.merge(normalize(options)) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/mongo/cluster/mode/sharded_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Cluster::Mode::Sharded do 4 | 5 | describe '.servers' do 6 | 7 | let(:mongos) do 8 | Mongo::Server.new('127.0.0.1:27017') 9 | end 10 | 11 | let(:standalone) do 12 | Mongo::Server.new('127.0.0.1:27017') 13 | end 14 | 15 | let(:replica_set) do 16 | Mongo::Server.new('127.0.0.1:27017') 17 | end 18 | 19 | let(:mongos_description) do 20 | Mongo::Server::Description.new(mongos, { 'msg' => 'isdbgrid' }) 21 | end 22 | 23 | let(:standalone_description) do 24 | Mongo::Server::Description.new(standalone, { 'ismaster' => true }) 25 | end 26 | 27 | let(:replica_set_description) do 28 | Mongo::Server::Description.new(replica_set, { 'ismaster' => true, 'setName' => 'testing' }) 29 | end 30 | 31 | before do 32 | mongos.instance_variable_set(:@description, mongos_description) 33 | standalone.instance_variable_set(:@description, standalone_description) 34 | replica_set.instance_variable_set(:@description, replica_set_description) 35 | end 36 | 37 | let(:servers) do 38 | described_class.servers([ mongos, standalone, replica_set ]) 39 | end 40 | 41 | it 'returns only mongos servers' do 42 | expect(servers).to eq([ mongos ]) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /mongo.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'mongo/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'mongo' 7 | s.rubyforge_project = 'mongo' 8 | s.version = Mongo::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | 11 | s.authors = ['Tyler Brock', 'Gary Murakami', 'Emily Stolfo', 'Brandon Black', 'Durran Jordan'] 12 | s.email = 'mongodb-dev@googlegroups.com' 13 | s.homepage = 'http://www.mongodb.org' 14 | s.summary = 'Ruby driver for MongoDB' 15 | s.description = 'A Ruby driver for MongoDB' 16 | s.license = 'Apache License Version 2.0' 17 | 18 | if File.exists?('gem-private_key.pem') 19 | s.signing_key = 'gem-private_key.pem' 20 | s.cert_chain = ['gem-public_cert.pem'] 21 | else 22 | warn "[#{s.name}] Warning: No private key present, creating unsigned gem." 23 | end 24 | 25 | s.files = Dir.glob('{bin,lib,spec}/**/*') 26 | s.files += %w[mongo.gemspec LICENSE README.md CONTRIBUTING.md Rakefile] 27 | s.test_files = Dir.glob('spec/**/*') 28 | 29 | s.require_paths = ['lib'] 30 | s.has_rdoc = 'yard' 31 | s.bindir = 'bin' 32 | 33 | s.add_dependency 'bson', '~> 2.2' 34 | end 35 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/command/remove_user.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Write 18 | module Command 19 | 20 | # Remove user commands on non-legacy servers. 21 | # 22 | # @since 2.0.0 23 | class RemoveUser 24 | include Executable 25 | include Writable 26 | 27 | private 28 | 29 | # The query selector for this drop user command operation. 30 | # 31 | # @return [ Hash ] The selector describing this drop user operation. 32 | # 33 | # @since 2.0.0 34 | def selector 35 | { :dropUser => @spec[:name] } 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/mongo/write_concern/unacknowledged.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module WriteConcern 17 | 18 | # An unacknowledged write concern will provide no error on write outside of 19 | # network and connection exceptions. 20 | # 21 | # @since 2.0.0 22 | class Unacknowledged < Mode 23 | 24 | # The noop constant for the gle. 25 | # 26 | # @since 2.0.0 27 | NOOP = nil 28 | 29 | # Get the gle command for an unacknowledged write. 30 | # 31 | # @example Get the gle command. 32 | # unacknowledged.get_last_error 33 | # 34 | # @return [ nil ] The noop. 35 | # 36 | # @since 2.0.0 37 | def get_last_error 38 | NOOP 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 1.9.3 5 | - 2.0.0 6 | - 2.1.1 7 | - ruby-head 8 | - jruby-19mode 9 | - jruby-head 10 | 11 | jdk: 12 | - oraclejdk7 13 | - openjdk7 14 | - oraclejdk8 15 | 16 | install: ruby -S bundle install --without release development 17 | 18 | env: CI="travis" 19 | 20 | script: bundle exec rake spec:ci 21 | 22 | notifications: 23 | email: false 24 | flowdock: 1da4416b8ff98d1880986472428b1b1b 25 | 26 | services: 27 | - mongodb 28 | 29 | branches: 30 | only: 31 | - master 32 | - 1.x-stable 33 | - 2.x-stable 34 | 35 | matrix: 36 | allow_failures: 37 | - rvm: ruby-head 38 | - rvm: jruby-head 39 | - rvm: rbx-2.2.5 40 | exclude: 41 | - rvm: 1.9.3 42 | jdk: openjdk7 43 | env: CI="travis" 44 | - rvm: 2.0.0 45 | jdk: openjdk7 46 | env: CI="travis" 47 | - rvm: 2.1.1 48 | jdk: openjdk7 49 | env: CI="travis" 50 | - rvm: ruby-head 51 | jdk: openjdk7 52 | env: CI="travis" 53 | - rvm: rbx-2.2.5 54 | jdk: openjdk7 55 | env: CI="travis" 56 | - rvm: 1.9.3 57 | jdk: oraclejdk8 58 | env: CI="travis" 59 | - rvm: 2.0.0 60 | jdk: oraclejdk8 61 | env: CI="travis" 62 | - rvm: 2.1.1 63 | jdk: oraclejdk8 64 | env: CI="travis" 65 | - rvm: ruby-head 66 | jdk: oraclejdk8 67 | env: CI="travis" 68 | -------------------------------------------------------------------------------- /lib/mongo/cluster/mode/sharded.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Cluster 17 | module Mode 18 | 19 | # Defines behaviour for when a cluster is in sharded mode. 20 | # 21 | # @since 2.0.0 22 | class Sharded 23 | 24 | # Select appropriate servers for this mode. 25 | # 26 | # @example Select the servers. 27 | # Sharded.servers(servers, 'test') 28 | # 29 | # @param [ Array ] servers The known servers. 30 | # 31 | # @return [ Array ] The mongos servers. 32 | # 33 | # @since 2.0.0 34 | def self.servers(servers, name = nil) 35 | servers.select{ |server| server.mongos? } 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/mongo/operation/write/remove_user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Write::RemoveUser do 4 | 5 | describe '#execute' do 6 | 7 | before do 8 | root_authorized_client.database.users.create( 9 | 'durran', 10 | password: 'password', roles: [ Mongo::Auth::Roles::READ_WRITE ] 11 | ) 12 | end 13 | 14 | let(:operation) do 15 | described_class.new(name: 'durran', db_name: TEST_DB) 16 | end 17 | 18 | context 'when user removal was successful' do 19 | 20 | let!(:response) do 21 | operation.execute(root_authorized_primary.context) 22 | end 23 | 24 | it 'removes the user from the database' do 25 | expect(response).to be_successful 26 | end 27 | end 28 | 29 | context 'when removal was not successful' do 30 | 31 | before do 32 | operation.execute(root_authorized_primary.context) 33 | end 34 | 35 | it 'raises an exception', if: write_command_enabled? do 36 | expect { 37 | operation.execute(root_authorized_primary.context) 38 | }.to raise_error(Mongo::Operation::Write::Failure) 39 | end 40 | 41 | it 'does not raise an exception', unless: write_command_enabled? do 42 | expect(operation.execute(root_authorized_primary.context).written_count).to eq(0) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/mongo/event/subscriber.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Event 17 | 18 | # Adds convenience methods for adding listeners to event publishers. 19 | # 20 | # @since 2.0.0 21 | module Subscriber 22 | 23 | # Subscribe to the provided publisher's event. 24 | # 25 | # @example Subscribe to the event. 26 | # subscriber.subscribe_to(publisher, 'test', listener) 27 | # 28 | # @param [ Mongo::Event::Publisher ] publisher The publisher. 29 | # @param [ String ] event The event. 30 | # @param [ Object ] listener The event listener. 31 | # 32 | # @since 2.0.0 33 | def subscribe_to(publisher, event, listener) 34 | publisher.add_listener(event, listener) 35 | end 36 | end 37 | end 38 | end 39 | 40 | -------------------------------------------------------------------------------- /lib/mongo/cluster/mode/standalone.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Cluster 17 | module Mode 18 | 19 | # Defines behaviour for when a cluster is in standalone mode. 20 | # 21 | # @since 2.0.0 22 | class Standalone 23 | 24 | # Select appropriate servers for this mode. 25 | # 26 | # @example Select the servers. 27 | # Standalone.servers(servers, 'test') 28 | # 29 | # @param [ Array ] servers The known servers. 30 | # 31 | # @return [ Array ] The standalone servers. 32 | # 33 | # @since 2.0.0 34 | def self.servers(servers, name = nil) 35 | [ servers.detect{ |server| server.standalone? } ] 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/command/create_user.rb: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2009-2014 MongoDB, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | module Mongo 17 | module Operation 18 | module Write 19 | module Command 20 | 21 | # Create user commands on non-legacy servers. 22 | # 23 | # @since 2.0.0 24 | class CreateUser 25 | include Executable 26 | include Writable 27 | 28 | private 29 | 30 | # The query selector for this create user command operation. 31 | # 32 | # @return [ Hash ] The selector describing this create user operation. 33 | # 34 | # @since 2.0.0 35 | def selector 36 | { :createUser => user.name, :digestPassword => false }.merge(user.spec) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/mongo/operation.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/operation/result' 16 | require 'mongo/operation/executable' 17 | require 'mongo/operation/slicable' 18 | require 'mongo/operation/read' 19 | require 'mongo/operation/write' 20 | require 'mongo/operation/aggregate' 21 | require 'mongo/operation/command' 22 | require 'mongo/operation/kill_cursors' 23 | require 'mongo/operation/map_reduce' 24 | 25 | module Mongo 26 | module Operation 27 | 28 | # Legacy error message field. 29 | # 30 | # @since 2.0.0 31 | ERROR = 'err'.freeze 32 | 33 | # The write errors field in the response, 2.6 and higher. 34 | # 35 | # @since 2.0.0 36 | WRITE_ERRORS = 'writeErrors'.freeze 37 | 38 | # Constant for the error code field. 39 | # 40 | # @since 2.0.0 41 | ERROR_CODE = 'code'.freeze 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/support/monitoring.rb: -------------------------------------------------------------------------------- 1 | module Mongo 2 | class Cluster 3 | 4 | # Force a scan of all servers in the cluster. 5 | # 6 | # @api test 7 | # 8 | # @example Scan the cluster. 9 | # cluster.scan! 10 | # 11 | # @note This is for testing purposes only. 12 | # 13 | # @return [ true ] Always true if no error. 14 | # 15 | # @since 2.0.0 16 | def scan! 17 | @servers.each{ |server| server.check! } and true 18 | end 19 | end 20 | 21 | class Server 22 | 23 | # Tells the monitor to immediately check the server status. 24 | # 25 | # @api test 26 | # 27 | # @example Check the server status. 28 | # server.check! 29 | # 30 | # @note Used for testing purposes. 31 | # 32 | # @return [ Server::Description ] The updated server description. 33 | # 34 | # @since 2.0.0 35 | def check! 36 | @monitor.check! 37 | end 38 | 39 | # In the test suite we don't need the monitor to run. 40 | def initialize(address, options = {}) 41 | @address = Address.new(address) 42 | @options = options 43 | @mutex = Mutex.new 44 | @monitor = Monitor.new(self, options) 45 | @description = Description.new(self) 46 | end 47 | 48 | class Monitor 49 | 50 | # We do synchronous scans in the test suite so need to expose the ability 51 | # to do it in the monitor. 52 | def check! 53 | server.description.update!(*ismaster) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/mongo/auth_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Auth do 4 | 5 | describe '#get' do 6 | 7 | context 'when a mongodb_cr user is provided' do 8 | 9 | let(:user) do 10 | Mongo::Auth::User.new(auth_mech: :mongodb_cr) 11 | end 12 | 13 | let(:cr) do 14 | described_class.get(user) 15 | end 16 | 17 | it 'returns CR' do 18 | expect(cr).to be_a(Mongo::Auth::CR) 19 | end 20 | end 21 | 22 | context 'when a mongodb_x509 user is provided' do 23 | 24 | let(:user) do 25 | Mongo::Auth::User.new(auth_mech: :mongodb_x509) 26 | end 27 | 28 | let(:x509) do 29 | described_class.get(user) 30 | end 31 | 32 | it 'returns X509' do 33 | expect(x509).to be_a(Mongo::Auth::X509) 34 | end 35 | end 36 | 37 | context 'when a plain user is provided' do 38 | 39 | let(:user) do 40 | Mongo::Auth::User.new(auth_mech: :plain) 41 | end 42 | 43 | let(:ldap) do 44 | described_class.get(user) 45 | end 46 | 47 | it 'returns LDAP' do 48 | expect(ldap).to be_a(Mongo::Auth::LDAP) 49 | end 50 | end 51 | 52 | context 'when an invalid mechanism is provided' do 53 | 54 | let(:user) do 55 | Mongo::Auth::User.new(auth_mech: :nothing) 56 | end 57 | 58 | it 'raises an error' do 59 | expect { 60 | described_class.get(user) 61 | }.to raise_error(Mongo::Auth::InvalidMechanism) 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/mongo/loggable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Loggable do 4 | 5 | let(:operation) do 6 | Class.new do 7 | def log_message 8 | 'test' 9 | end 10 | end.new 11 | end 12 | 13 | describe '#log' do 14 | 15 | let(:loggable) do 16 | Class.new do 17 | include Mongo::Loggable 18 | end.new 19 | end 20 | 21 | let(:operation) do 22 | double('operation') 23 | end 24 | 25 | before do 26 | expect(operation).to receive(:log_message).and_return('test') 27 | expect(Mongo::Logger).to receive(:debug).with('MONGO', 'test', anything()) 28 | end 29 | 30 | context 'when a block is provided' do 31 | 32 | context 'when an exception occurs' do 33 | 34 | it 'logs the message' do 35 | expect { 36 | loggable.log(:debug, 'MONGO', [ operation ]) do 37 | raise RuntimeError 38 | end 39 | }.to raise_error(RuntimeError) 40 | end 41 | end 42 | 43 | context 'when no exception occurs' do 44 | 45 | it 'executes the block and logs the message' do 46 | expect( 47 | loggable.log(:debug, 'MONGO', [ operation ]) do 48 | 'testing' 49 | end 50 | ).to eq('testing') 51 | end 52 | end 53 | end 54 | 55 | context 'when no block is provided' do 56 | 57 | it 'logs the message' do 58 | expect(loggable.log(:debug, 'MONGO', [ operation ])).to be_nil 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/mongo/cluster/mode/standalone_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Cluster::Mode::Standalone do 4 | 5 | describe '.servers' do 6 | 7 | let(:mongos) do 8 | Mongo::Server.new('127.0.0.1:27017') 9 | end 10 | 11 | let(:standalone) do 12 | Mongo::Server.new('127.0.0.1:27017') 13 | end 14 | 15 | let(:standalone_two) do 16 | Mongo::Server.new('127.0.0.1:27017') 17 | end 18 | 19 | let(:replica_set) do 20 | Mongo::Server.new('127.0.0.1:27017') 21 | end 22 | 23 | let(:mongos_description) do 24 | Mongo::Server::Description.new(mongos, { 'msg' => 'isdbgrid' }) 25 | end 26 | 27 | let(:standalone_description) do 28 | Mongo::Server::Description.new(standalone, { 'ismaster' => true }) 29 | end 30 | 31 | let(:replica_set_description) do 32 | Mongo::Server::Description.new(replica_set, { 'ismaster' => true, 'setName' => 'testing' }) 33 | end 34 | 35 | before do 36 | mongos.instance_variable_set(:@description, mongos_description) 37 | standalone.instance_variable_set(:@description, standalone_description) 38 | standalone_two.instance_variable_set(:@description, standalone_description) 39 | replica_set.instance_variable_set(:@description, replica_set_description) 40 | end 41 | 42 | let(:servers) do 43 | described_class.servers([ mongos, standalone, standalone_two, replica_set ]) 44 | end 45 | 46 | it 'returns only the first standalone server' do 47 | expect(servers).to eq([ standalone ]) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/mongo/collection/view/explainable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Collection 17 | class View 18 | 19 | # Defines explain related behaviour for collection view. 20 | # 21 | # @since 2.0.0 22 | module Explainable 23 | 24 | # Get the explain plan for the query. 25 | # 26 | # @example Get the explain plan for the query. 27 | # view.explain 28 | # 29 | # @return [ Hash ] A single document with the explain plan. 30 | # 31 | # @since 2.0.0 32 | def explain 33 | self.class.new(collection, selector, options.merge(explain_options)).first 34 | end 35 | 36 | private 37 | 38 | def explained? 39 | !!options[:explain] 40 | end 41 | 42 | def explain_options 43 | explain_limit = limit || 0 44 | { :limit => -explain_limit.abs, :explain => true } 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/mongo/server/description/inspection/server_added_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Server::Description::Inspection::ServerAdded do 4 | 5 | let(:server) do 6 | Mongo::Server.new('127.0.0.1:27017') 7 | end 8 | 9 | describe '.run' do 10 | 11 | let(:config) do 12 | { 13 | 'ismaster' => true, 14 | 'secondary' => false, 15 | 'hosts' => [ '127.0.0.1:27018', '127.0.0.1:27019' ], 16 | 'setName' => 'test' 17 | } 18 | end 19 | 20 | let(:description) do 21 | Mongo::Server::Description.new(server, config) 22 | end 23 | 24 | let(:updated) do 25 | Mongo::Server::Description.new(server, new_config) 26 | end 27 | 28 | let(:listener) do 29 | double('listener') 30 | end 31 | 32 | before do 33 | server.add_listener(Mongo::Event::SERVER_ADDED, listener) 34 | end 35 | 36 | context 'when a server is added' do 37 | 38 | let(:new_config) do 39 | { 'hosts' => [ '127.0.0.1:27019', '127.0.0.1:27020' ] } 40 | end 41 | 42 | it 'fires a server added event' do 43 | expect(listener).to receive(:handle).with('127.0.0.1:27020') 44 | described_class.run(description, updated) 45 | end 46 | end 47 | 48 | context 'when no server is added' do 49 | 50 | let(:new_config) do 51 | { 'hosts' => [ '127.0.0.1:27018', '127.0.0.1:27019' ] } 52 | end 53 | 54 | it 'fires no event' do 55 | expect(listener).to_not receive(:handle) 56 | described_class.run(description, updated) 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/mongo/operation/read/indexes.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Read 18 | 19 | # A MongoDB get indexes operation. 20 | # 21 | # @since 2.0.0 22 | class Indexes 23 | include Executable 24 | 25 | # Initialize the get indexes operation. 26 | # 27 | # @example Instantiate the operation. 28 | # Read::Indexes.new(:db_name => 'test', :coll_name => 'test_coll') 29 | # 30 | # @param [ Hash ] spec The specifications for the insert. 31 | # 32 | # @option spec :db_name [ String ] The name of the database. 33 | # @option spec :coll_name [ String ] The name of the collection. 34 | # 35 | # @since 2.0.0 36 | def initialize(spec) 37 | @spec = spec 38 | end 39 | 40 | private 41 | 42 | def message 43 | Protocol::Query.new(db_name, Index::COLLECTION, { ns: namespace }, options) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/mongo/cluster/mode/replica_set.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Cluster 17 | module Mode 18 | 19 | # Defines behaviour when a cluster is in replica set mode. 20 | # 21 | # @since 2.0.0 22 | class ReplicaSet 23 | 24 | # Select appropriate servers for this mode. 25 | # 26 | # @example Select the servers. 27 | # ReplicaSet.servers(servers, 'test') 28 | # 29 | # @param [ Array ] servers The known servers. 30 | # @param [ String ] replica_set_name The name of the replica set. 31 | # 32 | # @return [ Array ] The servers in the replica set. 33 | # 34 | # @since 2.0.0 35 | def self.servers(servers, replica_set_name = nil) 36 | servers.select do |server| 37 | (replica_set_name.nil? || server.replica_set_name == replica_set_name) && 38 | server.primary? || server.secondary? 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/mongo/server/monitor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Server::Monitor do 4 | 5 | describe '#check!' do 6 | 7 | let(:server) do 8 | Mongo::Server.new('127.0.0.1:27017') 9 | end 10 | 11 | let(:monitor) do 12 | described_class.new(server) 13 | end 14 | 15 | before do 16 | monitor.check! 17 | end 18 | 19 | it 'updates the server description' do 20 | expect(server.description).to be_standalone 21 | end 22 | end 23 | 24 | describe '#heartbeat_frequency' do 25 | 26 | let(:server) do 27 | Mongo::Server.new('127.0.0.1:27017') 28 | end 29 | 30 | context 'when an option is provided' do 31 | 32 | let(:monitor) do 33 | described_class.new(server, :heartbeat_frequency => 5) 34 | end 35 | 36 | it 'returns the option' do 37 | expect(monitor.heartbeat_frequency).to eq(5) 38 | end 39 | end 40 | 41 | context 'when no option is provided' do 42 | 43 | let(:monitor) do 44 | described_class.new(server) 45 | end 46 | 47 | it 'defaults to 5' do 48 | expect(monitor.heartbeat_frequency).to eq(10) 49 | end 50 | end 51 | end 52 | 53 | describe '#run' do 54 | 55 | let(:server) do 56 | Mongo::Server.new('127.0.0.1:27017') 57 | end 58 | 59 | let(:monitor) do 60 | described_class.new(server, :heartbeat_frequency => 1) 61 | end 62 | 63 | before do 64 | monitor.run 65 | sleep(1) 66 | end 67 | 68 | it 'refreshes the server on the provided interval' do 69 | expect(server.description).to_not be_nil 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/mongo/server/description/inspection/server_added.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Server 17 | class Description 18 | module Inspection 19 | 20 | # Handles inspecting the result of an ismaster command for servers 21 | # added to the cluster. 22 | # 23 | # @since 2.0.0 24 | class ServerAdded 25 | 26 | # Run the server added inspection. 27 | # 28 | # @example Run the inspection. 29 | # ServerAdded.run(description, {}) 30 | # 31 | # @param [ Description ] description The server description. 32 | # @param [ Description ] updated The updated description. 33 | # 34 | # @since 2.0.0 35 | def self.run(description, updated) 36 | updated.hosts.each do |host| 37 | unless description.hosts.include?(host) 38 | description.server.publish(Event::SERVER_ADDED, host) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/mongo/cluster/mode_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Cluster::Mode do 4 | 5 | describe '.get' do 6 | 7 | context 'when provided a replica set option' do 8 | 9 | let(:mode) do 10 | described_class.get(mode: :replica_set) 11 | end 12 | 13 | it 'returns a replica set mode' do 14 | expect(mode).to eq(Mongo::Cluster::Mode::ReplicaSet) 15 | end 16 | end 17 | 18 | context 'when provided a standalone option' do 19 | 20 | let(:mode) do 21 | described_class.get(mode: :standalone) 22 | end 23 | 24 | it 'returns a standalone mode' do 25 | expect(mode).to eq(Mongo::Cluster::Mode::Standalone) 26 | end 27 | end 28 | 29 | context 'when provided a sharded option' do 30 | 31 | let(:mode) do 32 | described_class.get(mode: :sharded) 33 | end 34 | 35 | it 'returns a sharded mode' do 36 | expect(mode).to eq(Mongo::Cluster::Mode::Sharded) 37 | end 38 | end 39 | 40 | context 'when provided no option' do 41 | 42 | context 'when a set name is in the options' do 43 | 44 | let(:mode) do 45 | described_class.get(set_name: 'testing') 46 | end 47 | 48 | it 'returns a replica set mode' do 49 | expect(mode).to eq(Mongo::Cluster::Mode::ReplicaSet) 50 | end 51 | end 52 | 53 | context 'when no set name is in the options' do 54 | 55 | let(:mode) do 56 | described_class.get({}) 57 | end 58 | 59 | it 'returns a standalone mode' do 60 | expect(mode).to eq(Mongo::Cluster::Mode::Standalone) 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/mongo/server/description/inspection/server_removed.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Server 17 | class Description 18 | module Inspection 19 | 20 | # Handles inspecting the result of an ismaster command for servers 21 | # that were removed from the cluster. 22 | # 23 | # @since 2.0.0 24 | class ServerRemoved 25 | 26 | # Run the server added inspection. 27 | # 28 | # @example Run the inspection. 29 | # ServerAdded.run(description, {}) 30 | # 31 | # @param [ Description ] description The server description. 32 | # @param [ Description ] updated The updated description. 33 | # 34 | # @since 2.0.0 35 | def self.run(description, updated) 36 | description.hosts.each do |host| 37 | if updated.primary? && !updated.hosts.include?(host) 38 | description.server.publish(Event::SERVER_REMOVED, host) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/mongo/event/server_added.rb: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2009-2014 MongoDB, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | module Mongo 17 | module Event 18 | 19 | # This handles host added events for server descriptions. 20 | # 21 | # @since 2.0.0 22 | class ServerAdded 23 | 24 | # @return [ Mongo::Cluster ] cluster The event publisher. 25 | attr_reader :cluster 26 | 27 | # Initialize the new host added event handler. 28 | # 29 | # @example Create the new handler. 30 | # ServerAdded.new(cluster) 31 | # 32 | # @param [ Mongo::Cluster ] cluster The cluster to publish from. 33 | # 34 | # @since 2.0.0 35 | def initialize(cluster) 36 | @cluster = cluster 37 | end 38 | 39 | # This event publishes an event to add the cluster and logs the 40 | # configuration change. 41 | # 42 | # @example Handle the event. 43 | # server_added.handle('127.0.0.1:27018') 44 | # 45 | # @param [ String ] address The added host. 46 | # 47 | # @since 2.0.0 48 | def handle(address) 49 | cluster.add(address) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/mongo/index.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/index/view' 16 | 17 | module Mongo 18 | 19 | # Contains constants for indexing purposes. 20 | # 21 | # @since 2.0.0 22 | module Index 23 | 24 | # Specify ascending order for an index. 25 | # 26 | # @since 2.0.0 27 | ASCENDING = 1 28 | 29 | # Specify descending order for an index. 30 | # 31 | # @since 2.0.0 32 | DESCENDING = -1 33 | 34 | # Specify a 2d Geo index. 35 | # 36 | # @since 2.0.0 37 | GEO2D = '2d'.freeze 38 | 39 | # Specify a 2d sphere Geo index. 40 | # 41 | # @since 2.0.0 42 | GEO2DSPHERE = '2dsphere'.freeze 43 | 44 | # Specify a geoHaystack index. 45 | # 46 | # @since 2.0.0 47 | GEOHAYSTACK = 'geoHaystack'.freeze 48 | 49 | # Encodes a text index. 50 | # 51 | # @since 2.0.0 52 | TEXT = 'text'.freeze 53 | 54 | # Specify a hashed index. 55 | # 56 | # @since 2.0.0 57 | HASHED = 'hashed'.freeze 58 | 59 | # Constant for the indexes collection. 60 | # 61 | # @since 2.0.0 62 | COLLECTION = 'system.indexes'.freeze 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/mongo/operation/kill_cursors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::KillCursors do 4 | include_context 'operation' 5 | 6 | let(:spec) { { :cursor_ids => [1,2] } } 7 | let(:op) { described_class.new(spec) } 8 | 9 | describe '#initialize' do 10 | 11 | it 'sets the spec' do 12 | expect(op.spec).to be(spec) 13 | end 14 | end 15 | 16 | describe '#==' do 17 | 18 | context ' when two ops have different specs' do 19 | let(:other_spec) do 20 | { :cursor_ids => [1, 2, 3] } 21 | end 22 | let(:other) { described_class.new(other_spec) } 23 | 24 | it 'returns false' do 25 | expect(op).not_to eq(other) 26 | end 27 | end 28 | end 29 | 30 | context '#merge' do 31 | let(:other_op) { described_class.new(spec) } 32 | 33 | it 'is not allowed' do 34 | expect{ op.merge(other_op) }.to raise_exception 35 | end 36 | end 37 | 38 | context '#merge!' do 39 | let(:other_op) { described_class.new(spec) } 40 | 41 | it 'is not allowed' do 42 | expect{ op.merge!(other_op) }.to raise_exception 43 | end 44 | end 45 | 46 | describe '#execute' do 47 | 48 | context 'message' do 49 | 50 | it 'creates a kill cursors wire protocol message with correct specs' do 51 | expect(Mongo::Protocol::KillCursors).to receive(:new) do |ids| 52 | expect(ids).to eq(spec[:cursor_ids]) 53 | end 54 | op.execute(primary_context) 55 | end 56 | end 57 | 58 | context 'connection' do 59 | 60 | it 'dispatches the message on the connection' do 61 | expect(connection).to receive(:dispatch) 62 | op.execute(primary_context) 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/command/drop_index.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Write 18 | module Command 19 | 20 | # A MongoDB drop index write command operation. 21 | # Supported in server versions >= 2.5.5 22 | # 23 | # @example 24 | # Write::Command::DropIndex.new({ 25 | # :index => { :foo => 1 }, 26 | # :db_name => 'test', 27 | # :coll_name => 'test_coll', 28 | # :index_name => 'foo_1' 29 | # }) 30 | 31 | # @since 2.0.0 32 | class DropIndex 33 | include Executable 34 | include Writable 35 | 36 | private 37 | 38 | # The query selector for this drop index command operation. 39 | # 40 | # @return [ Hash ] The selector describing this insert operation. 41 | # 42 | # @since 2.0.0 43 | def selector 44 | { 45 | :deleteIndexes => coll_name, 46 | :index => index_name 47 | } 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | 55 | -------------------------------------------------------------------------------- /spec/support/shared/operation.rb: -------------------------------------------------------------------------------- 1 | shared_context 'operation' do 2 | 3 | let(:db_name) { 'TEST_DB' } 4 | let(:coll_name) { 'test_coll' } 5 | let(:write_concern) { Mongo::WriteConcern::Mode.get(:w => 1) } 6 | let(:options) { {} } 7 | 8 | # Server doubles 9 | let(:secondary_server) do 10 | double('secondary_server').tap do |s| 11 | allow(s).to receive(:secondary?) { true } 12 | end 13 | end 14 | let(:primary_server) do 15 | double('primary_server').tap do |s| 16 | allow(s).to receive(:secondary?) { false } 17 | allow(s).to receive(:context) { primary_context } 18 | end 19 | end 20 | 21 | # Context doubles 22 | let(:primary_context) do 23 | double('primary_context').tap do |cxt| 24 | allow(cxt).to receive(:with_connection).and_yield(connection) 25 | allow(cxt).to receive(:server) { primary_server } 26 | allow(cxt).to receive(:write_command_enabled?) { true } 27 | allow(cxt).to receive(:primary?) { true } 28 | end 29 | end 30 | let(:secondary_context) do 31 | double('secondary_context').tap do |cxt| 32 | allow(cxt).to receive(:with_connection).and_yield(connection) 33 | allow(cxt).to receive(:server) do 34 | secondary_server 35 | end 36 | end 37 | end 38 | let(:primary_context_2_4_version) do 39 | double('primary_context').tap do |cxt| 40 | allow(cxt).to receive(:with_connection).and_yield(connection) 41 | allow(cxt).to receive(:server) { primary_server } 42 | allow(cxt).to receive(:write_command_enabled?) { false } 43 | allow(cxt).to receive(:primary?) { true } 44 | end 45 | end 46 | 47 | let(:connection) do 48 | double('connection').tap do |conn| 49 | allow(conn).to receive(:dispatch) { [] } 50 | end 51 | end 52 | end 53 | 54 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | # include gemspec and Rakefile 3 | Includes: 4 | - '**/*.gemspec' 5 | - '**/Rakefile' 6 | - 'tasks/**/*.rake' 7 | Excludes: [] 8 | 9 | # limit lines to 80 characters. 10 | LineLength: 11 | Max: 80 12 | Enabled: true 13 | 14 | # avoid methods longer than 30 lines of code 15 | MethodLength: 16 | Max: 30 17 | CountComments: false 18 | Enabled: true 19 | 20 | # avoid parameter lists longer than 5 parameters. 21 | ParameterLists: 22 | Max: 5 23 | CountKeywordArgs: true 24 | Enabled: true 25 | 26 | # don't prefer 'fail' over raise 27 | SignalException: 28 | Description: 'Checks for proper usage of fail and raise.' 29 | Enabled: false 30 | 31 | # disabled while we still support ruby 1.8 32 | HashSyntax: 33 | Description: 'Prefer Ruby 1.9 hash syntax over 1.8 syntax' 34 | Enabled: false 35 | 36 | # this doesn't work for things like port numbers 37 | # we don't enforce this but please do it where it makes sense 38 | NumericLiterals: 39 | Description: 'Add underscores to large numeric literals' 40 | Enabled: false 41 | 42 | # we have some special exception types with error messages built in 43 | RaiseArgs: 44 | Description: 'Checks the arguments passed to raise/fail.' 45 | Enabled: false 46 | 47 | # used in the wire-protocol 48 | ClassVars: 49 | Description: 'Avoid the use of class variables.' 50 | Enabled: false 51 | 52 | # don't enforce arbitrary max class length 53 | ClassLength: 54 | Description: 'Class length restriction' 55 | Enabled: false 56 | 57 | # enforce UTF-8 Encoding for Ruby 1.9 58 | Encoding: 59 | Enabled: false 60 | 61 | EmptyLinesAroundBody: 62 | Description: 'Keeps track of blank lines around expression bodies.' 63 | Enabled: false 64 | 65 | # because ruby 1.8.x, that's why 66 | DotPosition: 67 | Enabled: false 68 | -------------------------------------------------------------------------------- /lib/mongo/auth/executable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 - 2014 MongoDB Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Auth 17 | 18 | # Defines common behaviour for executable authorization commands. 19 | # 20 | # @since 2.0.0 21 | module Executable 22 | 23 | # @return [ Mongo::Auth::User ] The user to authenticate. 24 | attr_reader :user 25 | 26 | # Instantiate a new authenticator. 27 | # 28 | # @example Create the authenticator. 29 | # Mongo::Auth::X509.new(user) 30 | # 31 | # @param [ Mongo::Auth::User ] user The user to authenticate. 32 | # 33 | # @since 2.0.0 34 | def initialize(user) 35 | @user = user 36 | end 37 | 38 | private 39 | 40 | # If we are on MongoDB 2.6 and higher, we *always* authorize against the 41 | # admin database. Otherwise for 2.4 and lower we authorize against the 42 | # database provided, or the optional auth_source option. The logic for 43 | # that is encapsulated in the User class. 44 | def auth_database(connection) 45 | if connection.write_command_enabled? 46 | Database::ADMIN 47 | else 48 | user.database 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/mongo/server/address/resolvable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'resolv' 16 | 17 | module Mongo 18 | class Server 19 | class Address 20 | 21 | # Provides common behaviour between IPv4, IPv6, and socket address 22 | # resolution. 23 | # 24 | # @since 2.0.0 25 | module Resolvable 26 | 27 | # @return [ String ] host The original host name. 28 | attr_reader :host 29 | 30 | # @return [ String ] ip The resolved ip address. 31 | attr_reader :ip 32 | 33 | # @return [ Integer ] port The port. 34 | attr_reader :port 35 | 36 | # @return [ String ] seed The seed address. 37 | attr_reader :seed 38 | 39 | # Resolve the ip address. Will ensure that the resolved ip matches the 40 | # appropriate ip type. 41 | # 42 | # @example Resolve the ip address. 43 | # resolvable.resolve! 44 | # 45 | # @return [ String ] The ip address. 46 | # 47 | # @since 2.0.0 48 | def resolve! 49 | Resolv.each_address(host) do |address| 50 | return @ip = address if address =~ pattern 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | 58 | -------------------------------------------------------------------------------- /lib/mongo/auth/x509.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 - 2014 MongoDB Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Auth 17 | 18 | # Defines behaviour for x.509 authentication. 19 | # 20 | # @since 2.0.0 21 | class X509 22 | include Executable 23 | 24 | # The authentication mechinism string. 25 | # 26 | # @since 2.0.0 27 | MECHANISM = 'MONGODB-X509'.freeze 28 | 29 | # Log the user in on the given connection. 30 | # 31 | # @example Log the user in. 32 | # user.login(connection) 33 | # 34 | # @param [ Mongo::Connection ] connection The connection to log into. 35 | # on. 36 | # 37 | # @return [ Protocol::Reply ] The authentication response. 38 | # 39 | # @since 2.0.0 40 | def login(connection) 41 | reply = connection.dispatch([ login_message ]) 42 | raise Unauthorized.new(user) if reply.documents[0]['ok'] == 0 43 | reply 44 | end 45 | 46 | private 47 | 48 | def login_message 49 | Protocol::Query.new( 50 | Auth::EXTERNAL, 51 | Database::COMMAND, 52 | { authenticate: 1, user: user.name, mechanism: MECHANISM }, 53 | limit: -1 54 | ) 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/mongo/event/server_removed.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Event 17 | 18 | # This handles host removed events for server descriptions. 19 | # 20 | # @since 2.0.0 21 | class ServerRemoved 22 | include Loggable 23 | 24 | # @return [ Mongo::Cluster ] cluster The event publisher. 25 | attr_reader :cluster 26 | 27 | # Initialize the new host removed event handler. 28 | # 29 | # @example Create the new handler. 30 | # ServerRemoved.new(cluster) 31 | # 32 | # @param [ Mongo::Cluster ] cluster The cluster to publish from. 33 | # 34 | # @since 2.0.0 35 | def initialize(cluster) 36 | @cluster = cluster 37 | end 38 | 39 | # This event publishes an event to remove from the cluster and logs the 40 | # configuration change. 41 | # 42 | # @example Handle the event. 43 | # server_removed.handle('127.0.0.1:27018') 44 | # 45 | # @param [ String ] address The removed host. 46 | # 47 | # @since 2.0.0 48 | def handle(address) 49 | log(:debug, 'MONGODB', [ "#{address} being removed from the cluster." ]) 50 | cluster.remove(address) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/command/ensure_index.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Write 18 | module Command 19 | 20 | # A MongoDB ensure index write command operation. 21 | # Supported in server versions >= 2.5.5 22 | # 23 | # @example 24 | # Write::Command::EnsureIndex.new({ 25 | # :index => { :foo => 1 }, 26 | # :db_name => 'test', 27 | # :coll_name => 'test_coll', 28 | # :index_name => 'foo_1' 29 | # :options => { :unique => true } 30 | # }) 31 | # @since 2.0.0 32 | class EnsureIndex 33 | include Executable 34 | include Writable 35 | 36 | private 37 | 38 | # The query selector for this ensure index command operation. 39 | # 40 | # @return [ Hash ] The selector describing this insert operation. 41 | # 42 | # @since 2.0.0 43 | def selector 44 | { 45 | :createIndexes => coll_name, 46 | :indexes => [ options.merge(key: index, name: index_name) ], 47 | } 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | 55 | -------------------------------------------------------------------------------- /lib/mongo/cluster/mode.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/cluster/mode/replica_set' 16 | require 'mongo/cluster/mode/sharded' 17 | require 'mongo/cluster/mode/standalone' 18 | 19 | module Mongo 20 | class Cluster 21 | 22 | # Defines behaviour for getting selection modes. 23 | # 24 | # @since 2.0.0 25 | module Mode 26 | 27 | # The 2 various modes for server selection. 28 | # 29 | # @since 2.0.0 30 | OPTIONS = { 31 | replica_set: ReplicaSet, 32 | sharded: Sharded, 33 | standalone: Standalone 34 | } 35 | 36 | # Get the cluster mode for the provided options. 37 | # 38 | # @example Get the cluster mode. 39 | # Mode.get(mode: :replica_set) 40 | # 41 | # @note If a mode is not specified, we will return a replica set mode if 42 | # a set_name option is provided, otherwise a standalone. 43 | # 44 | # @param [ Hash ] options The cluster options. 45 | # 46 | # @return [ ReplicaSet, Sharded, Standalone ] The mode. 47 | # 48 | # @since 2.0.0 49 | def self.get(options) 50 | return OPTIONS.fetch(options[:mode]) if options.has_key?(:mode) 51 | options.has_key?(:set_name) ? ReplicaSet : Standalone 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/mongo/auth/user/view_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Auth::User::View do 4 | 5 | let(:view) do 6 | described_class.new(root_authorized_client.database) 7 | end 8 | 9 | describe '#create' do 10 | 11 | let!(:response) do 12 | view.create( 13 | 'durran', 14 | password: 'password', roles: [ Mongo::Auth::Roles::READ_WRITE ] 15 | ) 16 | end 17 | 18 | after do 19 | view.remove('durran') 20 | end 21 | 22 | context 'when user creation was successful' do 23 | 24 | it 'saves the user in the database' do 25 | expect(response).to be_successful 26 | end 27 | end 28 | 29 | context 'when creation was not successful' do 30 | 31 | it 'raises an exception' do 32 | expect { 33 | view.create('durran', password: 'password') 34 | }.to raise_error(Mongo::Operation::Write::Failure) 35 | end 36 | end 37 | end 38 | 39 | describe '#remove' do 40 | 41 | context 'when user removal was successful' do 42 | 43 | before do 44 | view.create( 45 | 'durran', 46 | password: 'password', roles: [ Mongo::Auth::Roles::READ_WRITE ] 47 | ) 48 | end 49 | 50 | let(:response) do 51 | view.remove('durran') 52 | end 53 | 54 | it 'saves the user in the database' do 55 | expect(response).to be_successful 56 | end 57 | end 58 | 59 | context 'when removal was not successful' do 60 | 61 | it 'raises an exception', if: write_command_enabled? do 62 | expect { 63 | view.remove('notauser') 64 | }.to raise_error(Mongo::Operation::Write::Failure) 65 | end 66 | 67 | it 'does not raise an exception', unless: write_command_enabled? do 68 | expect(view.remove('notauser').written_count).to eq(0) 69 | end 70 | end 71 | end 72 | 73 | describe '#update' do 74 | 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/mongo/pool/socket/unix.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Pool 17 | module Socket 18 | 19 | # Wrapper for Unix sockets. 20 | # 21 | # @since 2.0.0 22 | class Unix 23 | include Socket::Connectable 24 | 25 | # Establishes a socket connection. 26 | # 27 | # @example Connect to the socket. 28 | # sock.connect 29 | # 30 | # @return [ Unix ] The connected socket instance. 31 | # 32 | # @since 2.0.0 33 | def connect! 34 | initialize! 35 | end 36 | 37 | # Initializes a new Unix socket. 38 | # 39 | # @example Create the Unix socket. 40 | # Unix.new('/path/to/socket.sock', 30) 41 | # 42 | # @param [ String ] path The path to the unix socket. 43 | # @param [ Float ] timeout The socket timeout value. 44 | # @param [ Integer ] family The socket family. 45 | # 46 | # @since 2.0.0 47 | def initialize(path, timeout, family) 48 | @host = path 49 | @timeout = timeout 50 | @family = family 51 | end 52 | 53 | private 54 | 55 | def initialize_socket 56 | sock = default_socket 57 | sock.connect(host) 58 | sock 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/mongo/loggable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | # Allows objects to easily log operations. 18 | # 19 | # @since 2.0.0 20 | module Loggable 21 | 22 | # Log the operations. If a block is provided it will be yielded to, 23 | # otherwise only the logging will take place. 24 | # 25 | # @example Log a query operation. 26 | # loggable.log(:debug, "MONGO.query", operations) 27 | # 28 | # @param [ Symbol ] level The log level. 29 | # @param [ String ] prefix The prefix for the log line. 30 | # @param [ Array ] operations The operations to log. The must 31 | # respond to #log_message. 32 | # 33 | # @return [ Object ] The result of the block or nil if no block given. 34 | # 35 | # @since 2.0.0 36 | def log(level, prefix, operations) 37 | started = Time.new 38 | begin 39 | yield if block_given? 40 | rescue Exception => e 41 | raise e 42 | ensure 43 | runtime = ("%.4fms" % (1000 * (Time.now.to_f - started.to_f))) 44 | operations.each do |operation| 45 | Logger.send(level, prefix, log_inspect(operation), runtime) 46 | end 47 | end 48 | end 49 | 50 | private 51 | 52 | def log_inspect(operation) 53 | operation.respond_to?(:log_message) ? operation.log_message : operation 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/mongo/server/address/ipv6_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Server::Address::IPv6 do 4 | 5 | describe '#initialize' do 6 | 7 | context 'when a port is provided' do 8 | 9 | let(:resolver) do 10 | described_class.new('[::1]:27017') 11 | end 12 | 13 | it 'sets the host ip' do 14 | expect(resolver.ip).to eq('::1') 15 | end 16 | 17 | it 'sets the port' do 18 | expect(resolver.port).to eq(27017) 19 | end 20 | 21 | it 'sets the host' do 22 | expect(resolver.host).to eq('::1') 23 | end 24 | end 25 | 26 | context 'when no port is provided' do 27 | 28 | let(:resolver) do 29 | described_class.new('[::1]') 30 | end 31 | 32 | it 'sets the host ip' do 33 | expect(resolver.ip).to eq('::1') 34 | end 35 | 36 | it 'sets the port to 27017' do 37 | expect(resolver.port).to eq(27017) 38 | end 39 | 40 | it 'sets the host' do 41 | expect(resolver.host).to eq('::1') 42 | end 43 | end 44 | end 45 | 46 | describe '#socket' do 47 | 48 | let(:resolver) do 49 | described_class.new('[::1]') 50 | end 51 | 52 | context 'when ssl options are provided' do 53 | 54 | let(:socket) do 55 | resolver.socket(5, :ssl => true) 56 | end 57 | 58 | it 'returns an ssl socket' do 59 | expect(socket).to be_a(Mongo::Socket::SSL) 60 | end 61 | 62 | it 'sets the family as ipv6' do 63 | expect(socket.family).to eq(Socket::PF_INET6) 64 | end 65 | end 66 | 67 | context 'when ssl options are not provided' do 68 | 69 | let(:socket) do 70 | resolver.socket(5) 71 | end 72 | 73 | it 'returns a tcp socket' do 74 | expect(socket).to be_a(Mongo::Socket::TCP) 75 | end 76 | 77 | it 'sets the family a ipv6' do 78 | expect(socket.family).to eq(Socket::PF_INET6) 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/mongo/auth/ldap.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 - 2014 MongoDB Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Auth 17 | 18 | # Defines behaviour for LDAP Proxy authentication. 19 | # 20 | # @since 2.0.0 21 | class LDAP 22 | include Executable 23 | 24 | # The authentication mechinism string. 25 | # 26 | # @since 2.0.0 27 | MECHANISM = 'PLAIN'.freeze 28 | 29 | # Log the user in on the given connection. 30 | # 31 | # @example Log the user in. 32 | # user.login(connection) 33 | # 34 | # @param [ Mongo::Connection ] connection The connection to log into. 35 | # on. 36 | # 37 | # @return [ Protocol::Reply ] The authentication response. 38 | # 39 | # @since 2.0.0 40 | def login(connection) 41 | reply = connection.dispatch([ login_message ]) 42 | raise Unauthorized.new(user) if reply.documents[0]['ok'] == 0 43 | reply 44 | end 45 | 46 | private 47 | 48 | def login_message 49 | Protocol::Query.new( 50 | Auth::EXTERNAL, 51 | Database::COMMAND, 52 | { 53 | authenticate: 1, 54 | user: user.name, 55 | password: user.password, 56 | digestPassword: false, 57 | mechanism: MECHANISM 58 | }, 59 | limit: -1 60 | ) 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/mongo/protocol/kill_cursors.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Protocol 17 | 18 | # MongoDB Wire protocol KillCursors message. 19 | # 20 | # This is a client request message that is sent to the server in order 21 | # to kill a number of cursors. 22 | # 23 | # @api semipublic 24 | class KillCursors < Message 25 | 26 | # Creates a new KillCursors message 27 | # 28 | # @example Kill the cursor on the server with id 1. 29 | # KillCursors.new([1]) 30 | # 31 | # @param cursor_ids [Array] The cursor ids to kill. 32 | # @param options [Hash] The additional kill cursors options. 33 | def initialize(cursor_ids, options = {}) 34 | @cursor_ids = cursor_ids 35 | @id_count = @cursor_ids.size 36 | end 37 | 38 | private 39 | 40 | # The operation code required to specify +KillCursors+ message. 41 | # @return [Fixnum] the operation code. 42 | def op_code 43 | 2007 44 | end 45 | 46 | # Field representing Zero encoded as an Int32. 47 | field :zero, Zero 48 | 49 | # @!attribute 50 | # @return [Fixnum] Count of the number of cursor ids. 51 | field :id_count, Int32 52 | 53 | # @!attribute 54 | # @return [Array] Cursors to kill. 55 | field :cursor_ids, Int64, :multi => true 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/mongo/server/address/ipv4_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Server::Address::IPv4 do 4 | 5 | describe '#initialize' do 6 | 7 | context 'when a port is provided' do 8 | 9 | let(:resolver) do 10 | described_class.new('127.0.0.1:27017') 11 | end 12 | 13 | it 'sets the host ip' do 14 | expect(resolver.ip).to eq('127.0.0.1') 15 | end 16 | 17 | it 'sets the port' do 18 | expect(resolver.port).to eq(27017) 19 | end 20 | 21 | it 'sets the host' do 22 | expect(resolver.host).to eq('127.0.0.1') 23 | end 24 | end 25 | 26 | context 'when no port is provided' do 27 | 28 | let(:resolver) do 29 | described_class.new('127.0.0.1') 30 | end 31 | 32 | it 'sets the host ip' do 33 | expect(resolver.ip).to eq('127.0.0.1') 34 | end 35 | 36 | it 'sets the port to 27017' do 37 | expect(resolver.port).to eq(27017) 38 | end 39 | 40 | it 'sets the host' do 41 | expect(resolver.host).to eq('127.0.0.1') 42 | end 43 | end 44 | end 45 | 46 | describe '#socket' do 47 | 48 | let(:resolver) do 49 | described_class.new('127.0.0.1') 50 | end 51 | 52 | context 'when ssl options are provided' do 53 | 54 | let(:socket) do 55 | resolver.socket(5, :ssl => true) 56 | end 57 | 58 | it 'returns an ssl socket' do 59 | expect(socket).to be_a(Mongo::Socket::SSL) 60 | end 61 | 62 | it 'sets the family as ipv4' do 63 | expect(socket.family).to eq(Socket::PF_INET) 64 | end 65 | end 66 | 67 | context 'when ssl options are not provided' do 68 | 69 | let(:socket) do 70 | resolver.socket(5) 71 | end 72 | 73 | it 'returns a tcp socket' do 74 | expect(socket).to be_a(Mongo::Socket::TCP) 75 | end 76 | 77 | it 'sets the family a ipv4' do 78 | expect(socket.family).to eq(Socket::PF_INET) 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/mongo/pool/socket/tcp.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Pool 17 | module Socket 18 | 19 | # Wrapper for TCP sockets. 20 | # 21 | # @since 2.0.0 22 | class TCP 23 | include Socket::Connectable 24 | 25 | # Establishes a socket connection. 26 | # 27 | # @example Connect the socket. 28 | # sock.connect! 29 | # 30 | # @note This method mutates the object by setting the socket 31 | # internally. 32 | # 33 | # @return [ TCP ] The connected socket instance. 34 | # 35 | # @since 2.0.0 36 | def connect! 37 | initialize! 38 | end 39 | 40 | # Initializes a new TCP socket. 41 | # 42 | # @example Create the TCP socket. 43 | # TCP.new('::1', 27017, 30) 44 | # TCP.new('127.0.0.1', 27017, 30) 45 | # 46 | # @param [ String ] host The hostname or IP address. 47 | # @param [ Integer ] port The port number. 48 | # @param [ Float ] timeout The socket timeout value. 49 | # @param [ Integer ] family The socket family. 50 | # 51 | # @since 2.0.0 52 | def initialize(host, port, timeout, family) 53 | @host = host 54 | @port = port 55 | @timeout = timeout 56 | @family = family 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/mongo/operation/kill_cursors.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | module Operation 18 | 19 | # A MongoDB kill cursors operation. 20 | # 21 | # @since 2.0.0 22 | class KillCursors 23 | include Executable 24 | 25 | # The ids of the cursors to kill. 26 | # 27 | # @return [Array] Ids of the cursors to kill. 28 | # 29 | # @since 2.0.0 30 | attr_reader :cursor_ids 31 | 32 | # Initialize the kill cursors operation. 33 | # 34 | # @example 35 | # Mongo::Operation::KillCursor.new({ :cursor_ids => [1, 2] }) 36 | # 37 | # @param [ Hash ] spec The specifications for the operation. 38 | # 39 | # @option spec :cursor_ids [ Array ] The ids of cursors to kill. 40 | # 41 | # @since 2.0.0 42 | def initialize(spec) 43 | @spec = spec 44 | end 45 | 46 | private 47 | 48 | # The ids of the cursors to kill. 49 | # 50 | # @return [ Array ] The cursor ids. 51 | # 52 | # @since 2.0.0 53 | def cursor_ids 54 | @spec[:cursor_ids] 55 | end 56 | 57 | # The wire protocol message for this kill cursors operation. 58 | # 59 | # @return [ Mongo::Protocol::KillCursors ] Wire protocol message. 60 | # 61 | # @since 2.0.0 62 | def message 63 | Protocol::KillCursors.new(cursor_ids) 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/mongo/pool/socket/connectable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'socket' 16 | require 'openssl' 17 | require 'timeout' 18 | 19 | module Mongo 20 | class Pool 21 | module Socket 22 | 23 | # Module for behavior common across all supported socket types. 24 | module Connectable 25 | include ::Socket::Constants 26 | 27 | # The pack directive for timeouts. 28 | # 29 | # @since 2.0.0 30 | TIMEOUT_PACK = 'l_2'.freeze 31 | 32 | # @return [ Integer ] family The socket family (IPv4, IPv6, Unix). 33 | attr_reader :family 34 | 35 | # @return [ String ] host The host to connect to. 36 | attr_reader :host 37 | 38 | # @return [ Integer ] port The port to connect to. 39 | attr_reader :port 40 | 41 | # @return [ Float ] timeout The connection timeout. 42 | attr_reader :timeout 43 | 44 | def initialize! 45 | @socket = initialize_socket 46 | yield if block_given? 47 | self 48 | end 49 | 50 | def initialize_socket 51 | Timeout.timeout(timeout, Mongo::SocketTimeoutError) do 52 | sock = default_socket 53 | sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) 54 | # @todo: durran: This is where I deadlock at random. 55 | sock.connect(::Socket.pack_sockaddr_in(port, host)) 56 | sock 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/command/insert.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Write 18 | module Command 19 | 20 | # A MongoDB insert write command operation. 21 | # Supported in server versions >= 2.5.5 22 | # 23 | # @example 24 | # include Mongo 25 | # include Operation 26 | # Write::Command::Insert.new({ :documents => [{ :foo => 1 }], 27 | # :db_name => 'test', 28 | # :coll_name => 'test_coll', 29 | # :write_concern => write_concern, 30 | # :ordered => true 31 | # }) 32 | # @since 2.0.0 33 | class Insert 34 | include Executable 35 | include Writable 36 | 37 | private 38 | 39 | # The query selector for this insert command operation. 40 | # 41 | # @return [ Hash ] The selector describing this insert operation. 42 | # 43 | # @since 2.0.0 44 | def selector 45 | { :insert => coll_name, 46 | :documents => @spec[:documents], 47 | :write_concern => write_concern.options, 48 | :ordered => ordered? 49 | } 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /lib/mongo/socket/unix.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Socket 17 | 18 | # Wrapper for Unix sockets. 19 | # 20 | # @since 2.0.0 21 | class Unix < Socket 22 | 23 | # @return [ String ] path The path to connect to. 24 | attr_reader :path 25 | 26 | # @return [ Float ] timeout The connection timeout. 27 | attr_reader :timeout 28 | 29 | # Establishes a socket connection. 30 | # 31 | # @example Connect the socket. 32 | # sock.connect! 33 | # 34 | # @note This method mutates the object by setting the socket 35 | # internally. 36 | # 37 | # @return [ Unix ] The connected socket instance. 38 | # 39 | # @since 2.0.0 40 | def connect! 41 | Timeout.timeout(timeout, Mongo::SocketTimeoutError) do 42 | socket.connect(path) 43 | self 44 | end 45 | end 46 | 47 | # Initializes a new Unix socket. 48 | # 49 | # @example Create the Unix socket. 50 | # Unix.new('::1', 27017, 30) 51 | # Unix.new('127.0.0.1', 27017, 30) 52 | # 53 | # @param [ String ] host The hostname or IP address. 54 | # @param [ Integer ] port The port number. 55 | # @param [ Float ] timeout The socket timeout value. 56 | # @param [ Integer ] family The socket family. 57 | # 58 | # @since 2.0.0 59 | def initialize(path, timeout, family) 60 | @path, @timeout = path, timeout 61 | super(family) 62 | end 63 | end 64 | end 65 | end 66 | 67 | -------------------------------------------------------------------------------- /spec/mongo/operation/read/get_more_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Read::GetMore do 4 | include_context 'operation' 5 | 6 | let(:to_return) { 50 } 7 | let(:cursor_id) { 1 } 8 | 9 | let(:spec) do 10 | { :db_name => db_name, 11 | :coll_name => coll_name, 12 | :to_return => to_return, 13 | :cursor_id => cursor_id } 14 | end 15 | 16 | let(:op) { described_class.new(spec) } 17 | 18 | describe '#initialize' do 19 | 20 | it 'sets the spec' do 21 | expect(op.spec).to be(spec) 22 | end 23 | end 24 | 25 | describe '#==' do 26 | 27 | context ' when two ops have different specs' do 28 | let(:other_spec) do 29 | { :db_name => 'test_db', 30 | :coll_name => 'test_coll', 31 | :to_return => 50, 32 | :cursor_id => 2 } 33 | end 34 | let(:other) { described_class.new(other_spec) } 35 | 36 | it 'returns false' do 37 | expect(op).not_to eq(other) 38 | end 39 | end 40 | end 41 | 42 | context '#merge' do 43 | let(:other_op) { described_class.new(spec) } 44 | 45 | it 'is not allowed' do 46 | expect{ op.merge(other_op) }.to raise_exception 47 | end 48 | end 49 | 50 | context '#merge!' do 51 | let(:other_op) { described_class.new(spec) } 52 | 53 | it 'is not allowed' do 54 | expect{ op.merge!(other_op) }.to raise_exception 55 | end 56 | end 57 | 58 | describe '#execute' do 59 | 60 | context 'message' do 61 | 62 | it 'creates a get more wire protocol message with correct specs' do 63 | expect(Mongo::Protocol::GetMore).to receive(:new) do |db, coll, to_ret, id| 64 | expect(db).to eq(db_name) 65 | expect(coll).to eq(coll_name) 66 | expect(to_ret).to eq(to_return) 67 | expect(id).to eq(cursor_id) 68 | end 69 | op.execute(primary_context) 70 | end 71 | end 72 | 73 | context 'connection' do 74 | 75 | it 'dispatches the message on the connection' do 76 | expect(connection).to receive(:dispatch) 77 | op.execute(primary_context) 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/mongo/operation/write/ensure_index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Write::EnsureIndex do 4 | 5 | describe '#execute' do 6 | 7 | context 'when the index is created' do 8 | 9 | let(:spec) do 10 | { random: 1 } 11 | end 12 | 13 | let(:operation) do 14 | described_class.new( 15 | index: spec, 16 | db_name: TEST_DB, 17 | coll_name: TEST_COLL, 18 | index_name: 'random_1', 19 | options: { unique: true } 20 | ) 21 | end 22 | 23 | let(:response) do 24 | operation.execute(authorized_primary.context) 25 | end 26 | 27 | after do 28 | authorized_collection.indexes.drop(spec) 29 | end 30 | 31 | it 'returns ok' do 32 | expect(response).to be_successful 33 | end 34 | end 35 | 36 | context 'when index creation fails' do 37 | 38 | let(:spec) do 39 | { random: 1 } 40 | end 41 | 42 | let(:operation) do 43 | described_class.new( 44 | index: spec, 45 | db_name: TEST_DB, 46 | coll_name: TEST_COLL, 47 | index_name: 'random_1', 48 | options: { unique: true } 49 | ) 50 | end 51 | 52 | let(:second_operation) do 53 | described_class.new( 54 | index: spec, 55 | db_name: TEST_DB, 56 | coll_name: TEST_COLL, 57 | index_name: 'random_1', 58 | options: { unique: false } 59 | ) 60 | end 61 | 62 | before do 63 | operation.execute(authorized_primary.context) 64 | end 65 | 66 | after do 67 | authorized_collection.indexes.drop(spec) 68 | end 69 | 70 | it 'raises an exception', if: write_command_enabled? do 71 | expect { 72 | second_operation.execute(authorized_primary.context) 73 | }.to raise_error(Mongo::Operation::Write::Failure) 74 | end 75 | 76 | it 'does not raise an exception', unless: write_command_enabled? do 77 | expect(second_operation.execute(authorized_primary.context)).to be_successful 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/mongo/operation/write/response_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # describe Mongo::Operation::Write::Response do 4 | 5 | # describe '#initialize' do 6 | 7 | # let(:response) do 8 | # described_class.new(reply) 9 | # end 10 | 11 | # context 'when the reply is successful' do 12 | 13 | # let(:reply) do 14 | # Mongo::Protocol::Reply.new 15 | # end 16 | 17 | # let(:documents) do 18 | # [{ 'ok' => 1.0, 'n' => 1 }] 19 | # end 20 | 21 | # before do 22 | # reply.instance_variable_set(:@documents, documents) 23 | # end 24 | 25 | # it 'sets the documents on the response' do 26 | # expect(response.documents).to eq(documents) 27 | # end 28 | 29 | # it 'sets the written count' do 30 | # expect(response.n).to eq(1) 31 | # end 32 | # end 33 | 34 | # context 'when the reply is not successful' do 35 | 36 | # let(:reply) do 37 | # Mongo::Protocol::Reply.new 38 | # end 39 | 40 | # let(:documents) do 41 | # [{ 'ok' => 0.0, 'n' => 1 }] 42 | # end 43 | 44 | # before do 45 | # reply.instance_variable_set(:@documents, documents) 46 | # end 47 | 48 | # it 'raises an exception' do 49 | # expect { response }.to raise_error 50 | # end 51 | # end 52 | 53 | # context 'when the reply is nil' do 54 | 55 | # let(:reply) { nil } 56 | 57 | # it 'does not raise an exception' do 58 | # expect(response.documents).to be_empty 59 | # end 60 | 61 | # it 'does not set the written count' do 62 | # expect(response.n).to be_nil 63 | # end 64 | # end 65 | 66 | # context 'when providing a count' do 67 | 68 | # let(:reply) do 69 | # Mongo::Protocol::Reply.new 70 | # end 71 | 72 | # let(:response) do 73 | # described_class.new(reply, 5) 74 | # end 75 | 76 | # before do 77 | # reply.instance_variable_set(:@documents, [{ 'ok' => 1 }]) 78 | # end 79 | 80 | # it 'sets the document count' do 81 | # expect(response.n).to eq(5) 82 | # end 83 | # end 84 | # end 85 | # end 86 | -------------------------------------------------------------------------------- /spec/mongo/cluster/mode/replica_set_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Cluster::Mode::ReplicaSet do 4 | 5 | describe '.servers' do 6 | 7 | let(:mongos) do 8 | Mongo::Server.new('127.0.0.1:27017') 9 | end 10 | 11 | let(:standalone) do 12 | Mongo::Server.new('127.0.0.1:27017') 13 | end 14 | 15 | let(:replica_set) do 16 | Mongo::Server.new('127.0.0.1:27017') 17 | end 18 | 19 | let(:replica_set_two) do 20 | Mongo::Server.new('127.0.0.1:27017') 21 | end 22 | 23 | let(:mongos_description) do 24 | Mongo::Server::Description.new(mongos, { 'msg' => 'isdbgrid' }) 25 | end 26 | 27 | let(:standalone_description) do 28 | Mongo::Server::Description.new(standalone, { 'ismaster' => true }) 29 | end 30 | 31 | let(:replica_set_description) do 32 | Mongo::Server::Description.new(replica_set, { 'ismaster' => true, 'setName' => 'testing' }) 33 | end 34 | 35 | let(:replica_set_two_description) do 36 | Mongo::Server::Description.new(replica_set, { 'ismaster' => true, 'setName' => 'test' }) 37 | end 38 | 39 | before do 40 | mongos.instance_variable_set(:@description, mongos_description) 41 | standalone.instance_variable_set(:@description, standalone_description) 42 | replica_set.instance_variable_set(:@description, replica_set_description) 43 | replica_set_two.instance_variable_set(:@description, replica_set_two_description) 44 | end 45 | 46 | context 'when no replica set name is provided' do 47 | 48 | let(:servers) do 49 | described_class.servers([ mongos, standalone, replica_set, replica_set_two ]) 50 | end 51 | 52 | it 'returns only replica set members' do 53 | expect(servers).to eq([ replica_set, replica_set_two ]) 54 | end 55 | end 56 | 57 | context 'when a replica set name is provided' do 58 | 59 | let(:servers) do 60 | described_class.servers([ mongos, standalone, replica_set, replica_set_two ], 'testing') 61 | end 62 | 63 | it 'returns only replica set members is the provided set' do 64 | expect(servers).to eq([ replica_set ]) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/mongo/operation/aggregate/result_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Aggregate::Result do 4 | 5 | let(:result) do 6 | described_class.new(reply) 7 | end 8 | 9 | let(:cursor_id) { 0 } 10 | let(:documents) { [] } 11 | let(:flags) { [] } 12 | let(:starting_from) { 0 } 13 | 14 | let(:reply) do 15 | Mongo::Protocol::Reply.new.tap do |reply| 16 | reply.instance_variable_set(:@flags, flags) 17 | reply.instance_variable_set(:@cursor_id, cursor_id) 18 | reply.instance_variable_set(:@starting_from, starting_from) 19 | reply.instance_variable_set(:@number_returned, documents.size) 20 | reply.instance_variable_set(:@documents, documents) 21 | end 22 | end 23 | 24 | let(:aggregate) do 25 | [ 26 | { '_id' => 'New York', 'totalpop' => 40270 }, 27 | { '_id' => 'Berlin', 'totalpop' => 103056 } 28 | ] 29 | end 30 | 31 | describe '#cursor_id' do 32 | 33 | context 'when the result is not using a cursor' do 34 | 35 | let(:documents) do 36 | [{ 'result' => aggregate, 'ok' => 1.0 }] 37 | end 38 | 39 | it 'returns zero' do 40 | expect(result.cursor_id).to eq(0) 41 | end 42 | end 43 | 44 | context 'when the result is using a cursor' do 45 | 46 | let(:documents) do 47 | [{ 'cursor' => { 'id' => 15, 'ns' => 'test', 'firstBatch' => aggregate }, 'ok' => 1.0 }] 48 | end 49 | 50 | it 'returns the cursor id' do 51 | expect(result.cursor_id).to eq(15) 52 | end 53 | end 54 | end 55 | 56 | describe '#documents' do 57 | 58 | context 'when the result is not using a cursor' do 59 | 60 | let(:documents) do 61 | [{ 'result' => aggregate, 'ok' => 1.0 }] 62 | end 63 | 64 | it 'returns the documents' do 65 | expect(result.documents).to eq(aggregate) 66 | end 67 | end 68 | 69 | context 'when the result is using a cursor' do 70 | 71 | let(:documents) do 72 | [{ 'cursor' => { 'id' => 15, 'ns' => 'test', 'firstBatch' => aggregate }, 'ok' => 1.0 }] 73 | end 74 | 75 | it 'returns the documents' do 76 | expect(result.documents).to eq(aggregate) 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/mongo/operation/read/query_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Read::Query do 4 | include_context 'operation' 5 | 6 | let(:selector) { {} } 7 | let(:query_options) { {} } 8 | let(:spec) do 9 | { :selector => selector, 10 | :options => query_options, 11 | :db_name => db_name, 12 | :coll_name => coll_name 13 | } 14 | end 15 | let(:op) { described_class.new(spec) } 16 | 17 | describe '#initialize' do 18 | 19 | context 'query spec' do 20 | it 'sets the query spec' do 21 | expect(op.spec).to be(spec) 22 | end 23 | end 24 | end 25 | 26 | describe '#==' do 27 | 28 | context 'when two ops have different specs' do 29 | let(:other_spec) do 30 | { :selector => { :a => 1 }, 31 | :options => query_options, 32 | :db_name => db_name, 33 | :coll_name => coll_name 34 | } 35 | end 36 | let(:other) { described_class.new(other_spec) } 37 | 38 | it 'returns false' do 39 | expect(op).not_to eq(other) 40 | end 41 | end 42 | end 43 | 44 | context '#merge' do 45 | let(:other_op) { described_class.new(spec) } 46 | 47 | it 'is not allowed' do 48 | expect{ op.merge(other_op) }.to raise_exception 49 | end 50 | end 51 | 52 | context '#merge!' do 53 | let(:other_op) { described_class.new(spec) } 54 | 55 | it 'is not allowed' do 56 | expect{ op.merge!(other_op) }.to raise_exception 57 | end 58 | end 59 | 60 | describe '#execute' do 61 | 62 | context 'message' do 63 | 64 | it 'creates a query wire protocol message with correct specs' do 65 | expect(Mongo::Protocol::Query).to receive(:new) do |db, coll, sel, options| 66 | expect(db).to eq(db_name) 67 | expect(coll).to eq(coll_name) 68 | expect(sel).to eq(selector) 69 | expect(options).to eq(query_options) 70 | end 71 | op.execute(primary_context) 72 | end 73 | end 74 | 75 | context 'connection' do 76 | 77 | it 'dispatches the message on the connection' do 78 | expect(connection).to receive(:dispatch) 79 | op.execute(primary_context) 80 | end 81 | end 82 | end 83 | end 84 | 85 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/command/delete.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | module Operation 18 | 19 | module Write 20 | 21 | module Command 22 | 23 | # A MongoDB delete write command operation. 24 | # Supported in server versions >= 2.5.5 25 | # 26 | # @example Initialize a delete write command. 27 | # include Mongo 28 | # include Operation 29 | # Write::Command::Delete.new({ :deletes => [{ :q => { :foo => 1 }, 30 | # :limit => 1 }], 31 | # :db_name => 'test', 32 | # :coll_name => 'test_coll', 33 | # :write_concern => write_concern, 34 | # :ordered => true 35 | # }) 36 | # 37 | # @since 2.0.0 38 | class Delete 39 | include Executable 40 | include Writable 41 | 42 | private 43 | 44 | # The query selector for this delete command operation. 45 | # 46 | # @return [ Hash ] The selector describing this delete operation. 47 | # 48 | # @since 2.0.0 49 | def selector 50 | { :delete => coll_name, 51 | :deletes => @spec[:deletes], 52 | :write_concern => write_concern.options, 53 | :ordered => ordered? 54 | } 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | 62 | -------------------------------------------------------------------------------- /lib/mongo/server/address/unix.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Server 17 | class Address 18 | 19 | # Sets up socket addresses. 20 | # 21 | # @since 2.0.0 22 | class Unix 23 | include Resolvable 24 | 25 | # The regular expression to use to match a socket path. 26 | # 27 | # @since 2.0.0 28 | MATCH = Regexp.new('\.sock').freeze 29 | 30 | # Initialize the socket resolver. 31 | # 32 | # @example Initialize the resolver. 33 | # Sock.new("/path/to/socket.sock") 34 | # 35 | # @param [ String ] address The socket path. 36 | # 37 | # @since 2.0.0 38 | def initialize(address) 39 | @host = address 40 | @seed = address 41 | end 42 | 43 | # Get a socket for the provided address type, given the options. 44 | # 45 | # @example Get a Unix socket. 46 | # ipv4.socket(5) 47 | # 48 | # @param [ Float ] timeout The socket timeout. 49 | # @param [ Hash ] ssl_options SSL options - ignored. 50 | # 51 | # @return [ Pool::Socket::Unix ] The socket. 52 | # 53 | # @since 2.0.0 54 | def socket(timeout, ssl_options = {}) 55 | Socket::Unix.new(host, timeout, Socket::AF_UNIX) 56 | end 57 | 58 | # Get the address as a string. 59 | # 60 | # @example Get the address as a string. 61 | # ipv4.to_s 62 | # 63 | # @return [ String ] The nice string. 64 | # 65 | # @since 2.0.0 66 | alias :to_s :host 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/mongo/pool/socket/ssl/context.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Pool 17 | module Socket 18 | class SSL 19 | 20 | # Factory module for creating SSL context objects. 21 | # 22 | # @since 2.0.0 23 | module Context 24 | 25 | class << self 26 | 27 | # Create the new SSL context based off the provided options. 28 | # 29 | # @example Create the SSL context. 30 | # Context.create(ssl_cert: '/path/to/file') 31 | # 32 | # @param [ Hash ] options The SSL options. 33 | # 34 | # @return [ OpenSSL::SSL::SSLContext ] The created context. 35 | # 36 | # @since 2.0.0 37 | def create(options = {}) 38 | context = OpenSSL::SSL::SSLContext.new 39 | 40 | # Client SSL certificate. 41 | if options[:ssl_cert] 42 | context.cert = OpenSSL::X509::Certificate.new(File.open(options[:ssl_cert])) 43 | end 44 | 45 | # Client private key file (optional if included in cert). 46 | if options[:ssl_key] 47 | context.key = OpenSSL::PKey::RSA.new(File.open(options[:ssl_key])) 48 | end 49 | 50 | # Peer certificate validation. 51 | if options[:ssl_verify] || options[:ssl_ca_cert] 52 | context.ca_file = options[:ssl_ca_cert] 53 | context.verify_mode = OpenSSL::SSL::VERIFY_PEER 54 | end 55 | 56 | context 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/mongo/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Logger do 4 | 5 | let(:logger) do 6 | described_class.logger 7 | end 8 | 9 | describe '.debug' do 10 | 11 | it 'logs a debug message' do 12 | expect(logger).to receive(:debug).with("mongo.query | message | runtime: 10ms") 13 | described_class.debug('mongo.query', 'message', '10ms') 14 | end 15 | end 16 | 17 | describe '.error' do 18 | 19 | it 'logs a error message' do 20 | expect(logger).to receive(:error).with("mongo.query | message | runtime: 10ms") 21 | described_class.error('mongo.query', 'message', '10ms') 22 | end 23 | end 24 | 25 | describe '.fatal' do 26 | 27 | it 'logs a fatal message' do 28 | expect(logger).to receive(:fatal).with("mongo.query | message | runtime: 10ms") 29 | described_class.fatal('mongo.query', 'message', '10ms') 30 | end 31 | end 32 | 33 | describe '.info' do 34 | 35 | it 'logs a info message' do 36 | expect(logger).to receive(:info).with("mongo.query | message | runtime: 10ms") 37 | described_class.info('mongo.query', 'message', '10ms') 38 | end 39 | end 40 | 41 | describe '.logger' do 42 | 43 | context 'when no logger has been set' do 44 | 45 | it 'returns the default logger' do 46 | expect(logger.level).to eq(Logger::DEBUG) 47 | end 48 | end 49 | 50 | context 'when a logger has been set' do 51 | 52 | let(:info) do 53 | Logger.new($stdout).tap do |log| 54 | log.level = Logger::INFO 55 | end 56 | end 57 | 58 | let(:debug) do 59 | Logger.new($stdout).tap do |log| 60 | log.level = Logger::DEBUG 61 | end 62 | end 63 | 64 | before do 65 | described_class.logger = info 66 | end 67 | 68 | after do 69 | described_class.logger = debug 70 | end 71 | 72 | it 'returns the provided logger' do 73 | expect(logger.level).to eq(Logger::INFO) 74 | end 75 | end 76 | end 77 | 78 | describe '.warn' do 79 | 80 | it 'logs a warn message' do 81 | expect(logger).to receive(:warn).with("mongo.query | message | runtime: 10ms") 82 | described_class.warn('mongo.query', 'message', '10ms') 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/mongo/server/context.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Server 17 | 18 | # Represents a context in which messages are sent to the server on a 19 | # connection. 20 | # 21 | # @since 2.0.0 22 | class Context 23 | extend Forwardable 24 | 25 | # @return [ Mongo::Server ] server The server the context is for. 26 | attr_reader :server 27 | 28 | # Delegate state checks to the server. 29 | def_delegators :@server, 30 | :max_wire_version, 31 | :max_write_batch_size, 32 | :mongos?, 33 | :primary?, 34 | :secondary?, 35 | :standalone?, 36 | :write_command_enabled? 37 | 38 | # Instantiate a server context. 39 | # 40 | # @example Instantiate a server context. 41 | # Mongo::Server::Context.new(server) 42 | # 43 | # @param [ Mongo::Server ] server The server the context is for. 44 | # 45 | # @since 2.0.0 46 | def initialize(server) 47 | @server = server 48 | end 49 | 50 | # Execute a block of code with a connection, that is checked out of the 51 | # pool and then checked back in. 52 | # 53 | # @example Send a message with the connection. 54 | # context.with_connection do |connection| 55 | # connection.dispatch([ command ]) 56 | # end 57 | # 58 | # @return [ Object ] The result of the block execution. 59 | # 60 | # @since 2.0.0 61 | def with_connection(&block) 62 | server.pool.with_connection(&block) 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/mongo/operation/command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Operation::Command do 4 | 5 | let(:selector) { { :ismaster => 1 } } 6 | let(:options) { { :limit => -1 } } 7 | let(:spec) do 8 | { :selector => selector, 9 | :options => options, 10 | :db_name => TEST_DB 11 | } 12 | end 13 | let(:op) { described_class.new(spec) } 14 | 15 | describe '#initialize' do 16 | 17 | it 'sets the spec' do 18 | expect(op.spec).to be(spec) 19 | end 20 | end 21 | 22 | describe '#==' do 23 | 24 | context 'when the ops have different specs' do 25 | 26 | let(:other_selector) { { :ping => 1 } } 27 | let(:other_spec) do 28 | { :selector => other_selector, 29 | :options => {}, 30 | :db_name => 'test', 31 | } 32 | end 33 | let(:other) { described_class.new(other_spec) } 34 | 35 | it 'returns false' do 36 | expect(op).not_to eq(other) 37 | end 38 | end 39 | end 40 | 41 | context '#merge' do 42 | 43 | let(:other_op) { described_class.new(spec) } 44 | 45 | it 'raises an exception' do 46 | expect{ op.merge(other_op) }.to raise_exception 47 | end 48 | end 49 | 50 | context '#merge!' do 51 | 52 | let(:other_op) { described_class.new(spec) } 53 | 54 | it 'raises an exception' do 55 | expect{ op.merge!(other_op) }.to raise_exception 56 | end 57 | end 58 | 59 | describe '#execute' do 60 | 61 | context 'when the command succeeds' do 62 | 63 | let(:response) do 64 | op.execute(authorized_primary.context) 65 | end 66 | 67 | it 'returns the reponse' do 68 | expect(response).to be_successful 69 | end 70 | end 71 | 72 | context 'when the command fails' do 73 | 74 | let(:selector) do 75 | { notacommand: 1 } 76 | end 77 | 78 | it 'raises an exception' do 79 | expect { 80 | op.execute(authorized_primary.context) 81 | }.to raise_error(Mongo::Operation::Write::Failure) 82 | end 83 | end 84 | 85 | context 'when the command cannot run on a secondary' do 86 | 87 | context 'when the server is a secondary' do 88 | 89 | pending 'it re-routes to the primary' 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/mongo/protocol/bit_vector.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Protocol 17 | module Serializers 18 | # Class used to define a bitvector for a MongoDB wire protocol message. 19 | # 20 | # Defines serialization strategy upon initialization. 21 | # 22 | # @api private 23 | class BitVector 24 | 25 | # Initializes a BitVector with a layout 26 | # 27 | # @param layout [Array] the array of fields in the bit vector 28 | def initialize(layout) 29 | @masks = {} 30 | layout.each_with_index do |field, index| 31 | @masks[field] = 2**index 32 | end 33 | end 34 | 35 | # Serializes vector by encoding each symbol according to its mask 36 | # 37 | # @param buffer [IO] Buffer to receive the serialized vector 38 | # @param value [Array] Array of flags to encode 39 | # @return [IO] Buffer that received the serialized vector 40 | def serialize(buffer, value) 41 | bits = 0 42 | value.each { |flag| bits |= @masks[flag] } 43 | buffer << [bits].pack(INT32_PACK) 44 | end 45 | 46 | # Deserializes vector by decoding the symbol according to its mask 47 | # 48 | # @param io [IO] Stream containing the vector to be deserialized 49 | # @return [Array] Flags contained in the vector 50 | def deserialize(io) 51 | vector = io.read(4).unpack(INT32_PACK).first 52 | flags = [] 53 | @masks.each do |flag, mask| 54 | flags << flag if mask & vector != 0 55 | end 56 | flags 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/mongo/socket/tcp.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Socket 17 | 18 | # Wrapper for TCP sockets. 19 | # 20 | # @since 2.0.0 21 | class TCP < Socket 22 | 23 | # @return [ String ] host The host to connect to. 24 | attr_reader :host 25 | 26 | # @return [ Integer ] port The port to connect to. 27 | attr_reader :port 28 | 29 | # @return [ Float ] timeout The connection timeout. 30 | attr_reader :timeout 31 | 32 | # Establishes a socket connection. 33 | # 34 | # @example Connect the socket. 35 | # sock.connect! 36 | # 37 | # @note This method mutates the object by setting the socket 38 | # internally. 39 | # 40 | # @return [ TCP ] The connected socket instance. 41 | # 42 | # @since 2.0.0 43 | def connect! 44 | Timeout.timeout(timeout, Mongo::SocketTimeoutError) do 45 | socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) 46 | socket.connect(::Socket.pack_sockaddr_in(port, host)) 47 | self 48 | end 49 | end 50 | 51 | # Initializes a new TCP socket. 52 | # 53 | # @example Create the TCP socket. 54 | # TCP.new('::1', 27017, 30) 55 | # TCP.new('127.0.0.1', 27017, 30) 56 | # 57 | # @param [ String ] host The hostname or IP address. 58 | # @param [ Integer ] port The port number. 59 | # @param [ Float ] timeout The socket timeout value. 60 | # @param [ Integer ] family The socket family. 61 | # 62 | # @since 2.0.0 63 | def initialize(host, port, timeout, family) 64 | @host, @port, @timeout = host, port, timeout 65 | super(family) 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/mongo/server_preference.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'mongo/server_preference/selectable' 16 | require 'mongo/server_preference/nearest' 17 | require 'mongo/server_preference/primary' 18 | require 'mongo/server_preference/primary_preferred' 19 | require 'mongo/server_preference/secondary' 20 | require 'mongo/server_preference/secondary_preferred' 21 | 22 | module Mongo 23 | 24 | # Functionality for getting an object representing a specific server preference. 25 | # 26 | # @since 2.0.0 27 | module ServerPreference 28 | extend self 29 | 30 | # Hash lookup for the server preference classes based off the symbols 31 | # provided in configuration. 32 | # 33 | # @since 2.0.0 34 | PREFERENCES = { 35 | nearest: Nearest, 36 | primary: Primary, 37 | primary_preferred: PrimaryPreferred, 38 | secondary: Secondary, 39 | secondary_preferred: SecondaryPreferred 40 | }.freeze 41 | 42 | # Create a server preference object. 43 | # 44 | # @example Get a server preference object for selecting a secondary with 45 | # specific tag sets and acceptable latency. 46 | # Mongo::ServerPreference.get(:mode => :secondary, :tags => [{'tag' => 'set'}]) 47 | # 48 | # @param [ Hash ] options The read preference options. 49 | # 50 | # @option options :mode [ Symbol ] The read preference mode. 51 | # @option options :tags [ Array= 2.5.5 25 | # 26 | # @example 27 | # include Mongo 28 | # include Operation 29 | # Write::Command::Update.new({ :updates => [{ :q => { :foo => 1 }, 30 | # :u => { :$set => 31 | # { :bar => 1 }}, 32 | # :multi => true, 33 | # :upsert => false }], 34 | # :db_name => 'test', 35 | # :coll_name => 'test_coll', 36 | # :write_concern => write_concern, 37 | # :ordered => true 38 | # }) 39 | # 40 | # @since 2.0.0 41 | class Update 42 | include Executable 43 | include Writable 44 | 45 | private 46 | 47 | # The query selector for this update command operation. 48 | # 49 | # @return [ Hash ] The selector describing this update operation. 50 | def selector 51 | { :update => coll_name, 52 | :updates => @spec[:updates], 53 | :write_concern => write_concern.options, 54 | :ordered => ordered? 55 | } 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | 63 | -------------------------------------------------------------------------------- /lib/mongo/protocol/reply.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Protocol 17 | 18 | # The MongoDB wire protocol message representing a reply 19 | # 20 | # @example 21 | # socket = TCPSocket.new('localhost', 27017) 22 | # query = Protocol::Query.new('xgen', 'users', {:name => 'Tyler'}) 23 | # socket.write(query) 24 | # reply = Protocol::Reply::deserialize(socket) 25 | # 26 | # @api semipublic 27 | class Reply < Message 28 | 29 | private 30 | 31 | # The operation code required to specify a Reply message. 32 | # @return [Fixnum] the operation code. 33 | def op_code 34 | 1 35 | end 36 | 37 | # Available flags for a Reply message. 38 | FLAGS = [ 39 | :cursor_not_found, 40 | :query_failure, 41 | :shard_config_stale, 42 | :await_capable 43 | ] 44 | 45 | public 46 | 47 | # @!attribute 48 | # @return [Array] The flags for this reply. 49 | # 50 | # Supported flags: +:cursor_not_found+, +:query_failure+, 51 | # +:shard_config_stale+, +:await_capable+ 52 | field :flags, BitVector.new(FLAGS) 53 | 54 | # @!attribute 55 | # @return [Fixnum] The cursor id for this response. Will be zero 56 | # if there are no additional results. 57 | field :cursor_id, Int64 58 | 59 | # @!attribute 60 | # @return [Fixnum] The starting position of the cursor for this Reply. 61 | field :starting_from, Int32 62 | 63 | # @!attribute 64 | # @return [Fixnum] Number of documents in this Reply. 65 | field :number_returned, Int32 66 | 67 | # @!attribute 68 | # @return [Array] The documents in this Reply. 69 | field :documents, Document, :@number_returned 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/drop_index.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Write 18 | 19 | # A MongoDB drop index operation. 20 | # If a server with version >= 2.5.5 is being used, a write command operation 21 | # will be created and sent instead. 22 | # 23 | # @since 2.0.0 24 | class DropIndex 25 | include Executable 26 | 27 | # Initialize the drop index operation. 28 | # 29 | # @example 30 | # Write::DropIndex.new({ 31 | # :db_name => 'test', 32 | # :coll_name => 'test_coll', 33 | # :index_name => 'name_1_age_-1' 34 | # }) 35 | # 36 | # @param [ Hash ] spec The specifications for the drop. 37 | # 38 | # @option spec :index [ Hash ] The index spec to create. 39 | # @option spec :db_name [ String ] The name of the database. 40 | # @option spec :coll_name [ String ] The name of the collection. 41 | # @option spec :index_name [ String ] The name of the index. 42 | # 43 | # @since 2.0.0 44 | def initialize(spec) 45 | @spec = spec 46 | end 47 | 48 | # Execute the operation. 49 | # If the server has version < 2.5.5, an insert operation is sent. 50 | # If the server version is >= 2.5.5, an insert write command operation is created 51 | # and sent instead. 52 | # 53 | # @params [ Mongo::Server::Context ] The context for this operation. 54 | # 55 | # @return [ Result ] The result of the operation. 56 | # 57 | # @since 2.0.0 58 | def execute(context) 59 | Result.new(Command::DropIndex.new(spec).execute(context)).validate! 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/mongo/server/description/inspection/server_removed_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Server::Description::Inspection::ServerRemoved do 4 | 5 | let(:server) do 6 | Mongo::Server.new('127.0.0.1:27017') 7 | end 8 | 9 | describe '.run' do 10 | 11 | let(:config) do 12 | { 13 | 'ismaster' => true, 14 | 'secondary' => false, 15 | 'hosts' => [ '127.0.0.1:27018', '127.0.0.1:27019' ], 16 | 'setName' => 'test' 17 | } 18 | end 19 | 20 | let(:description) do 21 | Mongo::Server::Description.new(server, config) 22 | end 23 | 24 | let(:updated) do 25 | Mongo::Server::Description.new(server, new_config) 26 | end 27 | 28 | let(:listener) do 29 | double('listener') 30 | end 31 | 32 | before do 33 | server.add_listener(Mongo::Event::SERVER_REMOVED, listener) 34 | end 35 | 36 | context 'when no server is removed' do 37 | 38 | let(:new_config) do 39 | { 40 | 'hosts' => [ '127.0.0.1:27018', '127.0.0.1:27019' ], 41 | 'ismaster' => true, 42 | 'setName' => 'test' 43 | } 44 | end 45 | 46 | it 'does not fire a server removed event' do 47 | expect(listener).to_not receive(:handle) 48 | described_class.run(description, updated) 49 | end 50 | end 51 | 52 | context 'when a server is removed' do 53 | 54 | context 'when the server is a primary' do 55 | 56 | let(:new_config) do 57 | { 58 | 'hosts' => [ '127.0.0.1:27019', '127.0.0.1:27020' ], 59 | 'ismaster' => true, 60 | 'setName' => 'test' 61 | } 62 | end 63 | 64 | it 'fires a server removed event' do 65 | expect(listener).to receive(:handle).with('127.0.0.1:27018') 66 | described_class.run(description, updated) 67 | end 68 | end 69 | 70 | context 'when the server is not a primary' do 71 | 72 | let(:new_config) do 73 | { 74 | 'hosts' => [ '127.0.0.1:27019', '127.0.0.1:27020' ], 75 | 'secondary' => true, 76 | 'setName' => 'test' 77 | } 78 | end 79 | 80 | it 'does not fire a server removed event' do 81 | expect(listener).to_not receive(:handle) 82 | described_class.run(description, updated) 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/mongo/auth/cr.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 - 2014 MongoDB Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Auth 17 | 18 | # Defines behaviour for MongoDB-CR authentication. 19 | # 20 | # @since 2.0.0 21 | class CR 22 | include Executable 23 | 24 | # Log the user in on the given connection. 25 | # 26 | # @example Log the user in. 27 | # user.login(connection) 28 | # 29 | # @param [ Mongo::Connection ] connection The connection to log into. 30 | # 31 | # @return [ Protocol::Reply ] The authentication response. 32 | # 33 | # @since 2.0.0 34 | def login(connection) 35 | nonce = connection.dispatch([ nonce_message(connection) ]).documents[0] 36 | reply = connection.dispatch([ login_message(connection, nonce[Auth::NONCE]) ]) 37 | raise Unauthorized.new(user) if reply.documents[0]['ok'] == 0 38 | reply 39 | end 40 | 41 | private 42 | 43 | # On 2.6 and higher, nonce messages must always go to the admin database, 44 | # where on 2.4 and lower they go to the database the user is authorized 45 | # for. 46 | def nonce_message(connection) 47 | Protocol::Query.new( 48 | auth_database(connection), 49 | Database::COMMAND, 50 | Auth::GET_NONCE, 51 | limit: -1 52 | ) 53 | end 54 | 55 | # On 2.6 and higher, login messages must always go to the admin database, 56 | # where on 2.4 and lower they go to the database the user is authorized 57 | # for. 58 | def login_message(connection, nonce) 59 | Protocol::Query.new( 60 | auth_database(connection), 61 | Database::COMMAND, 62 | { authenticate: 1, user: user.name, nonce: nonce, key: user.auth_key(nonce) }, 63 | limit: -1 64 | ) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/remove_user.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Write 18 | 19 | # A MongoDB remove user operation. 20 | # 21 | # @since 2.0.0 22 | class RemoveUser 23 | include Executable 24 | 25 | # Initialize the remove user operation. 26 | # 27 | # @example Initialize the operation. 28 | # Write::RemoveUser.new(:db_name => 'test', :name => name) 29 | # 30 | # @param [ Hash ] spec The specifications for the remove. 31 | # 32 | # @option spec :name [ String ] The user name. 33 | # @option spec :db_name [ String ] The name of the database. 34 | # 35 | # @since 2.0.0 36 | def initialize(spec) 37 | @spec = spec 38 | end 39 | 40 | # Execute the operation. 41 | # If the server has version < 2.5.5, an insert operation is sent. 42 | # If the server version is >= 2.5.5, an insert write command operation is created 43 | # and sent instead. 44 | # 45 | # @params [ Mongo::Server::Context ] The context for this operation. 46 | # 47 | # @return [ Result ] The operation result. 48 | # 49 | # @since 2.0.0 50 | def execute(context) 51 | Result.new( 52 | if context.write_command_enabled? 53 | Command::RemoveUser.new(spec).execute(context) 54 | else 55 | context.with_connection do |connection| 56 | connection.dispatch([ message, gle ].compact) 57 | end 58 | end 59 | ).validate! 60 | end 61 | 62 | private 63 | 64 | def message 65 | Protocol::Delete.new(db_name, Auth::User::COLLECTION, { user: @spec[:name] }) 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/mongo/server_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Server do 4 | 5 | describe '#==' do 6 | 7 | let(:server) do 8 | described_class.new('127.0.0.1:27017') 9 | end 10 | 11 | context 'when the other is not a server' do 12 | 13 | let(:other) do 14 | false 15 | end 16 | 17 | it 'returns false' do 18 | expect(server).to_not eq(other) 19 | end 20 | end 21 | 22 | context 'when the other is a server' do 23 | 24 | context 'when the addresses match' do 25 | 26 | let(:other) do 27 | described_class.new('127.0.0.1:27017') 28 | end 29 | 30 | it 'returns true' do 31 | expect(server).to eq(other) 32 | end 33 | end 34 | 35 | context 'when the addresses dont match', simulator: 'cluster' do 36 | 37 | let(:other) do 38 | described_class.new('127.0.0.1:27018') 39 | end 40 | 41 | it 'returns false' do 42 | expect(server).to_not eq(other) 43 | end 44 | end 45 | end 46 | end 47 | 48 | describe '#context' do 49 | 50 | let(:server) do 51 | described_class.new('127.0.0.1:27017') 52 | end 53 | 54 | let(:context) do 55 | server.context 56 | end 57 | 58 | it 'returns a new server context' do 59 | expect(context.server).to eq(server) 60 | end 61 | end 62 | 63 | describe '#initialize' do 64 | 65 | let(:address) do 66 | '127.0.0.1:27017' 67 | end 68 | 69 | let(:server) do 70 | described_class.new(address, :heartbeat_frequency => 5) 71 | end 72 | 73 | it 'sets the address host' do 74 | expect(server.address.host).to eq('127.0.0.1') 75 | end 76 | 77 | it 'sets the address port' do 78 | expect(server.address.port).to eq(27017) 79 | end 80 | 81 | it 'sets the address ip' do 82 | expect(server.address.ip).to eq('127.0.0.1') 83 | end 84 | 85 | it 'sets the options' do 86 | expect(server.options).to eq(:heartbeat_frequency => 5) 87 | end 88 | end 89 | 90 | describe '#pool' do 91 | 92 | let(:server) do 93 | described_class.new('127.0.0.1:27017') 94 | end 95 | 96 | let(:pool) do 97 | server.pool 98 | end 99 | 100 | it 'returns the connection pool for the server' do 101 | expect(pool.pool_size).to eq(5) 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/mongo/operation/slicable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | module Operation 18 | 19 | # This module contains common functionality for splitting an operation 20 | # into the specified number of children operations. 21 | # An operation including this module must provide a method called 22 | # #slicable_key. It specifies the key of the spec array element to split. 23 | # 24 | # @since 2.0.0 25 | module Slicable 26 | 27 | # Slices this operation into the specified number of children operations. 28 | # 29 | # @params [ Integer ] n_slices The number of children operations to split 30 | # this one into. 31 | # 32 | # @return [ Array ] An array of children operations. 33 | # 34 | # @since 2.0.0 35 | def slice(n_slices) 36 | items = spec[slicable_key] 37 | group_size = items.size / n_slices 38 | divisions = items.each_slice(group_size).to_a 39 | 40 | # #each_slice makes groups containing exactly group_size number of items. 41 | # You could therefore end up with more groups than n_slices, so put the 42 | # remaining items in the last group. 43 | if divisions.size > n_slices 44 | divisions[n_slices - 1] << divisions.pop(divisions.size - n_slices) 45 | divisions[-1].flatten! 46 | end 47 | 48 | divisions.inject([]) do |children, division| 49 | spec_copy = spec.dup 50 | spec_copy[slicable_key] = division 51 | children << self.class.new(spec_copy) 52 | end 53 | end 54 | 55 | # Set a field :ord in the spec that keeps track of a higher-level ordering. 56 | # 57 | # @param [ Integer ] order The higher-level ordering of this op. 58 | # 59 | # @since 2.0.0 60 | def set_order(order) 61 | spec[slicable_key].each { |doc| doc[:ord] = order } 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/mongo/event/publisher_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Event::Publisher do 4 | 5 | let(:klass) do 6 | Class.new do 7 | include Mongo::Event::Publisher 8 | end 9 | end 10 | 11 | describe '#add_listener' do 12 | 13 | let(:publisher) do 14 | klass.new 15 | end 16 | 17 | let(:listener) do 18 | double('listener') 19 | end 20 | 21 | before do 22 | publisher.add_listener('test', listener) 23 | end 24 | 25 | it 'adds the listener for the event' do 26 | expect(publisher.listeners).to eq('test' => [ listener ]) 27 | end 28 | end 29 | 30 | describe '#publish' do 31 | 32 | let(:publisher) do 33 | klass.new 34 | end 35 | 36 | let(:listener) do 37 | double('listener') 38 | end 39 | 40 | context 'when the event has listeners' do 41 | 42 | before do 43 | publisher.add_listener('test', listener) 44 | publisher.add_listener('test', listener) 45 | end 46 | 47 | it 'handles the event for each listener' do 48 | expect(listener).to receive(:handle).with('test').twice 49 | publisher.publish('test', 'test') 50 | end 51 | end 52 | 53 | context 'when the event has no listeners' do 54 | 55 | it 'does not handle anything' do 56 | expect(listener).to receive(:handle).never 57 | publisher.publish('test', 'test') 58 | end 59 | end 60 | end 61 | 62 | describe '#listeners' do 63 | 64 | let(:publisher) do 65 | klass.new 66 | end 67 | 68 | let(:listener) do 69 | double('listener') 70 | end 71 | 72 | before do 73 | publisher.add_listener('test', listener) 74 | publisher.add_listener('other', listener) 75 | end 76 | 77 | it 'returns all the event listeners for the publisher' do 78 | expect(publisher.listeners).to eq({ 79 | 'test' => [ listener ], 80 | 'other' => [ listener ] 81 | }) 82 | end 83 | end 84 | 85 | describe '#listeners_for' do 86 | 87 | let(:publisher) do 88 | klass.new 89 | end 90 | 91 | let(:listener) do 92 | double('listener') 93 | end 94 | 95 | before do 96 | publisher.add_listener('test', listener) 97 | publisher.add_listener('other', listener) 98 | end 99 | 100 | it 'returns all the listeners for the specific event' do 101 | expect(publisher.listeners_for('test')).to eq([ listener ]) 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/create_user.rb: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2009-2014 MongoDB, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | module Mongo 17 | module Operation 18 | module Write 19 | 20 | # A MongoDB create user operation. 21 | # 22 | # @since 2.0.0 23 | class CreateUser 24 | include Executable 25 | 26 | # Initialize the create user operation. 27 | # 28 | # @example Initialize the operation. 29 | # Write::CreateUser.new(:db_name => 'test', :user => user) 30 | # 31 | # @param [ Hash ] spec The specifications for the create. 32 | # 33 | # @option spec :user [ Auth::User ] The user to create. 34 | # @option spec :db_name [ String ] The name of the database. 35 | # 36 | # @since 2.0.0 37 | def initialize(spec) 38 | @spec = spec 39 | end 40 | 41 | # Execute the operation. Creating users behaves different on 2.7+, 42 | # 2.6.x, and 2.4- so we need to break this out into separate 43 | # operations. 44 | # 45 | # @example Execute the operation. 46 | # operation.execute(context) 47 | # 48 | # @param [ Mongo::Server::Context ] The context for this operation. 49 | # 50 | # @return [ Result ] The operation result. 51 | # 52 | # @since 2.0.0 53 | def execute(context) 54 | Result.new( 55 | if context.write_command_enabled? 56 | Command::CreateUser.new(spec).execute(context) 57 | else 58 | context.with_connection do |connection| 59 | connection.dispatch([ message, gle ].compact) 60 | end 61 | end 62 | ).validate! 63 | end 64 | 65 | private 66 | 67 | def message 68 | user_spec = { user: user.name }.merge(user.spec) 69 | Protocol::Insert.new(db_name, Auth::User::COLLECTION, [ user_spec ]) 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/mongo/event/publisher.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Event 17 | 18 | # This module is included for objects that need to publish events. 19 | # 20 | # @since 2.0.0 21 | module Publisher 22 | 23 | # Add an event listener for the provided event. 24 | # 25 | # @example Add an event listener 26 | # publisher.add_listener("my_event", listener) 27 | # 28 | # @param [ String ] event The event to listen for. 29 | # @param [ Object ] listener The event listener. 30 | # 31 | # @return [ Array ] The listeners for the event. 32 | # 33 | # @since 2.0.0 34 | def add_listener(event, listener) 35 | listeners_for(event).push(listener) 36 | end 37 | 38 | # Publish the provided event. 39 | # 40 | # @example Publish an event. 41 | # publisher.publish("my_event", "payload") 42 | # 43 | # @param [ String ] event The event to publish. 44 | # @param [ Array ] args The objects to pass to the listeners. 45 | # 46 | # @since 2.0.0 47 | def publish(event, *args) 48 | listeners_for(event).each { |listener| listener.handle(*args) } 49 | end 50 | 51 | # Get all the listeners for the publisher. 52 | # 53 | # @example Get all the listeners. 54 | # publisher.listeners 55 | # 56 | # @return [ Hash ] The listeners. 57 | # 58 | # @since 2.0.0 59 | def listeners 60 | @listeners ||= {} 61 | end 62 | 63 | # Get the listeners for a specific event. 64 | # 65 | # @example Get the listeners. 66 | # publisher.listeners_for("test") 67 | # 68 | # @param [ String ] event The event name. 69 | # 70 | # @return [ Array ] The listeners. 71 | # 72 | # @since 2.0.0 73 | def listeners_for(event) 74 | listeners[event] ||= [] 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to the MongoDB Ruby Driver 2 | 3 | Thank you for your interest in contributing to the MongoDB Ruby driver. 4 | 5 | We are building this software together and strongly encourage contributions 6 | from the community that are within the guidelines set forth below. 7 | 8 | Bug Fixes and New Features 9 | -------------------------- 10 | 11 | Before starting to write code, look for existing [tickets] 12 | (https://jira.mongodb.org/browse/RUBY) or [create one] 13 | (https://jira.mongodb.org/secure/CreateIssue!default.jspa) 14 | for your bug, issue, or feature request. This helps the community 15 | avoid working on something that might not be of interest or which 16 | has already been addressed. 17 | 18 | Environment 19 | ----------- 20 | 21 | We highly suggest using [RVM](https://rvm.io/) or [rbenv] 22 | (https://github.com/sstephenson/rbenv) to set up Ruby development and 23 | testing environments. In this way, moving between and testing code for 24 | alternate Ruby versions (besides the one possibly included with your 25 | system) is simple. This practice is essential for ensuring the quality 26 | of the driver. 27 | 28 | Pull Requests 29 | ------------- 30 | 31 | Pull requests should be made against the master (development) 32 | branch and include relevant tests, if applicable. The driver follows 33 | the Git-Flow branching model where the traditional master branch is 34 | known as release and the master (default) branch is considered under 35 | development. 36 | 37 | Tests should pass under all Ruby interpreters which the MongoDB Ruby 38 | driver currently supports (1.8.7, 1.9.3, JRuby 1.6.x and 1.7.x) and will be 39 | automatically tested. 40 | 41 | The results of pull request testing will be appended to the request. 42 | If any tests do not pass, or relavant tests are not included the pull 43 | request will not be considered. 44 | 45 | Clusters and Replica Sets 46 | ------------------------- 47 | 48 | If your bug fix or enhancement deals with Cluster or Replica Set 49 | code, please run all relevant tests for those code subsets before 50 | issuing the request. 51 | 52 | * `rake test:sharded_cluster` for sharded clusters 53 | * `rake test:replica_set` for replica sets 54 | 55 | Cluster and Replica Set testing is currently **not** automatically 56 | performed so it is important they are run in a thorough fashion under 57 | all supported interpreters before a pull request is made. 58 | 59 | Talk To Us 60 | ---------- 61 | 62 | We love to hear from you. If you want to work on something or have 63 | questions / complaints please reach out to us by creating a [question] 64 | (https://jira.mongodb.org/secure/CreateIssue.jspa?pid=10005&issuetype=6). 65 | -------------------------------------------------------------------------------- /spec/mongo/protocol/kill_cursors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::Protocol::KillCursors do 4 | 5 | let(:opcode) { 2007 } 6 | let(:cursor_ids) { [123, 456, 789] } 7 | let(:id_count) { cursor_ids.size } 8 | let(:message) do 9 | described_class.new(cursor_ids) 10 | end 11 | 12 | describe '#initialize' do 13 | 14 | it 'sets the cursor ids' do 15 | expect(message.cursor_ids).to eq(cursor_ids) 16 | end 17 | 18 | it 'sets the count' do 19 | expect(message.id_count).to eq(id_count) 20 | end 21 | end 22 | 23 | describe '#==' do 24 | 25 | context 'when the other is a killcursors' do 26 | 27 | context 'when the cursor ids are equal' do 28 | let(:other) do 29 | described_class.new(cursor_ids) 30 | end 31 | 32 | it 'returns true' do 33 | expect(message).to eq(other) 34 | end 35 | end 36 | 37 | context 'when the cursor ids are not equal' do 38 | let(:other) do 39 | described_class.new([123, 456]) 40 | end 41 | 42 | it 'returns false' do 43 | expect(message).not_to eq(other) 44 | end 45 | end 46 | end 47 | 48 | context 'when the other is not a killcursors' do 49 | let(:other) do 50 | expect(message).not_to eq('test') 51 | end 52 | end 53 | end 54 | 55 | describe '#hash' do 56 | let(:values) do 57 | message.send(:fields).map do |field| 58 | message.instance_variable_get(field[:name]) 59 | end 60 | end 61 | 62 | it 'returns a hash of the field values' do 63 | expect(message.hash).to eq(values.hash) 64 | end 65 | end 66 | 67 | describe '#replyable?' do 68 | 69 | it 'returns false' do 70 | expect(message).to_not be_replyable 71 | end 72 | end 73 | 74 | describe '#serialize' do 75 | let(:bytes) { message.serialize } 76 | 77 | include_examples 'message with a header' 78 | 79 | describe 'zero' do 80 | let(:field) { bytes[16..19] } 81 | 82 | it 'serializes a zero' do 83 | expect(field).to be_int32(0) 84 | end 85 | end 86 | 87 | describe 'number of cursors' do 88 | let(:field) { bytes[20..23] } 89 | it 'serializes the cursor count' do 90 | expect(field).to be_int32(id_count) 91 | end 92 | end 93 | 94 | describe 'cursor ids' do 95 | let(:field) { bytes[24..-1] } 96 | it 'serializes the selector' do 97 | expect(field).to be_int64_sequence(cursor_ids) 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /spec/mongo/server_preference_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mongo::ServerPreference do 4 | 5 | describe '.get' do 6 | let(:server_pref) { described_class.get(:mode => name) } 7 | let(:name) { :secondary } 8 | let(:tag_sets) { [{ 'test' => 'tag' }] } 9 | 10 | context 'name' do 11 | 12 | context 'primary' do 13 | let(:name) { :primary } 14 | 15 | it 'returns a server preference of class Primary' do 16 | expect(server_pref).to be_a(Mongo::ServerPreference::Primary) 17 | end 18 | end 19 | 20 | context 'primary_preferred' do 21 | let(:name) { :primary_preferred } 22 | 23 | it 'returns a server preference of class PrimaryPreferred' do 24 | expect(server_pref).to be_a(Mongo::ServerPreference::PrimaryPreferred) 25 | end 26 | end 27 | 28 | context 'secondary' do 29 | let(:name) { :secondary } 30 | 31 | it 'returns a server preference of class Secondary' do 32 | expect(server_pref).to be_a(Mongo::ServerPreference::Secondary) 33 | end 34 | end 35 | 36 | context 'secondary_preferred' do 37 | let(:name) { :secondary_preferred } 38 | 39 | it 'returns a server preference of class SecondaryPreferred' do 40 | expect(server_pref).to be_a(Mongo::ServerPreference::SecondaryPreferred) 41 | end 42 | end 43 | 44 | context 'nearest' do 45 | let(:name) { :nearest } 46 | 47 | it 'returns a server preference of class Nearest' do 48 | expect(server_pref).to be_a(Mongo::ServerPreference::Nearest) 49 | end 50 | end 51 | end 52 | 53 | context 'name not provided' do 54 | let(:server_pref) { described_class.get } 55 | 56 | it 'returns a server preference of class Primary' do 57 | expect(server_pref).to be_a(Mongo::ServerPreference::Primary) 58 | end 59 | end 60 | 61 | context 'tag sets provided' do 62 | let(:server_pref) { described_class.get(:mode => name, :tags => tag_sets) } 63 | 64 | it 'sets tag sets on the server preference object' do 65 | expect(server_pref.tag_sets).to eq(tag_sets) 66 | end 67 | 68 | end 69 | 70 | context 'acceptable latency provided' do 71 | 72 | let(:acceptable_latency) { 100 } 73 | 74 | let(:server_pref) do 75 | described_class.get( 76 | :mode => name, 77 | :tags => tag_sets, 78 | :acceptable_latency => acceptable_latency 79 | ) 80 | end 81 | 82 | it 'sets acceptable latency on the server preference object' do 83 | expect(server_pref.acceptable_latency).to eq(acceptable_latency) 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/mongo/server_preference/primary.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | module ServerPreference 18 | 19 | # Encapsulates specifications for selecting the primary server given a list 20 | # of candidates. 21 | # 22 | # @since 2.0.0 23 | class Primary 24 | include Selectable 25 | 26 | # Get the name of the server mode type. 27 | # 28 | # @example Get the name of the server mode for this preference. 29 | # preference.name 30 | # 31 | # @return [ Symbol ] :primary 32 | # 33 | # @since 2.0.0 34 | def name 35 | :primary 36 | end 37 | 38 | # Whether the slaveOk bit should be set on wire protocol messages. 39 | # I.e. whether the operation can be performed on a secondary server. 40 | # 41 | # @return [ false ] false 42 | # 43 | # @since 2.0.0 44 | def slave_ok? 45 | false 46 | end 47 | 48 | # Whether tag sets are allowed to be defined for this server preference. 49 | # 50 | # @return [ false ] false 51 | # 52 | # @since 2.0.0 53 | def tags_allowed? 54 | false 55 | end 56 | 57 | # Convert this server preference definition into a format appropriate 58 | # for a mongos server. 59 | # 60 | # @example Convert this server preference definition into a format 61 | # for mongos. 62 | # preference = Mongo::ServerPreference::Primary.new 63 | # preference.to_mongos 64 | # 65 | # @return [ nil ] nil 66 | # 67 | # @since 2.0.0 68 | def to_mongos 69 | nil 70 | end 71 | 72 | # Select the primary server from a list of candidates. 73 | # 74 | # @example Select the primary server given a list of candidates. 75 | # preference = Mongo::ServerPreference::Primary.new 76 | # preference.select_servers([candidate_1, candidate_2]) 77 | # 78 | # @return [ Array ] The primary server from the list of candidates. 79 | # 80 | # @since 2.0.0 81 | def select_servers(candidates) 82 | primary(candidates) 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/mongo/protocol/delete.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Protocol 17 | 18 | # MongoDB Wire protocol Delete message. 19 | # 20 | # This is a client request message that is sent to the server in order 21 | # to delete selected documents in the specified namespace. 22 | # 23 | # The operation, by default, operates on many documents. Setting 24 | # the +:single_remove+ flag allows for a single matching document 25 | # to be removed. 26 | # 27 | # @api semipublic 28 | class Delete < Message 29 | 30 | # Creates a new Delete message 31 | # 32 | # @example Remove all users named Tyler. 33 | # Query.new('xgen', 'users', {:name => 'Tyler'}) 34 | # 35 | # @param database [String, Symbol] The database to remove from. 36 | # @param collection [String, Symbol] The collection to remove from. 37 | # @param selector [Hash] The query used to select doc(s) to remove. 38 | # @param options [Hash] The additional delete options. 39 | # 40 | # @option options :flags [Array] The flags for the delete message. 41 | # 42 | # Supported flags: +:single_remove+ 43 | def initialize(database, collection, selector, options = {}) 44 | @namespace = "#{database}.#{collection}" 45 | @selector = selector 46 | @flags = options[:flags] || [] 47 | end 48 | 49 | private 50 | 51 | # The operation code required to specify a Delete message. 52 | # @return [Fixnum] the operation code. 53 | def op_code 54 | 2006 55 | end 56 | 57 | # Available flags for a Delete message. 58 | FLAGS = [:single_remove] 59 | 60 | # Field representing Zero encoded as an Int32. 61 | field :zero, Zero 62 | 63 | # @!attribute 64 | # @return [String] The namespace for this Delete message. 65 | field :namespace, CString 66 | 67 | # @!attribute 68 | # @return [Array] The flags for this Delete message. 69 | field :flags, BitVector.new(FLAGS) 70 | 71 | # @!attribute 72 | # @return [Hash] The selector for this Delete message. 73 | field :selector, Document 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/mongo/auth/kerberos.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 - 2014 MongoDB Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'jruby' 16 | include Java 17 | require 'jsasl.jar' 18 | 19 | module Mongo 20 | module Auth 21 | 22 | # Defines behaviour for Kerberos authentication. 23 | # 24 | # @since 2.0.0 25 | class Kerberos 26 | include Executable 27 | 28 | # The authentication mechinism string. 29 | # 30 | # @since 2.0.0 31 | MECHANISM = 'GSSAPI'.freeze 32 | 33 | # Log the user in on the given connection. 34 | # 35 | # @example Log the user in. 36 | # user.login(connection) 37 | # 38 | # @param [ Mongo::Connection ] connection The connection to log into. 39 | # on. 40 | # 41 | # @return [ Protocol::Reply ] The authentication response. 42 | # 43 | # @since 2.0.0 44 | def login(connection) 45 | host = connection.address.host 46 | token = BSON::Binary.new(authenticator(host).initialize_challenge) 47 | reply = connection.dispatch([ login_message(token) ]).documents[0] 48 | until reply.documents[0]['done'] 49 | token = BSON::Binary.new(authenticator(host).evaluate_challenge(response['payload'].to_s)) 50 | reply = connection.dispatch([ continue_message(response, token) ]) 51 | end 52 | reply 53 | end 54 | 55 | private 56 | 57 | def authenticator(host) 58 | @authenticator ||= org.mongodb.sasl.GSSAPIAuthenticator.new( 59 | JRuby.runtime, 60 | user.name, 61 | host, 62 | user.gssapi_service_name, 63 | user.canonicalize_host_name 64 | ) 65 | end 66 | 67 | def login_message(token) 68 | Protocol::Query.new( 69 | Auth::EXTERNAL, 70 | Database::COMMAND, 71 | { saslStart: 1, payload: token, mechanism: MECHANISM, authAuthorize: 1 }, 72 | limit: -1 73 | ) 74 | end 75 | 76 | def continue_message(response, token) 77 | Protocol::Query.new( 78 | Auth::EXTERNAL, 79 | Database::COMMAND, 80 | { saslContinue: 1, payload: token, conversationId: response['conversationId'] }, 81 | limit: -1 82 | ) 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/mongo/operation/aggregate/result.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | class Aggregate 18 | 19 | # Defines custom behaviour of results in an aggregation context. 20 | # 21 | # @since 2.0.0 22 | class Result < Operation::Result 23 | 24 | # The field name for the cursor document in an aggregation. 25 | # 26 | # @since 2.0.0 27 | CURSOR = 'cursor'.freeze 28 | 29 | # The cursor id field in the cursor document. 30 | # 31 | # @since 2.0.0 32 | CURSOR_ID = 'id'.freeze 33 | 34 | # The field name for the first batch of a cursor. 35 | # 36 | # @since 2.0.0 37 | FIRST_BATCH = 'firstBatch'.freeze 38 | 39 | # The field name for a result without a cursor. 40 | # 41 | # @since 2.0.0 42 | RESULT = 'result'.freeze 43 | 44 | # Get the cursor id for the result. 45 | # 46 | # @example Get the cursor id. 47 | # result.cursor_id 48 | # 49 | # @note Even though the wire protocol has a cursor_id field for all 50 | # messages of type reply, it is always zero when using the 51 | # aggregation framework and must be retrieved from the cursor 52 | # document itself. Wahnsinn! 53 | # 54 | # @return [ Integer ] The cursor id. 55 | # 56 | # @since 2.0.0 57 | def cursor_id 58 | cursor_document ? cursor_document[CURSOR_ID] : super 59 | end 60 | 61 | # Get the documents for the aggregation result. This is either the 62 | # first documents' 'result' field, or if a cursor option was selected 63 | # it is the 'firstBatch' field in the 'cursor' field of the first 64 | # document returned. 65 | # 66 | # @example Get the documents. 67 | # result.documents 68 | # 69 | # @return [ Array ] The documents. 70 | # 71 | # @since 2.0.0 72 | def documents 73 | reply.documents[0][RESULT] || cursor_document[FIRST_BATCH] 74 | end 75 | 76 | private 77 | 78 | def cursor_document 79 | @cursor_document ||= reply.documents[0][CURSOR] 80 | end 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.2' 2 | require 'simplecov' 3 | require 'coveralls' 4 | 5 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 6 | SimpleCov::Formatter::HTMLFormatter, 7 | Coveralls::SimpleCov::Formatter 8 | ] 9 | 10 | SimpleCov.start do 11 | # report groups 12 | add_group 'Wire Protocol', 'lib/mongo/protocol' 13 | # filters 14 | add_filter 'tasks' 15 | add_filter 'spec' 16 | add_filter 'bin' 17 | end 18 | end 19 | 20 | require 'mongo' 21 | 22 | require 'support/matchers' 23 | require 'support/monitoring' 24 | require 'support/authorization' 25 | require 'support/cluster_simulator' 26 | 27 | Mongo::Logger.logger = Logger.new($stdout, Logger::DEBUG) 28 | 29 | RSpec.configure do |config| 30 | config.color = true 31 | config.fail_fast = true unless ENV['CI'] || ENV['JENKINS_HOME'] 32 | config.formatter = 'documentation' 33 | config.include(Authorization) 34 | config.include(ClusterSimulator::Helpers) 35 | ClusterSimulator.configure(config) 36 | 37 | config.before(:suite) do 38 | begin 39 | # Create the root user administrator as the first user to be added to the 40 | # database. This user will need to be authenticated in order to add any 41 | # more users to any other databases. 42 | p ADMIN_UNAUTHORIZED_CLIENT.database.users.create(ROOT_USER) 43 | rescue Exception => e 44 | p e 45 | end 46 | begin 47 | # Adds the test user to the test database with permissions on all 48 | # databases that will be used in the test suite. 49 | p ADMIN_AUTHORIZED_CLIENT.database.users.create(TEST_USER) 50 | rescue Exception => e 51 | p e 52 | unless write_command_enabled? 53 | # If we are on versions less than 2.6, we need to create a user for 54 | # each database, since the users are not stored in the admin database 55 | # but in the system.users collection on the datbases themselves. Also, 56 | # roles in versions lower than 2.6 can only be strings, not hashes. 57 | begin p ROOT_AUTHORIZED_CLIENT.database.users.create(TEST_READ_WRITE_USER); rescue; end 58 | end 59 | end 60 | end 61 | end 62 | 63 | TEST_SET = 'ruby-driver-rs' 64 | COVERAGE_MIN = 90 65 | 66 | # For instances where behaviour is different on different versions, we need to 67 | # determin in the specs if we are 2.6 or higher. 68 | # 69 | # @since 2.0.0 70 | def write_command_enabled? 71 | @client ||= initialize_scanned_client! 72 | @write_command_enabled ||= @client.cluster.servers.first.write_command_enabled? 73 | end 74 | 75 | # Inititializes a basic scanned client to do an ismaster check. 76 | # 77 | # @since 2.0.0 78 | def initialize_scanned_client! 79 | client = Mongo::Client.new([ '127.0.0.1:27017' ], database: TEST_DB) 80 | client.cluster.scan! 81 | client 82 | end 83 | 84 | # require all shared examples 85 | Dir['./spec/support/shared/*.rb'].sort.each { |file| require file } 86 | -------------------------------------------------------------------------------- /lib/mongo/protocol/get_more.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Protocol 17 | 18 | # MongoDB Wire protocol GetMore message. 19 | # 20 | # This is a client request message that is sent to the server in order 21 | # to retrieve additional documents from a cursor that has already been 22 | # instantiated. 23 | # 24 | # The operation requires that you specify the database and collection 25 | # name as well as the cursor id because cursors are scoped to a namespace. 26 | # 27 | # @api semipublic 28 | class GetMore < Message 29 | 30 | # Creates a new GetMore message 31 | # 32 | # @example Get 15 additional documents from cursor 123 in 'xgen.users'. 33 | # GetMore.new('xgen', 'users', 15, 123) 34 | # 35 | # @param database [String, Symbol] The database to query. 36 | # @param collection [String, Symbol] The collection to query. 37 | # @param number_to_return [Integer] The number of documents to return. 38 | # @param cursor_id [Integer] The cursor id returned in a reply. 39 | def initialize(database, collection, number_to_return, cursor_id) 40 | @namespace = "#{database}.#{collection}" 41 | @number_to_return = number_to_return 42 | @cursor_id = cursor_id 43 | end 44 | 45 | # Get more messages require replies from the database. 46 | # 47 | # @example Does the message require a reply? 48 | # message.replyable? 49 | # 50 | # @return [ true ] Always true for get more. 51 | # 52 | # @since 2.0.0 53 | def replyable? 54 | true 55 | end 56 | 57 | private 58 | 59 | # The operation code required to specify a GetMore message. 60 | # @return [Fixnum] the operation code. 61 | def op_code 62 | 2005 63 | end 64 | 65 | # Field representing Zero encoded as an Int32 66 | field :zero, Zero 67 | 68 | # @!attribute 69 | # @return [String] The namespace for this GetMore message. 70 | field :namespace, CString 71 | 72 | # @!attribute 73 | # @return [Fixnum] The number to return for this GetMore message. 74 | field :number_to_return, Int32 75 | 76 | # @!attribute 77 | # @return [Fixnum] The cursor id to get more documents from. 78 | field :cursor_id, Int64 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/mongo/server/address/ipv4.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Server 17 | class Address 18 | 19 | # Sets up DNS resolution with IPv4 support if the address is an ip 20 | # address. 21 | # 22 | # @since 2.0.0 23 | class IPv4 24 | include Resolvable 25 | 26 | # The regular expression to use to match an IPv4 ip address. 27 | # 28 | # @since 2.0.0 29 | MATCH = Regexp.new('/\./').freeze 30 | 31 | # Initialize the IPv4 resolver. 32 | # 33 | # @example Initialize the resolver. 34 | # IPv4.new("127.0.0.1:28011") 35 | # 36 | # @param [ String ] address The address to resolve. 37 | # 38 | # @since 2.0.0 39 | def initialize(address) 40 | parts = address.split(':') 41 | @host = parts[0] 42 | @port = (parts[1] || 27017).to_i 43 | @seed = address 44 | resolve! 45 | end 46 | 47 | # Get the pattern to use when the DNS is resolved to match an IPv4 48 | # address. 49 | # 50 | # @example Get the IPv4 regex pattern. 51 | # ipv4.pattern 52 | # 53 | # @return [ Regexp ] The regexp. 54 | # 55 | # @since 2.0.0 56 | def pattern 57 | Resolv::IPv4::Regex 58 | end 59 | 60 | # Get a socket for the provided address type, given the options. 61 | # 62 | # @example Get an IPv4 socket. 63 | # ipv4.socket(5, :ssl => true) 64 | # 65 | # @param [ Float ] timeout The socket timeout. 66 | # @param [ Hash ] ssl_options SSL options. 67 | # 68 | # @return [ Pool::Socket::SSL, Pool::Socket::TCP ] The socket. 69 | # 70 | # @since 2.0.0 71 | def socket(timeout, ssl_options = {}) 72 | unless ssl_options.empty? 73 | Socket::SSL.new(ip, port, timeout, Socket::PF_INET, ssl_options) 74 | else 75 | Socket::TCP.new(ip, port, timeout, Socket::PF_INET) 76 | end 77 | end 78 | 79 | # Get the address as a string. 80 | # 81 | # @example Get the address as a string. 82 | # ipv4.to_s 83 | # 84 | # @return [ String ] The nice string. 85 | # 86 | # @since 2.0.0 87 | def to_s 88 | "#{host}:#{port}" 89 | end 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/mongo/server/address/ipv6.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the 'License'); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an 'AS IS' BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | class Server 17 | class Address 18 | 19 | # Sets up DNS resolution with IPv6 support if the address is an ip 20 | # address. 21 | # 22 | # @since 2.0.0 23 | class IPv6 24 | include Resolvable 25 | 26 | # The regular expression to use to match an IPv6 ip address. 27 | # 28 | # @since 2.0.0 29 | MATCH = Regexp.new('::').freeze 30 | 31 | # Initialize the IPv6 resolver. 32 | # 33 | # @example Initialize the resolver. 34 | # IPv6.new("[::1]:28011") 35 | # 36 | # @param [ String ] address The address to resolve. 37 | # 38 | # @since 2.0.0 39 | def initialize(address) 40 | parts = address.match(/\[(.+)\]:?(.+)?/) 41 | @host = parts[1] 42 | @port = (parts[2] || 27017).to_i 43 | @seed = address 44 | resolve! 45 | end 46 | 47 | # Get the pattern to use when the DNS is resolved to match an IPv6 48 | # address. 49 | # 50 | # @example Get the IPv6 regex pattern. 51 | # ipv6.pattern 52 | # 53 | # @return [ Regexp ] The regexp. 54 | # 55 | # @since 2.0.0 56 | def pattern 57 | Resolv::IPv6::Regex 58 | end 59 | 60 | # Get a socket for the provided address type, given the options. 61 | # 62 | # @example Get an IPv6 socket. 63 | # ipv4.socket(5, :ssl => true) 64 | # 65 | # @param [ Float ] timeout The socket timeout. 66 | # @param [ Hash ] ssl_options SSL options. 67 | # 68 | # @return [ Pool::Socket::SSL, Pool::Socket::TCP ] The socket. 69 | # 70 | # @since 2.0.0 71 | def socket(timeout, ssl_options = {}) 72 | unless ssl_options.empty? 73 | Socket::SSL.new(ip, port, timeout, Socket::PF_INET6, ssl_options) 74 | else 75 | Socket::TCP.new(ip, port, timeout, Socket::PF_INET6) 76 | end 77 | end 78 | 79 | # Get the address as a string. 80 | # 81 | # @example Get the address as a string. 82 | # ipv4.to_s 83 | # 84 | # @return [ String ] The nice string. 85 | # 86 | # @since 2.0.0 87 | def to_s 88 | "#{host}:#{port}" 89 | end 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/support/shared/socket.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'shared socket behavior' do 2 | 3 | describe '#read' do 4 | before { allow(socket).to receive(:read).and_return(Object.new) } 5 | 6 | context 'when an exception occurs in Socket#read' do 7 | before do 8 | allow(object).to receive(:alive?).and_return(true) 9 | object.connect 10 | end 11 | 12 | it 'raises a Mongo::SocketTimeoutError for Errno::ETIMEDOUT' do 13 | allow_any_instance_of(::Socket).to receive(:read) do 14 | raise Errno::ETIMEDOUT 15 | end 16 | expect { object.read(4096) }.to raise_error(Mongo::SocketTimeoutError) 17 | end 18 | 19 | it 'raises a Mongo::SocketError for IOError' do 20 | allow_any_instance_of(::Socket).to receive(:read) { raise IOError } 21 | expect { object.read(4096) }.to raise_error(Mongo::SocketError) 22 | end 23 | 24 | it 'raises a Mongo::SocketError for SystemCallError' do 25 | allow_any_instance_of(::Socket).to receive(:read) do 26 | raise SystemCallError, 'Oh god. Everything is ruined.' 27 | end 28 | expect { object.read(4096) }.to raise_error(Mongo::SocketError) 29 | end 30 | 31 | it 'raises a Mongo::SocketError for OpenSSL::SSL::SSLError' do 32 | allow_any_instance_of(::Socket).to receive(:read) do 33 | raise OpenSSL::SSL::SSLError 34 | end 35 | expect { object.read(4096) }.to raise_error(Mongo::SocketError) 36 | end 37 | end 38 | end 39 | 40 | describe '#write' do 41 | let(:payload) { Object.new } 42 | 43 | before { allow(socket).to receive(:write).and_return(1024) } 44 | 45 | context 'when an exception occurs in Socket#write' do 46 | before do 47 | allow(object).to receive(:alive?).and_return(true) 48 | object.connect 49 | end 50 | 51 | it 'raises a Mongo::SocketTimeoutError for Errno::ETIMEDOUT' do 52 | allow_any_instance_of(::Socket).to receive(:write) do 53 | raise Errno::ETIMEDOUT 54 | end 55 | 56 | expect do 57 | object.write(payload) 58 | end.to raise_error(Mongo::SocketTimeoutError) 59 | end 60 | 61 | it 'raises a Mongo::SocketError for IOError' do 62 | allow_any_instance_of(::Socket).to receive(:write) { raise IOError } 63 | expect { object.write(payload) }.to raise_error(Mongo::SocketError) 64 | end 65 | 66 | it 'raises a Mongo::SocketError for SystemCallError' do 67 | allow_any_instance_of(::Socket).to receive(:write) do 68 | raise SystemCallError, 'Oh god. Everything is ruined.' 69 | end 70 | expect { object.write(payload) }.to raise_error(Mongo::SocketError) 71 | end 72 | 73 | it 'raises a Mongo::SocketError for OpenSSL::SSL::SSLError' do 74 | allow_any_instance_of(::Socket).to receive(:write) do 75 | raise OpenSSL::SSL::SSLError 76 | end 77 | expect { object.write(payload) }.to raise_error(Mongo::SocketError) 78 | end 79 | end 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /lib/mongo/server_preference/secondary.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | module ServerPreference 18 | 19 | # Encapsulates specifications for selecting secondary servers given a list 20 | # of candidates. 21 | # 22 | # @since 2.0.0 23 | class Secondary 24 | include Selectable 25 | 26 | # Get the name of the server mode type. 27 | # 28 | # @example Get the name of the server mode for this preference. 29 | # preference.name 30 | # 31 | # @return [ Symbol ] :secondary 32 | # 33 | # @since 2.0.0 34 | def name 35 | :secondary 36 | end 37 | 38 | # Whether the slaveOk bit should be set on wire protocol messages. 39 | # I.e. whether the operation can be performed on a secondary server. 40 | # 41 | # @return [ true ] true 42 | # 43 | # @since 2.0.0 44 | def slave_ok? 45 | true 46 | end 47 | 48 | # Whether tag sets are allowed to be defined for this server preference. 49 | # 50 | # @return [ true ] true 51 | # 52 | # @since 2.0.0 53 | def tags_allowed? 54 | true 55 | end 56 | 57 | # Convert this server preference definition into a format appropriate 58 | # for a mongos server. 59 | # 60 | # @example Convert this server preference definition into a format 61 | # for mongos. 62 | # preference = Mongo::ServerPreference::Secondary.new 63 | # preference.to_mongos 64 | # 65 | # @return [ Hash ] The server preference formatted for a mongos server. 66 | # 67 | # @since 2.0.0 68 | def to_mongos 69 | preference = { :mode => 'secondary' } 70 | preference.merge!({ :tags => tag_sets }) unless tag_sets.empty? 71 | preference 72 | end 73 | 74 | # Select the secondary servers taking into account any defined tag sets and 75 | # acceptable latency between the nearest secondary and other secondaries. 76 | # 77 | # @example Select secondary servers given a list of candidates. 78 | # preference = Mongo::ServerPreference::Secondary.new 79 | # preference.select_servers([candidate_1, candidate_2]) 80 | # 81 | # @return [ Array ] The secondary servers from the list of candidates. 82 | # 83 | # @since 2.0.0 84 | def select_servers(candidates) 85 | near_servers(secondaries(candidates)) 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/mongo/operation/read/query.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | module Operation 17 | module Read 18 | 19 | # A MongoDB query operation. 20 | # 21 | # @since 2.0.0 22 | class Query 23 | include Executable 24 | 25 | # Initialize the query operation. 26 | # 27 | # @example 28 | # include Mongo 29 | # include Operation 30 | # Read::Query.new({ :selector => { :foo => 1 }, 31 | # :db_name => 'TEST_DB', 32 | # :coll_name => 'test-coll', 33 | # :options => { :limit => 2 } }) 34 | # 35 | # @param [ Hash ] spec The specifications for the query. 36 | # 37 | # @option spec :selector [ Hash ] The query selector. 38 | # @option spec :db_name [ String ] The name of the database on which 39 | # the query should be run. 40 | # @option spec :coll_name [ String ] The name of the collection on which 41 | # the query should be run. 42 | # @option spec :options [ Hash ] Options for the query. 43 | # 44 | # @since 2.0.0 45 | def initialize(spec) 46 | @spec = spec 47 | end 48 | 49 | # Execute the operation. 50 | # The context gets a connection on which the operation 51 | # is sent in the block. 52 | # 53 | # @params [ Mongo::Server::Context ] The context for this operation. 54 | # 55 | # @return [ Result ] The operation response, if there is one. 56 | # 57 | # @since 2.0.0 58 | def execute(context) 59 | unless context.primary? || context.standalone? || secondary_ok? 60 | raise Exception, "Must use primary server" 61 | end 62 | context.with_connection do |connection| 63 | Result.new(connection.dispatch([ message ])) 64 | end 65 | end 66 | 67 | private 68 | 69 | # The selector for the query. 70 | # 71 | # @return [ Hash ] The query selector. 72 | # 73 | # @since 2.0.0 74 | def selector 75 | @spec[:selector] 76 | end 77 | 78 | # The wire protocol message for this query operation. 79 | # 80 | # @return [ Mongo::Protocol::Query ] Wire protocol message. 81 | # 82 | # @since 2.0.0 83 | def message 84 | Protocol::Query.new(db_name, coll_name, selector, options) 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/mongo/operation/write/ensure_index.rb: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (C) 2009-2014 MongoDB, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | module Mongo 17 | module Operation 18 | module Write 19 | 20 | # A MongoDB ensure index operation. 21 | # If a server with version >= 2.5.5 is being used, a write command operation 22 | # will be created and sent instead. 23 | # 24 | # @since 2.0.0 25 | class EnsureIndex 26 | include Executable 27 | 28 | # Initialize the ensure index operation. 29 | # 30 | # @example 31 | # Write::EnsureIndex.new({ 32 | # :index => { :name => 1, :age => -1 }, 33 | # :db_name => 'test', 34 | # :coll_name => 'test_coll', 35 | # :index_name => 'name_1_age_-1' 36 | # }) 37 | # 38 | # @param [ Hash ] spec The specifications for the insert. 39 | # 40 | # @option spec :index [ Hash ] The index spec to create. 41 | # @option spec :db_name [ String ] The name of the database. 42 | # @option spec :coll_name [ String ] The name of the collection. 43 | # @option spec :index_name [ String ] The name of the index. 44 | # @option spec :options [ Hash ] Options for the command, if it ends up being a 45 | # write command. 46 | # 47 | # @since 2.0.0 48 | def initialize(spec) 49 | @spec = spec 50 | end 51 | 52 | # Execute the operation. 53 | # If the server has version < 2.5.5, an insert operation is sent. 54 | # If the server version is >= 2.5.5, an insert write command operation is created 55 | # and sent instead. 56 | # 57 | # @params [ Mongo::Server::Context ] The context for this operation. 58 | # 59 | # @return [ Result ] The result of the operation. 60 | # 61 | # @since 2.0.0 62 | def execute(context) 63 | Result.new( 64 | if context.write_command_enabled? 65 | Command::EnsureIndex.new(spec).execute(context) 66 | else 67 | context.with_connection do |connection| 68 | connection.dispatch([ message(index), gle ].compact) 69 | end 70 | end 71 | ).validate! 72 | end 73 | 74 | private 75 | 76 | def message(index) 77 | index_spec = options.merge(ns: namespace, key: index, name: index_name) 78 | Protocol::Insert.new(db_name, Index::COLLECTION, [ index_spec ]) 79 | end 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/mongo/server_preference/primary_preferred.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2014 MongoDB, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Mongo 16 | 17 | module ServerPreference 18 | 19 | # Encapsulates specifications for selecting servers, with the 20 | # primary preferred, given a list of candidates. 21 | # 22 | # @since 2.0.0 23 | class PrimaryPreferred 24 | include Selectable 25 | 26 | # Get the name of the server mode type. 27 | # 28 | # @example Get the name of the server mode for this preference. 29 | # preference.name 30 | # 31 | # @return [ Symbol ] :primary_preferred 32 | # 33 | # @since 2.0.0 34 | def name 35 | :primary_preferred 36 | end 37 | 38 | # Whether the slaveOk bit should be set on wire protocol messages. 39 | # I.e. whether the operation can be performed on a secondary server. 40 | # 41 | # @return [ true ] true 42 | # 43 | # @since 2.0.0 44 | def slave_ok? 45 | true 46 | end 47 | 48 | # Whether tag sets are allowed to be defined for this server preference. 49 | # 50 | # @return [ true ] true 51 | # 52 | # @since 2.0.0 53 | def tags_allowed? 54 | true 55 | end 56 | 57 | # Convert this server preference definition into a format appropriate 58 | # for a mongos server. 59 | # 60 | # @example Convert this server preference definition into a format 61 | # for mongos. 62 | # preference = Mongo::ServerPreference::PrimaryPreferred.new 63 | # preference.to_mongos 64 | # 65 | # @return [ Hash ] The server preference formatted for a mongos server. 66 | # 67 | # @since 2.0.0 68 | def to_mongos 69 | preference = { :mode => 'primaryPreferred' } 70 | preference.merge!({ :tags => tag_sets }) unless tag_sets.empty? 71 | preference 72 | end 73 | 74 | # Select servers taking into account any defined tag sets and 75 | # acceptable latency, with the primary preferred. 76 | # 77 | # @example Select servers given a list of candidates, 78 | # with the primary preferred. 79 | # preference = Mongo::ServerPreference::PrimaryPreferred.new 80 | # preference.select_servers([candidate_1, candidate_2]) 81 | # 82 | # @return [ Array ] A list of servers matching tag sets and acceptable 83 | # latency with the primary preferred. 84 | # 85 | # @since 2.0.0 86 | def select_servers(candidates) 87 | primary(candidates) + near_servers(secondaries(candidates)) 88 | end 89 | end 90 | end 91 | end 92 | --------------------------------------------------------------------------------