Skip to content
Browse files

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
  • Loading branch information...
1 parent 71234da commit 2a12b56841bd6fd3998050e7677a1b2c08257479 @jeremy jeremy committed
Showing with 561 additions and 151 deletions.
  1. +2 −0 activerecord/CHANGELOG
  2. +1 −1 activerecord/lib/active_record/base.rb
  3. +9 −7 activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
  4. +90 −15 activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
  5. +20 −5 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
  6. +2 −0 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
  7. +1 −0 activerecord/lib/active_record/connection_adapters/db2_adapter.rb
  8. +6 −3 activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb
  9. +4 −1 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  10. +2 −1 activerecord/lib/active_record/connection_adapters/openbase_adapter.rb
  11. +3 −3 activerecord/lib/active_record/connection_adapters/oracle_adapter.rb
  12. +12 −8 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  13. +1 −0 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
  14. +36 −45 activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
  15. +13 −13 activerecord/lib/active_record/connection_adapters/sybase_adapter.rb
  16. +3 −2 activerecord/lib/active_record/migration.rb
  17. +6 −3 activerecord/lib/active_record/schema_dumper.rb
  18. +33 −1 activerecord/test/base_test.rb
  19. +1 −1 activerecord/test/calculations_test.rb
  20. +8 −10 activerecord/test/defaults_test.rb
  21. +1 −0 activerecord/test/fixtures/db_definitions/db2.drop.sql
  22. +9 −0 activerecord/test/fixtures/db_definitions/db2.sql
  23. +2 −0 activerecord/test/fixtures/db_definitions/firebird.drop.sql
  24. +12 −0 activerecord/test/fixtures/db_definitions/firebird.sql
  25. +1 −0 activerecord/test/fixtures/db_definitions/frontbase.drop.sql
  26. +11 −0 activerecord/test/fixtures/db_definitions/frontbase.sql
  27. +1 −0 activerecord/test/fixtures/db_definitions/mysql.drop.sql
  28. +9 −0 activerecord/test/fixtures/db_definitions/mysql.sql
  29. +13 −1 activerecord/test/fixtures/db_definitions/openbase.sql
  30. +2 −0 activerecord/test/fixtures/db_definitions/oracle.drop.sql
  31. +10 −0 activerecord/test/fixtures/db_definitions/oracle.sql
  32. +3 −1 activerecord/test/fixtures/db_definitions/postgresql.drop.sql
  33. +11 −1 activerecord/test/fixtures/db_definitions/postgresql.sql
  34. +1 −0 activerecord/test/fixtures/db_definitions/sqlite.drop.sql
  35. +10 −1 activerecord/test/fixtures/db_definitions/sqlite.sql
  36. +2 −0 activerecord/test/fixtures/db_definitions/sqlserver.drop.sql
  37. +27 −0 activerecord/test/fixtures/db_definitions/sqlserver.sql
  38. +1 −0 activerecord/test/fixtures/db_definitions/sybase.drop.sql
  39. +10 −1 activerecord/test/fixtures/db_definitions/sybase.sql
  40. +15 −0 activerecord/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb
  41. +5 −3 activerecord/test/locking_test.rb
  42. +135 −18 activerecord/test/migration_test.rb
  43. +10 −1 activerecord/test/schema_dumper_test.rb
  44. +7 −5 activerecord/test/validations_test.rb
