Permalink
Browse files

Consider number/numeric/decimal columns with a 0 scale to be integer …

…columns (e.g. numeric(10, 0))

This actually fixes two separate issues.  The simple one was that the
column type NUMBER was not recognized.  Oracle apparently uses this
type for numeric values.

The second issue was that previously Sequel always treated numeric
and decimal columns as decimals, so the model typecasting code uses
the ruby type BigDecimal.  However, if the scale of such a column is
0, it's really storing just an arbitrary sized integer as opposed
to a true decimal value.  Since ruby's integers already support
arbitrarily large values, it makes sense to typecast the values for
such columns to integer instead of decimal.

The generic handling of this is fairly clean, with a single override
in Database#schema_column_type.  However, some adapters such as the
JDBC and shared MSSQL adapter use :scale options instead of including
the scale in the :db_type option, and for those adding this behavior
is fairly ugly.  I choose to copy the approach the JDBC adapter used
to the shared MSSQL adapter, but if more adapters need it in the
future, I'll probably refactor to make the support more generic.

While working on this I discovered that if you use BigDecimal or
Numeric as a column type in Sequel, and you don't provide a :size
option as a two element array, most databases will assume a scale of
0 (thus creating an integer-like column), since that's what the SQL
standard specifies.  PostgreSQL doesn't do that, though, so if you
don't provide a :size option on PostgreSQL, it operates as a real
decimal field instead of an integer-like field.
  • Loading branch information...
1 parent abcad7d commit 468f045951d91571ae4c0ad912497308fe37977a @jeremyevans committed Apr 21, 2010
View
2 CHANGELOG
@@ -1,5 +1,7 @@
=== HEAD
+* Consider number/numeric/decimal columns with a 0 scale to be integer columns (e.g. numeric(10, 0)) (jeremyevans, QaDes)
+
* Fix Database#rename_table on Microsoft SQL Server (rohit.namjoshi) (#293)
* Add Dataset#provides_accurate_rows_matched?, for seeing if update and delete are likely to return correct numbers (jeremyevans)
View
10 lib/sequel/adapters/jdbc.rb
@@ -41,6 +41,10 @@ module JavaxNaming
# resource name.
JNDI_URI_REGEXP = /\Ajdbc:jndi:(.+)/
+ # The types to check for 0 scale to transform :decimal types
+ # to :integer.
+ DECIMAL_TYPE_RE = /number|numeric|decimal/io
+
# Contains procs keyed on sub adapter type that extend the
# given database object so it supports the correct database type.
DATABASE_SETUP = {:postgresql=>proc do |db|
@@ -426,7 +430,11 @@ def schema_parse_table(table, opts={})
pks << h[:column_name]
end
metadata(:getColumns, nil, schema, table, nil) do |h|
- ts << [m.call(h[:column_name]), {:type=>schema_column_type(h[:type_name]), :db_type=>h[:type_name], :default=>(h[:column_def] == '' ? nil : h[:column_def]), :allow_null=>(h[:nullable] != 0), :primary_key=>pks.include?(h[:column_name]), :column_size=>h[:column_size], :scale=>h[:decimal_digits]}]
+ s = {:type=>schema_column_type(h[:type_name]), :db_type=>h[:type_name], :default=>(h[:column_def] == '' ? nil : h[:column_def]), :allow_null=>(h[:nullable] != 0), :primary_key=>pks.include?(h[:column_name]), :column_size=>h[:column_size], :scale=>h[:decimal_digits]}
+ if s[:db_type] =~ DECIMAL_TYPE_RE && s[:scale] == 0
+ s[:type] = :integer
+ end
+ ts << [m.call(h[:column_name]), s]
end
ts
end
View
10 lib/sequel/adapters/shared/mssql.rb
@@ -12,6 +12,10 @@ module DatabaseMethods
SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
TEMPORARY = "#".freeze
+ # The types to check for 0 scale to transform :decimal types
+ # to :integer.
+ DECIMAL_TYPE_RE = /number|numeric|decimal/io
+
# Microsoft SQL Server uses the :mssql type.
def database_type
:mssql
@@ -140,7 +144,11 @@ def schema_parse_table(table_name, opts)
ds.map do |row|
row[:allow_null] = row[:allow_null] == 'YES' ? true : false
row[:default] = nil if blank_object?(row[:default])
- row[:type] = schema_column_type(row[:db_type])
+ row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE && row[:scale] == 0
+ :integer
+ else
+ schema_column_type(row[:db_type])
+ end
[m.call(row.delete(:column)), row]
end
end
View
6 lib/sequel/database.rb
@@ -953,11 +953,11 @@ def schema_column_type(db_type)
:boolean
when /\A(real|float|double( precision)?)\z/io
:float
- when /\A(((numeric|decimal)(\(\d+,\d+\))?)|(small)?money)\z/io
- :decimal
+ when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
+ $1 && $1 == '0' ? :integer : :decimal
when /bytea|blob|image|(var)?binary/io
:blob
- when /\Aenum/
+ when /\Aenum/io
:enum
end
end
View
4 spec/core/schema_spec.rb
@@ -812,8 +812,12 @@
@db.schema(:float).first.last[:type].should == :float
@db.schema(:double).first.last[:type].should == :float
@db.schema(:"double precision").first.last[:type].should == :float
+ @db.schema(:number).first.last[:type].should == :decimal
@db.schema(:numeric).first.last[:type].should == :decimal
@db.schema(:decimal).first.last[:type].should == :decimal
+ @db.schema(:"number(10,0)").first.last[:type].should == :integer
+ @db.schema(:"numeric(10, 10)").first.last[:type].should == :decimal
+ @db.schema(:"decimal(10,1)").first.last[:type].should == :decimal
@db.schema(:money).first.last[:type].should == :decimal
@db.schema(:bytea).first.last[:type].should == :blob
@db.schema(:blob).first.last[:type].should == :blob
View
6 spec/integration/schema_test.rb
@@ -87,10 +87,10 @@
INTEGRATION_DB.schema(:items).first.last[:type].should == :integer
INTEGRATION_DB.create_table!(:items){Float :number}
INTEGRATION_DB.schema(:items).first.last[:type].should == :float
- INTEGRATION_DB.create_table!(:items){BigDecimal :number}
- INTEGRATION_DB.schema(:items).first.last[:type].should == :decimal
- INTEGRATION_DB.create_table!(:items){Numeric :number}
+ INTEGRATION_DB.create_table!(:items){BigDecimal :number, :size=>[11, 2]}
INTEGRATION_DB.schema(:items).first.last[:type].should == :decimal
+ INTEGRATION_DB.create_table!(:items){Numeric :number, :size=>[12, 0]}
+ INTEGRATION_DB.schema(:items).first.last[:type].should == :integer
INTEGRATION_DB.create_table!(:items){String :number}
INTEGRATION_DB.schema(:items).first.last[:type].should == :string
INTEGRATION_DB.create_table!(:items){Date :number}

0 comments on commit 468f045

Please sign in to comment.