Skip to content

Commit 1b3aeef

Browse files
authored
Merge pull request #36227 from dimagi/revert-36091-jt/lookup-table-smart-sync
Revert "Lookup Table Limit Restore to Recently Modified"
2 parents baafffd + 0da6e30 commit 1b3aeef

File tree

8 files changed

+100
-199
lines changed

8 files changed

+100
-199
lines changed

Diff for: corehq/apps/fixtures/fixturegenerators.py

+27-70
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
from collections import defaultdict
2+
from functools import partial
23
from operator import attrgetter
3-
from io import BytesIO
44
from xml.etree import cElementTree as ElementTree
55

66
from casexml.apps.phone.fixtures import FixtureProvider
77
from casexml.apps.phone.utils import (
88
GLOBAL_USER_ID,
9-
record_datadog_metric,
10-
get_cached_fixture_items,
11-
cache_fixture_items_data,
12-
combine_io_streams
9+
get_or_cache_global_fixture,
1310
)
1411
from corehq.apps.fixtures.exceptions import FixtureTypeCheckError
15-
from corehq.apps.fixtures.models import fixture_bucket, LookupTable, LookupTableRow
12+
from corehq.apps.fixtures.models import FIXTURE_BUCKET, LookupTable, LookupTableRow
1613
from corehq.apps.products.fixtures import product_fixture_generator_json
1714
from corehq.apps.programs.fixtures import program_fixture_generator_json
1815
from corehq.util.metrics import metrics_histogram
1916
from corehq.util.xml_utils import serialize
2017
from .utils import clean_fixture_field_name, get_index_schema_node
21-
from dimagi.utils.couch import CriticalSection
2218

2319
LOOKUP_TABLE_FIXTURE = 'lookup_table_fixture'
2420
REPORT_FIXTURE = 'report_fixture'
@@ -97,34 +93,31 @@ def item_lists_by_app(app, module):
9793

9894

9995
def get_global_items_by_domain(domain, case_id):
100-
global_types = LookupTable.objects.by_domain(domain).filter(is_global=True)
101-
return ItemListsProvider().get_global_items(domain, global_types, case_id, False)
96+
global_types = {}
97+
for data_type in LookupTable.objects.by_domain(domain):
98+
if data_type.is_global:
99+
global_types[data_type.id] = data_type
100+
if global_types:
101+
data_fn = partial(ItemListsProvider()._get_global_items, global_types, domain)
102+
return get_or_cache_global_fixture(domain, case_id, FIXTURE_BUCKET, '', data_fn)
102103

103104

104105
class ItemListsProvider(FixtureProvider):
105106
id = 'item-list'
106107

107108
def __call__(self, restore_state):
108109
restore_user = restore_state.restore_user
109-
global_types = []
110+
global_types = {}
110111
user_types = {}
111-
should_full_sync = not restore_state.last_sync_log or not restore_state.last_sync_log.date
112-
if should_full_sync:
113-
data_types = LookupTable.objects.by_domain(restore_user.domain)
114-
else:
115-
data_types = LookupTable.objects.get_tables_modified_since(restore_user.domain,
116-
restore_state.last_sync_log.date)
117-
for data_type in data_types:
112+
for data_type in LookupTable.objects.by_domain(restore_user.domain):
118113
if data_type.is_global:
119-
global_types.append(data_type)
114+
global_types[data_type.id] = data_type
120115
else:
121116
user_types[data_type.id] = data_type
122117
items = []
123118
user_items_count = 0
124119
if global_types:
125-
global_items = self.get_global_items(restore_state.restore_user.domain, global_types,
126-
restore_state.restore_user.user_id,
127-
restore_state.overwrite_cache)
120+
global_items = self.get_global_items(global_types, restore_state)
128121
items.extend(global_items)
129122
if user_types:
130123
user_items, user_items_count = self.get_user_items_and_count(user_types, restore_user)
@@ -142,57 +135,21 @@ def __call__(self, restore_state):
142135
)
143136
return items
144137

