From 4ad401bac540b33f9c3ddb9af02ae5aa77f344ad Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Mon, 26 Mar 2018 16:51:27 +0200 Subject: [PATCH] Ignore delay when first migration was < 10min ago --- lib/migration/base_dropper.rb | 7 +- .../migration/column_dropper_spec.rb | 116 +++++++---- .../migration/table_dropper_spec.rb | 191 +++++++++++++----- 3 files changed, 226 insertions(+), 88 deletions(-) diff --git a/lib/migration/base_dropper.rb b/lib/migration/base_dropper.rb index c00035d4ab5..f36963d99ec 100644 --- a/lib/migration/base_dropper.rb +++ b/lib/migration/base_dropper.rb @@ -33,7 +33,12 @@ module Migration SELECT 1 FROM schema_migration_details WHERE name = :after_migration AND - created_at <= (current_timestamp AT TIME ZONE 'UTC' - INTERVAL :delay) + (created_at <= (current_timestamp AT TIME ZONE 'UTC' - INTERVAL :delay) OR + (SELECT created_at + FROM schema_migration_details + ORDER BY id ASC + LIMIT 1) > (current_timestamp AT TIME ZONE 'UTC' - INTERVAL '10 minutes') + ) ) SQL end diff --git a/spec/components/migration/column_dropper_spec.rb b/spec/components/migration/column_dropper_spec.rb index 8d3c4ad6f39..6ae3f06f508 100644 --- a/spec/components/migration/column_dropper_spec.rb +++ b/spec/components/migration/column_dropper_spec.rb @@ -4,49 +4,97 @@ require_dependency 'migration/column_dropper' RSpec.describe Migration::ColumnDropper do def has_column?(table, column) - Topic.exec_sql("SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS - WHERE - table_schema = 'public' AND - table_name = :table AND - column_name = :column - ", - table: table, column: column - ).to_a.length == 1 + ActiveRecord::Base.exec_sql(<<~SQL, table: table, column: column).to_a.length == 1 + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE + table_schema = 'public' AND + table_name = :table AND + column_name = :column + SQL end - it "can correctly drop columns after correct delay" do - Topic.exec_sql "ALTER TABLE topics ADD COLUMN junk int" - name = Topic - .exec_sql("SELECT name FROM schema_migration_details LIMIT 1") - .getvalue(0, 0) + def update_first_migration_date(created_at) + ActiveRecord::Base.exec_sql(<<~SQL, created_at: created_at) + UPDATE schema_migration_details + SET created_at = :created_at + WHERE id = (SELECT MIN(id) + FROM schema_migration_details) + SQL + end - Topic.exec_sql("UPDATE schema_migration_details SET created_at = :created_at WHERE name = :name", - name: name, created_at: 15.minutes.ago) + describe ".drop" do + let(:migration_name) do + ActiveRecord::Base + .exec_sql("SELECT name FROM schema_migration_details ORDER BY id DESC LIMIT 1") + .getvalue(0, 0) + end - dropped_proc_called = false + before do + Topic.exec_sql "ALTER TABLE topics ADD COLUMN junk int" - Migration::ColumnDropper.drop( - table: 'topics', - after_migration: name, - columns: ['junk'], - delay: 20.minutes, - on_drop: ->() { dropped_proc_called = true } - ) + ActiveRecord::Base.exec_sql(<<~SQL, name: migration_name, created_at: 15.minutes.ago) + UPDATE schema_migration_details + SET created_at = :created_at + WHERE name = :name + SQL + end - expect(has_column?('topics', 'junk')).to eq(true) - expect(dropped_proc_called).to eq(false) + it "can correctly drop columns after correct delay" do + dropped_proc_called = false + update_first_migration_date(2.years.ago) - Migration::ColumnDropper.drop( - table: 'topics', - after_migration: name, - columns: ['junk'], - delay: 10.minutes, - on_drop: ->() { dropped_proc_called = true } - ) + Migration::ColumnDropper.drop( + table: 'topics', + after_migration: migration_name, + columns: ['junk'], + delay: 20.minutes, + on_drop: ->() { dropped_proc_called = true } + ) - expect(has_column?('topics', 'junk')).to eq(false) - expect(dropped_proc_called).to eq(true) + expect(has_column?('topics', 'junk')).to eq(true) + expect(dropped_proc_called).to eq(false) + Migration::ColumnDropper.drop( + table: 'topics', + after_migration: migration_name, + columns: ['junk'], + delay: 10.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(has_column?('topics', 'junk')).to eq(false) + expect(dropped_proc_called).to eq(true) + end + + it "drops the columns immediately if the first migration was less than 10 minutes ago" do + dropped_proc_called = false + update_first_migration_date(11.minutes.ago) + + Migration::ColumnDropper.drop( + table: 'topics', + after_migration: migration_name, + columns: ['junk'], + delay: 30.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(has_column?('topics', 'junk')).to eq(true) + expect(dropped_proc_called).to eq(false) + + update_first_migration_date(9.minutes.ago) + + Migration::ColumnDropper.drop( + table: 'topics', + after_migration: migration_name, + columns: ['junk'], + delay: 30.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(has_column?('topics', 'junk')).to eq(false) + expect(dropped_proc_called).to eq(true) + end end describe '.mark_readonly' do diff --git a/spec/components/migration/table_dropper_spec.rb b/spec/components/migration/table_dropper_spec.rb index 3dcaf8c5d85..e6dc4a1a063 100644 --- a/spec/components/migration/table_dropper_spec.rb +++ b/spec/components/migration/table_dropper_spec.rb @@ -14,83 +14,168 @@ describe Migration::TableDropper do ActiveRecord::Base.exec_sql(sql).to_a.length > 0 end + def update_first_migration_date(created_at) + ActiveRecord::Base.exec_sql(<<~SQL, created_at: created_at) + UPDATE schema_migration_details + SET created_at = :created_at + WHERE id = (SELECT MIN(id) + FROM schema_migration_details) + SQL + end + + def create_new_table + ActiveRecord::Base.exec_sql "CREATE TABLE table_with_new_name (topic_id INTEGER)" + end + let(:migration_name) do ActiveRecord::Base - .exec_sql("SELECT name FROM schema_migration_details LIMIT 1") + .exec_sql("SELECT name FROM schema_migration_details ORDER BY id DESC LIMIT 1") .getvalue(0, 0) end before do ActiveRecord::Base.exec_sql "CREATE TABLE table_with_old_name (topic_id INTEGER)" - Topic.exec_sql("UPDATE schema_migration_details SET created_at = :created_at WHERE name = :name", - name: migration_name, created_at: 15.minutes.ago) + ActiveRecord::Base.exec_sql(<<~SQL, name: migration_name, created_at: 15.minutes.ago) + UPDATE schema_migration_details + SET created_at = :created_at + WHERE name = :name + SQL end - describe "#delayed_rename" do - it "can drop a table after correct delay and when new table exists" do - dropped_proc_called = false + context "first migration was a long time ago" do + before do + update_first_migration_date(2.years.ago) + end - described_class.delayed_rename( - old_name: 'table_with_old_name', - new_name: 'table_with_new_name', - after_migration: migration_name, - delay: 20.minutes, - on_drop: ->() { dropped_proc_called = true } - ) + describe ".delayed_rename" do + it "can drop a table after correct delay and when new table exists" do + dropped_proc_called = false - expect(table_exists?('table_with_old_name')).to eq(true) - expect(dropped_proc_called).to eq(false) + described_class.delayed_rename( + old_name: 'table_with_old_name', + new_name: 'table_with_new_name', + after_migration: migration_name, + delay: 20.minutes, + on_drop: ->() { dropped_proc_called = true } + ) - described_class.delayed_rename( - old_name: 'table_with_old_name', - new_name: 'table_with_new_name', - after_migration: migration_name, - delay: 10.minutes, - on_drop: ->() { dropped_proc_called = true } - ) + expect(table_exists?('table_with_old_name')).to eq(true) + expect(dropped_proc_called).to eq(false) - expect(table_exists?('table_with_old_name')).to eq(true) - expect(dropped_proc_called).to eq(false) + described_class.delayed_rename( + old_name: 'table_with_old_name', + new_name: 'table_with_new_name', + after_migration: migration_name, + delay: 10.minutes, + on_drop: ->() { dropped_proc_called = true } + ) - ActiveRecord::Base.exec_sql "CREATE TABLE table_with_new_name (topic_id INTEGER)" + expect(table_exists?('table_with_old_name')).to eq(true) + expect(dropped_proc_called).to eq(false) - described_class.delayed_rename( - old_name: 'table_with_old_name', - new_name: 'table_with_new_name', - after_migration: migration_name, - delay: 10.minutes, - on_drop: ->() { dropped_proc_called = true } - ) + create_new_table - expect(table_exists?('table_with_old_name')).to eq(false) - expect(dropped_proc_called).to eq(true) + described_class.delayed_rename( + old_name: 'table_with_old_name', + new_name: 'table_with_new_name', + after_migration: migration_name, + delay: 10.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(table_exists?('table_with_old_name')).to eq(false) + expect(dropped_proc_called).to eq(true) + end + end + + describe ".delayed_drop" do + it "can drop a table after correct delay" do + dropped_proc_called = false + + described_class.delayed_drop( + table_name: 'table_with_old_name', + after_migration: migration_name, + delay: 20.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(table_exists?('table_with_old_name')).to eq(true) + expect(dropped_proc_called).to eq(false) + + described_class.delayed_drop( + table_name: 'table_with_old_name', + after_migration: migration_name, + delay: 10.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(table_exists?('table_with_old_name')).to eq(false) + expect(dropped_proc_called).to eq(true) + end end end - describe "#delayed_drop" do - it "can drop a table after correct delay" do - dropped_proc_called = false + context "first migration was a less than 10 minutes ago" do + describe ".delayed_rename" do + it "can drop a table after correct delay and when new table exists" do + dropped_proc_called = false + update_first_migration_date(11.minutes.ago) + create_new_table - described_class.delayed_drop( - table_name: 'table_with_old_name', - after_migration: migration_name, - delay: 20.minutes, - on_drop: ->() { dropped_proc_called = true } - ) + described_class.delayed_rename( + old_name: 'table_with_old_name', + new_name: 'table_with_new_name', + after_migration: migration_name, + delay: 30.minutes, + on_drop: ->() { dropped_proc_called = true } + ) - expect(table_exists?('table_with_old_name')).to eq(true) - expect(dropped_proc_called).to eq(false) + expect(table_exists?('table_with_old_name')).to eq(true) + expect(dropped_proc_called).to eq(false) - described_class.delayed_drop( - table_name: 'table_with_old_name', - after_migration: migration_name, - delay: 10.minutes, - on_drop: ->() { dropped_proc_called = true } - ) + update_first_migration_date(9.minutes.ago) - expect(table_exists?('table_with_old_name')).to eq(false) - expect(dropped_proc_called).to eq(true) + described_class.delayed_rename( + old_name: 'table_with_old_name', + new_name: 'table_with_new_name', + after_migration: migration_name, + delay: 30.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(table_exists?('table_with_old_name')).to eq(false) + expect(dropped_proc_called).to eq(true) + end + end + + describe ".delayed_drop" do + it "immediately drops the table" do + dropped_proc_called = false + update_first_migration_date(11.minutes.ago) + + described_class.delayed_drop( + table_name: 'table_with_old_name', + after_migration: migration_name, + delay: 30.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(table_exists?('table_with_old_name')).to eq(true) + expect(dropped_proc_called).to eq(false) + + update_first_migration_date(9.minutes.ago) + + described_class.delayed_drop( + table_name: 'table_with_old_name', + after_migration: migration_name, + delay: 30.minutes, + on_drop: ->() { dropped_proc_called = true } + ) + + expect(table_exists?('table_with_old_name')).to eq(false) + expect(dropped_proc_called).to eq(true) + end end end end