Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idempotency bugs in add_reference_concurrently #56

Merged
merged 2 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## master (unreleased)

- Fix `add_reference_concurrently` to be idempotent when adding a foreign key
- Fix `enqueue_background_data_migration` to be idempotent

## 0.19.1 (2024-05-24)
Expand Down
3 changes: 2 additions & 1 deletion lib/online_migrations/schema_statements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,8 @@ def index_name(table_name, options)
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key
#
def add_foreign_key(from_table, to_table, **options)
if foreign_key_exists?(from_table, to_table, **options)
# Do not consider validation for idempotency.
if foreign_key_exists?(from_table, to_table, **options.except(:validate))
message = +"Foreign key was not created because it already exists " \
"(this can be due to an aborted migration or similar): from_table: #{from_table}, to_table: #{to_table}"
message << ", #{options.inspect}" if options.any?
Expand Down
12 changes: 12 additions & 0 deletions test/command_checker/add_reference_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ def test_add_reference_foreign_key_no_validate
assert_safe AddReferenceForeignKeyNoValidate
end

class AddReferenceForeignKeyNoValidatePluralizeName < TestMigration
disable_ddl_transaction!

def change
add_reference :projects, :users, index: false, foreign_key: { validate: false }
end
end

def test_add_reference_foreign_key_no_validate_with_pluralized_table_name
assert_safe AddReferenceForeignKeyNoValidatePluralizeName
end

class AddReferenceForeignKeyValidate < TestMigration
def change
add_reference :projects, :user, index: false, foreign_key: { validate: true }
Expand Down
9 changes: 9 additions & 0 deletions test/command_checker/foreign_keys_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ def test_add_foreign_key_no_validate
assert_safe AddForeignKeyNoValidate
end

def test_add_foreign_key_no_validate_is_idempotent
assert_empty @connection.foreign_keys(:projects)

migrate AddForeignKeyNoValidate
migrate AddForeignKeyNoValidate

assert_equal 1, @connection.foreign_keys(:projects).size
end

class AddForeignKeyFromNewTable < TestMigration
def change
create_table :posts_new do |t|
Expand Down
28 changes: 28 additions & 0 deletions test/schema_statements/add_reference_concurrently_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,34 @@ def test_add_reference_concurrently_with_foreign_key
end
end

def test_add_reference_concurrently_with_foreign_key_is_idempotent
assert_empty connection.foreign_keys(:milestones)

connection.add_reference_concurrently :milestones, :project, foreign_key: true
connection.add_reference_concurrently :milestones, :project, foreign_key: true
ghiculescu marked this conversation as resolved.
Show resolved Hide resolved

assert_equal 1, connection.foreign_keys(:milestones).size
end

def test_add_reference_concurrently_with_foreign_key_with_pluralized_table_name
assert_sql(
'REFERENCES "projects" ("id") NOT VALID',
'ALTER TABLE "milestones" VALIDATE CONSTRAINT'
) do
connection.add_reference_concurrently :milestones, :projects, foreign_key: true
end
end

def test_add_reference_concurrently_with_foreign_key_is_idempotent_with_pluralized_table_name
assert_empty connection.foreign_keys(:milestones)

connection.add_reference_concurrently :milestones, :projects, foreign_key: true
connection.add_reference_concurrently :milestones, :projects, foreign_key: true
ghiculescu marked this conversation as resolved.
Show resolved Hide resolved
connection.add_reference_concurrently :milestones, :projects, foreign_key: true

assert_equal 1, connection.foreign_keys(:milestones).size
end

def test_add_reference_concurrently_with_unvalidated_foreign_key
refute_sql("VALIDATE CONSTRAINT") do
connection.add_reference_concurrently :milestones, :project, foreign_key: { validate: false }
Expand Down
Loading