From 50a0f9bad7c3f3921fb0ffd2171e5d3b644e6d7c Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:54:10 +0900 Subject: [PATCH] WIP on skipping contradicted queries --- lib/mongoid/criteria.rb | 2 +- .../criteria/queryable/contradiciton.rb | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 lib/mongoid/criteria/queryable/contradiciton.rb diff --git a/lib/mongoid/criteria.rb b/lib/mongoid/criteria.rb index ad365b5a8f..23c88ddd38 100644 --- a/lib/mongoid/criteria.rb +++ b/lib/mongoid/criteria.rb @@ -281,7 +281,7 @@ def none # # @return [ true | false ] If the criteria is a none. def empty_and_chainable? - !!@none + !!@none || Queryable::Contradiction.contradicted?(self) end # Overriden to include _type in the fields. diff --git a/lib/mongoid/criteria/queryable/contradiciton.rb b/lib/mongoid/criteria/queryable/contradiciton.rb new file mode 100644 index 0000000000..b36acd9d3c --- /dev/null +++ b/lib/mongoid/criteria/queryable/contradiciton.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Mongoid + class Criteria + module Queryable + # Checks whether the criteria contains a contradiction which + # would cause the query to return no results. This can result from: + # - Conflicting equals: { 'name' => 'Bob', 'name' => 'Joe' } + # - Empty $in: { '$in' => [] } + # - Not empty $nin: { '$not' => { '$nin' => [] } } + module Contradiction + extend self + + def contradicted?(criteria) + traverse_selector(criteria.selector) + end + + private + + def traverse_selector(selector, negated = false) + # return true if selector.any? { |k, v| empty_condition?(k, v, '$in') } + + # selector.all? + selector.each do |k, v| + return true if empty_condition?(k, v, '$in') + + case k.to_s + when '$and' + Array(v).any? { |s| traverse_selector(s, negated) } + when '$or' + Array(v).all? { |s| traverse_selector(s, negated) } + when '$nor' + Array(v).all? { |s| traverse_selector(s, !negated) } + when '$not' + traverse_selector(selector, !negated) + end + end + end + + def empty_condition?(key, value, operator) + !key.to_s.start_with?('$') && + value.is_a?(Hash) && + value.size == 1 && + (value[operator] || value[operator.to_sym]) == [] + end + end + end + end +end