From 83098dccdf3172302cde54e11c36050c1d83f996 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 21 Mar 2014 18:48:57 +0100 Subject: [PATCH] [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 of 753a22a635, 0757e0f30d, and 6877a9d415 from master --- .../core/management/commands/createcachetable.py | 2 +- django/core/management/commands/flush.py | 9 +++++---- django/db/backends/__init__.py | 3 +++ django/db/backends/postgresql_psycopg2/base.py | 1 + django/db/backends/sqlite3/base.py | 1 + docs/releases/1.6.6.txt | 4 ++++ tests/cache/tests.py | 16 +++++++++++----- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index d7ce3e93fd..0af4aeddca 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -53,7 +53,7 @@ class Command(LabelCommand): for i, line in enumerate(table_output): full_statement.append(' %s%s' % (line, ',' if i < len(table_output)-1 else '')) full_statement.append(');') - with transaction.commit_on_success_unless_managed(): + with transaction.atomic(using=db, savepoint=connection.features.can_rollback_ddl): curs = connection.cursor() try: curs.execute("\n".join(full_statement)) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 95dd634d08..82dd246160 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -28,8 +28,8 @@ class Command(NoArgsCommand): 're-executed, and the initial_data fixture will be re-installed.') def handle_noargs(self, **options): - db = options.get('database') - connection = connections[db] + database = options.get('database') + connection = connections[database] verbosity = int(options.get('verbosity')) interactive = options.get('interactive') # 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': try: - with transaction.commit_on_success_unless_managed(): + with transaction.atomic(using=database, + savepoint=connection.features.can_rollback_ddl): cursor = connection.cursor() for sql in sql_list: 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]) if not inhibit_post_syncdb: - self.emit_post_syncdb(verbosity, interactive, db) + self.emit_post_syncdb(verbosity, interactive, database) # Reinstall the initial_data fixture. if options.get('load_initial_data'): diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 12f08a2d92..ce3b6bcc85 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -652,6 +652,9 @@ class BaseDatabaseFeatures(object): # supported by the Python driver supports_paramstyle_pyformat = True + # Can we roll back DDL in a transaction? + can_rollback_ddl = False + def __init__(self, connection): self.connection = connection diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 9aa8b476f1..f52d7a40bd 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -49,6 +49,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_tablespaces = True supports_transactions = True can_distinct_on_fields = True + can_rollback_ddl = True class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'postgresql' diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index a219178570..c4bd38bd23 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -102,6 +102,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): can_combine_inserts_with_and_without_auto_increment_pk = False autocommits_when_autocommit_is_off = True atomic_transactions = False + can_rollback_ddl = True supports_paramstyle_pyformat = False @cached_property diff --git a/docs/releases/1.6.6.txt b/docs/releases/1.6.6.txt index c61823bfdc..c8c7a037c0 100644 --- a/docs/releases/1.6.6.txt +++ b/docs/releases/1.6.6.txt @@ -22,3 +22,7 @@ Bugfixes * Restored ``pre_delete`` signals for ``GenericRelation`` cascade deletion (`#22998 `_). + +* Fixed transaction handling when specifying non-default database in + ``createcachetable`` and ``flush`` + (`#23089 `_). diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 04ef21b851..b3374ab497 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -20,7 +20,7 @@ from django.core.cache import get_cache from django.core.cache.backends.base import (CacheKeyWarning, InvalidCacheBackendError) 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.http import (HttpResponse, HttpRequest, StreamingHttpResponse, QueryDict) @@ -912,9 +912,16 @@ class CreateCacheTableForDBCacheTests(TestCase): database='default', verbosity=0, interactive=False) # cache table should be created on 'other' - # one query is used to create the table and another one the index - with self.assertNumQueries(2, using='other'): - management.call_command('createcachetable', 'cache_table', + # Queries: + # 1: create savepoint (if transactional DDL is supported) + # 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', verbosity=0, interactive=False) finally: @@ -1958,4 +1965,3 @@ class TestMakeTemplateFragmentKey(TestCase): key = make_template_fragment_key('spam', ['abc:def%']) self.assertEqual(key, 'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469') -