Skip to content

Commit

Permalink
Merge remote-tracking branch 'remotes/mongodb/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyshields committed Sep 2, 2023
2 parents 8b1829a + 30f46d4 commit 1d54ed6
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 121 deletions.
49 changes: 49 additions & 0 deletions docs/reference/configuration.txt
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,55 @@ For more information about TLS context hooks, including best practices for
assigning and removing them, see `the Ruby driver documentation <https://mongodb.com/docs/ruby-driver/current/reference/create-client/#modifying-sslcontext>`_.


Network Compression
===================

Mongoid supports compression of messages to and from MongoDB servers. This functionality is provided by
the Ruby driver, which implements the three algorithms that are supported by MongoDB servers:

- `Snappy <https://google.github.io/snappy/>`_: ``snappy`` compression
can be used when connecting to MongoDB servers starting with the 3.4 release,
and requires the `snappy <https://rubygems.org/gems/snappy>`_ library to be
installed.
- `Zlib <https://zlib.net/>`_: ``zlib`` compression can be used when
connecting to MongoDB servers starting with the 3.6 release.
- `Zstandard <https://facebook.github.io/zstd/>`_: ``zstd`` compression can be
used when connecting to MongoDB servers starting with the 4.2 release, and
requires the `zstd-ruby <https://rubygems.org/gems/zstd-ruby>`_ library to
be installed.

To use wire protocol compression, at least one compressor must be explicitly requested
using either the `compressors URI option <https://www.mongodb.com/docs/manual/reference/connection-string/#mongodb-urioption-urioption.compressors>`_,
or directly within the ``mongoid.yml``:

.. code-block:: yaml

development:
# Configure available database clients. (required)
clients:
# Define the default client. (required)
default:
# ...
options:
# These options are Ruby driver options, documented in
# https://mongodb.com/docs/ruby-driver/current/reference/create-client/
# ...
# Compressors to use. (default is to not use compression)
# Valid values are zstd, zlib or snappy - or any combination of the three
compressors: ["zstd", "snappy"]

If no compressors are explicitly requested, the driver will not use compression,
even if the required dependencies for one or more compressors are present on the
system.

The driver chooses the first compressor of the ones requested that is also supported
by the server. The ``zstd`` compressor is recommended as it produces the highest
compression at the same CPU consumption compared to the other compressors.

For maximum server compatibility all three compressors can be specified, e.g.
as ``compressors: ["zstd", "snappy", "zlib"]``.


Client-Side Encryption
======================

Expand Down
3 changes: 2 additions & 1 deletion lib/mongoid/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'active_model/attribute_methods'
require 'mongoid/attributes/dynamic'
require 'mongoid/attributes/embedded'
require 'mongoid/attributes/nested'
require 'mongoid/attributes/processing'
require 'mongoid/attributes/projector'
Expand Down Expand Up @@ -294,7 +295,7 @@ def read_raw_attribute(name)
if fields.key?(normalized)
attributes[normalized]
else
attributes.__nested__(normalized)
Embedded.traverse(attributes, normalized)
end
else
attributes[normalized]
Expand Down
34 changes: 34 additions & 0 deletions lib/mongoid/attributes/embedded.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module Mongoid
module Attributes
# Utility module for working with embedded attributes.
module Embedded
extend self

# Fetch an embedded value or subset of attributes via dot notation.
#
# @example Fetch an embedded value via dot notation.
# Embedded.traverse({ 'name' => { 'en' => 'test' } }, 'name.en')
# #=> 'test'
#
# @param [ Hash ] attributes The document attributes.
# @param [ String ] path The dot notation string.
#
# @return [ Object | nil ] The attributes at the given path,
# or nil if the path doesn't exist.
def traverse(attributes, path)
path.split('.').each do |key|
break if attributes.nil?

attributes = if attributes.try(:key?, key)
attributes[key]
elsif attributes.respond_to?(:each) && key.match?(/\A\d+\z/)
attributes[key.to_i]
end
end
attributes
end
end
end
end
2 changes: 1 addition & 1 deletion lib/mongoid/attributes/nested.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Mongoid
module Attributes

# Defines behavior around that lovel Rails feature nested attributes.
# Defines behavior for the Rails nested attributes feature.
module Nested
extend ActiveSupport::Concern

Expand Down
24 changes: 23 additions & 1 deletion lib/mongoid/contextual/mongo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ class Mongo
def count(options = {}, &block)
return super(&block) if block

view.count_documents(options)
if valid_for_count_documents?
view.count_documents(options)
else
view.count(options)
end
end

# Get the estimated number of documents matching the query.
Expand Down Expand Up @@ -963,6 +967,24 @@ def process_raw_docs(raw_docs, limit)
limit ? docs : docs.first
end

# Queries whether the current context is valid for use with
# the #count_documents? predicate. A context is valid if it
# does not include a `$where` operator.
#
# @return [ true | false ] whether or not the current context
# excludes a `$where` operator.
def valid_for_count_documents?(hash = view.filter)
# Note that `view.filter` is a BSON::Document, and all keys in a
# BSON::Document are strings; we don't need to worry about symbol
# representations of `$where`.
hash.keys.each do |key|
return false if key == '$where'
return false if hash[key].is_a?(Hash) && !valid_for_count_documents?(hash[key])
end

true
end

def raise_document_not_found_error
raise Errors::DocumentNotFound.new(klass, nil, nil)
end
Expand Down
23 changes: 0 additions & 23 deletions lib/mongoid/extensions/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,29 +122,6 @@ def extract_id
self['_id'] || self[:_id] || self['id'] || self[:id]
end

