Skip to content

Commit

Permalink
RUBY-2520 Change estimatedDocumentCount() to use the $collStats Agg S…
Browse files Browse the repository at this point in the history
…tage Instead of Count Command
  • Loading branch information
p committed Mar 28, 2021
1 parent e819484 commit f61c735
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 112 deletions.
2 changes: 1 addition & 1 deletion .evergreen/config.yml
Expand Up @@ -928,7 +928,7 @@ buildvariants:
tasks:
- name: "test-mlaunch"

- matrix_name: "mongo-4.9"
- matrix_name: "mongo-4.9-api-version"
matrix_spec:
auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"]
ruby: "ruby-2.7"
Expand Down
2 changes: 1 addition & 1 deletion .evergreen/config/standard.yml.erb
Expand Up @@ -131,7 +131,7 @@ buildvariants:
tasks:
- name: "test-mlaunch"

- matrix_name: "mongo-4.9"
- matrix_name: "mongo-4.9-api-version"
matrix_spec:
auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"]
ruby: "ruby-2.7"
Expand Down
38 changes: 25 additions & 13 deletions lib/mongo/collection/view/readable.rb
Expand Up @@ -230,24 +230,36 @@ def estimated_document_count(opts = {})
raise ArgumentError, "Cannot call estimated_document_count when querying with a filter"
end

cmd = { count: collection.name }
cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
if read_concern
cmd[:readConcern] = Options::Mapper.transform_values_to_strings(
read_concern)
end
Mongo::Lint.validate_underscore_read_preference(opts[:read])
read_pref = opts[:read] || read_preference
selector = ServerSelector.get(read_pref || server_selector)
with_session(opts) do |session|
context = Operation::Context.new(client: client, session: session)
read_with_retry(session, selector) do |server|
Operation::Count.new(
selector: cmd,
db_name: database.name,
read: read_pref,
session: session,
).execute(server, context: Operation::Context.new(client: client, session: session))
end.n.to_i
if server.description.server_version_gte?('5.0')
pipeline = [
{'$collStats' => {'count' => {}}},
{'$group' => {'_id' => 1, 'n' => {'$sum' => '$count'}}},
]
spec = Builder::Aggregation.new(pipeline, self, options.merge(session: session)).specification
result = Operation::Aggregate.new(spec).execute(server, context: context)
result.documents.first.fetch('n')
else
cmd = { count: collection.name }
cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
if read_concern
cmd[:readConcern] = Options::Mapper.transform_values_to_strings(
read_concern)
end
result = Operation::Count.new(
selector: cmd,
db_name: database.name,
read: read_pref,
session: session,
).execute(server, context: context)
result.n.to_i
end
end
end
end

Expand Down
3 changes: 1 addition & 2 deletions lib/mongo/operation.rb
Expand Up @@ -6,6 +6,7 @@
require 'mongo/operation/shared/executable_no_validate'
require 'mongo/operation/shared/executable_transaction_label'
require 'mongo/operation/shared/polymorphic_lookup'
require 'mongo/operation/shared/polymorphic_operation'
require 'mongo/operation/shared/polymorphic_result'
require 'mongo/operation/shared/read_preference_supported'
require 'mongo/operation/shared/bypass_document_validation'
Expand All @@ -19,8 +20,6 @@
require 'mongo/operation/shared/object_id_generator'
require 'mongo/operation/shared/op_msg_or_command'
require 'mongo/operation/shared/op_msg_or_find_command'
require 'mongo/operation/shared/op_msg_or_list_indexes_command'
require 'mongo/operation/shared/collections_info_or_list_collections'

require 'mongo/operation/op_msg_base'
require 'mongo/operation/command'
Expand Down
19 changes: 18 additions & 1 deletion lib/mongo/operation/collections_info.rb
Expand Up @@ -25,7 +25,24 @@ module Operation
# @since 2.0.0
class CollectionsInfo
include Specifiable
include CollectionsInfoOrListCollections
include PolymorphicOperation
include PolymorphicLookup

private

def final_operation(connection)
op_class = if connection.features.list_collections_enabled?
if connection.features.op_msg_enabled?
ListCollections::OpMsg
else
ListCollections::Command
end
else
CollectionsInfo::Command
end

op_class.new(spec)
end
end
end
end
16 changes: 15 additions & 1 deletion lib/mongo/operation/indexes.rb
Expand Up @@ -27,7 +27,21 @@ module Operation
# @since 2.0.0
class Indexes
include Specifiable
include OpMsgOrListIndexesCommand
include PolymorphicOperation
include PolymorphicLookup

private

def final_operation(connection)
cls = if connection.features.op_msg_enabled?
polymorphic_class(self.class.name, :OpMsg)
elsif connection.features.list_indexes_enabled?
polymorphic_class(self.class.name, :Command)
else
polymorphic_class(self.class.name, :Legacy)
end
cls.new(spec)
end
end
end
end
8 changes: 1 addition & 7 deletions lib/mongo/operation/shared/op_msg_or_command.rb
Expand Up @@ -20,15 +20,9 @@ module Operation
#
# @api private
module OpMsgOrCommand
include PolymorphicOperation
include PolymorphicLookup

