Skip to content

Commit a772112

Browse files
committed
Merge pull request #474 from estolfo/RUBY-790-count-hint
RUBY-790 Allow count to work with query hints
2 parents e064fa4 + 89a9d48 commit a772112

File tree

4 files changed

+131
-5
lines changed

4 files changed

+131
-5
lines changed

lib/mongo/collection.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,19 +1039,24 @@ def stats
10391039
# @option opts [Hash] :query ({}) A query selector for filtering the documents counted.
10401040
# @option opts [Integer] :skip (nil) The number of documents to skip.
10411041
# @option opts [Integer] :limit (nil) The number of documents to limit.
1042+
# @option opts [String, Array, OrderedHash] :hint hint for query optimizer, usually not necessary if
1043+
# using MongoDB > 1.1. This option is only supported with #count in server version > 2.6.
1044+
# @option opts [String] :named_hint for specifying a named index as a hint, will be overridden by :hint
1045+
# if :hint is also provided. This option is only supported with #count in server version > 2.6.
10421046
# @option opts [:primary, :secondary] :read Read preference for this command. See Collection#find for
10431047
# more details.
10441048
# @option opts [String] :comment (nil) a comment to include in profiling logs
10451049
#
10461050
# @return [Integer]
10471051
def count(opts={})
10481052
find(opts[:query],
1049-
:skip => opts[:skip],
1050-
:limit => opts[:limit],
1051-
:read => opts[:read],
1052-
:comment => opts[:comment]).count(true)
1053+
:skip => opts[:skip],
1054+
:limit => opts[:limit],
1055+
:named_hint => opts[:named_hint] || @hint,
1056+
:hint => opts[:hint] || @hint,
1057+
:read => opts[:read],
1058+
:comment => opts[:comment]).count(true)
10531059
end
1054-
10551060
alias :size :count
10561061

10571062
protected

lib/mongo/cursor.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,12 @@ def count(skip_and_limit = false)
203203
command.merge!(BSON::OrderedHash["skip", @skip]) if @skip != 0
204204
end
205205

206+
if @hint
207+
hint = @hint.is_a?(String) ? @hint : generate_index_name(@hint)
208+
end
209+
206210
command.merge!(BSON::OrderedHash["fields", @fields])
211+
command.merge!(BSON::OrderedHash["hint", hint]) if hint
207212

208213
response = @db.command(command, :read => @read, :comment => @comment)
209214
return response['n'].to_i if Mongo::Support.ok?(response)
@@ -715,5 +720,13 @@ def check_command_cursor
715720
def compile_regex?
716721
@compile_regex
717722
end
723+
724+
def generate_index_name(spec)
725+
indexes = []
726+
spec.each_pair do |field, type|
727+
indexes.push("#{field}_#{type}")
728+
end
729+
indexes.join("_")
730+
end
718731
end
719732
end

test/functional/collection_test.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,61 @@ def test_count
10081008
assert_equal 0, @test.count(:skip => 2)
10091009
end
10101010

