Skip to content

Commit 8efdd97

Browse files
authored
MONGOID-5827 Allow duplicate indexes to be declared (#5967)
* MONGOID-5827 allow duplicate indexes to be declared This adds a new feature flag (`allow_duplicate_index_declarations`) which defaults to false, which is the legacy behavior. When false, superficially-duplicate indexes are silently ignored. When true, all index declarations are passed through to the server, where duplicates will trigger a server-side error. * account for 4.4 behavior
1 parent 0b237f6 commit 8efdd97

File tree

4 files changed

+96
-4
lines changed

4 files changed

+96
-4
lines changed

lib/mongoid/config.rb

+13
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ module Config
180180
# See https://jira.mongodb.org/browse/MONGOID-5785 for more details.
181181
option :allow_scopes_to_unset_default_scope, default: false
182182

183+
# When this flag is false, indexes are (roughly) validated on the client
184+
# to prevent duplicate indexes being declared. This validation is
185+
# incomplete, however, and can result in some indexes being silently
186+
# ignored.
187+
#
188+
# Setting this to true will allow duplicate indexes to be declared and sent
189+
# to the server. The server will then validate the indexes and raise an
190+
# exception if duplicates are detected.
191+
#
192+
# See https://jira.mongodb.org/browse/MONGOID-5827 for an example of the
193+
# consequences of duplicate index checking.
194+
option :allow_duplicate_index_declarations, default: false
195+
183196
# Returns the Config singleton, for use in the configure DSL.
184197
#
185198
# @return [ self ] The Config singleton.

lib/mongoid/indexable.rb

+8-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ def add_indexes
9595
# @return [ Hash ] The index options.
9696
def index(spec, options = nil)
9797
specification = Specification.new(self, spec, options)
98-
if !index_specifications.include?(specification)
98+
99+
# the equality test for Indexable::Specification instances does not
100+
# consider any options, which means names are not compared. This means
101+
# that an index with different options from another, and a different
102+
# name, will be silently ignored unless duplicate index declarations
103+
# are allowed.
104+
if Mongoid.allow_duplicate_index_declarations || !index_specifications.include?(specification)
99105
index_specifications.push(specification)
100106
end
101107
end
@@ -110,9 +116,8 @@ def index(spec, options = nil)
110116
#
111117
# @return [ Specification ] The found specification.
112118
def index_specification(index_hash, index_name = nil)
113-
index = OpenStruct.new(fields: index_hash.keys, key: index_hash)
114119
index_specifications.detect do |spec|
115-
spec == index || (index_name && index_name == spec.name)
120+
spec.superficial_match?(key: index_hash, name: index_name)
116121
end
117122
end
118123

lib/mongoid/indexable/specification.rb

+19-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,25 @@ class Specification
3030
#
3131
# @return [ true | false ] If the specs are equal.
3232
def ==(other)
33-
fields == other.fields && key == other.key
33+
superficial_match?(key: other.key)
34+
end
35+
36+
# Performs a superficial comparison with the given criteria, checking
37+
# only the key and (optionally) the name. Options are not compared.
38+
#
39+
# Note that the ordering of the fields in the key is significant. Two
40+
# keys with different orderings will not match, here.
41+
#
42+
# @param [ Hash ] key the key that defines the index.
43+
# @param [ String | nil ] name the name given to the index, or nil to
44+
# ignore the name.
45+
#
46+
# @return [ true | false ] the result of the comparison, true if this
47+
# specification matches the criteria, and false otherwise.
48+
def superficial_match?(key: {}, name: nil)
49+
(name && name == self.name) ||
50+
self.fields == key.keys &&
51+
self.key == key
3452
end
3553

3654
# Instantiate a new index specification.

spec/mongoid/indexable_spec.rb

+56
Original file line numberDiff line numberDiff line change
@@ -959,5 +959,61 @@ def self.hereditary?
959959
end
960960
end
961961
end
962+
963+
context 'when declaring a duplicate index with different options' do
964+
def declare_duplicate_indexes!
965+
klass.index({ name: 1 }, { partial_filter_expression: { name: 'a' } })
966+
klass.index({ name: 1 }, { partial_filter_expression: { name: 'b' } })
967+
klass.create_indexes
968+
end
969+
970+
context 'when allow_duplicate_index_declarations is false' do
971+
config_override :allow_duplicate_index_declarations, false
972+
973+
it 'silently ignores the duplicate definition' do
974+
expect { declare_duplicate_indexes! }.not_to raise_exception
975+
end
976+
end
977+
978+
context 'when allow_duplicate_index_declarations is true' do
979+
config_override :allow_duplicate_index_declarations, true
980+
981+
it 'raises a server error' do
982+
expect { declare_duplicate_indexes! }.to raise_exception
983+
end
984+
end
985+
end
986+
987+
context 'when declaring a duplicate index with different names' do
988+
def declare_duplicate_indexes!
989+
klass.index({ name: 1 }, { partial_filter_expression: { name: 'a' } })
990+
klass.index({ name: 1 }, { name: 'alt_name', partial_filter_expression: { name: 'b' } })
991+
klass.create_indexes
992+
end
993+
994+
let(:index_count) { klass.collection.indexes.count }
995+
996+
997+
context 'when allow_duplicate_index_declarations is false' do
998+
config_override :allow_duplicate_index_declarations, false
999+
1000+
it 'silently ignores the duplicate definition' do
1001+
expect { declare_duplicate_indexes! }.not_to raise_exception
1002+
expect(index_count).to be == 2 # _id and name
1003+
end
1004+
end
1005+
1006+
context 'when allow_duplicate_index_declarations is true' do
1007+
# 4.4 apparently doesn't recognize :name option for indexes?
1008+
min_server_version '5.0'
1009+
1010+
config_override :allow_duplicate_index_declarations, true
1011+
1012+
it 'creates both indexes' do
1013+
expect { declare_duplicate_indexes! }.not_to raise_exception
1014+
expect(index_count).to be == 3 # _id, name, alt_name
1015+
end
1016+
end
1017+
end
9621018
end
9631019
end

0 commit comments

Comments
 (0)