diff options
Diffstat (limited to 'activerecord/lib/active_record')
16 files changed, 209 insertions, 107 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 1c8c819efa..e4c47c9bc3 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1726,7 +1726,7 @@ module ActiveRecord #:nodoc: 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(', ')}) " + 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: # <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 @@ module ActiveRecord # * <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 @@ 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 <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 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: diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 4eea7e5434..949b8f7951 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/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' diff --git a/activerecord/lib/active_record/connection_adapters/db2_adapter.rb b/activerecord/lib/active_record/connection_adapters/db2_adapter.rb index 3b81c526f2..3ff6bdfb98 100644 --- a/activerecord/lib/active_record/connection_adapters/db2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/db2_adapter.rb @@ -162,6 +162,7 @@ begin :text => { :name => 'clob', :limit => 32768 }, :integer => { :name => 'int' }, :float => { :name => 'float' }, + :decimal => { :name => 'decimal' }, :datetime => { :name => 'timestamp' }, :timestamp => { :name => 'timestamp' }, :time => { :name => 'time' }, diff --git a/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb b/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb index e25198fa0d..db9d2ccf3d 100644 --- a/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb @@ -160,7 +160,7 @@ module ActiveRecord @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 @@ module ActiveRecord :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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord (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 diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 417eca180e..b9fb5fa6af 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -102,6 +102,7 @@ module ActiveRecord :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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb b/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb index 886779d273..e52cde45b0 100644 --- a/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb @@ -32,7 +32,7 @@ module ActiveRecord 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 @@ module ActiveRecord :text => { :name => "text" }, :integer => { :name => "integer" }, :float => { :name => "float" }, + :decimal => { :name => "decimal" }, :datetime => { :name => "datetime" }, :timestamp => { :name => "timestamp" }, :time => { :name => "time" }, diff --git a/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb b/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb index 5a1c693ac9..81e4932fce 100644 --- a/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb @@ -93,9 +93,8 @@ begin 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 @@ begin :text => { :name => "CLOB" }, :integer => { :name => "NUMBER", :limit => 38 }, :float => { :name => "NUMBER" }, + :decimal => { :name => "DECIMAL" }, :datetime => { :name => "DATE" }, :timestamp => { :name => "DATE" }, :time => { :name => "DATE" }, diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5d42de1c6b..2fa357993f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -93,6 +93,7 @@ module ActiveRecord :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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord private BYTEA_COLUMN_TYPE_OID = 17 + NUMERIC_COLUMN_TYPE_OID = 1700 TIMESTAMPOID = 1114 TIMESTAMPTZOID = 1184 @@ -417,6 +419,8 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index fec30fb021..308a21191d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -110,6 +110,7 @@ module ActiveRecord :text => { :name => "text" }, :integer => { :name => "integer" }, :float => { :name => "float" }, + :decimal => { :name => "decimal" }, :datetime => { :name => "datetime" }, :timestamp => { :name => "datetime" }, :time => { :name => "datetime" }, diff --git a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb index 2e1ac2350b..04a6443159 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb +++ b/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 @@ module ActiveRecord 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 @@ module ActiveRecord :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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 = [] diff --git a/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb b/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb index e464fc56ad..dc1cce451a 100644 --- a/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb @@ -86,14 +86,15 @@ module ActiveRecord 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 @@ module ActiveRecord :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 @@ module ActiveRecord 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) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 8dd3086413..ffb4ffd030 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -64,8 +64,9 @@ module ActiveRecord # * <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. diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 6c896f7bed..2d31b5d88e 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/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 @@ HEADER 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| |