2018-11-16 13:16:36 +03:00
|
|
|
import datetime
|
2018-11-29 17:15:27 +03:00
|
|
|
import logging
|
2018-11-29 13:19:25 +03:00
|
|
|
from subprocess import Popen
|
2018-11-20 15:24:15 +03:00
|
|
|
from time import sleep
|
2018-12-03 14:41:34 +03:00
|
|
|
from unittest import expectedFailure, skip
|
2018-11-16 13:16:36 +03:00
|
|
|
|
2018-11-28 13:30:19 +03:00
|
|
|
import os
|
|
|
|
from django.test import TransactionTestCase
|
2018-11-29 13:19:25 +03:00
|
|
|
from django.utils.timezone import now
|
|
|
|
from random import randint
|
2018-11-20 15:24:15 +03:00
|
|
|
|
2018-11-16 13:16:36 +03:00
|
|
|
from django_clickhouse.database import connections
|
|
|
|
from django_clickhouse.migrations import migrate_app
|
2018-12-03 14:41:34 +03:00
|
|
|
from django_clickhouse.utils import int_ranges
|
2018-11-26 17:17:58 +03:00
|
|
|
from tests.clickhouse_models import ClickHouseTestModel, ClickHouseCollapseTestModel, ClickHouseMultiTestModel
|
2018-11-16 13:16:36 +03:00
|
|
|
from tests.models import TestModel
|
|
|
|
|
2018-11-29 17:15:27 +03:00
|
|
|
logger = logging.getLogger('django-clickhouse')
|
|
|
|
|
2018-11-16 13:16:36 +03:00
|
|
|
|
|
|
|
class SyncTest(TransactionTestCase):
|
|
|
|
def setUp(self):
|
|
|
|
self.db = connections['default']
|
|
|
|
self.db.drop_database()
|
|
|
|
self.db.create_database()
|
|
|
|
migrate_app('tests', 'default')
|
2018-11-20 15:24:15 +03:00
|
|
|
ClickHouseTestModel.get_storage().flush()
|
2018-11-16 13:16:36 +03:00
|
|
|
|
|
|
|
def test_simple(self):
|
2019-01-14 10:45:46 +03:00
|
|
|
obj = TestModel.objects.create(value=1, created=datetime.datetime.now(), created_date=datetime.date.today())
|
2018-11-16 13:16:36 +03:00
|
|
|
ClickHouseTestModel.sync_batch_from_storage()
|
|
|
|
|
2018-11-28 13:33:01 +03:00
|
|
|
synced_data = list(ClickHouseTestModel.objects.all())
|
2018-11-16 13:16:36 +03:00
|
|
|
self.assertEqual(1, len(synced_data))
|
|
|
|
self.assertEqual(obj.created_date, synced_data[0].created_date)
|
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
|
|
|
|
2019-01-11 11:00:42 +03:00
|
|
|
def test_collapsing_update_by_final(self):
|
2019-01-14 10:45:46 +03:00
|
|
|
obj = TestModel.objects.create(value=1, created=datetime.datetime.now(), created_date=datetime.date.today())
|
2018-11-16 13:16:36 +03:00
|
|
|
obj.value = 2
|
|
|
|
obj.save()
|
|
|
|
ClickHouseCollapseTestModel.sync_batch_from_storage()
|
|
|
|
|
2018-11-20 15:24:15 +03:00
|
|
|
# sync_batch_from_storage uses FINAL, so data would be collapsed by now
|
2018-11-28 13:33:01 +03:00
|
|
|
synced_data = list(ClickHouseCollapseTestModel.objects.all())
|
2018-11-16 13:16:36 +03:00
|
|
|
self.assertEqual(1, len(synced_data))
|
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
|
|
|
|
|
|
|
obj.value = 3
|
|
|
|
obj.save()
|
|
|
|
ClickHouseCollapseTestModel.sync_batch_from_storage()
|
|
|
|
|
|
|
|
synced_data = list(self.db.select('SELECT * FROM $table FINAL', model_class=ClickHouseCollapseTestModel))
|
2018-11-16 15:41:54 +03:00
|
|
|
self.assertGreaterEqual(1, len(synced_data))
|
2018-11-16 13:16:36 +03:00
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
2018-11-20 15:24:15 +03:00
|
|
|
|
2019-01-11 11:00:42 +03:00
|
|
|
def test_collapsing_update_by_version(self):
|
|
|
|
ClickHouseCollapseTestModel.engine.version_col = 'version'
|
|
|
|
|
2019-01-14 10:45:46 +03:00
|
|
|
obj = TestModel.objects.create(value=1, created=datetime.datetime.now(), created_date=datetime.date.today())
|
2019-01-11 11:00:42 +03:00
|
|
|
obj.value = 2
|
|
|
|
obj.save()
|
|
|
|
ClickHouseCollapseTestModel.sync_batch_from_storage()
|
|
|
|
|
|
|
|
# sync_batch_from_storage uses FINAL, so data would be collapsed by now
|
|
|
|
synced_data = list(ClickHouseCollapseTestModel.objects.all())
|
|
|
|
self.assertEqual(1, len(synced_data))
|
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
|
|
|
|
|
|
|
obj.value = 3
|
|
|
|
obj.save()
|
|
|
|
ClickHouseCollapseTestModel.sync_batch_from_storage()
|
|
|
|
|
|
|
|
synced_data = list(self.db.select('SELECT * FROM $table FINAL', model_class=ClickHouseCollapseTestModel))
|
|
|
|
self.assertGreaterEqual(1, len(synced_data))
|
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
|
|
|
|
|
|
|
ClickHouseCollapseTestModel.engine.version_col = None
|
|
|
|
|
2018-11-20 15:24:15 +03:00
|
|
|
@expectedFailure
|
|
|
|
def test_collapsing_delete(self):
|
|
|
|
obj = TestModel.objects.create(value=1, created_date=datetime.date.today())
|
|
|
|
ClickHouseCollapseTestModel.sync_batch_from_storage()
|
|
|
|
obj.delete()
|
|
|
|
ClickHouseCollapseTestModel.sync_batch_from_storage()
|
|
|
|
|
|
|
|
# sync_batch_from_storage uses FINAL, so data would be collapsed by now
|
2018-11-28 13:33:01 +03:00
|
|
|
synced_data = list(ClickHouseCollapseTestModel.objects.all())
|
2018-11-20 15:24:15 +03:00
|
|
|
self.assertEqual(0, len(synced_data))
|
|
|
|
|
2018-11-26 17:17:58 +03:00
|
|
|
def test_multi_model(self):
|
2019-01-14 10:45:46 +03:00
|
|
|
obj = TestModel.objects.create(value=1, created=datetime.datetime.now(), created_date=datetime.date.today())
|
2018-11-26 17:17:58 +03:00
|
|
|
obj.value = 2
|
|
|
|
obj.save()
|
|
|
|
ClickHouseMultiTestModel.sync_batch_from_storage()
|
|
|
|
|
2018-11-28 13:33:01 +03:00
|
|
|
synced_data = list(ClickHouseTestModel.objects.all())
|
2018-11-26 17:17:58 +03:00
|
|
|
self.assertEqual(1, len(synced_data))
|
|
|
|
self.assertEqual(obj.created_date, synced_data[0].created_date)
|
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
|
|
|
|
|
|
|
# sync_batch_from_storage uses FINAL, so data would be collapsed by now
|
2018-11-28 13:33:01 +03:00
|
|
|
synced_data = list(ClickHouseCollapseTestModel.objects.all())
|
2018-11-26 17:17:58 +03:00
|
|
|
self.assertEqual(1, len(synced_data))
|
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
|
|
|
|
|
|
|
obj.value = 3
|
|
|
|
obj.save()
|
|
|
|
ClickHouseMultiTestModel.sync_batch_from_storage()
|
|
|
|
|
|
|
|
synced_data = list(self.db.select('SELECT * FROM $table FINAL', model_class=ClickHouseCollapseTestModel))
|
|
|
|
self.assertGreaterEqual(1, len(synced_data))
|
|
|
|
self.assertEqual(obj.value, synced_data[0].value)
|
|
|
|
self.assertEqual(obj.id, synced_data[0].id)
|
|
|
|
|
2018-11-20 15:24:15 +03:00
|
|
|
|
|
|
|
class KillTest(TransactionTestCase):
|
2018-12-03 14:41:34 +03:00
|
|
|
TEST_TIME = 60
|
2018-11-29 13:19:25 +03:00
|
|
|
maxDiff = None
|
2018-11-20 15:24:15 +03:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
ClickHouseTestModel.get_storage().flush()
|
2018-11-29 13:19:25 +03:00
|
|
|
connections['default'].drop_database()
|
|
|
|
connections['default'].create_database()
|
|
|
|
migrate_app('tests', 'default')
|
2018-11-20 15:24:15 +03:00
|
|
|
|
2018-12-03 14:41:34 +03:00
|
|
|
# Disable sync for not interesting models
|
|
|
|
ClickHouseMultiTestModel.sync_enabled = False
|
|
|
|
ClickHouseTestModel.sync_enabled = False
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
# Disable sync for not interesting models
|
|
|
|
ClickHouseMultiTestModel.sync_enabled = True
|
|
|
|
ClickHouseTestModel.sync_enabled = True
|
|
|
|
|
2018-11-20 15:24:15 +03:00
|
|
|
def _check_data(self):
|
2018-12-03 14:41:34 +03:00
|
|
|
logger.debug('django-clickhouse: syncing left test data')
|
|
|
|
|
2018-11-29 13:19:25 +03:00
|
|
|
# Sync all data that is not synced
|
2018-11-29 17:10:41 +03:00
|
|
|
# Data is expected to be in test_db, not default. So we need to call subprocess
|
|
|
|
# in order everything works correctly
|
2018-12-03 14:41:34 +03:00
|
|
|
import_key = ClickHouseCollapseTestModel.get_import_key()
|
|
|
|
storage = ClickHouseCollapseTestModel.get_storage()
|
|
|
|
sync_left = storage.operations_count(import_key)
|
|
|
|
while sync_left:
|
|
|
|
logger.debug('django-clickhouse: final sync (%d left)' % sync_left)
|
2018-11-29 17:10:41 +03:00
|
|
|
self.sync_iteration(False)
|
2018-12-03 14:41:34 +03:00
|
|
|
sync_left = storage.operations_count(import_key)
|
2018-11-20 15:24:15 +03:00
|
|
|
|
|
|
|
ch_data = list(connections['default'].select('SELECT * FROM $table FINAL ORDER BY id',
|
|
|
|
model_class=ClickHouseCollapseTestModel))
|
|
|
|
pg_data = list(TestModel.objects.all().order_by('id'))
|
|
|
|
|
2018-12-03 14:41:34 +03:00
|
|
|
if len(pg_data) != len(ch_data):
|
|
|
|
absent_ids = set(item.id for item in pg_data) - set(item.id for item in ch_data)
|
|
|
|
logger.debug('django_clickhouse: absent ranges: %s (min: %d, max: %d)'
|
|
|
|
% (','.join(('(%d, %d)' % r) for r in int_ranges(absent_ids)),
|
|
|
|
min(item.id for item in pg_data), max(item.id for item in pg_data)))
|
|
|
|
|
2018-11-20 15:24:15 +03:00
|
|
|
self.assertEqual(len(pg_data), len(ch_data))
|
2018-11-29 13:19:25 +03:00
|
|
|
serializer = ClickHouseCollapseTestModel.get_django_model_serializer()
|
2019-01-24 15:47:52 +03:00
|
|
|
self.assertListEqual(ch_data, list(serializer.serialize_many(pg_data)))
|
2018-11-29 13:19:25 +03:00
|
|
|
|
|
|
|
@classmethod
|
2018-11-29 17:10:41 +03:00
|
|
|
def sync_iteration(cls, kill=True):
|
2018-11-29 13:19:25 +03:00
|
|
|
test_script = os.path.join(os.path.dirname(__file__), 'kill_test_sub_process.py')
|
2018-12-03 14:41:34 +03:00
|
|
|
if kill:
|
|
|
|
args = ['--test-time', str(cls.TEST_TIME)]
|
|
|
|
else:
|
|
|
|
args = ['--once', 'true']
|
|
|
|
p_sync = Popen(['python3', test_script, 'sync'] + args)
|
2018-11-29 17:10:41 +03:00
|
|
|
|
|
|
|
if kill:
|
|
|
|
sleep(randint(0, 5))
|
2018-12-03 14:41:34 +03:00
|
|
|
logger.debug('django-clickhouse: test killing: %d' % p_sync.pid)
|
2018-11-29 17:10:41 +03:00
|
|
|
p_sync.kill()
|
|
|
|
else:
|
|
|
|
p_sync.wait()
|
2018-11-20 15:24:15 +03:00
|
|
|
|
|
|
|
def test_kills(self):
|
2018-11-29 13:19:25 +03:00
|
|
|
test_script = os.path.join(os.path.dirname(__file__), 'kill_test_sub_process.py')
|
|
|
|
p_create = Popen(['python3', test_script, 'create', '--test-time', str(self.TEST_TIME)])
|
2018-12-03 14:41:34 +03:00
|
|
|
|
|
|
|
# Updates must be slower than inserts, or they will do nothing
|
|
|
|
p_update = Popen(['python3', test_script, 'update', '--test-time', str(self.TEST_TIME), '--batch-size', '500'])
|
2018-11-29 13:19:25 +03:00
|
|
|
|
|
|
|
start = now()
|
|
|
|
while (now() - start).total_seconds() < self.TEST_TIME:
|
|
|
|
self.sync_iteration()
|
|
|
|
|
|
|
|
p_create.wait()
|
2018-11-29 17:15:27 +03:00
|
|
|
p_update.wait()
|
2018-11-29 13:19:25 +03:00
|
|
|
|
|
|
|
self._check_data()
|