145-
def get_global_items(self, domain, global_types, user_id, overwrite_cache):
146-
"""
147-
:param user_id: User's id, if this is for case restore, then pass in case id
148-
"""
149-
return combine_io_streams(
150-
[self._get_or_cache_global_fixture(
151-
domain,
152-
global_type,
153-
overwrite_cache,
154-
) for global_type in sorted(global_types, key=lambda global_type: global_type.tag)],
155-
user_id
156-
)
157-
158-
def _get_or_cache_global_fixture(self, domain, global_type, overwrite_cache=False):
159-
"""
160-
Get the fixture data for a global fixture (one that does not vary by user).
161-
162-
:param domain: The domain to get or cache the fixture
163-
:param user_id: User's id, if this is for case restore, then pass in case id
164-
:param overwrite_cache: a boolean property from RestoreState object, default is False
165-
:return: a byte string representation of the fixture
166-
"""
167-
io_stream = None
168-
key = fixture_bucket(global_type.id, domain)
169-
170-
if not overwrite_cache:
171-
io_stream = get_cached_fixture_items(key)
172-
record_datadog_metric('cache_miss' if io_stream is None else 'cache_hit', key)
173-
if io_stream is not None:
174-
io_stream = BytesIO(io_stream)
175-
176-
if io_stream is None:
177-
with CriticalSection([key]):
178-
if not overwrite_cache:
179-
# re-check cache to avoid re-computing it
180-
io_stream = get_cached_fixture_items(key)
181-
if io_stream is not None:
182-
io_stream = BytesIO(io_stream)
183-
if io_stream is None:
184-
record_datadog_metric('generate', key)
185-
item = self._get_global_item(global_type, domain)
186-
io_stream = BytesIO()
187-
io_stream.write(ElementTree.tostring(item, encoding='utf-8'))
188-
io_stream.seek(0)
189-
cache_fixture_items_data(io_stream, domain, '', key)
190-
return io_stream
191-
192-
def _get_global_item(self, global_type, domain):
138+
def get_global_items(self, global_types, restore_state):
139+
domain = restore_state.restore_user.domain
140+
data_fn = partial(self._get_global_items, global_types, domain)
141+
return get_or_cache_global_fixture(restore_state.restore_user.domain,
142+
restore_state.restore_user.user_id,
143+
FIXTURE_BUCKET,
144+
'',
145+
data_fn,
146+
restore_state.overwrite_cache)
147+
148+
def _get_global_items(self, global_types, domain):
193149
def get_items_by_type(data_type):
194150
return LookupTableRow.objects.iter_rows(domain, table_id=data_type.id)
195-
return self._get_fixtures({global_type.id: global_type}, get_items_by_type, GLOBAL_USER_ID)[0]
151+
152+
return self._get_fixtures(global_types, get_items_by_type, GLOBAL_USER_ID)
196153

197154
def get_user_items_and_count(self, user_types, restore_user):
198155
user_items_count = 0

Diff for: corehq/apps/fixtures/models.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@
1212
from corehq.util.jsonattrs import AttrsDict, AttrsList, list_of
1313
from .exceptions import FixtureVersionError
1414

15-
FIXTURE_BUCKET_PREFIX = 'domain-fixtures'
16-
17-
18-
def fixture_bucket(lookup_table_id, domain):
19-
return f'{FIXTURE_BUCKET_PREFIX}-{lookup_table_id}' + '/' + domain
15+
FIXTURE_BUCKET = 'domain-fixtures'
2016

2117

2218
class LookupTableManager(models.Manager):
@@ -31,9 +27,6 @@ def by_domain_tag(self, domain_name, tag):
3127
def domain_tag_exists(self, domain_name, tag):
3228
return self.filter(domain=domain_name, tag=tag).exists()
3329

