mirror of
				https://github.com/django/django.git
				synced 2025-10-29 00:26:07 +00:00 
			
		
		
		
	[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, and6877a9d415from master
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						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>`_). | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								tests/cache/tests.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								tests/cache/tests.py
									
									
									
									
										vendored
									
									
								
							| @@ -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') | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user