Skip to content

Commit ef14d13

Browse files
committed
Correctly dump check constraints for MySQL 8.0.16+
If you're using MySQL 8.0.16+ and your database contains a table with a check constraint, the first and last characters of the constraint will be stripped when dumping the schema; this makes it impossible to use check constraints in a MySQL 8.0 database with the `:ruby` schema format; once dumped, they cannot be re-imported. This is because, up until MySQL 8.0.16, all check constraints were surrounded by an extra set of parentheses; this behaviour differed from that presented by MariaDB, which handled them correctly thanks to an engine check within the AbstractMySqlAdapter. With this change, we only strip the parentheses if they are present, which prevents the corruption of the constraint. It is also possible that the exported check constraint will contain invalid and unexpected whitespace characters; we handle that here too, stripping out sequences of `\\` and `\n`, as well as any other whitespace.
1 parent a1a026f commit ef14d13

File tree

2 files changed

+40
-1
lines changed

2 files changed

+40
-1
lines changed

activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,8 @@ def check_constraints(table_name)
526526
name: row["name"]
527527
}
528528
expression = row["expression"]
529-
expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
529+
expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
530+
expression = strip_whitespace_characters(expression)
530531
CheckConstraintDefinition.new(table_name, expression, options)
531532
end
532533
else
@@ -714,6 +715,12 @@ def extract_precision(sql_type)
714715
EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
715716

716717
private
718+
def strip_whitespace_characters(expression)
719+
expression = expression.gsub(/\\n|\\\\/, "")
720+
expression = expression.gsub(/\s{2,}/, " ")
721+
expression
722+
end
723+
717724
def text_type?(type)
718725
TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
719726
end

activerecord/test/cases/migration/check_constraint_test.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,21 @@ class Trade < ActiveRecord::Base
2323
t.integer :price
2424
t.integer :quantity
2525
end
26+
27+
if current_adapter?(:Mysql2Adapter)
28+
@connection.create_table "constraint_test", force: true do |t|
29+
t.json :options, default: nil
30+
end
31+
end
2632
end
2733

2834
teardown do
2935
@connection.drop_table "trades", if_exists: true rescue nil
3036
@connection.drop_table "purchases", if_exists: true rescue nil
37+
38+
if current_adapter?(:Mysql2Adapter)
39+
@connection.drop_table "constraint_test", if_exists: true rescue nil
40+
end
3141
end
3242

3343
def test_check_constraints
@@ -44,6 +54,28 @@ def test_check_constraints
4454
assert_equal "price > discounted_price", constraint.expression
4555
end
4656

57+
if current_adapter?(:Mysql2Adapter)
58+
begin
59+
@connection.add_check_constraint(:constraint_test, <<~SQL,
60+
json_contains('
61+
{
62+
"a": 1,
63+
"b": 2,
64+
"c": {
65+
"d": 4
66+
}
67+
}
68+
', options)
69+
SQL
70+
name: "non_empty_test_array")
71+
72+
constraint = @connection.check_constraints("constraint_test").find { |c| c.name == "non_empty_test_array" }
73+
assert_includes constraint.expression, "json_contains"
74+
ensure
75+
@connection.remove_check_constraint(:constraint_test, name: "non_empty_test_array", if_exists: true)
76+
end
77+
end
78+
4779
if current_adapter?(:PostgreSQLAdapter)
4880
begin
4981
# Test that complex expression is correctly parsed from the database

0 commit comments

Comments
 (0)