Add detection when a DEFAULT is added to a erroneous NOT NULL column
This commit is contained in:
1 parent
e29f2abdb1
commit
e00c18fee3
1 file changed
+42
-14
+42
-14
@@ -21,25 +21,39 @@ from subprocess import Popen, PIPE
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def has_default(sql, **kwargs):
|
||||||
|
if re.search('SET DEFAULT', sql) and kwargs['errors']:
|
||||||
|
err = next(err for err in kwargs['errors'] if err['code'] == 'NOT_NULL')
|
||||||
|
if err:
|
||||||
|
log.info('Found a NOT_NULL error in migration, but it has a default value added: {}'.format(err))
|
||||||
|
kwargs['errors'].remove(err)
|
||||||
|
|
||||||
|
return False # Never fails
|
||||||
|
|
||||||
class MigrationLinter:
|
class MigrationLinter:
|
||||||
MIGRATION_FOLDER_NAME = 'migrations'
|
MIGRATION_FOLDER_NAME = 'migrations'
|
||||||
|
|
||||||
migration_tests = (
|
migration_tests = (
|
||||||
{
|
{
|
||||||
'fn': lambda sql: re.search('NOT NULL', sql) and not re.search('CREATE TABLE', sql),
|
'code': 'NOT_NULL',
|
||||||
|
'fn': lambda sql, **kw: re.search('NOT NULL', sql) and not re.search('CREATE TABLE', sql),
|
||||||
'err_msg': 'NOT NULL constraint on columns'
|
'err_msg': 'NOT NULL constraint on columns'
|
||||||
}, {
|
}, {
|
||||||
'fn': lambda sql: re.search('DROP COLUMN', sql),
|
'code': 'DROP_COLUMN',
|
||||||
|
'fn': lambda sql, **kw: re.search('DROP COLUMN', sql),
|
||||||
'err_msg': 'DROPPING columns'
|
'err_msg': 'DROPPING columns'
|
||||||
}, {
|
}, {
|
||||||
'fn': lambda sql: re.search('ADD COLUMN .* DEFAULT', sql),
|
'code': 'RENAME_COLUMN',
|
||||||
'err_msg': 'ADD columns with default'
|
'fn': lambda sql, **kw: re.search('ALTER TABLE .* CHANGE', sql) or re.search('ALTER TABLE .* RENAME COLUMN', sql), # mysql or postgre
|
||||||
}, {
|
|
||||||
'fn': lambda sql: re.search('ALTER TABLE .* CHANGE', sql) or re.search('ALTER TABLE .* RENAME COLUMN', sql), # mysql or postgre
|
|
||||||
'err_msg': 'RENAMING columns'
|
'err_msg': 'RENAMING columns'
|
||||||
}, {
|
}, {
|
||||||
'fn': lambda sql: re.search('RENAME TABLE', sql) or re.search('ALTER TABLE .* RENAME TO', sql), # mysql or postgre
|
'code': 'RENAME_TABLE',
|
||||||
|
'fn': lambda sql, **kw: re.search('RENAME TABLE', sql) or re.search('ALTER TABLE .* RENAME TO', sql), # mysql or postgre
|
||||||
'err_msg': 'RENAMING tables'
|
'err_msg': 'RENAMING tables'
|
||||||
|
}, {
|
||||||
|
'code': '',
|
||||||
|
'fn': has_default,
|
||||||
|
'err_msg': ''
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,11 +108,11 @@ class MigrationLinter:
|
|||||||
nb_ignore += 1
|
nb_ignore += 1
|
||||||
else:
|
else:
|
||||||
sql_statements = self.django_sqlmigrate(app_name, migration_name)
|
sql_statements = self.django_sqlmigrate(app_name, migration_name)
|
||||||
errors = set()
|
errors = []
|
||||||
for statement in sql_statements:
|
for statement in sql_statements:
|
||||||
is_valid, err_msg = self._test_sql_statement_for_backward_incompatibility(statement)
|
is_valid, err = self._test_sql_statement_for_backward_incompatibility(statement, errors)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
errors.add(err_msg)
|
errors.append(err)
|
||||||
if not errors:
|
if not errors:
|
||||||
print('OK')
|
print('OK')
|
||||||
nb_valid += 1
|
nb_valid += 1
|
||||||
@@ -106,7 +120,13 @@ class MigrationLinter:
|
|||||||
print('ERR')
|
print('ERR')
|
||||||
nb_erroneous += 1
|
nb_erroneous += 1
|
||||||
for err in errors:
|
for err in errors:
|
||||||
print('\t' + err)
|
error_str = '\t{0}'.format(err['err_msg'])
|
||||||
|
if err['table']:
|
||||||
|
error_str += ' (table: {0}'.format(err['table'])
|
||||||
|
if err['column']:
|
||||||
|
error_str += ', column: {0}'.format(err['column'])
|
||||||
|
error_str += ')'
|
||||||
|
print(error_str)
|
||||||
print('*** Summary:')
|
print('*** Summary:')
|
||||||
print('Valid migrations: {0}/{1} - erroneous migrations: {2}/{1} - ignored migrations: {3}/{1}'.format(nb_valid, len(self.changed_migration_files), nb_erroneous, nb_ignore))
|
print('Valid migrations: {0}/{1} - erroneous migrations: {2}/{1} - ignored migrations: {3}/{1}'.format(nb_valid, len(self.changed_migration_files), nb_erroneous, nb_ignore))
|
||||||
return nb_erroneous > 0
|
return nb_erroneous > 0
|
||||||
@@ -138,11 +158,19 @@ class MigrationLinter:
|
|||||||
log.info('Found {0} sql migration lines'.format(len(sql_statements)))
|
log.info('Found {0} sql migration lines'.format(len(sql_statements)))
|
||||||
return sql_statements
|
return sql_statements
|
||||||
|
|
||||||
def _test_sql_statement_for_backward_incompatibility(self, sql_statement):
|
def _test_sql_statement_for_backward_incompatibility(self, sql_statement, errors):
|
||||||
for test in self.migration_tests:
|
for test in self.migration_tests:
|
||||||
if test['fn'](sql_statement):
|
if test['fn'](sql_statement, errors=errors):
|
||||||
log.info('Testing {0} -- ERROR'.format(sql_statement))
|
log.info('Testing {0} -- ERROR'.format(sql_statement))
|
||||||
return False, test['err_msg']
|
table_search = re.search('TABLE `([^`]*)`', sql_statement, re.IGNORECASE)
|
||||||
|
col_search = re.search('COLUMN `([^`]*)`', sql_statement, re.IGNORECASE)
|
||||||
|
err = {
|
||||||
|
'err_msg': test['err_msg'],
|
||||||
|
'code': test['code'],
|
||||||
|
'table': table_search.group(1) if table_search else None,
|
||||||
|
'column': col_search.group(1) if col_search else None
|
||||||
|
}
|
||||||
|
return False, err
|
||||||
log.info('Testing {0} -- PASSED'.format(sql_statement))
|
log.info('Testing {0} -- PASSED'.format(sql_statement))
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user