Skip to content

Commit

Permalink
Add migration to add id column and primary key to join tables
Browse files Browse the repository at this point in the history
This migration also creates the sequence using the current region.

Because of this, records in a global (replicated) environment need
to be removed as we will not be able to guarentee that the new
primary key assigned in the regional database is set to the same
row in the global database at the time of the migration.

Also, only delete rows on the global region if they came from a different region

This will preserve rows that belong to the global region
like ones in seeded tables like `miq_groups_users` or if
someone is managing some environment using the global database.
  • Loading branch information
carbonin committed Apr 8, 2016
1 parent b5ef7df commit 7656212
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
99 changes: 99 additions & 0 deletions db/migrate/20160406195810_add_id_primary_key_to_join_tables.rb
@@ -0,0 +1,99 @@
class AddIdPrimaryKeyToJoinTables < ActiveRecord::Migration[5.0]
JOIN_TABLES = %w(
conditions_miq_policies
key_pairs_vms
miq_roles_features
security_groups_vms
storages_vms_and_templates
miq_groups_users
cloud_tenants_vms
customization_scripts_operating_system_flavors
configuration_locations_configuration_profiles
configuration_organizations_configuration_profiles
configuration_profiles_configuration_tags
configuration_tags_configured_systems
container_groups_container_services
direct_configuration_profiles_configuration_tags
direct_configuration_tags_configured_systems
network_ports_security_groups
).freeze

COMPOSITE_KEY_MAP = {
"cloud_tenants_vms" => "cloud_tenant_id, vm_id",
"conditions_miq_policies" => "miq_policy_id, condition_id",
"configuration_locations_configuration_profiles" => "configuration_location_id, configuration_profile_id",
"configuration_organizations_configuration_profiles" => "configuration_organization_id, configuration_profile_id",
"configuration_profiles_configuration_tags" => "configuration_profile_id, configuration_tag_id",
"configuration_tags_configured_systems" => "configured_system_id, configuration_tag_id",
"container_groups_container_services" => "container_service_id, container_group_id",
"customization_scripts_operating_system_flavors" => "customization_script_id, operating_system_flavor_id",
"direct_configuration_profiles_configuration_tags" => "configuration_profile_id, configuration_tag_id",
"direct_configuration_tags_configured_systems" => "configured_system_id, configuration_tag_id",
"key_pairs_vms" => "authentication_id, vm_id",
"miq_groups_users" => "miq_group_id, user_id",
"miq_roles_features" => "miq_user_role_id, miq_product_feature_id",
"network_ports_security_groups" => "network_port_id, security_group_id",
"security_groups_vms" => "security_group_id, vm_id",
"storages_vms_and_templates" => "storage_id, vm_or_template_id"
}.freeze

class MiqRegion < ApplicationRecord
self.inheritance_column = :_type_disabled
end

def up
say_with_time("Removing composite primary keys from join tables") do
COMPOSITE_KEY_MAP.keys.each do |table|
execute("ALTER TABLE #{table} DROP CONSTRAINT #{table}_pkey")
end
end

JOIN_TABLES.each do |t|
delete_remote_region_rows(t) if on_replication_target?

say_with_time("Add primary key \"id\" to #{t}") do
execute <<-SQL
CREATE SEQUENCE #{sequence_name(t)} START #{seq_start_value}
SQL

execute <<-SQL
ALTER TABLE #{t} ADD COLUMN id BIGINT PRIMARY KEY
DEFAULT NEXTVAL('#{sequence_name(t)}')
SQL

execute <<-SQL
ALTER SEQUENCE #{sequence_name(t)} OWNED BY #{t}.id
SQL
end
end
end

def down
JOIN_TABLES.each do |t|
remove_column t.to_sym, :id
end

COMPOSITE_KEY_MAP.each do |table, key|
execute("ALTER TABLE #{table} ADD PRIMARY KEY (#{key})")
end
end

def delete_remote_region_rows(table)
model = Class.new(ApplicationRecord) { self.table_name = table }
col = model.column_names_symbols.first
model.where.not(col => model.region_to_range(model.my_region_number)).delete_all
end

def on_replication_target?
MiqRegion.select(:region).distinct.count > 1
end

def sequence_name(table)
"#{table}_id_seq"
end

def seq_start_value
val = ApplicationRecord.rails_sequence_start
val == 0 ? 1 : val
end
end
@@ -0,0 +1,72 @@
require_migration

describe AddIdPrimaryKeyToJoinTables do
let(:connection) { described_class.connection }
let(:region_stub) { migration_stub(:MiqRegion) }

migration_context :up do
context "on a replication target" do
let(:remote_region_id) { ApplicationRecord.my_region_number + 1 }
let(:remote_region_range_start) { ApplicationRecord.region_to_range(remote_region_id).begin }
let(:my_region_id) { ApplicationRecord.my_region_number }
let(:my_region_range_start) { ApplicationRecord.region_to_range(my_region_id).begin }

before do
region_stub.create!(:id => my_region_range_start, :region => my_region_id)
region_stub.create!(:id => remote_region_range_start, :region => remote_region_id)
end

it "removes rows from remote regions" do
described_class::JOIN_TABLES.each do |table|
connection.select_value <<-SQL
INSERT INTO #{table} VALUES (#{my_region_range_start}, #{my_region_range_start + 1})
SQL

connection.select_value <<-SQL
INSERT INTO #{table} VALUES (#{remote_region_range_start}, #{remote_region_range_start + 1})
SQL
end

migrate

described_class::JOIN_TABLES.each do |table|
connection.select_all("SELECT * FROM #{table}").each do |row|
row.each do |k, v|
expect(ApplicationRecord.id_in_current_region?(v)).to be(true), <<-EOS.lstrip
#{k} value (#{v}) in table #{table} is not in the correct region
EOS
end
end
end
end
end

it "assigns an id in the correct range" do
described_class::JOIN_TABLES.each_with_index do |table, i|
connection.select_value <<-SQL
INSERT INTO #{table} VALUES (#{i}, #{i + 1})
SQL
end

migrate

described_class::JOIN_TABLES.each do |table|
expect(connection.primary_keys(table)).to eq(["id"])

connection.select_all("SELECT * FROM #{table}").each do |row|
expect(ApplicationRecord.id_in_current_region?(row["id"])).to be true
end
end
end
end

migration_context :down do
it "recreates the composite primary key" do
migrate

described_class::JOIN_TABLES.each do |table|
expect(connection.primary_keys(table).count).to eq(2)
end
end
end
end

0 comments on commit 7656212

Please sign in to comment.