From 2a12b56841bd6fd3998050e7677a1b2c08257479 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 8 Jul 2006 20:35:56 +0000 Subject: r4704@asus: jeremy | 2006-06-27 12:00:19 -0700 decimal r4705@asus: jeremy | 2006-06-27 12:20:47 -0700 current_adapter? checks whether any of its arguments is the name of the current adapter class r4834@asus: jeremy | 2006-07-08 13:08:24 -0700 Room to float. r4835@asus: jeremy | 2006-07-08 13:09:18 -0700 Give lock test a few chances. r4836@asus: jeremy | 2006-07-08 13:12:05 -0700 Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. Closes #5454. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4596 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- .../connection_adapters/abstract/quoting.rb | 16 ++-- .../abstract/schema_definitions.rb | 105 ++++++++++++++++++--- .../abstract/schema_statements.rb | 25 ++++- 3 files changed, 119 insertions(+), 27 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters/abstract') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 05beddac75..1c1b00252c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -16,13 +16,15 @@ module ActiveRecord else "'#{quote_string(value)}'" # ' (for ruby-mode) end - when NilClass then "NULL" - when TrueClass then (column && column.type == :integer ? '1' : quoted_true) - when FalseClass then (column && column.type == :integer ? '0' : quoted_false) - when Float, Fixnum, Bignum then value.to_s - when Date then "'#{value.to_s}'" - when Time, DateTime then "'#{quoted_date(value)}'" - else "'#{quote_string(value.to_yaml)}'" + when NilClass then "NULL" + when TrueClass then (column && column.type == :integer ? '1' : quoted_true) + when FalseClass then (column && column.type == :integer ? '0' : quoted_false) + when Float, Fixnum, Bignum then value.to_s + # BigDecimals need to be output in a non-normalized form and quoted. + when BigDecimal then value.to_s('F') + when Date then "'#{value.to_s}'" + when Time, DateTime then "'#{quoted_date(value)}'" + else "'#{quote_string(value.to_yaml)}'" end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 3398bc68cd..4c46a2fde2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -1,10 +1,12 @@ require 'date' +require 'bigdecimal' +require 'bigdecimal/util' module ActiveRecord module ConnectionAdapters #:nodoc: # An abstract definition of a column in a table. class Column - attr_reader :name, :default, :type, :limit, :null, :sql_type + attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale attr_accessor :primary # Instantiates a new column in the table. @@ -15,6 +17,7 @@ module ActiveRecord # +null+ determines if this column allows +NULL+ values. def initialize(name, default, sql_type = nil, null = true) @name, @sql_type, @null, @limit = name, sql_type, null, extract_limit(sql_type) + @precision, @scale = extract_precision(sql_type), extract_scale(sql_type) # simplified_type may depend on #limit, type_cast depends on #type @type = simplified_type(sql_type) @@ -28,7 +31,7 @@ module ActiveRecord end def number? - [:float, :integer].include? type + [:float, :integer, :decimal].include? type end # Returns the Ruby class that corresponds to the abstract data type. @@ -36,6 +39,7 @@ module ActiveRecord case type when :integer then Fixnum when :float then Float + when :decimal then BigDecimal when :datetime then Time when :date then Date when :timestamp then Time @@ -54,6 +58,7 @@ module ActiveRecord when :text then value when :integer then value.to_i rescue value ? 1 : 0 when :float then value.to_f + when :decimal then self.class.value_to_decimal(value) when :datetime then self.class.string_to_time(value) when :timestamp then self.class.string_to_time(value) when :time then self.class.string_to_dummy_time(value) @@ -70,6 +75,7 @@ module ActiveRecord when :text then nil when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)" when :float then "#{var_name}.to_f" + when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})" when :datetime then "#{self.class.name}.string_to_time(#{var_name})" when :timestamp then "#{self.class.name}.string_to_time(#{var_name})" when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})" @@ -127,10 +133,21 @@ module ActiveRecord # convert something to a boolean def self.value_to_boolean(value) - return value if value==true || value==false - case value.to_s.downcase - when "true", "t", "1" then true - else false + if value == true || value == false + value + else + %w(true t 1).include?(value.to_s.downcase) + end + end + + # convert something to a BigDecimal + def self.value_to_decimal(value) + if value.is_a?(BigDecimal) + value + elsif value.respond_to?(:to_d) + value.to_d + else + value.to_s.to_d end end @@ -142,16 +159,28 @@ module ActiveRecord end def extract_limit(sql_type) - return unless sql_type $1.to_i if sql_type =~ /\((.*)\)/ end + def extract_precision(sql_type) + $2.to_i if sql_type =~ /^(numeric|decimal)\((\d+)(,\d+)?\)/i + end + + def extract_scale(sql_type) + case sql_type + when /^(numeric|decimal)\((\d+)\)/i then 0 + when /^(numeric|decimal)\((\d+)(,(\d+))\)/i then $4.to_i + end + end + def simplified_type(field_type) case field_type when /int/i :integer - when /float|double|decimal|numeric/i + when /float|double/i :float + when /decimal|numeric/i + extract_scale(field_type) == 0 ? :integer : :decimal when /datetime/i :datetime when /timestamp/i @@ -175,17 +204,17 @@ module ActiveRecord class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc: end - class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null) #:nodoc: + class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc: def to_sql - column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit)}" + column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit, precision, scale)}" add_column_options!(column_sql, :null => null, :default => default) column_sql end alias to_s :to_sql private - def type_to_sql(name, limit) - base.type_to_sql(name, limit) rescue name + def type_to_sql(name, limit, precision, scale) + base.type_to_sql(name, limit, precision, scale) rescue name end def add_column_options!(sql, options) @@ -217,9 +246,9 @@ module ActiveRecord # Instantiates a new column for the table. # The +type+ parameter must be one of the following values: # :primary_key, :string, :text, - # :integer, :float, :datetime, - # :timestamp, :time, :date, - # :binary, :boolean. + # :integer, :float, :decimal, + # :datetime, :timestamp, :time, + # :date, :binary, :boolean. # # Available options are (none of these exists by default): # * :limit: @@ -232,6 +261,39 @@ module ActiveRecord # * :null: # Allows or disallows +NULL+ values in the column. This option could # have been named :null_allowed. + # * :precision: + # Specifies the precision for a :decimal column. + # * :scale: + # Specifies the scale for a :decimal column. + # + # Please be aware of different RDBMS implementations behavior with + # :decimal columns: + # * The SQL standard says the default scale should be 0, :scale <= + # :precision, and makes no comments about the requirements of + # :precision. + # * MySQL: :precision [1..63], :scale [0..30]. + # Default is (10,0). + # * PostGres?: :precision [1..infinity], + # :scale [0..infinity]. No default. + # * Sqlite2: Any :precision and :scale may be used. + # Internal storage as strings. No default. + # * Sqlite3: No restrictions on :precision and :scale, + # but the maximum supported :precision is 16. No default. + # * Oracle: :precision [1..38], :scale [-84..127]. + # Default is (38,0). + # * DB2: :precision [1..63], :scale [0..62]. + # Default unknown. + # * Firebird: :precision [1..18], :scale [0..18]. + # Default (9,0). Internal types NUMERIC and DECIMAL have different + # storage rules, decimal being better. + # * FrontBase?: :precision [1..38], :scale [0..38]. + # Default (38,0). WARNING Max :precision/:scale for + # NUMERIC is 19, and DECIMAL is 38. + # * SqlServer?: :precision [1..38], :scale [0..38]. + # Default (38,0). + # * Sybase: :precision [1..38], :scale [0..38]. + # Default (38,0). + # * OpenBase?: Documentation unclear. Claims storage in double. # # This method returns self. # @@ -245,9 +307,22 @@ module ActiveRecord # # td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false) # #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL + # + # def.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2) + # #=> bill_gates_money DECIMAL(15,2) + # + # def.column(:sensor_reading, :decimal, :precision => 30, :scale => 20) + # #=> sensor_reading DECIMAL(30,20) + # + # # While :scale defaults to zero on most databases, it + # # probably wouldn't hurt to include it. + # def.column(:huge_integer, :decimal, :precision => 30) + # #=> huge_integer DECIMAL(30) def column(name, type, options = {}) column = self[name] || ColumnDefinition.new(@base, name, type) column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym] + column.precision = options[:precision] + column.scale = options[:scale] column.default = options[:default] column.null = options[:null] @columns << column unless @columns.include? column diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index b57f2c86f7..542d3d131d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -119,7 +119,7 @@ module ActiveRecord # Adds a new column to the named table. # See TableDefinition#column for details of the options you can use. def add_column(table_name, column_name, type, options = {}) - add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}" + add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" add_column_options!(add_column_sql, options) execute(add_column_sql) end @@ -254,12 +254,27 @@ module ActiveRecord end - def type_to_sql(type, limit = nil) #:nodoc: + def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: native = native_database_types[type] - limit ||= native[:limit] column_type_sql = native[:name] - column_type_sql << "(#{limit})" if limit - column_type_sql + if type == :decimal # ignore limit, use precison and scale + precision ||= native[:precision] + scale ||= native[:scale] + if precision + if scale + column_type_sql << "(#{precision},#{scale})" + else + column_type_sql << "(#{precision})" + end + else + raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specifed" if scale + end + column_type_sql + else + limit ||= native[:limit] + column_type_sql << "(#{limit})" if limit + column_type_sql + end end def add_column_options!(sql, options) #:nodoc: -- cgit v1.2.3