Allow changing text and blob size without giving the `limit` option

In MySQL, the text column size is 65,535 bytes by default (1 GiB in
PostgreSQL). It is sometimes too short when people want to use a text
column, so they sometimes change the text size to mediumtext (16 MiB) or
longtext (4 GiB) by giving the `limit` option.

Unlike MySQL, PostgreSQL doesn't allow the `limit` option for a text
column (raises ERROR: type modifier is not allowed for type "text").
So `limit: 4294967295` (longtext) couldn't be used in Action Text.

I've allowed changing text and blob size without giving the `limit`
option, it prevents that migration failure on PostgreSQL.
This commit is contained in:
Ryuta Kamizono 2019-01-25 22:01:07 +09:00
parent b8baa15adb
commit 1745e905a3
11 changed files with 65 additions and 23 deletions

View File

@ -5,11 +5,7 @@ class CreateActionMailboxTables < ActiveRecord::Migration[6.0]
t.string :message_id, null: false
t.string :message_checksum, null: false
if supports_datetime_with_precision?
t.timestamps precision: 6
else
t.timestamps
end
t.timestamps
t.index [ :message_id, :message_checksum ], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
end

View File

@ -2,7 +2,7 @@ class CreateActionTextTables < ActiveRecord::Migration[6.0]
def change
create_table :action_text_rich_texts do |t|
t.string :name, null: false
t.text :body, limit: 16777215
t.text :body, size: :long
t.references :record, null: false, polymorphic: true, index: false
t.timestamps

View File

@ -2,11 +2,10 @@ class CreateActionTextTables < ActiveRecord::Migration[6.0]
def change
create_table :action_text_rich_texts do |t|
t.string :name, null: false
t.text :body, limit: 16777215
t.text :body, size: :long
t.references :record, null: false, polymorphic: true, index: false
t.datetime :created_at, null: false
t.datetime :updated_at, null: false
t.timestamps
t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
end

View File

@ -14,7 +14,7 @@ ActiveRecord::Schema.define(version: 2018_10_03_185713) do
create_table "action_text_rich_texts", force: :cascade do |t|
t.string "name", null: false
t.text "body", limit: 16777215
t.text "body"
t.string "record_type", null: false
t.integer "record_id", null: false
t.datetime "created_at", precision: 6, null: false

View File

@ -1,3 +1,7 @@
* MySQL: Support `:size` option to change text and blob size.
*Ryuta Kamizono*
* Make `t.timestamps` with precision by default.
*Ryuta Kamizono*

View File

@ -29,7 +29,7 @@ module ActiveRecord
NATIVE_DATABASE_TYPES = {
primary_key: "bigint auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
text: { name: "text", limit: 65535 },
text: { name: "text" },
integer: { name: "int", limit: 4 },
float: { name: "float", limit: 24 },
decimal: { name: "decimal" },
@ -37,7 +37,8 @@ module ActiveRecord
timestamp: { name: "timestamp" },
time: { name: "time" },
date: { name: "date" },
binary: { name: "blob", limit: 65535 },
binary: { name: "blob" },
blob: { name: "blob" },
boolean: { name: "tinyint", limit: 1 },
json: { name: "json" },
}

View File

@ -56,6 +56,16 @@ module ActiveRecord
case type
when :virtual
type = options[:type]
when :text, :blob, :binary
case (size = options[:size])&.to_s
when "tiny", "medium", "long"
sql_type = @conn.native_database_types[type][:name]
type = "#{size}#{sql_type}"
else
raise ArgumentError, <<~MSG unless size.nil?
#{size.inspect} is invalid :size value. Only :tiny, :medium, and :long are allowed.
MSG
end
when :primary_key
type = :integer
options[:limit] ||= 8

View File

