Skip to content

Commit

Permalink
refactor shared sql into helper methods
Browse files Browse the repository at this point in the history
  • Loading branch information
d-m-u committed Nov 29, 2020
1 parent 7662357 commit 58287bd
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 65 deletions.
148 changes: 83 additions & 65 deletions db/migrate/20200607025146_add_ancestry_to_vm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,73 @@ class VmOrTemplate < ActiveRecord::Base
class Relationship < ActiveRecord::Base
end

#
# build sql that converts relationship ancestry with the id of the relationship to model-side ancestry with the id of the model
#

# def ancestry_resource_ids(relationship, model_class_name, id_range)
# Relationship.where(
# :relationship => relationship,
# :resource_type => model_class_name,
# :resource_id => id_range
# ).where.not(:ancestry => nil).flat_map do |a_rels|
# a_rels.ancestry.split('/').each_with_index.map { |rel_id, indx| [a_rels.resource_id, rel_id, indx] }
# end
# end

def ancestry_resource_ids(relationship, model_class_name, id_range)
def up
add_column :vms, :ancestry, :string
add_index :vms, :ancestry

say_with_time("set vm ancestry from existing genealogy relationship resource information") do
connection.execute(transfer_relationships_to_ancestry(VmOrTemplate, 'VmOrTemplate', 'genealogy'))
end

Relationship.where(:relationship => 'genealogy', :resource_type => 'VmOrTemplate', :resource_id => VmOrTemplate.all.select(:id)).delete_all
end

def down
say_with_time("create relationship records from vm ancestry") do
create_relationships_from_ancestry(VmOrTemplate.where(:template => false), Relationship, 'VmOrTemplate', 'genealogy')
end

remove_column :vms, :ancestry
end

# src is relationship, dest is vm
def transfer_relationships_to_ancestry(dest_model, resource_type, relationship)
ancestry_resources = ancestry_resource_ids(relationship, resource_type, dest_model.rails_sequence_range(dest_model.my_region_number))
ancestry_sources = ancestry_src_ids(ancestry_resources)
new_ancestors = ancestry_of_src_ids_for_src(ancestry_sources)
update_src(new_ancestors, dest_model)
end

# src is vm, dest is relationship
def create_relationships_from_ancestry(src_model, dest_model, resource_type, relationship)
children = src_model.select("ancestry::bigint").where("ancestry not like '%/%'")
rels = src_model.where.not(:ancestry => nil).or(src_model.where(:id => children)).map do |obj|
dest_model.create!(:relationship => relationship,
:resource_type => resource_type,
:resource_id => obj.id,
:ancestry => obj.ancestry)
end.index_by(&:resource_id)
rels.each do |_, r|
next if r.ancestry.nil?

ancestry = r.ancestry.split('/').map { |rel| rels[rel.to_i].id }.join('/')
r.update!(:ancestry => ancestry)
end
end

private

def ancestry_resource_ids(relationship, resource_type, id_range)
<<-SQL
SELECT a_rels.resource_id AS src_id, relationships.id AS rel_id, relationships.indx AS rel_indx
FROM relationships a_rels
LEFT JOIN LATERAL UNNEST(STRING_TO_ARRAY(a_rels.ancestry, '/')::BIGINT[])
WITH ORDINALITY AS relationships(id, indx) ON TRUE
WHERE a_rels.relationship = '#{relationship}'
AND a_rels.resource_type = '#{model_class_name}'
AND a_rels.resource_type = '#{resource_type}'
AND a_rels.resource_id BETWEEN #{id_range.first} AND #{id_range.last}
SQL
end

# def ancestry_src_ids(ancestry_resources)
# ancestry_resources.map do |rec|
# {
# src_id: rec.src_id,
# ancestry_src_id: Relationship.find(rec[1]).resource_id
# }
# end
# end
# The above is a pure-SQL version of this similar Ruby code:
#
# Relationship.where(
# :relationship => relationship,
# :resource_type => resource_type,
# :resource_id => id_range
# ).where.not(:ancestry => nil).flat_map do |a_rels|
# a_rels.ancestry.split('/').each_with_index.map { |rel_id, indx| [a_rels.resource_id, rel_id, indx] }
# end
end

