jeremyevans / sequel

Sequel: The Database Toolkit for Ruby
http://sequel.jeremyevans.net
Other
4.98k stars 1.07k forks source link

Possibility to remove conditions from a dataset #692

Closed sdepold closed 11 years ago

sdepold commented 11 years ago

Hi.

I really like Sequel, but while my current development on a plugin, I found the need for a way to remove a specific where condition from a dataset. Is this somehow (hacks are OK) possible?

Greetings, sdepold

jeremyevans commented 11 years ago

Well, Dataset#unfiltered removes all WHERE/HAVING conditions. If you want to remove a specific condition, you'll have to modify the abstract syntax tree storing the conditions, which you can access via dataset.opts[:where] and dataset.opts[:having]. The Sequel::ASTTransformer class may be helpful to do that.

Note that removing specific conditions would generally be considered a code smell. If it's possible to modify the plugin so that it isn't required (e.g. by saving a copy of the dataset before such conditions are added), that would be my recommendation.

Also note that in the future, questions about how to use Sequel should be posted to the sequel-talk Google Group. GitHub Issues should only be used to report bugs in Sequel.

iblue commented 10 years ago

In real production code it's not always possible to prevent some code smell. I have implemented a Dataset#unfilter method.

Except that nobody should ever use this, except in bad smelly code, should I submit this as a pull request?

def unfilter(*cond, &block)
  cond = cond.first if cond.size == 1 # De-array

  # Empty unfilter -> Do nothing
  if cond.respond_to?(:empty?) && cond.empty? && !block
    return clone
  end

  # Compile expression
  cond = filter_expr(cond, &block)

  # Get top level of WHERE-clause AST
  top      = @opts[:where]

  # Empty original filter -> Do nothing
  return clone if top.nil?

  # If the only condition on top is the to-be-unfiltered condition, remove all
  # conditions.
  return clone(:where => nil) if top == cond

  # Skip everything except AND expressions
  if top != ::Sequel::SQL::BooleanExpression and top.op == :AND
    return clone
  end

  # We can do this, because AND conditions always collapse and are not nested
  # (are they?)
  if top.args.include? cond
    # Remove the to-be-unfiltered condition from the ANDs and return.
    expr = ::Sequel::SQL::BooleanExpression.new(:AND, *top.args.reject{|x| x == cond})

    clone(:where => expr)
  else
    return clone
  end
end
jeremyevans commented 10 years ago

The issue with your method is that it only works in certain cases. You can handle cases such as:

ds.where(cond).unfilter(cond)
ds.where(cond).where(other_code).unfilter(cond)

but not

ds.where(cond).or(other_cond).unfilter(cond)
ds.where(cond).where(other_cond).or(other_cond2).unfilter(cond)

even though those should arguably resolve to:

ds.where(other_cond)
ds.where(other_cond).or(other_cond2)

That's not to say it couldn't be included as an extension that ships with Sequel, but you'd have to ask on sequel-talk and see if anyone else is interested in such an extension, and add tests and RDoc for it.