Browse Source

Tolera índices já existentes nas migrations 0071/0072 de sessao

Antes de serem renumeradas no #3855, estas migrations se chamavam
0070_votoparlamentar_unique_constraint e
0071_votacao_aberta_unique_constraint, e uma imagem de teste as aplicou
com esses nomes em produção. Nesses bancos, o RC4 tenta aplicar
0071_votoparlamentar_unique_constraint de novo e falha com
DuplicateTable, porque os índices únicos já existem.

Os AddConstraint passam a ser só operações de estado, e o banco recebe
o mesmo CREATE UNIQUE INDEX que o Django geraria, com IF NOT EXISTS.
fix/migrations-votacao-indices-existentes
João Moraes 2 weeks ago
parent
commit
d58bef8301
  1. 29
      sapl/sessao/migrations/0071_votoparlamentar_unique_constraint.py
  2. 25
      sapl/sessao/migrations/0072_votacao_aberta_unique_constraint.py
  3. 24
      sapl/sessao/tests/test_votacao_nominal.py

29
sapl/sessao/migrations/0071_votoparlamentar_unique_constraint.py

@ -3,6 +3,23 @@
from django.db import migrations, models from django.db import migrations, models
from django.db.models import Count from django.db.models import Count
# Antes de ser renumerada no #3855, esta migration se chamava
# 0070_votoparlamentar_unique_constraint e chegou a ser aplicada em alguns
# bancos de produção. Eles já têm os índices, mas não registram a migration
# com o nome atual: o IF NOT EXISTS evita o DuplicateTable neles.
# Nos demais, o SQL é o mesmo que o AddConstraint geraria.
INDICE_UNICO_SQL = """
CREATE UNIQUE INDEX IF NOT EXISTS "{nome}"
ON "sessao_votoparlamentar" ("parlamentar_id", "{coluna}")
WHERE "{coluna}" IS NOT NULL;
"""
def indice_unico(nome, coluna):
return migrations.RunSQL(
INDICE_UNICO_SQL.format(nome=nome, coluna=coluna),
'DROP INDEX IF EXISTS "{}";'.format(nome))
def remove_votos_duplicados(apps, schema_editor): def remove_votos_duplicados(apps, schema_editor):
"""Preserva o voto canônico antes de criar os índices únicos.""" """Preserva o voto canônico antes de criar os índices únicos."""
@ -43,6 +60,16 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RunPython( migrations.RunPython(
remove_votos_duplicados, migrations.RunPython.noop), remove_votos_duplicados, migrations.RunPython.noop),
migrations.SeparateDatabaseAndState(
database_operations=[
indice_unico(
'sessao_votoparlamentar_unique_parlamentar_ordem',
'ordem_id'),
indice_unico(
'sessao_votoparlamentar_unique_parlamentar_expediente',
'expediente_id'),
],
state_operations=[
migrations.AddConstraint( migrations.AddConstraint(
model_name='votoparlamentar', model_name='votoparlamentar',
constraint=models.UniqueConstraint(condition=models.Q(ordem__isnull=False), fields=('parlamentar', 'ordem'), name='sessao_votoparlamentar_unique_parlamentar_ordem'), constraint=models.UniqueConstraint(condition=models.Q(ordem__isnull=False), fields=('parlamentar', 'ordem'), name='sessao_votoparlamentar_unique_parlamentar_ordem'),
@ -51,4 +78,6 @@ class Migration(migrations.Migration):
model_name='votoparlamentar', model_name='votoparlamentar',
constraint=models.UniqueConstraint(condition=models.Q(expediente__isnull=False), fields=('parlamentar', 'expediente'), name='sessao_votoparlamentar_unique_parlamentar_expediente'), constraint=models.UniqueConstraint(condition=models.Q(expediente__isnull=False), fields=('parlamentar', 'expediente'), name='sessao_votoparlamentar_unique_parlamentar_expediente'),
), ),
],
),
] ]

