def test_executes_only_after_final_transaction_committed(self): with transaction.atomic(): with transaction.atomic(): self.do(1) self.assertNotified([]) self.assertNotified([]) self.assertDone([1])
def test_hooks_cleared_after_successful_commit(self): with transaction.atomic(): self.do(1) with transaction.atomic(): self.do(2) self.assertDone([1, 2]) # not [1, 1, 2]
def test_implicit_savepoint_rollback(self): """MySQL implicitly rolls back savepoints when it deadlocks (#22291).""" Reporter.objects.create(id=1) Reporter.objects.create(id=2) main_thread_ready = threading.Event() def other_thread(): try: with transaction.atomic(): Reporter.objects.select_for_update().get(id=1) main_thread_ready.wait() # 1) This line locks... (see below for 2) Reporter.objects.exclude(id=1).update(id=2) finally: # This is the thread-local connection, not the main connection. connection.close() other_thread = threading.Thread(target=other_thread) other_thread.start() with self.assertRaisesMessage(OperationalError, 'Deadlock found'): # Double atomic to enter a transaction and create a savepoint. with transaction.atomic(): with transaction.atomic(): Reporter.objects.select_for_update().get(id=2) main_thread_ready.set() # The two threads can't be synchronized with an event here # because the other thread locks. Sleep for a little while. time.sleep(1) # 2) ... and this line deadlocks. (see above for 1) Reporter.objects.exclude(id=2).update(id=1) other_thread.join()
def test_merged_rollback_commit(self): with self.assertRaisesMessage(Exception, "Oops"): with transaction.atomic(): Reporter.objects.create(last_name="Tintin") with transaction.atomic(savepoint=False): Reporter.objects.create(last_name="Haddock") raise Exception("Oops, that's his first name") self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_wrap_callable_instance(self): """#20028 -- Atomic must support wrapping callable instances.""" class Callable: def __call__(self): pass # Must not raise an exception transaction.atomic(Callable())
def test_runs_hooks_in_order_registered(self): with transaction.atomic(): self.do(1) with transaction.atomic(): self.do(2) self.do(3) self.assertDone([1, 2, 3])
def test_merged_commit_rollback(self): with transaction.atomic(): Reporter.objects.create(first_name="Tintin") with self.assertRaisesMessage(Exception, "Oops"): with transaction.atomic(savepoint=False): Reporter.objects.create(first_name="Haddock") raise Exception("Oops, that's his last name") # Writes in the outer block are rolled back too. self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_nested_commit_commit(self): with transaction.atomic(): Reporter.objects.create(first_name="Tintin") with transaction.atomic(): Reporter.objects.create(first_name="Archibald", last_name="Haddock") self.assertQuerysetEqual( Reporter.objects.all(), ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>'])
def test_nested_commit_rollback(self): with transaction.atomic(): Reporter.objects.create(first_name="Tintin") with self.assertRaisesMessage(Exception, "Oops"): with transaction.atomic(): Reporter.objects.create(first_name="Haddock") raise Exception("Oops, that's his last name") self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
def test_no_savepoints_atomic_merged_with_outer(self): with transaction.atomic(): with transaction.atomic(): self.do(1) try: with transaction.atomic(savepoint=False): raise ForcedError() except ForcedError: pass self.assertDone([])
def test_inner_savepoint_does_not_affect_outer(self): with transaction.atomic(): with transaction.atomic(): self.do(1) try: with transaction.atomic(): raise ForcedError() except ForcedError: pass self.assertDone([1])
def test_error_in_hook_doesnt_prevent_clearing_hooks(self): try: with transaction.atomic(): transaction.on_commit(lambda: self.notify('error')) except ForcedError: pass with transaction.atomic(): self.do(1) self.assertDone([1])
def test_hooks_cleared_on_reconnect(self): with transaction.atomic(): self.do(1) connection.close() connection.connect() with transaction.atomic(): self.do(2) self.assertDone([2])
def test_no_hooks_run_from_failed_transaction(self): """If outer transaction fails, no hooks from within it run.""" try: with transaction.atomic(): with transaction.atomic(): self.do(1) raise ForcedError() except ForcedError: pass self.assertDone([])
def test_inner_savepoint_rolled_back_with_outer(self): with transaction.atomic(): try: with transaction.atomic(): with transaction.atomic(): self.do(1) raise ForcedError() except ForcedError: pass self.do(2) self.assertDone([2])
def test_hooks_cleared_after_rollback(self): try: with transaction.atomic(): self.do(1) raise ForcedError() except ForcedError: pass with transaction.atomic(): self.do(2) self.assertDone([2])
def add(self, *objs, bulk=True): self._remove_prefetched_objects() db = router.db_for_write(self.model, instance=self.instance) def check_and_update_obj(obj): if not isinstance(obj, self.model): raise TypeError("'%s' instance expected, got %r" % (self.model._meta.object_name, obj)) setattr(obj, self.content_type_field_name, self.content_type) setattr(obj, self.object_id_field_name, self.pk_val) if bulk: pks = [] for obj in objs: if obj._state.adding or obj._state.db != db: raise ValueError( "%r instance isn't saved. Use bulk=False or save " "the object first." % obj) check_and_update_obj(obj) pks.append(obj.pk) self.model._base_manager.using(db).filter(pk__in=pks).update( **{ self.content_type_field_name: self.content_type, self.object_id_field_name: self.pk_val, }) else: with transaction.atomic(using=db, savepoint=False): for obj in objs: check_and_update_obj(obj) obj.save()
def test_db_query_in_hook(self): with transaction.atomic(): Thing.objects.create(num=1) transaction.on_commit( lambda: [self.notify(t.num) for t in Thing.objects.all()]) self.assertDone([1])
def _enter_atomics(cls): """Open atomic blocks for multiple databases.""" atomics = {} for db_name in cls._databases_names(): atomics[db_name] = transaction.atomic(using=db_name) atomics[db_name].__enter__() return atomics
def test_add(self): # Create an Article via the Reporter object. new_article = self.r.article_set.create(headline="John's second story", pub_date=datetime.date(2005, 7, 29)) self.assertEqual(repr(new_article), "<Article: John's second story>") self.assertEqual(new_article.reporter.id, self.r.id) # Create a new article, and add it to the article set. new_article2 = Article(headline="Paul's story", pub_date=datetime.date(2006, 1, 17)) msg = "<Article: Paul's story> instance isn't saved. Use bulk=False or save the object first." with self.assertRaisesMessage(ValueError, msg): self.r.article_set.add(new_article2) self.r.article_set.add(new_article2, bulk=False) self.assertEqual(new_article2.reporter.id, self.r.id) self.assertQuerysetEqual( self.r.article_set.all(), ["<Article: John's second story>", "<Article: Paul's story>", "<Article: This is a test>"] ) # Add the same article to a different article set - check that it moves. self.r2.article_set.add(new_article2) self.assertEqual(new_article2.reporter.id, self.r2.id) self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"]) # Adding an object of the wrong type raises TypeError. with transaction.atomic(): with self.assertRaisesMessage(TypeError, "'Article' instance expected, got <Reporter:"): self.r.article_set.add(self.r2) self.assertQuerysetEqual( self.r.article_set.all(), ["<Article: John's second story>", "<Article: This is a test>"] )
def _rename(self, apps, schema_editor, old_model, new_model): ContentType = apps.get_model('contenttypes', 'ContentType') db = schema_editor.connection.alias if not router.allow_migrate_model(db, ContentType): return try: content_type = ContentType.objects.db_manager( db).get_by_natural_key(self.app_label, old_model) except ContentType.DoesNotExist: pass else: content_type.model = new_model try: with transaction.atomic(using=db): content_type.save(update_fields={'model'}) except IntegrityError: # Gracefully fallback if a stale content type causes a # conflict as remove_stale_contenttypes will take care of # asking the user what should be done next. content_type.model = old_model else: # Clear the cache as the `get_by_natual_key()` call will cache # the renamed ContentType instance by its old model name. ContentType.objects.clear_cache()
def test_prevent_rollback(self): with transaction.atomic(): Reporter.objects.create(first_name="Tintin") sid = transaction.savepoint() # trigger a database error inside an inner atomic without savepoint with self.assertRaises(DatabaseError): with transaction.atomic(savepoint=False): with connection.cursor() as cursor: cursor.execute( "SELECT no_such_col FROM transactions_reporter") # prevent atomic from rolling back since we're recovering manually self.assertTrue(transaction.get_rollback()) transaction.set_rollback(False) transaction.savepoint_rollback(sid) self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
def test_postgres_options(self): qs = Tag.objects.filter(name='test') test_options = [ { 'COSTS': False, 'BUFFERS': True, 'ANALYZE': True }, { 'costs': False, 'buffers': True, 'analyze': True }, { 'verbose': True, 'timing': True, 'analyze': True }, { 'verbose': False, 'timing': False, 'analyze': True }, ] if connection.pg_version >= 100000: test_options.append({'summary': True}) for options in test_options: with self.subTest(**options), transaction.atomic(): with CaptureQueriesContext(connection) as captured_queries: qs.explain(format='text', **options) self.assertEqual(len(captured_queries), 1) for name, value in options.items(): option = '{} {}'.format(name.upper(), 'true' if value else 'false') self.assertIn(option, captured_queries[0]['sql'])
def test_force_rollback(self): with transaction.atomic(): Reporter.objects.create(first_name="Tintin") # atomic block shouldn't rollback, but force it. self.assertFalse(transaction.get_rollback()) transaction.set_rollback(True) self.assertQuerysetEqual(Reporter.objects.all(), [])
def delete(self): # sort instance collections for model, instances in self.data.items(): self.data[model] = sorted(instances, key=attrgetter("pk")) # if possible, bring the models in an order suitable for databases that # don't support transactions or cannot defer constraint checks until the # end of a transaction. self.sort() # number of objects deleted for each model label deleted_counter = Counter() with transaction.atomic(using=self.using, savepoint=False): # send pre_delete signals for model, obj in self.instances_with_model(): if not model._meta.auto_created: signals.pre_delete.send(sender=model, instance=obj, using=self.using) # fast deletes for qs in self.fast_deletes: count = qs._raw_delete(using=self.using) deleted_counter[qs.model._meta.label] += count # update fields for model, instances_for_fieldvalues in self.field_updates.items(): for (field, value), instances in instances_for_fieldvalues.items(): query = sql.UpdateQuery(model) query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) # reverse instance collections for instances in self.data.values(): instances.reverse() # delete instances for model, instances in self.data.items(): query = sql.DeleteQuery(model) pk_list = [obj.pk for obj in instances] count = query.delete_batch(pk_list, self.using) deleted_counter[model._meta.label] += count if not model._meta.auto_created: for obj in instances: signals.post_delete.send(sender=model, instance=obj, using=self.using) # update collected instances for instances_for_fieldvalues in self.field_updates.values(): for (field, value), instances in instances_for_fieldvalues.items(): for obj in instances: setattr(obj, field.attname, value) for model, instances in self.data.items(): for instance in instances: setattr(instance, model._meta.pk.attname, None) return sum(deleted_counter.values()), dict(deleted_counter)
def test_for_update_of_self_when_self_is_not_selected(self): """ select_for_update(of=['self']) when the only columns selected are from related tables. """ with transaction.atomic(): values = list(Person.objects.select_related('born').select_for_update(of=('self',)).values('born__name')) self.assertEqual(values, [{'born__name': self.city1.name}])
def test_unsupported_nowait_raises_error(self): """ NotSupportedError is raised if a SELECT...FOR UPDATE NOWAIT is run on a database backend that supports FOR UPDATE but not NOWAIT. """ with self.assertRaisesMessage(NotSupportedError, 'NOWAIT is not supported on this database backend.'): with transaction.atomic(): Person.objects.select_for_update(nowait=True).get()
def test_unsupported_skip_locked_raises_error(self): """ NotSupportedError is raised if a SELECT...FOR UPDATE SKIP LOCKED is run on a database backend that supports FOR UPDATE but not SKIP LOCKED. """ with self.assertRaisesMessage(NotSupportedError, 'SKIP LOCKED is not supported on this database backend.'): with transaction.atomic(): Person.objects.select_for_update(skip_locked=True).get()
def test_ordered_select_for_update(self): """ Subqueries should respect ordering as an ORDER BY clause may be useful to specify a row locking order to prevent deadlocks (#27193). """ with transaction.atomic(): qs = Person.objects.filter(id__in=Person.objects.order_by('-id').select_for_update()) self.assertIn('ORDER BY', str(qs.query))
def test_for_update_sql_generated_skip_locked(self): """ The backend's FOR UPDATE SKIP LOCKED variant appears in generated SQL when select_for_update is invoked. """ with transaction.atomic(), CaptureQueriesContext(connection) as ctx: list(Person.objects.all().select_for_update(skip_locked=True)) self.assertTrue(self.has_for_update_sql(ctx.captured_queries, skip_locked=True))