34-
def get_tables_modified_since(self, domain, since_datetime):
35-
return self.filter(domain=domain, last_modified__gt=since_datetime)
36-
3730

3831
@define
3932
class Alias:

Diff for: corehq/apps/fixtures/tests/test_fixture_data.py

+5-56
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,28 @@
11
from xml.etree import cElementTree as ElementTree
2-
from datetime import datetime
32

43
from django.test import TestCase
54

6-
from casexml.apps.phone.models import SimplifiedSyncLog
75
from casexml.apps.case.tests.util import check_xml_line_by_line
86
from casexml.apps.phone.tests.utils import \
97
call_fixture_generator as call_fixture_generator_raw
108

119
from corehq.apps.fixtures import fixturegenerators
1210
from corehq.apps.fixtures.models import (
13-
fixture_bucket,
11+
FIXTURE_BUCKET,
1412
Field,
1513
LookupTable,
1614
LookupTableRow,
1715
LookupTableRowOwner,
1816
OwnerType,
1917
TypeField,
2018
)
21-
from corehq.apps.fixtures.utils import clear_fixture_cache
2219
from corehq.apps.users.models import CommCareUser
2320
from corehq.blobs import get_blob_db
2421

2522

26-
def call_fixture_generator(user, last_sync=None):
23+
def call_fixture_generator(user):
2724
return [ElementTree.fromstring(f) if isinstance(f, bytes) else f
28-
for f in call_fixture_generator_raw(fixturegenerators.item_lists, user,
29-
last_sync=last_sync)]
25+
for f in call_fixture_generator_raw(fixturegenerators.item_lists, user)]
3026

3127

3228
class FixtureDataTest(TestCase):
@@ -48,7 +44,6 @@ def setUp(self):
4844
item_attributes=[],
4945
)
5046
self.data_type.save()
51-
self.addCleanup(get_blob_db().delete, key=fixture_bucket(self.data_type.id, self.domain))
5247

5348
self.data_item = LookupTableRow(
5449
domain=self.domain,
@@ -80,14 +75,7 @@ def setUp(self):
8075
row_id=self.data_item.id,
8176
)
8277
self.ownership.save()
83-
84-
self.sync_log = SimplifiedSyncLog(
85-
date=datetime.utcnow(),
86-
domain=self.domain,
87-
user_id=self.user.user_id,
88-
build_id='some-build-id'
89-
)
90-
self.sync_log.save()
78+
self.addCleanup(get_blob_db().delete, key=FIXTURE_BUCKET + '/' + self.domain)
9179

