Skip to content

Commit c192222

Browse files
committed
update store write path
1 parent 0f309c3 commit c192222

2 files changed

Lines changed: 162 additions & 4 deletions

File tree

morango/sync/stream/serialize.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ def _handle_store_update(self, task: SerializeTask):
186186
# clear last_transfer_session_id
187187
task.store.last_transfer_session_id = None
188188

189+
self_ref_fk = task.self_referential_fk()
190+
if self_ref_fk:
191+
new_fk_value = getattr(task.obj, self_ref_fk) or ""
192+
if new_fk_value != task.store._self_ref_fk:
193+
task.store._self_ref_fk = new_fk_value
194+
task.store._self_ref_order = self._compute_self_ref_order(new_fk_value)
195+
189196
def _handle_store_create(self, task: SerializeTask):
190197
kwargs = {
191198
"id": task.obj.id,
@@ -200,11 +207,28 @@ def _handle_store_create(self, task: SerializeTask):
200207

201208
self_ref_fk = task.self_referential_fk()
202209
if self_ref_fk:
203-
self_ref_fk_value = getattr(task.obj, self_ref_fk)
204-
kwargs["_self_ref_fk"] = self_ref_fk_value or ""
210+
self_ref_fk_value = getattr(task.obj, self_ref_fk) or ""
211+
kwargs["_self_ref_fk"] = self_ref_fk_value
212+
kwargs["_self_ref_order"] = self._compute_self_ref_order(self_ref_fk_value)
205213

206214
task.set_store(Store(**kwargs))
207215

216+
@staticmethod
217+
def _compute_self_ref_order(self_ref_fk_value):
218+
"""
219+
Compute ``_self_ref_order`` for a self-referential store record.
220+
221+
Returns ``0`` when the record has no parent (root), otherwise queries
222+
the parent ``Store`` row and returns its ``_self_ref_order`` value.
223+
"""
224+
if not self_ref_fk_value:
225+
return 0
226+
return (
227+
Store.objects.filter(id=self_ref_fk_value)
228+
.values_list("_self_ref_order", flat=True)
229+
.first()
230+
)
231+
208232

209233
class ModelPartitionBuffer(Buffer[List[SerializeTask]]):
210234
"""Buffers tasks into chunks that have the same model class."""

tests/testapp/tests/sync/stream/test_serialize.py

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import json
2+
import uuid
23

34
import mock
45
from django.db.models import Q
5-
from django.test import SimpleTestCase
6+
from django.test import SimpleTestCase, TestCase
67

78
from morango.models.certificates import Filter
89
from morango.models.core import InstanceIDModel, RecordMaxCounter, Store, SyncableModel
@@ -143,6 +144,18 @@ def test_transform(self, mock_bulk, mock_qs):
143144

144145

145146
class StoreUpdateTestCase(SimpleTestCase):
147+
def _build_sync_obj(self, **overrides):
148+
obj = mock.Mock()
149+
obj.id = uuid.uuid4().hex
150+
obj.serialize.return_value = {}
151+
obj.morango_model_name = "mymodel"
152+
obj.morango_profile = "profile"
153+
obj._morango_partition = "partition"
154+
obj._morango_source_id = "src"
155+
for key, value in overrides.items():
156+
setattr(obj, key, value)
157+
return obj
158+
146159
@mock.patch("morango.sync.stream.serialize.StoreUpdate._handle_store_create")
147160
def test_transform__creates(self, mock_handle_store_create):
148161
current_id = mock.Mock(id="inst_1", counter=10)
@@ -176,7 +189,8 @@ def test_transform__updates(self, mock_handle_store_update):
176189
mock_handle_store_update.assert_called_once_with(task)
177190
self.assertEqual(task.counter.counter, 10)
178191

179-
def test_handle_store_update(self):
192+
@mock.patch("morango.sync.stream.serialize.self_referential_fk", return_value=None)
193+
def test_handle_store_update(self, _mock_srf):
180194
current_id = mock.Mock(id="inst_1", counter=10)
181195
update = StoreUpdate(current_id)
182196

@@ -193,6 +207,126 @@ def test_handle_store_update(self):
193207
self.assertEqual(ser_data["old"], 1)
194208
self.assertEqual(ser_data["new"], 2)
195209

210+
@mock.patch("morango.sync.stream.serialize.self_referential_fk", return_value=None)
211+
def test_handle_store_create__non_self_ref(self, _mock_srf):
212+
current_id = mock.Mock(id="inst_1", counter=1)
213+
update = StoreUpdate(current_id)
214+
215+
obj = self._build_sync_obj()
216+
217+
task = SerializeTask(mock.Mock(), obj)
218+
update._handle_store_create(task)
219+
220+
self.assertIsNone(task.store._self_ref_order)
221+
222+
@mock.patch("morango.sync.stream.serialize.self_referential_fk", return_value="parent_id")
223+
def test_handle_store_create__self_ref_no_parent(self, _mock_srf):
224+
current_id = mock.Mock(id="inst_1", counter=1)
225+
update = StoreUpdate(current_id)
226+
227+
obj = self._build_sync_obj(parent_id=None) # no parent
228+
229+
task = SerializeTask(mock.Mock(), obj)
230+
update._handle_store_create(task)
231+
232+
self.assertEqual(task.store._self_ref_fk, "")
233+
self.assertEqual(task.store._self_ref_order, 0)
234+
235+
@mock.patch("morango.sync.stream.serialize.self_referential_fk", return_value="parent_id")
236+
def test_handle_store_update__self_ref_fk_unchanged(self, _mock_srf):
237+
current_id = mock.Mock(id="inst_1", counter=2)
238+
update = StoreUpdate(current_id)
239+
240+
parent_id = uuid.uuid4().hex
241+
store = Store(
242+
serialized=json.dumps({}),
243+
dirty_bit=False,
244+
_self_ref_fk=parent_id,
245+
_self_ref_order=5,
246+
)
247+
obj = self._build_sync_obj(parent_id=parent_id) # same FK — no change
248+
249+
task = SerializeTask(mock.Mock(), obj)
250+
task.set_store(store)
251+
update._handle_store_update(task)
252+
253+
self.assertEqual(task.store._self_ref_fk, parent_id)
254+
self.assertEqual(task.store._self_ref_order, 5)
255+
256+
257+
def _make_store(**kwargs):
258+
defaults = dict(
259+
id=uuid.uuid4().hex,
260+
profile="facilitydata",
261+
serialized="{}",
262+
last_saved_instance=uuid.uuid4().hex,
263+
last_saved_counter=1,
264+
partition="partition",
265+
source_id=uuid.uuid4().hex,
266+
model_name="facility",
267+
)
268+
defaults.update(kwargs)
269+
return Store.objects.create(**defaults)
270+
271+
272+
class StoreUpdateSelfRefOrderDbTestCase(TestCase):
273+
def setUp(self):
274+
self.current_id = mock.Mock(id="inst_1", counter=1)
275+
self.update = StoreUpdate(self.current_id)
276+
277+
def _task(self, self_ref_fk_field, fk_value):
278+
obj = mock.Mock()
279+
obj.id = uuid.uuid4().hex
280+
obj.serialize.return_value = {}
281+
obj.morango_model_name = "facility"
282+
obj.morango_profile = "facilitydata"
283+
obj._morango_partition = "partition"
284+
obj._morango_source_id = "src"
285+
setattr(obj, self_ref_fk_field, fk_value)
286+
return SerializeTask(mock.Mock(), obj)
287+
288+
@mock.patch("morango.sync.stream.serialize.self_referential_fk", return_value="parent_id")
289+
def test_handle_store_create__self_ref_with_parent(self, _mock_srf):
290+
parent_store = _make_store(_self_ref_order=3)
291+
292+
task = self._task("parent_id", parent_store.id)
293+
self.update._handle_store_create(task)
294+
295+
self.assertEqual(task.store._self_ref_fk, parent_store.id)
296+
self.assertEqual(task.store._self_ref_order, 3)
297+
298+
@mock.patch("morango.sync.stream.serialize.self_referential_fk", return_value="parent_id")
299+
def test_handle_store_create__self_ref_parent_not_in_store(self, _mock_srf):
300+
missing_parent_id = uuid.uuid4().hex
301+
302+
task = self._task("parent_id", missing_parent_id)
303+
self.update._handle_store_create(task)
304+
305+
self.assertEqual(task.store._self_ref_fk, missing_parent_id)
306+
self.assertIsNone(task.store._self_ref_order)
307+
308+
@mock.patch("morango.sync.stream.serialize.self_referential_fk", return_value="parent_id")
309+
def test_handle_store_update__self_ref_fk_changed(self, _mock_srf):
310+
old_parent_store = _make_store(_self_ref_order=0)
311+
new_parent_store = _make_store(_self_ref_order=7)
312+
313+
store = Store(
314+
serialized=json.dumps({}),
315+
dirty_bit=False,
316+
_self_ref_fk=old_parent_store.id,
317+
_self_ref_order=0,
318+
)
319+
obj = mock.Mock()
320+
obj.serialize.return_value = {}
321+
obj.parent_id = new_parent_store.id # FK changed
322+
323+
task = SerializeTask(mock.Mock(), obj)
324+
task.set_store(store)
325+
self.update._handle_store_update(task)
326+
327+
self.assertEqual(task.store._self_ref_fk, new_parent_store.id)
328+
self.assertEqual(task.store._self_ref_order, 7)
329+
196330

197331
class ModelPartitionBufferTestCase(SimpleTestCase):
198332
def test_buffer_splits_on_model_change(self):

0 commit comments

Comments
 (0)