Skip to content

Commit

Permalink
Condition#do_eval takes into account the current record
Browse files Browse the repository at this point in the history
This allows us to use `rec` in the expression and not have to
post evaluate the to_ruby output

Skipped over the associations and the find clauses
Also skipped the hash contexts
  • Loading branch information
kbrock committed Nov 6, 2024
1 parent 12d5b91 commit e9d9f18
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 116 deletions.
10 changes: 5 additions & 5 deletions app/models/condition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ def self.evaluate(cond, rec, _inputs = {}, attr = :expression)
# similar to MiqExpression#evaluate
# @return [Boolean] true if the expression matches the record
def self.subst_matches?(expr, rec)
do_eval(subst(expr, rec))
do_eval(subst(expr, rec), rec)
end

def self.do_eval(expr)
!!eval(expr)
def self.do_eval(expr, rec)
!!eval(expr, binding)
end
private_class_method :do_eval

Expand Down Expand Up @@ -167,7 +167,7 @@ def self._subst_find(rec, expr)
value = MiqExpression.quote(obj.send(attr), opts[:type]&.to_sym)
value = value.gsub("\\", '\&\&') if value.kind_of?(String)
e = search.gsub(/<value[^>]*>.+<\/value>/im, value.to_s)
obj if do_eval(e)
obj if do_eval(e, obj)
end.compact

MiqPolicy.logger.debug("MIQ(condition-_subst_find): Search Expression returned: [#{list.length}] records")
Expand Down Expand Up @@ -207,7 +207,7 @@ def self._subst_find(rec, expr)
e = check.gsub(/<value[^>]*>.+<\/value>/im, value.to_s)
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Check Expression after substitution: [#{e}]")

result = do_eval(e)
result = do_eval(e, obj)

return true if result && checkmode == "any"
return false if !result && checkmode == "all"
Expand Down
45 changes: 26 additions & 19 deletions lib/miq_expression.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def to_ruby(timezone = nil, prune_sql: false)
end
end

def self._to_ruby(exp, context_type, tz)
def self._to_ruby(exp, context_type, tz, obj_name: :rec)
return exp unless exp.kind_of?(Hash)

operator = exp.keys.first
Expand All @@ -200,29 +200,29 @@ def self._to_ruby(exp, context_type, tz)

case operator
when "equal", "=", "<", ">", ">=", "<=", "!="
operands = operands2rubyvalue(operator, op_args, context_type)
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
when "before"
col_type = Target.parse(col_name).column_type if col_name
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
val = op_args["value"]
clause = ruby_for_date_compare(col_ruby, col_type, tz, "<", val)
when "after"
col_type = Target.parse(col_name).column_type if col_name
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
val = op_args["value"]
clause = ruby_for_date_compare(col_ruby, col_type, tz, nil, nil, ">", val)
when "includes all"
operands = operands2rubyvalue(operator, op_args, context_type)
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
clause = "(#{operands[0]} & #{operands[1]}) == #{operands[1]}"
when "includes any"
operands = operands2rubyvalue(operator, op_args, context_type)
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
clause = "(#{operands[1]} - #{operands[0]}) != #{operands[1]}"
when "includes only", "limited to"
operands = operands2rubyvalue(operator, op_args, context_type)
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
clause = "(#{operands[0]} - #{operands[1]}) == []"
when "like", "not like", "starts with", "ends with", "includes"
operands = operands2rubyvalue(operator, op_args, context_type)
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
operands[1] =
case operator
when "starts with"
Expand All @@ -235,7 +235,7 @@ def self._to_ruby(exp, context_type, tz)
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
clause = "!(" + clause + ")" if operator == "not like"
when "regular expression matches", "regular expression does not match"
operands = operands2rubyvalue(operator, op_args, context_type)
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)

