Fixed #17653 -- Allowed using zero as AutoFields value on MySQL if NO_AUTO_VALUE_ON_ZERO SQL mode is enabled.

This commit is contained in:
Mariusz Felisiak 2020-07-20 09:48:31 +02:00 committed by GitHub
parent 730711e828
commit 83f55aafdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 27 additions and 8 deletions

View File

@ -100,7 +100,7 @@ class BaseDatabaseFeatures:
# The database's limit on the number of query parameters. # The database's limit on the number of query parameters.
max_query_params = None 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 allows_auto_pk_0 = True
# Do we need to NULL a ForeignKey out, or can the constraint check be # Do we need to NULL a ForeignKey out, or can the constraint check be

View File

@ -17,7 +17,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_index_column_ordering = False supports_index_column_ordering = False
supports_timezones = False supports_timezones = False
requires_explicit_null_ordering_when_grouping = True requires_explicit_null_ordering_when_grouping = True
allows_auto_pk_0 = False
can_release_savepoints = True can_release_savepoints = True
atomic_transactions = False atomic_transactions = False
can_clone_databases = True 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" "Internal method used in Django tests. Don't rely on this from your code"
return self.connection.mysql_server_data['default_storage_engine'] 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 @cached_property
def update_can_self_select(self): def update_can_self_select(self):
return self.connection.mysql_is_mariadb and self.connection.mysql_version >= (10, 3, 2) return self.connection.mysql_is_mariadb and self.connection.mysql_version >= (10, 3, 2)

View File

@ -227,8 +227,9 @@ class DatabaseOperations(BaseDatabaseOperations):
] ]
def validate_autopk_value(self, value): def validate_autopk_value(self, value):
# MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. # Zero in AUTO_INCREMENT field does not work without the
if value == 0: # 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 ' raise ValueError('The database backend does not accept 0 as a '
'value for AutoField.') 'value for AutoField.')
return value return value
@ -266,6 +267,9 @@ class DatabaseOperations(BaseDatabaseOperations):
def max_name_length(self): def max_name_length(self):
return 64 return 64
def pk_default_value(self):
return 'NULL'
def bulk_insert_sql(self, fields, placeholder_rows): def bulk_insert_sql(self, fields, placeholder_rows):
placeholder_rows_sql = (", ".join(row) for row in placeholder_rows) placeholder_rows_sql = (", ".join(row) for row in placeholder_rows)
values_sql = ", ".join("(%s)" % sql for sql in placeholder_rows_sql) values_sql = ", ".join("(%s)" % sql for sql in placeholder_rows_sql)

View File

@ -32,3 +32,9 @@ class TestFeatures(TestCase):
database_features = DatabaseFeatures(_connection) database_features = DatabaseFeatures(_connection)
self.assertFalse(database_features.has_select_for_update_skip_locked) self.assertFalse(database_features.has_select_for_update_skip_locked)
self.assertFalse(database_features.has_select_for_update_nowait) 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)

View File

@ -806,7 +806,8 @@ class ThreadTests(TransactionTestCase):
class MySQLPKZeroTests(TestCase): class MySQLPKZeroTests(TestCase):
""" """
Zero as id for AutoField should raise exception in MySQL, because MySQL 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') @skipIfDBFeature('allows_auto_pk_0')
def test_zero_as_autoval(self): def test_zero_as_autoval(self):

View File

@ -115,7 +115,8 @@ class BulkCreateTests(TestCase):
def test_zero_as_autoval(self): def test_zero_as_autoval(self):
""" """
Zero as id for AutoField should raise exception in MySQL, because MySQL 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') valid_country = Country(name='Germany', iso_two_letter='DE')
invalid_country = Country(id=0, name='Poland', iso_two_letter='PL') invalid_country = Country(id=0, name='Poland', iso_two_letter='PL')

View File

@ -379,8 +379,8 @@ if connection.features.interprets_empty_strings_as_nulls:
data[3] is None)] data[3] is None)]
# Regression test for #8651 -- a FK to an object with PK of 0 # 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 # This won't work on MySQL without the NO_AUTO_VALUE_ON_ZERO SQL mode since it
# with an autoincrement primary key of 0, # won't let you create an object with an autoincrement primary key of 0.
if connection.features.allows_auto_pk_0: if connection.features.allows_auto_pk_0:
test_data.extend([ test_data.extend([
(data_obj, 0, Anchor, "Anchor 0"), (data_obj, 0, Anchor, "Anchor 0"),