1011+
def test_count_with_hint
1012+
@test.drop
1013+
@test.save(:i => 1)
1014+
@test.save(:i => 2)
1015+
assert_equal 2, @test.count
1016+
1017+
@test.ensure_index(BSON::OrderedHash[:i, Mongo::ASCENDING])
1018+
1019+
# Check that a named_hint can be specified
1020+
assert_equal 1, @test.count(:query => { :i => 1 }, :named_hint => '_id_')
1021+
assert_equal 2, @test.count(:query => { }, :named_hint => '_id_')
1022+
1023+
# Verify that the hint is being sent to the server by providing a bad hint
1024+
if @version > '2.6'
1025+
assert_raise Mongo::OperationFailure do
1026+
@test.count(:query => { :i => 1 }, :hint => 'bad_hint')
1027+
end
1028+
else
1029+
assert_equal 1, @test.count(:query => { :i => 1 }, :hint => 'bad_hint')
1030+
end
1031+
1032+
# Verify that the named_hint is being sent to the server by providing a bad hint
1033+
if @version > '2.6'
1034+
assert_raise Mongo::OperationFailure do
1035+
@test.count(:query => { :i => 1 }, :named_hint => 'bad_hint')
1036+
end
1037+
else
1038+
assert_equal 1, @test.count(:query => { :i => 1 }, :named_hint => 'bad_hint')
1039+
end
1040+
1041+
@test.ensure_index(BSON::OrderedHash[:x, Mongo::ASCENDING], :sparse => true)
1042+
1043+
# The sparse index won't have any entries.
1044+
# Check that count returns 0 when using the hint.
1045+
expected = @version > '2.6' ? 0 : 1
1046+
assert_equal expected, @test.count(:query => { :i => 1 }, :hint => { 'x' => 1 })
1047+
assert_equal expected, @test.count(:query => { :i => 1 }, :hint => 'x')
1048+
assert_equal expected, @test.count(:query => { :i => 1 }, :named_hint => 'x_1')
1049+
1050+
# Verify that the hint / named hint set on the collection is used.
1051+
@test.hint = { 'x' => 1 }
1052+
assert_equal expected, @test.count(:query => { :i => 1 })
1053+
1054+
@test.hint = 'x'
1055+
assert_equal expected, @test.count(:query => { :i => 1 })
1056+
1057+
# The driver should allow x_1, but the code sets named_hint to @hint without
1058+
# normalizing.
1059+
@test.named_hint = 'x'
1060+
assert_equal expected, @test.count(:query => { :i => 1 })
1061+
1062+
assert_equal 2, @test.count(:query => { }, :hint => 'x')
1063+
assert_equal 2, @test.count(:query => { }, :named_hint => 'x_1')
1064+
end
1065+
10111066
# Note: #size is just an alias for #count.
10121067
def test_size
10131068
@test.drop

test/functional/cursor_test.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,59 @@ def test_count_with_fields
535535
end
536536
end
537537

538+
def test_count_with_hint
539+
@coll.drop
540+
@coll.save(:i => 1)
541+
@coll.save(:i => 2)
542+
assert_equal 2, @coll.find.count
543+
544+
@coll.ensure_index(BSON::OrderedHash[:i, Mongo::ASCENDING])
545+
546+
# Check that a named_hint can be specified
547+
assert_equal 1, @coll.find({ :i => 1 }, :named_hint => '_id_').count
548+
assert_equal 2, @coll.find({ }, :named_hint => '_id_').count
549+
550+
# Verify that the hint is being sent to the server by providing a bad hint
551+
if @version > '2.6'
552+
assert_raise Mongo::OperationFailure do
553+
@coll.find({ :i => 1 }, :hint => 'bad_hint').count
554+
end
555+
else
556+
assert_equal 1, @coll.find({ :i => 1 }, :hint => 'bad_hint').count
557+
end
558+
559+
# Verify that the named_hint is being sent to the server by providing a bad hint
560+
if @version > '2.6'
561+
assert_raise Mongo::OperationFailure do
562+
@coll.find({ :i => 1 }, :named_hint => 'bad_hint').count
563+
end
564+
else
565+
assert_equal 1, @coll.find({ :i => 1 }, :named_hint => 'bad_hint').count
566+
end
567+
568+
@coll.ensure_index(BSON::OrderedHash[:x, Mongo::ASCENDING], :sparse => true)
569+
570+
# The sparse index won't have any entries.
571+
# Check that count returns 0 when using the hint.
572+
expected = @version > '2.6' ? 0 : 1
573+
assert_equal expected, @coll.find({ :i => 1 }, :hint => { 'x' => 1 }).count
574+
assert_equal expected, @coll.find({ :i => 1 }, :hint => 'x').count
575+
assert_equal expected, @coll.find({ :i => 1 }, :named_hint => 'x_1').count
576+
577+
# Verify that the hint / named hint set on the collection is used.
578+
@coll.hint = { 'x' => 1 }
579+
assert_equal expected, @coll.find(:i => 1).count
580+
581+
@coll.hint = 'x'
582+
assert_equal expected, @coll.find(:i => 1).count
583+
584+
@coll.named_hint = 'x_1'
585+
assert_equal expected, @coll.find(:i => 1).count
586+
587+
assert_equal 2, @coll.find({ }, :hint => 'x').count
588+
assert_equal 2, @coll.find({ }, :named_hint => 'x_1').count
589+
end
590+
538591
def test_has_next
539592
@coll.remove
540593
200.times do |n|

0 commit comments

Comments
 (0)