View
2 activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 [robbat2@gentoo.org, work@ashleymoran.me.uk]
+
* Firebird migrations support. #5337 [Ken Kunz <kennethkunz@gmail.com>]
* PostgreSQL: create/drop as postgres user. #4790 [mail@matthewpainter.co.uk, mlaster@metavillage.com]
View
2 activerecord/lib/active_record/base.rb
@@ -1726,7 +1726,7 @@ def create
if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
self.id = connection.next_sequence_value(self.class.sequence_name)
end
-
+
self.id = connection.insert(
"INSERT INTO #{self.class.table_name} " +
"(#{quoted_column_names.join(', ')}) " +
View
16 activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -16,13 +16,15 @@ def quote(value, column = nil)
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
View
105 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 @@ class Column
# +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 @@ def text?
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 @@ def klass
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 @@ def type_cast(value)
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 @@ def type_cast_code(var_name)
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 @@ def self.string_to_dummy_time(string)
# 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 @@ def self.microseconds(time)
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 @@ def simplified_type(field_type)
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 @@ def [](name)
# Instantiates a new column for the table.
# The +type+ parameter must be one of the following values:
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
- # <tt>:integer</tt>, <tt>:float</tt>, <tt>:datetime</tt>,
- # <tt>:timestamp</tt>, <tt>:time</tt>, <tt>:date</tt>,
- # <tt>:binary</tt>, <tt>:boolean</tt>.
+ # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
+ # <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
+ # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
#
# Available options are (none of these exists by default):
# * <tt>:limit</tt>:
@@ -232,6 +261,39 @@ def [](name)
# * <tt>:null</tt>:
# Allows or disallows +NULL+ values in the column. This option could
# have been named <tt>:null_allowed</tt>.
+ # * <tt>:precision</tt>:
+ # Specifies the precision for a <tt>:decimal</tt> column.
+ # * <tt>:scale</tt>:
+ # Specifies the scale for a <tt>:decimal</tt> column.
+ #
+ # Please be aware of different RDBMS implementations behavior with
+ # <tt>:decimal</tt> columns:
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
+ # <tt>:precision</tt>, and makes no comments about the requirements of
+ # <tt>:precision</tt>.
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
+ # Default is (10,0).
+ # * PostGres?: <tt>:precision</tt> [1..infinity],
+ # <tt>:scale</tt> [0..infinity]. No default.
+ # * Sqlite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
+ # Internal storage as strings. No default.
+ # * Sqlite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
+ # Default is (38,0).
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
+ # Default unknown.
+ # * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18].
+ # Default (9,0). Internal types NUMERIC and DECIMAL have different
+ # storage rules, decimal being better.
+ # * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
+ # Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for
+ # NUMERIC is 19, and DECIMAL is 38.
+ # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
+ # Default (38,0).
+ # * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
+ # Default (38,0).
+ # * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.
#
# This method returns <tt>self</tt>.
#
@@ -245,9 +307,22 @@ def [](name)
#
# 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 <tt>:scale</tt> 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
View
25 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -119,7 +119,7 @@ def drop_table(name)
# 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 @@ def dump_schema_information #:nodoc:
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:
View
2 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,5 +1,7 @@
require 'benchmark'
require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
require 'active_record/connection_adapters/abstract/schema_definitions'
require 'active_record/connection_adapters/abstract/schema_statements'
View
1 activerecord/lib/active_record/connection_adapters/db2_adapter.rb
@@ -162,6 +162,7 @@ def native_database_types
:text => { :name => 'clob', :limit => 32768 },
:integer => { :name => 'int' },
:float => { :name => 'float' },
+ :decimal => { :name => 'decimal' },
:datetime => { :name => 'timestamp' },
:timestamp => { :name => 'timestamp' },
:time => { :name => 'time' },
View
9 activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb
@@ -160,7 +160,7 @@ def initialize(base, name, type, typename, limit, precision, scale, default, nul
@default = default
@null = nullable == "YES"
@text = [:string, :text].include? @type
- @number = [:float, :integer].include? @type
+ @number = [:float, :integer, :decimal].include? @type
@fb_autogen = false
if @default
@@ -278,6 +278,7 @@ def native_database_types #:nodoc
:text => { :name => "CLOB" },
:integer => { :name => "INTEGER" },
:float => { :name => "FLOAT" },
+ :decimal => { :name => "DECIMAL" },
:datetime => { :name => "TIMESTAMP" },
:timestamp => { :name => "TIMESTAMP" },
:time => { :name => "TIME" },
@@ -319,6 +320,8 @@ def quote(value, column = nil)
end
when :float
value.to_f.to_s
+ when :decimal
+ value.to_d.to_s("F")
when :datetime, :timestamp
"TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
when :time
@@ -359,7 +362,7 @@ def quote(value, column = nil)
if column && column.type == :binary
s = value.unpack("H*").first
"X'#{s}'"
- elsif column && [:integer, :float].include?(column.type)
+ elsif column && [:integer, :float, :decimal].include?(column.type)
value.to_s
else
"'#{quote_string(value)}'" # ' (for ruby-mode)
@@ -370,7 +373,7 @@ def quote(value, column = nil)
(column && column.type == :integer ? '1' : quoted_true)
when FalseClass
(column && column.type == :integer ? '0' : quoted_false)
- when Float, Fixnum, Bignum
+ when Float, Fixnum, Bignum, BigDecimal
value.to_s
when Time, Date, DateTime
if column
View
5 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -102,6 +102,7 @@ def native_database_types #:nodoc
:text => { :name => "text" },
:integer => { :name => "int", :limit => 11 },
:float => { :name => "float" },
+ :decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "datetime" },
:time => { :name => "time" },
@@ -118,6 +119,8 @@ def quote(value, column = nil)
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
s = column.class.string_to_binary(value).unpack("H*")[0]
"x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ "'#{value.to_s("F")}'"
else
super
end
@@ -312,7 +315,7 @@ def change_column(table_name, column_name, type, options = {}) #:nodoc:
options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
end
- change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
+ change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
add_column_options!(change_column_sql, options)
execute(change_column_sql)
end
View
3 activerecord/lib/active_record/connection_adapters/openbase_adapter.rb
@@ -32,7 +32,7 @@ class OpenBaseColumn < Column #:nodoc:
private
def simplified_type(field_type)
return :integer if field_type.downcase =~ /long/
- return :float if field_type.downcase == "money"
+ return :decimal if field_type.downcase == "money"
return :binary if field_type.downcase == "object"
super
end
@@ -68,6 +68,7 @@ def native_database_types
:text => { :name => "text" },
:integer => { :name => "integer" },
:float => { :name => "float" },
+ :decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "timestamp" },
:time => { :name => "time" },
View
6 activerecord/lib/active_record/connection_adapters/oracle_adapter.rb
@@ -93,9 +93,8 @@ def type_cast(value)
def simplified_type(field_type)
return :boolean if OracleAdapter.emulate_booleans && field_type == 'NUMBER(1)'
case field_type
- when /num/i : @scale == 0 ? :integer : :float
- when /date|time/i : :datetime
- else super
+ when /date|time/i then :datetime
+ else super
end
end
@@ -161,6 +160,7 @@ def native_database_types #:nodoc
:text => { :name => "CLOB" },
:integer => { :name => "NUMBER", :limit => 38 },
:float => { :name => "NUMBER" },
+ :decimal => { :name => "DECIMAL" },
:datetime => { :name => "DATE" },
:timestamp => { :name => "DATE" },
:time => { :name => "DATE" },
View
20 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -93,6 +93,7 @@ def native_database_types
:text => { :name => "text" },
:integer => { :name => "integer" },
:float => { :name => "float" },
+ :decimal => { :name => "decimal" },
:datetime => { :name => "timestamp" },
:timestamp => { :name => "timestamp" },
:time => { :name => "time" },
@@ -232,9 +233,9 @@ def indexes(table_name, name = nil) #:nodoc:
end
def columns(table_name, name = nil) #:nodoc:
- column_definitions(table_name).collect do |name, type, default, notnull|
- Column.new(name, default_value(default), translate_field_type(type),
- notnull == "f")
+ column_definitions(table_name).collect do |name, type, default, notnull, typmod|
+ # typmod now unused as limit, precision, scale all handled by superclass
+ Column.new(name, default_value(default), translate_field_type(type), notnull == "f")
end
end
@@ -346,12 +347,12 @@ def add_column(table_name, column_name, type, options = {})
def change_column(table_name, column_name, type, options = {}) #:nodoc:
begin
- execute "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
rescue ActiveRecord::StatementInvalid
# This is PG7, so we use a more arcane way of doing it.
begin_db_transaction
add_column(table_name, "#{column_name}_ar_tmp", type, options)
- execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
+ execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
remove_column(table_name, column_name)
rename_column(table_name, "#{column_name}_ar_tmp", column_name)
commit_db_transaction
@@ -360,18 +361,18 @@ def change_column(table_name, column_name, type, options = {}) #:nodoc:
end
def change_column_default(table_name, column_name, default) #:nodoc:
- execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT '#{default}'"
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT '#{default}'"
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
- execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
+ execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
end
def remove_index(table_name, options) #:nodoc:
execute "DROP INDEX #{index_name(table_name, options)}"
end
- def type_to_sql(type, limit = nil) #:nodoc:
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
return super unless type.to_s == 'integer'
if limit.nil? || limit == 4
@@ -385,6 +386,7 @@ def type_to_sql(type, limit = nil) #:nodoc:
private
BYTEA_COLUMN_TYPE_OID = 17
+ NUMERIC_COLUMN_TYPE_OID = 1700
TIMESTAMPOID = 1114
TIMESTAMPTZOID = 1184
@@ -417,6 +419,8 @@ def select(sql, name = nil)
column = unescape_bytea(column)
when TIMESTAMPTZOID, TIMESTAMPOID
column = cast_to_time(column)
+ when NUMERIC_COLUMN_TYPE_OID
+ column = column.to_d if column.respond_to?(:to_d)
end
hashed_row[fields[cel_index]] = column
View
1 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -110,6 +110,7 @@ def native_database_types #:nodoc:
:text => { :name => "text" },
:integer => { :name => "integer" },
:float => { :name => "float" },
+ :decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "datetime" },
:time => { :name => "datetime" },
View
81 activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
@@ -1,5 +1,8 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'bigdecimal'
+require 'bigdecimal/util'
+
# sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
#
# Author: Joey Gibson <joey@joeygibson.com>
@@ -45,45 +48,37 @@ def self.sqlserver_connection(config) #:nodoc:
end # class Base
module ConnectionAdapters
- class ColumnWithIdentity < Column# :nodoc:
- attr_reader :identity, :is_special, :scale
+ class SQLServerColumn < Column# :nodoc:
+ attr_reader :identity, :is_special
- def initialize(name, default, sql_type = nil, is_identity = false, null = true, scale_value = 0)
+ def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0
super(name, default, sql_type, null)
- @identity = is_identity
- @is_special = sql_type =~ /text|ntext|image/i ? true : false
- @scale = scale_value
+ @identity = identity
+ @is_special = sql_type =~ /text|ntext|image/i
+ # TODO: check ok to remove @scale = scale_value
# SQL Server only supports limits on *char and float types
@limit = nil unless @type == :float or @type == :string
end
def simplified_type(field_type)
case field_type
- when /int|bigint|smallint|tinyint/i then :integer
- when /float|double|decimal|money|numeric|real|smallmoney/i then @scale == 0 ? :integer : :float
- when /datetime|smalldatetime/i then :datetime
- when /timestamp/i then :timestamp
- when /time/i then :time
- when /text|ntext/i then :text
- when /binary|image|varbinary/i then :binary
- when /char|nchar|nvarchar|string|varchar/i then :string
- when /bit/i then :boolean
- when /uniqueidentifier/i then :string
+ when /money/i then :decimal
+ when /image/i then :binary
+ when /bit/i then :boolean
+ when /uniqueidentifier/i then :string
+ else super
end
end
def type_cast(value)
return nil if value.nil? || value =~ /^\s*null\s*$/i
case type
- when :string then value
- when :integer then value == true || value == false ? value == true ? 1 : 0 : value.to_i
- when :float then value.to_f
when :datetime then cast_to_datetime(value)
when :timestamp then cast_to_time(value)
when :time then cast_to_time(value)
when :date then cast_to_datetime(value)
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
- else value
+ else super
end
end
@@ -184,12 +179,13 @@ def native_database_types
:text => { :name => "text" },
:integer => { :name => "int" },
:float => { :name => "float", :limit => 8 },
+ :decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "datetime" },
:time => { :name => "datetime" },
:date => { :name => "datetime" },
- :binary => { :name => "image"},
- :boolean => { :name => "bit"}
+ :binary => { :name => "image" },
+ :boolean => { :name => "bit" }
}
end
@@ -240,7 +236,16 @@ def columns(table_name, name = nil)
return [] if table_name.blank?
table_name = table_name.to_s if table_name.is_a?(Symbol)
table_name = table_name.split('.')[-1] unless table_name.nil?
- sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, IS_NULLABLE As IsNullable, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '#{table_name}'"
+ sql = "SELECT COLUMN_NAME as ColName,
+ COLUMN_DEFAULT as DefaultValue,
+ DATA_TYPE as ColType,
+ IS_NULLABLE As IsNullable,
+ COL_LENGTH('#{table_name}', COLUMN_NAME) as Length,
+ COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity,
+ NUMERIC_PRECISION as [Precision],
+ NUMERIC_SCALE as Scale
+ FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_NAME = '#{table_name}'"
# Comment out if you want to have the Columns select statment logged.
# Personally, I think it adds unnecessary bloat to the log.
# If you do comment it out, make sure to un-comment the "result" line that follows
@@ -249,10 +254,14 @@ def columns(table_name, name = nil)
columns = []
result.each do |field|
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
- type = "#{field[:ColType]}(#{field[:Length]})"
+ if field[:ColType] =~ /numeric|decimal/i
+ type = "#{field[:ColType]}(#{field[:Precision]},#{field[:Scale]})"
+ else
+ type = "#{field[:ColType]}(#{field[:Length]})"
+ end
is_identity = field[:IsIdentity] == 1
is_nullable = field[:IsNullable] == 'YES'
- columns << ColumnWithIdentity.new(field[:ColName], default, type, is_identity, is_nullable, field[:Scale])
+ columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
end
columns
end
@@ -336,19 +345,10 @@ def quote(value, column = nil)
return value.quoted_id if value.respond_to?(:quoted_id)
case value
- when String
- if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
- "'#{quote_string(column.class.string_to_binary(value))}'"
- else
- "'#{quote_string(value)}'"
- end
- when NilClass then "NULL"
when TrueClass then '1'
when FalseClass then '0'
- when Float, Fixnum, Bignum then value.to_s
- when Date then "'#{value.to_s}'"
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
- else "'#{quote_string(value.to_yaml)}'"
+ else super
end
end
@@ -459,7 +459,7 @@ def rename_column(table, column, new_column_name)
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
- sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"]
+ sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
if options[:default]
remove_default_constraint(table_name, column_name)
sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
@@ -485,15 +485,6 @@ def remove_index(table_name, options = {})
execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
end
- def type_to_sql(type, limit = nil) #:nodoc:
- native = native_database_types[type]
- # if there's no :limit in the default type definition, assume that type doesn't support limits
- limit = limit || native[:limit]
- column_type_sql = native[:name]
- column_type_sql << "(#{limit})" if limit
- column_type_sql
- end
-
private
def select(sql, name = nil)
rows = []
View
26 activerecord/lib/active_record/connection_adapters/sybase_adapter.rb
@@ -86,14 +86,15 @@ def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, pr
def simplified_type(field_type)
case field_type
- when /int|bigint|smallint|tinyint/i then :integer
- when /float|double|decimal|money|numeric|real|smallmoney/i then :float
- when /text|ntext/i then :text
- when /binary|image|varbinary/i then :binary
- when /char|nchar|nvarchar|string|varchar/i then :string
- when /bit/i then :boolean
- when /datetime|smalldatetime/i then :datetime
- else super
+ when /int|bigint|smallint|tinyint/i then :integer
+ when /float|double|real/i then :float
+ when /decimal|money|numeric|smallmoney/i then :decimal
+ when /text|ntext/i then :text
+ when /binary|image|varbinary/i then :binary
+ when /char|nchar|nvarchar|string|varchar/i then :string
+ when /bit/i then :boolean
+ when /datetime|smalldatetime/i then :datetime
+ else super
end
end
@@ -137,6 +138,7 @@ def native_database_types
:text => { :name => "text" },
:integer => { :name => "int" },
:float => { :name => "float", :limit => 8 },
+ :decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "timestamp" },
:time => { :name => "time" },
@@ -287,18 +289,16 @@ def quote(value, column = nil)
when NilClass then (column && column.type == :boolean) ? '0' : "NULL"
when TrueClass then '1'
when FalseClass then '0'
- when Float, Fixnum, Bignum
- force_numeric?(column) ? value.to_s : "'#{value.to_s}'"
- when Date then "'#{value.to_s}'"
+ when Float, Fixnum, Bignum then force_numeric?(column) ? value.to_s : "'#{value.to_s}'"
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
- else "'#{quote_string(value.to_yaml)}'"
+ else super
end
end
# True if column is explicitly declared non-numeric, or
# if column is nil (not specified).
def force_numeric?(column)
- (column.nil? || [:integer, :float].include?(column.type))
+ (column.nil? || [:integer, :float, :decimal].include?(column.type))
end
def quote_string(s)
View
5 activerecord/lib/active_record/migration.rb
@@ -64,8 +64,9 @@ def initialize(version)
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
# named +column_name+ specified to be one of the following types:
- # :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean. A default value can be specified
- # by passing an +options+ hash like { :default => 11 }.
+ # :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time,
+ # :date, :binary, :boolean. A default value can be specified by passing an
+ # +options+ hash like { :default => 11 }.
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
# parameters as add_column.
View
9 activerecord/lib/active_record/schema_dumper.rb
@@ -1,4 +1,5 @@
require 'stringio'
+require 'bigdecimal'
module ActiveRecord
# This class is used to dump the database schema for some connection to some
@@ -90,13 +91,15 @@ def table(table, stream)
spec = {}
spec[:name] = column.name.inspect
spec[:type] = column.type.inspect
- spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit]
- spec[:default] = column.default.inspect if !column.default.nil?
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
+ spec[:precision] = column.precision.inspect if !column.precision.nil?
+ spec[:scale] = column.scale.inspect if !column.scale.nil?
spec[:null] = 'false' if !column.null
+ spec[:default] = (column.default.is_a?(BigDecimal) ? column.default.to_s : column.default.inspect) if !column.default.nil?
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
spec
end.compact
- keys = [:name, :type, :limit, :default, :null] & column_specs.map{ |spec| spec.keys }.inject([]){ |a,b| a | b }
+ keys = [:name, :type, :limit, :precision, :scale, :default, :null] & column_specs.map{ |spec| spec.keys }.inject([]){ |a,b| a | b }
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
format_string = lengths.map{ |len| "%-#{len}s" }.join("")
column_specs.each do |colspec|
View
34 activerecord/test/base_test.rb
@@ -910,12 +910,44 @@ def test_geometric_content
end
end
+ class NumericData < ActiveRecord::Base
+ self.table_name = 'numeric_data'
+ end
+
+ def test_numeric_fields
+ m = NumericData.new(
+ :bank_balance => 1586.43,
+ :big_bank_balance => BigDecimal("1000234000567.95"),
+ :world_population => 6000000000,
+ :my_house_population => 3
+ )
+ assert m.save
+
+ m1 = NumericData.find(m.id)
+ assert_not_nil m1
+
+ # As with migration_test.rb, we should make world_population >= 2**62
+ # to cover 64-bit platforms and test it is a Bignum, but the main thing
+ # is that it's an Integer.
+ assert_kind_of Integer, m1.world_population
+ assert_equal 6000000000, m1.world_population
+
+ assert_kind_of Fixnum, m1.my_house_population
+ assert_equal 3, m1.my_house_population
+
+ assert_kind_of BigDecimal, m1.bank_balance
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
+
+ assert_kind_of BigDecimal, m1.big_bank_balance
+ assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
+ end
+
def test_auto_id
auto = AutoId.new
auto.save
assert (auto.id > 0)
end
-
+
def quote_column_name(name)
"<#{name}>"
end
View
2 activerecord/test/calculations_test.rb
@@ -13,8 +13,8 @@ def test_should_sum_field
def test_should_average_field
value = Account.average(:credit_limit)
- assert_equal 53, value
assert_kind_of Float, value
+ assert_in_delta 53.0, value, 0.001
end
def test_should_get_maximum_of_field
View
18 activerecord/test/defaults_test.rb
@@ -1,18 +1,16 @@
require 'abstract_unit'
require 'fixtures/default'
-class DefaultsTest < Test::Unit::TestCase
- if %w(PostgreSQL).include? ActiveRecord::Base.connection.adapter_name
+if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)
+ class DefaultsTest < Test::Unit::TestCase
def test_default_integers
default = Default.new
- assert_instance_of(Fixnum, default.positive_integer)
- assert_equal(default.positive_integer, 1)
- assert_instance_of(Fixnum, default.negative_integer)
- assert_equal(default.negative_integer, -1)
- end
- else
- def test_dummy
- assert true
+ assert_instance_of Fixnum, default.positive_integer
+ assert_equal 1, default.positive_integer
+ assert_instance_of Fixnum, default.negative_integer
+ assert_equal -1, default.negative_integer
+ assert_instance_of BigDecimal, default.decimal_number
+ assert_equal BigDecimal.new("2.78"), default.decimal_number
end
end
end
View
1 activerecord/test/fixtures/db_definitions/db2.drop.sql
@@ -28,3 +28,4 @@ DROP TABLE fk_test_has_pk;
DROP TABLE fk_test_has_fk;
DROP TABLE keyboards;
DROP TABLE legacy_things;
+DROP TABLE numeric_data;
View
9 activerecord/test/fixtures/db_definitions/db2.sql
@@ -215,3 +215,12 @@ CREATE TABLE legacy_things (
version INT DEFAULT 0,
PRIMARY KEY (id)
);
+
+CREATE TABLE numeric_data (
+ id INT NOT NULL PRIMARY KEY,
+ bank_balance DECIMAL(10,2),
+ big_bank_balance DECIMAL(15,2),
+ world_population DECIMAL(10),
+ my_house_population DECIMAL(2),
+ decimal_number_with_default DECIMAL(3,2) DEFAULT 2.78
+);
View
2 activerecord/test/fixtures/db_definitions/firebird.drop.sql
@@ -29,6 +29,7 @@ DROP TABLE fk_test_has_pk;
DROP TABLE keyboards;
DROP TABLE defaults;
DROP TABLE legacy_things;
+DROP TABLE numeric_data;
DROP DOMAIN D_BOOLEAN;
@@ -57,3 +58,4 @@ DROP GENERATOR categories_seq;
DROP GENERATOR keyboards_seq;
DROP GENERATOR defaults_seq;
DROP GENERATOR legacy_things_seq;
+DROP GENERATOR numeric_data_seq;
View
12 activerecord/test/fixtures/db_definitions/firebird.sql
@@ -283,3 +283,15 @@ CREATE TABLE legacy_things (
);
CREATE GENERATOR legacy_things_seq;
SET GENERATOR legacy_things_seq TO 10000;
+
+CREATE TABLE numeric_data (
+ id BIGINT NOT NULL,
+ bank_balance DECIMAL(10,2),
+ big_bank_balance DECIMAL(15,2),
+ world_population DECIMAL(10),
+ my_house_population DECIMAL(2),
+ decimal_number_with_default DECIMAL(3,2) DEFAULT 2.78,
+ PRIMARY KEY (id)
+);
+CREATE GENERATOR numeric_data_seq;
+SET GENERATOR numeric_data_seq TO 10000;
View
1 activerecord/test/fixtures/db_definitions/frontbase.drop.sql
@@ -28,3 +28,4 @@ DROP TABLE fk_test_has_fk CASCADE;
DROP TABLE fk_test_has_pk CASCADE;
DROP TABLE keyboards CASCADE;
DROP TABLE legacy_things CASCADE;
+DROP TABLE numeric_data CASCADE;
View
11 activerecord/test/fixtures/db_definitions/frontbase.sql
@@ -249,3 +249,14 @@ create table "legacy_things"
primary key ("id")
);
SET UNIQUE FOR legacy_things(id);
+
+CREATE TABLE "numeric_data" (
+ "id" integer NOT NULL
+ "bank_balance" DECIMAL(10,2),
+ "big_bank_balance" DECIMAL(15,2),
+ "world_population" DECIMAL(10),
+ "my_house_population" DECIMAL(2),
+ "decimal_number_with_default" DECIMAL(3,2) DEFAULT 2.78,
+ primary key ("id")
+);
+SET UNIQUE FOR numeric_data(id);
View
1 activerecord/test/fixtures/db_definitions/mysql.drop.sql
@@ -28,3 +28,4 @@ DROP TABLE fk_test_has_fk;
DROP TABLE fk_test_has_pk;
DROP TABLE keyboards;
DROP TABLE legacy_things;
+DROP TABLE numeric_data;
View
9 activerecord/test/fixtures/db_definitions/mysql.sql
@@ -217,3 +217,12 @@ CREATE TABLE `legacy_things` (
`version` int(11) NOT NULL default 0,
PRIMARY KEY (`id`)
) TYPE=InnoDB;
+
+CREATE TABLE `numeric_data` (
+ `id` INTEGER NOT NULL PRIMARY KEY,
+ `bank_balance` decimal(10,2),
+ `big_bank_balance` decimal(15,2),
+ `world_population` decimal(10),
+ `my_house_population` decimal(2),
+ `decimal_number_with_default` decimal(3,2) DEFAULT 2.78
+) TYPE=InnoDB;
View
14 activerecord/test/fixtures/db_definitions/openbase.sql
@@ -279,4 +279,16 @@ CREATE TABLE legacy_things (
)
go
CREATE PRIMARY KEY legacy_things (id)
-go
+go
+
+CREATE TABLE numeric_data (
+ id INTEGER NOT NULL DEFAULT _rowid,
+ bank_balance DECIMAL(10,2),
+ big_bank_balance DECIMAL(15,2),
+ world_population DECIMAL(10),
+ my_house_population DECIMAL(2),
+ decimal_number_with_default DECIMAL(3,2) DEFAULT 2.78
+);
+go
+CREATE PRIMARY KEY numeric_data (id)
+go
View
2 activerecord/test/fixtures/db_definitions/oracle.drop.sql
@@ -29,6 +29,7 @@ drop table fk_test_has_pk;
drop table fk_test_has_fk;
drop table keyboards;
drop table legacy_things;
+drop table numeric_data;
drop sequence accounts_seq;
drop sequence funny_jokes_seq;
@@ -59,3 +60,4 @@ drop sequence fk_test_has_pk_seq;
drop sequence fk_test_has_fk_seq;
drop sequence keyboards_seq;
drop sequence legacy_things_seq;
+drop sequence numeric_data_seq;
View
10 activerecord/test/fixtures/db_definitions/oracle.sql
@@ -290,3 +290,13 @@ create table legacy_things (
version integer default 0
);
create sequence legacy_things_seq minvalue 10000;
+
+CREATE TABLE numeric_data (
+ id integer NOT NULL PRIMARY KEY,
+ bank_balance decimal(10,2),
+ big_bank_balance decimal(15,2),
+ world_population decimal(10),
+ my_house_population decimal(2),
+ decimal_number_with_default decimal(3,2) DEFAULT 2.78
+);
+create sequence numeric_data_seq minvalue 10000;
View
4 activerecord/test/fixtures/db_definitions/postgresql.drop.sql
@@ -1,5 +1,5 @@
-DROP SEQUENCE accounts_id_seq;
DROP TABLE accounts;
+DROP SEQUENCE accounts_id_seq;
DROP TABLE funny_jokes;
DROP TABLE companies;
DROP SEQUENCE companies_nonstd_seq;
@@ -32,3 +32,5 @@ DROP TABLE fk_test_has_pk;
DROP TABLE geometrics;
DROP TABLE keyboards;
DROP TABLE legacy_things;
+DROP TABLE numeric_data;
+DROP TABLE column_data;
View
12 activerecord/test/fixtures/db_definitions/postgresql.sql
@@ -118,7 +118,8 @@ CREATE TABLE defaults (
char2 character varying(50) default 'a varchar field',
char3 text default 'a text field',
positive_integer integer default 1,
- negative_integer integer default -1
+ negative_integer integer default -1,
+ decimal_number decimal(3,2) default 2.78
);
CREATE TABLE auto_id_tests (
@@ -246,3 +247,12 @@ CREATE TABLE legacy_things (
tps_report_number integer,
version integer default 0
);
+
+CREATE TABLE numeric_data (
+ id serial primary key,
+ bank_balance decimal(10,2),
+ big_bank_balance decimal(15,2),
+ world_population decimal(10),
+ my_house_population decimal(2),
+ decimal_number_with_default decimal(3,2) default 2.78
+);
View
1 activerecord/test/fixtures/db_definitions/sqlite.drop.sql
@@ -28,3 +28,4 @@ DROP TABLE fk_test_has_fk;
DROP TABLE fk_test_has_pk;
DROP TABLE keyboards;
DROP TABLE legacy_things;
+DROP TABLE numeric_data;
View
11 activerecord/test/fixtures/db_definitions/sqlite.sql
@@ -198,4 +198,13 @@ CREATE TABLE 'legacy_things' (
'id' INTEGER NOT NULL PRIMARY KEY,
'tps_report_number' INTEGER DEFAULT NULL,
'version' INTEGER NOT NULL DEFAULT 0
-)
+);
+
+CREATE TABLE 'numeric_data' (
+ 'id' INTEGER NOT NULL PRIMARY KEY,
+ 'bank_balance' DECIMAL(10,2),
+ 'big_bank_balance' DECIMAL(15,2),
+ 'world_population' DECIMAL(10),
+ 'my_house_population' DECIMAL(2),
+ 'decimal_number_with_default' DECIMAL(3,2) DEFAULT 2.78
+);
View
2 activerecord/test/fixtures/db_definitions/sqlserver.drop.sql
@@ -10,6 +10,7 @@ DROP TABLE orders;
DROP TABLE movies;
DROP TABLE subscribers;
DROP TABLE booleantests;
+DROP TABLE defaults;
DROP TABLE auto_id_tests;
DROP TABLE entrants;
DROP TABLE colnametests;
@@ -28,3 +29,4 @@ DROP TABLE fk_test_has_fk;
DROP TABLE fk_test_has_pk;
DROP TABLE keyboards;
DROP TABLE legacy_things;
+DROP TABLE numeric_data;
View
27 activerecord/test/fixtures/db_definitions/sqlserver.sql
@@ -88,6 +88,24 @@ CREATE TABLE booleantests (
value bit default NULL
);
+CREATE TABLE defaults (
+ id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
+-- these brought from the PostgreSQL defaults_test.rb but
+-- tests only exist for integers and decimals, currently
+-- modified_date date default CURRENT_DATE,
+-- modified_date_function date default now(),
+-- fixed_date date default '2004-01-01',
+-- modified_time timestamp default CURRENT_TIMESTAMP,
+-- modified_time_function timestamp default now(),
+-- fixed_time timestamp default '2004-01-01 00:00:00.000000-00',
+-- char1 char(1) default 'Y',
+-- char2 character varying(50) default 'a varchar field',
+-- char3 text default 'a text field',
+ positive_integer integer default 1,
+ negative_integer integer default -1,
+ decimal_number decimal(3,2) default 2.78
+);
+
CREATE TABLE auto_id_tests (
auto_id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
value int default NULL
@@ -201,3 +219,12 @@ CREATE TABLE legacy_things (
version int default 0,
PRIMARY KEY (id)
);
+
+CREATE TABLE numeric_data (
+ id int NOT NULL IDENTITY(1, 1),
+ bank_balance decimal(10,2),
+ big_bank_balance decimal(15,2),
+ world_population decimal(10),
+ my_house_population decimal(2),
+ decimal_number_with_default decimal(3,2) DEFAULT 2.78
+);
View
1 activerecord/test/fixtures/db_definitions/sybase.drop.sql
@@ -28,4 +28,5 @@ DROP TABLE fk_test_has_fk
DROP TABLE fk_test_has_pk
DROP TABLE keyboards
DROP TABLE legacy_things
+DROP TABLE numeric_data
go
View
11 activerecord/test/fixtures/db_definitions/sybase.sql
@@ -200,5 +200,14 @@ CREATE TABLE legacy_things (
version int default 0,
)
-go
+CREATE TABLE numeric_data (
+ id numeric((9,0) IDENTITY PRIMARY KEY,
+ bank_balance numeric(10,2),
+ big_bank_balance numeric(15,2),
+ world_population numeric(10),
+ my_house_population numeric(2),
+ decimal_number_with_default numeric(3,2) DEFAULT 2.78
+)
+
+go
View
15 activerecord/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb
@@ -0,0 +1,15 @@
+class GiveMeBigNumbers < ActiveRecord::Migration
+ def self.up
+ create_table :big_numbers do |table|
+ table.column :bank_balance, :decimal, :precision => 10, :scale => 2
+ table.column :big_bank_balance, :decimal, :precision => 15, :scale => 2
+ table.column :world_population, :decimal, :precision => 10
+ table.column :my_house_population, :decimal, :precision => 2
+ table.column :value_of_e, :decimal
+ end
+ end
+
+ def self.down
+ drop_table :big_numbers
+ end
+end
View
8 activerecord/test/locking_test.rb
@@ -115,12 +115,14 @@ def test_no_locks_no_wait
end
def test_second_lock_waits
- first, second = duel { Person.find 1, :lock => true }
- assert second.end > first.end
+ assert [0.2, 1, 5].any? { |zzz|
+ first, second = duel(zzz) { Person.find 1, :lock => true }
+ second.end > first.end
+ }
end
protected
- def duel(zzz = 1.0)
+ def duel(zzz = 5)
t0, t1, t2, t3 = nil, nil, nil, nil
a = Thread.new do
View
153 activerecord/test/migration_test.rb
@@ -1,10 +1,15 @@
require 'abstract_unit'
+require 'bigdecimal/util'
+
require 'fixtures/person'
require 'fixtures/topic'
require File.dirname(__FILE__) + '/fixtures/migrations/1_people_have_last_names'
require File.dirname(__FILE__) + '/fixtures/migrations/2_we_need_reminders'
+require File.dirname(__FILE__) + '/fixtures/migrations_with_decimal/1_give_me_big_numbers'
if ActiveRecord::Base.connection.supports_migrations?
+ class BigNumber < ActiveRecord::Base; end
+
class Reminder < ActiveRecord::Base; end
class ActiveRecord::Migration
@@ -29,20 +34,15 @@ def teardown
ActiveRecord::Base.connection.initialize_schema_information
ActiveRecord::Base.connection.update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0"
- Reminder.connection.drop_table("reminders") rescue nil
- Reminder.connection.drop_table("people_reminders") rescue nil
- Reminder.connection.drop_table("prefix_reminders_suffix") rescue nil
+ %w(reminders people_reminders prefix_reminders_suffix).each do |table|
+ Reminder.connection.drop_table(table) rescue nil
+ end
Reminder.reset_column_information
- Person.connection.remove_column("people", "last_name") rescue nil
- Person.connection.remove_column("people", "key") rescue nil
- Person.connection.remove_column("people", "bio") rescue nil
- Person.connection.remove_column("people", "age") rescue nil
- Person.connection.remove_column("people", "height") rescue nil
- Person.connection.remove_column("people", "birthday") rescue nil
- Person.connection.remove_column("people", "favorite_day") rescue nil
- Person.connection.remove_column("people", "male") rescue nil
- Person.connection.remove_column("people", "administrator") rescue nil
+ %w(last_name key bio age height wealth birthday favorite_day
+ mail administrator).each do |column|
+ Person.connection.remove_column('people', column) rescue nil
+ end
Person.connection.remove_column("people", "first_name") rescue nil
Person.connection.add_column("people", "first_name", :string, :limit => 40)
Person.reset_column_information
@@ -187,23 +187,74 @@ def test_add_column_not_null_with_default
Person.connection.drop_table :testings rescue nil
end
+ # We specifically do a manual INSERT here, and then test only the SELECT
+ # functionality. This allows us to more easily catch INSERT being broken,
+ # but SELECT actually working fine.
+ def test_native_decimal_insert_manual_vs_automatic
+ # SQLite3 always uses float in violation of SQL
+ # 16 decimal places
+ correct_value = (current_adapter?(:SQLiteAdapter) ? '0.123456789012346E20' : '0012345678901234567890.0123456789').to_d
+
+ Person.delete_all
+ Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
+ Person.reset_column_information
+
+ # Do a manual insertion
+ Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)"
+
+ # SELECT
+ row = Person.find(:first)
+ assert_kind_of BigDecimal, row.wealth
+
+ # If this assert fails, that means the SELECT is broken!
+ assert_equal correct_value, row.wealth
+
+ # Reset to old state
+ Person.delete_all
+
+ # Now use the Rails insertion
+ assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") }
+
+ # SELECT
+ row = Person.find(:first)
+ assert_kind_of BigDecimal, row.wealth
+
+ # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
+ assert_equal correct_value, row.wealth
+
+ # Reset to old state
+ Person.connection.del_column "people", "wealth" rescue nil
+ Person.reset_column_information
+ end
+
def test_native_types
Person.delete_all
Person.connection.add_column "people", "last_name", :string
Person.connection.add_column "people", "bio", :text
Person.connection.add_column "people", "age", :integer
Person.connection.add_column "people", "height", :float
+ Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
Person.connection.add_column "people", "birthday", :datetime
Person.connection.add_column "people", "favorite_day", :date
Person.connection.add_column "people", "male", :boolean
- assert_nothing_raised { Person.create :first_name => 'bob', :last_name => 'bobsen', :bio => "I was born ....", :age => 18, :height => 1.78, :birthday => 18.years.ago, :favorite_day => 10.days.ago, :male => true }
+ assert_nothing_raised { Person.create :first_name => 'bob', :last_name => 'bobsen', :bio => "I was born ....", :age => 18, :height => 1.78, :wealth => BigDecimal.new("12345678901234567890.0123456789"), :birthday => 18.years.ago, :favorite_day => 10.days.ago, :male => true }
bob = Person.find(:first)
- assert_equal bob.first_name, 'bob'
- assert_equal bob.last_name, 'bobsen'
- assert_equal bob.bio, "I was born ...."
- assert_equal bob.age, 18
- assert_equal bob.male?, true
+ assert_equal 'bob', bob.first_name
+ assert_equal 'bobsen', bob.last_name
+ assert_equal "I was born ....", bob.bio
+ assert_equal 18, bob.age
+
+ # Test for 30 significent digits (beyond the 16 of float), 10 of them
+ # after the decimal place.
+ if current_adapter?(:SQLiteAdapter)
+ # SQLite3 uses float in violation of SQL. Test for 16 decimal places.
+ assert_equal BigDecimal.new('0.123456789012346E20'), bob.wealth
+ else
+ assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
+ end
+
+ assert_equal true, bob.male?
assert_equal String, bob.first_name.class
assert_equal String, bob.last_name.class
@@ -219,6 +270,7 @@ def test_native_types
end
assert_equal TrueClass, bob.male?.class
+ assert_kind_of BigDecimal, bob.wealth
end
def test_add_remove_single_field_using_string_arguments
@@ -351,6 +403,71 @@ def test_add_table
assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
end
+ def test_add_table_with_decimals
+ Person.connection.drop_table :big_numbers rescue nil
+
+ assert !BigNumber.table_exists?
+ GiveMeBigNumbers.up
+
+ assert BigNumber.create(
+ :bank_balance => 1586.43,
+ :big_bank_balance => BigDecimal("1000234000567.95"),
+ :world_population => 6000000000,
+ :my_house_population => 3,
+ :value_of_e => BigDecimal("2.7182818284590452353602875")
+ )
+
+ b = BigNumber.find(:first)
+ assert_not_nil b
+
+ assert_not_nil b.bank_balance
+ assert_not_nil b.big_bank_balance
+ assert_not_nil b.world_population
+ assert_not_nil b.my_house_population
+ assert_not_nil b.value_of_e
+
+ # TODO: set world_population >= 2**62 to cover 64-bit platforms and test
+ # is_a?(Bignum)
+ assert_kind_of Integer, b.world_population
+ assert_equal 6000000000, b.world_population
+ assert_kind_of Fixnum, b.my_house_population
+ assert_equal 3, b.my_house_population
+ assert_kind_of BigDecimal, b.bank_balance
+ assert_equal BigDecimal("1586.43"), b.bank_balance
+ assert_kind_of BigDecimal, b.big_bank_balance
+ assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
+
+ # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with
+ # precision/scale explictly left out. By the SQL standard, numbers
+ # assigned to this field should be truncated but that's seldom respected.
+ if current_adapter?(:PostgreSQLAdapter, :SQLite2Adapter)
+ # - PostgreSQL changes the SQL spec on columns declared simply as
+ # "decimal" to something more useful: instead of being given a scale
+ # of 0, they take on the compile-time limit for precision and scale,
+ # so the following should succeed unless you have used really wacky
+ # compilation options
+ # - SQLite2 has the default behavior of preserving all data sent in,
+ # so this happens there too
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
+ elsif current_adapter?(:SQLiteAdapter)
+ # - SQLite3 stores a float, in violation of SQL
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_equal BigDecimal("2.71828182845905"), b.value_of_e
+ elsif current_adapter?(:SQLServer)
+ # - SQL Server rounds instead of truncating
+ assert_kind_of Fixnum, b.value_of_e
+ assert_equal 3, b.value_of_e
+ else
+ # - SQL standard is an integer
+ assert_kind_of Fixnum, b.value_of_e
+ assert_equal 2, b.value_of_e
+ end
+
+ GiveMeBigNumbers.down
+ assert_raises(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
+ end
+
def test_migrator
assert !Person.column_methods_hash.include?(:last_name)
assert !Reminder.table_exists?
View
11 activerecord/test/schema_dumper_test.rb
@@ -7,6 +7,7 @@
class SchemaDumperTest < Test::Unit::TestCase
def standard_dump
stream = StringIO.new
+ ActiveRecord::SchemaDumper.ignore_tables = []
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
stream.string
end
@@ -30,7 +31,7 @@ def assert_line_up(lines, pattern, required = false)
def test_arguments_line_up
output = standard_dump
output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) }.each do |column_set|
- assert_line_up(column_set, /:(?:integer|float|datetime|timestamp|time|date|text|binary|string|boolean)/, true)
+ assert_line_up(column_set, /:(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)/, true)
assert_line_up(column_set, /:default => /)
assert_line_up(column_set, /:limit => /)
assert_line_up(column_set, /:null => /)
@@ -82,6 +83,14 @@ def test_schema_dump_illegal_ignored_table_value
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
end
end
+
+ def test_schema_dump_includes_decimal_options
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/]
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ output = stream.string
+ assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 0.278E1}, output
+ end
end
end
View
12 activerecord/test/validations_test.rb
@@ -1015,10 +1015,12 @@ def test_errors_to_xml
class ValidatesNumericalityTest
NIL = [nil, "", " ", " \t \r \n"]
- FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1)
+ BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
+ FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
INTEGERS = [0, 10, -10] + INTEGER_STRINGS
+ BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12"]
def setup
@@ -1031,27 +1033,27 @@ def test_default_validates_numericality_of
Topic.validates_numericality_of :approved
invalid!(NIL + JUNK)
- valid!(FLOATS + INTEGERS)
+ valid!(FLOATS + INTEGERS + BIGDECIMAL)
end
def test_validates_numericality_of_with_nil_allowed
Topic.validates_numericality_of :approved, :allow_nil => true
invalid!(JUNK)
- valid!(NIL + FLOATS + INTEGERS)
+ valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
end
def test_validates_numericality_of_with_integer_only
Topic.validates_numericality_of :approved, :only_integer => true
- invalid!(NIL + JUNK + FLOATS)
+ invalid!(NIL + JUNK + FLOATS + BIGDECIMAL)
valid!(INTEGERS)
end
def test_validates_numericality_of_with_integer_only_and_nil_allowed
Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
- invalid!(JUNK + FLOATS)
+ invalid!(JUNK + FLOATS + BIGDECIMAL)
valid!(NIL + INTEGERS)
end

0 comments on commit 2a12b56

Please sign in to comment.
Something went wrong with that request. Please try again.