From f3772f729c72d098156b35eb105d3ffdd928c5eb Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 26 Nov 2014 18:54:07 +0900 Subject: Add `unsigned` support for numeric data types in MySQL Example: create_table :foos do |t| t.integer :unsigned_integer, unsigned: true t.bigint :unsigned_bigint, unsigned: true t.float :unsigned_float, unsigned: true t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 end --- activerecord/CHANGELOG.md | 13 +++++++++ .../connection_adapters/abstract_mysql_adapter.rb | 32 +++++++++++++++++++--- .../cases/adapters/mysql/unsigned_type_test.rb | 26 ++++++++++++++++-- .../cases/adapters/mysql2/unsigned_type_test.rb | 26 ++++++++++++++++-- activerecord/test/cases/primary_keys_test.rb | 3 +- 5 files changed, 91 insertions(+), 9 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 918097449b..8c53069f5b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,16 @@ +* Add `unsigned` support for numeric data types in MySQL. + + Example: + + create_table :foos do |t| + t.integer :unsigned_integer, unsigned: true + t.bigint :unsigned_bigint, unsigned: true + t.float :unsigned_float, unsigned: true + t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 + end + + *Ryuta Kamizono* + * Lookup the attribute name for `restrict_with_error` messages on the model class that defines the association. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 597f0d0597..55de098f70 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -17,7 +17,7 @@ module ActiveRecord end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :charset + attr_accessor :charset, :unsigned end class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition @@ -30,6 +30,7 @@ module ActiveRecord column.type = :integer column.auto_increment = true end + column.unsigned ||= options[:unsigned] column.charset = options[:charset] column end @@ -65,6 +66,11 @@ module ActiveRecord create_sql end + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) + super + end + def visit_AddColumnDefinition(o) add_column_position!(super, column_options(o.column)) end @@ -117,6 +123,7 @@ module ActiveRecord spec = {} if column.auto_increment? spec[:id] = ':bigint' if column.bigint? + spec[:unsigned] = 'true' if column.unsigned? return if spec.empty? else spec[:id] = column.type.inspect @@ -125,6 +132,16 @@ module ActiveRecord spec end + def prepare_column_options(column) + spec = super + spec[:unsigned] = 'true' if column.unsigned? + spec + end + + def migration_keys + super + [:unsigned] + end + private def schema_limit(column) @@ -171,6 +188,10 @@ module ActiveRecord sql_type =~ /blob/i || type == :text end + def unsigned? + /unsigned/ === sql_type + end + def case_sensitive? collation && !collation.match(/_ci$/) end @@ -712,8 +733,8 @@ module ActiveRecord end # Maps logical Rails types to MySQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil) - case type.to_s + def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil) + sql = case type.to_s when 'binary' binary_to_sql(limit) when 'integer' @@ -721,8 +742,11 @@ module ActiveRecord when 'text' text_to_sql(limit) else - super + super(type, limit, precision, scale) end + + sql << ' unsigned' if unsigned && type != :primary_key + sql end # SHOW VARIABLES LIKE 'name' diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb index ed9398a918..12c52b97b6 100644 --- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb @@ -1,6 +1,8 @@ require "cases/helper" +require "support/schema_dumping_helper" class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase + include SchemaDumpingHelper self.use_transactional_tests = false class UnsignedType < ActiveRecord::Base @@ -9,12 +11,15 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase setup do @connection = ActiveRecord::Base.connection @connection.create_table("unsigned_types", force: true) do |t| - t.column :unsigned_integer, "int unsigned" + t.integer :unsigned_integer, unsigned: true + t.bigint :unsigned_bigint, unsigned: true + t.float :unsigned_float, unsigned: true + t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 end end teardown do - @connection.drop_table "unsigned_types" + @connection.drop_table "unsigned_types", if_exists: true end test "unsigned int max value is in range" do @@ -26,5 +31,22 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase assert_raise(RangeError) do UnsignedType.create(unsigned_integer: -10) end + assert_raise(RangeError) do + UnsignedType.create(unsigned_bigint: -10) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_float: -10.0) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_decimal: -10.0) + end + end + + test "schema dump includes unsigned option" do + schema = dump_table_schema "unsigned_types" + assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema + assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema + assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema + assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema end end diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index 9e06db2519..aea2ef4af5 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -1,6 +1,8 @@ require "cases/helper" +require "support/schema_dumping_helper" class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase + include SchemaDumpingHelper self.use_transactional_tests = false class UnsignedType < ActiveRecord::Base @@ -9,12 +11,15 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase setup do @connection = ActiveRecord::Base.connection @connection.create_table("unsigned_types", force: true) do |t| - t.column :unsigned_integer, "int unsigned" + t.integer :unsigned_integer, unsigned: true + t.bigint :unsigned_bigint, unsigned: true + t.float :unsigned_float, unsigned: true + t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 end end teardown do - @connection.drop_table "unsigned_types" + @connection.drop_table "unsigned_types", if_exists: true end test "unsigned int max value is in range" do @@ -26,5 +31,22 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase assert_raise(RangeError) do UnsignedType.create(unsigned_integer: -10) end + assert_raise(RangeError) do + UnsignedType.create(unsigned_bigint: -10) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_float: -10.0) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_decimal: -10.0) + end + end + + test "schema dump includes unsigned option" do + schema = dump_table_schema "unsigned_types" + assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema + assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema + assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema + assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema end end diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 0745a37ee9..58f3dd2ac2 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -300,11 +300,12 @@ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter) if current_adapter?(:MysqlAdapter, :Mysql2Adapter) test "primary key column type with options" do - @connection.create_table(:widgets, id: :primary_key, limit: 8, force: true) + @connection.create_table(:widgets, id: :primary_key, limit: 8, unsigned: true, force: true) column = @connection.columns(:widgets).find { |c| c.name == 'id' } assert column.auto_increment? assert_equal :integer, column.type assert_equal 8, column.limit + assert column.unsigned? end end end -- cgit v1.2.3 From dfeb3ee78a44b630ef414a7ce6e93265b2cc4f28 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 14 Feb 2015 15:59:33 +0900 Subject: Add `unsigned` types for numeric data types in MySQL In the case of using `unsigned` as the type: create_table :foos do |t| t.unsigned_integer :unsigned_integer t.unsigned_bigint :unsigned_bigint t.unsigned_float :unsigned_float t.unsigned_decimal :unsigned_decimal, precision: 10, scale: 2 end --- activerecord/CHANGELOG.md | 9 +++++++++ .../connection_adapters/abstract_mysql_adapter.rb | 21 +++++++++++++++++++++ .../test/cases/adapters/mysql/unsigned_type_test.rb | 13 +++++++++++++ .../cases/adapters/mysql2/unsigned_type_test.rb | 13 +++++++++++++ 4 files changed, 56 insertions(+) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 8c53069f5b..1f300dea5d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -9,6 +9,15 @@ t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 end + In the case of using `unsigned` as the type: + + create_table :foos do |t| + t.unsigned_integer :unsigned_integer + t.unsigned_bigint :unsigned_bigint + t.unsigned_float :unsigned_float + t.unsigned_decimal :unsigned_decimal, precision: 10, scale: 2 + end + *Ryuta Kamizono* * Lookup the attribute name for `restrict_with_error` messages on the diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 55de098f70..245d476abb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -14,6 +14,22 @@ module ActiveRecord def json(*args, **options) args.each { |name| column(name, :json, options) } end + + def unsigned_integer(*args, **options) + args.each { |name| column(name, :unsigned_integer, options) } + end + + def unsigned_bigint(*args, **options) + args.each { |name| column(name, :unsigned_bigint, options) } + end + + def unsigned_float(*args, **options) + args.each { |name| column(name, :unsigned_float, options) } + end + + def unsigned_decimal(*args, **options) + args.each { |name| column(name, :unsigned_decimal, options) } + end end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition @@ -29,6 +45,9 @@ module ActiveRecord when :primary_key column.type = :integer column.auto_increment = true + when /\Aunsigned_(?.+)\z/ + column.type = $~[:type].to_sym + column.unsigned = true end column.unsigned ||= options[:unsigned] column.charset = options[:charset] @@ -1108,6 +1127,8 @@ module ActiveRecord ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) end end end diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb index 12c52b97b6..84c5394c2e 100644 --- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb @@ -42,6 +42,19 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase end end + test "schema definition can use unsigned as the type" do + @connection.change_table("unsigned_types") do |t| + t.unsigned_integer :unsigned_integer_t + t.unsigned_bigint :unsigned_bigint_t + t.unsigned_float :unsigned_float_t + t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 + end + + @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column| + assert column.unsigned? + end + end + test "schema dump includes unsigned option" do schema = dump_table_schema "unsigned_types" assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index aea2ef4af5..a6f6dd21bb 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -42,6 +42,19 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase end end + test "schema definition can use unsigned as the type" do + @connection.change_table("unsigned_types") do |t| + t.unsigned_integer :unsigned_integer_t + t.unsigned_bigint :unsigned_bigint_t + t.unsigned_float :unsigned_float_t + t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 + end + + @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column| + assert column.unsigned? + end + end + test "schema dump includes unsigned option" do schema = dump_table_schema "unsigned_types" assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema -- cgit v1.2.3