mirror of https://github.com/django/django.git
[1.6.x] Fixed #23089 -- Fixed transaction handling in two management commands.
Previously, when createcachetable and flush operated on non-default databases, they weren't atomic. Also avoided transactional DDL and transactional truncates on databases that don't support them (refs #22308). Backport of753a22a635
,0757e0f30d
, and6877a9d415
from master
This commit is contained in:
parent
290e389fe9
commit
83098dccdf
|
@ -53,7 +53,7 @@ class Command(LabelCommand):
|
||||||
for i, line in enumerate(table_output):
|
for i, line in enumerate(table_output):
|
||||||
full_statement.append(' %s%s' % (line, ',' if i < len(table_output)-1 else ''))
|
full_statement.append(' %s%s' % (line, ',' if i < len(table_output)-1 else ''))
|
||||||
full_statement.append(');')
|
full_statement.append(');')
|
||||||
with transaction.commit_on_success_unless_managed():
|
with transaction.atomic(using=db, savepoint=connection.features.can_rollback_ddl):
|
||||||
curs = connection.cursor()
|
curs = connection.cursor()
|
||||||
try:
|
try:
|
||||||
curs.execute("\n".join(full_statement))
|
curs.execute("\n".join(full_statement))
|
||||||
|
|
|
@ -28,8 +28,8 @@ class Command(NoArgsCommand):
|
||||||
're-executed, and the initial_data fixture will be re-installed.')
|
're-executed, and the initial_data fixture will be re-installed.')
|
||||||
|
|
||||||
def handle_noargs(self, **options):
|
def handle_noargs(self, **options):
|
||||||
db = options.get('database')
|
database = options.get('database')
|
||||||
connection = connections[db]
|
connection = connections[database]
|
||||||
verbosity = int(options.get('verbosity'))
|
verbosity = int(options.get('verbosity'))
|
||||||
interactive = options.get('interactive')
|
interactive = options.get('interactive')
|
||||||
# The following are stealth options used by Django's internals.
|
# The following are stealth options used by Django's internals.
|
||||||
|
@ -63,7 +63,8 @@ Are you sure you want to do this?
|
||||||
|
|
||||||
if confirm == 'yes':
|
if confirm == 'yes':
|
||||||
try:
|
try:
|
||||||
with transaction.commit_on_success_unless_managed():
|
with transaction.atomic(using=database,
|
||||||
|
savepoint=connection.features.can_rollback_ddl):
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
for sql in sql_list:
|
for sql in sql_list:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
|
@ -78,7 +79,7 @@ Are you sure you want to do this?
|
||||||
six.reraise(CommandError, CommandError(new_msg), sys.exc_info()[2])
|
six.reraise(CommandError, CommandError(new_msg), sys.exc_info()[2])
|
||||||
|
|
||||||
if not inhibit_post_syncdb:
|
if not inhibit_post_syncdb:
|
||||||
self.emit_post_syncdb(verbosity, interactive, db)
|
self.emit_post_syncdb(verbosity, interactive, database)
|
||||||
|
|
||||||
# Reinstall the initial_data fixture.
|
# Reinstall the initial_data fixture.
|
||||||
if options.get('load_initial_data'):
|
if options.get('load_initial_data'):
|
||||||
|
|
|
@ -652,6 +652,9 @@ class BaseDatabaseFeatures(object):
|
||||||
# supported by the Python driver
|
# supported by the Python driver
|
||||||
supports_paramstyle_pyformat = True
|
supports_paramstyle_pyformat = True
|
||||||
|
|
||||||
|
# Can we roll back DDL in a transaction?
|
||||||
|
can_rollback_ddl = False
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
supports_tablespaces = True
|
supports_tablespaces = True
|
||||||
supports_transactions = True
|
supports_transactions = True
|
||||||
can_distinct_on_fields = True
|
can_distinct_on_fields = True
|
||||||
|
can_rollback_ddl = True
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
vendor = 'postgresql'
|
vendor = 'postgresql'
|
||||||
|
|
|
@ -102,6 +102,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
can_combine_inserts_with_and_without_auto_increment_pk = False
|
can_combine_inserts_with_and_without_auto_increment_pk = False
|
||||||
autocommits_when_autocommit_is_off = True
|
autocommits_when_autocommit_is_off = True
|
||||||
atomic_transactions = False
|
atomic_transactions = False
|
||||||
|
can_rollback_ddl = True
|
||||||
supports_paramstyle_pyformat = False
|
supports_paramstyle_pyformat = False
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|
|
@ -22,3 +22,7 @@ Bugfixes
|
||||||
|
|
||||||
* Restored ``pre_delete`` signals for ``GenericRelation`` cascade deletion
|
* Restored ``pre_delete`` signals for ``GenericRelation`` cascade deletion
|
||||||
(`#22998 <https://code.djangoproject.com/ticket/22998>`_).
|
(`#22998 <https://code.djangoproject.com/ticket/22998>`_).
|
||||||
|
|
||||||
|
* Fixed transaction handling when specifying non-default database in
|
||||||
|
``createcachetable`` and ``flush``
|
||||||
|
(`#23089 <https://code.djangoproject.com/ticket/23089>`_).
|
||||||
|
|
|
@ -20,7 +20,7 @@ from django.core.cache import get_cache
|
||||||
from django.core.cache.backends.base import (CacheKeyWarning,
|
from django.core.cache.backends.base import (CacheKeyWarning,
|
||||||
InvalidCacheBackendError)
|
InvalidCacheBackendError)
|
||||||
from django.core.context_processors import csrf
|
from django.core.context_processors import csrf
|
||||||
from django.db import router, transaction
|
from django.db import connections, router, transaction
|
||||||
from django.core.cache.utils import make_template_fragment_key
|
from django.core.cache.utils import make_template_fragment_key
|
||||||
from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse,
|
from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse,
|
||||||
QueryDict)
|
QueryDict)
|
||||||
|
@ -912,9 +912,16 @@ class CreateCacheTableForDBCacheTests(TestCase):
|
||||||
database='default',
|
database='default',
|
||||||
verbosity=0, interactive=False)
|
verbosity=0, interactive=False)
|
||||||
# cache table should be created on 'other'
|
# cache table should be created on 'other'
|
||||||
# one query is used to create the table and another one the index
|
# Queries:
|
||||||
with self.assertNumQueries(2, using='other'):
|
# 1: create savepoint (if transactional DDL is supported)
|
||||||
management.call_command('createcachetable', 'cache_table',
|
# 2: create the table
|
||||||
|
# 3: create the index
|
||||||
|
# 4: release savepoint (if transactional DDL is supported)
|
||||||
|
from django.db import connections
|
||||||
|
num = 4 if connections['other'].features.can_rollback_ddl else 2
|
||||||
|
with self.assertNumQueries(num, using='other'):
|
||||||
|
management.call_command('createcachetable',
|
||||||
|
'cache_table',
|
||||||
database='other',
|
database='other',
|
||||||
verbosity=0, interactive=False)
|
verbosity=0, interactive=False)
|
||||||
finally:
|
finally:
|
||||||
|
@ -1958,4 +1965,3 @@ class TestMakeTemplateFragmentKey(TestCase):
|
||||||
key = make_template_fragment_key('spam', ['abc:def%'])
|
key = make_template_fragment_key('spam', ['abc:def%'])
|
||||||
self.assertEqual(key,
|
self.assertEqual(key,
|
||||||
'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469')
|
'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue