2017-06-16 20:37:37 +03:00
|
|
|
import unittest
|
|
|
|
import pytz
|
|
|
|
|
|
|
|
from infi.clickhouse_orm.database import Database
|
|
|
|
from infi.clickhouse_orm.models import Model
|
|
|
|
from infi.clickhouse_orm.fields import *
|
|
|
|
from infi.clickhouse_orm.engines import *
|
2019-06-13 05:07:56 +03:00
|
|
|
from infi.clickhouse_orm.utils import comma_join
|
2017-06-16 20:37:37 +03:00
|
|
|
|
|
|
|
from datetime import date, datetime
|
|
|
|
|
|
|
|
|
|
|
|
class NullableFieldsTest(unittest.TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2019-06-13 08:12:56 +03:00
|
|
|
self.database = Database('test-db', log_statements=True)
|
2017-06-16 20:37:37 +03:00
|
|
|
self.database.create_table(ModelWithNullable)
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
self.database.drop_database()
|
|
|
|
|
|
|
|
def test_nullable_datetime_field(self):
|
|
|
|
f = NullableField(DateTimeField())
|
|
|
|
epoch = datetime(1970, 1, 1, tzinfo=pytz.utc)
|
|
|
|
# Valid values
|
|
|
|
for value in (date(1970, 1, 1),
|
|
|
|
datetime(1970, 1, 1),
|
|
|
|
epoch,
|
|
|
|
epoch.astimezone(pytz.timezone('US/Eastern')),
|
|
|
|
epoch.astimezone(pytz.timezone('Asia/Jerusalem')),
|
|
|
|
'1970-01-01 00:00:00',
|
|
|
|
'1970-01-17 00:00:17',
|
|
|
|
'0000-00-00 00:00:00',
|
|
|
|
0,
|
|
|
|
'\\N'):
|
|
|
|
dt = f.to_python(value, pytz.utc)
|
|
|
|
if value == '\\N':
|
|
|
|
self.assertIsNone(dt)
|
|
|
|
else:
|
2020-06-12 11:29:47 +03:00
|
|
|
self.assertTrue(dt.tzinfo)
|
2017-06-16 20:37:37 +03:00
|
|
|
# Verify that conversion to and from db string does not change value
|
|
|
|
dt2 = f.to_python(f.to_db_string(dt, quote=False), pytz.utc)
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(dt, dt2)
|
2017-06-16 20:37:37 +03:00
|
|
|
# Invalid values
|
|
|
|
for value in ('nope', '21/7/1999', 0.5):
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
f.to_python(value, pytz.utc)
|
|
|
|
|
|
|
|
def test_nullable_uint8_field(self):
|
|
|
|
f = NullableField(UInt8Field())
|
|
|
|
# Valid values
|
|
|
|
for value in (17, '17', 17.0, '\\N'):
|
|
|
|
python_value = f.to_python(value, pytz.utc)
|
|
|
|
if value == '\\N':
|
|
|
|
self.assertIsNone(python_value)
|
|
|
|
self.assertEqual(value, f.to_db_string(python_value))
|
|
|
|
else:
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(python_value, 17)
|
2017-06-16 20:37:37 +03:00
|
|
|
|
|
|
|
# Invalid values
|
|
|
|
for value in ('nope', date.today()):
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
f.to_python(value, pytz.utc)
|
|
|
|
|
|
|
|
def test_nullable_string_field(self):
|
|
|
|
f = NullableField(StringField())
|
|
|
|
# Valid values
|
|
|
|
for value in ('\\\\N', 'N', 'some text', '\\N'):
|
|
|
|
python_value = f.to_python(value, pytz.utc)
|
|
|
|
if value == '\\N':
|
|
|
|
self.assertIsNone(python_value)
|
|
|
|
self.assertEqual(value, f.to_db_string(python_value))
|
|
|
|
else:
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(python_value, value)
|
2017-06-16 20:37:37 +03:00
|
|
|
|
2018-07-05 16:32:11 +03:00
|
|
|
def test_isinstance(self):
|
|
|
|
for field in (StringField, UInt8Field, Float32Field, DateTimeField):
|
|
|
|
f = NullableField(field())
|
|
|
|
self.assertTrue(f.isinstance(field))
|
|
|
|
self.assertTrue(f.isinstance(NullableField))
|
|
|
|
for field in (Int8Field, Int16Field, Int32Field, Int64Field, UInt8Field, UInt16Field, UInt32Field, UInt64Field):
|
|
|
|
f = NullableField(field())
|
|
|
|
self.assertTrue(f.isinstance(BaseIntField))
|
|
|
|
for field in (Float32Field, Float64Field):
|
|
|
|
f = NullableField(field())
|
|
|
|
self.assertTrue(f.isinstance(BaseFloatField))
|
|
|
|
f = NullableField(NullableField(UInt32Field()))
|
|
|
|
self.assertTrue(f.isinstance(BaseIntField))
|
|
|
|
self.assertTrue(f.isinstance(NullableField))
|
|
|
|
self.assertFalse(f.isinstance(BaseFloatField))
|
|
|
|
|
2017-06-16 20:37:37 +03:00
|
|
|
def _insert_sample_data(self):
|
|
|
|
dt = date(1970, 1, 1)
|
|
|
|
self.database.insert([
|
2017-08-20 09:30:40 +03:00
|
|
|
ModelWithNullable(date_field='2016-08-30', null_str='', null_int=42, null_date=dt),
|
|
|
|
ModelWithNullable(date_field='2016-08-30', null_str='nothing', null_int=None, null_date=None),
|
|
|
|
ModelWithNullable(date_field='2016-08-31', null_str=None, null_int=42, null_date=dt),
|
2019-06-13 05:07:56 +03:00
|
|
|
ModelWithNullable(date_field='2016-08-31', null_str=None, null_int=None, null_date=None, null_default=None)
|
2017-06-16 20:37:37 +03:00
|
|
|
])
|
|
|
|
|
|
|
|
def _assert_sample_data(self, results):
|
2019-06-13 05:07:56 +03:00
|
|
|
for r in results:
|
|
|
|
print(r.to_dict())
|
2017-06-16 20:37:37 +03:00
|
|
|
dt = date(1970, 1, 1)
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(len(results), 4)
|
2017-06-16 20:37:37 +03:00
|
|
|
self.assertIsNone(results[0].null_str)
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(results[0].null_int, 42)
|
2019-06-13 05:07:56 +03:00
|
|
|
self.assertEqual(results[0].null_default, 7)
|
|
|
|
self.assertEqual(results[0].null_alias, 21)
|
|
|
|
self.assertEqual(results[0].null_materialized, 420)
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(results[0].null_date, dt)
|
2017-06-16 20:37:37 +03:00
|
|
|
self.assertIsNone(results[1].null_date)
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(results[1].null_str, 'nothing')
|
2017-06-16 20:37:37 +03:00
|
|
|
self.assertIsNone(results[1].null_date)
|
|
|
|
self.assertIsNone(results[2].null_str)
|
2018-08-19 18:22:22 +03:00
|
|
|
self.assertEqual(results[2].null_date, dt)
|
|
|
|
self.assertEqual(results[2].null_int, 42)
|
2019-06-13 05:07:56 +03:00
|
|
|
self.assertEqual(results[2].null_default, 7)
|
|
|
|
self.assertEqual(results[2].null_alias, 21)
|
|
|
|
self.assertEqual(results[0].null_materialized, 420)
|
2017-06-16 20:37:37 +03:00
|
|
|
self.assertIsNone(results[3].null_int)
|
2019-06-13 05:07:56 +03:00
|
|
|
self.assertIsNone(results[3].null_default)
|
|
|
|
self.assertIsNone(results[3].null_alias)
|
|
|
|
self.assertIsNone(results[3].null_materialized)
|
2017-06-16 20:37:37 +03:00
|
|
|
self.assertIsNone(results[3].null_str)
|
|
|
|
self.assertIsNone(results[3].null_date)
|
|
|
|
|
|
|
|
def test_insert_and_select(self):
|
|
|
|
self._insert_sample_data()
|
2019-06-13 05:07:56 +03:00
|
|
|
fields = comma_join(ModelWithNullable.fields().keys())
|
|
|
|
query = 'SELECT %s from $table ORDER BY date_field' % fields
|
2017-06-16 20:37:37 +03:00
|
|
|
results = list(self.database.select(query, ModelWithNullable))
|
|
|
|
self._assert_sample_data(results)
|
|
|
|
|
|
|
|
def test_ad_hoc_model(self):
|
|
|
|
self._insert_sample_data()
|
2019-06-13 05:07:56 +03:00
|
|
|
fields = comma_join(ModelWithNullable.fields().keys())
|
|
|
|
query = 'SELECT %s from $db.modelwithnullable ORDER BY date_field' % fields
|
2017-06-16 20:37:37 +03:00
|
|
|
results = list(self.database.select(query))
|
|
|
|
self._assert_sample_data(results)
|
|
|
|
|
|
|
|
|
|
|
|
class ModelWithNullable(Model):
|
|
|
|
|
|
|
|
date_field = DateField()
|
|
|
|
null_str = NullableField(StringField(), extra_null_values={''})
|
|
|
|
null_int = NullableField(Int32Field())
|
|
|
|
null_date = NullableField(DateField())
|
2019-06-13 05:07:56 +03:00
|
|
|
null_default = NullableField(Int32Field(), default=7)
|
|
|
|
null_alias = NullableField(Int32Field(), alias='null_int/2')
|
|
|
|
null_materialized = NullableField(Int32Field(), alias='null_int*10')
|
2017-06-16 20:37:37 +03:00
|
|
|
|
|
|
|
engine = MergeTree('date_field', ('date_field',))
|