Skip to content

Commit

Permalink
Implemented HappyBase Table.counter_inc().
Browse files Browse the repository at this point in the history
  • Loading branch information
dhermes committed Sep 8, 2015
1 parent 40b04fd commit d6adc32
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 16 deletions.
18 changes: 15 additions & 3 deletions gcloud_bigtable/happybase/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"""Google Cloud Bigtable HappyBase table module."""


import struct

from gcloud_bigtable.column_family import GarbageCollectionRule
from gcloud_bigtable.column_family import GarbageCollectionRuleIntersection
from gcloud_bigtable.happybase.batch import Batch
Expand Down Expand Up @@ -503,10 +505,20 @@ def counter_inc(self, row, column, value=1):
:param value: Amount to increment the counter by. (If negative,
this is equivalent to decrement.)
:raises: :class:`NotImplementedError <exceptions.NotImplementedError>`
temporarily until the method is implemented.
:rtype: int
:returns: Counter value after incrementing.
"""
raise NotImplementedError('Temporarily not implemented.')
table = _LowLevelTable(self.name, self.connection._cluster)
row = table.row(row)
column_family_id, column_qualifier = column.split(':')
row.increment_cell_value(column_family_id, column_qualifier, value)
modified_cells = row.commit_modifications()
column_cells = modified_cells[column_family_id][column_qualifier]
if len(column_cells) != 1:
raise ValueError('Expected server to return one modified cell.')
bytes_value = column_cells[0][0]
int_value, = struct.unpack('>q', bytes_value)
return int_value

def counter_dec(self, row, column, value=1):
"""Atomically decrement a counter column.
Expand Down
132 changes: 121 additions & 11 deletions gcloud_bigtable/happybase/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,17 +407,6 @@ def test_counter_set(self):
with self.assertRaises(NotImplementedError):
table.counter_set(row, column, value=value)

def test_counter_inc(self):
name = 'table-name'
connection = object()
table = self._makeOne(name, connection)

row = 'row-key'
column = 'fam:col1'
value = 42
with self.assertRaises(NotImplementedError):
table.counter_inc(row, column, value=value)

def test_counter_dec(self):
klass = self._getTargetClass()
counter_value = 42
Expand All @@ -444,6 +433,105 @@ def counter_inc(self, row, column, value=1):
self.assertEqual(result, counter_value - dec_value)
self.assertEqual(TableWithInc.incremented, [(row, column, -dec_value)])

def _counter_inc_helper(self, row, column, value, commit_result):
from gcloud_bigtable._testing import _Monkey
from gcloud_bigtable.happybase import table as MUT

name = 'table-name'
cluster = object()
connection = _Connection(cluster)
table = self._makeOne(name, connection)
tables_constructed = []

def make_low_level_table(*args, **kwargs):
result = _MockLowLevelTable(*args, **kwargs)
tables_constructed.append(result)
result.row_values[row] = _MockLowLevelRow(
row, commit_result=commit_result)
return result

with _Monkey(MUT, _LowLevelTable=make_low_level_table):
result = table.counter_inc(row, column, value=value)

incremented_value = value + _MockLowLevelRow.COUNTER_DEFAULT
self.assertEqual(result, incremented_value)

# Just one table would have been created.
table_instance, = tables_constructed
self.assertEqual(table_instance.args, (name, cluster))
self.assertEqual(table_instance.kwargs, {})

# Check the row values returned.
row_obj = table_instance.row_values[row]
self.assertEqual(row_obj.counts,
{tuple(column.split(':')): incremented_value})

def test_counter_inc(self):
import struct

row = 'row-key'
col_fam = 'fam'
col_qual = 'col1'
column = col_fam + ':' + col_qual
value = 42
packed_value = struct.pack('>q', value)
fake_timestamp = None
commit_result = {
col_fam: {
col_qual: [(packed_value, fake_timestamp)],
}
}
self._counter_inc_helper(row, column, value, commit_result)

def test_counter_inc_bad_result(self):
row = 'row-key'
col_fam = 'fam'
col_qual = 'col1'
column = col_fam + ':' + col_qual
value = 42
commit_result = None
with self.assertRaises(TypeError):
self._counter_inc_helper(row, column, value, commit_result)

def test_counter_inc_result_key_error(self):
row = 'row-key'
col_fam = 'fam'
col_qual = 'col1'
column = col_fam + ':' + col_qual
value = 42
commit_result = {}
with self.assertRaises(KeyError):
self._counter_inc_helper(row, column, value, commit_result)

def test_counter_inc_result_nested_key_error(self):
row = 'row-key'
col_fam = 'fam'
col_qual = 'col1'
column = col_fam + ':' + col_qual
value = 42
commit_result = {col_fam: {}}
with self.assertRaises(KeyError):
self._counter_inc_helper(row, column, value, commit_result)

def test_counter_inc_result_non_unique_cell(self):
row = 'row-key'
col_fam = 'fam'
col_qual = 'col1'
column = col_fam + ':' + col_qual
value = 42
fake_timestamp = None
packed_value = None
commit_result = {
col_fam: {
col_qual: [
(packed_value, fake_timestamp),
(packed_value, fake_timestamp),
],
}
}
with self.assertRaises(ValueError):
self._counter_inc_helper(row, column, value, commit_result)


class _MockLowLevelTable(object):

Expand All @@ -452,11 +540,15 @@ def __init__(self, *args, **kwargs):
self.kwargs = kwargs
self.list_column_families_calls = 0
self.column_families = {}
self.row_values = {}

def list_column_families(self):
self.list_column_families_calls += 1
return self.column_families

def row(self, row_key):
return self.row_values[row_key]


class _MockLowLevelColumnFamily(object):

Expand Down Expand Up @@ -491,3 +583,21 @@ def put(self, *args):

def delete(self, *args):
self.delete_args.append(args)


class _MockLowLevelRow(object):

COUNTER_DEFAULT = 0

def __init__(self, row_key, commit_result=None):
self.row_key = row_key
self.counts = {}
self.commit_result = commit_result

def increment_cell_value(self, column_family_id, column, int_value):
count = self.counts.setdefault((column_family_id, column),
self.COUNTER_DEFAULT)
self.counts[(column_family_id, column)] = count + int_value

def commit_modifications(self):
return self.commit_result
4 changes: 2 additions & 2 deletions gcloud_bigtable/row.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def append_cell_value(self, column_family_id, column, value):
This method adds a read-modify rule protobuf to the accumulated
read-modify rules on this :class:`Row`, but does not make an API
request. To actually send an API request (with the rules) to the
Google Cloud Bigtable API, call :meth:`commit`.
Google Cloud Bigtable API, call :meth:`commit_modifications`.
:type column_family_id: str
:param column_family_id: The column family that contains the column.
Expand Down Expand Up @@ -251,7 +251,7 @@ def increment_cell_value(self, column_family_id, column, int_value):
This method adds a read-modify rule protobuf to the accumulated
read-modify rules on this :class:`Row`, but does not make an API
request. To actually send an API request (with the rules) to the
Google Cloud Bigtable API, call :meth:`commit`.
Google Cloud Bigtable API, call :meth:`commit_modifications`.
:type column_family_id: str
:param column_family_id: The column family that contains the column.
Expand Down

0 comments on commit d6adc32

Please sign in to comment.