Browse Source

Refatora unificação de autores repetidos

Com alguns testes acrescentados
pull/1596/head
Marcio Mazza 7 years ago
parent
commit
7768da56ed
  1. 360
      sapl/legacy/migration.py
  2. 13
      sapl/legacy/scripts/utils.py
  3. 55
      sapl/legacy/test_migration.py

360
sapl/legacy/migration.py

@ -1,12 +1,12 @@
import re
from datetime import date
from functools import lru_cache, partial
from itertools import groupby
from subprocess import PIPE, call
import pkg_resources
import yaml
import reversion
import yaml
from django.apps import apps
from django.apps.config import AppConfig
from django.contrib.auth import get_user_model
@ -16,6 +16,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, transaction
from django.db.models import Count, Max
from django.db.models.base import ModelBase
from sapl.base.models import AppConfig as AppConf
from sapl.base.models import (Autor, CasaLegislativa, ProblemaMigracao,
TipoAutor)
@ -144,28 +145,32 @@ def exec_sql(sql, db='default'):
cursor.execute(sql)
return cursor
# UNIFORMIZAÇÃO DO BANCO ANTES DA MIGRAÇÃO ###############################
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 = '''
SELECT count(*)
FROM information_schema.columns
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):
@ -182,223 +187,120 @@ def garante_tabela_no_legado(create_table):
assert existe_tabela_no_legado(tabela)
def migra_autor():
SQL_ENUMERA_REPETIDOS = '''
select cod_parlamentar, COUNT(*)
from autor where cod_parlamentar is not null
group by cod_parlamentar
having 1 < COUNT(*)
order by cod_parlamentar asc;
'''
SQL_INFOS_AUTOR = '''
select cod_autor from autor
where cod_parlamentar = {}
group by cod_autor
order by col_username, des_cargo desc;
'''
SQL_UPDATE_AUTOR = "update autoria set cod_autor = {} where cod_autor in ({});"
SQL_ENUMERA_AUTORIA_REPETIDOS = '''
select cod_materia, COUNT(*) from autoria where cod_autor in ({})
group by cod_materia
having 1 < COUNT(*);
'''
SQL_DELETE_AUTORIA = '''
delete from autoria where cod_materia in ({}) and cod_autor in ({});
'''
SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO = '''
update documento_administrativo
set cod_autor = {}
where cod_autor in ({});
'''
SQL_UPDATE_PROPOSICAO = '''
update proposicao
set cod_autor = {}
where cod_autor in ({});
'''
SQL_UPDATE_PROTOCOLO = '''
update protocolo
set cod_autor = {}
where cod_autor in ({});
'''
SQL_DELETE_AUTOR = '''
delete from autor where cod_autor in ({})
and cod_autor not in ({});
'''
cursor = exec_legado('update autor set ind_excluido = 0 where cod_autor is not null;')
cursor = exec_legado(SQL_ENUMERA_REPETIDOS)
autores_parlamentares = [r[0] for r in cursor if r[0]]
for cod_autor in autores_parlamentares:
sql = SQL_INFOS_AUTOR.format(cod_autor)
cursor = exec_legado(sql)
autores = []
for response in cursor:
autores.append(response)
ids = [a[0] for a in autores]
id_ativo, ids_inativos = ids[-1], ids[:-1]
ids = str(ids).strip('[]')
id_ativo = str(id_ativo).strip('[]')
ids_inativos = str(ids_inativos).strip('[]')
tabelas = ['autoria', 'documento_administrativo',
'proposicao', 'protocolo']
for tabela in tabelas:
if tabela == 'autoria' and id_ativo and ids_inativos:
# Para update e delete no MySQL -> SET SQL_SAFE_UPDATES = 0;
sql = SQL_ENUMERA_AUTORIA_REPETIDOS.format(ids)
cursor = exec_legado(sql)
materias = []
for response in cursor:
materias.append(response[0])
materias = str(materias).strip('[]')
if materias:
sql = SQL_DELETE_AUTORIA.format(materias, ids_inativos)
exec_legado(sql)
sql = SQL_UPDATE_AUTOR.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'documento_administrativo' and id_ativo and ids_inativos:
sql = SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'proposicao' and id_ativo and ids_inativos:
sql = SQL_UPDATE_PROPOSICAO.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'protocolo' and id_ativo and ids_inativos:
sql = SQL_UPDATE_PROTOCOLO.format(id_ativo, ids_inativos)
exec_legado(sql)
# Faz a exclusão dos autores que não serão migrados
sql = SQL_DELETE_AUTOR.format(ids, id_ativo)
cursor = exec_legado(sql)
def migra_comissao():
SQL_ENUMERA_REPETIDOS = '''
select cod_comissao, COUNT(*)
from autor where cod_comissao is not null
group by cod_comissao
having 1 < COUNT(*)
order by cod_comissao asc;
'''
SQL_INFOS_COMISSAO = '''
select cod_autor from autor
where cod_comissao = {}
group by cod_autor;
'''
SQL_UPDATE_AUTOR = "update autoria set cod_autor = {} where cod_autor in ({});"
SQL_ENUMERA_AUTORIA_REPETIDOS = '''
select cod_materia, COUNT(*) from autoria where cod_autor in ({})
group by cod_materia
having 1 < COUNT(*);
'''
SQL_DELETE_AUTORIA = '''
delete from autoria where cod_materia in ({}) and cod_autor in ({});
'''
SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO = '''
update documento_administrativo
set cod_autor = {}
where cod_autor in ({});
'''
SQL_UPDATE_PROPOSICAO = '''
update proposicao
set cod_autor = {}
where cod_autor in ({});
'''
SQL_UPDATE_PROTOCOLO = '''
update protocolo
set cod_autor = {}
where cod_autor in ({});
'''
SQL_DELETE_AUTOR = '''
delete from autor where cod_autor in ({})
and cod_autor not in ({});
'''
cursor = exec_legado('update autor set ind_excluido = 0 where cod_comissao is not null;')
cursor = exec_legado(SQL_ENUMERA_REPETIDOS)
comissoes_parlamentares = [r[0] for r in cursor if r[0]]
for cod_comissao in comissoes_parlamentares:
sql = SQL_INFOS_COMISSAO.format(cod_comissao)
cursor = exec_legado(sql)
comissoes = []
for response in cursor:
comissoes.append(response)
ids = [c[0] for c in comissoes]
id_ativo, ids_inativos = ids[-1], ids[:-1]
ids = str(ids).strip('[]')
id_ativo = str(id_ativo).strip('[]')
ids_inativos = str(ids_inativos).strip('[]')
tabelas = ['autoria', 'documento_administrativo',
'proposicao', 'protocolo']
for tabela in tabelas:
if tabela == 'autoria' and id_ativo and ids_inativos:
# Para update e delete no MySQL -> SET SQL_SAFE_UPDATES = 0;
sql = SQL_ENUMERA_AUTORIA_REPETIDOS.format(ids)
cursor = exec_legado(sql)
materias = []
for response in cursor:
materias.append(response[0])
materias = str(materias).strip('[]')
if materias:
sql = SQL_DELETE_AUTORIA.format(materias, ids_inativos)
exec_legado(sql)
sql = SQL_UPDATE_AUTOR.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'documento_administrativo' and id_ativo and ids_inativos:
sql = SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'proposicao' and id_ativo and ids_inativos:
sql = SQL_UPDATE_PROPOSICAO.format(id_ativo, ids_inativos)
exec_legado(sql)
TABELAS_REFERENCIANDO_AUTOR = [
# <nome da tabela>, <tem ind excluido>
('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 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])))
elif tabela == 'protocolo' and id_ativo and ids_inativos:
sql = SQL_UPDATE_PROTOCOLO.format(id_ativo, ids_inativos)
exec_legado(sql)
# 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))
# Faz a exclusão dos autores que não serão migrados
sql = SQL_DELETE_AUTOR.format(ids, id_ativo)
cursor = exec_legado(sql)
# 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():
@ -481,8 +383,14 @@ relatoria | tip_fim_relatoria = NULL | tip_fim_relatoria = 0
spec = spec.split('|')
exec_legado('UPDATE {} SET {} WHERE {}'.format(*spec))
migra_autor() # Migra autores para um único autor
migra_comissao() # Migra comissões para uma única comissão
# 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):

13
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)]

55
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),
])
Loading…
Cancel
Save