mongo 2.19.2 → 2.19.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/mongo/collection.rb +23 -1
- data/lib/mongo/operation/create_search_indexes/op_msg.rb +31 -0
- data/lib/mongo/operation/create_search_indexes.rb +15 -0
- data/lib/mongo/operation/drop_search_index/op_msg.rb +33 -0
- data/lib/mongo/operation/drop_search_index.rb +15 -0
- data/lib/mongo/operation/shared/specifiable.rb +7 -0
- data/lib/mongo/operation/update_search_index/op_msg.rb +34 -0
- data/lib/mongo/operation/update_search_index.rb +15 -0
- data/lib/mongo/operation.rb +3 -0
- data/lib/mongo/search_index/view.rb +232 -0
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo.rb +1 -0
- data/spec/atlas/atlas_connectivity_spec.rb +1 -5
- data/spec/atlas/operations_spec.rb +1 -5
- data/spec/integration/search_indexes_prose_spec.rb +168 -0
- data/spec/lite_spec_helper.rb +32 -10
- data/spec/runners/unified/search_index_operations.rb +63 -0
- data/spec/runners/unified/test.rb +3 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/spec_tests/data/index_management/createSearchIndex.yml +62 -0
- data/spec/spec_tests/data/index_management/createSearchIndexes.yml +83 -0
- data/spec/spec_tests/data/index_management/dropSearchIndex.yml +42 -0
- data/spec/spec_tests/data/index_management/listSearchIndexes.yml +85 -0
- data/spec/spec_tests/data/index_management/updateSearchIndex.yml +45 -0
- data/spec/spec_tests/index_management_unified_spec.rb +13 -0
- data/spec/support/faas/app/aws_lambda/mongodb/Gemfile.lock +19 -0
- data/spec/support/spec_config.rb +5 -0
- data.tar.gz.sig +0 -0
- metadata +1276 -1251
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0916a285c62c2e70c94532ca2ed61af6bfcb1ead971a2be825aefbde560209c3'
|
4
|
+
data.tar.gz: a9db987bb6f1e2b7e9882c86472bc2c8b10eca0d4bdca9de2a37968de086d8f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab6a8e7e008174f346f1acd96bb073a39cd4fbdd2a70e44f63f8dcdc421c4d38ba9277cc9c50099c944599886e53230597bdab8bd4c861de29af2bd66dd2be76
|
7
|
+
data.tar.gz: 8fe9155b17bbb9b57021eabd7e2211baf2cb0002b211900e8564e0b1c2f9e894bf726a037b1e8b72e6dd55251edefe1e81fd8c3ea61aacd0fd5d7eb76cbc4163
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/mongo/collection.rb
CHANGED
@@ -725,13 +725,35 @@ module Mongo
|
|
725
725
|
#
|
726
726
|
# @option options [ Session ] :session The session to use.
|
727
727
|
#
|
728
|
-
# @return [ View
|
728
|
+
# @return [ Index::View ] The index view.
|
729
729
|
#
|
730
730
|
# @since 2.0.0
|
731
731
|
def indexes(options = {})
|
732
732
|
Index::View.new(self, options)
|
733
733
|
end
|
734
734
|
|
735
|
+
# Get a view of all search indexes for this collection. Can be iterated or
|
736
|
+
# operated on directly. If id or name are given, the iterator will return
|
737
|
+
# only the indicated index. For all other operations, id and name are
|
738
|
+
# ignored.
|
739
|
+
#
|
740
|
+
# @note Only one of id or name may be given; it is an error to specify both,
|
741
|
+
# although both may be omitted safely.
|
742
|
+
#
|
743
|
+
# @param [ Hash ] options The options to use to configure the view.
|
744
|
+
#
|
745
|
+
# @option options [ String ] :id The id of the specific index to query (optional)
|
746
|
+
# @option options [ String ] :name The name of the specific index to query (optional)
|
747
|
+
# @option options [ Hash ] :aggregate The options hash to pass to the
|
748
|
+
# aggregate command (optional)
|
749
|
+
#
|
750
|
+
# @return [ SearchIndex::View ] The search index view.
|
751
|
+
#
|
752
|
+
# @since 2.0.0
|
753
|
+
def search_indexes(options = {})
|
754
|
+
SearchIndex::View.new(self, options)
|
755
|
+
end
|
756
|
+
|
735
757
|
# Get a pretty printed string inspection for the collection.
|
736
758
|
#
|
737
759
|
# @example Inspect the collection.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongo
|
4
|
+
module Operation
|
5
|
+
class CreateSearchIndexes
|
6
|
+
# A MongoDB createSearchIndexes operation sent as an op message.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class OpMsg < OpMsgBase
|
10
|
+
include ExecutableTransactionLabel
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Returns the command to send to the database, describing the
|
15
|
+
# desired createSearchIndexes operation.
|
16
|
+
#
|
17
|
+
# @param [ Mongo::Server ] _server the server that will receive the
|
18
|
+
# command
|
19
|
+
#
|
20
|
+
# @return [ Hash ] the selector
|
21
|
+
def selector(_server)
|
22
|
+
{
|
23
|
+
createSearchIndexes: coll_name,
|
24
|
+
:$db => db_name,
|
25
|
+
indexes: indexes,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mongo/operation/create_search_indexes/op_msg'
|
4
|
+
|
5
|
+
module Mongo
|
6
|
+
module Operation
|
7
|
+
# A MongoDB createSearchIndexes command operation.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class CreateSearchIndexes
|
11
|
+
include Specifiable
|
12
|
+
include OpMsgExecutable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongo
|
4
|
+
module Operation
|
5
|
+
class DropSearchIndex
|
6
|
+
# A MongoDB createSearchIndexes operation sent as an op message.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class OpMsg < OpMsgBase
|
10
|
+
include ExecutableTransactionLabel
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Returns the command to send to the database, describing the
|
15
|
+
# desired dropSearchIndex operation.
|
16
|
+
#
|
17
|
+
# @param [ Mongo::Server ] _server the server that will receive the
|
18
|
+
# command
|
19
|
+
#
|
20
|
+
# @return [ Hash ] the selector
|
21
|
+
def selector(_server)
|
22
|
+
{
|
23
|
+
dropSearchIndex: coll_name,
|
24
|
+
:$db => db_name,
|
25
|
+
}.tap do |sel|
|
26
|
+
sel[:id] = index_id if index_id
|
27
|
+
sel[:name] = index_name if index_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mongo/operation/drop_search_index/op_msg'
|
4
|
+
|
5
|
+
module Mongo
|
6
|
+
module Operation
|
7
|
+
# A MongoDB dropSearchIndex command operation.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class DropSearchIndex
|
11
|
+
include Specifiable
|
12
|
+
include OpMsgExecutable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -260,6 +260,13 @@ module Mongo
|
|
260
260
|
spec[INDEX]
|
261
261
|
end
|
262
262
|
|
263
|
+
# Get the index id from the spec.
|
264
|
+
#
|
265
|
+
# @return [ String ] The index id.
|
266
|
+
def index_id
|
267
|
+
spec[:index_id]
|
268
|
+
end
|
269
|
+
|
263
270
|
# Get the index name from the spec.
|
264
271
|
#
|
265
272
|
# @example Get the index name.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongo
|
4
|
+
module Operation
|
5
|
+
class UpdateSearchIndex
|
6
|
+
# A MongoDB updateSearchIndex operation sent as an op message.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class OpMsg < OpMsgBase
|
10
|
+
include ExecutableTransactionLabel
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Returns the command to send to the database, describing the
|
15
|
+
# desired updateSearchIndex operation.
|
16
|
+
#
|
17
|
+
# @param [ Mongo::Server ] _server the server that will receive the
|
18
|
+
# command
|
19
|
+
#
|
20
|
+
# @return [ Hash ] the selector
|
21
|
+
def selector(_server)
|
22
|
+
{
|
23
|
+
updateSearchIndex: coll_name,
|
24
|
+
:$db => db_name,
|
25
|
+
definition: index,
|
26
|
+
}.tap do |sel|
|
27
|
+
sel[:id] = index_id if index_id
|
28
|
+
sel[:name] = index_name if index_name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mongo/operation/update_search_index/op_msg'
|
4
|
+
|
5
|
+
module Mongo
|
6
|
+
module Operation
|
7
|
+
# A MongoDB updateSearchIndex command operation.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class UpdateSearchIndex
|
11
|
+
include Specifiable
|
12
|
+
include OpMsgExecutable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/mongo/operation.rb
CHANGED
@@ -51,6 +51,9 @@ require 'mongo/operation/update_user'
|
|
51
51
|
require 'mongo/operation/remove_user'
|
52
52
|
require 'mongo/operation/create_index'
|
53
53
|
require 'mongo/operation/drop_index'
|
54
|
+
require 'mongo/operation/create_search_indexes'
|
55
|
+
require 'mongo/operation/drop_search_index'
|
56
|
+
require 'mongo/operation/update_search_index'
|
54
57
|
|
55
58
|
module Mongo
|
56
59
|
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongo
|
4
|
+
module SearchIndex
|
5
|
+
# A class representing a view of search indexes.
|
6
|
+
class View
|
7
|
+
include Enumerable
|
8
|
+
include Retryable
|
9
|
+
include Collection::Helpers
|
10
|
+
|
11
|
+
# @return [ Mongo::Collection ] the collection this view belongs to
|
12
|
+
attr_reader :collection
|
13
|
+
|
14
|
+
# @return [ nil | String ] the index id to query
|
15
|
+
attr_reader :requested_index_id
|
16
|
+
|
17
|
+
# @return [ nil | String ] the index name to query
|
18
|
+
attr_reader :requested_index_name
|
19
|
+
|
20
|
+
# @return [ Hash ] the options hash to use for the aggregate command
|
21
|
+
# when querying the available indexes.
|
22
|
+
attr_reader :aggregate_options
|
23
|
+
|
24
|
+
# Create the new search index view.
|
25
|
+
#
|
26
|
+
# @param [ Collection ] collection The collection.
|
27
|
+
# @param [ Hash ] options The options that configure the behavior of the view.
|
28
|
+
#
|
29
|
+
# @option options [ String ] :id The specific index id to query (optional)
|
30
|
+
# @option options [ String ] :name The name of the specific index to query (optional)
|
31
|
+
# @option options [ Hash ] :aggregate The options hash to send to the
|
32
|
+
# aggregate command when querying the available indexes.
|
33
|
+
def initialize(collection, options = {})
|
34
|
+
@collection = collection
|
35
|
+
@requested_index_id = options[:id]
|
36
|
+
@requested_index_name = options[:name]
|
37
|
+
@aggregate_options = options[:aggregate] || {}
|
38
|
+
|
39
|
+
return if @aggregate_options.is_a?(Hash)
|
40
|
+
|
41
|
+
raise ArgumentError, "The :aggregate option must be a Hash (got a #{@aggregate_options.class})"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Create a single search index with the given definition. If the name is
|
45
|
+
# provided, the new index will be given that name.
|
46
|
+
#
|
47
|
+
# @param [ Hash ] definition The definition of the search index.
|
48
|
+
# @param [ nil | String ] name The name to give the new search index.
|
49
|
+
#
|
50
|
+
# @return [ String ] the name of the new search index.
|
51
|
+
def create_one(definition, name: nil)
|
52
|
+
create_many([ { name: name, definition: definition } ]).first
|
53
|
+
end
|
54
|
+
|
55
|
+
# Create multiple search indexes with a single command.
|
56
|
+
#
|
57
|
+
# @param [ Array<Hash> ] indexes The description of the indexes to
|
58
|
+
# create. Each element of the list must be a hash with a definition
|
59
|
+
# key, and an optional name key.
|
60
|
+
#
|
61
|
+
# @return [ Array<String> ] the names of the new search indexes.
|
62
|
+
def create_many(indexes)
|
63
|
+
spec = spec_with(indexes: indexes.map { |v| validate_search_index!(v) })
|
64
|
+
result = Operation::CreateSearchIndexes.new(spec).execute(next_primary, context: execution_context)
|
65
|
+
result.first['indexesCreated'].map { |idx| idx['name'] }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Drop the search index with the given id, or name. One or the other must
|
69
|
+
# be specified, but not both.
|
70
|
+
#
|
71
|
+
# @param [ String ] id the id of the index to drop
|
72
|
+
# @param [ String ] name the name of the index to drop
|
73
|
+
#
|
74
|
+
# @return [ Mongo::Operation::Result | false ] the result of the
|
75
|
+
# operation, or false if the given index does not exist.
|
76
|
+
def drop_one(id: nil, name: nil)
|
77
|
+
validate_id_or_name!(id, name)
|
78
|
+
|
79
|
+
spec = spec_with(index_id: id, index_name: name)
|
80
|
+
op = Operation::DropSearchIndex.new(spec)
|
81
|
+
|
82
|
+
# per the spec:
|
83
|
+
# Drivers MUST suppress NamespaceNotFound errors for the
|
84
|
+
# ``dropSearchIndex`` helper. Drop operations should be idempotent.
|
85
|
+
do_drop(op, nil, execution_context)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Iterate over the search indexes.
|
89
|
+
#
|
90
|
+
# @param [ Proc ] block if given, each search index will be yieleded to
|
91
|
+
# the block.
|
92
|
+
#
|
93
|
+
# @return [ self | Enumerator ] if a block is given, self is returned.
|
94
|
+
# Otherwise, an enumerator will be returned.
|
95
|
+
def each(&block)
|
96
|
+
@result ||= begin
|
97
|
+
spec = {}.tap do |s|
|
98
|
+
s[:id] = requested_index_id if requested_index_id
|
99
|
+
s[:name] = requested_index_name if requested_index_name
|
100
|
+
end
|
101
|
+
|
102
|
+
collection.aggregate(
|
103
|
+
[ { '$listSearchIndexes' => spec } ],
|
104
|
+
aggregate_options
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
return @result.to_enum unless block
|
109
|
+
|
110
|
+
@result.each(&block)
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# Update the search index with the given id or name. One or the other
|
115
|
+
# must be provided, but not both.
|
116
|
+
#
|
117
|
+
# @param [ Hash ] definition the definition to replace the given search
|
118
|
+
# index with.
|
119
|
+
# @param [ nil | String ] id the id of the search index to update
|
120
|
+
# @param [ nil | String ] name the name of the search index to update
|
121
|
+
#
|
122
|
+
# @return [ Mongo::Operation::Result ] the result of the operation
|
123
|
+
def update_one(definition, id: nil, name: nil)
|
124
|
+
validate_id_or_name!(id, name)
|
125
|
+
|
126
|
+
spec = spec_with(index_id: id, index_name: name, index: definition)
|
127
|
+
Operation::UpdateSearchIndex.new(spec).execute(next_primary, context: execution_context)
|
128
|
+
end
|
129
|
+
|
130
|
+
# The following methods are to make the view act more like an array,
|
131
|
+
# without having to explicitly make it an array...
|
132
|
+
|
133
|
+
# Queries whether the search index enumerable is empty.
|
134
|
+
#
|
135
|
+
# @return [ true | false ] whether the enumerable is empty or not.
|
136
|
+
def empty?
|
137
|
+
count.zero?
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# A helper method for building the specification document with certain
|
143
|
+
# values pre-populated.
|
144
|
+
#
|
145
|
+
# @param [ Hash ] extras the values to put into the specification
|
146
|
+
#
|
147
|
+
# @return [ Hash ] the specification document
|
148
|
+
def spec_with(extras)
|
149
|
+
{
|
150
|
+
coll_name: collection.name,
|
151
|
+
db_name: collection.database.name,
|
152
|
+
}.merge(extras)
|
153
|
+
end
|
154
|
+
|
155
|
+
# A helper method for retrieving the primary server from the cluster.
|
156
|
+
#
|
157
|
+
# @return [ Mongo::Server ] the server to use
|
158
|
+
def next_primary(ping = nil, session = nil)
|
159
|
+
collection.cluster.next_primary(ping, session)
|
160
|
+
end
|
161
|
+
|
162
|
+
# A helper method for constructing a new operation context for executing
|
163
|
+
# an operation.
|
164
|
+
#
|
165
|
+
# @return [ Mongo::Operation::Context ] the operation context
|
166
|
+
def execution_context
|
167
|
+
Operation::Context.new(client: collection.client)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Validates the given id and name, ensuring that exactly one of them
|
171
|
+
# is non-nil.
|
172
|
+
#
|
173
|
+
# @param [ nil | String ] id the id to validate
|
174
|
+
# @param [ nil | String ] name the name to validate
|
175
|
+
#
|
176
|
+
# @raise [ ArgumentError ] if neither or both arguments are nil
|
177
|
+
def validate_id_or_name!(id, name)
|
178
|
+
return unless (id.nil? && name.nil?) || (!id.nil? && !name.nil?)
|
179
|
+
|
180
|
+
raise ArgumentError, 'exactly one of id or name must be specified'
|
181
|
+
end
|
182
|
+
|
183
|
+
# Validates the given search index document, ensuring that it has no
|
184
|
+
# extra keys, and that the name and definition are valid.
|
185
|
+
#
|
186
|
+
# @param [ Hash ] doc the document to validate
|
187
|
+
#
|
188
|
+
# @raise [ ArgumentError ] if the document is invalid.
|
189
|
+
def validate_search_index!(doc)
|
190
|
+
validate_search_index_keys!(doc.keys)
|
191
|
+
validate_search_index_name!(doc[:name] || doc['name'])
|
192
|
+
validate_search_index_definition!(doc[:definition] || doc['definition'])
|
193
|
+
doc
|
194
|
+
end
|
195
|
+
|
196
|
+
# Validates the keys of a search index document, ensuring that
|
197
|
+
# they are all valid.
|
198
|
+
#
|
199
|
+
# @param [ Array<String | Hash> ] keys the keys of a search index document
|
200
|
+
#
|
201
|
+
# @raise [ ArgumentError ] if the list contains any invalid keys
|
202
|
+
def validate_search_index_keys!(keys)
|
203
|
+
extras = keys - [ 'name', 'definition', :name, :definition ]
|
204
|
+
|
205
|
+
raise ArgumentError, "invalid keys in search index creation: #{extras.inspect}" if extras.any?
|
206
|
+
end
|
207
|
+
|
208
|
+
# Validates the name of a search index, ensuring that it is either a
|
209
|
+
# String or nil.
|
210
|
+
#
|
211
|
+
# @param [ nil | String ] name the name of a search index
|
212
|
+
#
|
213
|
+
# @raise [ ArgumentError ] if the name is not valid
|
214
|
+
def validate_search_index_name!(name)
|
215
|
+
return if name.nil? || name.is_a?(String)
|
216
|
+
|
217
|
+
raise ArgumentError, "search index name must be nil or a string (got #{name.inspect})"
|
218
|
+
end
|
219
|
+
|
220
|
+
# Validates the definition of a search index.
|
221
|
+
#
|
222
|
+
# @param [ Hash ] definition the definition of a search index
|
223
|
+
#
|
224
|
+
# @raise [ ArgumentError ] if the definition is not valid
|
225
|
+
def validate_search_index_definition!(definition)
|
226
|
+
return if definition.is_a?(Hash)
|
227
|
+
|
228
|
+
raise ArgumentError, "search index definition must be a Hash (got #{definition.inspect})"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
data/lib/mongo/version.rb
CHANGED
data/lib/mongo.rb
CHANGED
@@ -7,11 +7,7 @@ describe 'Atlas connectivity' do
|
|
7
7
|
let(:uri) { ENV['ATLAS_URI'] }
|
8
8
|
let(:client) { Mongo::Client.new(uri) }
|
9
9
|
|
10
|
-
|
11
|
-
if uri.nil?
|
12
|
-
skip "ATLAS_URI not set in environment"
|
13
|
-
end
|
14
|
-
end
|
10
|
+
require_atlas
|
15
11
|
|
16
12
|
describe 'connection to Atlas' do
|
17
13
|
it 'runs ismaster successfully' do
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
class SearchIndexHelper
|
6
|
+
attr_reader :client, :collection_name
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@client = client
|
10
|
+
|
11
|
+
# https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id4
|
12
|
+
# "...each test uses a randomly generated collection name. Drivers may
|
13
|
+
# generate this collection name however they like, but a suggested
|
14
|
+
# implementation is a hex representation of an ObjectId..."
|
15
|
+
@collection_name = BSON::ObjectId.new.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
# `soft_create` means to create the collection object without forcing it to
|
19
|
+
# be created in the database.
|
20
|
+
def collection(soft_create: false)
|
21
|
+
@collection ||= client.database[collection_name].tap do |collection|
|
22
|
+
collection.create unless soft_create
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Wait for all of the indexes with the given names to be ready; then return
|
27
|
+
# the list of index definitions corresponding to those names.
|
28
|
+
def wait_for(*names, &condition)
|
29
|
+
timeboxed_wait do
|
30
|
+
result = collection.search_indexes
|
31
|
+
return filter_results(result, names) if names.all? { |name| ready?(result, name, &condition) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Wait until all of the indexes with the given names are absent from the
|
36
|
+
# search index list.
|
37
|
+
def wait_for_absense_of(*names)
|
38
|
+
names.each do |name|
|
39
|
+
timeboxed_wait do
|
40
|
+
break if collection.search_indexes(name: name).empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def timeboxed_wait(step: 5, max: 300)
|
48
|
+
start = Mongo::Utils.monotonic_time
|
49
|
+
|
50
|
+
loop do
|
51
|
+
yield
|
52
|
+
|
53
|
+
sleep step
|
54
|
+
raise Timeout::Error, 'wait took too long' if Mongo::Utils.monotonic_time - start > max
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns true if the list of search indexes includes one with the given name,
|
59
|
+
# which is ready to be queried.
|
60
|
+
def ready?(list, name, &condition)
|
61
|
+
condition ||= ->(index) { index['queryable'] }
|
62
|
+
list.any? { |index| index['name'] == name && condition[index] }
|
63
|
+
end
|
64
|
+
|
65
|
+
def filter_results(result, names)
|
66
|
+
result.select { |index| names.include?(index['name']) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'Mongo::Collection#search_indexes prose tests' do
|
71
|
+
# https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id5
|
72
|
+
# "These tests must run against an Atlas cluster with a 7.0+ server."
|
73
|
+
require_atlas
|
74
|
+
|
75
|
+
let(:client) do
|
76
|
+
Mongo::Client.new(
|
77
|
+
ENV['ATLAS_URI'],
|
78
|
+
database: SpecConfig.instance.test_db,
|
79
|
+
ssl: true,
|
80
|
+
ssl_verify: true
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
let(:helper) { SearchIndexHelper.new(client) }
|
85
|
+
|
86
|
+
let(:name) { 'test-search-index' }
|
87
|
+
let(:definition) { { 'mappings' => { 'dynamic' => false } } }
|
88
|
+
let(:create_index) { helper.collection.search_indexes.create_one(definition, name: name) }
|
89
|
+
|
90
|
+
# Case 1: Driver can successfully create and list search indexes
|
91
|
+
context 'when creating and listing search indexes' do
|
92
|
+
let(:index) { helper.wait_for(name).first }
|
93
|
+
|
94
|
+
it 'succeeds' do
|
95
|
+
expect(create_index).to be == name
|
96
|
+
expect(index['latestDefinition']).to be == definition
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Case 2: Driver can successfully create multiple indexes in batch
|
101
|
+
context 'when creating multiple indexes in batch' do
|
102
|
+
let(:specs) do
|
103
|
+
[
|
104
|
+
{ 'name' => 'test-search-index-1', 'definition' => definition },
|
105
|
+
{ 'name' => 'test-search-index-2', 'definition' => definition }
|
106
|
+
]
|
107
|
+
end
|
108
|
+
|
109
|
+
let(:names) { specs.map { |spec| spec['name'] } }
|
110
|
+
let(:create_indexes) { helper.collection.search_indexes.create_many(specs) }
|
111
|
+
|
112
|
+
let(:indexes) { helper.wait_for(*names) }
|
113
|
+
|
114
|
+
let(:index1) { indexes[0] }
|
115
|
+
let(:index2) { indexes[1] }
|
116
|
+
|
117
|
+
it 'succeeds' do
|
118
|
+
expect(create_indexes).to be == names
|
119
|
+
expect(index1['latestDefinition']).to be == specs[0]['definition']
|
120
|
+
expect(index2['latestDefinition']).to be == specs[1]['definition']
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Case 3: Driver can successfully drop search indexes
|
125
|
+
context 'when dropping search indexes' do
|
126
|
+
it 'succeeds' do
|
127
|
+
expect(create_index).to be == name
|
128
|
+
helper.wait_for(name)
|
129
|
+
|
130
|
+
helper.collection.search_indexes.drop_one(name: name)
|
131
|
+
|
132
|
+
expect { helper.wait_for_absense_of(name) }.not_to raise_error
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Case 4: Driver can update a search index
|
137
|
+
context 'when updating search indexes' do
|
138
|
+
let(:new_definition) { { 'mappings' => { 'dynamic' => true } } }
|
139
|
+
|
140
|
+
let(:index) do
|
141
|
+
helper
|
142
|
+
.wait_for(name) { |idx| idx['queryable'] && idx['status'] == 'READY' }
|
143
|
+
.first
|
144
|
+
end
|
145
|
+
|
146
|
+
# rubocop:disable RSpec/ExampleLength
|
147
|
+
it 'succeeds' do
|
148
|
+
expect(create_index).to be == name
|
149
|
+
helper.wait_for(name)
|
150
|
+
|
151
|
+
expect do
|
152
|
+
helper.collection.search_indexes.update_one(new_definition, name: name)
|
153
|
+
end.not_to raise_error
|
154
|
+
|
155
|
+
expect(index['latestDefinition']).to be == new_definition
|
156
|
+
end
|
157
|
+
# rubocop:enable RSpec/ExampleLength
|
158
|
+
end
|
159
|
+
|
160
|
+
# Case 5: dropSearchIndex suppresses namespace not found errors
|
161
|
+
context 'when dropping a non-existent search index' do
|
162
|
+
it 'ignores `namespace not found` errors' do
|
163
|
+
collection = helper.collection(soft_create: true)
|
164
|
+
expect { collection.search_indexes.drop_one(name: name) }
|
165
|
+
.not_to raise_error
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|