Allow the setting of safe mode globally on the Connection,
DB, and Collection levels. The safe mode setting will automatically be inherited down the hierarchy Connection -> DB -> Collection -> (insert, update, remove). This default can be overridden at any time. Connection#safe, DB#safe, and Collection#safe will yield the current default value.
This commit is contained in:
parent
f7d151c8dc
commit
68af3dbe8f
@ -21,13 +21,18 @@ module Mongo
|
||||
# A named collection of documents in a database.
|
||||
class Collection
|
||||
|
||||
attr_reader :db, :name, :pk_factory, :hint
|
||||
attr_reader :db, :name, :pk_factory, :hint, :safe
|
||||
|
||||
# Initialize a collection object.
|
||||
#
|
||||
# @param [DB] db a MongoDB database instance.
|
||||
# @param [String, Symbol] name the name of the collection.
|
||||
#
|
||||
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
|
||||
# for insert, update, and remove method called on this Collection instance. If no
|
||||
# value is provided, the default value set on this instance's DB will be used. This
|
||||
# default can be overridden for any invocation of insert, update, or remove.
|
||||
#
|
||||
# @raise [InvalidNSName]
|
||||
# if collection name is empty, contains '$', or starts or ends with '.'
|
||||
#
|
||||
@ -37,7 +42,7 @@ module Mongo
|
||||
# @return [Collection]
|
||||
#
|
||||
# @core collections constructor_details
|
||||
def initialize(db, name, pk_factory=nil)
|
||||
def initialize(db, name, pk_factory=nil, options={})
|
||||
case name
|
||||
when Symbol, String
|
||||
else
|
||||
@ -60,6 +65,7 @@ module Mongo
|
||||
@connection = @db.connection
|
||||
@logger = @connection.logger
|
||||
@pk_factory = pk_factory || BSON::ObjectId
|
||||
@safe = options.has_key?(:safe) ? options[:safe] : @db.safe
|
||||
@hint = nil
|
||||
end
|
||||
|
||||
@ -245,8 +251,10 @@ module Mongo
|
||||
# @option opts [Boolean, Hash] :safe (+false+)
|
||||
# run the operation in safe mode, which run a getlasterror command on the
|
||||
# database to report any assertion. In addition, a hash can be provided to
|
||||
# run an fsync and/or wait for replication of the insert (>= 1.5.1). See the options
|
||||
# for DB#error.
|
||||
# run an fsync and/or wait for replication of the insert (>= 1.5.1). Safe
|
||||
# options provided here will override any safe options set on this collection,
|
||||
# its database object, or the current connection. See the options on
|
||||
# for DB#get_last_error.
|
||||
#
|
||||
# @see DB#remove for options that can be passed to :safe.
|
||||
#
|
||||
@ -254,7 +262,8 @@ module Mongo
|
||||
def insert(doc_or_docs, options={})
|
||||
doc_or_docs = [doc_or_docs] unless doc_or_docs.is_a?(Array)
|
||||
doc_or_docs.collect! { |doc| @pk_factory.create_pk(doc) }
|
||||
result = insert_documents(doc_or_docs, @name, true, options[:safe])
|
||||
safe = options.has_key?(:safe) ? options[:safe] : @safe
|
||||
result = insert_documents(doc_or_docs, @name, true, safe)
|
||||
result.size > 1 ? result : result.first
|
||||
end
|
||||
alias_method :<<, :insert
|
||||
@ -265,10 +274,11 @@ module Mongo
|
||||
# If specified, only matching documents will be removed.
|
||||
#
|
||||
# @option opts [Boolean, Hash] :safe (+false+)
|
||||
# run the operation in safe mode, which run a getlasterror command on the
|
||||
# run the operation in safe mode, which will run a getlasterror command on the
|
||||
# database to report any assertion. In addition, a hash can be provided to
|
||||
# run an fsync and/or wait for replication of the remove (>= 1.5.1). See the options
|
||||
# for DB#get_last_error.
|
||||
# run an fsync and/or wait for replication of the remove (>= 1.5.1). Safe
|
||||
# options provided here will override any safe options set on this collection,
|
||||
# its database, or the current connection. See the options for DB#get_last_error for more details.
|
||||
#
|
||||
# @example remove all documents from the 'users' collection:
|
||||
# users.remove
|
||||
@ -287,14 +297,15 @@ module Mongo
|
||||
# @core remove remove-instance_method
|
||||
def remove(selector={}, opts={})
|
||||
# Initial byte is 0.
|
||||
safe = opts.has_key?(:safe) ? opts[:safe] : @safe
|
||||
message = BSON::ByteBuffer.new("\0\0\0\0")
|
||||
BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
|
||||
message.put_int(0)
|
||||
message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
|
||||
|
||||
@logger.debug("MONGODB #{@db.name}['#{@name}'].remove(#{selector.inspect})") if @logger
|
||||
if opts[:safe]
|
||||
@connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, opts[:safe])
|
||||
if safe
|
||||
@connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, safe)
|
||||
# the return value of send_message_with_safe_check isn't actually meaningful --
|
||||
# only the fact that it didn't raise an error is -- so just return true
|
||||
true
|
||||
@ -320,11 +331,14 @@ module Mongo
|
||||
# @option opts [Boolean] :safe (+false+)
|
||||
# If true, check that the save succeeded. OperationFailure
|
||||
# will be raised on an error. Note that a safe check requires an extra
|
||||
# round-trip to the database.
|
||||
# round-trip to the database. Safe options provided here will override any safe
|
||||
# options set on this collection, its database object, or the current collection.
|
||||
# See the options for DB#get_last_error for details.
|
||||
#
|
||||
# @core update update-instance_method
|
||||
def update(selector, document, options={})
|
||||
# Initial byte is 0.
|
||||
safe = options.has_key?(:safe) ? options[:safe] : @safe
|
||||
message = BSON::ByteBuffer.new("\0\0\0\0")
|
||||
BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}")
|
||||
update_options = 0
|
||||
@ -334,8 +348,8 @@ module Mongo
|
||||
message.put_binary(BSON::BSON_CODER.serialize(selector, false, true).to_s)
|
||||
message.put_binary(BSON::BSON_CODER.serialize(document, false, true).to_s)
|
||||
@logger.debug("MONGODB #{@db.name}['#{@name}'].update(#{selector.inspect}, #{document.inspect})") if @logger
|
||||
if options[:safe]
|
||||
@connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, nil, options[:safe])
|
||||
if safe
|
||||
@connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, nil, safe)
|
||||
else
|
||||
@connection.send_message(Mongo::Constants::OP_UPDATE, message, nil)
|
||||
end
|
||||
|
@ -38,7 +38,8 @@ module Mongo
|
||||
MONGODB_URI_MATCHER = /(([-_.\w\d]+):([-_\w\d]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
|
||||
MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
|
||||
|
||||
attr_reader :logger, :size, :host, :port, :nodes, :auths, :sockets, :checked_out, :primary, :secondaries, :arbiters
|
||||
attr_reader :logger, :size, :host, :port, :nodes, :auths, :sockets, :checked_out, :primary, :secondaries, :arbiters,
|
||||
:safe
|
||||
|
||||
# Counter for generating unique request ids.
|
||||
@@current_request_id = 0
|
||||
@ -61,6 +62,10 @@ module Mongo
|
||||
# @param [String, Hash] host.
|
||||
# @param [Integer] port specify a port number here if only one host is being specified.
|
||||
#
|
||||
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
|
||||
# propogated to DB objects instantiated off of this Connection. This
|
||||
# default can be overridden upon instantiation of any DB by explicity setting a :safe value
|
||||
# on initialization.
|
||||
# @option options [Boolean] :slave_ok (false) Must be set to +true+ when connecting
|
||||
# to a single, slave node.
|
||||
# @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
|
||||
@ -114,6 +119,9 @@ module Mongo
|
||||
# Mutex for synchronizing pool access
|
||||
@connection_mutex = Mutex.new
|
||||
|
||||
# Global safe option. This is false by default.
|
||||
@safe = options[:safe] || false
|
||||
|
||||
# Create a mutex when a new key, in this case a socket,
|
||||
# is added to the hash.
|
||||
@safe_mutexes = Hash.new { |h, k| h[k] = Mutex.new }
|
||||
@ -312,7 +320,7 @@ module Mongo
|
||||
#
|
||||
# @core databases []-instance_method
|
||||
def [](db_name)
|
||||
DB.new(db_name, self)
|
||||
DB.new(db_name, self, :safe => @safe)
|
||||
end
|
||||
|
||||
# Drop a database.
|
||||
|
@ -45,8 +45,8 @@ module Mongo
|
||||
# Returns the value of the +strict+ flag.
|
||||
def strict?; @strict; end
|
||||
|
||||
# The name of the database.
|
||||
attr_reader :name
|
||||
# The name of the database and the local safe option.
|
||||
attr_reader :name, :safe
|
||||
|
||||
# The Mongo::Connection instance connecting to the MongoDB server.
|
||||
attr_reader :connection
|
||||
@ -65,12 +65,19 @@ module Mongo
|
||||
# fields the factory wishes to inject. (NOTE: if the object already has a primary key,
|
||||
# the factory should not inject a new key).
|
||||
#
|
||||
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
|
||||
# propogated to Collection objects instantiated off of this DB. If no
|
||||
# value is provided, the default value set on this instance's Connection object will be used. This
|
||||
# default can be overridden upon instantiation of any collection by explicity setting a :safe value
|
||||
# on initialization
|
||||
#
|
||||
# @core databases constructor_details
|
||||
def initialize(db_name, connection, options={})
|
||||
@name = Mongo::Support.validate_db_name(db_name)
|
||||
@connection = connection
|
||||
@strict = options[:strict]
|
||||
@pk_factory = options[:pk]
|
||||
@safe = options.has_key?(:safe) ? options[:safe] : @connection.safe
|
||||
end
|
||||
|
||||
# Authenticate with the given username and password. Note that mongod
|
||||
@ -259,9 +266,13 @@ module Mongo
|
||||
# @raise [MongoDBError] if collection does not already exist and we're in +strict+ mode.
|
||||
#
|
||||
# @return [Mongo::Collection]
|
||||
def collection(name)
|
||||
return Collection.new(self, name, @pk_factory) if !strict? || collection_names.include?(name)
|
||||
raise Mongo::MongoDBError, "Collection #{name} doesn't exist. Currently in strict mode."
|
||||
def collection(name, options={})
|
||||
if strict? && !collection_names.include?(name)
|
||||
raise Mongo::MongoDBError, "Collection #{name} doesn't exist. Currently in strict mode."
|
||||
else
|
||||
options[:safe] = options.has_key?(:safe) ? options[:safe] : @safe
|
||||
Collection.new(self, name, @pk_factory, options)
|
||||
end
|
||||
end
|
||||
alias_method :[], :collection
|
||||
|
||||
|
42
test/safe_test.rb
Normal file
42
test/safe_test.rb
Normal file
@ -0,0 +1,42 @@
|
||||
require './test/test_helper'
|
||||
include Mongo
|
||||
|
||||
class SafeTest < Test::Unit::TestCase
|
||||
context "Safe tests: " do
|
||||
setup do
|
||||
@con = standard_connection(:safe => {:w => 1})
|
||||
@db = @con[MONGO_TEST_DB]
|
||||
@col = @db['test-safe']
|
||||
@col.create_index([[:a, 1]], :unique => true)
|
||||
@col.remove
|
||||
end
|
||||
|
||||
should "propogate safe option on insert" do
|
||||
@col.insert({:a => 1})
|
||||
|
||||
assert_raise_error(OperationFailure, "duplicate key") do
|
||||
@col.insert({:a => 1})
|
||||
end
|
||||
end
|
||||
|
||||
should "allow safe override on insert" do
|
||||
@col.insert({:a => 1})
|
||||
@col.insert({:a => 1}, :safe => false)
|
||||
end
|
||||
|
||||
should "propogate safe option on update" do
|
||||
@col.insert({:a => 1})
|
||||
@col.insert({:a => 2})
|
||||
|
||||
assert_raise_error(OperationFailure, "duplicate key") do
|
||||
@col.update({:a => 2}, {:a => 1})
|
||||
end
|
||||
end
|
||||
|
||||
should "allow safe override on update" do
|
||||
@col.insert({:a => 1})
|
||||
@col.insert({:a => 2})
|
||||
@col.update({:a => 2}, {:a => 1}, :safe => false)
|
||||
end
|
||||
end
|
||||
end
|
@ -16,7 +16,9 @@ class DBTest < Test::Unit::TestCase
|
||||
context "DB commands" do
|
||||
setup do
|
||||
@conn = stub()
|
||||
@conn.stubs(:safe)
|
||||
@db = DB.new("testing", @conn)
|
||||
@db.stubs(:safe)
|
||||
@collection = mock()
|
||||
@db.stubs(:system_command_collection).returns(@collection)
|
||||
end
|
||||
|
@ -5,12 +5,14 @@ class GridTest < Test::Unit::TestCase
|
||||
context "GridFS: " do
|
||||
setup do
|
||||
@conn = stub()
|
||||
@conn.stubs(:safe)
|
||||
@db = DB.new("testing", @conn)
|
||||
@files = mock()
|
||||
@chunks = mock()
|
||||
|
||||
@db.expects(:[]).with('fs.files').returns(@files)
|
||||
@db.expects(:[]).with('fs.chunks').returns(@chunks)
|
||||
@db.stubs(:safe)
|
||||
end
|
||||
|
||||
context "Grid classe with standard connections" do
|
||||
|
125
test/unit/safe_test.rb
Normal file
125
test/unit/safe_test.rb
Normal file
@ -0,0 +1,125 @@
|
||||
require File.expand_path('./test/test_helper.rb')
|
||||
|
||||
class SafeTest < Test::Unit::TestCase
|
||||
|
||||
context "Safe mode on connection: " do
|
||||
setup do
|
||||
@safe_value = {:w => 7}
|
||||
@con = Mongo::Connection.new('localhost', 27017, :safe => @safe_value, :connect => false)
|
||||
end
|
||||
|
||||
should "propogate to DB" do
|
||||
db = @con['foo']
|
||||
assert_equal @safe_value, db.safe
|
||||
|
||||
|
||||
db = @con.db('foo')
|
||||
assert_equal @safe_value, db.safe
|
||||
|
||||
db = DB.new('foo', @con)
|
||||
assert_equal @safe_value, db.safe
|
||||
end
|
||||
|
||||
should "allow db override" do
|
||||
db = DB.new('foo', @con, :safe => false)
|
||||
assert_equal false, db.safe
|
||||
|
||||
db = @con.db('foo', :safe => false)
|
||||
assert_equal false, db.safe
|
||||
end
|
||||
|
||||
context "on DB: " do
|
||||
setup do
|
||||
@db = @con['foo']
|
||||
end
|
||||
|
||||
should "propogate to collection" do
|
||||
col = @db.collection('bar')
|
||||
assert_equal @safe_value, col.safe
|
||||
|
||||
col = @db['bar']
|
||||
assert_equal @safe_value, col.safe
|
||||
|
||||
col = Collection.new(@db, 'bar')
|
||||
assert_equal @safe_value, col.safe
|
||||
end
|
||||
|
||||
should "allow override on collection" do
|
||||
col = @db.collection('bar', :safe => false)
|
||||
assert_equal false, col.safe
|
||||
|
||||
col = Collection.new(@db, 'bar', nil, :safe => false)
|
||||
assert_equal false, col.safe
|
||||
end
|
||||
end
|
||||
|
||||
context "on operations supporting safe mode" do
|
||||
setup do
|
||||
@col = @con['foo']['bar']
|
||||
end
|
||||
|
||||
should "use default value on insert" do
|
||||
@con.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe|
|
||||
safe == @safe_value
|
||||
end
|
||||
|
||||
@col.insert({:a => 1})
|
||||
end
|
||||
|
||||
should "allow override alternate value on insert" do
|
||||
@con.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe|
|
||||
safe == {:w => 100}
|
||||
end
|
||||
|
||||
@col.insert({:a => 1}, :safe => {:w => 100})
|
||||
end
|
||||
|
||||
should "allow override to disable on insert" do
|
||||
@con.expects(:send_message)
|
||||
@col.insert({:a => 1}, :safe => false)
|
||||
end
|
||||
|
||||
should "use default value on update" do
|
||||
@con.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe|
|
||||
safe == @safe_value
|
||||
end
|
||||
|
||||
@col.update({:a => 1}, {:a => 2})
|
||||
end
|
||||
|
||||
should "allow override alternate value on update" do
|
||||
@con.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe|
|
||||
safe == {:w => 100}
|
||||
end
|
||||
|
||||
@col.update({:a => 1}, {:a => 2}, :safe => {:w => 100})
|
||||
end
|
||||
|
||||
should "allow override to disable on update" do
|
||||
@con.expects(:send_message)
|
||||
@col.update({:a => 1}, {:a => 2}, :safe => false)
|
||||
end
|
||||
|
||||
should "use default value on remove" do
|
||||
@con.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe|
|
||||
safe == @safe_value
|
||||
end
|
||||
|
||||
@col.remove
|
||||
end
|
||||
|
||||
should "allow override alternate value on remove" do
|
||||
@con.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe|
|
||||
safe == {:w => 100}
|
||||
end
|
||||
|
||||
@col.remove({}, :safe => {:w => 100})
|
||||
end
|
||||
|
||||
should "allow override to disable on remove" do
|
||||
@con.expects(:send_message)
|
||||
@col.remove({}, :safe => false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user