@ -10,6 +10,10 @@ module ActiveRecord
spec[:unsigned] = "true" if column.unsigned?
spec[:auto_increment] = "true" if column.auto_increment?
if /\A(?<size>tiny|medium|long)(?:text|blob)/ =~ column.sql_type
spec = { size: size.to_sym.inspect }.merge!(spec)
end
if @connection.supports_virtual_columns? && column.virtual?
spec[:as] = extract_expression_for_virtual_column(column)
spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
@ -37,13 +41,15 @@ module ActiveRecord
case column.sql_type
when /\Atimestamp\b/
:timestamp
when "tinyblob"
:blob
else
super
end
end
def schema_limit(column)
super unless /\A(?:tiny|medium|long)?(?:text|blob)/.match?(column.sql_type)
end
def schema_precision(column)
super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
end

View File

@ -626,6 +626,18 @@ class MigrationTest < ActiveRecord::TestCase
ensure
Person.connection.drop_table :test_text_limits, if_exists: true
end
def test_invalid_text_size_should_raise
e = assert_raise(ArgumentError) do
Person.connection.create_table :test_text_sizes, force: true do |t|
t.text :bigtext, size: 0xfffffffff
end
end
assert_match(/#{0xfffffffff} is invalid :size value\. Only :tiny, :medium, and :long are allowed\./, e.message)
ensure
Person.connection.drop_table :test_text_sizes, if_exists: true
end
end
if ActiveRecord::Base.connection.supports_advisory_locks?

View File

@ -245,25 +245,31 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:Mysql2Adapter)
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
output = dump_table_schema "binary_fields"
assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output
assert_match %r{t\.binary\s+"var_binary_large",\s+limit: 4095$}, output
end
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
assert_match %r{t\.blob\s+"tiny_blob",\s+limit: 255$}, output
output = dump_table_schema "binary_fields"
assert_match %r{t\.binary\s+"tiny_blob",\s+size: :tiny$}, output
assert_match %r{t\.binary\s+"normal_blob"$}, output
assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output
assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output
assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output
assert_match %r{t\.binary\s+"medium_blob",\s+size: :medium$}, output
assert_match %r{t\.binary\s+"long_blob",\s+size: :long$}, output
assert_match %r{t\.text\s+"tiny_text",\s+size: :tiny$}, output
assert_match %r{t\.text\s+"normal_text"$}, output
assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output
assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output
assert_match %r{t\.text\s+"medium_text",\s+size: :medium$}, output
assert_match %r{t\.text\s+"long_text",\s+size: :long$}, output
assert_match %r{t\.binary\s+"tiny_blob_2",\s+size: :tiny$}, output
assert_match %r{t\.binary\s+"medium_blob_2",\s+size: :medium$}, output
assert_match %r{t\.binary\s+"long_blob_2",\s+size: :long$}, output
assert_match %r{t\.text\s+"tiny_text_2",\s+size: :tiny$}, output
assert_match %r{t\.text\s+"medium_text_2",\s+size: :medium$}, output
assert_match %r{t\.text\s+"long_text_2",\s+size: :long$}, output
end
def test_schema_does_not_include_limit_for_emulated_mysql_boolean_fields
output = standard_dump
output = dump_table_schema "booleans"
assert_no_match %r{t\.boolean\s+"has_fun",.+limit: 1}, output
end

View File

@ -27,6 +27,7 @@ ActiveRecord::Schema.define do
create_table :binary_fields, force: true do |t|
t.binary :var_binary, limit: 255
t.binary :var_binary_large, limit: 4095
t.tinyblob :tiny_blob
t.blob :normal_blob
t.mediumblob :medium_blob
@ -36,6 +37,13 @@ ActiveRecord::Schema.define do
t.mediumtext :medium_text
t.longtext :long_text
t.binary :tiny_blob_2, size: :tiny
t.binary :medium_blob_2, size: :medium
t.binary :long_blob_2, size: :long
t.text :tiny_text_2, size: :tiny
t.text :medium_text_2, size: :medium
t.text :long_text_2, size: :long
t.index :var_binary
end