Skip to content

Commit

Permalink
Refactor blockless filter support
Browse files Browse the repository at this point in the history
Split ComplexExpression functionality into three subclasses:
BooleanExpression, NumericExpression, and StringExpression.  Each
subclass implements certain methods that the other subclasses do not.

BooleanExpression: &, |, ~
NumericExpression: +, -, *, /, <, >, <=, >=
StringExpression: like, <, >, <=, >=

The methods are defined in the BooleanMethods, NumericMethods,
StringMethods, and InequalityMethods modules, which are included
in the above classes as necessary.

All modules are included in the ComplexExpressionMethods module
which is included in LiteralString, Symbol, and SQL::Function.
ComplexExpressionMethods used to be included in SQL::Expression,
but it only really makes sense for SQL::Function, and would
have caused problems if it were included in SQL::Expression,
since ComplexExpression is a subclass of that.

ComplexExpressionMethods also has three new methods: sql_boolean,
sql_number, and sql_string, which return the receiver wrapped
in the appropriate type of expression.  This can be useful if
other libraries overwrite the operators for classes that Sequel
defines the SQL for.  For example, I ran into a situation where
Symbol#/ was defined to make file system path creation easier,
but it returned the result as a string, which Sequel produced
the wrong SQL with (quoting it as a string).  The fix was to
use :symbol.sql_number/10.
  • Loading branch information
jeremyevans committed Jun 3, 2008
1 parent b582d6a commit e7b2375
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 208 deletions.
2 changes: 1 addition & 1 deletion sequel_core/lib/sequel_core/adapters/mysql.rb
Expand Up @@ -268,7 +268,7 @@ def schema_ds_dataset
def schema_ds_filter(table_name, opts)
filt = super
# Restrict it to the given or current database, unless specifically requesting :database = nil
filt = SQL::ComplexExpression.new(:AND, filt, {:c__table_schema=>opts[:database] || self.opts[:database]}) if opts[:database] || !opts.include?(:database)
filt = SQL::BooleanExpression.new(:AND, filt, {:c__table_schema=>opts[:database] || self.opts[:database]}) if opts[:database] || !opts.include?(:database)
filt
end

Expand Down
2 changes: 1 addition & 1 deletion sequel_core/lib/sequel_core/adapters/postgres.rb
Expand Up @@ -365,7 +365,7 @@ def connection_pool_default_options
def schema_ds_filter(table_name, opts)
filt = super
# Restrict it to the given or public schema, unless specifically requesting :schema = nil
filt = SQL::ComplexExpression.new(:AND, filt, {:c__table_schema=>opts[:schema] || 'public'}) if opts[:schema] || !opts.include?(:schema)
filt = SQL::BooleanExpression.new(:AND, filt, {:c__table_schema=>opts[:schema] || 'public'}) if opts[:schema] || !opts.include?(:schema)
filt
end
end
Expand Down
44 changes: 22 additions & 22 deletions sequel_core/lib/sequel_core/core_sql.rb
Expand Up @@ -2,31 +2,31 @@
# code.

class Array
# Return a Sequel::SQL::ComplexExpression created from this array, not matching any of the
# Return a Sequel::SQL::BooleanExpression created from this array, not matching any of the
# conditions.
def ~
sql_expr_if_all_two_pairs(:OR, true)
end

# Return a Sequel::SQL::ComplexExpression created from this array, matching all of the
# Return a Sequel::SQL::BooleanExpression created from this array, matching all of the
# conditions.
def sql_expr
sql_expr_if_all_two_pairs
end

# Return a Sequel::SQL::ComplexExpression created from this array, matching none
# Return a Sequel::SQL::BooleanExpression created from this array, matching none
# of the conditions.
def sql_negate
sql_expr_if_all_two_pairs(:AND, true)
end

# Return a Sequel::SQL::ComplexExpression created from this array, matching any of the
# Return a Sequel::SQL::BooleanExpression created from this array, matching any of the
# conditions.
def sql_or
sql_expr_if_all_two_pairs(:OR)
end

# Return a Sequel::SQL::ComplexExpression representing an SQL string made up of the
# Return a Sequel::SQL::BooleanExpression representing an SQL string made up of the
# concatenation of this array's elements. If an argument is passed
# it is used in between each element of the array in the SQL
# concatenation.
Expand All @@ -41,7 +41,7 @@ def sql_string_join(joiner=nil)
args = self
end
args = args.collect{|a| a.is_one_of?(Symbol, ::Sequel::SQL::Expression, ::Sequel::LiteralString, TrueClass, FalseClass, NilClass) ? a : a.to_s}
::Sequel::SQL::ComplexExpression.new(:'||', *args)
::Sequel::SQL::StringExpression.new(:'||', *args)
end