9280
def test_xml(self):
9381
check_xml_line_by_line(self, """
@@ -258,49 +246,11 @@ def test_cached_global_fixture_user_id(self):
258246

259247
fixtures = call_fixture_generator(frank)
260248
self.assertEqual({item.attrib['user_id'] for item in fixtures}, {frank.user_id})
261-
self.assertTrue(get_blob_db().exists(key=fixture_bucket(sandwich.id, self.domain)))
249+
self.assertTrue(get_blob_db().exists(key=FIXTURE_BUCKET + '/' + self.domain))
262250

263251
fixtures = call_fixture_generator(sammy)
264252
self.assertEqual({item.attrib['user_id'] for item in fixtures}, {sammy.user_id})
265253

266-
def test_include_fixture_when_data_type_modified(self):
267-
fixture = call_fixture_generator(self.user.to_ota_restore_user(self.domain), self.sync_log)
268-
self.assertEqual(len(fixture), 0)
269-
270-
self.data_type.save()
271-
fixture, = call_fixture_generator(self.user.to_ota_restore_user(self.domain), self.sync_log)
272-
check_xml_line_by_line(self, """
273-
<fixture id="item-list:district" user_id="%s">
274-
<district_list>
275-
<district>
276-
<state_name>Delhi_state</state_name>
277-
<district_name lang="hin">Delhi_in_HIN</district_name>
278-
<district_name lang="eng">Delhi_in_ENG</district_name>
279-
<district_id>Delhi_id</district_id>
280-
</district>
281-
</district_list>
282-
</fixture>
283-
""" % self.user.user_id, ElementTree.tostring(fixture, encoding='utf-8'))
284-
285-
def test_include_fixture_when_cache_is_cleared(self):
286-
fixture = call_fixture_generator(self.user.to_ota_restore_user(self.domain), self.sync_log)
287-
self.assertEqual(len(fixture), 0)
288-
289-
clear_fixture_cache(self.domain, [self.data_type.id])
290-
fixture, = call_fixture_generator(self.user.to_ota_restore_user(self.domain), self.sync_log)
291-
check_xml_line_by_line(self, """
292-
<fixture id="item-list:district" user_id="%s">
293-
<district_list>
294-
<district>
295-
<state_name>Delhi_state</state_name>
296-
<district_name lang="hin">Delhi_in_HIN</district_name>
297-
<district_name lang="eng">Delhi_in_ENG</district_name>
298-
<district_id>Delhi_id</district_id>
299-
</district>
300-
</district_list>
301-
</fixture>
302-
""" % self.user.user_id, ElementTree.tostring(fixture, encoding='utf-8'))
303-
304254
def make_data_type(self, name, is_global):
305255
data_type = LookupTable(
306256
domain=self.domain,
@@ -313,7 +263,6 @@ def make_data_type(self, name, is_global):
313263
item_attributes=[],
314264
)
315265
data_type.save()
316-
self.addCleanup(get_blob_db().delete, key=fixture_bucket(data_type.id, self.domain))
317266
return data_type
318267

319268
def make_data_item(self, data_type, cost):

Diff for: corehq/apps/fixtures/tests/test_upload.py

-38
Original file line numberDiff line numberDiff line change
@@ -684,24 +684,6 @@ def error_tx():
684684
self.upload([(None, 'N', 'orange')])
685685
clear_cache.assert_called_once()
686686

687-
def test_row_added_clears_its_table_cache(self):
688-
self.upload([(None, 'N', 'apple')])
689-
table = self.get_table()
690-
apple_id = self.get_rows(None)[0].id.hex
691-
692-
with patch.object(mod, "clear_fixture_cache") as mock_clear:
693-
self.upload([(apple_id, 'N', 'apple'), (None, 'N', 'banana')])
694-
self.assertEqual(mock_clear.call_args[0][1], {table.id})
695-
696-
def test_row_deleted_clears_its_table_cache(self):
697-
self.upload([(None, 'N', 'apple')])
698-
table = self.get_table()
699-
apple_id = self.get_rows(None)[0].id.hex
700-
701-
with patch.object(mod, "clear_fixture_cache") as mock_clear:
702-
self.upload([(apple_id, 'Y', 'apple')])
703-
self.assertEqual(mock_clear.call_args[0][1], {table.id})
704-
705687

706688
class TestLookupTableOwnershipUpload(TestCase):
707689
do_upload = _run_upload
@@ -825,26 +807,6 @@ def test_non_string_owner_names(self):
825807
self.assertEqual(self.get_rows(), [('apple', {'3'}, {'3'}, {'3'})])
826808
self.assertFalse(result.errors)
827809

828-
def test_owner_added_clears_cache(self):
829-
"""Test that adding an owner to an existing row clears that table's cache"""
830-
self.upload([(None, 'N', 'apple')])
831-
table = LookupTable.objects.by_domain_tag(self.domain, 'things')
832-
apple_id = self.get_rows(None)[0].id.hex
833-
834-
with patch.object(mod, "clear_fixture_cache") as mock_clear:
835-
self.upload([(apple_id, 'N', 'apple', 'user1', 'G1', 'loc1')])
836-
self.assertEqual(mock_clear.call_args[0][1], {table.id})
837-
838-
def test_owner_deleted_clears_cache(self):
839-
"""Test that removing an owner from an existing row clears that table's cache"""
840-
self.upload([(None, 'N', 'apple', 'user1', 'G1', 'loc1')])
841-
table = LookupTable.objects.by_domain_tag(self.domain, 'things')
842-
apple_id = self.get_rows(None)[0].id.hex
843-
844-
with patch.object(mod, "clear_fixture_cache") as mock_clear:
845-
self.upload([(apple_id, 'N', 'apple', None, None, None)])
846-
self.assertEqual(mock_clear.call_args[0][1], {table.id})
847-
848810
def upload(self, rows, *, check_result=True, **kw):
849811
data = self.make_rows(rows)
850812
workbook = TestFixtureUpload.get_workbook_from_data(self.headers, data)

Diff for: corehq/apps/fixtures/utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def get_index_schema_node(fixture_id, attrs_to_index):
5959

6060

6161
def clear_fixture_cache(domain, data_type_ids):
62+
from corehq.apps.fixtures.models import FIXTURE_BUCKET
6263
assert not isinstance(data_type_ids, (str, UUID)), f'expected list or set, got {type(data_type_ids).__name__}'
6364
LookupTable.objects.filter(id__in=data_type_ids).update(last_modified=datetime.utcnow())
64-
from corehq.apps.fixtures.models import fixture_bucket
65-
for data_type_id in data_type_ids:
66-
get_blob_db().delete(key=fixture_bucket(data_type_id, domain))
65+
66+
get_blob_db().delete(key=FIXTURE_BUCKET + '/' + domain)

Diff for: corehq/blobs/models.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ class Meta:
6767
unique_together = [
6868
# HACK work around unique=True implies db_index=True
6969
# https://code.djangoproject.com/ticket/24082
70-
# Avoid extra varchar_pattern_ops index
70+
# Avoid extra varchar_pattern_ops index while still maintaining
71+
# an index for exact matches. We don't need varchar_pattern_ops
7172
# since we do not do LIKE queries on these
7273
# https://stackoverflow.com/a/50926644/10840
7374
("key",),

Diff for: corehq/ex-submodules/casexml/apps/phone/tests/test_ota_fixtures.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from casexml.apps.phone.utils import MockDevice
44
from corehq.apps.domain.models import Domain
55
from corehq.apps.fixtures.models import (
6-
fixture_bucket,
6+
FIXTURE_BUCKET,
77
Field,
88
LookupTable,
99
LookupTableRow,
@@ -37,13 +37,11 @@ def setUpClass(cls):
3737
cls.group2 = Group(domain=DOMAIN, name='group2', case_sharing=True, users=[])
3838
cls.group2.save()
3939

40-
sa_data_type, _ = make_item_lists(SA_PROVINCES, 'western cape')
41-
fr_data_type, _ = make_item_lists(FR_PROVINCES, 'burgundy', cls.group1)
42-
ca_data_type, _ = make_item_lists(CA_PROVINCES, 'alberta', cls.group2)
40+
make_item_lists(SA_PROVINCES, 'western cape'),
41+
make_item_lists(FR_PROVINCES, 'burgundy', cls.group1),
42+
make_item_lists(CA_PROVINCES, 'alberta', cls.group2),
4343

44-
cls.addClassCleanup(get_blob_db().delete, key=fixture_bucket(sa_data_type.id, DOMAIN))
45-
cls.addClassCleanup(get_blob_db().delete, key=fixture_bucket(fr_data_type.id, DOMAIN))
46-
cls.addClassCleanup(get_blob_db().delete, key=fixture_bucket(ca_data_type.id, DOMAIN))
44+
cls.addClassCleanup(get_blob_db().delete, key=FIXTURE_BUCKET + "/" + DOMAIN)
4745

4846
cls.restore_user = cls.user.to_ota_restore_user(DOMAIN)
4947

0 commit comments

Comments
 (0)