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.
This commit is contained in:
Aymeric Augustin 2013-05-22 10:56:06 +02:00
parent adeec00979
commit 0e51d8eb66
2 changed files with 22 additions and 4 deletions

View File

@ -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="-"):

View File

@ -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):