Skip to content

Commit 3c87007

Browse files
authored
MONGOID-5827 Allow duplicate indexes to be declared (mongodb#5967) (mongodb#5971)
* 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 1f975dd commit 3c87007

File tree

4 files changed

+96
-4
lines changed

4 files changed

+96
-4
lines changed

lib/mongoid/config.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,19 @@ module Config
198198
# See https://jira.mongodb.org/browse/MONGOID-5658 for more details.
199199
option :around_callbacks_for_embeds, default: true
200200

201+
# When this flag is false, indexes are (roughly) validated on the client
202+
# to prevent duplicate indexes being declared. This validation is
203+
# incomplete, however, and can result in some indexes being silently
204+
# ignored.
205+
#
206+
# Setting this to true will allow duplicate indexes to be declared and sent
207+
# to the server. The server will then validate the indexes and raise an
208+
# exception if duplicates are detected.
209+
#
210+
# See https://jira.mongodb.org/browse/MONGOID-5827 for an example of the
211+
# consequences of duplicate index checking.
212+
option :allow_duplicate_index_declarations, default: false
213+
201214
# Returns the Config singleton, for use in the configure DSL.
202215
#
203216
# @return [ self ] The Config singleton.

lib/mongoid/indexable.rb

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

lib/mongoid/indexable/specification.rb

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

3553
# Instantiate a new index specification.

spec/mongoid/indexable_spec.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,5 +645,61 @@ def self.hereditary?
645645
end
646646
end
647647
end
648+
649+
context 'when declaring a duplicate index with different options' do
650+
def declare_duplicate_indexes!
651+
klass.index({ name: 1 }, { partial_filter_expression: { name: 'a' } })
652+
klass.index({ name: 1 }, { partial_filter_expression: { name: 'b' } })
653+
klass.create_indexes
654+
end
655+
656+
context 'when allow_duplicate_index_declarations is false' do
657+
config_override :allow_duplicate_index_declarations, false
658+
659+
it 'silently ignores the duplicate definition' do
660+
expect { declare_duplicate_indexes! }.not_to raise_exception
661+
end
662+
end
663+
664+
context 'when allow_duplicate_index_declarations is true' do
665+
config_override :allow_duplicate_index_declarations, true
666+
667+
it 'raises a server error' do
668+
expect { declare_duplicate_indexes! }.to raise_exception
669+
end
670+
end
671+
end
672+
673+
context 'when declaring a duplicate index with different names' do
674+
def declare_duplicate_indexes!
675+
klass.index({ name: 1 }, { partial_filter_expression: { name: 'a' } })
676+
klass.index({ name: 1 }, { name: 'alt_name', partial_filter_expression: { name: 'b' } })
677+
klass.create_indexes
678+
end
679+
680+
let(:index_count) { klass.collection.indexes.count }
681+
682+
683+
context 'when allow_duplicate_index_declarations is false' do
684+
config_override :allow_duplicate_index_declarations, false
685+
686+
it 'silently ignores the duplicate definition' do
687+
expect { declare_duplicate_indexes! }.not_to raise_exception
688+
expect(index_count).to be == 2 # _id and name
689+
end
690+
end
691+
692+
context 'when allow_duplicate_index_declarations is true' do
693+
# 4.4 apparently doesn't recognize :name option for indexes?
694+
min_server_version '5.0'
695+
696+
config_override :allow_duplicate_index_declarations, true
697+
698+
it 'creates both indexes' do
699+
expect { declare_duplicate_indexes! }.not_to raise_exception
700+
expect(index_count).to be == 3 # _id, name, alt_name
701+
end
702+
end
703+
end
648704
end
649705
end

0 commit comments

Comments
 (0)