# Concatenates an array of strings into an SQL string. ANSI SQL and C-style
Expand All @@ -53,50 +53,50 @@ def to_sql

private

# Raise an error if this array is not made up of all two pairs, otherwise create a Sequel::SQL::ComplexExpression from this array.
# Raise an error if this array is not made up of all two pairs, otherwise create a Sequel::SQL::BooleanExpression from this array.
def sql_expr_if_all_two_pairs(*args)
raise(Sequel::Error, 'Not all elements of the array are arrays of size 2, so it cannot be converted to an SQL expression') unless all_two_pairs?
::Sequel::SQL::ComplexExpression.from_value_pairs(self, *args)
::Sequel::SQL::BooleanExpression.from_value_pairs(self, *args)
end
end

class Hash
# Return a Sequel::SQL::ComplexExpression created from this hash, matching
# Return a Sequel::SQL::BooleanExpression created from this hash, matching
# all of the conditions in this hash and the condition specified by
# the given argument.
def &(ce)
::Sequel::SQL::ComplexExpression.new(:AND, self, ce)
::Sequel::SQL::BooleanExpression.new(:AND, self, ce)
end

# Return a Sequel::SQL::ComplexExpression created from this hash, matching
# Return a Sequel::SQL::BooleanExpression created from this hash, matching
# all of the conditions in this hash or the condition specified by
# the given argument.
def |(ce)
::Sequel::SQL::ComplexExpression.new(:OR, self, ce)
::Sequel::SQL::BooleanExpression.new(:OR, self, ce)
end

# Return a Sequel::SQL::ComplexExpression created from this hash, not matching any of the
# Return a Sequel::SQL::BooleanExpression created from this hash, not matching any of the
# conditions.
def ~
::Sequel::SQL::ComplexExpression.from_value_pairs(self, :OR, true)
::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR, true)
end

# Return a Sequel::SQL::ComplexExpression created from this hash, matching all of the
# Return a Sequel::SQL::BooleanExpression created from this hash, matching all of the
# conditions.
def sql_expr
::Sequel::SQL::ComplexExpression.from_value_pairs(self)
::Sequel::SQL::BooleanExpression.from_value_pairs(self)
end

# Return a Sequel::SQL::ComplexExpression created from this hash, matching none
# Return a Sequel::SQL::BooleanExpression created from this hash, matching none
# of the conditions.
def sql_negate
::Sequel::SQL::ComplexExpression.from_value_pairs(self, :AND, true)
::Sequel::SQL::BooleanExpression.from_value_pairs(self, :AND, true)
end

# Return a Sequel::SQL::ComplexExpression created from this hash, matching any of the
# Return a Sequel::SQL::BooleanExpression created from this hash, matching any of the
# conditions.
def sql_or
::Sequel::SQL::ComplexExpression.from_value_pairs(self, :OR)
::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR)
end
end

Expand Down Expand Up @@ -136,7 +136,7 @@ class Symbol

# If no argument is given, returns a Sequel::SQL::ColumnAll object specifying all
# columns for this table.
# If an argument is given, returns a Sequel::SQL::ComplexExpression using the *
# If an argument is given, returns a Sequel::SQL::NumericExpression using the *
# (multiplication) operator with this and the given argument.
def *(ce=(arg=false;nil))
return super(ce) unless arg == false
Expand All @@ -151,7 +151,7 @@ def [](*args)

