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/db/migrate/migration_utils/attachable_utils.rb

193 lines
6.8 KiB

# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require_relative 'utils'
module Migration::Utils
module AttachableUtils
MissingAttachment = Struct.new(:journaled_id,
:journaled_type,
:attachment_id,
:filename,
:last_version)
def add_missing_attachable_journals
result = missing_attachments
repair_journals(result)
end
def repair_attachable_journal_entries(journal_type, legacy_journal_type)
result = invalid_attachments(legacy_journal_type)
result.map { |m| m.journaled_type = journal_type }
repair_journals(result)
end
def remove_initial_journal_entries(journal_type, legacy_journal_type)
result = invalid_attachments(legacy_journal_type)
result.map { |m| m.journaled_type = journal_type }
remove_initial_journals(result)
end
def repair_journals(result)
result.each do |m|
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, m.journaled_type)
journal_ids.each do |journal_id|
insert <<-SQL
INSERT INTO attachable_journals (journal_id, attachment_id, filename)
VALUES (#{journal_id}, #{m.attachment_id}, '#{m.filename}')
SQL
end
end
end
def remove_initial_journals(result)
result.each do |m|
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, m.journaled_type)
delete <<-SQL
DELETE FROM attachable_journals
WHERE journal_id IN (#{journal_ids.join(", ")})
SQL
end
end
private
def missing_attachments
begin
result = select_all <<-SQL
SELECT * FROM (
SELECT a.container_id AS journaled_id, a.container_type AS journaled_type, a.id AS attachment_id, a.filename, MAX(aj.id) AS aj_id, MAX(j.version) AS last_version
FROM attachments AS a JOIN journals AS j
ON (a.container_id = j.journable_id AND a.container_type = j.journable_type) LEFT JOIN attachable_journals AS aj
ON (a.id = aj.attachment_id)
GROUP BY a.container_id, a.container_type, a.id, a.filename
) AS tmp
WHERE aj_id IS NULL
SQL
rescue ActiveRecord::StatementInvalid => ex
raise ex unless mysql?
raise "An MySQL error occured (see details below)!"\
"\n\n"\
"If you're facing an 'Illegal mix of collations error, consider "\
"running rake task "\
"'migrations:journals:fix_attachments_collation'."\
"\n\n"\
"#{ex.message}"
end
result.collect { |row| MissingAttachment.new(row['journaled_id'],
row['journaled_type'],
row['attachment_id'],
row['filename'],
row['last_version']) }
end
COLUMNS = ['changed_data', 'version', 'journaled_id']
def invalid_attachments(legacy_journal_type)
result = []
update_column_values('legacy_journals',
COLUMNS,
find_work_packages_with_missing_initial_attachment(legacy_journal_type,
result),
journal_filter(legacy_journal_type))
result.flatten
end
def journal_filter(legacy_journal_type)
"type = '#{legacy_journal_type}' AND changed_data LIKE '%attachments%'"
end
def find_work_packages_with_missing_initial_attachment(legacy_journal_type, result)
Proc.new do |row|
missing_entries = missing_initial_attachable_journals(legacy_journal_type,
row['id'],
row['journaled_id'],
row['version'],
row['changed_data'])
result << missing_entries unless missing_entries.empty?
UpdateResult.new(row, false)
end
end
def missing_initial_attachable_journals(legacy_journal_type, journal_id, journaled_id, version, changed_data)
removed_attachments = parse_attachment_removals(changed_data)
missing_entries = missing_initial_attachment_entries(legacy_journal_type,
journaled_id,
version,
removed_attachments)
missing_entries.map { |e| MissingAttachment.new(journaled_id,
nil,
e[:id],
e[:filename],
version.to_i - 1) }
end
############################################
# Matches attachment removals of the form: #
# #
# attachments<id>: #
# - #
# - <filename> #
############################################
ATTACHMENT_REMOVAL_REGEX = /attachments_(?<id>\d+): \n-\s(?<filename>.+)\n-\s$/
def parse_attachment_removals(changed_data)
matches = changed_data.scan(ATTACHMENT_REMOVAL_REGEX)
matches.each_with_object([]) { |m, l| l << { id: m[0], filename: m[1] } }
end
def missing_initial_attachment_entries(legacy_journal_type, journaled_id, version, attachments)
attachments.select do |a|
result = select_all <<-SQL
SELECT version
FROM legacy_journals
WHERE journaled_id = #{journaled_id}
AND type = '#{legacy_journal_type}'
AND version < #{version}
AND changed_data LIKE '%attachments#{a[:id]}:%'
AND changed_data LIKE '%- #{a[:filename]}%'
ORDER BY version
SQL
result.empty?
end
end
def affected_journal_ids(journaled_id, last_version, journal_type)
result_set = select_all <<-SQL
SELECT id
FROM journals
WHERE journable_id = #{journaled_id}
AND journable_type = '#{journal_type}'
AND version <= #{last_version}
SQL
result_set.collect { |r| r['id'] }
end
end
end