From f9a2a7db173b53f9cb0cd2d3def80d4eed72631c Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 19 Mar 2016 12:15:59 +0100 Subject: [PATCH] Fixed #26351 -- Added MySQL check to warn about strict mode option Thanks Adam Chainz for the initial implementation in django-mysql. Thanks Adam Chainz, Tim Graham, and Shai Berger for the reviews. --- django/db/backends/mysql/validation.py | 23 +++++++++++++++++++++++ docs/ref/checks.txt | 2 ++ tests/check_framework/test_database.py | 25 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/django/db/backends/mysql/validation.py b/django/db/backends/mysql/validation.py index 2e3bb0279b..e62a6e4c7e 100644 --- a/django/db/backends/mysql/validation.py +++ b/django/db/backends/mysql/validation.py @@ -1,8 +1,31 @@ from django.core import checks from django.db.backends.base.validation import BaseDatabaseValidation +from django.utils.version import get_docs_version class DatabaseValidation(BaseDatabaseValidation): + def check(self, **kwargs): + issues = super(DatabaseValidation, self).check(**kwargs) + issues.extend(self._check_sql_mode(**kwargs)) + return issues + + def _check_sql_mode(self, **kwargs): + with self.connection.cursor() as cursor: + cursor.execute("SELECT @@sql_mode") + sql_mode = cursor.fetchone() + modes = set(sql_mode[0].split(',')) + if not (modes & {'STRICT_TRANS_TABLES', 'STRICT_ALL_TABLES'}): + return [checks.Warning( + "MySQL Strict Mode is not set for database connection '%s'" % self.connection.alias, + hint="MySQL's Strict Mode fixes many data integrity problems in MySQL, " + "such as data truncation upon insertion, by escalating warnings into " + "errors. It is strongly recommended you activate it. See: " + "https://docs.djangoproject.com/en/%s/ref/databases/#mysql-sql-mode" + % (get_docs_version(),), + id='mysql.W002', + )] + return [] + def check_field(self, field, **kwargs): """ MySQL has the following field length restriction: diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index a332df2071..7c15bbeb7b 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -592,6 +592,8 @@ If you're using MySQL, the following checks will be performed: * **mysql.E001**: MySQL does not allow unique ``CharField``\s to have a ``max_length`` > 255. +* **mysql.W002**: MySQL Strict Mode is not set for database connection + ''. See also :ref:`mysql-sql-mode`. Templates --------- diff --git a/tests/check_framework/test_database.py b/tests/check_framework/test_database.py index 73edd6234c..594f4ccee6 100644 --- a/tests/check_framework/test_database.py +++ b/tests/check_framework/test_database.py @@ -31,3 +31,28 @@ class DatabaseCheckTests(TestCase): with mock.patch('django.db.backends.base.validation.BaseDatabaseValidation.check') as mocked_check: run_checks(tags=[Tags.database]) self.assertTrue(mocked_check.called) + + @unittest.skipUnless(connection.vendor == 'mysql', 'Test only for MySQL') + def test_mysql_strict_mode(self): + good_sql_modes = [ + 'STRICT_TRANS_TABLES,STRICT_ALL_TABLES', + 'STRICT_TRANS_TABLES', + 'STRICT_ALL_TABLES', + ] + for response in good_sql_modes: + with mock.patch( + 'django.db.backends.utils.CursorWrapper.fetchone', create=True, + return_value=(response,) + ): + self.assertEqual(self.func(None), []) + + bad_sql_modes = ['', 'WHATEVER'] + for response in bad_sql_modes: + with mock.patch( + 'django.db.backends.utils.CursorWrapper.fetchone', create=True, + return_value=(response,) + ): + # One warning for each database alias + result = self.func(None) + self.assertEqual(len(result), 2) + self.assertEqual([r.id for r in result], ['mysql.W002', 'mysql.W002'])