ar_table_name = model_class.table_name
filters.each do |filter|
attrs_in, operator, operand = filter
- if attrs_in == 'any'
+ if attrs_in == 'any' && operator != '@@'
attrs = model_class.searchable_columns(operator)
elsif attrs_in.is_a? Array
attrs = attrs_in
elsif !operator.is_a? String
raise ArgumentError.new("Invalid operator '#{operator}' (#{operator.class}) in filter")
end
+
cond_out = []
+
+ if operator == '@@'
+ # Full-text search
+ if attrs_in != 'any'
+ raise ArgumentError.new("Full text search on individual columns is not supported")
+ end
+ if operand.is_a? Array
+ raise ArgumentError.new("Full text search not supported for array operands")
+ end
+
+ # Skip the generic per-column operator loop below
+ attrs = []
+ # Use to_tsquery since plainto_tsquery does not support prefix
+ # search. And, split operand and join the words with ' & '
+ cond_out << model_class.full_text_tsvector+" @@ to_tsquery(?)"
+ param_out << operand.split.join(' & ')
+ end
attrs.each do |attr|
if !model_class.searchable_columns(operator).index attr.to_s
raise ArgumentError.new("Invalid attribute '#{attr}' in filter")
end
case operator.downcase
- when '=', '<', '<=', '>', '>=', '!=', 'like'
+ when '=', '<', '<=', '>', '>=', '!=', 'like', 'ilike'
attr_type = model_class.attribute_column(attr).type
operator = '<>' if operator == '!='
if operand.is_a? String
"boolean attribute '#{attr}'")
end
end
- cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+ if operator == '<>'
+ # explicitly allow NULL
+ cond_out << "#{ar_table_name}.#{attr} #{operator} ? OR #{ar_table_name}.#{attr} IS NULL"
+ else
+ cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+ end
if (# any operator that operates on value rather than
# representation:
operator.match(/[<=>]/) and (attr_type == :datetime))
end
end
cond_out << cond.join(' OR ')
+ else
+ raise ArgumentError.new("Invalid operator '#{operator}'")
end
end
conds_out << cond_out.join(' OR ') if cond_out.any?