From 14d1f98b071cb20e0b7ab3db432b98b53dd48bab Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Mon, 21 Dec 2015 14:13:27 -0800 Subject: [PATCH] Implementing Bigtable ColumnRangeFilter. --- gcloud/bigtable/row.py | 72 +++++++++++++++++++++ gcloud/bigtable/test_row.py | 122 ++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) diff --git a/gcloud/bigtable/row.py b/gcloud/bigtable/row.py index 95af344049acf..204498ecae02d 100644 --- a/gcloud/bigtable/row.py +++ b/gcloud/bigtable/row.py @@ -227,6 +227,78 @@ def to_pb(self): return data_pb2.RowFilter(column_qualifier_regex_filter=self.regex) +class ColumnRangeFilter(RowFilter): + """A row filter to restrict to a range of columns. + + Both the start and end column can be included or excluded in the range. + By default, we include them both, but this can be changed with optional + flags. + + :type column_family_id: str + :param column_family_id: The column family that contains the columns. Must + be of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``. + + :type start_column: bytes + :param start_column: The start of the range of columns. If no value is used, + the backend applies no upper bound to the values. + + :type end_column: bytes + :param end_column: The end of the range of columns. If no value is used, + the backend applies no upper bound to the values. + + :type inclusive_start: bool + :param inclusive_start: Boolean indicating if the start column should be + included in the range (or excluded). + + :type inclusive_end: bool + :param inclusive_end: Boolean indicating if the end column should be + included in the range (or excluded). + """ + + def __init__(self, column_family_id, start_column=None, end_column=None, + inclusive_start=True, inclusive_end=True): + self.column_family_id = column_family_id + self.start_column = start_column + self.end_column = end_column + self.inclusive_start = inclusive_start + self.inclusive_end = inclusive_end + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return (other.column_family_id == self.column_family_id and + other.start_column == self.start_column and + other.end_column == self.end_column and + other.inclusive_start == self.inclusive_start and + other.inclusive_end == self.inclusive_end) + + def to_pb(self): + """Converts the row filter to a protobuf. + + First converts to a :class:`.data_pb2.ColumnRange` and then uses it + in the ``column_range_filter`` field. + + :rtype: :class:`.data_pb2.RowFilter` + :returns: The converted current object. + """ + column_range_kwargs = {'family_name': self.column_family_id} + if self.start_column is not None: + if self.inclusive_start: + key = 'start_qualifier_inclusive' + else: + key = 'start_qualifier_exclusive' + column_range_kwargs[key] = _to_bytes(self.start_column) + if self.end_column is not None: + if self.inclusive_end: + key = 'end_qualifier_inclusive' + else: + key = 'end_qualifier_exclusive' + column_range_kwargs[key] = _to_bytes(self.end_column) + + column_range = data_pb2.ColumnRange(**column_range_kwargs) + return data_pb2.RowFilter(column_range_filter=column_range) + + class ValueRegexFilter(_RegexFilter): """Row filter for a value regular expression. diff --git a/gcloud/bigtable/test_row.py b/gcloud/bigtable/test_row.py index c0e44d18c46b9..0dda41c3a815c 100644 --- a/gcloud/bigtable/test_row.py +++ b/gcloud/bigtable/test_row.py @@ -230,6 +230,128 @@ def test_to_pb(self): self.assertEqual(pb_val, expected_pb) +class TestColumnRangeFilter(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.bigtable.row import ColumnRangeFilter + return ColumnRangeFilter + + def _makeOne(self, *args, **kwargs): + return self._getTargetClass()(*args, **kwargs) + + def test_constructor_defaults(self): + column_family_id = object() + row_filter = self._makeOne(column_family_id) + self.assertTrue(row_filter.column_family_id is column_family_id) + self.assertEqual(row_filter.start_column, None) + self.assertEqual(row_filter.end_column, None) + self.assertTrue(row_filter.inclusive_start) + self.assertTrue(row_filter.inclusive_end) + + def test_constructor_explicit(self): + column_family_id = object() + start_column = object() + end_column = object() + inclusive_start = object() + inclusive_end = object() + row_filter = self._makeOne(column_family_id, start_column=start_column, + end_column=end_column, + inclusive_start=inclusive_start, + inclusive_end=inclusive_end) + self.assertTrue(row_filter.column_family_id is column_family_id) + self.assertTrue(row_filter.start_column is start_column) + self.assertTrue(row_filter.end_column is end_column) + self.assertTrue(row_filter.inclusive_start is inclusive_start) + self.assertTrue(row_filter.inclusive_end is inclusive_end) + + def test___eq__(self): + column_family_id = object() + start_column = object() + end_column = object() + inclusive_start = object() + inclusive_end = object() + row_filter1 = self._makeOne(column_family_id, + start_column=start_column, + end_column=end_column, + inclusive_start=inclusive_start, + inclusive_end=inclusive_end) + row_filter2 = self._makeOne(column_family_id, + start_column=start_column, + end_column=end_column, + inclusive_start=inclusive_start, + inclusive_end=inclusive_end) + self.assertEqual(row_filter1, row_filter2) + + def test___eq__type_differ(self): + column_family_id = object() + row_filter1 = self._makeOne(column_family_id) + row_filter2 = object() + self.assertNotEqual(row_filter1, row_filter2) + + def test_to_pb(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + column_family_id = u'column-family-id' + row_filter = self._makeOne(column_family_id) + col_range_pb = data_pb2.ColumnRange(family_name=column_family_id) + expected_pb = data_pb2.RowFilter(column_range_filter=col_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_inclusive_start(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + column_family_id = u'column-family-id' + column = b'column' + row_filter = self._makeOne(column_family_id, start_column=column) + col_range_pb = data_pb2.ColumnRange( + family_name=column_family_id, + start_qualifier_inclusive=column, + ) + expected_pb = data_pb2.RowFilter(column_range_filter=col_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_exclusive_start(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + column_family_id = u'column-family-id' + column = b'column' + row_filter = self._makeOne(column_family_id, start_column=column, + inclusive_start=False) + col_range_pb = data_pb2.ColumnRange( + family_name=column_family_id, + start_qualifier_exclusive=column, + ) + expected_pb = data_pb2.RowFilter(column_range_filter=col_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_inclusive_end(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + column_family_id = u'column-family-id' + column = b'column' + row_filter = self._makeOne(column_family_id, end_column=column) + col_range_pb = data_pb2.ColumnRange( + family_name=column_family_id, + end_qualifier_inclusive=column, + ) + expected_pb = data_pb2.RowFilter(column_range_filter=col_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_exclusive_end(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + column_family_id = u'column-family-id' + column = b'column' + row_filter = self._makeOne(column_family_id, end_column=column, + inclusive_end=False) + col_range_pb = data_pb2.ColumnRange( + family_name=column_family_id, + end_qualifier_exclusive=column, + ) + expected_pb = data_pb2.RowFilter(column_range_filter=col_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + class TestValueRegexFilter(unittest2.TestCase): def _getTargetClass(self):