diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/type')
16 files changed, 471 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/type/binary.rb b/activerecord/lib/active_record/connection_adapters/type/binary.rb new file mode 100644 index 0000000000..60afe44de1 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/binary.rb @@ -0,0 +1,19 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Binary < Value + def type + :binary + end + + def binary? + true + end + + def klass + ::String + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/boolean.rb b/activerecord/lib/active_record/connection_adapters/type/boolean.rb new file mode 100644 index 0000000000..0d97379189 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/boolean.rb @@ -0,0 +1,21 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Boolean < Value + def type + :boolean + end + + private + + def cast_value(value) + if value == '' + nil + else + Column::TRUE_VALUES.include?(value) + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/date.rb b/activerecord/lib/active_record/connection_adapters/type/date.rb new file mode 100644 index 0000000000..e8becbe1f4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/date.rb @@ -0,0 +1,44 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Date < Value + def type + :date + end + + def klass + ::Date + end + + private + + def cast_value(value) + if value.is_a?(::String) + return if value.empty? + fast_string_to_date(value) || fallback_string_to_date(value) + elsif value.respond_to?(:to_date) + value.to_date + else + value + end + end + + def fast_string_to_date(string) + if string =~ Column::Format::ISO_DATE + new_date $1.to_i, $2.to_i, $3.to_i + end + end + + def fallback_string_to_date(string) + new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) + end + + def new_date(year, mon, mday) + if year && year != 0 + ::Date.new(year, mon, mday) rescue nil + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/date_time.rb b/activerecord/lib/active_record/connection_adapters/type/date_time.rb new file mode 100644 index 0000000000..64f5d05301 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/date_time.rb @@ -0,0 +1,35 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class DateTime < Value + include TimeValue + + def type + :datetime + end + + private + + def cast_value(string) + return string unless string.is_a?(::String) + return if string.empty? + + fast_string_to_time(string) || fallback_string_to_time(string) + end + + # '0.123456' -> 123456 + # '1.123456' -> 123456 + def microseconds(time) + time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 + end + + def fallback_string_to_time(string) + time_hash = ::Date._parse(string) + time_hash[:sec_fraction] = microseconds(time_hash) + + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/decimal.rb b/activerecord/lib/active_record/connection_adapters/type/decimal.rb new file mode 100644 index 0000000000..e93906ba19 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/decimal.rb @@ -0,0 +1,27 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Decimal < Value + include Numeric + + def type + :decimal + end + + def klass + ::BigDecimal + end + + private + + def cast_value(value) + if value.respond_to?(:to_d) + value.to_d + else + value.to_s.to_d + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/decimal_without_scale.rb b/activerecord/lib/active_record/connection_adapters/type/decimal_without_scale.rb new file mode 100644 index 0000000000..e58c6e198d --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/decimal_without_scale.rb @@ -0,0 +1,13 @@ +require 'active_record/connection_adapters/type/integer' + +module ActiveRecord + module ConnectionAdapters + module Type + class DecimalWithoutScale < Integer # :nodoc: + def type + :decimal + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/float.rb b/activerecord/lib/active_record/connection_adapters/type/float.rb new file mode 100644 index 0000000000..f2427d2dfa --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/float.rb @@ -0,0 +1,25 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Float < Value + include Numeric + + def type + :float + end + + def klass + ::Float + end + + alias type_cast_for_database type_cast + + private + + def cast_value(value) + value.to_f + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb new file mode 100644 index 0000000000..bb1abc77ff --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb @@ -0,0 +1,21 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class HashLookupTypeMap < TypeMap # :nodoc: + delegate :key?, to: :@mapping + + def lookup(type, *args) + @mapping.fetch(type, proc { default_value }).call(type, *args) + end + + def fetch(type, *args, &block) + @mapping.fetch(type, block).call(type, *args) + end + + def alias_type(type, alias_type) + register_type(type) { |_, *args| lookup(alias_type, *args) } + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/integer.rb b/activerecord/lib/active_record/connection_adapters/type/integer.rb new file mode 100644 index 0000000000..596f4de2a8 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/integer.rb @@ -0,0 +1,29 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Integer < Value + include Numeric + + def type + :integer + end + + def klass + ::Fixnum + end + + alias type_cast_for_database type_cast + + private + + def cast_value(value) + case value + when true then 1 + when false then 0 + else value.to_i rescue nil + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/numeric.rb b/activerecord/lib/active_record/connection_adapters/type/numeric.rb new file mode 100644 index 0000000000..a3379831cb --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/numeric.rb @@ -0,0 +1,20 @@ +module ActiveRecord + module ConnectionAdapters + module Type + module Numeric # :nodoc: + def number? + true + end + + def type_cast_for_write(value) + case value + when true then 1 + when false then 0 + when ::String then value.presence + else super + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/string.rb b/activerecord/lib/active_record/connection_adapters/type/string.rb new file mode 100644 index 0000000000..471f949e09 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/string.rb @@ -0,0 +1,29 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class String < Value + def type + :string + end + + def text? + true + end + + def klass + ::String + end + + private + + def cast_value(value) + case value + when true then "1" + when false then "0" + else value.to_s + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/text.rb b/activerecord/lib/active_record/connection_adapters/type/text.rb new file mode 100644 index 0000000000..61095ebb38 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/text.rb @@ -0,0 +1,13 @@ +require 'active_record/connection_adapters/type/string' + +module ActiveRecord + module ConnectionAdapters + module Type + class Text < String + def type + :text + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/time.rb b/activerecord/lib/active_record/connection_adapters/type/time.rb new file mode 100644 index 0000000000..bc331b0fa7 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/time.rb @@ -0,0 +1,28 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Time < Value + include TimeValue + + def type + :time + end + + private + + def cast_value(value) + return value unless value.is_a?(::String) + return if value.empty? + + dummy_time_value = "2000-01-01 #{value}" + + fast_string_to_time(dummy_time_value) || begin + time_hash = ::Date._parse(dummy_time_value) + return if time_hash[:hour].nil? + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/time_value.rb b/activerecord/lib/active_record/connection_adapters/type/time_value.rb new file mode 100644 index 0000000000..e9ca4adeda --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/time_value.rb @@ -0,0 +1,36 @@ +module ActiveRecord + module ConnectionAdapters + module Type + module TimeValue # :nodoc: + def klass + ::Time + end + + private + + def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) + # Treat 0000-00-00 00:00:00 as nil. + return if year.nil? || (year == 0 && mon == 0 && mday == 0) + + if offset + time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil + return unless time + + time -= offset + Base.default_timezone == :utc ? time : time.getlocal + else + ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil + end + end + + # Doesn't handle time zones. + def fast_string_to_time(string) + if string =~ Column::Format::ISO_DATETIME + microsec = ($7.to_r * 1_000_000).to_i + new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/type_map.rb b/activerecord/lib/active_record/connection_adapters/type/type_map.rb new file mode 100644 index 0000000000..48b8b51417 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/type_map.rb @@ -0,0 +1,50 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class TypeMap # :nodoc: + def initialize + @mapping = {} + end + + def lookup(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + default_value + end + end + + def register_type(key, value = nil, &block) + raise ::ArgumentError unless value || block + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + end + + def alias_type(key, target_key) + register_type(key) do |sql_type, *args| + metadata = sql_type[/\(.*\)/, 0] + lookup("#{target_key}#{metadata}", *args) + end + end + + def clear + @mapping.clear + end + + private + + def default_value + @default_value ||= Value.new + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/value.rb b/activerecord/lib/active_record/connection_adapters/type/value.rb new file mode 100644 index 0000000000..60b443004c --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/value.rb @@ -0,0 +1,61 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Value + attr_reader :precision, :scale, :limit + + # Valid options are +precision+, +scale+, and +limit+. + # They are only used when dumping schema. + def initialize(options = {}) + options.assert_valid_keys(:precision, :scale, :limit) + @precision = options[:precision] + @scale = options[:scale] + @limit = options[:limit] + end + + # The simplified that this object represents. Subclasses + # should override this method. + def type; end + + # Takes an input from the database, or from attribute setters, + # and casts it to a type appropriate for this object. This method + # should not be overriden by subclasses. Instead, override `cast_value`. + def type_cast(value) + cast_value(value) unless value.nil? + end + + def type_cast_for_write(value) + value + end + + def type_cast_for_database(value) + type_cast_for_write(value) + end + + def text? + false + end + + def number? + false + end + + def binary? + false + end + + def klass + ::Object + end + + private + + # Responsible for casting values from external sources to the appropriate + # type. Called by `type_cast` for all values except `nil`. + def cast_value(value) # :api: public + value + end + end + end + end +end |