Skip to content

Commit 0b9cc8a

Browse files
authored
Merge pull request #76 from cloudblue/fix/LITE-23142_transaction-commit-error
LITE-23142 catch database layer errors on transaction commit
2 parents 4f85ced + e0733b1 commit 0b9cc8a

File tree

5 files changed

+85
-22
lines changed

5 files changed

+85
-22
lines changed

dj_cqrs/controller/consumer.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from dj_cqrs.constants import SignalType
88
from dj_cqrs.registries import ReplicaRegistry
99

10-
from django.db import close_old_connections, transaction
10+
from django.db import Error, close_old_connections, transaction
1111

1212

1313
logger = logging.getLogger('django-cqrs')
@@ -42,16 +42,26 @@ def route_signal_to_replica_model(signal_type, cqrs_id, instance_data, previous_
4242
if db_is_needed:
4343
close_old_connections()
4444

45-
with transaction.atomic(savepoint=False) if db_is_needed else ExitStack():
46-
if signal_type == SignalType.DELETE:
47-
return model_cls.cqrs_delete(instance_data)
45+
try:
46+
with transaction.atomic(savepoint=False) if db_is_needed else ExitStack():
47+
if signal_type == SignalType.DELETE:
48+
return model_cls.cqrs_delete(instance_data)
4849

49-
elif signal_type == SignalType.SAVE:
50-
return model_cls.cqrs_save(instance_data, previous_data=previous_data)
50+
elif signal_type == SignalType.SAVE:
51+
return model_cls.cqrs_save(instance_data, previous_data=previous_data)
5152

52-
elif signal_type == SignalType.SYNC:
53-
return model_cls.cqrs_save(
54-
instance_data,
55-
previous_data=previous_data,
56-
sync=True,
57-
)
53+
elif signal_type == SignalType.SYNC:
54+
return model_cls.cqrs_save(
55+
instance_data,
56+
previous_data=previous_data,
57+
sync=True,
58+
)
59+
except Error as e:
60+
pk_value = instance_data.get(model_cls._meta.pk.name)
61+
cqrs_revision = instance_data.get('cqrs_revision')
62+
63+
logger.error(
64+
'{0}\nCQRS {1} error: pk = {2}, cqrs_revision = {3} ({4}).'.format(
65+
str(e), signal_type, pk_value, cqrs_revision, model_cls.CQRS_ID,
66+
),
67+
)

dj_cqrs/managers.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dj_cqrs.constants import FIELDS_TRACKER_FIELD_NAME, TRACKED_FIELDS_ATTR_NAME
66

77
from django.core.exceptions import ValidationError
8-
from django.db import Error, IntegrityError, transaction
8+
from django.db import Error, transaction
99
from django.db.models import F, Manager
1010
from django.utils import timezone
1111

@@ -121,13 +121,6 @@ def create_instance(self, mapped_data, previous_data=None, sync=False):
121121
)
122122
except (Error, ValidationError) as e:
123123
pk_value = mapped_data[self._get_model_pk_name()]
124-
if isinstance(e, IntegrityError):
125-
logger.warning(
126-
'Potentially wrong CQRS sync order: '
127-
'pk = {0}, cqrs_revision = {1} ({2}).'.format(
128-
pk_value, mapped_data['cqrs_revision'], self.model.CQRS_ID,
129-
),
130-
)
131124

132125
logger.error(
133126
'{0}\nCQRS create error: pk = {1} ({2}).'.format(

tests/dj_replica/models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ class Book(models.Model):
121121
author = models.ForeignKey(AuthorRef, on_delete=models.CASCADE)
122122

123123

124+
class Article(ReplicaMixin, models.Model):
125+
CQRS_ID = 'article'
126+
127+
id = models.IntegerField(primary_key=True)
128+
129+
author = models.ForeignKey(AuthorRef, on_delete=models.CASCADE)
130+
131+
@classmethod
132+
def cqrs_create(cls, sync, mapped_data, previous_data=None):
133+
data = {
134+
'id': mapped_data['id'],
135+
'author_id': mapped_data['author']['id'],
136+
'cqrs_revision': mapped_data['cqrs_revision'],
137+
'cqrs_updated': mapped_data['cqrs_updated'],
138+
}
139+
return super().cqrs_create(sync, data, previous_data)
140+
141+
124142
class FailModel(ReplicaMixin, models.Model):
125143
CQRS_ID = 'fail'
126144

tests/test_controller.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from dj_cqrs.dataclasses import TransportPayload
77

88
from django.conf import settings
9+
from django.utils.timezone import now
910

1011
import pytest
1112

@@ -66,6 +67,30 @@ def test_route_signal_to_replica_model_with_db(django_assert_num_queries):
6667
route_signal_to_replica_model(SignalType.SAVE, 'lock', {})
6768

6869

70+
@pytest.mark.django_db(transaction=True)
71+
def test_route_signal_to_replica_model_integrity_error(caplog):
72+
instance_data = {
73+
'id': 10,
74+
'author': {
75+
'id': 100,
76+
},
77+
'cqrs_revision': 0,
78+
'cqrs_updated': now(),
79+
}
80+
instance = route_signal_to_replica_model(SignalType.SAVE, 'article', instance_data)
81+
assert not instance
82+
83+
errors = {
84+
'sqlite': 'FOREIGN KEY constraint failed',
85+
'postgres': (
86+
'insert or update on table "dj_replica_article" violates foreign key constraint'
87+
),
88+
'mysql': 'Cannot add or update a child row: a foreign key constraint',
89+
}
90+
91+
assert errors[settings.DB_ENGINE] in caplog.text
92+
93+
6994
def test_route_signal_to_replica_model_without_db():
7095
with pytest.raises(NotImplementedError):
7196
route_signal_to_replica_model(SignalType.SAVE, 'no_db', {})

tests/test_replica/test_mixin.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from dj_cqrs.metas import ReplicaMeta
44

5+
from django.conf import settings
56
from django.db.models import CharField, IntegerField, QuerySet
67
from django.utils.timezone import now
78

@@ -331,8 +332,24 @@ def test_update_before_create_is_over(caplog):
331332
assert updated_instance.cqrs_revision == 1
332333
assert updated_instance.char_field == 'new_text'
333334
assert not created_instance
334-
assert 'Potentially wrong CQRS sync order: pk = 1, cqrs_revision = 0 (basic).' in caplog.text
335-
assert 'CQRS create error: pk = 1 (basic).' in caplog.text
335+
336+
errors = {
337+
'sqlite': (
338+
'UNIQUE constraint failed: dj_replica_basicfieldsmodelref.int_field\n'
339+
'CQRS create error: pk = 1 (basic).\n'
340+
),
341+
'postgres': (
342+
'duplicate key value violates unique constraint "dj_replica_basicfieldsmodelref_pkey"\n'
343+
'DETAIL: Key (int_field)=(1) already exists.\n\n'
344+
'CQRS create error: pk = 1 (basic).\n'
345+
),
346+
'mysql': (
347+
'(1062, "Duplicate entry \'1\' for key \'dj_replica_basicfieldsmodelref.PRIMARY\'")\n'
348+
'CQRS create error: pk = 1 (basic).\n'
349+
),
350+
}
351+
352+
assert errors[settings.DB_ENGINE] in caplog.text
336353

337354

338355
@pytest.mark.django_db(transaction=True)

0 commit comments

Comments
 (0)