Browse Source

Merge branch '1576-migra-autor'

Fix #1576
pull/1678/head
Marcio Mazza 7 years ago
parent
commit
0fabd68dea
  1. 143
      sapl/legacy/migration.py
  2. 13
      sapl/legacy/scripts/utils.py
  3. 55
      sapl/legacy/test_migration.py

143
sapl/legacy/migration.py

@ -1,6 +1,7 @@
import re import re
from datetime import date from datetime import date
from functools import lru_cache, partial from functools import lru_cache, partial
from itertools import groupby
from subprocess import PIPE, call from subprocess import PIPE, call
import pkg_resources import pkg_resources
@ -144,6 +145,13 @@ def exec_sql(sql, db='default'):
cursor.execute(sql) cursor.execute(sql)
return cursor 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 ############################### # UNIFORMIZAÇÃO DO BANCO ANTES DA MIGRAÇÃO ###############################
SQL_NAO_TEM_TABELA = ''' SQL_NAO_TEM_TABELA = '''
@ -152,19 +160,17 @@ SQL_NAO_TEM_TABELA = '''
WHERE table_schema=database() WHERE table_schema=database()
AND TABLE_NAME="{}" 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): def existe_tabela_no_legado(tabela):
sql = SQL_NAO_TEM_TABELA.format(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): def existe_coluna_no_legado(tabela, coluna):
sql = SQL_NAO_TEM_COLUNA.format(tabela, coluna) sql_nao_tem_coluna = SQL_NAO_TEM_TABELA + ' AND COLUMN_NAME="{}"'
return exec_legado(sql).fetchone()[0] > 0 sql = sql_nao_tem_coluna.format(tabela, coluna)
return primeira_coluna(exec_legado(sql))[0] > 0
def garante_coluna_no_legado(tabela, spec_coluna): 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) assert existe_tabela_no_legado(tabela)
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])))
# 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(): def uniformiza_banco():
exec_legado(''' exec_legado('''
SELECT replace(@@sql_mode,"STRICT_TRANS_TABLES,","ALLOW_INVALID_DATES"); 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('|') spec = spec.split('|')
exec_legado('UPDATE {} SET {} WHERE {}'.format(*spec)) 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): def iter_sql_records(sql, db):
class Record: class Record:

13
sapl/legacy/scripts/utils.py

@ -1,6 +1,19 @@
import inspect import inspect
from sapl.base.models import Autor
from sapl.legacy.migration import appconfs
def getsourcelines(model): def getsourcelines(model):
return [line.rstrip('\n').decode('utf-8') return [line.rstrip('\n').decode('utf-8')
for line in inspect.getsourcelines(model)[0]] 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