Home | History | Annotate | Download | only in migrations
      1 def execute_safely(manager, statement):
      2     try:
      3         manager.execute(statement)
      4     except Exception:
      5         print 'Statement %r failed (this is not fatal)' % statement
      6 
      7 
      8 def delete_duplicates(manager, table, first_id, second_id):
      9     rows = manager.execute(
     10         'SELECT %s, %s, COUNT(1) AS count FROM %s '
     11         'GROUP BY %s, %s HAVING count > 1' %
     12         (first_id, second_id, table, first_id, second_id))
     13     for first_id_value, second_id_value, count_unused in rows:
     14         manager.execute('DELETE FROM %s '
     15                         'WHERE %s = %%s AND %s = %%s LIMIT 1' %
     16                         (table, first_id, second_id),
     17                         first_id_value, second_id_value)
     18     if rows:
     19         print 'Deleted %s duplicate rows from %s' % (len(rows), table)
     20 
     21 
     22 def delete_invalid_foriegn_keys(manager, pivot_table, foreign_key_field,
     23                                 destination_table):
     24     manager.execute(
     25         'DELETE %(table)s.* FROM %(table)s '
     26         'LEFT JOIN %(destination_table)s '
     27         'ON %(table)s.%(field)s = %(destination_table)s.id '
     28         'WHERE %(destination_table)s.id IS NULL' %
     29         dict(table=pivot_table, field=foreign_key_field,
     30              destination_table=destination_table))
     31     deleted_count = manager._database.rowcount
     32     if deleted_count:
     33         print ('Deleted %s invalid foreign key references from %s (%s)' %
     34                (deleted_count, pivot_table, foreign_key_field))
     35 
     36 
     37 def unique_index_name(table):
     38     return table + '_both_ids'
     39 
     40 
     41 def basic_index_name(table, field):
     42     if field == 'aclgroup_id':
     43         field = 'acl_group_id'
     44     return table + '_' + field
     45 
     46 
     47 def create_unique_index(manager, pivot_table, first_field, second_field):
     48     index_name = unique_index_name(pivot_table)
     49     manager.execute('CREATE UNIQUE INDEX %s ON %s (%s, %s)' %
     50                     (index_name, pivot_table, first_field, second_field))
     51 
     52     # these indices are in the migrations but may not exist for historical
     53     # reasons
     54     old_index_name = basic_index_name(pivot_table, first_field)
     55     execute_safely(manager, 'DROP INDEX %s ON %s' %
     56                    (old_index_name, pivot_table))
     57 
     58 
     59 def drop_unique_index(manager, pivot_table, first_field):
     60     index_name = unique_index_name(pivot_table)
     61     manager.execute('DROP INDEX %s ON %s' % (index_name, pivot_table))
     62 
     63     old_index_name = basic_index_name(pivot_table, first_field)
     64     manager.execute('CREATE INDEX %s ON %s (%s)' %
     65                     (old_index_name, pivot_table, first_field))
     66 
     67 
     68 def foreign_key_name(table, field):
     69     return '_'.join([table, field, 'fk'])
     70 
     71 
     72 def create_foreign_key_constraint(manager, table, field, destination_table):
     73     key_name = foreign_key_name(table, field)
     74     manager.execute('ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) '
     75                     'REFERENCES %s (id) ON DELETE NO ACTION' %
     76                     (table, key_name, field, destination_table))
     77 
     78 
     79 def drop_foreign_key_constraint(manager, table, field):
     80     key_name = foreign_key_name(table, field)
     81     manager.execute('ALTER TABLE %s DROP FOREIGN KEY %s' % (table, key_name))
     82 
     83 
     84 def cleanup_m2m_pivot(manager, pivot_table, first_field, first_table,
     85                       second_field, second_table, create_unique):
     86     delete_duplicates(manager, pivot_table, first_field, second_field)
     87     delete_invalid_foriegn_keys(manager, pivot_table, first_field, first_table)
     88     delete_invalid_foriegn_keys(manager, pivot_table, second_field,
     89                                 second_table)
     90 
     91     if create_unique:
     92         # first field is the more commonly used one, so we'll replace the
     93         # less-commonly-used index with the larger unique index
     94         create_unique_index(manager, pivot_table, second_field, first_field)
     95 
     96     create_foreign_key_constraint(manager, pivot_table, first_field,
     97                                   first_table)
     98     create_foreign_key_constraint(manager, pivot_table, second_field,
     99                                   second_table)
    100 
    101 
    102 def reverse_cleanup_m2m_pivot(manager, pivot_table, first_field, second_field,
    103                               drop_unique):
    104     drop_foreign_key_constraint(manager, pivot_table, second_field)
    105     drop_foreign_key_constraint(manager, pivot_table, first_field)
    106     if drop_unique:
    107         drop_unique_index(manager, pivot_table, second_field)
    108 
    109 
    110 TABLES = (
    111         ('hosts_labels', 'host_id', 'hosts', 'label_id', 'labels', True),
    112         ('acl_groups_hosts', 'host_id', 'hosts', 'aclgroup_id', 'acl_groups',
    113          True),
    114         ('acl_groups_users', 'user_id', 'users', 'aclgroup_id', 'acl_groups',
    115          True),
    116         ('autotests_dependency_labels', 'test_id', 'autotests', 'label_id',
    117          'labels', False),
    118         ('jobs_dependency_labels', 'job_id', 'jobs', 'label_id', 'labels',
    119          False),
    120         ('ineligible_host_queues', 'job_id', 'jobs', 'host_id', 'hosts', True),
    121     )
    122 
    123 
    124 def migrate_up(manager):
    125     for (table, first_field, first_table, second_field, second_table,
    126          create_unique) in TABLES:
    127         cleanup_m2m_pivot(manager, table, first_field, first_table,
    128                           second_field, second_table, create_unique)
    129 
    130 
    131 def migrate_down(manager):
    132     for (table, first_field, first_table, second_field, second_table,
    133          drop_unique) in reversed(TABLES):
    134         reverse_cleanup_m2m_pivot(manager, table, first_field, second_field,
    135                                   drop_unique)
    136