Merge branch 'fix-array-builder-wheres'

This commit is contained in:
eileencodes 2020-05-05 12:48:50 -04:00
commit 70ddb8a704
No known key found for this signature in database
GPG Key ID: BA5C575120BBE8DF
16 changed files with 159 additions and 10 deletions

@ -7,15 +7,21 @@ module Numeric
def serialize(value)
cast(value)
end
alias :unchecked_serialize :serialize
def cast(value)
value = \
# Checks whether the value is numeric. Spaceship operator
# will return nil if value is not numeric.
value = if value <=> 0
value
else
case value
when true then 1
when false then 0
when ::String then value.presence
else value
else value.presence
end
end
super(value)
end

@ -28,15 +28,24 @@ def serialize(value)
ensure_in_range(super)
end
def serializable?(value)
cast_value = cast(value)
in_range?(cast_value) && super
end
private
attr_reader :range
def in_range?(value)
!value || range.member?(value)
end
def cast_value(value)
value.to_i rescue nil
end
def ensure_in_range(value)
if value && !range.cover?(value)
unless in_range?(value)
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
end
value

@ -11,6 +11,14 @@ def initialize(precision: nil, limit: nil, scale: nil)
@limit = limit
end
# Returns true if this type can convert +value+ to a type that is usable
# by the database. For example a boolean type can return +true+ if the
# value parameter is a Ruby boolean, but may return +false+ if the value
# parameter is some other object.
def serializable?(value)
true
end
def type # :nodoc:
end
@ -45,6 +53,7 @@ def cast(value)
def serialize(value)
value
end
alias :unchecked_serialize :serialize
# Type casts a value for schema dumping. This method is private, as we are
# hoping to remove it entirely.

@ -139,12 +139,17 @@ def deserialize(value)
mapping.key(subtype.deserialize(value))
end
def serializable?(value)
(value.blank? || mapping.has_key?(value) || mapping.has_value?(value)) && super
end
def serialize(value)
mapping.fetch(value, value)
end
alias :unchecked_serialize :serialize
def assert_valid_value(value)
unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
unless serializable?(value)
raise ArgumentError, "'#{value}' is not a valid #{name}"
end
end

@ -21,10 +21,21 @@ def call(attribute, value)
when 0 then NullPredicate
when 1 then predicate_builder.build(attribute, values.first)
else
values.map! do |v|
predicate_builder.build_bind_attribute(attribute.name, v)
if nils.empty? && ranges.empty?
type = attribute.type_caster
casted_values = values.map do |raw_value|
type.unchecked_serialize(raw_value) if type.serializable?(raw_value)
end
casted_values.compact!
return Arel::Nodes::HomogeneousIn.new(casted_values, attribute, :in)
else
attribute.in values.map { |v|
predicate_builder.build_bind_attribute(attribute.name, v)
}
end
values.empty? ? NullPredicate : attribute.in(values)
end
unless nils.empty?

@ -137,7 +137,7 @@ def predicates_unreferenced_by(other)
end
def equality_node?(node)
node.respond_to?(:operator) && node.operator == :==
!node.is_a?(String) && node.equality?
end
def invert_predicate(node)

@ -9,10 +9,14 @@ def initialize(types)
def type_cast_for_database(attr_name, value)
return value if value.is_a?(Arel::Nodes::BindParam)
type = types.type_for_attribute(attr_name)
type = type_for_attribute(attr_name)
type.serialize(value)
end
def type_for_attribute(name)
types.type_for_attribute(name)
end
private
attr_reader :types
end

@ -9,6 +9,10 @@ class Attribute < Struct.new :relation, :name
include Arel::OrderPredications
include Arel::Math
def type_caster
relation.type_for_attribute(name)
end
###
# Create a node for lowering this attribute
def lower

@ -18,6 +18,7 @@
# unary
require "arel/nodes/unary"
require "arel/nodes/grouping"
require "arel/nodes/homogeneous_in"
require "arel/nodes/ordering"
require "arel/nodes/ascending"
require "arel/nodes/descending"

@ -5,6 +5,8 @@ module Nodes
class Equality < Arel::Nodes::Binary
def operator; :== end
def equality?; true; end
def invert
Arel::Nodes::NotEqual.new(left, right)
end

@ -0,0 +1,57 @@
# frozen_string_literal: true
module Arel # :nodoc: all
module Nodes
class HomogeneousIn < Node
attr_reader :attribute, :values, :type
def initialize(values, attribute, type)
@values = values
@attribute = attribute
@type = type
end
def hash
ivars.hash
end
def eql?(other)
super || (self.class == other.class && self.ivars == other.ivars)
end
alias :== :eql?
def equality?
true
end
def invert
Arel::Nodes::HomogeneousIn.new(values, attribute, type == :in ? :notin : :in)
end
def left
attribute
end
def table_name
attribute.relation.table_alias || attribute.relation.name
end
def column_name
attribute.name
end
def fetch_attribute(&block)
if attribute
yield attribute
else
expr.fetch_attribute(&block)
end
end
protected
def ivars
[@attribute, @values, @type]
end
end
end
end

@ -44,6 +44,8 @@ def to_sql(engine = Table.engine)
def fetch_attribute
end
def equality?; false; end
end
end
end

@ -19,6 +19,10 @@ def type_cast_for_database(*args)
relation.type_cast_for_database(*args)
end
def type_for_attribute(name)
relation.type_for_attribute(name)
end
def able_to_type_cast?
relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast?
end

@ -100,6 +100,10 @@ def type_cast_for_database(attribute_name, value)
type_caster.type_cast_for_database(attribute_name, value)
end
def type_for_attribute(name)
type_caster.type_for_attribute(name)
end
def able_to_type_cast?
!type_caster.nil?
end

@ -161,6 +161,12 @@ def visit_Arel_Nodes_Casted(o)
visit_edge o, "attribute"
end
def visit_Arel_Nodes_HomogeneousIn(o)
visit_edge o, "values"
visit_edge o, "type"
visit_edge o, "attribute"
end
def visit_Arel_Attribute(o)
visit_edge o, "relation"
visit_edge o, "name"

@ -321,6 +321,31 @@ def visit_Arel_Nodes_Grouping(o, collector)
end
end
def visit_Arel_Nodes_HomogeneousIn(o, collector)
collector.preparable = false
collector << "("
collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name)
if o.type == :in
collector << "IN ("
else
collector << "NOT IN ("
end
values = o.values.map { |v| @connection.quote v }
expr = if values.empty?
@connection.quote(nil)
else
values.join(",")
end
collector << expr
collector << "))"
collector
end
def visit_Arel_SelectManager(o, collector)
collector << "("
visit(o.ast, collector) << ")"