Skip to content

Commit b43a947

Browse files
committed
cqle: introduce 'date' and 'time' column types
PYTHON-245
1 parent ccd7e46 commit b43a947

4 files changed

Lines changed: 63 additions & 40 deletions

File tree

cassandra/cqlengine/columns.py

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
import six
2020
import warnings
2121

22-
from cassandra.cqltypes import DateType
23-
from cassandra.encoder import cql_quote
24-
22+
from cassandra import util
23+
from cassandra.cqltypes import DateType, SimpleDateType
2524
from cassandra.cqlengine import ValidationError
2625

2726
log = logging.getLogger(__name__)
@@ -361,13 +360,21 @@ def to_database(self, value):
361360
class TinyInt(Integer):
362361
"""
363362
Stores an 8-bit signed integer value
363+
364+
.. versionadded:: 2.6.0
365+
366+
requires C* 2.2+ and protocol v4+
364367
"""
365368
db_type = 'tinyint'
366369

367370

368371
class SmallInt(Integer):
369372
"""
370373
Stores a 16-bit signed integer value
374+
375+
.. versionadded:: 2.6.0
376+
377+
requires C* 2.2+ and protocol v4+
371378
"""
372379
db_type = 'smallint'
373380

@@ -466,35 +473,43 @@ def to_database(self, value):
466473

467474
class Date(Column):
468475
"""
469-
*Note: this type is overloaded, and will likely be changed or removed to accommodate distinct date type
470-
in a future version*
476+
Stores a simple date, with no time-of-day
477+
478+
.. versionchanged:: 2.6.0
479+
480+
removed overload of Date and DateTime. DateTime is a drop-in replacement for legacy models
471481
472-
Stores a date value, with no time-of-day
482+
requires C* 2.2+ and protocol v4+
473483
"""
474-
db_type = 'timestamp'
484+
db_type = 'date'
475485

476-
def to_python(self, value):
486+
def to_database(self, value):
487+
value = super(Date, self).to_database(value)
477488
if value is None:
478489
return
479-
if isinstance(value, datetime):
480-
return value.date()
481-
elif isinstance(value, date):
482-
return value
483-
try:
484-
return datetime.utcfromtimestamp(value).date()
485-
except TypeError:
486-
return datetime.utcfromtimestamp(DateType.deserialize(value)).date()
490+
491+
# need to translate to int version because some dates are not representable in
492+
# string form (datetime limitation)
493+
d = value if isinstance(value, util.Date) else util.Date(value)
494+
return d.days_from_epoch + SimpleDateType.EPOCH_OFFSET_DAYS
495+
496+
497+
class Time(Column):
498+
"""
499+
Stores a timezone-naive time-of-day, with nanosecond precision
500+
501+
.. versionadded:: 2.6.0
502+
503+
requires C* 2.2+ and protocol v4+
504+
"""
505+
db_type = 'time'
487506

488507
def to_database(self, value):
489-
value = super(Date, self).to_database(value)
508+
value = super(Time, self).to_database(value)
490509
if value is None:
491510
return
492-
if isinstance(value, datetime):
493-
value = value.date()
494-
if not isinstance(value, date):
495-
raise ValidationError("{} '{}' is not a date object".format(self.column_name, repr(value)))
496-
497-
return int((value - date(1970, 1, 1)).total_seconds() * 1000)
511+
# str(util.Time) yields desired CQL encoding
512+
return value if isinstance(value, util.Time) else util.Time(value)
498513

499514

500515
class UUID(Column):
@@ -852,7 +867,7 @@ class UserDefinedType(Column):
852867

853868
def __init__(self, user_type, **kwargs):
854869
"""
855-
:param type user_type: specifies the :class:`~.UserType` model of the column
870+
:param type user_type: specifies the :class:`~.cqlengine.usertype.UserType` model of the column
856871
"""
857872
self.user_type = user_type
858873
self.db_type = "frozen<%s>" % user_type.type_name()

