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

Add columns to improve automate object looking up performance #465

Merged
merged 2 commits into from
May 12, 2020
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
124 changes: 124 additions & 0 deletions db/migrate/20200325170358_add_domain_and_relative_path_to_automate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
require 'ancestry'

class AddDomainAndRelativePathToAutomate < ActiveRecord::Migration[5.1]
class MiqAeDomain < ActiveRecord::Base
self.table_name = 'miq_ae_namespaces'
end

class MiqAeNamespace < ActiveRecord::Base
has_many :ae_classes, :class_name => "AddDomainAndRelativePathToAutomate::MiqAeClass", :foreign_key => :namespace_id
has_ancestry
Fryguy marked this conversation as resolved.
Show resolved Hide resolved
end
Fryguy marked this conversation as resolved.
Show resolved Hide resolved

class MiqAeClass < ActiveRecord::Base
self.inheritance_column = :_type_disabled
belongs_to :ae_namespace, :class_name => "AddDomainAndRelativePathToAutomate::MiqAeNamespace", :foreign_key => :namespace_id
end

class MiqAeInstance < ActiveRecord::Base
belongs_to :ae_class, :class_name => "AddDomainAndRelativePathToAutomate::MiqAeClass", :foreign_key => :class_id
end

class MiqAeMethod < ActiveRecord::Base
belongs_to :ae_class, :class_name => "AddDomainAndRelativePathToAutomate::MiqAeClass", :foreign_key => :class_id
end

def populate_children(nodes)
nodes.each do |parent, children|
children.keys.each do |child|
child.update!(:relative_path => "#{parent.relative_path}/#{child.name}", :domain_id => parent.domain_id)
end
populate_children(children)
end
end

# for miq_ae_class, object_name is nil
# joins namespace's relative_path, '/', miq_ae_classes' name
# for '$' domain, namespace is absent, so don't output the '/'
# for miq_ae_instance/miq_ae_method, object_name has table name
# joins namespace's relative_path, '/', class's name, '/', object_name's name
# need to join from object_name to ae_class
# for '$' domain, namespace is absent, so don't output the first '/'
#
def relative_path_sql(object_name = nil)
optional_slash_sql = "CASE WHEN miq_ae_namespaces.relative_path IS NULL THEN '' ELSE '/' END"
columns = ["miq_ae_namespaces.relative_path", optional_slash_sql, "miq_ae_classes.name"]
columns += ["'/'", "#{object_name}.name"] if object_name

sql = MiqAeNamespace.select("concat(#{columns.join(', ')})")
if object_name
sql.joins(:ae_classes).where("#{object_name}.class_id = miq_ae_classes.id").to_sql
else
sql.where("miq_ae_namespaces.id = miq_ae_classes.namespace_id").to_sql
end
end

# usually it is structured as /domain/namespace/class/method, namespace has domain_id set
# for '$' domain, it is structured like /$/class/method, namespace is the domain, domain_id is absent, so uses id
def domain_id_sql(object_name = nil)
sql = MiqAeNamespace.select("COALESCE(miq_ae_namespaces.domain_id, miq_ae_namespaces.id)")
if object_name
sql.joins(:ae_classes).where("#{object_name}.class_id = miq_ae_classes.id").to_sql
else
sql.where("miq_ae_namespaces.id = miq_ae_classes.namespace_id").to_sql
end
end

def up
add_column :miq_ae_namespaces, :domain_id, :bigint
add_column :miq_ae_namespaces, :relative_path, :string
add_index :miq_ae_namespaces, :relative_path

add_column :miq_ae_classes, :domain_id, :bigint
add_column :miq_ae_classes, :relative_path, :string
add_index :miq_ae_classes, :relative_path

add_column :miq_ae_instances, :domain_id, :bigint
add_column :miq_ae_instances, :relative_path, :string
add_index :miq_ae_instances, :relative_path

add_column :miq_ae_methods, :domain_id, :bigint
add_column :miq_ae_methods, :relative_path, :string
add_index :miq_ae_methods, :relative_path

say_with_time("Add domain_id and relative_path to MiqAeNamespace") do
MiqAeNamespace.where(:ancestry => nil).each do |domain|
children = domain.descendants.arrange
children.keys.each do |child|
child.update!(:domain_id => domain.id, :relative_path => child.name)
end
populate_children(children)
end
end

say_with_time("Add domain_id and relative_path to MiqAeClass") do
MiqAeClass.update_all("domain_id = (#{domain_id_sql}), relative_path = (#{relative_path_sql})")
Fryguy marked this conversation as resolved.
Show resolved Hide resolved
end

say_with_time("Add domain_id and relative_path to MiqAeInstance") do
MiqAeInstance.update_all("domain_id = (#{domain_id_sql("miq_ae_instances")}), relative_path = (#{relative_path_sql("miq_ae_instances")})")
end

say_with_time("Add domain_id and relative_path to MiqAeMethod") do
MiqAeMethod.update_all("domain_id = (#{domain_id_sql("miq_ae_methods")}), relative_path = (#{relative_path_sql("miq_ae_methods")})")
end
end

def down
remove_index :miq_ae_namespaces, :column => :relative_path
remove_column :miq_ae_namespaces, :domain_id
remove_column :miq_ae_namespaces, :relative_path

remove_index :miq_ae_classes, :column => :relative_path
remove_column :miq_ae_classes, :domain_id
remove_column :miq_ae_classes, :relative_path

