diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index e156c56bdac..877dd0ed884 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -100,7 +100,7 @@ class BaseDatabaseFeatures: # The database's limit on the number of query parameters. max_query_params = None - # Can an object have an autoincrement primary key of 0? MySQL says No. + # Can an object have an autoincrement primary key of 0? allows_auto_pk_0 = True # Do we need to NULL a ForeignKey out, or can the constraint check be diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py index d4651516fed..e55d098057b 100644 --- a/django/db/backends/mysql/features.py +++ b/django/db/backends/mysql/features.py @@ -17,7 +17,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_index_column_ordering = False supports_timezones = False requires_explicit_null_ordering_when_grouping = True - allows_auto_pk_0 = False can_release_savepoints = True atomic_transactions = False can_clone_databases = True @@ -51,6 +50,14 @@ class DatabaseFeatures(BaseDatabaseFeatures): "Internal method used in Django tests. Don't rely on this from your code" return self.connection.mysql_server_data['default_storage_engine'] + @cached_property + def allows_auto_pk_0(self): + """ + Autoincrement primary key can be set to 0 if it doesn't generate new + autoincrement values. + """ + return 'NO_AUTO_VALUE_ON_ZERO' in self.connection.sql_mode + @cached_property def update_can_self_select(self): return self.connection.mysql_is_mariadb and self.connection.mysql_version >= (10, 3, 2) diff --git a/django/db/backends/mysql/operations.py b/django/db/backends/mysql/operations.py index ffddb64e3c8..05523fcdbef 100644 --- a/django/db/backends/mysql/operations.py +++ b/django/db/backends/mysql/operations.py @@ -227,8 +227,9 @@ class DatabaseOperations(BaseDatabaseOperations): ] def validate_autopk_value(self, value): - # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. - if value == 0: + # Zero in AUTO_INCREMENT field does not work without the + # NO_AUTO_VALUE_ON_ZERO SQL mode. + if value == 0 and not self.connection.features.allows_auto_pk_0: raise ValueError('The database backend does not accept 0 as a ' 'value for AutoField.') return value @@ -266,6 +267,9 @@ class DatabaseOperations(BaseDatabaseOperations): def max_name_length(self): return 64 + def pk_default_value(self): + return 'NULL' + def bulk_insert_sql(self, fields, placeholder_rows): placeholder_rows_sql = (", ".join(row) for row in placeholder_rows) values_sql = ", ".join("(%s)" % sql for sql in placeholder_rows_sql) diff --git a/tests/backends/mysql/test_features.py b/tests/backends/mysql/test_features.py index 1385c9b88c8..5d27890a5d4 100644 --- a/tests/backends/mysql/test_features.py +++ b/tests/backends/mysql/test_features.py @@ -32,3 +32,9 @@ class TestFeatures(TestCase): database_features = DatabaseFeatures(_connection) self.assertFalse(database_features.has_select_for_update_skip_locked) self.assertFalse(database_features.has_select_for_update_nowait) + + def test_allows_auto_pk_0(self): + with mock.MagicMock() as _connection: + _connection.sql_mode = {'NO_AUTO_VALUE_ON_ZERO'} + database_features = DatabaseFeatures(_connection) + self.assertIs(database_features.allows_auto_pk_0, True) diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 08bdac34375..cf6bdbf25d3 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -806,7 +806,8 @@ class ThreadTests(TransactionTestCase): class MySQLPKZeroTests(TestCase): """ Zero as id for AutoField should raise exception in MySQL, because MySQL - does not allow zero for autoincrement primary key. + does not allow zero for autoincrement primary key if the + NO_AUTO_VALUE_ON_ZERO SQL mode is not enabled. """ @skipIfDBFeature('allows_auto_pk_0') def test_zero_as_autoval(self): diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py index ff89bcb1ddc..2b1d901e312 100644 --- a/tests/bulk_create/tests.py +++ b/tests/bulk_create/tests.py @@ -115,7 +115,8 @@ class BulkCreateTests(TestCase): def test_zero_as_autoval(self): """ Zero as id for AutoField should raise exception in MySQL, because MySQL - does not allow zero for automatic primary key. + does not allow zero for automatic primary key if the + NO_AUTO_VALUE_ON_ZERO SQL mode is not enabled. """ valid_country = Country(name='Germany', iso_two_letter='DE') invalid_country = Country(id=0, name='Poland', iso_two_letter='PL') diff --git a/tests/serializers/test_data.py b/tests/serializers/test_data.py index 1ddba02565c..323cb13e821 100644 --- a/tests/serializers/test_data.py +++ b/tests/serializers/test_data.py @@ -379,8 +379,8 @@ if connection.features.interprets_empty_strings_as_nulls: data[3] is None)] # Regression test for #8651 -- a FK to an object with PK of 0 -# This won't work on MySQL since it won't let you create an object -# with an autoincrement primary key of 0, +# This won't work on MySQL without the NO_AUTO_VALUE_ON_ZERO SQL mode since it +# won't let you create an object with an autoincrement primary key of 0. if connection.features.allows_auto_pk_0: test_data.extend([ (data_obj, 0, Anchor, "Anchor 0"),