cassandra/cqltypes.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -638,27 +638,29 @@ class SimpleDateType(_CassandraType):
638638
typename = 'date'
639639
date_format = "%Y-%m-%d"
640640

641+
# Values of the 'date'` type are encoded as 32-bit unsigned integers
642+
# representing a number of days with epoch (January 1st, 1970) at the center of the
643+
# range (2^31).
644+
EPOCH_OFFSET_DAYS = 2 ** 31
645+
641646
@classmethod
642647
def validate(cls, val):
643648
if not isinstance(val, util.Date):
644649
val = util.Date(val)
645650
return val
646651

652+
@staticmethod
653+
def deserialize(byts, protocol_version):
654+
days = uint32_unpack(byts) - SimpleDateType.EPOCH_OFFSET_DAYS
655+
return util.Date(days)
656+
647657
@staticmethod
648658
def serialize(val, protocol_version):
649-
# Values of the 'date'` type are encoded as 32-bit unsigned integers
650-
# representing a number of days with epoch (January 1st, 1970) at the center of the
651-
# range (2^31).
652659
try:
653660
days = val.days_from_epoch
654661
except AttributeError:
655662
days = util.Date(val).days_from_epoch
656-
return uint32_pack(days + 2 ** 31)
657-
658-
@staticmethod
659-
def deserialize(byts, protocol_version):
660-
days = uint32_unpack(byts) - 2 ** 31
661-
return util.Date(days)
663+
return uint32_pack(days + SimpleDateType.EPOCH_OFFSET_DAYS)
662664

663665

664666
class ShortType(_CassandraType):
@@ -682,6 +684,10 @@ def validate(cls, val):
682684
val = util.Time(val)
683685
return val
684686

687+
@staticmethod
688+
def deserialize(byts, protocol_version):
689+
return util.Time(int64_unpack(byts))
690+
685691
@staticmethod
686692
def serialize(val, protocol_version):
687693
try:
@@ -690,10 +696,6 @@ def serialize(val, protocol_version):
690696
nano = util.Time(val).nanosecond_time
691697
return int64_pack(nano)
692698

693-
@staticmethod
694-
def deserialize(byts, protocol_version):
695-
return util.Time(int64_unpack(byts))
696-
697699

698700
class UTF8Type(_CassandraType):
699701
typename = 'text'

cassandra/util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,7 @@ def __str__(self):
917917

918918
class Date(object):
919919
'''
920-
Idealized naive date: year, month, day
920+
Idealized date: year, month, day
921921
922922
Offers wider year range than datetime.date. For Dates that cannot be represented
923923
as a datetime.date (because datetime.MINYEAR, datetime.MAXYEAR), this type falls back
@@ -997,5 +997,5 @@ def __str__(self):
997997
dt = datetime_from_timestamp(self.seconds)
998998
return "%04d-%02d-%02d" % (dt.year, dt.month, dt.day)
999999
except:
1000-
# If we overflow datetime.[MIN|M
1000+
# If we overflow datetime.[MIN|MAX]
10011001
return str(self.days_from_epoch)

docs/api/cassandra/cqlengine/columns.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Columns of all types are initialized by passing :class:`.Column` attributes to t
5252

5353
.. autoclass:: Counter
5454

55-
.. autoclass:: Date(**kwargs)
55+
.. autoclass:: Date
5656

5757
.. autoclass:: DateTime(**kwargs)
5858

@@ -70,10 +70,16 @@ Columns of all types are initialized by passing :class:`.Column` attributes to t
7070

7171
.. autoclass:: Set
7272

73+
.. autoclass:: SmallInt
74+
7375
.. autoclass:: Text
7476

77+
.. autoclass:: Time
78+
7579
.. autoclass:: TimeUUID(**kwargs)
7680

81+
.. autoclass:: TinyInt
82+
7783
.. autoclass:: UserDefinedType
7884

7985
.. autoclass:: UUID(**kwargs)

0 commit comments

Comments
 (0)