Repository: fossasia/open-event-server

Issue:
IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "event_sub_topics_slug_key"
https://sentry.eventyay.com/eventyay/api/issues/16/

```
IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "event_sub_topics_slug_key"
DETAIL:  Key (slug)=(other) already exists.
 [SQL: "INSERT INTO event_sub_topics(name, slug, event_topic_id) SELECT DISTINCT event_sub_topic_id, lower(replace(regexp_replace(event_sub_topic_id, '& |,', '', 'g'), ' ', '-')), event_topic_id               FROM events where not exists (SELECT 1 FROM event_sub_topics where event_sub_topics.name=events.event_sub_topic_id) and event_sub_topic_id is not null"]
(25 additional frame(s) were not displayed)
...
  File "sqlalchemy/engine/base.py", line 1189, in _execute_context
    context)
  File "sqlalchemy/engine/base.py", line 1402, in _handle_dbapi_exception
    exc_info
  File "sqlalchemy/util/compat.py", line 203, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)

IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "event_sub_topics_slug_key"
DETAIL:  Key (slug)=(other) already exists.
 [SQL: "INSERT INTO event_sub_topics(name, slug, event_topic_id) SELECT DISTINCT event_sub_topic_id, lower(replace(regexp_replace(event_sub_topic_id, '& |,', '', 'g'), ' ', '-')), event_topic_id               FROM events where not exists (SELECT 1 FROM event_sub_topics where event_sub_topics.name=events.event_sub_topic_id) and event_sub_topic_id is not null"]
```

> This occurred while running the migrations against a copy of the vintage production database

The slug should be unique along with event_topic_id. (ie) a same slug=event_topic_id pair cannot exist. 

Eg. 

Valid entries:
```
slug = other
event_topic_id = 1

slug = other
event_topic_id = 2
```
Invalid entries.
```
slug = other
event_topic_id = 1

slug = other
event_topic_id = 1
```


Discussion:

Resolving patch:
diff --git a/migrations/versions/52d175c3da98_.py b/migrations/versions/52d175c3da98_.py
--- a/migrations/versions/52d175c3da98_.py
+++ b/migrations/versions/52d175c3da98_.py
@@ -18,24 +18,27 @@
 def upgrade():
     ### commands auto generated by Alembic - please adjust! ###
     op.create_table('event_sub_topics',
-    sa.Column('id', sa.Integer(), nullable=False),
-    sa.Column('name', sa.String(), nullable=False),
-    sa.Column('slug', sa.String(), nullable=False),
-    sa.Column('event_topic_id', sa.Integer(), nullable=True),
-    sa.ForeignKeyConstraint(['event_topic_id'], ['event_topics.id'], ondelete='CASCADE'),
-    sa.PrimaryKeyConstraint('id'),
-    sa.UniqueConstraint('id'),
-    sa.UniqueConstraint('slug')
-    )
+                    sa.Column('id', sa.Integer(), nullable=False),
+                    sa.Column('name', sa.String(), nullable=False),
+                    sa.Column('slug', sa.String(), nullable=False),
+                    sa.Column('event_topic_id', sa.Integer(), nullable=True),
+                    sa.ForeignKeyConstraint(['event_topic_id'], ['event_topics.id'], ondelete='CASCADE'),
+                    sa.PrimaryKeyConstraint('id'),
+                    sa.UniqueConstraint('id')
+                    )
+    op.execute("CREATE UNIQUE INDEX slug_event_topic_id ON event_sub_topics (slug, event_topic_id)")
     op.alter_column('events', 'sub_topic', new_column_name='event_sub_topic_id')
     op.alter_column('events_version', 'sub_topic', new_column_name='event_sub_topic_id')
     op.execute('INSERT INTO event_sub_topics(name, slug, event_topic_id) SELECT DISTINCT event_sub_topic_id, lower(replace(regexp_replace(event_sub_topic_id, \'& |,\', \'\', \'g\'), \' \', \'-\')), event_topic_id\
                FROM events where not exists (SELECT 1 FROM event_sub_topics where event_sub_topics.name=events.event_sub_topic_id) and event_sub_topic_id is not null')
-    op.execute('UPDATE events SET event_sub_topic_id = (SELECT id FROM event_sub_topics WHERE event_sub_topics.name=events.event_sub_topic_id)')
+    op.execute(
+        'UPDATE events SET event_sub_topic_id = (SELECT id FROM event_sub_topics WHERE event_sub_topics.name=events.event_sub_topic_id)')
     op.execute('ALTER TABLE events ALTER COLUMN event_sub_topic_id TYPE integer USING event_sub_topic_id::integer')
     op.create_foreign_key(None, 'events', 'event_sub_topics', ['event_sub_topic_id'], ['id'], ondelete='CASCADE')
-    op.execute('UPDATE events_version SET event_sub_topic_id = (SELECT id FROM event_sub_topics WHERE event_sub_topics.name=events_version.event_sub_topic_id)')
-    op.execute('ALTER TABLE events_version ALTER COLUMN event_sub_topic_id TYPE integer USING event_sub_topic_id::integer')
+    op.execute(
+        'UPDATE events_version SET event_sub_topic_id = (SELECT id FROM event_sub_topics WHERE event_sub_topics.name=events_version.event_sub_topic_id)')
+    op.execute(
+        'ALTER TABLE events_version ALTER COLUMN event_sub_topic_id TYPE integer USING event_sub_topic_id::integer')
     op.execute('UPDATE event_types set slug=replace(slug, \'/\', \'-\') where slug like \'%/%\'')
     op.execute('UPDATE event_topics set slug=replace(slug, \'/\', \'-\') where slug like \'%/%\'')
     op.execute('UPDATE event_sub_topics set slug=replace(slug, \'/\', \'-\') where slug like \'%/%\'')
@@ -46,9 +49,12 @@ def downgrade():
     ### commands auto generated by Alembic - please adjust! ###
     op.drop_constraint('events_event_sub_topic_id_fkey', 'events', type_='foreignkey')
     op.execute('ALTER TABLE events ALTER COLUMN event_sub_topic_id TYPE varchar USING event_sub_topic_id::varchar')
-    op.execute('UPDATE events SET event_sub_topic_id = (SELECT name FROM event_sub_topics WHERE event_sub_topics.id=cast(events.event_sub_topic_id as int))')
-    op.execute('ALTER TABLE events_version ALTER COLUMN event_sub_topic_id TYPE varchar USING event_sub_topic_id::varchar')
-    op.execute('UPDATE events_version SET event_sub_topic_id = (SELECT name FROM event_sub_topics WHERE event_sub_topics.id=cast(events_version.event_sub_topic_id as int))')
+    op.execute(
+        'UPDATE events SET event_sub_topic_id = (SELECT name FROM event_sub_topics WHERE event_sub_topics.id=cast(events.event_sub_topic_id as int))')
+    op.execute(
+        'ALTER TABLE events_version ALTER COLUMN event_sub_topic_id TYPE varchar USING event_sub_topic_id::varchar')
+    op.execute(
+        'UPDATE events_version SET event_sub_topic_id = (SELECT name FROM event_sub_topics WHERE event_sub_topics.id=cast(events_version.event_sub_topic_id as int))')
     op.alter_column('events', 'event_sub_topic_id', new_column_name='sub_topic')
     op.alter_column('events_version', 'event_sub_topic_id', new_column_name='sub_topic')
     op.drop_table('event_sub_topics')


Test patch: