lin
2025-07-30 fcd736bf35fd93b563e9bbf594f2aa7b62028cc9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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)