Skip to content

Commit 95de4ad

Browse files
p-mongop
andauthored
RUBY-2520 Change estimatedDocumentCount() to use the $collStats Agg Stage Instead of Count Command (#2198)
Co-authored-by: Oleg Pudeyev <oleg@bsdpower.com>
1 parent e819484 commit 95de4ad

24 files changed

+672
-173
lines changed

.evergreen/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ buildvariants:
928928
tasks:
929929
- name: "test-mlaunch"
930930

931-
- matrix_name: "mongo-4.9"
931+
- matrix_name: "mongo-4.9-api-version"
932932
matrix_spec:
933933
auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"]
934934
ruby: "ruby-2.7"

.evergreen/config/standard.yml.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ buildvariants:
131131
tasks:
132132
- name: "test-mlaunch"
133133

134-
- matrix_name: "mongo-4.9"
134+
- matrix_name: "mongo-4.9-api-version"
135135
matrix_spec:
136136
auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"]
137137
ruby: "ruby-2.7"

lib/mongo/collection/view/readable.rb

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -230,24 +230,46 @@ def estimated_document_count(opts = {})
230230
raise ArgumentError, "Cannot call estimated_document_count when querying with a filter"
231231
end
232232

233-
cmd = { count: collection.name }
234-
cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
235-
if read_concern
236-
cmd[:readConcern] = Options::Mapper.transform_values_to_strings(
237-
read_concern)
238-
end
239233
Mongo::Lint.validate_underscore_read_preference(opts[:read])
240234
read_pref = opts[:read] || read_preference
241235
selector = ServerSelector.get(read_pref || server_selector)
242236
with_session(opts) do |session|
237+
context = Operation::Context.new(client: client, session: session)
243238
read_with_retry(session, selector) do |server|
244-
Operation::Count.new(
245-
selector: cmd,
246-
db_name: database.name,
247-
read: read_pref,
248-
session: session,
249-
).execute(server, context: Operation::Context.new(client: client, session: session))
250-
end.n.to_i
239+
if server.description.server_version_gte?('5.0')
240+
pipeline = [
241+
{'$collStats' => {'count' => {}}},
242+
{'$group' => {'_id' => 1, 'n' => {'$sum' => '$count'}}},
243+
]
244+
spec = Builder::Aggregation.new(pipeline, self, options.merge(session: session)).specification
245+
result = Operation::Aggregate.new(spec).execute(server, context: context)
246+
result.documents.first.fetch('n')
247+
else
248+
cmd = { count: collection.name }
249+
cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
250+
if read_concern
251+
cmd[:readConcern] = Options::Mapper.transform_values_to_strings(
252+
read_concern)
253+
end
254+
result = Operation::Count.new(
255+
selector: cmd,
256+
db_name: database.name,
257+
read: read_pref,
258+
session: session,
259+
).execute(server, context: context)
260+
result.n.to_i
261+
end
262+
end
263+
end
264+
rescue Error::OperationFailure => exc
265+
if exc.code == 26
266+
# NamespaceNotFound
267+
# This should only happen with the aggregation pipeline path
268+
# (server 4.9+). Previous servers should return 0 for nonexistent
269+
# collections.
270+
0
271+
else
272+
raise
251273
end
252274
end
253275

lib/mongo/operation.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'mongo/operation/shared/executable_no_validate'
77
require 'mongo/operation/shared/executable_transaction_label'
88
require 'mongo/operation/shared/polymorphic_lookup'
9+
require 'mongo/operation/shared/polymorphic_operation'
910
require 'mongo/operation/shared/polymorphic_result'
1011
require 'mongo/operation/shared/read_preference_supported'
1112
require 'mongo/operation/shared/bypass_document_validation'
@@ -19,8 +20,6 @@
1920
require 'mongo/operation/shared/object_id_generator'
2021
require 'mongo/operation/shared/op_msg_or_command'
2122
require 'mongo/operation/shared/op_msg_or_find_command'
22-
require 'mongo/operation/shared/op_msg_or_list_indexes_command'
23-
require 'mongo/operation/shared/collections_info_or_list_collections'
2423

2524
require 'mongo/operation/op_msg_base'
2625
require 'mongo/operation/command'

lib/mongo/operation/collections_info.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,24 @@ module Operation
2525
# @since 2.0.0
2626
class CollectionsInfo
2727
include Specifiable
28-
include CollectionsInfoOrListCollections
28+
include PolymorphicOperation
29+
include PolymorphicLookup
30+
31+
private
32+
33+
def final_operation(connection)
34+
op_class = if connection.features.list_collections_enabled?
35+
if connection.features.op_msg_enabled?
36+
ListCollections::OpMsg
37+
else
38+
ListCollections::Command
39+
end
40+
else
41+
CollectionsInfo::Command
42+
end
43+
44+
op_class.new(spec)
45+
end
2946
end
3047
end
3148
end

lib/mongo/operation/indexes.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,21 @@ module Operation
2727
# @since 2.0.0
2828
class Indexes
2929
include Specifiable
30-
include OpMsgOrListIndexesCommand
30+
include PolymorphicOperation
31+
include PolymorphicLookup
32+
33+
private
34+
35+
def final_operation(connection)
36+
cls = if connection.features.op_msg_enabled?
37+
polymorphic_class(self.class.name, :OpMsg)
38+
elsif connection.features.list_indexes_enabled?
39+
polymorphic_class(self.class.name, :Command)
40+
else
41+
polymorphic_class(self.class.name, :Legacy)
42+
end
43+
cls.new(spec)
44+
end
3145
end
3246
end
3347
end

lib/mongo/operation/shared/op_msg_or_command.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,9 @@ module Operation
2020
#
2121
# @api private
2222
module OpMsgOrCommand
23+
include PolymorphicOperation
2324
include PolymorphicLookup
2425

25-
def execute(server, context:, options: {})
26-
server.with_connection do |connection|
27-
operation = final_operation(connection)
28-
operation.execute(connection, context: context, options: options)
29-
end
30-
end
31-
3226
private
3327

3428
def final_operation(connection)

lib/mongo/operation/shared/op_msg_or_find_command.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,9 @@ module Operation
2121
#
2222
# @api private
2323
module OpMsgOrFindCommand
24+
include PolymorphicOperation
2425
include PolymorphicLookup
2526

26-
def execute(server, context:)
27-
server.with_connection do |connection|
28-
operation = final_operation(connection)
29-
operation.execute(connection, context: context)
30-
end
31-
end
32-
3327
private
3428

3529
def final_operation(connection)

lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb

Lines changed: 0 additions & 47 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2020 MongoDB Inc.
1+
# Copyright (C) 2021 MongoDB Inc.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -15,39 +15,25 @@
1515
module Mongo
1616
module Operation
1717

18+
# Shared behavior of implementing an operation differently based on
19+
# the server that will be executing the operation.
20+
#
1821
# @api private
19-
module CollectionsInfoOrListCollections
20-
include PolymorphicLookup
22+
module PolymorphicOperation
2123

2224
# Execute the operation.
2325
#
2426
# @param [ Mongo::Server ] server The server to send the operation to.
2527
# @param [ Operation::Context ] context The operation context.
28+
# @param [ Hash ] options Operation execution options.
2629
#
27-
# @return [ Mongo::Operation::CollectionsInfo::Result,
28-
# Mongo::Operation::ListCollections::Result ] The operation result.
29-
def execute(server, context:)
30+
# @return [ Mongo::Operation::Result ] The operation result.
31+
def execute(server, context:, options: {})
3032
server.with_connection do |connection|
3133
operation = final_operation(connection)
32-
operation.execute(connection, context: context)
34+
operation.execute(connection, context: context, options: options)
3335
end
3436
end
35-
36-
private
37-
38-
def final_operation(connection)
39-
op_class = if connection.features.list_collections_enabled?
40-
if connection.features.op_msg_enabled?
41-
ListCollections::OpMsg
42-
else
43-
ListCollections::Command
44-
end
45-
else
46-
CollectionsInfo::Command
47-
end
48-
49-
op_class.new(spec)
50-
end
5137
end
5238
end
5339
end

0 commit comments

Comments
 (0)