From dc623f5c5ca0804b41b5cb77c8354c6aaf07af1d Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 20 Sep 2016 11:21:43 +0200 Subject: [PATCH] Improving history migration 0011 --- .../projects/epics/migrations/0001_initial.py | 1 + .../migrations/0011_auto_20160629_1036.py | 156 ++++++++++++++++-- 2 files changed, 144 insertions(+), 13 deletions(-) diff --git a/taiga/projects/epics/migrations/0001_initial.py b/taiga/projects/epics/migrations/0001_initial.py index 78d2dc8f..e757b7be 100644 --- a/taiga/projects/epics/migrations/0001_initial.py +++ b/taiga/projects/epics/migrations/0001_initial.py @@ -17,6 +17,7 @@ class Migration(migrations.Migration): dependencies = [ ('userstories', '0012_auto_20160614_1201'), ('projects', '0049_auto_20160629_1443'), + ('history', '0012_auto_20160629_1036'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] diff --git a/taiga/projects/history/migrations/0011_auto_20160629_1036.py b/taiga/projects/history/migrations/0011_auto_20160629_1036.py index d3f1eb02..698f6a21 100644 --- a/taiga/projects/history/migrations/0011_auto_20160629_1036.py +++ b/taiga/projects/history/migrations/0011_auto_20160629_1036.py @@ -2,30 +2,160 @@ # Generated by Django 1.9.2 on 2016-06-29 10:36 from __future__ import unicode_literals -from django.db import migrations +from django.db import migrations, connection from taiga.projects.history.services import get_instance_from_key -def forward_func(apps, schema_editor): - HistoryEntry = apps.get_model("history", "HistoryEntry") - db_alias = schema_editor.connection.alias - for entry in HistoryEntry.objects.using(db_alias).all().iterator(): - instance = get_instance_from_key(entry.key) - if type(instance) == apps.get_model("projects", "Project"): - entry.project_id = instance.id - else: - entry.project_id = getattr(instance, 'project_id', None) - entry.save() +GENERATE_CORRECT_HISTORY_ENTRIES_TABLE = """ + -- Creating a table containing all the existing object keys and the project ids + DROP TABLE IF EXISTS project_keys; + CREATE TABLE project_keys ( + key VARCHAR, + project_id INTEGER + ); - HistoryEntry.objects.using(db_alias).filter(project_id__isnull=True).delete() + DROP INDEX IF EXISTS project_keys_index; + CREATE INDEX project_keys_index + ON project_keys + USING btree + (key); + + INSERT INTO project_keys + SELECT 'milestones.milestone:' || id, project_id + FROM milestones_milestone; + + INSERT INTO project_keys + SELECT 'userstories.userstory:' || id, project_id + FROM userstories_userstory; + + INSERT INTO project_keys + SELECT 'tasks.task:' || id, project_id + FROM tasks_task; + + INSERT INTO project_keys + SELECT 'issues.issue:' || id, project_id + FROM issues_issue; + + INSERT INTO project_keys + SELECT 'wiki.wikipage:' || id, project_id + FROM wiki_wikipage; + + INSERT INTO project_keys + SELECT 'projects.project:' || id, id + FROM projects_project; + + -- Create a table where we will insert all the history_historyentry content with its correct project_id + -- Elements without project_id won't be inserted + DROP TABLE IF EXISTS history_historyentry_correct; + CREATE TABLE history_historyentry_correct AS + SELECT + history_historyentry.id , + history_historyentry.user, + history_historyentry.created_at, + history_historyentry.type, + history_historyentry.is_snapshot, + history_historyentry.key, + history_historyentry.diff, + history_historyentry.snapshot, + history_historyentry.values, + history_historyentry.comment, + history_historyentry.comment_html, + history_historyentry.delete_comment_date, + history_historyentry.delete_comment_user, + history_historyentry.is_hidden, + history_historyentry.comment_versions, + history_historyentry.edit_comment_date, + project_keys.project_id + FROM history_historyentry + INNER JOIN project_keys + ON project_keys.key = history_historyentry.key; + + -- Delete aux table + DROP TABLE IF EXISTS project_keys; + """ + +def get_constraints_def_sql(table_name): + cursor = connection.cursor() + query = """ + SELECT 'ALTER TABLE "'||nspname||'"."'||relname||'" ADD CONSTRAINT "'||conname||'" '|| + pg_get_constraintdef(pg_constraint.oid)||';' + FROM pg_constraint + INNER JOIN pg_class ON conrelid=pg_class.oid + INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace + WHERE relname='{}' + ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END DESC,contype DESC,nspname DESC,relname DESC,conname DESC; + """.format(table_name) + cursor.execute(query) + return [row[0] for row in cursor.fetchall()] + + +def get_indexes_def_sql(table_name): + cursor = connection.cursor() + query = """ + SELECT pg_get_indexdef(idx.oid)||';' + FROM pg_index ind + JOIN pg_class idx ON idx.oid = ind.indexrelid + JOIN pg_class tbl ON tbl.oid = ind.indrelid + LEFT JOIN pg_namespace ns ON ns.oid = tbl.relnamespace + WHERE + tbl.relname = '{}' AND + indisprimary=FALSE; + """.format(table_name) + cursor.execute(query) + return [row[0] for row in cursor.fetchall()] + + +def drop_constraints(table_name): + # This query returns all the ALTER sentences needed to drop the constraints + cursor = connection.cursor() + alter_sentences_query = """ + SELECT 'ALTER TABLE "'||nspname||'"."'||relname||'" DROP CONSTRAINT "'||conname||'" '||';' + FROM pg_constraint + INNER JOIN pg_class ON conrelid=pg_class.oid + INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace + WHERE relname='{}' + ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END DESC,contype DESC,nspname DESC,relname DESC,conname DESC; + """.format(table_name) + cursor.execute(alter_sentences_query) + alter_sentences = [row[0] for row in cursor.fetchall()] + + #Now we execute those sentences + for alter_sentence in alter_sentences: + cursor.execute(alter_sentence) + + +def toggle_history_entries_tables(apps, schema_editor): + history_entry_sql_def_contraints = get_constraints_def_sql("history_historyentry") + history_entry_sql_def_indexes = get_indexes_def_sql("history_historyentry") + history_change_notifications_sql_def_contraints = get_constraints_def_sql("notifications_historychangenotification_history_entries") + drop_constraints("notifications_historychangenotification_history_entries") + cursor = connection.cursor() + cursor.execute(""" + DELETE FROM notifications_historychangenotification_history_entries; + DROP TABLE history_historyentry; + ALTER TABLE "history_historyentry_correct" RENAME to "history_historyentry"; + """) + + for history_entry_sql_def_contraint in history_entry_sql_def_contraints: + cursor.execute(history_entry_sql_def_contraint) + + for history_entry_sql_def_index in history_entry_sql_def_indexes: + cursor.execute(history_entry_sql_def_index) + + # Restoring the dropped constraints and indexes + for history_change_notifications_sql_def_contraint in history_change_notifications_sql_def_contraints: + cursor.execute(history_change_notifications_sql_def_contraint) class Migration(migrations.Migration): dependencies = [ ('history', '0010_historyentry_project'), + ('wiki', '0003_auto_20160615_0721'), + ('users', '0022_auto_20160629_1443') ] operations = [ - migrations.RunPython(forward_func, atomic=False), + migrations.RunSQL(GENERATE_CORRECT_HISTORY_ENTRIES_TABLE), + migrations.RunPython(toggle_history_entries_tables) ]