# If the given argument is an Integer or an array containing an Integer, returns
# a Sequel::SQL::Subscript with this column and the given arg.
# Otherwise returns a Sequel::SQL::ComplexExpression where this column (which should be boolean)
# Otherwise returns a Sequel::SQL::BooleanExpression where this column (which should be boolean)
# or the given argument is true.
def |(sub)
return super unless (Integer === sub) || ((Array === sub) && sub.any?{|x| Integer === x})
Expand Down
32 changes: 16 additions & 16 deletions sequel_core/lib/sequel_core/dataset/sql.rb
Expand Up @@ -52,6 +52,8 @@ def complex_expression_sql(op, args)
"(#{args.collect{|a| literal(a)}.join(" #{op} ")})"
when :NOT
"NOT #{literal(args.at(0))}"
when :NOOP
literal(args.at(0))
else
raise(Sequel::Error, "invalid operator #{op}")
end
Expand Down Expand Up @@ -107,8 +109,8 @@ def exclude(*cond, &block)
cond = cond.first if cond.size == 1
cond = cond.sql_or if (Hash === cond) || ((Array === cond) && (cond.all_two_pairs?))
cond = filter_expr(block || cond)
cond = SQL::ComplexExpression === cond ? ~cond : SQL::ComplexExpression.new(:NOT, cond)
cond = SQL::ComplexExpression.new(:AND, @opts[clause], cond) if @opts[clause]
cond = SQL::BooleanExpression.invert(cond)
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
clone(clause => cond)
end

Expand All @@ -135,7 +137,7 @@ def exists(opts = nil)
# specified.
# * String - taken literally
# * Symbol - taken as a boolean column argument (e.g. WHERE active)
# * Sequel::SQL::ComplexExpression - an existing condition expression,
# * Sequel::SQL::BooleanExpression - an existing condition expression,
# probably created using the Sequel blockless filter DSL.
#
# filter also takes a block, but use of this is discouraged as it requires
Expand Down Expand Up @@ -169,7 +171,7 @@ def filter(*cond, &block)
raise(Error::InvalidFilter, "Invalid filter specified. Did you mean to supply a block?") if cond === true || cond === false
cond = transform_save(cond) if @transform if cond.is_a?(Hash)
cond = filter_expr(block || cond)
cond = SQL::ComplexExpression.new(:AND, @opts[clause], cond) if @opts[clause] && !@opts[clause].blank?
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause] && !@opts[clause].blank?
clone(clause => cond)
end
alias_method :where, :filter
Expand Down Expand Up @@ -215,11 +217,11 @@ def function_sql(f)

# Pattern match any of the columns to any of the terms. The terms can be
# strings (which use LIKE) or regular expressions (which are only supported
# in some databases). See Sequel::SQL::ComplexExpression.like. Note that the
# in some databases). See Sequel::SQL::StringExpression.like. Note that the
# total number of pattern matches will be cols.length * terms.length,
# which could cause performance issues.
def grep(cols, terms)
filter(SQL::ComplexExpression.new(:OR, *Array(cols).collect{|c| SQL::ComplexExpression.like(c, *terms)}))
filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
end

# Returns a copy of the dataset with the results grouped by the value of
Expand Down Expand Up @@ -312,12 +314,8 @@ def invert
having, where = @opts[:having], @opts[:where]
raise(Error, "No current filter") unless having || where
o = {}
if having
o[:having] = SQL::ComplexExpression === having ? ~having : SQL::ComplexExpression.new(:NOT, having)
end
if where
o[:where] = SQL::ComplexExpression === where ? ~where : SQL::ComplexExpression.new(:NOT, where)
end
o[:having] = SQL::BooleanExpression.invert(having) if having
o[:where] = SQL::BooleanExpression.invert(where) if where
clone(o)
end

Expand Down Expand Up @@ -451,7 +449,7 @@ def or(*cond, &block)
clause = (@opts[:having] ? :having : :where)
cond = cond.first if cond.size == 1
if @opts[clause]
clone(clause => SQL::ComplexExpression.new(:OR, @opts[clause], filter_expr(block || cond)))
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(block || cond)))
else
raise Error::NoExistingFilter, "No existing filter found."
end
Expand Down Expand Up @@ -710,21 +708,23 @@ def column_list(columns)
def filter_expr(expr)
case expr
when Hash
SQL::ComplexExpression.from_value_pairs(expr)
SQL::BooleanExpression.from_value_pairs(expr)
when Array
if String === expr[0]
filter_expr(expr.shift.gsub(QUESTION_MARK){literal(expr.shift)}.lit)
else
SQL::ComplexExpression.from_value_pairs(expr)
SQL::BooleanExpression.from_value_pairs(expr)
end
when Proc
expr.to_sql(self).lit
when SQL::NumericExpression, SQL::StringExpression
raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
when Symbol, SQL::Expression
expr
when String
"(#{expr})".lit
else
raise(Sequel::Error, 'Invalid filter argument')
raise(Error, 'Invalid filter argument')
end
end

Expand Down

0 comments on commit e7b2375

Please sign in to comment.