def execute(server, context:, options: {})
server.with_connection do |connection|
operation = final_operation(connection)
operation.execute(connection, context: context, options: options)
end
end

private

def final_operation(connection)
Expand Down
8 changes: 1 addition & 7 deletions lib/mongo/operation/shared/op_msg_or_find_command.rb
Expand Up @@ -21,15 +21,9 @@ module Operation
#
# @api private
module OpMsgOrFindCommand
include PolymorphicOperation
include PolymorphicLookup

def execute(server, context:)
server.with_connection do |connection|
operation = final_operation(connection)
operation.execute(connection, context: context)
end
end

private

def final_operation(connection)
Expand Down
47 changes: 0 additions & 47 deletions lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb

This file was deleted.

@@ -1,4 +1,4 @@
# Copyright (C) 2020 MongoDB Inc.
# Copyright (C) 2021 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,39 +15,25 @@
module Mongo
module Operation

# Shared behavior of implementing an operation differently based on
# the server that will be executing the operation.
#
# @api private
module CollectionsInfoOrListCollections
include PolymorphicLookup
module PolymorphicOperation

# Execute the operation.
#
# @param [ Mongo::Server ] server The server to send the operation to.
# @param [ Operation::Context ] context The operation context.
# @param [ Hash ] options Operation execution options.
#
# @return [ Mongo::Operation::CollectionsInfo::Result,
# Mongo::Operation::ListCollections::Result ] The operation result.
def execute(server, context:)
# @return [ Mongo::Operation::Result ] The operation result.
def execute(server, context:, options: {})
server.with_connection do |connection|
operation = final_operation(connection)
operation.execute(connection, context: context)
operation.execute(connection, context: context, options: options)
end
end

private

def final_operation(connection)
op_class = if connection.features.list_collections_enabled?
if connection.features.op_msg_enabled?
ListCollections::OpMsg
else
ListCollections::Command
end
else
CollectionsInfo::Command
end

op_class.new(spec)
end
end
end
end
4 changes: 4 additions & 0 deletions lib/mongo/server/description.rb
Expand Up @@ -790,6 +790,10 @@ def ==(other)
# @api private
def server_version_gte?(version)
required_wv = case version
when '5.0'
12
when '4.4'
9
when '4.2'
8
when '4.0'
Expand Down
17 changes: 9 additions & 8 deletions lib/mongo/server/description/features.rb
Expand Up @@ -23,14 +23,15 @@ class Features
# List of features and the wire protocol version they appear in.
#
# Wire protocol versions map to server releases as follows:
# - 2 => 2.6
# - 3 => 3.0
# - 4 => 3.2
# - 5 => 3.4
# - 6 => 3.6
# - 7 => 4.0
# - 8 => 4.2
# - 9 => 4.4
# - 2 => 2.6
# - 3 => 3.0
# - 4 => 3.2
# - 5 => 3.4
# - 6 => 3.6
# - 7 => 4.0
# - 8 => 4.2
# - 9 => 4.4
# - 12 => 5.0
#
# @since 2.0.0
MAPPINGS = {
Expand Down
@@ -0,0 +1,60 @@
runOn:
- minServerVersion: "4.9.0"

database_name: &database_name "retryable-reads-tests"
collection_name: &collection_name "coll"

data:
- { _id: 1, x: 11 }
- { _id: 2, x: 22 }

tests:
-
description: "EstimatedDocumentCount succeeds on first attempt"
operations:
- &retryable_operation_succeeds
<<: &retryable_operation
name: estimatedDocumentCount
object: collection
result: 2
expectations:
- &retryable_command_started_event
command_started_event:
command:
aggregate: *collection_name
pipeline: &pipeline
- $collStats: { count: {} }
- $group: { _id: 1, n: { $sum: $count }}
database_name: *database_name
-
description: "EstimatedDocumentCount succeeds on second attempt"
failPoint: &failCommand_failPoint
configureFailPoint: failCommand
mode: { times: 1 }
data:
failCommands: [aggregate]
closeConnection: true
operations: [*retryable_operation_succeeds]
expectations:
- *retryable_command_started_event
- *retryable_command_started_event
-
description: "EstimatedDocumentCount fails on first attempt"
clientOptions:
retryReads: false
failPoint: *failCommand_failPoint
operations:
- &retryable_operation_fails
<<: *retryable_operation
error: true
expectations:
- *retryable_command_started_event
-
description: "EstimatedDocumentCount fails on second attempt"
failPoint:
<<: *failCommand_failPoint
mode: { times: 2 }
operations: [*retryable_operation_fails]
expectations:
- *retryable_command_started_event
- *retryable_command_started_event
@@ -1,9 +1,11 @@
runOn:
-
minServerVersion: "4.0"
maxServerVersion: "4.8.99"
topology: ["single", "replicaset"]
-
minServerVersion: "4.1.7"
maxServerVersion: "4.8.99"
topology: ["sharded"]

database_name: &database_name "retryable-reads-tests"
Expand Down

0 comments on commit f61c735

Please sign in to comment.