mongoid 9.0.11 → 9.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/config/locales/en.yml +45 -0
- data/lib/mongoid/association/depending.rb +8 -4
- data/lib/mongoid/association/nested/many.rb +34 -5
- data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
- data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
- data/lib/mongoid/association/relatable.rb +17 -0
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/encryption.rb +36 -18
- data/lib/mongoid/config.rb +56 -9
- data/lib/mongoid/contextual/aggregable/memory.rb +5 -2
- data/lib/mongoid/contextual/memory.rb +18 -7
- data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
- data/lib/mongoid/criteria/queryable/selectable.rb +109 -0
- data/lib/mongoid/encryptable.rb +46 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors/no_encryption_schema.rb +28 -0
- data/lib/mongoid/errors.rb +2 -0
- data/lib/mongoid/field_readable.rb +70 -0
- data/lib/mongoid/indexable.rb +39 -27
- data/lib/mongoid/matchable.rb +6 -1
- data/lib/mongoid/matcher/eq_impl_with_regexp.rb +4 -9
- data/lib/mongoid/matcher/regex.rb +9 -13
- data/lib/mongoid/matcher/regexp_budget.rb +383 -0
- data/lib/mongoid/matcher.rb +1 -0
- data/lib/mongoid/persistence_context.rb +35 -0
- data/lib/mongoid/search_indexable.rb +14 -7
- data/lib/mongoid/tasks/database.rb +23 -9
- data/lib/mongoid/threaded.rb +37 -0
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/app_spec.rb +7 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +149 -0
- data/spec/integration/matcher_operator_data/regex.yml +21 -0
- data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
- data/spec/integration/query_operator_guard_spec.rb +89 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
- data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
- data/spec/mongoid/attributes/nested_spec.rb +204 -10
- data/spec/mongoid/config/defaults_spec.rb +18 -0
- data/spec/mongoid/config/encryption_spec.rb +228 -0
- data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
- data/spec/mongoid/contextual/memory_spec.rb +177 -0
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +2 -0
- data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +200 -0
- data/spec/mongoid/criteria_spec.rb +2 -0
- data/spec/mongoid/encryptable_spec.rb +61 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/tasks/database_spec.rb +18 -0
- data/spec/support/crypt/models.rb +157 -0
- metadata +14 -2
|
@@ -823,6 +823,12 @@ module Mongoid
|
|
|
823
823
|
# only ever specify one criterion to #where.
|
|
824
824
|
@criterion = criterion
|
|
825
825
|
if criterion.is_a?(String)
|
|
826
|
+
unless Mongoid.allow_unsafe_query_operators?
|
|
827
|
+
raise Errors::InvalidQuery,
|
|
828
|
+
"String criteria are not allowed because they compile to the '$where' operator, " \
|
|
829
|
+
'which is not allowed in a query expression. Set Mongoid.allow_unsafe_query_operators = true ' \
|
|
830
|
+
'to permit all operators.'
|
|
831
|
+
end
|
|
826
832
|
js_query(criterion)
|
|
827
833
|
else
|
|
828
834
|
expr_query(criterion)
|
|
@@ -832,6 +838,19 @@ module Mongoid
|
|
|
832
838
|
|
|
833
839
|
private
|
|
834
840
|
|
|
841
|
+
# Operators permitted at the top level of a query expression without
|
|
842
|
+
# opt-in. Excludes $where (JS execution) and other operators not needed
|
|
843
|
+
# for ordinary application queries.
|
|
844
|
+
ALLOWED_QUERY_OPERATORS = %w[
|
|
845
|
+
$and $or $nor $not $text $comment $expr $jsonSchema $alwaysFalse $alwaysTrue
|
|
846
|
+
].freeze
|
|
847
|
+
|
|
848
|
+
# Operators that execute server-side JavaScript. These are rejected at
|
|
849
|
+
# any depth, not just at the top level: the allowlist above permits
|
|
850
|
+
# $expr and the logical operators, and their values are arbitrary
|
|
851
|
+
# nested expressions that can carry $function or $where.
|
|
852
|
+
JAVASCRIPT_QUERY_OPERATORS = %w[$where $function $accumulator].freeze
|
|
853
|
+
|
|
835
854
|
# Adds the specified expression to the query.
|
|
836
855
|
#
|
|
837
856
|
# Criterion must be a hash in one of the following forms:
|
|
@@ -857,6 +876,8 @@ module Mongoid
|
|
|
857
876
|
raise Errors::InvalidQuery, "Expression must be a Hash: #{Errors::InvalidQuery.truncate_expr(criterion)}"
|
|
858
877
|
end
|
|
859
878
|
|
|
879
|
+
# The operator guard is applied by _mongoid_expand_keys, which every
|
|
880
|
+
# query method that accepts a user-supplied expression passes through.
|
|
860
881
|
normalized = _mongoid_expand_keys(criterion)
|
|
861
882
|
clone.tap do |query|
|
|
862
883
|
normalized.each do |field, value|
|
|
@@ -872,6 +893,94 @@ module Mongoid
|
|
|
872
893
|
end
|
|
873
894
|
end
|
|
874
895
|
|
|
896
|
+
# Enforces the operator rules governed by the
|
|
897
|
+
# +allow_unsafe_query_operators+ configuration option against a
|
|
898
|
+
# normalized query expression.
|
|
899
|
+
#
|
|
900
|
+
# Two rules apply, and both are skipped when the option is true:
|
|
901
|
+
#
|
|
902
|
+
# - An operator at the top level of the expression must appear in
|
|
903
|
+
# ALLOWED_QUERY_OPERATORS.
|
|
904
|
+
# - An operator in JAVASCRIPT_QUERY_OPERATORS is rejected at any depth.
|
|
905
|
+
#
|
|
906
|
+
# This is called from #_mongoid_expand_keys rather than from the
|
|
907
|
+
# individual query methods, because that is the one point every query
|
|
908
|
+
# method taking a user-supplied expression passes through on its way to
|
|
909
|
+
# the selector.
|
|
910
|
+
#
|
|
911
|
+
# It deliberately does not cover the APIs that ask for JavaScript
|
|
912
|
+
# outright, such as #js_query and Criteria#for_js: there the developer
|
|
913
|
+
# has chosen server-side JavaScript, so there is nothing to guard
|
|
914
|
+
# against. The same goes for the low-level Storable methods
|
|
915
|
+
# (#add_field_expression, #add_operator_expression), which write to the
|
|
916
|
+
# selector directly.
|
|
917
|
+
#
|
|
918
|
+
# @param [ Hash ] expr A normalized query expression.
|
|
919
|
+
#
|
|
920
|
+
# @raise [ Errors::InvalidQuery ] If a disallowed operator is present.
|
|
921
|
+
#
|
|
922
|
+
# @api private
|
|
923
|
+
def _mongoid_validate_operators!(expr)
|
|
924
|
+
return if Mongoid.allow_unsafe_query_operators?
|
|
925
|
+
|
|
926
|
+
expr.each_key do |field|
|
|
927
|
+
field_s = field.to_s
|
|
928
|
+
next unless field_s.start_with?('$')
|
|
929
|
+
next if ALLOWED_QUERY_OPERATORS.include?(field_s)
|
|
930
|
+
|
|
931
|
+
raise Errors::InvalidQuery,
|
|
932
|
+
"Operator '#{field_s}' is not allowed in a query expression. " \
|
|
933
|
+
'Set Mongoid.allow_unsafe_query_operators = true to permit all operators.'
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
_mongoid_validate_no_javascript!(expr)
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
# Walks a query expression looking for operators that execute
|
|
940
|
+
# server-side JavaScript, descending through both hashes and arrays so
|
|
941
|
+
# that nested forms such as {'$expr' => {'$function' => ...}} and
|
|
942
|
+
# {'$or' => [ {'$where' => ...} ]} are caught.
|
|
943
|
+
#
|
|
944
|
+
# The walk does not distinguish operator position from value position,
|
|
945
|
+
# so it also rejects queries where a JavaScript operator name appears as
|
|
946
|
+
# data rather than as an operator. Server 5.0+ permits $-prefixed field
|
|
947
|
+
# names in stored documents, which makes this reachable:
|
|
948
|
+
#
|
|
949
|
+
# Doc.where(payload: { '$eq' => { '$function' => 'abc' } })
|
|
950
|
+
#
|
|
951
|
+
# Here the $eq marks its argument as a literal value to compare, so the
|
|
952
|
+
# server never evaluates it, but the guard raises anyway. The only
|
|
953
|
+
# workaround today is the global allow_unsafe_query_operators flag.
|
|
954
|
+
#
|
|
955
|
+
# TODO: discuss whether to track operator position (skipping the subtree
|
|
956
|
+
# under $eq, $ne, $in, $nin, and $elemMatch values) in a future
|
|
957
|
+
# iteration. It removes the false positive but adds exactly the kind of
|
|
958
|
+
# state that a real bypass could hide in, so it was left out for now.
|
|
959
|
+
#
|
|
960
|
+
# @param [ Object ] object A fragment of a query expression.
|
|
961
|
+
#
|
|
962
|
+
# @raise [ Errors::InvalidQuery ] If a JavaScript operator is present.
|
|
963
|
+
#
|
|
964
|
+
# @api private
|
|
965
|
+
def _mongoid_validate_no_javascript!(object)
|
|
966
|
+
case object
|
|
967
|
+
when Hash
|
|
968
|
+
object.each do |key, value|
|
|
969
|
+
key_s = key.to_s
|
|
970
|
+
if JAVASCRIPT_QUERY_OPERATORS.include?(key_s)
|
|
971
|
+
raise Errors::InvalidQuery,
|
|
972
|
+
"Operator '#{key_s}' executes server-side JavaScript and is not allowed " \
|
|
973
|
+
'anywhere in a query expression. Set Mongoid.allow_unsafe_query_operators = true ' \
|
|
974
|
+
'to permit all operators.'
|
|
975
|
+
end
|
|
976
|
+
|
|
977
|
+
_mongoid_validate_no_javascript!(value)
|
|
978
|
+
end
|
|
979
|
+
when Array
|
|
980
|
+
object.each { |value| _mongoid_validate_no_javascript!(value) }
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
|
|
875
984
|
# Force the values of the criterion to be evolved.
|
|
876
985
|
#
|
|
877
986
|
# @api private
|
data/lib/mongoid/encryptable.rb
CHANGED
|
@@ -37,6 +37,52 @@ module Mongoid
|
|
|
37
37
|
!encrypt_metadata.empty? || fields.any? { |_, field| field.is_a?(Mongoid::Fields::Encrypted) }
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Whether an encryption schema has to be generated for this model.
|
|
41
|
+
#
|
|
42
|
+
# True when the model declares encryption itself, and also when any model
|
|
43
|
+
# reachable through its embeds_one relations does. A model in the second
|
|
44
|
+
# group has no encrypted field of its own, but its collection still needs
|
|
45
|
+
# a schema, otherwise the embedded fields are written in plaintext.
|
|
46
|
+
#
|
|
47
|
+
# The answer is memoized, since this runs on the persistence path.
|
|
48
|
+
# Declaring encryption on a model after it has already been persisted is
|
|
49
|
+
# not supported.
|
|
50
|
+
#
|
|
51
|
+
# @return [ true | false ] Whether the model needs an encryption schema.
|
|
52
|
+
#
|
|
53
|
+
# @api private
|
|
54
|
+
def requires_encryption_schema?
|
|
55
|
+
return @requires_encryption_schema if defined?(@requires_encryption_schema)
|
|
56
|
+
|
|
57
|
+
@requires_encryption_schema = encrypted? || embeds_encrypted?([ self ])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Whether any model reachable through this model's embeds_one relations
|
|
61
|
+
# declares encryption.
|
|
62
|
+
#
|
|
63
|
+
# embeds_many is not considered: libmongocrypt cannot express per-field
|
|
64
|
+
# encryption under array items, so those fields are never mapped.
|
|
65
|
+
#
|
|
66
|
+
# @param [ Array<Class> ] path The models the walk is already inside of.
|
|
67
|
+
# A model embedding itself terminates here.
|
|
68
|
+
#
|
|
69
|
+
# @return [ true | false ] Whether an embedded model is encrypted.
|
|
70
|
+
#
|
|
71
|
+
# @api private
|
|
72
|
+
def embeds_encrypted?(path)
|
|
73
|
+
relations.each_value.any? do |relation|
|
|
74
|
+
next false unless relation.is_a?(Association::Embedded::EmbedsOne)
|
|
75
|
+
|
|
76
|
+
klass = relation.try_relation_class
|
|
77
|
+
# An association target does not have to be a Mongoid document, and
|
|
78
|
+
# the class it names does not have to exist.
|
|
79
|
+
next false unless klass.respond_to?(:encrypted?)
|
|
80
|
+
next false if path.include?(klass)
|
|
81
|
+
|
|
82
|
+
klass.encrypted? || klass.embeds_encrypted?(path + [ klass ])
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
40
86
|
# Override the key_id for the model.
|
|
41
87
|
#
|
|
42
88
|
# This method is solely for testing purposes and should not be used in
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongoid
|
|
4
|
+
module Errors
|
|
5
|
+
# This error is raised when evaluating a query in memory exceeds
|
|
6
|
+
# Mongoid::Config.in_memory_regexp_time_limit.
|
|
7
|
+
#
|
|
8
|
+
# What the limit bounds depends on the Ruby in use. Where per-Regexp
|
|
9
|
+
# timeouts are available it is the time spent executing regular
|
|
10
|
+
# expressions; elsewhere it is the elapsed time of the whole in-memory
|
|
11
|
+
# evaluation. The message is worded to hold either way.
|
|
12
|
+
class InMemoryRegexpTimeout < MongoidError
|
|
13
|
+
# Create the new error.
|
|
14
|
+
#
|
|
15
|
+
# @example Create the new in-memory regexp timeout error.
|
|
16
|
+
# InMemoryRegexpTimeout.new(5.0)
|
|
17
|
+
#
|
|
18
|
+
# @param [ Float ] limit The limit that was exceeded, in seconds. Not
|
|
19
|
+
# always the configured one: a global Regexp.timeout stricter than the
|
|
20
|
+
# configuration takes its place.
|
|
21
|
+
def initialize(limit)
|
|
22
|
+
super(compose_message('in_memory_regexp_timeout', limit: limit))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongoid
|
|
4
|
+
module Errors
|
|
5
|
+
# This error is raised when a model declares encrypted fields, but the
|
|
6
|
+
# namespace it is about to be persisted to is not covered by the automatic
|
|
7
|
+
# encryption schema of the client in use. Without a schema the driver has
|
|
8
|
+
# nothing to encrypt with, and the fields would be stored in plaintext.
|
|
9
|
+
class NoEncryptionSchema < MongoidError
|
|
10
|
+
# Create the new error.
|
|
11
|
+
#
|
|
12
|
+
# @example Create the error.
|
|
13
|
+
# NoEncryptionSchema.new(Band, 'music.bands', :default)
|
|
14
|
+
#
|
|
15
|
+
# @param [ Class ] klass The model class.
|
|
16
|
+
# @param [ String ] namespace The namespace the model resolved to.
|
|
17
|
+
# @param [ String | Symbol ] client The name of the client in use.
|
|
18
|
+
def initialize(klass, namespace, client)
|
|
19
|
+
super(
|
|
20
|
+
compose_message(
|
|
21
|
+
'no_encryption_schema',
|
|
22
|
+
{ klass: klass, namespace: namespace, client: client }
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/mongoid/errors.rb
CHANGED
|
@@ -12,6 +12,7 @@ require "mongoid/errors/document_not_found"
|
|
|
12
12
|
require "mongoid/errors/empty_config_file"
|
|
13
13
|
require "mongoid/errors/immutable_attribute"
|
|
14
14
|
require "mongoid/errors/in_memory_collation_not_supported"
|
|
15
|
+
require "mongoid/errors/in_memory_regexp_timeout"
|
|
15
16
|
require "mongoid/errors/invalid_auto_encryption_configuration"
|
|
16
17
|
require "mongoid/errors/invalid_around_callback"
|
|
17
18
|
require "mongoid/errors/invalid_async_query_executor"
|
|
@@ -52,6 +53,7 @@ require "mongoid/errors/mixed_client_configuration"
|
|
|
52
53
|
require "mongoid/errors/nested_attributes_metadata_not_found"
|
|
53
54
|
require "mongoid/errors/no_default_client"
|
|
54
55
|
require "mongoid/errors/no_environment"
|
|
56
|
+
require "mongoid/errors/no_encryption_schema"
|
|
55
57
|
require "mongoid/errors/no_map_reduce_output"
|
|
56
58
|
require "mongoid/errors/no_metadata"
|
|
57
59
|
require "mongoid/errors/no_parent"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rubocop:todo all
|
|
3
|
+
|
|
4
|
+
module Mongoid
|
|
5
|
+
# Reads the value of a field name from a document without dispatching the
|
|
6
|
+
# name as an arbitrary method.
|
|
7
|
+
#
|
|
8
|
+
# Field names may come from application input, where a name like +destroy+
|
|
9
|
+
# or +attributes+ would delete the document or disclose its contents.
|
|
10
|
+
# Declared field and association names are validated when they are declared
|
|
11
|
+
# (see Mongoid.destructive_fields), so a name that resolves to one of them
|
|
12
|
+
# is safe to send to a document. Any other name is read from the attributes
|
|
13
|
+
# hash, which is what the database-backed query contexts do.
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
module FieldReadable
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# Read the value of the given field name from the given document.
|
|
20
|
+
#
|
|
21
|
+
# @param [ Document ] document The document to read from.
|
|
22
|
+
# @param [ String | Symbol ] name The name of the field.
|
|
23
|
+
#
|
|
24
|
+
# @return [ Object | nil ] The value of the field, or nil when the name
|
|
25
|
+
# is neither a declared field nor present in the attributes.
|
|
26
|
+
def read_field_value(document, name)
|
|
27
|
+
name = name.to_s
|
|
28
|
+
# A blank name cannot name a field. Return nil, which is what reading
|
|
29
|
+
# one has always done.
|
|
30
|
+
return nil if name.blank?
|
|
31
|
+
|
|
32
|
+
if (meth = readable_method_for(document.class, name))
|
|
33
|
+
document.public_send(meth)
|
|
34
|
+
else
|
|
35
|
+
document.attributes[document.class.database_field_name(name)]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Resolve the given name to a method that is declared by the given class,
|
|
40
|
+
# and therefore safe to send to one of its instances.
|
|
41
|
+
#
|
|
42
|
+
# @param [ Class ] klass The document class.
|
|
43
|
+
# @param [ String ] name The name of the field.
|
|
44
|
+
#
|
|
45
|
+
# @return [ String | nil ] The method to send, or nil when the name is
|
|
46
|
+
# not declared by the class.
|
|
47
|
+
def readable_method_for(klass, name)
|
|
48
|
+
# Fields, associations, and field aliases each define a reader of their
|
|
49
|
+
# own name. Note that associations must be resolved before aliases: a
|
|
50
|
+
# belongs_to aliases its own name to its foreign key, and reading
|
|
51
|
+
# `band` must give the document, not the id.
|
|
52
|
+
return name if klass.relations.key?(name) ||
|
|
53
|
+
klass.fields.key?(name) ||
|
|
54
|
+
klass.aliased_fields.key?(name)
|
|
55
|
+
|
|
56
|
+
# An association may also be named by its `store_as`, which has no
|
|
57
|
+
# reader of its own, or by its ids accessor, which does.
|
|
58
|
+
if (assoc = klass.relations[klass.aliased_associations[name]])
|
|
59
|
+
return (assoc.store_as == name) ? assoc.name.to_s : name
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Localized fields also get a _translations reader, which does not
|
|
63
|
+
# appear in the fields hash.
|
|
64
|
+
base = name.delete_suffix(Fields::TRANSLATIONS_SFX)
|
|
65
|
+
return nil if base == name
|
|
66
|
+
|
|
67
|
+
name if klass.fields[klass.database_field_name(base)]&.localized?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/mongoid/indexable.rb
CHANGED
|
@@ -27,18 +27,9 @@ module Mongoid
|
|
|
27
27
|
def create_indexes
|
|
28
28
|
return unless index_specifications
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
key, options = spec.key, default_options.merge(spec.options)
|
|
34
|
-
if database = options[:database]
|
|
35
|
-
with(database: database) do |klass|
|
|
36
|
-
klass.collection.indexes(session: _session).create_one(key, options.except(:database))
|
|
37
|
-
end
|
|
38
|
-
else
|
|
39
|
-
collection.indexes(session: _session).create_one(key, options)
|
|
40
|
-
end
|
|
41
|
-
end and true
|
|
30
|
+
Threaded.with_collection_management do
|
|
31
|
+
perform_create_indexes
|
|
32
|
+
end
|
|
42
33
|
end
|
|
43
34
|
|
|
44
35
|
# Send the actual index removal comments to the MongoDB driver,
|
|
@@ -49,21 +40,9 @@ module Mongoid
|
|
|
49
40
|
#
|
|
50
41
|
# @return [ true ] If the operation succeeded.
|
|
51
42
|
def remove_indexes
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
klass.collection.indexes(session: _session).each do |spec|
|
|
56
|
-
unless spec["name"] == "_id_"
|
|
57
|
-
klass.collection.indexes(session: _session).drop_one(spec["key"])
|
|
58
|
-
logger.info(
|
|
59
|
-
"MONGOID: Removed index '#{spec["name"]}' on collection " +
|
|
60
|
-
"'#{klass.collection.name}' in database '#{database}'."
|
|
61
|
-
)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
rescue Mongo::Error::OperationFailure; end
|
|
65
|
-
end
|
|
66
|
-
end and true
|
|
43
|
+
Threaded.with_collection_management do
|
|
44
|
+
perform_remove_indexes
|
|
45
|
+
end
|
|
67
46
|
end
|
|
68
47
|
|
|
69
48
|
# Add the default indexes to the root document if they do not already
|
|
@@ -118,6 +97,39 @@ module Mongoid
|
|
|
118
97
|
|
|
119
98
|
private
|
|
120
99
|
|
|
100
|
+
def perform_create_indexes
|
|
101
|
+
default_options = {background: Config.background_indexing}
|
|
102
|
+
|
|
103
|
+
index_specifications.each do |spec|
|
|
104
|
+
key, options = spec.key, default_options.merge(spec.options)
|
|
105
|
+
if database = options[:database]
|
|
106
|
+
with(database: database) do |klass|
|
|
107
|
+
klass.collection.indexes(session: _session).create_one(key, options.except(:database))
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
collection.indexes(session: _session).create_one(key, options)
|
|
111
|
+
end
|
|
112
|
+
end and true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def perform_remove_indexes
|
|
116
|
+
indexed_database_names.each do |database|
|
|
117
|
+
with(database: database) do |klass|
|
|
118
|
+
begin
|
|
119
|
+
klass.collection.indexes(session: _session).each do |spec|
|
|
120
|
+
unless spec["name"] == "_id_"
|
|
121
|
+
klass.collection.indexes(session: _session).drop_one(spec["key"])
|
|
122
|
+
logger.info(
|
|
123
|
+
"MONGOID: Removed index '#{spec["name"]}' on collection " +
|
|
124
|
+
"'#{klass.collection.name}' in database '#{database}'."
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
rescue Mongo::Error::OperationFailure; end
|
|
129
|
+
end
|
|
130
|
+
end and true
|
|
131
|
+
end
|
|
132
|
+
|
|
121
133
|
# Get the names of all databases for this model that have index
|
|
122
134
|
# definitions.
|
|
123
135
|
#
|
data/lib/mongoid/matchable.rb
CHANGED
|
@@ -18,7 +18,12 @@ module Mongoid
|
|
|
18
18
|
#
|
|
19
19
|
# @return [ true | false ] True if matches, false if not.
|
|
20
20
|
def _matches?(selector)
|
|
21
|
-
|
|
21
|
+
# Opens a regexp budget for this document only. Callers that match many
|
|
22
|
+
# documents against one selector open a budget of their own first, and
|
|
23
|
+
# this one joins it rather than giving every document a fresh limit.
|
|
24
|
+
Matcher::RegexpBudget.open(selector) do
|
|
25
|
+
Matcher::Expression.matches?(self, selector)
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
end
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
# rubocop:todo all
|
|
2
1
|
module Mongoid
|
|
3
2
|
module Matcher
|
|
4
|
-
|
|
5
3
|
# This is an internal equality implementation that performs exact
|
|
6
4
|
# comparisons and regular expression matches.
|
|
7
5
|
#
|
|
8
6
|
# @api private
|
|
9
7
|
module EqImplWithRegexp
|
|
10
|
-
|
|
11
8
|
# Returns whether a value satisfies an $eq (or similar) expression,
|
|
12
9
|
# performing a regular expression match if the condition is a regular
|
|
13
10
|
# expression.
|
|
@@ -19,14 +16,12 @@ module Mongoid
|
|
|
19
16
|
# @return [ true | false ] Whether the value matches.
|
|
20
17
|
#
|
|
21
18
|
# @api private
|
|
22
|
-
module_function def matches?(
|
|
19
|
+
module_function def matches?(_original_operator, value, condition)
|
|
23
20
|
case condition
|
|
24
|
-
when Regexp
|
|
25
|
-
value
|
|
26
|
-
when ::BSON::Regexp::Raw
|
|
27
|
-
value =~ condition.compile
|
|
21
|
+
when Regexp, ::BSON::Regexp::Raw
|
|
22
|
+
value.respond_to?(:=~) && RegexpBudget.match?(value, condition)
|
|
28
23
|
else
|
|
29
|
-
if value.
|
|
24
|
+
if value.is_a?(Time) && condition.is_a?(Time)
|
|
30
25
|
EqImpl.time_eq?(value, condition)
|
|
31
26
|
else
|
|
32
27
|
value == condition
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
# rubocop:todo all
|
|
2
1
|
module Mongoid
|
|
3
2
|
module Matcher
|
|
4
|
-
|
|
5
3
|
# In-memory matcher for $regex expression.
|
|
6
4
|
#
|
|
7
5
|
# @see https://www.mongodb.com/docs/manual/reference/operator/query/regex/
|
|
8
6
|
#
|
|
9
7
|
# @api private
|
|
10
8
|
module Regex
|
|
11
|
-
|
|
12
9
|
# Returns whether a value satisfies a $regex expression.
|
|
13
10
|
#
|
|
14
11
|
# @param [ true | false ] exists Not used.
|
|
@@ -18,25 +15,24 @@ module Mongoid
|
|
|
18
15
|
# @return [ true | false ] Whether the value matches.
|
|
19
16
|
#
|
|
20
17
|
# @api private
|
|
21
|
-
module_function def matches?(
|
|
22
|
-
condition
|
|
23
|
-
when Regexp
|
|
24
|
-
condition
|
|
25
|
-
when BSON::Regexp::Raw
|
|
26
|
-
condition.compile
|
|
27
|
-
else
|
|
18
|
+
module_function def matches?(_exists, value, condition)
|
|
19
|
+
unless condition.is_a?(Regexp) || condition.is_a?(BSON::Regexp::Raw)
|
|
28
20
|
# Note that strings must have been converted to a regular expression
|
|
29
21
|
# instance already (with $options taken into account, if provided).
|
|
30
22
|
raise Errors::InvalidQuery, "$regex requires a regular expression argument: #{Errors::InvalidQuery.truncate_expr(condition)}"
|
|
31
23
|
end
|
|
32
24
|
|
|
25
|
+
# The condition is compiled by RegexpBudget rather than here, so that
|
|
26
|
+
# the budget's timeout can be baked into the pattern.
|
|
33
27
|
case value
|
|
34
28
|
when Array
|
|
29
|
+
# Object#=~ is gone as of Ruby 3.2, so an element that cannot be
|
|
30
|
+
# matched against has to be rejected rather than passed to =~.
|
|
35
31
|
value.any? do |v|
|
|
36
|
-
v
|
|
32
|
+
v.respond_to?(:=~) && RegexpBudget.match?(v, condition)
|
|
37
33
|
end
|
|
38
34
|
when String
|
|
39
|
-
value
|
|
35
|
+
RegexpBudget.match?(value, condition)
|
|
40
36
|
else
|
|
41
37
|
false
|
|
42
38
|
end
|
|
@@ -52,7 +48,7 @@ module Mongoid
|
|
|
52
48
|
#
|
|
53
49
|
# @api private
|
|
54
50
|
module_function def matches_array_or_scalar?(value, condition)
|
|
55
|
-
if Array
|
|
51
|
+
if value.is_a?(Array)
|
|
56
52
|
value.any? do |v|
|
|
57
53
|
matches?(true, v, condition)
|
|
58
54
|
end
|