def ancestry_src_ids(ancestry_resources)
<<-SQL
Expand All @@ -52,16 +85,16 @@ def ancestry_src_ids(ancestry_resources)
ON res_rels.id = ancestry_resources.rel_id
ORDER BY ancestry_resources.src_id, ancestry_resources.rel_indx
SQL
end

# def ancestry_of_src_ids_for_src(ancestry_sources)
# ancestry_sources.group_by { |rec| rec.src_id }.map do |src_id, recs|
# {
# src_id: src_id,
# new_ancestry: recs.map { |rec| rec.ancestry_src_id }.join('/')
# }
# end
# end
# The above is a pure-SQL version of this similar Ruby code:
#
# ancestry_resources.map do |rec|
# {
# src_id: rec.src_id,
# ancestry_src_id: Relationship.find(rec[1]).resource_id
# }
# end
end

def ancestry_of_src_ids_for_src(ancestry_sources)
<<-SQL
Expand All @@ -70,43 +103,28 @@ def ancestry_of_src_ids_for_src(ancestry_sources)
FROM (#{ancestry_sources}) AS ancestry_sources
GROUP BY ancestry_sources.src_id
SQL
end

# def update_src(model, new_ancestors)
# new_ancestors.each { |a| model.find(a[:src_id]).update(:ancestry => a[:new_ancestry]) }
# end
# The above is a pure-SQL version of this similar Ruby code:
#
# ancestry_sources.group_by { |rec| rec.src_id }.map do |src_id, recs|
# {
# src_id: src_id,
# new_ancestry: recs.map { |rec| rec.ancestry_src_id }.join('/')
# }
# end
end

def update_src(new_ancestors)
def update_src(new_ancestors, model)
table_name = model.table_name
<<-SQL
UPDATE vms
UPDATE #{table_name}
SET ancestry = new_ancestry
FROM (#{new_ancestors}) AS new_ancestors
WHERE new_ancestors.src_id = vms.id
WHERE new_ancestors.src_id = #{table_name}.id
SQL
end

def up
add_column :vms, :ancestry, :string
add_index :vms, :ancestry

say_with_time("set vm ancestry from existing genealogy relationship resource information") do
ancestry_resources = ancestry_resource_ids('genealogy', "VmOrTemplate", VmOrTemplate.rails_sequence_range(VmOrTemplate.my_region_number))
ancestry_sources = ancestry_src_ids(ancestry_resources)
new_ancestors = ancestry_of_src_ids_for_src(ancestry_sources)
connection.execute(update_src(new_ancestors))
end

Relationship.where(:relationship => 'genealogy', :resource_type => 'VmOrTemplate', :resource_id => VmOrTemplate.all.select(:id)).delete_all
end

def down
say_with_time("create relationship records from vm ancestry") do
vms_with_ancestry = VmOrTemplate.where.not(:ancestry => nil)
vms_with_ancestry.each do |vm|
Relationship.create!(:relationship => 'genealogy', :resource_type => 'VmOrTemplate', :resource_id => vm.id, :ancestry => vm.ancestry)
end

remove_column :vms, :ancestry
end
# The above is a(n almost) pure-SQL version of this similar Ruby code:
#
# new_ancestors.each { |a| model.find(a[:src_id]).update(:ancestry => a[:new_ancestry]) }
end
end
1 change: 1 addition & 0 deletions spec/migrations/20200607025146_add_ancestry_to_vm_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

migrate

expect(vm.reload.ancestry).to eq(nil)
expect(child.reload.ancestry).to eq(nil)
expect(parent.reload.ancestry).to eq(nil)
expect(rel_stub.count).to eq(2)
Expand Down

0 comments on commit 58287bd

Please sign in to comment.