25
sapl/sessao/migrations/0072_votacao_aberta_unique_constraint.py

@ -2,6 +2,21 @@
from django.db import migrations, models from django.db import migrations, models
# Mesmo caso da 0071: com o nome antigo 0071_votacao_aberta_unique_constraint
# esta migration já foi aplicada em alguns bancos de produção, que têm os
# índices sem registrar a migration com o nome atual.
INDICE_UNICO_SQL = """
CREATE UNIQUE INDEX IF NOT EXISTS "{nome}"
ON "{tabela}" ("votacao_aberta")
WHERE "votacao_aberta" = true;
"""
def indice_unico(nome, tabela):
return migrations.RunSQL(
INDICE_UNICO_SQL.format(nome=nome, tabela=tabela),
'DROP INDEX IF EXISTS "{}";'.format(nome))
def fecha_materias_abertas_duplicadas(apps, schema_editor): def fecha_materias_abertas_duplicadas(apps, schema_editor):
""" """
@ -34,6 +49,14 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RunPython( migrations.RunPython(
fecha_materias_abertas_duplicadas, migrations.RunPython.noop), fecha_materias_abertas_duplicadas, migrations.RunPython.noop),
migrations.SeparateDatabaseAndState(
database_operations=[
indice_unico('sessao_expedientemateria_unique_votacao_aberta',
'sessao_expedientemateria'),
indice_unico('sessao_ordemdia_unique_votacao_aberta',
'sessao_ordemdia'),
],
state_operations=[
migrations.AddConstraint( migrations.AddConstraint(
model_name='expedientemateria', model_name='expedientemateria',
constraint=models.UniqueConstraint(condition=models.Q(votacao_aberta=True), fields=('votacao_aberta',), name='sessao_expedientemateria_unique_votacao_aberta'), constraint=models.UniqueConstraint(condition=models.Q(votacao_aberta=True), fields=('votacao_aberta',), name='sessao_expedientemateria_unique_votacao_aberta'),
@ -42,4 +65,6 @@ class Migration(migrations.Migration):
model_name='ordemdia', model_name='ordemdia',
constraint=models.UniqueConstraint(condition=models.Q(votacao_aberta=True), fields=('votacao_aberta',), name='sessao_ordemdia_unique_votacao_aberta'), constraint=models.UniqueConstraint(condition=models.Q(votacao_aberta=True), fields=('votacao_aberta',), name='sessao_ordemdia_unique_votacao_aberta'),
), ),
],
),
] ]

24
sapl/sessao/tests/test_votacao_nominal.py

@ -667,6 +667,30 @@ def test_migracao_0072_fecha_duplicatas_antes_da_constraint():
assert encerrada.registro_aberto is False assert encerrada.registro_aberto is False
@pytest.mark.django_db(transaction=False)
@pytest.mark.parametrize('anterior, migracao', [
('0070_presenca_unica_por_sessao',
'0071_votoparlamentar_unique_constraint'),
('0071_votoparlamentar_unique_constraint',
'0072_votacao_aberta_unique_constraint'),
])
def test_migracao_tolera_indices_ja_existentes(anterior, migracao):
"""
Bancos que aplicaram estas migrations com a numeração antiga
(0070_votoparlamentar_unique_constraint e
0071_votacao_aberta_unique_constraint) já têm os índices, mas não as
registram com o nome atual. Reaplicá-las não pode falhar com
DuplicateTable — o banco de teste já tem os índices, como eles.
"""
from django.db import connection
from django.db.migrations.loader import MigrationLoader
loader = MigrationLoader(connection)
estado = loader.project_state(('sessao', anterior))
with connection.schema_editor() as schema_editor:
loader.get_migration('sessao', migracao).apply(estado, schema_editor)
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_api_nao_permite_abrir_votacao_via_patch(admin_client): def test_api_nao_permite_abrir_votacao_via_patch(admin_client):
""" """

Loading…
Cancel
Save