class Hotdog::Expression::UnaryExpressionNode

Attributes

expression[R]
op[R]

Public Class Methods

new(op, expression, options={}) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 30
def initialize(op, expression, options={})
  case (op || "not").to_s
  when "NOOP", "noop"
    @op = :NOOP
  when "!", "~", "NOT", "not"
    @op = :NOT
  else
    raise(SyntaxError.new("unknown unary operator: #{op.inspect}"))
  end
  @expression = expression
  @options = {}
end

Public Instance Methods

==(other) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 111
def ==(other)
  self.class === other and @op == other.op and @expression == other.expression
end
compact(options={}) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 99
def compact(options={})
  case op
  when :NOOP
    expression.compact(options)
  else
    UnaryExpressionNode.new(
      op,
      expression.compact(options),
    )
  end
end
dump(options={}) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 115
def dump(options={})
  {unary_op: @op.to_s, expression: @expression.dump(options)}
end
evaluate(environment, options={}) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 43
def evaluate(environment, options={})
  case @op
  when :NOOP
    @expression.evaluate(environment, options)
  when :NOT
    values = @expression.evaluate(environment, options).tap do |values|
      environment.logger.debug("expr: #{values.length} value(s)")
    end
    if values.empty?
      EverythingNode.new().evaluate(environment, options).tap do |values|
        environment.logger.debug("NOT expr: #{values.length} value(s)")
      end
    else
      # workaround for "too many terms in compound SELECT"
      min, max = environment.execute("SELECT MIN(id), MAX(id) FROM hosts LIMIT 1;").first.to_a
      sqlite_limit_compound_select = options[:sqlite_limit_compound_select] || SQLITE_LIMIT_COMPOUND_SELECT
      (min / (sqlite_limit_compound_select - 2)).upto(max / (sqlite_limit_compound_select - 2)).flat_map { |i|
        range = ((sqlite_limit_compound_select - 2) * i)...((sqlite_limit_compound_select - 2) * (i + 1))
        selected = values.select { |n| range === n }
        if 0 < selected.length
          q = "SELECT id FROM hosts " \
                "WHERE ? <= id AND id < ? AND id NOT IN (%s);"
          environment.execute(q % selected.map { "?" }.join(", "), [range.first, range.last] + selected).map { |row| row.first }
        else
          []
        end
      }.tap do |values|
        environment.logger.debug("NOT expr: #{values.length} value(s)")
      end
    end
  else
    []
  end
end
optimize(options={}) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 78
def optimize(options={})
  o_self = compact(options)
  if UnaryExpressionNode === o_self
    case o_self.op
    when :NOT
      case o_self.expression
      when EverythingNode
        NothingNode.new(options)
      when NothingNode
        EverythingNode.new(options)
      else
        o_self.optimize1(options)
      end
    else
      o_self.optimize1(options)
    end
  else
    o_self.optimize(options)
  end
end

Protected Instance Methods

optimize1(options={}) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 120
def optimize1(options={})
  case op
  when :NOOP
    expression.optimize(options)
  when :NOT
    if UnaryExpressionNode === expression
      case expression.op
      when :NOOP
        expression.optimize(options)
      when :NOT
        expression.expression.optimize(options)
      else
        self.dup
      end
    else
      optimize2(options)
    end
  else
    self.dup
  end
end
optimize2(options={}) click to toggle source
# File lib/hotdog/expression/semantics.rb, line 142
def optimize2(options={})
  sqlite_limit_compound_select = options[:sqlite_limit_compound_select] || SQLITE_LIMIT_COMPOUND_SELECT
  case expression
  when QueryExpressionNode
    q = expression.query
    v = expression.values
    if q and v.length <= sqlite_limit_compound_select
      QueryExpressionNode.new("SELECT id AS host_id FROM hosts EXCEPT #{q.sub(/\s*;\s*\z/, "")};", v)
    else
      self.dup
    end
  when TagExpressionNode
    q = expression.maybe_query(options)
    v = expression.condition_values(options)
    if q and v.length <= sqlite_limit_compound_select
      QueryExpressionNode.new("SELECT id AS host_id FROM hosts EXCEPT #{q.sub(/\s*;\s*\z/, "")};", v)
    else
      self.dup
    end
  else
    self.dup
  end
end