Skip to content

Commit

Permalink
Date32 support
Browse files Browse the repository at this point in the history
  • Loading branch information
xzkostyan committed Sep 21, 2021
1 parent 5884cab commit 500e91f
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Features

* Float32/64
* [U]Int8/16/32/64/128/256
* Date/DateTime('timezone')/DateTime64('timezone')
* Date/Date32/DateTime('timezone')/DateTime64('timezone')
* String/FixedString(N)
* Enum8/16
* Array(T)
Expand Down
36 changes: 28 additions & 8 deletions clickhouse_driver/columns/datecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@


epoch_start = date(1970, 1, 1)
epoch_end = date(2149, 6, 6)

epoch_start_date32 = date(1925, 1, 1)
epoch_end_date32 = date(2283, 11, 11)


class DateColumn(FormatColumn):
ch_type = 'Date'
py_types = (date, )
format = 'H'

epoch_start = epoch_start
epoch_end = date(2105, 12, 31)
min_value = epoch_start
max_value = epoch_end

date_lut = {x: epoch_start + timedelta(x) for x in range(65535)}
date_lut_days = (epoch_end - epoch_start).days + 1
date_lut = {x: epoch_start + timedelta(x) for x in range(date_lut_days)}
date_lut_reverse = {value: key for key, value in date_lut.items()}

def before_write_items(self, items, nulls_map=None):
null_value = self.null_value

date_lut_reverse = self.date_lut_reverse
epoch_start = self.epoch_start
epoch_end = self.epoch_end
min_value = self.min_value
max_value = self.max_value

for i, item in enumerate(items):
if nulls_map and nulls_map[i]:
Expand All @@ -32,10 +37,10 @@ def before_write_items(self, items, nulls_map=None):
if type(item) != date:
item = date(item.year, item.month, item.day)

if item > epoch_end or item < epoch_start:
items[i] = 0
else:
if min_value <= item <= max_value:
items[i] = date_lut_reverse[item]
else:
items[i] = 0

def after_read_items(self, items, nulls_map=None):
date_lut = self.date_lut
Expand All @@ -47,3 +52,18 @@ def after_read_items(self, items, nulls_map=None):
(None if is_null else date_lut[items[i]])
for i, is_null in enumerate(nulls_map)
)


class Date32Column(DateColumn):
ch_type = 'Date32'
format = 'i'

min_value = epoch_start_date32
max_value = epoch_end_date32

date_lut_days = (epoch_end_date32 - epoch_start).days + 1
date_lut = {
x: epoch_start + timedelta(x)
for x in range((epoch_start_date32 - epoch_start).days, date_lut_days)
}
date_lut_reverse = {value: key for key, value in date_lut.items()}
4 changes: 2 additions & 2 deletions clickhouse_driver/columns/service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .. import errors
from .arraycolumn import create_array_column
from .datecolumn import DateColumn
from .datecolumn import DateColumn, Date32Column
from .datetimecolumn import create_datetime_column
from .decimalcolumn import create_decimal_column
from . import exceptions as column_exceptions
Expand Down Expand Up @@ -31,7 +31,7 @@


column_by_type = {c.ch_type: c for c in [
DateColumn, Float32Column, Float64Column,
DateColumn, Date32Column, Float32Column, Float64Column,
Int8Column, Int16Column, Int32Column, Int64Column,
Int128Column, UInt128Column, Int256Column, UInt256Column,
UInt8Column, UInt16Column, UInt32Column, UInt64Column,
Expand Down
9 changes: 4 additions & 5 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ INSERT types: :class:`float`, :class:`int`, :class:`long`.
SELECT type: :class:`float`.


Date
----
Date/Date32
-----------

*Date32 support is new in version 0.2.2.*

INSERT types: :class:`~datetime.date`, :class:`~datetime.datetime`.

SELECT type: :class:`~datetime.date`.

Only values after the beginning of the epoch (1970-01-01) are supported.

DateTime('timezone')/DateTime64('timezone')
-------------------------------------------
Expand All @@ -51,8 +52,6 @@ You can cast DateTime column to integers if you are facing performance issues wh

Due to Python's current limitations minimal DateTime64 resolution is one microsecond.

Only values after the beginning of the epoch (1970-01-01) are supported.


String/FixedString(N)
---------------------
Expand Down
69 changes: 61 additions & 8 deletions tests/columns/test_date.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,70 @@ def test_insert_datetime_to_date(self):
inserted = self.emit_cli(query)
self.assertEqual(inserted, '2015-06-06\n')

def test_wrong_datetime_insert(self):
def test_wrong_date_insert(self):
with self.create_table('a Date'):
wrongTime = date(5555, 1, 1)
nullTime = date(1, 1, 1)
self.client.execute(
'INSERT INTO test (a) VALUES', [(wrongTime, ), (nullTime, )]
)
data = [
(date(5555, 1, 1), ),
(date(1, 1, 1), ),
(date(2149, 6, 7), )
]
self.client.execute('INSERT INTO test (a) VALUES', data)
query = 'SELECT * FROM test'
inserted = self.emit_cli(query)
expected = (
'1970-01-01\n1970-01-01\n' if self.server_version > (20, 7, 2)
else '0000-00-00\n0000-00-00\n'
3*'1970-01-01\n' if self.server_version > (20, 7, 2)
else 3*'0000-00-00\n'
)
self.assertEqual(inserted, expected)

def test_boundaries(self):
extended_date = self.server_version > (21, 4)

with self.create_table('a Date'):
data = [
(date(1970, 1, 1), ),
((date(2149, 6, 6) if extended_date else date(2106, 2, 7)), )
]
self.client.execute('INSERT INTO test (a) VALUES', data)

query = 'SELECT * FROM test'
inserted = self.emit_cli(query)
if extended_date:
expected = '1970-01-01\n2149-06-06\n'
else:
if self.server_version > (20, 7, 2):
expected = '1970-01-01\n2106-02-07\n'
else:
expected = '0000-00-00\n2106-02-07\n'
self.assertEqual(inserted, expected)

inserted = self.client.execute(query)
self.assertEqual(inserted, data)


class Date32TestCase(BaseTestCase):
required_server_version = (21, 9)

def test_wrong_date_insert(self):
with self.create_table('a Date32'):
data = [
(date(5555, 1, 1), ),
(date(1, 1, 1), ),
(date(2284, 1, 1), )
]
self.client.execute('INSERT INTO test (a) VALUES', data)
query = 'SELECT * FROM test'
inserted = self.emit_cli(query)
self.assertEqual(inserted, '1970-01-01\n1970-01-01\n1970-01-01\n')

def test_boundaries(self):
with self.create_table('a Date32'):
data = [(date(1925, 1, 1), ), (date(2283, 11, 11), )]
self.client.execute('INSERT INTO test (a) VALUES', data)

query = 'SELECT * FROM test'
inserted = self.emit_cli(query)
self.assertEqual(inserted, '1925-01-01\n2283-11-11\n')

inserted = self.client.execute(query)
self.assertEqual(inserted, data)

0 comments on commit 500e91f

Please sign in to comment.