Skip to content

Commit 02c1210

Browse files
RUBY-3332 Fix tailable cursors (mongodb#2793)
* RUBY-3332 Fix tailable cursors * Extract method * Fix code review remarks
1 parent 9c90702 commit 02c1210

File tree

3 files changed

+243
-0
lines changed

3 files changed

+243
-0
lines changed

lib/mongo/collection/view.rb

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def hash
127127
# return in each response from MongoDB.
128128
# @option options [ Hash ] :collation The collation to use.
129129
# @option options [ String ] :comment Associate a comment with the query.
130+
# @option options [ :tailable, :tailable_await ] :cursor_type The type of cursor to use.
130131
# @option options [ Hash ] :explain Execute an explain with the provided
131132
# explain options (known options are :verbose and :verbosity) rather
132133
# than a find.

lib/mongo/collection/view/iterable.rb

+15
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ def initial_query_op(session)
185185
collection.client.log_warn("The :oplog_replay option is deprecated and ignored by MongoDB 4.4 and later")
186186
end
187187

188+
maybe_set_tailable_options(spec)
189+
188190
if explained?
189191
spec[:explain] = options[:explain]
190192
Operation::Explain.new(spec)
@@ -200,6 +202,19 @@ def send_initial_query(server, session = nil)
200202
def use_query_cache?
201203
QueryCache.enabled? && !collection.system_collection?
202204
end
205+
206+
# Add tailable cusror options to the command specifiction if needed.
207+
#
208+
# @param [ Hash ] spec The command specification.
209+
def maybe_set_tailable_options(spec)
210+
case cursor_type
211+
when :tailable
212+
spec[:tailable] = true
213+
when :tailable_await
214+
spec[:tailable] = true
215+
spec[:await_data] = true
216+
end
217+
end
203218
end
204219
end
205220
end

spec/integration/find_options_spec.rb

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'Find operation options' do
6+
require_mri
7+
require_no_auth
8+
min_server_fcv '4.4'
9+
10+
let(:subscriber) { Mrss::EventSubscriber.new }
11+
12+
let(:seeds) do
13+
[ SpecConfig.instance.addresses.first ]
14+
end
15+
16+
let(:client_options) do
17+
{}
18+
end
19+
20+
let(:collection_options) do
21+
{}
22+
end
23+
24+
let(:client) do
25+
ClientRegistry.instance.new_local_client(
26+
seeds,
27+
SpecConfig.instance.test_options
28+
.merge(database: SpecConfig.instance.test_db)
29+
.merge(client_options)
30+
).tap do |client|
31+
client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
32+
end
33+
end
34+
35+
let(:collection) do
36+
client['find_options', collection_options]
37+
end
38+
39+
let(:find_command) do
40+
subscriber.started_events.find { |cmd| cmd.command_name == 'find' }
41+
end
42+
43+
let(:should_create_collection) { true }
44+
45+
before do
46+
client['find_options'].drop
47+
collection.create if should_create_collection
48+
collection.insert_many([ { a: 1 }, { a: 2 }, { a: 3 } ])
49+
end
50+
51+
describe 'collation' do
52+
let(:client_options) do
53+
{}
54+
end
55+
56+
let(:collation) do
57+
{ 'locale' => 'en_US' }
58+
end
59+
60+
context 'when defined on the collection' do
61+
let(:collection_options) do
62+
{ collation: collation }
63+
end
64+
65+
it 'uses the collation defined on the collection' do
66+
collection.find.to_a
67+
expect(find_command.command['collation']).to be_nil
68+
end
69+
end
70+
71+
context 'when defined on the operation' do
72+
let(:collection_options) do
73+
{}
74+
end
75+
76+
it 'uses the collation defined on the collection' do
77+
collection.find({}, collation: collation).to_a
78+
expect(find_command.command['collation']).to eq(collation)
79+
end
80+
end
81+
82+
context 'when defined on both collection and operation' do
83+
let(:collection_options) do
84+
{ 'locale' => 'de_AT' }
85+
end
86+
87+
let(:should_create_collection) { false }
88+
89+
it 'uses the collation defined on the collection' do
90+
collection.find({}, collation: collation).to_a
91+
expect(find_command.command['collation']).to eq(collation)
92+
end
93+
end
94+
end
95+
96+
describe 'read concern' do
97+
context 'when defined on the client' do
98+
let(:client_options) do
99+
{ read_concern: { level: :local } }
100+
end
101+
102+
let(:collection_options) do
103+
{}
104+
end
105+
106+
it 'uses the read concern defined on the client' do
107+
collection.find.to_a
108+
expect(find_command.command['readConcern']).to eq('level' => 'local')
109+
end
110+
111+
context 'when defined on the collection' do
112+
let(:collection_options) do
113+
{ read_concern: { level: :majority } }
114+
end
115+
116+
it 'uses the read concern defined on the collection' do
117+
collection.find.to_a
118+
expect(find_command.command['readConcern']).to eq('level' => 'majority')
119+
end
120+
121+
context 'when defined on the operation' do
122+
let(:operation_read_concern) do
123+
{ level: :available }
124+
end
125+
126+
it 'uses the read concern defined on the operation' do
127+
collection.find({}, read_concern: operation_read_concern).to_a
128+
expect(find_command.command['readConcern']).to eq('level' => 'available')
129+
end
130+
end
131+
end
132+
133+
context 'when defined on the operation' do
134+
let(:collection_options) do
135+
{}
136+
end
137+
138+
let(:operation_read_concern) do
139+
{ level: :available }
140+
end
141+
142+
it 'uses the read concern defined on the operation' do
143+
collection.find({}, read_concern: operation_read_concern).to_a
144+
expect(find_command.command['readConcern']).to eq('level' => 'available')
145+
end
146+
end
147+
end
148+
149+
context 'when defined on the collection' do
150+
let(:client_options) do
151+
{}
152+
end
153+
154+
let(:collection_options) do
155+
{ read_concern: { level: :majority } }
156+
end
157+
158+
it 'uses the read concern defined on the collection' do
159+
collection.find.to_a
160+
expect(find_command.command['readConcern']).to eq('level' => 'majority')
161+
end
162+
163+
context 'when defined on the operation' do
164+
let(:operation_read_concern) do
165+
{ level: :available }
166+
end
167+
168+
it 'uses the read concern defined on the operation' do
169+
collection.find({}, read_concern: operation_read_concern).to_a
170+
expect(find_command.command['readConcern']).to eq('level' => 'available')
171+
end
172+
end
173+
end
174+
end
175+
176+
describe 'read preference' do
177+
require_topology :replica_set
178+
179+
context 'when defined on the client' do
180+
let(:client_options) do
181+
{ read: { mode: :secondary } }
182+
end
183+
184+
let(:collection_options) do
185+
{}
186+
end
187+
188+
it 'uses the read preference defined on the client' do
189+
collection.find.to_a
190+
expect(find_command.command['$readPreference']).to eq('mode' => 'secondary')
191+
end
192+
193+
context 'when defined on the collection' do
194+
let(:collection_options) do
195+
{ read: { mode: :secondary_preferred } }
196+
end
197+
198+
it 'uses the read concern defined on the collection' do
199+
collection.find.to_a
200+
expect(find_command.command['$readPreference']).to eq('mode' => 'secondaryPreferred')
201+
end
202+
end
203+
end
204+
end
205+
206+
describe 'cursor type' do
207+
let(:collection_options) do
208+
{ capped: true, size: 1000 }
209+
end
210+
211+
context 'when cursor type is :tailable' do
212+
it 'sets the cursor type to tailable' do
213+
collection.find({}, cursor_type: :tailable).first
214+
expect(find_command.command['tailable']).to be true
215+
expect(find_command.command['awaitData']).to be_falsey
216+
end
217+
end
218+
219+
context 'when cursor type is :tailable_await' do
220+
it 'sets the cursor type to tailable' do
221+
collection.find({}, cursor_type: :tailable_await).first
222+
expect(find_command.command['tailable']).to be true
223+
expect(find_command.command['awaitData']).to be true
224+
end
225+
end
226+
end
227+
end

0 commit comments

Comments
 (0)