From a637c8eb92007c33704f90bcf61c60c6125556d3 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 20 Apr 2021 13:42:20 +0100 Subject: [PATCH 1/2] Add unique key on annual_billing for service_id + financial_year_start. This is an extra precaution for the table to ensure data integrity. Since we only update/insert the data using the annual_billing_dao methods the integrity is in tact. I've check the data on preview, staging and prod there are no violations of this unique key. --- app/dao/annual_billing_dao.py | 2 +- app/models.py | 3 +++ .../0351_unique_key_annual_billing.py | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/0351_unique_key_annual_billing.py diff --git a/app/dao/annual_billing_dao.py b/app/dao/annual_billing_dao.py index 9497b0d75..ffad11ade 100644 --- a/app/dao/annual_billing_dao.py +++ b/app/dao/annual_billing_dao.py @@ -102,7 +102,7 @@ def set_default_free_allowance_for_service(service, year_start=None): f"{default_free_sms_fragment_limits['other'][year_start]}") free_allowance = default_free_sms_fragment_limits['other'][year_start] - dao_create_or_update_annual_billing_for_year( + return dao_create_or_update_annual_billing_for_year( service.id, free_allowance, year_start diff --git a/app/models.py b/app/models.py index 9d0db0563..2b91d9395 100644 --- a/app/models.py +++ b/app/models.py @@ -579,6 +579,9 @@ class AnnualBilling(db.Model): UniqueConstraint('financial_year_start', 'service_id', name='ix_annual_billing_service_id') service = db.relationship(Service, backref=db.backref("annual_billing", uselist=True)) + __table_args__ = (UniqueConstraint( + 'service_id', 'financial_year_start', name='uix_service_id_financial_year_start'),) + def serialize_free_sms_items(self): return { 'free_sms_fragment_limit': self.free_sms_fragment_limit, diff --git a/migrations/versions/0351_unique_key_annual_billing.py b/migrations/versions/0351_unique_key_annual_billing.py new file mode 100644 index 000000000..2f814b815 --- /dev/null +++ b/migrations/versions/0351_unique_key_annual_billing.py @@ -0,0 +1,22 @@ +""" + +Revision ID: 0351_unique_key_annual_billing +Revises: 0350_update_rates +Create Date: 2021-04-12 09:02:45.098875 + +""" +from alembic import op + +revision = '0351_unique_key_annual_billing' +down_revision = '0350_update_rates' + + +def upgrade(): + op.create_unique_constraint( + 'uix_service_id_financial_year_start', 'annual_billing', ['service_id', 'financial_year_start'] + ) + + +def downgrade(): + op.drop_constraint('uix_service_id_financial_year_start', 'annual_billing', type_='unique') + From 490efad97a00f1473251b0fce1f76d3b5ce2b3d4 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Fri, 23 Apr 2021 15:08:23 +0100 Subject: [PATCH 2/2] To avoid any table locks and contention on the production database we are going to add the unique index concurrently. This only works if we execute a commit first. --- .../versions/0351_unique_key_annual_billing.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/migrations/versions/0351_unique_key_annual_billing.py b/migrations/versions/0351_unique_key_annual_billing.py index 2f814b815..288b18395 100644 --- a/migrations/versions/0351_unique_key_annual_billing.py +++ b/migrations/versions/0351_unique_key_annual_billing.py @@ -5,16 +5,28 @@ Revises: 0350_update_rates Create Date: 2021-04-12 09:02:45.098875 """ +import os + from alembic import op revision = '0351_unique_key_annual_billing' down_revision = '0350_update_rates' +environment = os.environ['NOTIFY_ENVIRONMENT'] + def upgrade(): - op.create_unique_constraint( - 'uix_service_id_financial_year_start', 'annual_billing', ['service_id', 'financial_year_start'] - ) + index = """ + CREATE UNIQUE INDEX CONCURRENTLY uix_service_id_financial_year_start + ON annual_billing (service_id, financial_year_start) + """ + constraint = """ + ALTER TABLE annual_BILLING add constraint uix_service_id_financial_year_start + UNIQUE USING INDEX uix_service_id_financial_year_start + """ + op.execute('COMMIT') + op.execute(index) + op.execute(constraint) def downgrade():