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