lib/mongoid/criteria/queryable/mergeable.rb in mongoid-7.2.6 vs lib/mongoid/criteria/queryable/mergeable.rb in mongoid-7.3.0
- old
+ new
@@ -225,68 +225,94 @@
end
out
end
# Takes a criteria hash and expands Key objects into hashes containing
- # MQL corresponding to said key objects.
+ # MQL corresponding to said key objects. Also converts the input to
+ # BSON::Document to permit indifferent access.
#
+ # The argument must be a hash containing key-value pairs of the
+ # following forms:
+ # - {field_name: value}
+ # - {'field_name' => value}
+ # - {key_instance: value}
+ # - {:$operator => operator_value_expression}
+ # - {'$operator' => operator_value_expression}
+ #
# Ruby does not permit multiple symbol operators. For example,
# {:foo.gt => 1, :foo.gt => 2} is collapsed to {:foo.gt => 2} by the
# language. Therefore this method never has to deal with multiple
# identical operators.
#
# Similarly, this method should never need to expand a literal value
# and an operator at the same time.
#
+ # This method effectively converts symbol keys to string keys in
+ # the input +expr+, such that the downstream code can assume that
+ # conditions always contain string keys.
+ #
# @param [ Hash ] expr Criteria including Key instances.
#
- # @return [ Hash ] The expanded criteria.
+ # @return [ BSON::Document ] The expanded criteria.
private def _mongoid_expand_keys(expr)
unless expr.is_a?(Hash)
raise ArgumentError, 'Argument must be a Hash'
end
- result = {}
+ result = BSON::Document.new
expr.each do |field, value|
- field.__expr_part__(value.__expand_complex__).each do |k, v|
- if result[k]
- if result[k].is_a?(Hash)
+ field.__expr_part__(value.__expand_complex__, negating?).each do |k, v|
+ if existing = result[k]
+ if existing.is_a?(Hash)
# Existing value is an operator.
# If new value is also an operator, ensure there are no
# conflicts and add
if v.is_a?(Hash)
# The new value is also an operator.
# If there are no conflicts, combine the hashes, otherwise
# add new conditions to top level with $and.
- if (v.keys & result[k].keys).empty?
- result[k].update(v)
+ if (v.keys & existing.keys).empty?
+ existing.update(v)
else
raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
result['$and'] ||= []
result['$and'] << {k => v}
end
else
# The new value is a simple value.
- # If there isn't an $eq operator already in the query,
- # transform the new value into an $eq operator and add it
- # to the existing hash. Otherwise add the new condition
- # with $and to the top level.
- if result[k].key?('$eq')
+ # Transform the implicit equality to either $eq or $regexp
+ # depending on the type of the argument. See
+ # https://docs.mongodb.com/manual/reference/operator/query/eq/#std-label-eq-usage-examples
+ # for the description of relevant server behavior.
+ op = case v
+ when Regexp, BSON::Regexp::Raw
+ '$regex'
+ else
+ '$eq'
+ end
+ # If there isn't an $eq/$regex operator already in the
+ # query, transform the new value into an operator
+ # expression and add it to the existing hash. Otherwise
+ # add the new condition with $and to the top level.
+ if existing.key?(op)
raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
result['$and'] ||= []
result['$and'] << {k => v}
else
- result[k].update('$eq' => v)
+ existing.update(op => v)
end
end
else
# Existing value is a simple value.
- # If we are adding an operator, and the operator is not $eq,
- # convert existing value into $eq and add the new operator
- # to the same hash. Otherwise add the new condition with $and
- # to the top level.
- if v.is_a?(Hash) && !v.key?('$eq')
- result[k] = {'$eq' => result[k]}.update(v)
+ # See the notes above about transformations to $eq/$regex.
+ op = case existing
+ when Regexp, BSON::Regexp::Raw
+ '$regex'
+ else
+ '$eq'
+ end
+ if v.is_a?(Hash) && !v.key?(op)
+ result[k] = {op => existing}.update(v)
else
raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
result['$and'] ||= []
result['$and'] << {k => v}
end