# If it looks like a regular expression, sanitize from forward
# slashes and interpolation
Expand All @@ -255,11 +255,11 @@ def self._to_ruby(exp, context_type, tz)
end
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
when "and", "or"
clause = "(" + op_args.collect { |operand| _to_ruby(operand, context_type, tz) }.join(" #{normalize_ruby_operator(operator)} ") + ")"
clause = "(" + op_args.collect { |operand| _to_ruby(operand, context_type, tz, :obj_name => obj_name) }.join(" #{normalize_ruby_operator(operator)} ") + ")"
when "not", "!"
clause = normalize_ruby_operator(operator) + "(" + _to_ruby(op_args, context_type, tz) + ")"
clause = normalize_ruby_operator(operator) + "(" + _to_ruby(op_args, context_type, tz, :obj_name => obj_name) + ")"
when "is null", "is not null", "is empty", "is not empty"
operands = operands2rubyvalue(operator, op_args, context_type)
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
when "contains"
op_args["tag"] ||= col_name
Expand Down Expand Up @@ -294,14 +294,14 @@ def self._to_ruby(exp, context_type, tz)

check =~ /^check(.*)$/
mode = $1.downcase
clause = "<find><search>" + _to_ruby(op_args["search"], context_type, tz) + "</search>" \
"<check mode=#{mode}>" + _to_ruby(op_args[check], context_type, tz) + "</check></find>"
clause = "<find><search>" + _to_ruby(op_args["search"], context_type, tz, :obj_name => nil) + "</search>" \
"<check mode=#{mode}>" + _to_ruby(op_args[check], context_type, tz, :obj_name => nil) + "</check></find>"
when "key exists"
clause, = operands2rubyvalue(operator, op_args, context_type)
clause, = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
when "value exists"
clause, = operands2rubyvalue(operator, op_args, context_type)
clause, = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
when "is"
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
col_type = Target.parse(col_name).column_type
value = op_args["value"]
clause = if col_type == :date && !RelativeDatetime.relative?(value)
Expand All @@ -310,7 +310,7 @@ def self._to_ruby(exp, context_type, tz)
ruby_for_date_compare(col_ruby, col_type, tz, ">=", value, "<=", value)
end
when "from"
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
col_type = Target.parse(col_name).column_type

start_val, end_val = op_args["value"]
Expand Down Expand Up @@ -703,7 +703,7 @@ def self.quote_by(operator, value, column_type = nil)
end
end

def self.operands2rubyvalue(operator, ops, context_type)
def self.operands2rubyvalue(operator, ops, context_type, obj_name: nil)
if ops["field"]
if ops["field"] == "<count>"
["<count>", quote(ops["value"], :integer)]
Expand All @@ -713,6 +713,9 @@ def self.operands2rubyvalue(operator, ops, context_type)

[if context_type == "hash"
"<value type=#{col_type}>#{ops["field"].split(".").last.split("-").join(".")}</value>"
elsif obj_name && !virtual_custom_attribute?(target.column) && target.associations.empty?
# TODO: handle value for fields with associations (they could be habtm, has_one, has_many, belongs_to)
"#{obj_name}.#{target.column}"
else
"<value ref=#{target.model.to_s.downcase}, type=#{col_type}>#{target.tag_path_with}</value>"
end, quote_by(operator, ops["value"], col_type)]
Expand Down Expand Up @@ -838,6 +841,10 @@ def self.sanitize_regular_expression(string)
string.gsub(%r{\\*/}, "\\/").gsub(/\\*#/, "\\#")
end

def self.virtual_custom_attribute?(attribute)
attribute.include?(CustomAttributeMixin::CUSTOM_ATTRIBUTES_PREFIX)
end

def self.escape_virtual_custom_attribute(attribute)
if attribute.include?(CustomAttributeMixin::CUSTOM_ATTRIBUTES_PREFIX)
uri_parser = URI::RFC2396_Parser.new
Expand Down
Loading

0 comments on commit e9d9f18

Please sign in to comment.