# Fetch a nested value via dot syntax.
#
# @example Fetch a nested value via dot syntax.
# { "name" => { "en" => "test" }}.__nested__("name.en")
#
# @param [ String ] string the dot syntax string.
#
# @return [ Object ] The matching value.
def __nested__(string)
keys = string.split('.')
value = self
keys.each do |key|
return nil if value.nil?

value_for_key = value[key]
if value_for_key.nil? && key.to_i.to_s == key
value_for_key = value[key.to_i]
end
value = value_for_key
end
value
end

# Turn the object from the ruby type we deal with to a Mongo friendly
# type.
#
Expand Down
19 changes: 3 additions & 16 deletions lib/mongoid/reloadable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,10 @@ def reload_root_document
#
# @return [ Hash ] The reloaded attributes.
def reload_embedded_document
extract_embedded_attributes(
collection(_root).find(_root.atomic_selector, session: _session).read(mode: :primary).first
Mongoid::Attributes::Embedded.traverse(
collection(_root).find(_root.atomic_selector, session: _session).read(mode: :primary).first,
atomic_position
)
end

# Extract only the desired embedded document from the attributes.
#
# @example Extract the embedded document.
# document.extract_embedded_attributes(attributes)
#
# @param [ Hash ] attributes The document in the db.
#
# @return [ Hash | nil ] The document's extracted attributes or nil if the
# document doesn't exist.
def extract_embedded_attributes(attributes)
segments = atomic_position.split('.').map { |part| Utils.maybe_integer(part) }
attributes.dig(*segments)
end
end
end
14 changes: 0 additions & 14 deletions lib/mongoid/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,6 @@ def placeholder?(value)
value == PLACEHOLDER
end

# If value can be coerced to an integer, return it as an integer.
# Otherwise, return the value itself.
#
# @param [ String ] value the string to possibly coerce.
#
# @return [ String | Integer ] the result of the coercion.
def maybe_integer(value)
if value.match?(/^\d/)
value.to_i
else
value
end
end

# This function should be used if you need to measure time.
# @example Calculate elapsed time.
# starting = Utils.monotonic_time
Expand Down
118 changes: 118 additions & 0 deletions spec/mongoid/attributes/embedded_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# frozen_string_literal: true

require 'spec_helper'

describe Mongoid::Attributes::Embedded do
describe '.traverse' do
subject(:embedded) { described_class.traverse(attributes, path) }

let(:path) { '100.name' }

context 'when the attribute key is a string' do
let(:attributes) { { '100' => { 'name' => 'hundred' } } }

it 'retrieves an embedded value under the provided key' do
expect(embedded).to eq 'hundred'
end

context 'when the value is false' do
let(:attributes) { { '100' => { 'name' => false } } }

it 'retrieves the embedded value under the provided key' do
expect(embedded).to be false
end
end

context 'when the value does not exist' do
let(:attributes) { { '100' => { 0 => 'Please do not return this value!' } } }

it 'returns nil' do
expect(embedded).to be_nil
end
end
end

context 'when the attribute key is an integer' do
let(:attributes) { { 100 => { 'name' => 'hundred' } } }

it 'retrieves an embedded value under the provided key' do
expect(embedded).to eq 'hundred'
end
end

context 'when the attribute value is nil' do
let(:attributes) { { 100 => { 'name' => nil } } }

it 'returns nil' do
expect(embedded).to be_nil
end
end

context 'when both string and integer keys are present' do
let(:attributes) { { '100' => { 'name' => 'Fred' }, 100 => { 'name' => 'Daphne' } } }

it 'returns the string key value' do
expect(embedded).to eq 'Fred'
end

context 'when the string key value is nil' do
let(:attributes) { { '100' => nil, 100 => { 'name' => 'Daphne' } } }

it 'returns nil' do
expect(embedded).to be_nil
end
end
end

context 'when attributes is an array' do
let(:attributes) do
[ { 'name' => 'Fred' }, { 'name' => 'Daphne' }, { 'name' => 'Velma' }, { 'name' => 'Shaggy' } ]
end
let(:path) { '2.name' }

it 'retrieves the nth value' do
expect(embedded).to eq 'Velma'
end

context 'when the member does not exist' do
let(:attributes) { [ { 'name' => 'Fred' }, { 'name' => 'Daphne' } ] }

it 'returns nil' do
expect(embedded).to be_nil
end
end
end

context 'when the path includes a scalar value' do
let(:attributes) { { '100' => 'name' } }

it 'returns nil' do
expect(embedded).to be_nil
end
end

context 'when the parent key is not present' do
let(:attributes) { { '101' => { 'name' => 'hundred and one' } } }

it 'returns nil' do
expect(embedded).to be_nil
end
end

context 'when the attributes are deeply nested' do
let(:attributes) { { '100' => { 'name' => { 300 => %w[a b c] } } } }

it 'retrieves the embedded subset of attributes' do
expect(embedded).to eq(300 => %w[a b c])
end

context 'when the path is deeply nested' do
let(:path) { '100.name.300.1' }

it 'retrieves the embedded value' do
expect(embedded).to eq 'b'
end
end
end
end
end
10 changes: 10 additions & 0 deletions spec/mongoid/contextual/mongo_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@
end
end
end

context 'when for_js is present' do
let(:context) do
Band.for_js('this.name == "Depeche Mode"')
end

it 'counts the expected records' do
expect(context.count).to eq(1)
end
end
end

describe '#estimated_count' do
Expand Down
Loading

0 comments on commit 1d54ed6

Please sign in to comment.