added ensure_index
This commit is contained in:
parent
5e81cf2f82
commit
d33ddfb8e0
@ -74,6 +74,8 @@ module Mongo
|
||||
@db, @name = db, name
|
||||
@connection = @db.connection
|
||||
@logger = @connection.logger
|
||||
@cache_time = @db.cache_time
|
||||
@cache = Hash.new(0)
|
||||
unless pk_factory
|
||||
@safe = options.has_key?(:safe) ? options[:safe] : @db.safe
|
||||
end
|
||||
@ -407,44 +409,38 @@ module Mongo
|
||||
# @core indexes create_index-instance_method
|
||||
def create_index(spec, opts={})
|
||||
opts[:dropDups] = opts.delete(:drop_dups) if opts[:drop_dups]
|
||||
field_spec = BSON::OrderedHash.new
|
||||
if spec.is_a?(String) || spec.is_a?(Symbol)
|
||||
field_spec[spec.to_s] = 1
|
||||
elsif spec.is_a?(Array) && spec.all? {|field| field.is_a?(Array) }
|
||||
spec.each do |f|
|
||||
if [Mongo::ASCENDING, Mongo::DESCENDING, Mongo::GEO2D].include?(f[1])
|
||||
field_spec[f[0].to_s] = f[1]
|
||||
else
|
||||
raise MongoArgumentError, "Invalid index field #{f[1].inspect}; " +
|
||||
"should be one of Mongo::ASCENDING (1), Mongo::DESCENDING (-1) or Mongo::GEO2D ('2d')."
|
||||
end
|
||||
end
|
||||
else
|
||||
raise MongoArgumentError, "Invalid index specification #{spec.inspect}; " +
|
||||
"should be either a string, symbol, or an array of arrays."
|
||||
end
|
||||
|
||||
field_spec = parse_index_spec(spec)
|
||||
name = opts.delete(:name) || generate_index_name(field_spec)
|
||||
|
||||
selector = {
|
||||
:name => name,
|
||||
:ns => "#{@db.name}.#{@name}",
|
||||
:key => field_spec
|
||||
}
|
||||
selector.merge!(opts)
|
||||
generate_indexes(field_spec, name, opts)
|
||||
name
|
||||
end
|
||||
|
||||
begin
|
||||
insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, true)
|
||||
|
||||
rescue Mongo::OperationFailure => e
|
||||
if selector[:dropDups] && e.message =~ /^11000/
|
||||
# NOP. If the user is intentionally dropping dups, we can ignore duplicate key errors.
|
||||
else
|
||||
raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following error: " +
|
||||
"#{e.message}"
|
||||
end
|
||||
# Calls create_index and sets a flag to not do so again for another X minutes.
|
||||
# this time can be specified as an option when initializing a Mongo::Db object as options[:cache_time]
|
||||
# Any changes to an index will be propogated through regardless of cache time (eg, if you change index direction)
|
||||
# @example Call sequence:
|
||||
# Time t: @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and sets the 5 minute cache
|
||||
# Time t+2min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- doesn't do anything
|
||||
# Time t+3min : @posts.ensure_index([['something_else', Mongo::ASCENDING]) -- calls create_index and sets 5 minute cache
|
||||
# Time t+10min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and resets the 5 minute counter
|
||||
def ensure_index(spec, opts={})
|
||||
valid = BSON::OrderedHash.new
|
||||
now = Time.now.utc.to_i
|
||||
field_spec = parse_index_spec(spec)
|
||||
|
||||
|
||||
field_spec.each do |key, value|
|
||||
cache_key = generate_index_name({key => value}) #bit of a hack.
|
||||
timeout = @cache[cache_key] || 0
|
||||
valid[key] = value if timeout <= now
|
||||
end
|
||||
name = opts.delete(:name) || generate_index_name(valid)
|
||||
generate_indexes(valid, name, opts) if valid.any?
|
||||
|
||||
# I do this here instead of in the above loop in case there were any errors inserting. Best to be safe.
|
||||
name.each {|n| @cache[n] = now + @cache_time}
|
||||
name
|
||||
end
|
||||
|
||||
@ -454,6 +450,7 @@ module Mongo
|
||||
#
|
||||
# @core indexes
|
||||
def drop_index(name)
|
||||
@cache[name] = [] # I do this first because there is no harm in clearing the cache.
|
||||
@db.drop_index(@name, name)
|
||||
end
|
||||
|
||||
@ -461,7 +458,7 @@ module Mongo
|
||||
#
|
||||
# @core indexes
|
||||
def drop_indexes
|
||||
|
||||
@cache = {}
|
||||
# Note: calling drop_indexes with no args will drop them all.
|
||||
@db.drop_index(@name, '*')
|
||||
end
|
||||
@ -713,6 +710,51 @@ module Mongo
|
||||
|
||||
private
|
||||
|
||||
|
||||
def parse_index_spec(spec)
|
||||
field_spec = BSON::OrderedHash.new
|
||||
if spec.is_a?(String) || spec.is_a?(Symbol)
|
||||
field_spec[spec.to_s] = 1
|
||||
elsif spec.is_a?(Array) && spec.all? {|field| field.is_a?(Array) }
|
||||
spec.each do |f|
|
||||
if [Mongo::ASCENDING, Mongo::DESCENDING, Mongo::GEO2D].include?(f[1])
|
||||
field_spec[f[0].to_s] = f[1]
|
||||
else
|
||||
raise MongoArgumentError, "Invalid index field #{f[1].inspect}; " +
|
||||
"should be one of Mongo::ASCENDING (1), Mongo::DESCENDING (-1) or Mongo::GEO2D ('2d')."
|
||||
end
|
||||
end
|
||||
else
|
||||
raise MongoArgumentError, "Invalid index specification #{spec.inspect}; " +
|
||||
"should be either a string, symbol, or an array of arrays."
|
||||
end
|
||||
field_spec
|
||||
end
|
||||
|
||||
def generate_indexes(field_spec, name, opts)
|
||||
selector = {
|
||||
:name => name,
|
||||
:ns => "#{@db.name}.#{@name}",
|
||||
:key => field_spec
|
||||
}
|
||||
selector.merge!(opts)
|
||||
|
||||
begin
|
||||
insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, true)
|
||||
|
||||
rescue Mongo::OperationFailure => e
|
||||
if selector[:dropDups] && e.message =~ /^11000/
|
||||
# NOP. If the user is intentionally dropping dups, we can ignore duplicate key errors.
|
||||
else
|
||||
raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following error: " +
|
||||
"#{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
# Sends a Mongo::Constants::OP_INSERT message to the database.
|
||||
# Takes an array of +documents+, an optional +collection_name+, and a
|
||||
# +check_keys+ setting.
|
||||
|
@ -51,6 +51,9 @@ module Mongo
|
||||
# The Mongo::Connection instance connecting to the MongoDB server.
|
||||
attr_reader :connection
|
||||
|
||||
# The length of time that Collection.ensure_index should cache index calls
|
||||
attr_accessor :cache_time
|
||||
|
||||
# Instances of DB are normally obtained by calling Mongo#db.
|
||||
#
|
||||
# @param [String] db_name the database name.
|
||||
@ -70,6 +73,7 @@ module Mongo
|
||||
# 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
|
||||
# @option options [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command.
|
||||
#
|
||||
# @core databases constructor_details
|
||||
def initialize(db_name, connection, options={})
|
||||
@ -78,6 +82,7 @@ module Mongo
|
||||
@strict = options[:strict]
|
||||
@pk_factory = options[:pk]
|
||||
@safe = options.has_key?(:safe) ? options[:safe] : @connection.safe
|
||||
@cache_time = options[:cache_time] || 300 #5 minutes.
|
||||
end
|
||||
|
||||
# Authenticate with the given username and password. Note that mongod
|
||||
|
@ -566,6 +566,30 @@ class TestCollection < Test::Unit::TestCase
|
||||
assert_equal 1, x
|
||||
end
|
||||
|
||||
|
||||
def test_ensure_index
|
||||
@@test.drop_indexes
|
||||
@@test.insert("x" => "hello world")
|
||||
assert_equal 1, @@test.index_information.keys.count #default index
|
||||
|
||||
@@test.ensure_index([["x", Mongo::DESCENDING]], {})
|
||||
assert_equal 2, @@test.index_information.keys.count
|
||||
assert @@test.index_information.keys.include? "x_-1"
|
||||
|
||||
@@test.ensure_index([["x", Mongo::ASCENDING]])
|
||||
assert @@test.index_information.keys.include? "x_1"
|
||||
|
||||
@@test.drop_index("x_1")
|
||||
assert_equal 2, @@test.index_information.keys.count
|
||||
@@test.drop_index("x_-1")
|
||||
assert_equal 1, @@test.index_information.keys.count
|
||||
|
||||
@@test.ensure_index([["x", Mongo::DESCENDING]], {}) #should work as not cached.
|
||||
assert_equal 2, @@test.index_information.keys.count
|
||||
assert @@test.index_information.keys.include? "x_-1"
|
||||
|
||||
end
|
||||
|
||||
context "Grouping" do
|
||||
setup do
|
||||
@@test.remove
|
||||
|
@ -81,5 +81,54 @@ class CollectionTest < Test::Unit::TestCase
|
||||
@logger.stubs(:debug)
|
||||
@coll.update({}, {:title => 'Moby Dick'}, :safe => true)
|
||||
end
|
||||
|
||||
should "not call insert for each ensure_index call" do
|
||||
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
|
||||
@db = @conn['testing']
|
||||
@coll = @db.collection('books')
|
||||
@coll.expects(:generate_indexes).once
|
||||
|
||||
@coll.ensure_index [["x", Mongo::DESCENDING]]
|
||||
@coll.ensure_index [["x", Mongo::DESCENDING]]
|
||||
|
||||
end
|
||||
should "call generate_indexes for a new direction on the same field for ensure_index" do
|
||||
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
|
||||
@db = @conn['testing']
|
||||
@coll = @db.collection('books')
|
||||
@coll.expects(:generate_indexes).twice
|
||||
|
||||
@coll.ensure_index [["x", Mongo::DESCENDING]]
|
||||
@coll.ensure_index [["x", Mongo::ASCENDING]]
|
||||
|
||||
end
|
||||
|
||||
should "call generate_indexes twice because the cache time is 0 seconds" do
|
||||
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
|
||||
@db = @conn['testing']
|
||||
@db.cache_time = 0
|
||||
@coll = @db.collection('books')
|
||||
@coll.expects(:generate_indexes).twice
|
||||
|
||||
|
||||
@coll.ensure_index [["x", Mongo::DESCENDING]]
|
||||
@coll.ensure_index [["x", Mongo::DESCENDING]]
|
||||
|
||||
end
|
||||
|
||||
should "call generate_indexes for each key when calling ensure_indexes" do
|
||||
@conn = Connection.new('localhost', 27017, :logger => @logger, :connect => false)
|
||||
@db = @conn['testing']
|
||||
@db.cache_time = 300
|
||||
@coll = @db.collection('books')
|
||||
@coll.expects(:generate_indexes).once.with do |a, b, c|
|
||||
a == {"x"=>-1, "y"=>-1}
|
||||
end
|
||||
|
||||
@coll.ensure_index [["x", Mongo::DESCENDING], ["y", Mongo::DESCENDING]]
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user