普通文本  |  136行  |  5.29 KB

def execute_safely(manager, statement):
    try:
        manager.execute(statement)
    except Exception:
        print 'Statement %r failed (this is not fatal)' % statement


def delete_duplicates(manager, table, first_id, second_id):
    rows = manager.execute(
        'SELECT %s, %s, COUNT(1) AS count FROM %s '
        'GROUP BY %s, %s HAVING count > 1' %
        (first_id, second_id, table, first_id, second_id))
    for first_id_value, second_id_value, count_unused in rows:
        manager.execute('DELETE FROM %s '
                        'WHERE %s = %%s AND %s = %%s LIMIT 1' %
                        (table, first_id, second_id),
                        first_id_value, second_id_value)
    if rows:
        print 'Deleted %s duplicate rows from %s' % (len(rows), table)


def delete_invalid_foriegn_keys(manager, pivot_table, foreign_key_field,
                                destination_table):
    manager.execute(
        'DELETE %(table)s.* FROM %(table)s '
        'LEFT JOIN %(destination_table)s '
        'ON %(table)s.%(field)s = %(destination_table)s.id '
        'WHERE %(destination_table)s.id IS NULL' %
        dict(table=pivot_table, field=foreign_key_field,
             destination_table=destination_table))
    deleted_count = manager._database.rowcount
    if deleted_count:
        print ('Deleted %s invalid foreign key references from %s (%s)' %
               (deleted_count, pivot_table, foreign_key_field))


def unique_index_name(table):
    return table + '_both_ids'


def basic_index_name(table, field):
    if field == 'aclgroup_id':
        field = 'acl_group_id'
    return table + '_' + field


def create_unique_index(manager, pivot_table, first_field, second_field):
    index_name = unique_index_name(pivot_table)
    manager.execute('CREATE UNIQUE INDEX %s ON %s (%s, %s)' %
                    (index_name, pivot_table, first_field, second_field))

    # these indices are in the migrations but may not exist for historical
    # reasons
    old_index_name = basic_index_name(pivot_table, first_field)
    execute_safely(manager, 'DROP INDEX %s ON %s' %
                   (old_index_name, pivot_table))


def drop_unique_index(manager, pivot_table, first_field):
    index_name = unique_index_name(pivot_table)
    manager.execute('DROP INDEX %s ON %s' % (index_name, pivot_table))

    old_index_name = basic_index_name(pivot_table, first_field)
    manager.execute('CREATE INDEX %s ON %s (%s)' %
                    (old_index_name, pivot_table, first_field))


def foreign_key_name(table, field):
    return '_'.join([table, field, 'fk'])


def create_foreign_key_constraint(manager, table, field, destination_table):
    key_name = foreign_key_name(table, field)
    manager.execute('ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) '
                    'REFERENCES %s (id) ON DELETE NO ACTION' %
                    (table, key_name, field, destination_table))


def drop_foreign_key_constraint(manager, table, field):
    key_name = foreign_key_name(table, field)
    manager.execute('ALTER TABLE %s DROP FOREIGN KEY %s' % (table, key_name))


def cleanup_m2m_pivot(manager, pivot_table, first_field, first_table,
                      second_field, second_table, create_unique):
    delete_duplicates(manager, pivot_table, first_field, second_field)
    delete_invalid_foriegn_keys(manager, pivot_table, first_field, first_table)
    delete_invalid_foriegn_keys(manager, pivot_table, second_field,
                                second_table)

    if create_unique:
        # first field is the more commonly used one, so we'll replace the
        # less-commonly-used index with the larger unique index
        create_unique_index(manager, pivot_table, second_field, first_field)

    create_foreign_key_constraint(manager, pivot_table, first_field,
                                  first_table)
    create_foreign_key_constraint(manager, pivot_table, second_field,
                                  second_table)


def reverse_cleanup_m2m_pivot(manager, pivot_table, first_field, second_field,
                              drop_unique):
    drop_foreign_key_constraint(manager, pivot_table, second_field)
    drop_foreign_key_constraint(manager, pivot_table, first_field)
    if drop_unique:
        drop_unique_index(manager, pivot_table, second_field)


TABLES = (
        ('hosts_labels', 'host_id', 'hosts', 'label_id', 'labels', True),
        ('acl_groups_hosts', 'host_id', 'hosts', 'aclgroup_id', 'acl_groups',
         True),
        ('acl_groups_users', 'user_id', 'users', 'aclgroup_id', 'acl_groups',
         True),
        ('autotests_dependency_labels', 'test_id', 'autotests', 'label_id',
         'labels', False),
        ('jobs_dependency_labels', 'job_id', 'jobs', 'label_id', 'labels',
         False),
        ('ineligible_host_queues', 'job_id', 'jobs', 'host_id', 'hosts', True),
    )


def migrate_up(manager):
    for (table, first_field, first_table, second_field, second_table,
         create_unique) in TABLES:
        cleanup_m2m_pivot(manager, table, first_field, first_table,
                          second_field, second_table, create_unique)


def migrate_down(manager):
    for (table, first_field, first_table, second_field, second_table,
         drop_unique) in reversed(TABLES):
        reverse_cleanup_m2m_pivot(manager, table, first_field, second_field,
                                  drop_unique)