Kaydet (Commit) 0e51d8eb authored tarafından Aymeric Augustin's avatar Aymeric Augustin

Fixed #20463 -- Made get_or_create more robust.

When an exception other than IntegrityError was raised, get_or_create
could fail and leave the database connection in an unusable state.

Thanks UloPe for the report.
üst adeec009
......@@ -9,7 +9,7 @@ import warnings
from django.conf import settings
from django.core import exceptions
from django.db import connections, router, transaction, IntegrityError
from django.db import connections, router, transaction, DatabaseError
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import AutoField
from django.db.models.query_utils import (Q, select_related_descend,
......@@ -382,13 +382,13 @@ class QuerySet(object):
obj.save(force_insert=True, using=self.db)
transaction.savepoint_commit(sid, using=self.db)
return obj, True
except IntegrityError:
except DatabaseError:
transaction.savepoint_rollback(sid, using=self.db)
exc_info = sys.exc_info()
try:
return self.get(**lookup), False
except self.model.DoesNotExist:
# Re-raise the IntegrityError with its original traceback.
# Re-raise the DatabaseError with its original traceback.
six.reraise(*exc_info)
def _earliest_or_latest(self, field_name=None, direction="-"):
......
......@@ -2,14 +2,16 @@ from __future__ import absolute_import
from datetime import date
import traceback
import warnings
from django.db import IntegrityError
from django.db import IntegrityError, DatabaseError
from django.test import TestCase, TransactionTestCase
from .models import Person, ManualPrimaryKeyTest, Profile, Tag, Thing
class GetOrCreateTests(TestCase):
def test_get_or_create(self):
p = Person.objects.create(
first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)
......@@ -64,6 +66,22 @@ class GetOrCreateTests(TestCase):
formatted_traceback = traceback.format_exc()
self.assertIn('obj.save', formatted_traceback)
def test_savepoint_rollback(self):
# Regression test for #20463: the database connection should still be
# usable after a DataError or ProgrammingError in .get_or_create().
try:
# Hide warnings when broken data is saved with a warning (MySQL).
with warnings.catch_warnings():
warnings.simplefilter('ignore')
Person.objects.get_or_create(
birthday=date(1970, 1, 1),
defaults={'first_name': "\xff", 'last_name': "\xff"})
except DatabaseError:
Person.objects.create(
first_name="Bob", last_name="Ross", birthday=date(1950, 1, 1))
else:
self.skipTest("This backend accepts broken utf-8.")
class GetOrCreateTransactionTests(TransactionTestCase):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment