# Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import unittest2 class Test_SetDeleteRow(unittest2.TestCase): def _getTargetClass(self): from gcloud.bigtable.row import _SetDeleteRow return _SetDeleteRow def _makeOne(self, *args, **kwargs): return self._getTargetClass()(*args, **kwargs) def test__get_mutations_virtual(self): row = self._makeOne(b'row-key', None) with self.assertRaises(NotImplementedError): row._get_mutations(None) class TestDirectRow(unittest2.TestCase): def _getTargetClass(self): from gcloud.bigtable.row import DirectRow return DirectRow def _makeOne(self, *args, **kwargs): return self._getTargetClass()(*args, **kwargs) def test_constructor(self): row_key = b'row_key' table = object() row = self._makeOne(row_key, table) self.assertEqual(row._row_key, row_key) self.assertTrue(row._table is table) self.assertEqual(row._pb_mutations, []) def test_constructor_with_unicode(self): row_key = u'row_key' row_key_bytes = b'row_key' table = object() row = self._makeOne(row_key, table) self.assertEqual(row._row_key, row_key_bytes) self.assertTrue(row._table is table) def test_constructor_with_non_bytes(self): row_key = object() with self.assertRaises(TypeError): self._makeOne(row_key, None) def test__get_mutations(self): row_key = b'row_key' row = self._makeOne(row_key, None) row._pb_mutations = mutations = object() self.assertTrue(mutations is row._get_mutations(None)) def _set_cell_helper(self, column=None, column_bytes=None, value=b'foobar', timestamp=None, timestamp_micros=-1): import six import struct row_key = b'row_key' column_family_id = u'column_family_id' if column is None: column = b'column' table = object() row = self._makeOne(row_key, table) self.assertEqual(row._pb_mutations, []) row.set_cell(column_family_id, column, value, timestamp=timestamp) if isinstance(value, six.integer_types): value = struct.pack('>q', value) expected_pb = _MutationPB( set_cell=_MutationSetCellPB( family_name=column_family_id, column_qualifier=column_bytes or column, timestamp_micros=timestamp_micros, value=value, ), ) self.assertEqual(row._pb_mutations, [expected_pb]) def test_set_cell(self): self._set_cell_helper() def test_set_cell_with_string_column(self): column_bytes = b'column' column_non_bytes = u'column' self._set_cell_helper(column=column_non_bytes, column_bytes=column_bytes) def test_set_cell_with_integer_value(self): value = 1337 self._set_cell_helper(value=value) def test_set_cell_with_non_bytes_value(self): row_key = b'row_key' column = b'column' column_family_id = u'column_family_id' table = object() row = self._makeOne(row_key, table) value = object() # Not bytes with self.assertRaises(TypeError): row.set_cell(column_family_id, column, value) def test_set_cell_with_non_null_timestamp(self): import datetime from gcloud._helpers import _EPOCH microseconds = 898294371 millis_granularity = microseconds - (microseconds % 1000) timestamp = _EPOCH + datetime.timedelta(microseconds=microseconds) self._set_cell_helper(timestamp=timestamp, timestamp_micros=millis_granularity) def test_delete(self): row_key = b'row_key' row = self._makeOne(row_key, object()) self.assertEqual(row._pb_mutations, []) row.delete() expected_pb = _MutationPB( delete_from_row=_MutationDeleteFromRowPB(), ) self.assertEqual(row._pb_mutations, [expected_pb]) def test_delete_cell(self): klass = self._getTargetClass() class MockRow(klass): def __init__(self, *args, **kwargs): super(MockRow, self).__init__(*args, **kwargs) self._args = [] self._kwargs = [] # Replace the called method with one that logs arguments. def _delete_cells(self, *args, **kwargs): self._args.append(args) self._kwargs.append(kwargs) row_key = b'row_key' column = b'column' column_family_id = u'column_family_id' table = object() mock_row = MockRow(row_key, table) # Make sure no values are set before calling the method. self.assertEqual(mock_row._pb_mutations, []) self.assertEqual(mock_row._args, []) self.assertEqual(mock_row._kwargs, []) # Actually make the request against the mock class. time_range = object() mock_row.delete_cell(column_family_id, column, time_range=time_range) self.assertEqual(mock_row._pb_mutations, []) self.assertEqual(mock_row._args, [(column_family_id, [column])]) self.assertEqual(mock_row._kwargs, [{ 'state': None, 'time_range': time_range, }]) def test_delete_cells_non_iterable(self): row_key = b'row_key' column_family_id = u'column_family_id' table = object() row = self._makeOne(row_key, table) columns = object() # Not iterable with self.assertRaises(TypeError): row.delete_cells(column_family_id, columns) def test_delete_cells_all_columns(self): row_key = b'row_key' column_family_id = u'column_family_id' table = object() row = self._makeOne(row_key, table) klass = self._getTargetClass() self.assertEqual(row._pb_mutations, []) row.delete_cells(column_family_id, klass.ALL_COLUMNS) expected_pb = _MutationPB( delete_from_family=_MutationDeleteFromFamilyPB( family_name=column_family_id, ), ) self.assertEqual(row._pb_mutations, [expected_pb]) def test_delete_cells_no_columns(self): row_key = b'row_key' column_family_id = u'column_family_id' table = object() row = self._makeOne(row_key, table) columns = [] self.assertEqual(row._pb_mutations, []) row.delete_cells(column_family_id, columns) self.assertEqual(row._pb_mutations, []) def _delete_cells_helper(self, time_range=None): row_key = b'row_key' column = b'column' column_family_id = u'column_family_id' table = object() row = self._makeOne(row_key, table) columns = [column] self.assertEqual(row._pb_mutations, []) row.delete_cells(column_family_id, columns, time_range=time_range) expected_pb = _MutationPB( delete_from_column=_MutationDeleteFromColumnPB( family_name=column_family_id, column_qualifier=column, ), ) if time_range is not None: expected_pb.delete_from_column.time_range.CopyFrom( time_range.to_pb()) self.assertEqual(row._pb_mutations, [expected_pb]) def test_delete_cells_no_time_range(self): self._delete_cells_helper() def test_delete_cells_with_time_range(self): import datetime from gcloud._helpers import _EPOCH from gcloud.bigtable.row_filters import TimestampRange microseconds = 30871000 # Makes sure already milliseconds granularity start = _EPOCH + datetime.timedelta(microseconds=microseconds) time_range = TimestampRange(start=start) self._delete_cells_helper(time_range=time_range) def test_delete_cells_with_bad_column(self): # This makes sure a failure on one of the columns doesn't leave # the row's mutations in a bad state. row_key = b'row_key' column = b'column' column_family_id = u'column_family_id' table = object() row = self._makeOne(row_key, table) columns = [column, object()] self.assertEqual(row._pb_mutations, []) with self.assertRaises(TypeError): row.delete_cells(column_family_id, columns) self.assertEqual(row._pb_mutations, []) def test_delete_cells_with_string_columns(self): row_key = b'row_key' column_family_id = u'column_family_id' column1 = u'column1' column1_bytes = b'column1' column2 = u'column2' column2_bytes = b'column2' table = object() row = self._makeOne(row_key, table) columns = [column1, column2] self.assertEqual(row._pb_mutations, []) row.delete_cells(column_family_id, columns) expected_pb1 = _MutationPB( delete_from_column=_MutationDeleteFromColumnPB( family_name=column_family_id, column_qualifier=column1_bytes, ), ) expected_pb2 = _MutationPB( delete_from_column=_MutationDeleteFromColumnPB( family_name=column_family_id, column_qualifier=column2_bytes, ), ) self.assertEqual(row._pb_mutations, [expected_pb1, expected_pb2]) def test_commit(self): from google.protobuf import empty_pb2 from gcloud.bigtable._testing import _FakeStub row_key = b'row_key' table_name = 'projects/more-stuff' column_family_id = u'column_family_id' column = b'column' timeout_seconds = 711 client = _Client(timeout_seconds=timeout_seconds) table = _Table(table_name, client=client) row = self._makeOne(row_key, table) # Create request_pb value = b'bytes-value' mutation = _MutationPB( set_cell=_MutationSetCellPB( family_name=column_family_id, column_qualifier=column, timestamp_micros=-1, # Default value. value=value, ), ) request_pb = _MutateRowRequestPB( table_name=table_name, row_key=row_key, mutations=[mutation], ) # Create response_pb response_pb = empty_pb2.Empty() # Patch the stub used by the API method. client._data_stub = stub = _FakeStub(response_pb) # Create expected_result. expected_result = None # commit() has no return value when no filter. # Perform the method and check the result. row.set_cell(column_family_id, column, value) result = row.commit() self.assertEqual(result, expected_result) self.assertEqual(stub.method_calls, [( 'MutateRow', (request_pb, timeout_seconds), {}, )]) self.assertEqual(row._pb_mutations, []) def test_commit_too_many_mutations(self): from gcloud._testing import _Monkey from gcloud.bigtable import row as MUT row_key = b'row_key' table = object() row = self._makeOne(row_key, table) row._pb_mutations = [1, 2, 3] num_mutations = len(row._pb_mutations) with _Monkey(MUT, MAX_MUTATIONS=num_mutations - 1): with self.assertRaises(ValueError): row.commit() def test_commit_no_mutations(self): from gcloud.bigtable._testing import _FakeStub row_key = b'row_key' client = _Client() table = _Table(None, client=client) row = self._makeOne(row_key, table) self.assertEqual(row._pb_mutations, []) # Patch the stub used by the API method. client._data_stub = stub = _FakeStub() # Perform the method and check the result. result = row.commit() self.assertEqual(result, None) # Make sure no request was sent. self.assertEqual(stub.method_calls, []) class TestConditionalRow(unittest2.TestCase): def _getTargetClass(self): from gcloud.bigtable.row import ConditionalRow return ConditionalRow def _makeOne(self, *args, **kwargs): return self._getTargetClass()(*args, **kwargs) def test_constructor(self): row_key = b'row_key' table = object() filter_ = object() row = self._makeOne(row_key, table, filter_=filter_) self.assertEqual(row._row_key, row_key) self.assertTrue(row._table is table) self.assertTrue(row._filter is filter_) self.assertEqual(row._true_pb_mutations, []) self.assertEqual(row._false_pb_mutations, []) def test__get_mutations(self): row_key = b'row_key' filter_ = object() row = self._makeOne(row_key, None, filter_=filter_) row._true_pb_mutations = true_mutations = object() row._false_pb_mutations = false_mutations = object() self.assertTrue(true_mutations is row._get_mutations(True)) self.assertTrue(false_mutations is row._get_mutations(False)) self.assertTrue(false_mutations is row._get_mutations(None)) def test_commit(self): from gcloud.bigtable._testing import _FakeStub from gcloud.bigtable.row_filters import RowSampleFilter row_key = b'row_key' table_name = 'projects/more-stuff' column_family_id1 = u'column_family_id1' column_family_id2 = u'column_family_id2' column_family_id3 = u'column_family_id3' column1 = b'column1' column2 = b'column2' timeout_seconds = 262 client = _Client(timeout_seconds=timeout_seconds) table = _Table(table_name, client=client) row_filter = RowSampleFilter(0.33) row = self._makeOne(row_key, table, filter_=row_filter) # Create request_pb value1 = b'bytes-value' mutation1 = _MutationPB( set_cell=_MutationSetCellPB( family_name=column_family_id1, column_qualifier=column1, timestamp_micros=-1, # Default value. value=value1, ), ) mutation2 = _MutationPB( delete_from_row=_MutationDeleteFromRowPB(), ) mutation3 = _MutationPB( delete_from_column=_MutationDeleteFromColumnPB( family_name=column_family_id2, column_qualifier=column2, ), ) mutation4 = _MutationPB( delete_from_family=_MutationDeleteFromFamilyPB( family_name=column_family_id3, ), ) request_pb = _CheckAndMutateRowRequestPB( table_name=table_name, row_key=row_key, predicate_filter=row_filter.to_pb(), true_mutations=[mutation1, mutation3, mutation4], false_mutations=[mutation2], ) # Create response_pb predicate_matched = True response_pb = _CheckAndMutateRowResponsePB( predicate_matched=predicate_matched) # Patch the stub used by the API method. client._data_stub = stub = _FakeStub(response_pb) # Create expected_result. expected_result = predicate_matched # Perform the method and check the result. row.set_cell(column_family_id1, column1, value1, state=True) row.delete(state=False) row.delete_cell(column_family_id2, column2, state=True) row.delete_cells(column_family_id3, row.ALL_COLUMNS, state=True) result = row.commit() self.assertEqual(result, expected_result) self.assertEqual(stub.method_calls, [( 'CheckAndMutateRow', (request_pb, timeout_seconds), {}, )]) self.assertEqual(row._true_pb_mutations, []) self.assertEqual(row._false_pb_mutations, []) def test_commit_too_many_mutations(self): from gcloud._testing import _Monkey from gcloud.bigtable import row as MUT row_key = b'row_key' table = object() filter_ = object() row = self._makeOne(row_key, table, filter_=filter_) row._true_pb_mutations = [1, 2, 3] num_mutations = len(row._true_pb_mutations) with _Monkey(MUT, MAX_MUTATIONS=num_mutations - 1): with self.assertRaises(ValueError): row.commit() def test_commit_no_mutations(self): from gcloud.bigtable._testing import _FakeStub row_key = b'row_key' client = _Client() table = _Table(None, client=client) filter_ = object() row = self._makeOne(row_key, table, filter_=filter_) self.assertEqual(row._true_pb_mutations, []) self.assertEqual(row._false_pb_mutations, []) # Patch the stub used by the API method. client._data_stub = stub = _FakeStub() # Perform the method and check the result. result = row.commit() self.assertEqual(result, None) # Make sure no request was sent. self.assertEqual(stub.method_calls, []) class TestAppendRow(unittest2.TestCase): def _getTargetClass(self): from gcloud.bigtable.row import AppendRow return AppendRow def _makeOne(self, *args, **kwargs): return self._getTargetClass()(*args, **kwargs) def test_constructor(self): row_key = b'row_key' table = object() row = self._makeOne(row_key, table) self.assertEqual(row._row_key, row_key) self.assertTrue(row._table is table) self.assertEqual(row._rule_pb_list, []) def test_clear(self): row_key = b'row_key' table = object() row = self._makeOne(row_key, table) row._rule_pb_list = [1, 2, 3] row.clear() self.assertEqual(row._rule_pb_list, []) def test_append_cell_value(self): table = object() row_key = b'row_key' row = self._makeOne(row_key, table) self.assertEqual(row._rule_pb_list, []) column = b'column' column_family_id = u'column_family_id' value = b'bytes-val' row.append_cell_value(column_family_id, column, value) expected_pb = _ReadModifyWriteRulePB( family_name=column_family_id, column_qualifier=column, append_value=value) self.assertEqual(row._rule_pb_list, [expected_pb]) def test_increment_cell_value(self): table = object() row_key = b'row_key' row = self._makeOne(row_key, table) self.assertEqual(row._rule_pb_list, []) column = b'column' column_family_id = u'column_family_id' int_value = 281330 row.increment_cell_value(column_family_id, column, int_value) expected_pb = _ReadModifyWriteRulePB( family_name=column_family_id, column_qualifier=column, increment_amount=int_value) self.assertEqual(row._rule_pb_list, [expected_pb]) def test_commit(self): from gcloud._testing import _Monkey from gcloud.bigtable._testing import _FakeStub from gcloud.bigtable import row as MUT row_key = b'row_key' table_name = 'projects/more-stuff' column_family_id = u'column_family_id' column = b'column' timeout_seconds = 87 client = _Client(timeout_seconds=timeout_seconds) table = _Table(table_name, client=client) row = self._makeOne(row_key, table) # Create request_pb value = b'bytes-value' # We will call row.append_cell_value(COLUMN_FAMILY_ID, COLUMN, value). request_pb = _ReadModifyWriteRowRequestPB( table_name=table_name, row_key=row_key, rules=[ _ReadModifyWriteRulePB( family_name=column_family_id, column_qualifier=column, append_value=value, ), ], ) # Create response_pb response_pb = object() # Patch the stub used by the API method. client._data_stub = stub = _FakeStub(response_pb) # Create expected_result. row_responses = [] expected_result = object() def mock_parse_rmw_row_response(row_response): row_responses.append(row_response) return expected_result # Perform the method and check the result. with _Monkey(MUT, _parse_rmw_row_response=mock_parse_rmw_row_response): row.append_cell_value(column_family_id, column, value) result = row.commit() self.assertEqual(result, expected_result) self.assertEqual(stub.method_calls, [( 'ReadModifyWriteRow', (request_pb, timeout_seconds), {}, )]) self.assertEqual(row_responses, [response_pb]) self.assertEqual(row._rule_pb_list, []) def test_commit_no_rules(self): from gcloud.bigtable._testing import _FakeStub row_key = b'row_key' client = _Client() table = _Table(None, client=client) row = self._makeOne(row_key, table) self.assertEqual(row._rule_pb_list, []) # Patch the stub used by the API method. client._data_stub = stub = _FakeStub() # Perform the method and check the result. result = row.commit() self.assertEqual(result, {}) # Make sure no request was sent. self.assertEqual(stub.method_calls, []) def test_commit_too_many_mutations(self): from gcloud._testing import _Monkey from gcloud.bigtable import row as MUT row_key = b'row_key' table = object() row = self._makeOne(row_key, table) row._rule_pb_list = [1, 2, 3] num_mutations = len(row._rule_pb_list) with _Monkey(MUT, MAX_MUTATIONS=num_mutations - 1): with self.assertRaises(ValueError): row.commit() class Test__parse_rmw_row_response(unittest2.TestCase): def _callFUT(self, row_response): from gcloud.bigtable.row import _parse_rmw_row_response return _parse_rmw_row_response(row_response) def test_it(self): from gcloud._helpers import _datetime_from_microseconds col_fam1 = u'col-fam-id' col_fam2 = u'col-fam-id2' col_name1 = b'col-name1' col_name2 = b'col-name2' col_name3 = b'col-name3-but-other-fam' cell_val1 = b'cell-val' cell_val2 = b'cell-val-newer' cell_val3 = b'altcol-cell-val' cell_val4 = b'foo' microseconds = 1000871 timestamp = _datetime_from_microseconds(microseconds) expected_output = { col_fam1: { col_name1: [ (cell_val1, timestamp), (cell_val2, timestamp), ], col_name2: [ (cell_val3, timestamp), ], }, col_fam2: { col_name3: [ (cell_val4, timestamp), ], }, } response_row = _RowPB( families=[ _FamilyPB( name=col_fam1, columns=[ _ColumnPB( qualifier=col_name1, cells=[ _CellPB( value=cell_val1, timestamp_micros=microseconds, ), _CellPB( value=cell_val2, timestamp_micros=microseconds, ), ], ), _ColumnPB( qualifier=col_name2, cells=[ _CellPB( value=cell_val3, timestamp_micros=microseconds, ), ], ), ], ), _FamilyPB( name=col_fam2, columns=[ _ColumnPB( qualifier=col_name3, cells=[ _CellPB( value=cell_val4, timestamp_micros=microseconds, ), ], ), ], ), ], ) sample_input = _ReadModifyWriteRowResponsePB(row=response_row) self.assertEqual(expected_output, self._callFUT(sample_input)) class Test__parse_family_pb(unittest2.TestCase): def _callFUT(self, family_pb): from gcloud.bigtable.row import _parse_family_pb return _parse_family_pb(family_pb) def test_it(self): from gcloud._helpers import _datetime_from_microseconds col_fam1 = u'col-fam-id' col_name1 = b'col-name1' col_name2 = b'col-name2' cell_val1 = b'cell-val' cell_val2 = b'cell-val-newer' cell_val3 = b'altcol-cell-val' microseconds = 5554441037 timestamp = _datetime_from_microseconds(microseconds) expected_dict = { col_name1: [ (cell_val1, timestamp), (cell_val2, timestamp), ], col_name2: [ (cell_val3, timestamp), ], } expected_output = (col_fam1, expected_dict) sample_input = _FamilyPB( name=col_fam1, columns=[ _ColumnPB( qualifier=col_name1, cells=[ _CellPB( value=cell_val1, timestamp_micros=microseconds, ), _CellPB( value=cell_val2, timestamp_micros=microseconds, ), ], ), _ColumnPB( qualifier=col_name2, cells=[ _CellPB( value=cell_val3, timestamp_micros=microseconds, ), ], ), ], ) self.assertEqual(expected_output, self._callFUT(sample_input)) def _CheckAndMutateRowRequestPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( bigtable_pb2 as messages_v2_pb2) return messages_v2_pb2.CheckAndMutateRowRequest(*args, **kw) def _CheckAndMutateRowResponsePB(*args, **kw): from gcloud.bigtable._generated_v2 import ( bigtable_pb2 as messages_v2_pb2) return messages_v2_pb2.CheckAndMutateRowResponse(*args, **kw) def _MutateRowRequestPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( bigtable_pb2 as messages_v2_pb2) return messages_v2_pb2.MutateRowRequest(*args, **kw) def _ReadModifyWriteRowRequestPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( bigtable_pb2 as messages_v2_pb2) return messages_v2_pb2.ReadModifyWriteRowRequest(*args, **kw) def _ReadModifyWriteRowResponsePB(*args, **kw): from gcloud.bigtable._generated_v2 import ( bigtable_pb2 as messages_v2_pb2) return messages_v2_pb2.ReadModifyWriteRowResponse(*args, **kw) def _CellPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Cell(*args, **kw) def _ColumnPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Column(*args, **kw) def _FamilyPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Family(*args, **kw) def _MutationPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Mutation(*args, **kw) def _MutationSetCellPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Mutation.SetCell(*args, **kw) def _MutationDeleteFromColumnPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Mutation.DeleteFromColumn(*args, **kw) def _MutationDeleteFromFamilyPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Mutation.DeleteFromFamily(*args, **kw) def _MutationDeleteFromRowPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Mutation.DeleteFromRow(*args, **kw) def _RowPB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.Row(*args, **kw) def _ReadModifyWriteRulePB(*args, **kw): from gcloud.bigtable._generated_v2 import ( data_pb2 as data_v2_pb2) return data_v2_pb2.ReadModifyWriteRule(*args, **kw) class _Client(object): data_stub = None def __init__(self, timeout_seconds=None): self.timeout_seconds = timeout_seconds class _Instance(object): def __init__(self, client=None): self._client = client class _Table(object): def __init__(self, name, client=None): self.name = name self._instance = _Instance(client)