From b52e9a9b34411b67d264d4c0a6d4d2c3f2ef76d9 Mon Sep 17 00:00:00 2001 From: Michael Cyrulnik Date: Thu, 17 Oct 2013 11:17:22 -0400 Subject: [PATCH 1/2] add BigInt column --- cqlengine/columns.py | 4 ++++ cqlengine/tests/columns/test_validation.py | 11 +++++++++++ cqlengine/tests/columns/test_value_io.py | 6 ++++++ docs/topics/columns.rst | 8 +++++++- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/cqlengine/columns.py b/cqlengine/columns.py index 10a5e1b9..8781e62f 100644 --- a/cqlengine/columns.py +++ b/cqlengine/columns.py @@ -252,6 +252,10 @@ class Integer(Column): return self.validate(value) +class BigInt(Integer): + db_type = 'bigint' + + class VarInt(Column): db_type = 'varint' diff --git a/cqlengine/tests/columns/test_validation.py b/cqlengine/tests/columns/test_validation.py index 3c6c6656..b89e4a91 100644 --- a/cqlengine/tests/columns/test_validation.py +++ b/cqlengine/tests/columns/test_validation.py @@ -15,6 +15,7 @@ from cqlengine.columns import Bytes from cqlengine.columns import Ascii from cqlengine.columns import Text from cqlengine.columns import Integer +from cqlengine.columns import BigInt from cqlengine.columns import VarInt from cqlengine.columns import DateTime from cqlengine.columns import Date @@ -180,6 +181,16 @@ class TestInteger(BaseCassEngTestCase): it = self.IntegerTest() it.validate() +class TestBigInt(BaseCassEngTestCase): + class BigIntTest(Model): + test_id = UUID(primary_key=True, default=lambda:uuid4()) + value = BigInt(default=0, required=True) + + def test_default_zero_fields_validate(self): + """ Tests that bigint columns with a default value of 0 validate """ + it = self.BigIntTest() + it.validate() + class TestText(BaseCassEngTestCase): def test_min_length(self): diff --git a/cqlengine/tests/columns/test_value_io.py b/cqlengine/tests/columns/test_value_io.py index 1c822a9e..54d8f5d5 100644 --- a/cqlengine/tests/columns/test_value_io.py +++ b/cqlengine/tests/columns/test_value_io.py @@ -95,6 +95,12 @@ class TestInteger(BaseColumnIOTest): pkey_val = 5 data_val = 6 +class TestBigInt(BaseColumnIOTest): + + column = columns.BigInt + pkey_val = 6 + data_val = pow(2, 63) - 1 + class TestDateTime(BaseColumnIOTest): column = columns.DateTime diff --git a/docs/topics/columns.rst b/docs/topics/columns.rst index 6d04df64..0f71e3c7 100644 --- a/docs/topics/columns.rst +++ b/docs/topics/columns.rst @@ -38,10 +38,16 @@ Columns .. class:: Integer() - Stores an integer value :: + Stores a 32-bit signed integer value :: columns.Integer() +.. class:: BigInt() + + Stores a 64-bit signed long value :: + + columns.BigInt() + .. class:: VarInt() Stores an arbitrary-precision integer :: From 442872f6a861bcc0686c21bb813f04e23ada6fbb Mon Sep 17 00:00:00 2001 From: Kai Lautaportti Date: Fri, 18 Oct 2013 15:40:12 +0100 Subject: [PATCH 2/2] Allow use of timezone aware datetimes in {Max,Min}TimeUUID Convert the given timezone aware datetimes to UTC in a similar manner as the DateTime column type does instead of resulting in an error. --- cqlengine/columns.py | 15 ++----- cqlengine/functions.py | 12 +++-- cqlengine/tests/query/test_queryset.py | 62 ++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/cqlengine/columns.py b/cqlengine/columns.py index 10a5e1b9..dd7b3ba3 100644 --- a/cqlengine/columns.py +++ b/cqlengine/columns.py @@ -328,11 +328,9 @@ class DateTime(Column): else: raise ValidationError("'{}' is not a datetime object".format(value)) epoch = datetime(1970, 1, 1, tzinfo=value.tzinfo) - offset = 0 - if epoch.tzinfo: - offset_delta = epoch.tzinfo.utcoffset(epoch) - offset = offset_delta.days*24*3600 + offset_delta.seconds - return long(((value - epoch).total_seconds() - offset) * 1000) + offset = epoch.tzinfo.utcoffset(epoch).total_seconds() if epoch.tzinfo else 0 + + return long(((value - epoch).total_seconds() - offset) * 1000) class Date(Column): @@ -402,12 +400,7 @@ class TimeUUID(UUID): global _last_timestamp epoch = datetime(1970, 1, 1, tzinfo=dt.tzinfo) - - offset = 0 - if epoch.tzinfo: - offset_delta = epoch.tzinfo.utcoffset(epoch) - offset = offset_delta.days*24*3600 + offset_delta.seconds - + offset = epoch.tzinfo.utcoffset(epoch).total_seconds() if epoch.tzinfo else 0 timestamp = (dt - epoch).total_seconds() - offset node = None diff --git a/cqlengine/functions.py b/cqlengine/functions.py index eee1fcab..13661845 100644 --- a/cqlengine/functions.py +++ b/cqlengine/functions.py @@ -54,8 +54,10 @@ class MinTimeUUID(BaseQueryFunction): super(MinTimeUUID, self).__init__(value) def get_value(self): - epoch = datetime(1970, 1, 1) - return long((self.value - epoch).total_seconds() * 1000) + epoch = datetime(1970, 1, 1, tzinfo=self.value.tzinfo) + offset = epoch.tzinfo.utcoffset(epoch).total_seconds() if epoch.tzinfo else 0 + + return long(((self.value - epoch).total_seconds() - offset) * 1000) def get_dict(self, column): return {self.identifier: self.get_value()} @@ -79,8 +81,10 @@ class MaxTimeUUID(BaseQueryFunction): super(MaxTimeUUID, self).__init__(value) def get_value(self): - epoch = datetime(1970, 1, 1) - return long((self.value - epoch).total_seconds() * 1000) + epoch = datetime(1970, 1, 1, tzinfo=self.value.tzinfo) + offset = epoch.tzinfo.utcoffset(epoch).total_seconds() if epoch.tzinfo else 0 + + return long(((self.value - epoch).total_seconds() - offset) * 1000) def get_dict(self, column): return {self.identifier: self.get_value()} diff --git a/cqlengine/tests/query/test_queryset.py b/cqlengine/tests/query/test_queryset.py index 117c87c1..9004a28b 100644 --- a/cqlengine/tests/query/test_queryset.py +++ b/cqlengine/tests/query/test_queryset.py @@ -11,6 +11,25 @@ from cqlengine.management import delete_table from cqlengine.models import Model from cqlengine import columns from cqlengine import query +from datetime import timedelta +from datetime import tzinfo + + +class TzOffset(tzinfo): + """Minimal implementation of a timezone offset to help testing with timezone + aware datetimes. + """ + def __init__(self, offset): + self._offset = timedelta(hours=offset) + + def utcoffset(self, dt): + return self._offset + + def tzname(self, dt): + return 'TzOffset: {}'.format(self._offset.hours) + + def dst(self, dt): + return timedelta(0) class TestModel(Model): test_id = columns.Integer(primary_key=True) @@ -515,6 +534,49 @@ class TestMinMaxTimeUUIDFunctions(BaseCassEngTestCase): super(TestMinMaxTimeUUIDFunctions, cls).tearDownClass() delete_table(TimeUUIDQueryModel) + def test_tzaware_datetime_support(self): + """Test that using timezone aware datetime instances works with the + MinTimeUUID/MaxTimeUUID functions. + """ + pk = uuid4() + midpoint_utc = datetime.utcnow().replace(tzinfo=TzOffset(0)) + midpoint_helsinki = midpoint_utc.astimezone(TzOffset(3)) + + # Assert pre-condition that we have the same logical point in time + assert midpoint_utc.utctimetuple() == midpoint_helsinki.utctimetuple() + assert midpoint_utc.timetuple() != midpoint_helsinki.timetuple() + + TimeUUIDQueryModel.create( + partition=pk, + time=columns.TimeUUID.from_datetime(midpoint_utc - timedelta(minutes=1)), + data='1') + + TimeUUIDQueryModel.create( + partition=pk, + time=columns.TimeUUID.from_datetime(midpoint_utc), + data='2') + + TimeUUIDQueryModel.create( + partition=pk, + time=columns.TimeUUID.from_datetime(midpoint_utc + timedelta(minutes=1)), + data='3') + + assert ['1', '2'] == [o.data for o in TimeUUIDQueryModel.filter( + TimeUUIDQueryModel.partition == pk, + TimeUUIDQueryModel.time <= functions.MaxTimeUUID(midpoint_utc))] + + assert ['1', '2'] == [o.data for o in TimeUUIDQueryModel.filter( + TimeUUIDQueryModel.partition == pk, + TimeUUIDQueryModel.time <= functions.MaxTimeUUID(midpoint_helsinki))] + + assert ['2', '3'] == [o.data for o in TimeUUIDQueryModel.filter( + TimeUUIDQueryModel.partition == pk, + TimeUUIDQueryModel.time >= functions.MinTimeUUID(midpoint_utc))] + + assert ['2', '3'] == [o.data for o in TimeUUIDQueryModel.filter( + TimeUUIDQueryModel.partition == pk, + TimeUUIDQueryModel.time >= functions.MinTimeUUID(midpoint_helsinki))] + def test_success_case(self): """ Test that the min and max time uuid functions work as expected """ pk = uuid4()