diff --git a/lib/miq_expression.rb b/lib/miq_expression.rb index 754e36d713c..4464f0258d8 100644 --- a/lib/miq_expression.rb +++ b/lib/miq_expression.rb @@ -361,11 +361,11 @@ def operator_hash(operator, children, unary: false) # # in complicated cases, the child_exp can be different from the child nodes # - def reduce_exp_children(children, mode) + def reduce_exp_children(children, mode, swap:) seen = MODE_NONE filtered_children = [] children.each do |child| - child_exp, child_seen = reduce_exp(child, mode) + child_exp, child_seen = reduce_exp(child, mode, :swap => swap) seen |= child_seen filtered_children << child_exp if child_exp end @@ -418,21 +418,36 @@ def reduce_exp_children(children, mode) # # In the AND case, the sql nodes line up with the filtered child nodes # - def reduce_exp(exp, mode) + # + # exp |==>| exp (mode=sql) |and| exp (mode=ruby) + # -------------------|---|----------------|---|----------------- + # !(sql OR sql) |==>| !(sql OR sql) |AND| + # !(sql OR ruby) |==>| !(sql) |AND| !(ruby) + # !(ruby1 OR ruby2) |==>| |AND| !(ruby1 OR ruby2) + # + # exp |==>| exp (mode=sql) |and| exp (mode=ruby) + # -------------------|---|----------------|---|----------------- + # !(sql AND sql) |==>| !(sql AND sql) |AND| + # !(sql AND ruby) |==>| |AND| !(sql AND ruby) + # !(ruby1 AND ruby2) |==>| |AND| !(ruby1 AND ruby2) + # + # Inside a NOT, the OR acts like the AND, and the AND acts like the OR + # so swap is introduced to handle Demorgan's law + # + def reduce_exp(exp, mode, swap: false) operator = exp.keys.first case operator.downcase - when "and" - children, seen = reduce_exp_children(exp[operator], mode) - [operator_hash(operator, children), seen] - when "or" - children, seen = reduce_exp_children(exp[operator], mode) - if seen == MODE_BOTH + when "and", "or" + children, seen = reduce_exp_children(exp[operator], mode, :swap => swap) + if (operator.downcase == "and") != swap + [operator_hash(operator, children), seen] + elsif seen == MODE_BOTH [mode == MODE_RUBY ? exp : nil, seen] else [operator_hash(operator, children), seen] end when "not", "!" - children, seen = reduce_exp(exp[operator], mode) + children, seen = reduce_exp(exp[operator], mode, :swap => !swap) [operator_hash(operator, children, :unary => true), seen] else if sql_supports_atom?(exp) diff --git a/spec/lib/miq_expression_spec.rb b/spec/lib/miq_expression_spec.rb index 53bcb238f97..cb127d7241d 100644 --- a/spec/lib/miq_expression_spec.rb +++ b/spec/lib/miq_expression_spec.rb @@ -267,13 +267,11 @@ end it "!(sql OR ruby) => (!(sql) AND !(ruby)) => !(sql)" do - expect(sql_reduced_exp("NOT" => {"OR" => [sql_field, ruby_field.clone]})).to be_nil - # TODO: eq("NOT" => sql_field) + expect(sql_reduced_exp("NOT" => {"OR" => [sql_field, ruby_field.clone]})).to eq("NOT" => sql_field) end it "!(sql AND ruby) => (!(sql) OR !(ruby)) => nil" do - expect(sql_reduced_exp("NOT" => {"AND" => [sql_field, ruby_field.clone]})).to eq("NOT" => sql_field) - # TODO: be_nil + expect(sql_reduced_exp("NOT" => {"AND" => [sql_field, ruby_field.clone]})).to be_nil end end @@ -331,13 +329,11 @@ end it "!(sql OR ruby) => (!(sql) AND !(ruby)) => !(ruby)" do - expect(ruby_reduced_exp("NOT" => {"OR" => [sql_field, ruby_field.clone]})).to eq("NOT" => {"OR" => [sql_field, ruby_field.clone]}) - # TODO: eq("NOT" => ruby_field) + expect(ruby_reduced_exp("NOT" => {"OR" => [sql_field, ruby_field.clone]})).to eq("NOT" => ruby_field) end it "!(sql AND ruby) => (!(sql) OR !(ruby)) => !(sql AND ruby)" do - expect(ruby_reduced_exp("NOT" => {"AND" => [sql_field, ruby_field.clone]})).to eq("NOT" => ruby_field) - # TODO: eq("NOT" => {"AND" => [sql_field, ruby_field]}) + expect(ruby_reduced_exp("NOT" => {"AND" => [sql_field, ruby_field.clone]})).to eq("NOT" => {"AND" => [sql_field, ruby_field]}) end end end