OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/app/models/relation/hierarchy_paths.rb

176 lines
5.8 KiB

#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Relation::HierarchyPaths
extend ActiveSupport::Concern
included do
after_create :add_hierarchy_path
after_destroy :remove_hierarchy_path
after_update :update_hierarchy_path
def self.rebuild_hierarchy_paths!
execute_sql remove_hierarchy_path_sql
execute_sql add_hierarchy_path_sql
end
def self.execute_sql(sql)
ActiveRecord::Base.connection.execute sql
end
private
def add_hierarchy_path
return unless hierarchy?
self.class.execute_sql self.class.add_hierarchy_path_sql(to_id)
end
def remove_hierarchy_path
self.class.execute_sql self.class.remove_hierarchy_path_sql(to_id)
self.class.execute_sql self.class.add_hierarchy_path_sql(to_id)
end
def update_hierarchy_path
if was_hierarchy_relation?
remove_hierarchy_path
elsif now_hierarchy_relation_or_former_id_changed?
add_hierarchy_path
elsif hierarchy_relation_and_to_id_changed?
alter_hierarchy_path
end
end
def was_hierarchy_relation?
saved_change_to_relation_type? && relation_type_before_last_save == Relation::TYPE_HIERARCHY
end
def now_hierarchy_relation_or_former_id_changed?
(saved_change_to_relation_type? || saved_change_to_from_id?) && hierarchy?
end
def hierarchy_relation_and_to_id_changed?
hierarchy? && saved_change_to_to_id?
end
def alter_hierarchy_path
self.class.execute_sql self.class.remove_hierarchy_path_sql(to_id_before_last_save)
self.class.execute_sql self.class.add_hierarchy_path_sql(to_id)
end
def self.add_hierarchy_path_sql(id = nil)
if id.nil?
stmt = <<-SQL
INSERT INTO
#{hierarchy_table_name}
(work_package_id, path)
SELECT
to_id, #{add_hierarchy_agg_function} AS path
FROM
(SELECT to_id, from_id, hierarchy
FROM relations
WHERE hierarchy > 0 AND relates = 0 AND blocks = 0 AND duplicates = 0 AND includes = 0 AND requires = 0 AND follows = 0
) ordered_by_hierarchy
GROUP BY to_id
#{add_hierarchy_conflict_statement}
SQL
else
stmt = <<-SQL
INSERT INTO
#{hierarchy_table_name}
(work_package_id, path)
SELECT
to_id, #{add_hierarchy_agg_function} AS path
FROM (
SELECT to_id, from_id, hierarchy
FROM relations
WHERE to_id = #{id} AND
hierarchy > 0 AND relates = 0 AND blocks = 0 AND duplicates = 0 AND includes = 0 AND requires = 0 AND follows = 0
UNION SELECT to_id,from_id,hierarchy
FROM relations
WHERE from_id=#{id} AND
hierarchy > 0 AND relates = 0 AND blocks = 0 AND duplicates = 0 AND includes = 0 AND requires = 0 AND follows = 0
UNION SELECT b.to_id, b.from_id, b.hierarchy FROM relations a
JOIN relations b ON b.to_id = a.to_id
WHERE a.from_id = #{id} AND
a.hierarchy > 0 AND a.relates = 0 AND a.blocks = 0 AND a.duplicates = 0 AND a.includes = 0 AND a.requires = 0 AND a.follows = 0 AND
b.hierarchy > 0 AND b.relates = 0 AND b.blocks = 0 AND b.duplicates = 0 AND b.includes = 0 AND b.requires = 0 AND b.follows = 0
) AS Foo
GROUP BY to_id
#{add_hierarchy_conflict_statement}
SQL
end
stmt
end
def self.remove_hierarchy_path_sql(id = nil)
id_constraint = if id
"WHERE work_package_id = #{id}"
end
<<-SQL
DELETE FROM
#{hierarchy_table_name}
#{id_constraint}
SQL
end
def self.add_hierarchy_id_constraint(id)
if id
<<-SQL
AND (to_id = #{id}
OR to_id IN (#{Relation.hierarchy.where(from_id: id).select(:to_id).to_sql}))
SQL
end
end
def self.add_hierarchy_conflict_statement
if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
"ON DUPLICATE KEY
UPDATE #{hierarchy_table_name}.path = VALUES(path)"
else
"ON CONFLICT (work_package_id)
DO UPDATE SET path = EXCLUDED.path"
end
end
def self.add_hierarchy_agg_function
if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
"GROUP_CONCAT(from_id ORDER BY hierarchy DESC SEPARATOR ',')"
else
"string_agg(from_id::TEXT, ',' ORDER BY hierarchy DESC)"
end
end
def self.hierarchy_table_name
'hierarchy_paths'
end
end
end