diff --git a/src/java/org/apache/cassandra/service/CassandraServer.java b/src/java/org/apache/cassandra/service/CassandraServer.java index 3af824dfde..4f3db3fece 100644 --- a/src/java/org/apache/cassandra/service/CassandraServer.java +++ b/src/java/org/apache/cassandra/service/CassandraServer.java @@ -211,10 +211,18 @@ public class CassandraServer implements Cassandra.Iface throws InvalidRequestException { logger.debug("get_column_count"); - ThriftValidation.validateColumnParent(table, column_parent); + // validateColumnParent assumes we require simple columns; g_c_c is the only + // one of the columnParent-taking apis that can also work at the SC level. + // so we roll a one-off validator here. + String cfType = ThriftValidation.validateColumnFamily(table, column_parent.column_family); + if (cfType.equals("Standard") && column_parent.super_column != null) + { + throw new InvalidRequestException("columnfamily alone is required for standard CF " + column_parent.column_family); + } ColumnFamily cfamily; - if (DatabaseDescriptor.isNameSortingEnabled(table, column_parent.column_family)) + if (DatabaseDescriptor.isNameSortingEnabled(table, column_parent.column_family) + && column_parent.super_column == null) { cfamily = readColumnFamily(new SliceFromReadCommand(table, key, column_parent, "", "", true, 0, Integer.MAX_VALUE)); } diff --git a/src/java/org/apache/cassandra/service/ThriftValidation.java b/src/java/org/apache/cassandra/service/ThriftValidation.java index d21521527f..f1d90e0e95 100644 --- a/src/java/org/apache/cassandra/service/ThriftValidation.java +++ b/src/java/org/apache/cassandra/service/ThriftValidation.java @@ -66,6 +66,14 @@ public class ThriftValidation throw new InvalidRequestException("supercolumn parameter is invalid for standard CF " + column_path.column_family); } } + else if (column_path.super_column == null) + { + throw new InvalidRequestException("column parameter is not optional for super CF " + column_path.column_family); + } + if (column_path.column == null) + { + throw new InvalidRequestException("column parameter is not optional"); + } } static void validateColumnParent(String tablename, ColumnParent column_parent) throws InvalidRequestException diff --git a/test/system/test_server.py b/test/system/test_server.py index 833d955d18..f1416a60a0 100644 --- a/test/system/test_server.py +++ b/test/system/test_server.py @@ -22,64 +22,60 @@ import os, sys, time from . import client, root, CassandraTester from thrift.Thrift import TApplicationException -from ttypes import batch_mutation_t, batch_mutation_super_t, superColumn_t, column_t, NotFoundException, InvalidRequestException +from ttypes import * -_SIMPLE_COLUMNS = [column_t(columnName='c1', value='value1', timestamp=0), - column_t(columnName='c2', value='value2', timestamp=0)] -_SUPER_COLUMNS = [superColumn_t(name='sc1', - columns=[column_t(columnName='c4', value='value4', timestamp=0)]), - superColumn_t(name='sc2', - columns=[column_t(columnName='c5', value='value5', timestamp=0), - column_t(columnName='c6', value='value6', timestamp=0)])] +_SIMPLE_COLUMNS = [Column('c1', 'value1', 0), + Column('c2', 'value2', 0)] +_SUPER_COLUMNS = [SuperColumn(name='sc1', columns=[Column('c4', 'value4', 0)]), + SuperColumn(name='sc2', columns=[Column('c5', 'value5', 0), + Column('c6', 'value6', 0)])] def _insert_simple(block=True): - client.insert('Table1', 'key1', 'Standard1:c1', 'value1', 0, block) - client.insert('Table1', 'key1', 'Standard1:c2', 'value2', 0, block) + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c1'), 'value1', 0, block) + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c2'), 'value2', 0, block) def _insert_batch(block): cfmap = {'Standard1': _SIMPLE_COLUMNS, 'Standard2': _SIMPLE_COLUMNS} - client.batch_insert(batch_mutation_t(table='Table1', key='key1', cfmap=cfmap), block) + client.batch_insert('Table1', BatchMutation(key='key1', cfmap=cfmap), block) def _verify_batch(): _verify_simple() - L = client.get_slice('Table1', 'key1', 'Standard2', '', '', True, 0, 1000) + L = client.get_slice('Table1', 'key1', ColumnParent('Standard2'), '', '', True, 0, 1000) assert L == _SIMPLE_COLUMNS, L def _verify_simple(): - assert client.get_column('Table1', 'key1', 'Standard1:c1') == \ - column_t(columnName='c1', value='value1', timestamp=0) - L = client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) + assert client.get_column('Table1', 'key1', ColumnPath('Standard1', column='c1')) == Column('c1', 'value1', 0) + L = client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) assert L == _SIMPLE_COLUMNS, L def _insert_super(): - client.insert('Table1', 'key1', 'Super1:sc1:c4', 'value4', 0, False) - client.insert('Table1', 'key1', 'Super1:sc2:c5', 'value5', 0, False) - client.insert('Table1', 'key1', 'Super1:sc2:c6', 'value6', 0, False) + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc1', 'c4'), 'value4', 0, False) + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c5'), 'value5', 0, False) + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c6'), 'value6', 0, False) time.sleep(0.1) def _insert_range(): - client.insert('Table1', 'key1', 'Standard1:c1', 'value1', 0, True) - client.insert('Table1', 'key1', 'Standard1:c2', 'value2', 0, True) - client.insert('Table1', 'key1', 'Standard1:c3', 'value3', 0, True) + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c1'), 'value1', 0, True) + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c2'), 'value2', 0, True) + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c3'), 'value3', 0, True) time.sleep(0.1) def _verify_range(): - result = client.get_slice('Table1','key1', 'Standard1', 'c1', 'c2', True, 0, 1000) + result = client.get_slice('Table1','key1', ColumnParent('Standard1'), 'c1', 'c2', True, 0, 1000) assert len(result) == 2 - assert result[0].columnName == 'c1' - assert result[1].columnName == 'c2' + assert result[0].column_name == 'c1' + assert result[1].column_name == 'c2' - result = client.get_slice('Table1','key1', 'Standard1', 'a', 'z' , True, 0, 1000) + result = client.get_slice('Table1','key1', ColumnParent('Standard1'), 'a', 'z' , True, 0, 1000) assert len(result) == 3, result - result = client.get_slice('Table1','key1', 'Standard1', 'a', 'z' , True, 0, 2) + result = client.get_slice('Table1','key1', ColumnParent('Standard1'), 'a', 'z' , True, 0, 2) assert len(result) == 2, result -def _verify_super(supercolumn='Super1'): - assert client.get_column('Table1', 'key1', supercolumn + ':sc1:c4') == \ - column_t(columnName='c4', value='value4', timestamp=0) +def _verify_super(supercf='Super1'): + assert client.get_column('Table1', 'key1', ColumnPath(supercf, 'sc1', 'c4')) == Column('c4', 'value4', 0) slice = client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) assert slice == _SUPER_COLUMNS, slice @@ -101,16 +97,21 @@ class TestMutations(CassandraTester): _verify_simple() def test_empty_slice(self): - assert client.get_slice('Table1', 'key1', 'Standard2', '', '', True, 0, 1000) == [] + assert client.get_slice('Table1', 'key1', ColumnParent('Standard2'), '', '', True, 0, 1000) == [] def test_empty_slice_super(self): assert client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) == [] def test_missing_super(self): - _expect_missing(lambda: client.get_column('Table1', 'key1', 'Super1:sc1:c1')) + _expect_missing(lambda: client.get_column('Table1', 'key1', ColumnPath('Super1', 'sc1', 'c1'))) def test_count(self): - assert client.get_column_count('Table1', 'key1', 'Standard2') == 0 + _insert_simple() + _insert_super() + assert client.get_column_count('Table1', 'key1', ColumnParent('Standard2')) == 0 + assert client.get_column_count('Table1', 'key1', ColumnParent('Standard1')) == 2 + assert client.get_column_count('Table1', 'key1', ColumnParent('Super1', 'sc2')) == 2 + assert client.get_column_count('Table1', 'key1', ColumnParent('Super1')) == 2 def test_insert_blocking(self): _insert_simple() @@ -130,19 +131,18 @@ class TestMutations(CassandraTester): _verify_batch() def test_bad_calls(self): - _expect_exception(lambda: client.insert('Table1', 'key1', 'Standard1:x:y', 'value', 0, True), InvalidRequestException) + _expect_exception(lambda: client.insert('Table1', 'key1', ColumnPath('Standard1', 'x', 'y'), 'value', 0, True), InvalidRequestException) - _expect_exception(lambda: client.get_column('Table1', 'key1', 'Standard1'), InvalidRequestException) - _expect_exception(lambda: client.get_column('Table1', 'key1', 'Standard1:x:y'), InvalidRequestException) - _expect_exception(lambda: client.get_column('Table1', 'key1', 'Super1'), InvalidRequestException) - _expect_exception(lambda: client.get_column('Table1', 'key1', 'Super1:x'), InvalidRequestException) - _expect_exception(lambda: client.get_column('Table1', 'key1', 'Super1:x:y:z'), InvalidRequestException) + _expect_exception(lambda: client.get_column('Table1', 'key1', ColumnPath('Standard1')), InvalidRequestException) + _expect_exception(lambda: client.get_column('Table1', 'key1', ColumnPath('Standard1', 'x', 'y')), InvalidRequestException) + _expect_exception(lambda: client.get_column('Table1', 'key1', ColumnPath('Super1')), InvalidRequestException) + _expect_exception(lambda: client.get_column('Table1', 'key1', ColumnPath('Super1', 'x')), InvalidRequestException) _expect_exception(lambda: client.get_key_range('Table1', 'S', '', '', 1000), InvalidRequestException) def test_batch_insert_super(self): cfmap = {'Super1': _SUPER_COLUMNS, 'Super2': _SUPER_COLUMNS} - client.batch_insert_superColumn(batch_mutation_t(table='Table1', key='key1', cfmap=cfmap), False) + client.batch_insert_super_column('Table1', BatchMutation(key='key1', cfmap=cfmap), False) time.sleep(0.1) _verify_super('Super1') _verify_super('Super2') @@ -150,37 +150,33 @@ class TestMutations(CassandraTester): def test_batch_insert_super_blocking(self): cfmap = {'Super1': _SUPER_COLUMNS, 'Super2': _SUPER_COLUMNS} - client.batch_insert_superColumn(batch_mutation_t(table='Table1', key='key1', cfmap=cfmap), True) + client.batch_insert_super_column('Table1', BatchMutation(key='key1', cfmap=cfmap), True) _verify_super('Super1') _verify_super('Super2') def test_cf_remove_column(self): _insert_simple() - client.remove('Table1', 'key1', 'Standard1:c1', 1, True) - _expect_missing(lambda: client.get_column('Table1', 'key1', 'Standard1:c1')) - assert client.get_column('Table1', 'key1', 'Standard1:c2') == \ - column_t(columnName='c2', value='value2', timestamp=0) - assert client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) == \ - [column_t(columnName='c2', value='value2', timestamp=0)] + client.remove('Table1', 'key1', ColumnPathOrParent('Standard1', column='c1'), 1, True) + _expect_missing(lambda: client.get_column('Table1', 'key1', ColumnPath('Standard1', column='c1'))) + assert client.get_column('Table1', 'key1', ColumnPath('Standard1', column='c2')) \ + == Column('c2', 'value2', 0) + assert client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) \ + == [Column('c2', 'value2', 0)] # New insert, make sure it shows up post-remove: - client.insert('Table1', 'key1', 'Standard1:c3', 'value3', 0, True) - assert client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) == \ - [column_t(columnName='c2', value='value2', timestamp=0), - column_t(columnName='c3', value='value3', timestamp=0)] + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c3'), 'value3', 0, True) + assert client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) == \ + [Column('c2', 'value2', 0), Column('c3', 'value3', 0)] # Test resurrection. First, re-insert the value w/ older timestamp, # and make sure it stays removed - client.insert('Table1', 'key1', 'Standard1:c1', 'value1', 0, True) - assert client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) == \ - [column_t(columnName='c2', value='value2', timestamp=0), - column_t(columnName='c3', value='value3', timestamp=0)] + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c1'), 'value1', 0, True) + assert client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) == \ + [Column('c2', 'value2', 0), Column('c3', 'value3', 0)] # Next, w/ a newer timestamp; it should come back: - client.insert('Table1', 'key1', 'Standard1:c1', 'value1', 2, True) - assert client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) == \ - [column_t(columnName='c1', value='value1', timestamp=2), - column_t(columnName='c2', value='value2', timestamp=0), - column_t(columnName='c3', value='value3', timestamp=0)] + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c1'), 'value1', 2, True) + assert client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) == \ + [Column('c1', 'value1', 2), Column('c2', 'value2', 0), Column('c3', 'value3', 0)] def test_cf_remove(self): @@ -188,18 +184,18 @@ class TestMutations(CassandraTester): _insert_super() # Remove the key1:Standard1 cf: - client.remove('Table1', 'key1', 'Standard1', 3, True) - assert client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) == [] + client.remove('Table1', 'key1', ColumnPathOrParent('Standard1'), 3, True) + assert client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) == [] _verify_super() # Test resurrection. First, re-insert a value w/ older timestamp, # and make sure it stays removed: - client.insert('Table1', 'key1', 'Standard1:c1', 'value1', 0, True) - assert client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) == [] + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c1'), 'value1', 0, True) + assert client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) == [] # Next, w/ a newer timestamp; it should come back: - client.insert('Table1', 'key1', 'Standard1:c1', 'value1', 4, True) - assert client.get_slice('Table1', 'key1', 'Standard1', '', '', True, 0, 1000) == \ - [column_t(columnName='c1', value='value1', timestamp=4)] + client.insert('Table1', 'key1', ColumnPath('Standard1', column='c1'), 'value1', 4, True) + assert client.get_slice('Table1', 'key1', ColumnParent('Standard1'), '', '', True, 0, 1000) == \ + [Column('c1', 'value1', 4)] def test_super_cf_remove_column(self): @@ -207,71 +203,62 @@ class TestMutations(CassandraTester): _insert_super() # Make sure remove clears out what it's supposed to, and _only_ that: - client.remove('Table1', 'key1', 'Super1:sc2:c5', 5, True) - _expect_missing(lambda: client.get_column('Table1', 'key1', 'Super1:sc2:c5')) + client.remove('Table1', 'key1', ColumnPathOrParent('Super1', 'sc2', 'c5'), 5, True) + _expect_missing(lambda: client.get_column('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c5'))) assert client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) == \ - [superColumn_t(name='sc1', - columns=[column_t(columnName='c4', value='value4', timestamp=0)]), - superColumn_t(name='sc2', - columns=[column_t(columnName='c6', value='value6', timestamp=0)])] + [SuperColumn(name='sc1', columns=[Column('c4', 'value4', 0)]), + SuperColumn(name='sc2', columns=[Column('c6', 'value6', 0)])] _verify_simple() # New insert, make sure it shows up post-remove: - client.insert('Table1', 'key1', 'Super1:sc2:c7', 'value7', 0, True) - scs = [superColumn_t(name='sc1', - columns=[column_t(columnName='c4', value='value4', timestamp=0)]), - superColumn_t(name='sc2', - columns=[column_t(columnName='c6', value='value6', timestamp=0), - column_t(columnName='c7', value='value7', timestamp=0)])] + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c7'), 'value7', 0, True) + scs = [SuperColumn(name='sc1', columns=[Column('c4', 'value4', 0)]), + SuperColumn(name='sc2', + columns=[Column('c6', 'value6', 0), Column('c7', 'value7', 0)])] assert client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) == scs # Test resurrection. First, re-insert the value w/ older timestamp, # and make sure it stays removed: - client.insert('Table1', 'key1', 'Super1:sc2:c5', 'value5', 0, True) + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c5'), 'value5', 0, True) actual = client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) assert actual == scs, actual # Next, w/ a newer timestamp; it should come back - client.insert('Table1', 'key1', 'Super1:sc2:c5', 'value5', 6, True) + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c5'), 'value5', 6, True) actual = client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) assert actual == \ - [superColumn_t(name='sc1', - columns=[column_t(columnName='c4', value='value4', timestamp=0)]), - superColumn_t(name='sc2', - columns=[column_t(columnName='c5', value='value5', timestamp=6), - column_t(columnName='c6', value='value6', timestamp=0), - column_t(columnName='c7', value='value7', timestamp=0)])], actual + [SuperColumn(name='sc1', columns=[Column('c4', 'value4', 0)]), + SuperColumn(name='sc2', columns=[Column('c5', 'value5', 6), + Column('c6', 'value6', 0), + Column('c7', 'value7', 0)])], actual def test_super_cf_remove_supercolumn(self): _insert_simple() _insert_super() # Make sure remove clears out what it's supposed to, and _only_ that: - client.remove('Table1', 'key1', 'Super1:sc2', 5, True) - _expect_missing(lambda: client.get_column('Table1', 'key1', 'Super1:sc2:c5')) - actual = client.get_columns_since('Table1', 'key1', 'Super1:sc2', -1) + client.remove('Table1', 'key1', ColumnPathOrParent('Super1', 'sc2'), 5, True) + _expect_missing(lambda: client.get_column('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c5'))) + actual = client.get_columns_since('Table1', 'key1', ColumnParent('Super1', 'sc2'), -1) assert actual == [], actual - scs = [superColumn_t(name='sc1', - columns=[column_t(columnName='c4', value='value4', timestamp=0)])] + scs = [SuperColumn(name='sc1', columns=[Column('c4', 'value4', 0)])] actual = client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) assert actual == scs, actual _verify_simple() # Test resurrection. First, re-insert the value w/ older timestamp, # and make sure it stays removed: - client.insert('Table1', 'key1', 'Super1:sc2:c5', 'value5', 0, True) + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c5'), 'value5', 0, True) actual = client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) assert actual == scs, actual # Next, w/ a newer timestamp; it should come back - client.insert('Table1', 'key1', 'Super1:sc2:c5', 'value5', 6, True) + client.insert('Table1', 'key1', ColumnPath('Super1', 'sc2', 'c5'), 'value5', 6, True) actual = client.get_slice_super('Table1', 'key1', 'Super1', '', '', True, 0, 1000) assert actual == \ - [superColumn_t(name='sc1', - columns=[column_t(columnName='c4', value='value4', timestamp=0)]), - superColumn_t(name='sc2', - columns=[column_t(columnName='c5', value='value5', timestamp=6)])], actual + [SuperColumn(name='sc1', columns=[Column('c4', 'value4', 0)]), + SuperColumn(name='sc2', columns=[Column('c5', 'value5', 6)])], actual def test_empty_range(self): @@ -283,20 +270,20 @@ class TestMutations(CassandraTester): _insert_simple() assert client.get_key_range('Table1', 'Standard1', 'key1', '', 1000) == ['key1'] - client.remove('Table1', 'key1', 'Standard1:c1', 1, True) - client.remove('Table1', 'key1', 'Standard1:c2', 1, True) + client.remove('Table1', 'key1', ColumnPathOrParent('Standard1', column='c1'), 1, True) + client.remove('Table1', 'key1', ColumnPathOrParent('Standard1', column='c2'), 1, True) assert client.get_key_range('Table1', 'Standard1', '', '', 1000) == [] def test_range_collation(self): for key in ['-a', '-b', 'a', 'b'] + [str(i) for i in xrange(100)]: - client.insert('Table1', key, 'Standard1:' + key, 'v', 0, True) + client.insert('Table1', key, ColumnPath('Standard1', column=key), 'v', 0, True) L = client.get_key_range('Table1', 'Standard1', '', '', 1000) # note the collated ordering rather than ascii assert L == ['0', '1', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '2', '20', '21', '22', '23', '24', '25', '26', '27','28', '29', '3', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '4', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '5', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '6', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '7', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '8', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '9', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', 'a', '-a', 'b', '-b'], L def test_range_partial(self): for key in ['-a', '-b', 'a', 'b'] + [str(i) for i in xrange(100)]: - client.insert('Table1', key, 'Standard1:' + key, 'v', 0, True) + client.insert('Table1', key, ColumnPath('Standard1', column=key), 'v', 0, True) L = client.get_key_range('Table1', 'Standard1', 'a', '', 1000) assert L == ['a', '-a', 'b', '-b'], L @@ -316,12 +303,12 @@ class TestMutations(CassandraTester): def test_get_slice_by_names(self): _insert_range() - result = client.get_slice_by_names('Table1','key1', 'Standard1', ['c1', 'c2']) + result = client.get_slice_by_names('Table1','key1', ColumnParent('Standard1'), ['c1', 'c2']) assert len(result) == 2 - assert result[0].columnName == 'c1' - assert result[1].columnName == 'c2' + assert result[0].column_name == 'c1' + assert result[1].column_name == 'c2' _insert_super() - result = client.get_slice_by_names('Table1','key1', 'Super1:sc1', ['c4']) + result = client.get_slice_by_names('Table1','key1', ColumnParent('Super1', 'sc1'), ['c4']) assert len(result) == 1 - assert result[0].columnName == 'c4' + assert result[0].column_name == 'c4'