cqle: introduce 'date' and 'time' column types

PYTHON-245
This commit is contained in:
Adam Holmberg
2015-05-19 16:27:48 -05:00
parent ccd7e460e2
commit b43a947d4a
4 changed files with 64 additions and 41 deletions

View File

@@ -19,9 +19,8 @@ import re
import six import six
import warnings import warnings
from cassandra.cqltypes import DateType from cassandra import util
from cassandra.encoder import cql_quote from cassandra.cqltypes import DateType, SimpleDateType
from cassandra.cqlengine import ValidationError from cassandra.cqlengine import ValidationError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -361,6 +360,10 @@ class Integer(Column):
class TinyInt(Integer): class TinyInt(Integer):
""" """
Stores an 8-bit signed integer value Stores an 8-bit signed integer value
.. versionadded:: 2.6.0
requires C* 2.2+ and protocol v4+
""" """
db_type = 'tinyint' db_type = 'tinyint'
@@ -368,6 +371,10 @@ class TinyInt(Integer):
class SmallInt(Integer): class SmallInt(Integer):
""" """
Stores a 16-bit signed integer value Stores a 16-bit signed integer value
.. versionadded:: 2.6.0
requires C* 2.2+ and protocol v4+
""" """
db_type = 'smallint' db_type = 'smallint'
@@ -466,35 +473,43 @@ class DateTime(Column):
class Date(Column): class Date(Column):
""" """
*Note: this type is overloaded, and will likely be changed or removed to accommodate distinct date type Stores a simple date, with no time-of-day
in a future version*
Stores a date value, with no time-of-day .. versionchanged:: 2.6.0
removed overload of Date and DateTime. DateTime is a drop-in replacement for legacy models
requires C* 2.2+ and protocol v4+
""" """
db_type = 'timestamp' db_type = 'date'
def to_python(self, value):
if value is None:
return
if isinstance(value, datetime):
return value.date()
elif isinstance(value, date):
return value
try:
return datetime.utcfromtimestamp(value).date()
except TypeError:
return datetime.utcfromtimestamp(DateType.deserialize(value)).date()
def to_database(self, value): def to_database(self, value):
value = super(Date, self).to_database(value) value = super(Date, self).to_database(value)
if value is None: if value is None:
return return
if isinstance(value, datetime):
value = value.date()
if not isinstance(value, date):
raise ValidationError("{} '{}' is not a date object".format(self.column_name, repr(value)))
return int((value - date(1970, 1, 1)).total_seconds() * 1000) # need to translate to int version because some dates are not representable in
# string form (datetime limitation)
d = value if isinstance(value, util.Date) else util.Date(value)
return d.days_from_epoch + SimpleDateType.EPOCH_OFFSET_DAYS
class Time(Column):
"""
Stores a timezone-naive time-of-day, with nanosecond precision
.. versionadded:: 2.6.0
requires C* 2.2+ and protocol v4+
"""
db_type = 'time'
def to_database(self, value):
value = super(Time, self).to_database(value)
if value is None:
return
# str(util.Time) yields desired CQL encoding
return value if isinstance(value, util.Time) else util.Time(value)
class UUID(Column): class UUID(Column):
@@ -852,7 +867,7 @@ class UserDefinedType(Column):
def __init__(self, user_type, **kwargs): def __init__(self, user_type, **kwargs):
""" """
:param type user_type: specifies the :class:`~.UserType` model of the column :param type user_type: specifies the :class:`~.cqlengine.usertype.UserType` model of the column
""" """
self.user_type = user_type self.user_type = user_type
self.db_type = "frozen<%s>" % user_type.type_name() self.db_type = "frozen<%s>" % user_type.type_name()

View File

@@ -638,27 +638,29 @@ class SimpleDateType(_CassandraType):
typename = 'date' typename = 'date'
date_format = "%Y-%m-%d" date_format = "%Y-%m-%d"
# Values of the 'date'` type are encoded as 32-bit unsigned integers
# representing a number of days with epoch (January 1st, 1970) at the center of the
# range (2^31).
EPOCH_OFFSET_DAYS = 2 ** 31
@classmethod @classmethod
def validate(cls, val): def validate(cls, val):
if not isinstance(val, util.Date): if not isinstance(val, util.Date):
val = util.Date(val) val = util.Date(val)
return val return val
@staticmethod
def deserialize(byts, protocol_version):
days = uint32_unpack(byts) - SimpleDateType.EPOCH_OFFSET_DAYS
return util.Date(days)
@staticmethod @staticmethod
def serialize(val, protocol_version): def serialize(val, protocol_version):
# Values of the 'date'` type are encoded as 32-bit unsigned integers
# representing a number of days with epoch (January 1st, 1970) at the center of the
# range (2^31).
try: try:
days = val.days_from_epoch days = val.days_from_epoch
except AttributeError: except AttributeError:
days = util.Date(val).days_from_epoch days = util.Date(val).days_from_epoch
return uint32_pack(days + 2 ** 31) return uint32_pack(days + SimpleDateType.EPOCH_OFFSET_DAYS)
@staticmethod
def deserialize(byts, protocol_version):
days = uint32_unpack(byts) - 2 ** 31
return util.Date(days)
class ShortType(_CassandraType): class ShortType(_CassandraType):
@@ -682,6 +684,10 @@ class TimeType(_CassandraType):
val = util.Time(val) val = util.Time(val)
return val return val
@staticmethod
def deserialize(byts, protocol_version):
return util.Time(int64_unpack(byts))
@staticmethod @staticmethod
def serialize(val, protocol_version): def serialize(val, protocol_version):
try: try:
@@ -690,10 +696,6 @@ class TimeType(_CassandraType):
nano = util.Time(val).nanosecond_time nano = util.Time(val).nanosecond_time
return int64_pack(nano) return int64_pack(nano)
@staticmethod
def deserialize(byts, protocol_version):
return util.Time(int64_unpack(byts))
class UTF8Type(_CassandraType): class UTF8Type(_CassandraType):
typename = 'text' typename = 'text'

View File

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

View File

@@ -52,7 +52,7 @@ Columns of all types are initialized by passing :class:`.Column` attributes to t
.. autoclass:: Counter .. autoclass:: Counter
.. autoclass:: Date(**kwargs) .. autoclass:: Date
.. autoclass:: DateTime(**kwargs) .. autoclass:: DateTime(**kwargs)
@@ -70,10 +70,16 @@ Columns of all types are initialized by passing :class:`.Column` attributes to t
.. autoclass:: Set .. autoclass:: Set
.. autoclass:: SmallInt
.. autoclass:: Text .. autoclass:: Text
.. autoclass:: Time
.. autoclass:: TimeUUID(**kwargs) .. autoclass:: TimeUUID(**kwargs)
.. autoclass:: TinyInt
.. autoclass:: UserDefinedType .. autoclass:: UserDefinedType
.. autoclass:: UUID(**kwargs) .. autoclass:: UUID(**kwargs)