diff --git a/sapl/legacy/migration.py b/sapl/legacy/migration.py index f61f564ff..136517e31 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migration.py @@ -1,6 +1,7 @@ import re from datetime import date from functools import lru_cache, partial +from itertools import groupby from subprocess import PIPE, call import pkg_resources @@ -144,6 +145,13 @@ def exec_sql(sql, db='default'): cursor.execute(sql) return cursor +exec_legado = partial(exec_sql, db='legacy') + + +def primeira_coluna(cursor): + return (r[0] for r in cursor) + + # UNIFORMIZAÇÃO DO BANCO ANTES DA MIGRAÇÃO ############################### SQL_NAO_TEM_TABELA = ''' @@ -152,19 +160,17 @@ SQL_NAO_TEM_TABELA = ''' WHERE table_schema=database() AND TABLE_NAME="{}" ''' -SQL_NAO_TEM_COLUNA = SQL_NAO_TEM_TABELA + ' AND COLUMN_NAME="{}"' - -exec_legado = partial(exec_sql, db='legacy') def existe_tabela_no_legado(tabela): sql = SQL_NAO_TEM_TABELA.format(tabela) - return exec_legado(sql).fetchone()[0] + return primeira_coluna(exec_legado(sql))[0] def existe_coluna_no_legado(tabela, coluna): - sql = SQL_NAO_TEM_COLUNA.format(tabela, coluna) - return exec_legado(sql).fetchone()[0] > 0 + sql_nao_tem_coluna = SQL_NAO_TEM_TABELA + ' AND COLUMN_NAME="{}"' + sql = sql_nao_tem_coluna.format(tabela, coluna) + return primeira_coluna(exec_legado(sql))[0] > 0 def garante_coluna_no_legado(tabela, spec_coluna): @@ -181,6 +187,122 @@ def garante_tabela_no_legado(create_table): assert existe_tabela_no_legado(tabela) +TABELAS_REFERENCIANDO_AUTOR = [ + # , + ('autoria', True), + ('documento_administrativo', True), + ('proposicao', True), + ('protocolo', False)] + + +def reverte_exclusao_de_autores_referenciados_no_legado(): + + def get_autores_referenciados(tabela, tem_ind_excluido): + sql = '''select distinct cod_autor from {} + where cod_autor is not null + '''.format(tabela) + if tem_ind_excluido: + sql += ' and ind_excluido != 1' + return primeira_coluna(exec_legado(sql)) + + # reverte exclusões de autores referenciados por outras tabelas + autores_referenciados = { + cod + for tabela, tem_ind_excluido in TABELAS_REFERENCIANDO_AUTOR + for cod in get_autores_referenciados(tabela, tem_ind_excluido)} + exec_legado( + 'update autor set ind_excluido = 0 where cod_autor in {}'.format( + tuple(autores_referenciados) + )) + + +def get_reapontamento_de_autores_repetidos(autores): + """ Dada uma lista ordenada de pares (cod_zzz, cod_autor) retorna: + + * a lista de grupos de cod_autor'es repetidos + (quando há mais de um cod_autor para um mesmo cod_zzz) + + * a lista de cod_autor'es a serem apagados (todos além do 1o de cada grupo) + """ + grupos_de_repetidos = [ + [cod_autor for _, cod_autor in grupo] + for cod_zzz, grupo in groupby(autores, lambda r: r[0])] + # mantém apenas os grupos com mais de um autor por cod_zzz + grupos_de_repetidos = [g for g in grupos_de_repetidos if len(g) > 1] + # aponta cada autor de cada grupo de repetidos para o 1o do seu grupo + reapontamento = {autor: grupo[0] + for grupo in grupos_de_repetidos + for autor in grupo} + # apagaremos todos menos o primeiro + apagar = [k for k, v in reapontamento.items() if k != v] + return reapontamento, apagar + + +def get_autorias_sem_repeticoes(autoria, reapontamento): + "Autorias sem repetições de autores e com ind_primeiro_autor ajustado" + + # substitui cada autor repetido pelo 1o de seu grupo + autoria = sorted((reapontamento[a], m, i) for a, m, i in autoria) + # agrupa por [autor (1o do grupo de repetidos), materia], com + # ind_primeiro_autor == 1 se isso acontece em qualquer autor do grupo + autoria = [(a, m, max(i for a, m, i in grupo)) + for (a, m), grupo in groupby(autoria, lambda x: x[:2])] + return autoria + + +def unifica_autores_repetidos_no_legado(campo_agregador): + "Reúne autores repetidos em um único, antes da migracão" + + # enumeramos a repeticoes segundo o campo relevante + # (p. ex. cod_parlamentar ou cod_comissao) + # a ordenação prioriza, as entradas: + # - não excluidas, + # - em seguida as que têm col_username, + # - em seguida as que têm des_cargo + autores = exec_legado(''' + select {cod_parlamentar}, cod_autor from autor + where {cod_parlamentar} is not null + order by {cod_parlamentar}, + ind_excluido, col_username desc, des_cargo desc'''.format( + cod_parlamentar=campo_agregador)) + + reapontamento, apagar = get_reapontamento_de_autores_repetidos(autores) + + # Reaponta AUTORIA (many-to-many) + + # simplificamos retirando inicialmente as autorias excluidas + exec_legado('delete from autoria where ind_excluido = 1') + + # selecionamos as autorias envolvidas em repetições de autores + from_autoria = ' from autoria where cod_autor in {}'.format( + tuple(reapontamento)) + autoria = exec_legado( + 'select cod_autor, cod_materia, ind_primeiro_autor' + from_autoria) + + # apagamos todas as autorias envolvidas + exec_legado('delete ' + from_autoria) + # e depois inserimos apenas as sem repetições c ind_primeiro_autor ajustado + nova_autoria = get_autorias_sem_repeticoes(autoria, reapontamento) + exec_legado(''' + insert into autoria + (cod_autor, cod_materia, ind_primeiro_autor, ind_excluido) + values {}'''.format(', '.join([str((a, m, i, 0)) + for a, m, i in nova_autoria]))) + + # Reaponta outras tabelas que referenciam autor + for tabela, _ in TABELAS_REFERENCIANDO_AUTOR: + for antigo, novo in reapontamento.items(): + if antigo != novo: + exec_legado(''' + update {} set cod_autor = {} where cod_autor = {} + '''.format(tabela, novo, antigo)) + + # Finalmente excluimos os autores redundantes, + # cujas referências foram todas substituídas a essa altura + exec_legado('delete from autor where cod_autor in {}'.format( + tuple(apagar))) + + def uniformiza_banco(): exec_legado(''' SELECT replace(@@sql_mode,"STRICT_TRANS_TABLES,","ALLOW_INVALID_DATES"); @@ -261,6 +383,15 @@ relatoria | tip_fim_relatoria = NULL | tip_fim_relatoria = 0 spec = spec.split('|') exec_legado('UPDATE {} SET {} WHERE {}'.format(*spec)) + # retira apontamentos de materia para assunto inexistente + exec_legado('delete from materia_assunto where cod_assunto = 0') + + # corrige string "None" em autor + exec_legado('update autor set des_cargo = NULL where des_cargo = "None"') + + unifica_autores_repetidos_no_legado('cod_parlamentar') + unifica_autores_repetidos_no_legado('cod_comissao') + def iter_sql_records(sql, db): class Record: diff --git a/sapl/legacy/scripts/utils.py b/sapl/legacy/scripts/utils.py index 6e2e03723..21f74ce52 100644 --- a/sapl/legacy/scripts/utils.py +++ b/sapl/legacy/scripts/utils.py @@ -1,6 +1,19 @@ import inspect +from sapl.base.models import Autor +from sapl.legacy.migration import appconfs + def getsourcelines(model): return [line.rstrip('\n').decode('utf-8') for line in inspect.getsourcelines(model)[0]] + + +def get_models_com_referencia_a_autor(): + + def tem_referencia_a_autor(model): + return any(getattr(field, 'related_model', None) == Autor + for field in model._meta.get_fields()) + + return [model for app in appconfs for model in app.models.values() + if tem_referencia_a_autor(model)] diff --git a/sapl/legacy/test_migration.py b/sapl/legacy/test_migration.py new file mode 100644 index 000000000..a1ae77365 --- /dev/null +++ b/sapl/legacy/test_migration.py @@ -0,0 +1,55 @@ +from random import shuffle + +from .migration import (get_autorias_sem_repeticoes, + get_reapontamento_de_autores_repetidos) + + +def test_unifica_autores_repetidos_no_legado(): + + # cod_parlamentar, cod_autor + autores = [[0, 0], + [1, 10], + [1, 11], + [1, 12], + [2, 20], + [2, 21], + [2, 22], + [3, 30], + [3, 31], + [4, 40], + [5, 50]] + reapontamento, apagar = get_reapontamento_de_autores_repetidos(autores) + assert reapontamento == {10: 10, 11: 10, 12: 10, + 20: 20, 21: 20, 22: 20, + 30: 30, 31: 30} + assert sorted(apagar) == [11, 12, 21, 22, 31] + + # cod_autor, cod_materia, ind_primeiro_autor + autoria = [[10, 111, 0], # não é repetida, mas envolve um autor repetido + + [22, 222, 1], # não é repetida, mas envolve um autor repetido + + [10, 777, 1], # repetição c ind_primeiro_autor==1 no INÍCIO + [10, 777, 0], + [11, 777, 0], + [12, 777, 0], + + [30, 888, 0], # repetição c ind_primeiro_autor==1 no MEIO + [31, 888, 1], + [30, 888, 0], + + [11, 999, 0], # repetição SEM ind_primeiro_autor==1 + [12, 999, 0], + + [21, 999, 0], # repetição SEM ind_primeiro_autor==1 + [22, 999, 0], + ] + shuffle(autoria) # não devemos supor ordem na autoria + nova_autoria = get_autorias_sem_repeticoes(autoria, reapontamento) + assert nova_autoria == sorted([(10, 111, 0), + (20, 222, 1), + (10, 777, 1), + (30, 888, 1), + (10, 999, 0), + (20, 999, 0), + ])