remove_index :miq_ae_instances, :column => :relative_path
remove_column :miq_ae_instances, :domain_id
remove_column :miq_ae_instances, :relative_path

remove_index :miq_ae_methods, :column => :relative_path
remove_column :miq_ae_methods, :domain_id
remove_column :miq_ae_methods, :relative_path
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
require_migration

describe AddDomainAndRelativePathToAutomate do
let(:dom_stub) { migration_stub(:MiqAeDomain) }
let(:ns_stub) { migration_stub(:MiqAeNamespace) }
let(:class_stub) { migration_stub(:MiqAeClass) }
let(:method_stub) { migration_stub(:MiqAeMethod) }
let(:instance_stub) { migration_stub(:MiqAeInstance) }

migration_context :up do
it "handles domain without namespace" do
dom = dom_stub.create!(:name => 'dom')

migrate

assert_domain(dom)
end

# nodes:
# dom
# ns
# class1
# instance
# method
it "handles domain with single namespace" do
dom = dom_stub.create!(:name => 'dom')
ns = ns_stub.create!(:name => 'ns', :ancestry => dom.id.to_s)
class1 = class_stub.create!(:namespace_id => ns.id)
instance = instance_stub.create!(:class_id => class1.id)
method = method_stub.create!(:class_id => class1.id)

migrate

assert_domain(dom)
assert_object(ns, dom.id, ns.name)
assert_object(class1, dom.id, "#{ns.name}/#{class1.name}")
assert_object(instance, dom.id, "#{ns.name}/#{class1.name}/#{instance.name}")
assert_object(method, dom.id, "#{ns.name}/#{class1.name}/#{method.name}")
end

# nodes:
# dom
# ns1
# ns11
# class1
# instance
it "handles domain with multiple namespaces" do
dom = dom_stub.create!(:name => 'dom')
ns1 = ns_stub.create!(:name => 'ns1', :ancestry => dom.id.to_s)
ns11 = ns_stub.create!(:name => 'ns11', :ancestry => "#{dom.id}/#{ns1.id}")
class1 = class_stub.create!(:namespace_id => ns11.id)
instance = instance_stub.create!(:class_id => class1.id)

migrate

assert_domain(dom)
assert_object(ns1, dom.id, ns1.name)
assert_object(ns11, dom.id, "#{ns1.name}/#{ns11.name}")
assert_object(class1, dom.id, "#{ns1.name}/#{ns11.name}/#{class1.name}")
assert_object(instance, dom.id, "#{ns1.name}/#{ns11.name}/#{class1.name}/#{instance.name}")
end

# nodes:
# dom
# ns1
# ns11
# ns12
it "handles namespace without class" do
dom = dom_stub.create!(:name => 'dom')
ns1 = ns_stub.create!(:name => 'ns1', :ancestry => dom.id.to_s)
ns11 = ns_stub.create!(:name => 'ns11', :ancestry => "#{dom.id}/#{ns1.id}")
ns12 = ns_stub.create!(:name => 'ns12', :ancestry => "#{dom.id}/#{ns1.id}")

migrate

assert_domain(dom)
assert_object(ns1, dom.id, ns1.name)
assert_object(ns11, dom.id, "#{ns1.name}/#{ns11.name}")
assert_object(ns12, dom.id, "#{ns1.name}/#{ns12.name}")
end

# nodes:
# dom
# ns
# class1
# instance11
# method12
# class2
# instance21
# method22
it "handles namespace with multiple classes" do
dom = dom_stub.create!(:name => 'dom')
ns = ns_stub.create!(:name => 'ns', :ancestry => dom.id.to_s)
class1 = class_stub.create!(:namespace_id => ns.id)
instance11 = instance_stub.create!(:class_id => class1.id)
method12 = method_stub.create!(:class_id => class1.id)
class2 = class_stub.create!(:namespace_id => ns.id)
instance21 = instance_stub.create!(:class_id => class2.id)
method22 = method_stub.create!(:class_id => class2.id)

migrate

assert_domain(dom)
assert_object(ns, dom.id, ns.name)
assert_object(class1, dom.id, "#{ns.name}/#{class1.name}")
assert_object(instance11, dom.id, "#{ns.name}/#{class1.name}/#{instance11.name}")
assert_object(method12, dom.id, "#{ns.name}/#{class1.name}/#{method12.name}")
assert_object(class2, dom.id, "#{ns.name}/#{class2.name}")
assert_object(instance21, dom.id, "#{ns.name}/#{class2.name}/#{instance21.name}")
assert_object(method22, dom.id, "#{ns.name}/#{class2.name}/#{method22.name}")
end

# nodes: - Simulate the structure for '$' domain
# dom
# class1
# method1
it "handles class without namespace" do
dom = dom_stub.create!(:name => 'dom')
class1 = class_stub.create!(:name => 'class1', :namespace_id => dom.id)
method1 = method_stub.create!(:name => 'method1', :class_id => class1.id)

migrate

assert_domain(dom)
assert_object(class1, dom.id, class1.name)
assert_object(method1, dom.id, "#{class1.name}/#{method1.name}")
end
end

def assert_domain(domain)
expect(domain.reload.domain_id).to be_nil
expect(domain.relative_path).to be_nil
end

def assert_object(object, domain_id, relative_path)
expect(object.reload.domain_id).to eq(domain_id)
expect(object.relative_path).to eq(relative_path)
end
end