Merge branch 'master' into 2.0
Conflicts: tests/unit/test_types.py
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -15,3 +15,4 @@ cover/ | |||||||
| docs/_build/ | docs/_build/ | ||||||
| tests/integration/ccm | tests/integration/ccm | ||||||
| setuptools*.tar.gz | setuptools*.tar.gz | ||||||
|  | setuptools*.egg | ||||||
|   | |||||||
| @@ -1,7 +1,21 @@ | |||||||
| 1.0.2 | 1.0.3 | ||||||
| ===== | ===== | ||||||
| In Progress | In Progress | ||||||
|  |  | ||||||
|  | Features | ||||||
|  | -------- | ||||||
|  | * Support static columns in schemas, which are available starting in | ||||||
|  |   Cassandra 2.1. (github issue #91) | ||||||
|  |  | ||||||
|  | Other | ||||||
|  | ----- | ||||||
|  | * Don't ignore column names when parsing typestrings.  This is needed for | ||||||
|  |   user-defined type support.  (github issue #90) | ||||||
|  |  | ||||||
|  | 1.0.2 | ||||||
|  | ===== | ||||||
|  | March 4, 2014 | ||||||
|  |  | ||||||
| Bug Fixes | Bug Fixes | ||||||
| --------- | --------- | ||||||
| * With asyncorereactor, correctly handle EAGAIN/EWOULDBLOCK when the message from | * With asyncorereactor, correctly handle EAGAIN/EWOULDBLOCK when the message from | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ class NullHandler(logging.Handler): | |||||||
| logging.getLogger('cassandra').addHandler(NullHandler()) | logging.getLogger('cassandra').addHandler(NullHandler()) | ||||||
|  |  | ||||||
|  |  | ||||||
| __version_info__ = (1, 0, 1, 'post') | __version_info__ = (1, 0, 2, 'post') | ||||||
| __version__ = '.'.join(map(str, __version_info__)) | __version__ = '.'.join(map(str, __version_info__)) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -254,7 +254,7 @@ class Cluster(object): | |||||||
|     control_connection = None |     control_connection = None | ||||||
|     scheduler = None |     scheduler = None | ||||||
|     executor = None |     executor = None | ||||||
|     _is_shutdown = False |     is_shutdown = False | ||||||
|     _is_setup = False |     _is_setup = False | ||||||
|     _prepared_statements = None |     _prepared_statements = None | ||||||
|     _prepared_statement_lock = Lock() |     _prepared_statement_lock = Lock() | ||||||
| @@ -461,7 +461,7 @@ class Cluster(object): | |||||||
|         operations on the ``Session``. |         operations on the ``Session``. | ||||||
|         """ |         """ | ||||||
|         with self._lock: |         with self._lock: | ||||||
|             if self._is_shutdown: |             if self.is_shutdown: | ||||||
|                 raise Exception("Cluster is already shut down") |                 raise Exception("Cluster is already shut down") | ||||||
|  |  | ||||||
|             if not self._is_setup: |             if not self._is_setup: | ||||||
| @@ -502,10 +502,10 @@ class Cluster(object): | |||||||
|         Once shutdown, a Cluster should not be used for any purpose. |         Once shutdown, a Cluster should not be used for any purpose. | ||||||
|         """ |         """ | ||||||
|         with self._lock: |         with self._lock: | ||||||
|             if self._is_shutdown: |             if self.is_shutdown: | ||||||
|                 raise Exception("The Cluster was already shutdown") |                 raise Exception("The Cluster was already shutdown") | ||||||
|             else: |             else: | ||||||
|                 self._is_shutdown = True |                 self.is_shutdown = True | ||||||
|  |  | ||||||
|         if self.scheduler: |         if self.scheduler: | ||||||
|             self.scheduler.shutdown() |             self.scheduler.shutdown() | ||||||
| @@ -525,7 +525,7 @@ class Cluster(object): | |||||||
|         # Sessions while they are still being used (in case there are no |         # Sessions while they are still being used (in case there are no | ||||||
|         # longer any references to this Cluster object, but there are |         # longer any references to this Cluster object, but there are | ||||||
|         # still references to the Session object) |         # still references to the Session object) | ||||||
|         if not self._is_shutdown: |         if not self.is_shutdown: | ||||||
|             if self.scheduler: |             if self.scheduler: | ||||||
|                 self.scheduler.shutdown() |                 self.scheduler.shutdown() | ||||||
|             if self.control_connection: |             if self.control_connection: | ||||||
| @@ -589,7 +589,7 @@ class Cluster(object): | |||||||
|         """ |         """ | ||||||
|         Intended for internal use only. |         Intended for internal use only. | ||||||
|         """ |         """ | ||||||
|         if self._is_shutdown: |         if self.is_shutdown: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         host._handle_node_up_condition.acquire() |         host._handle_node_up_condition.acquire() | ||||||
| @@ -669,7 +669,7 @@ class Cluster(object): | |||||||
|         """ |         """ | ||||||
|         Intended for internal use only. |         Intended for internal use only. | ||||||
|         """ |         """ | ||||||
|         if self._is_shutdown: |         if self.is_shutdown: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         with host.lock: |         with host.lock: | ||||||
| @@ -691,7 +691,7 @@ class Cluster(object): | |||||||
|         self._start_reconnector(host, is_host_addition) |         self._start_reconnector(host, is_host_addition) | ||||||
|  |  | ||||||
|     def on_add(self, host): |     def on_add(self, host): | ||||||
|         if self._is_shutdown: |         if self.is_shutdown: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         log.debug("Adding or renewing pools for new host %s and notifying listeners", host) |         log.debug("Adding or renewing pools for new host %s and notifying listeners", host) | ||||||
| @@ -751,7 +751,7 @@ class Cluster(object): | |||||||
|             session.update_created_pools() |             session.update_created_pools() | ||||||
|  |  | ||||||
|     def on_remove(self, host): |     def on_remove(self, host): | ||||||
|         if self._is_shutdown: |         if self.is_shutdown: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         log.debug("Removing host %s", host) |         log.debug("Removing host %s", host) | ||||||
| @@ -1481,7 +1481,7 @@ class ControlConnection(object): | |||||||
|  |  | ||||||
|     def _submit(self, *args, **kwargs): |     def _submit(self, *args, **kwargs): | ||||||
|         try: |         try: | ||||||
|             if not self._cluster._is_shutdown: |             if not self._cluster.is_shutdown: | ||||||
|                 return self._cluster.executor.submit(*args, **kwargs) |                 return self._cluster.executor.submit(*args, **kwargs) | ||||||
|         except ReferenceError: |         except ReferenceError: | ||||||
|             pass |             pass | ||||||
| @@ -1512,7 +1512,7 @@ class ControlConnection(object): | |||||||
|             self._signal_error() |             self._signal_error() | ||||||
|  |  | ||||||
|     def _refresh_schema(self, connection, keyspace=None, table=None): |     def _refresh_schema(self, connection, keyspace=None, table=None): | ||||||
|         if self._cluster._is_shutdown: |         if self._cluster.is_shutdown: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         self.wait_for_schema_agreement(connection) |         self.wait_for_schema_agreement(connection) | ||||||
|   | |||||||
| @@ -108,24 +108,29 @@ def parse_casstype_args(typestring): | |||||||
|     tokens, remainder = casstype_scanner.scan(typestring) |     tokens, remainder = casstype_scanner.scan(typestring) | ||||||
|     if remainder: |     if remainder: | ||||||
|         raise ValueError("weird characters %r at end" % remainder) |         raise ValueError("weird characters %r at end" % remainder) | ||||||
|     args = [[]] |  | ||||||
|  |     # use a stack of (types, names) lists | ||||||
|  |     args = [([], [])] | ||||||
|     for tok in tokens: |     for tok in tokens: | ||||||
|         if tok == '(': |         if tok == '(': | ||||||
|             args.append([]) |             args.append(([], [])) | ||||||
|         elif tok == ')': |         elif tok == ')': | ||||||
|             arglist = args.pop() |             types, names = args.pop() | ||||||
|             ctype = args[-1].pop() |             prev_types, prev_names = args[-1] | ||||||
|             paramized = ctype.apply_parameters(*arglist) |             prev_types[-1] = prev_types[-1].apply_parameters(types, names) | ||||||
|             args[-1].append(paramized) |  | ||||||
|         else: |         else: | ||||||
|  |             types, names = args[-1] | ||||||
|             if ':' in tok: |             if ':' in tok: | ||||||
|                 # ignore those column name hex encoding bit; we have the |                 name, tok = tok.rsplit(':', 1) | ||||||
|                 # proper column name from elsewhere |                 names.append(name) | ||||||
|                 tok = tok.rsplit(':', 1)[-1] |             else: | ||||||
|             ctype = lookup_casstype_simple(tok) |                 names.append(None) | ||||||
|             args[-1].append(ctype) |  | ||||||
|  |  | ||||||
|     return args[0][0] |             ctype = lookup_casstype_simple(tok) | ||||||
|  |             types.append(ctype) | ||||||
|  |  | ||||||
|  |     # return the first (outer) type, which will have all parameters applied | ||||||
|  |     return args[0][0][0] | ||||||
|  |  | ||||||
|  |  | ||||||
| def lookup_casstype(casstype): | def lookup_casstype(casstype): | ||||||
| @@ -260,13 +265,16 @@ class _CassandraType(object): | |||||||
|         return '%s(%s)' % (cname, sublist) |         return '%s(%s)' % (cname, sublist) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def apply_parameters(cls, *subtypes): |     def apply_parameters(cls, subtypes, names=None): | ||||||
|         """ |         """ | ||||||
|         Given a set of other CassandraTypes, create a new subtype of this type |         Given a set of other CassandraTypes, create a new subtype of this type | ||||||
|         using them as parameters. This is how composite types are constructed. |         using them as parameters. This is how composite types are constructed. | ||||||
|  |  | ||||||
|             >>> MapType.apply_parameters(DateType, BooleanType) |             >>> MapType.apply_parameters(DateType, BooleanType) | ||||||
|             <class 'cassandra.types.MapType(DateType, BooleanType)'> |             <class 'cassandra.types.MapType(DateType, BooleanType)'> | ||||||
|  |  | ||||||
|  |         `subtypes` will be a sequence of CassandraTypes.  If provided, `names` | ||||||
|  |         will be an equally long sequence of column names or Nones. | ||||||
|         """ |         """ | ||||||
|         if cls.num_subtypes != 'UNKNOWN' and len(subtypes) != cls.num_subtypes: |         if cls.num_subtypes != 'UNKNOWN' and len(subtypes) != cls.num_subtypes: | ||||||
|             raise ValueError("%s types require %d subtypes (%d given)" |             raise ValueError("%s types require %d subtypes (%d given)" | ||||||
|   | |||||||
| @@ -499,11 +499,11 @@ class ResultMessage(_MessageType): | |||||||
|                                     " entire result set." % (optid,)) |                                     " entire result set." % (optid,)) | ||||||
|         if typeclass in (ListType, SetType): |         if typeclass in (ListType, SetType): | ||||||
|             subtype = cls.read_type(f) |             subtype = cls.read_type(f) | ||||||
|             typeclass = typeclass.apply_parameters(subtype) |             typeclass = typeclass.apply_parameters((subtype,)) | ||||||
|         elif typeclass == MapType: |         elif typeclass == MapType: | ||||||
|             keysubtype = cls.read_type(f) |             keysubtype = cls.read_type(f) | ||||||
|             valsubtype = cls.read_type(f) |             valsubtype = cls.read_type(f) | ||||||
|             typeclass = typeclass.apply_parameters(keysubtype, valsubtype) |             typeclass = typeclass.apply_parameters((keysubtype, valsubtype)) | ||||||
|         elif typeclass == CUSTOM_TYPE: |         elif typeclass == CUSTOM_TYPE: | ||||||
|             classname = read_string(f) |             classname = read_string(f) | ||||||
|             typeclass = lookup_casstype(classname) |             typeclass = lookup_casstype(classname) | ||||||
|   | |||||||
| @@ -283,7 +283,8 @@ class Metadata(object): | |||||||
|     def _build_column_metadata(self, table_metadata, row): |     def _build_column_metadata(self, table_metadata, row): | ||||||
|         name = row["column_name"] |         name = row["column_name"] | ||||||
|         data_type = types.lookup_casstype(row["validator"]) |         data_type = types.lookup_casstype(row["validator"]) | ||||||
|         column_meta = ColumnMetadata(table_metadata, name, data_type) |         is_static = row.get("type", None) == "static" | ||||||
|  |         column_meta = ColumnMetadata(table_metadata, name, data_type, is_static=is_static) | ||||||
|         index_meta = self._build_index_metadata(column_meta, row) |         index_meta = self._build_index_metadata(column_meta, row) | ||||||
|         column_meta.index = index_meta |         column_meta.index = index_meta | ||||||
|         return column_meta |         return column_meta | ||||||
| @@ -684,7 +685,7 @@ class TableMetadata(object): | |||||||
|  |  | ||||||
|         columns = [] |         columns = [] | ||||||
|         for col in self.columns.values(): |         for col in self.columns.values(): | ||||||
|             columns.append("%s %s" % (protect_name(col.name), col.typestring)) |             columns.append("%s %s%s" % (protect_name(col.name), col.typestring, ' static' if col.is_static else '')) | ||||||
|  |  | ||||||
|         if len(self.partition_key) == 1 and not self.clustering_key: |         if len(self.partition_key) == 1 and not self.clustering_key: | ||||||
|             columns[0] += " PRIMARY KEY" |             columns[0] += " PRIMARY KEY" | ||||||
| @@ -807,11 +808,18 @@ class ColumnMetadata(object): | |||||||
|     :class:`.IndexMetadata`, otherwise :const:`None`. |     :class:`.IndexMetadata`, otherwise :const:`None`. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, table_metadata, column_name, data_type, index_metadata=None): |     is_static = False | ||||||
|  |     """ | ||||||
|  |     If this column is static (available in Cassandra 2.1+), this will | ||||||
|  |     be :const:`True`, otherwise :const:`False`. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, table_metadata, column_name, data_type, index_metadata=None, is_static=False): | ||||||
|         self.table = table_metadata |         self.table = table_metadata | ||||||
|         self.name = column_name |         self.name = column_name | ||||||
|         self.data_type = data_type |         self.data_type = data_type | ||||||
|         self.index = index_metadata |         self.index = index_metadata | ||||||
|  |         self.is_static = is_static | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def typestring(self): |     def typestring(self): | ||||||
|   | |||||||
| @@ -25,5 +25,3 @@ | |||||||
|    :members: |    :members: | ||||||
|  |  | ||||||
| .. autoexception:: TraceUnavailable | .. autoexception:: TraceUnavailable | ||||||
|  |  | ||||||
| .. autoexception:: InvalidParameterTypeError |  | ||||||
|   | |||||||
| @@ -121,9 +121,7 @@ when you execute: | |||||||
|         ("John O'Reilly", 42, uuid.uuid1()) |         ("John O'Reilly", 42, uuid.uuid1()) | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
| It is translated to the following CQL query: | It is translated to the following CQL query:: | ||||||
|  |  | ||||||
| .. code-block:: |  | ||||||
|  |  | ||||||
|     INSERT INTO users (name, credits, user_id) |     INSERT INTO users (name, credits, user_id) | ||||||
|     VALUES ('John O''Reilly', 42, 2644bada-852c-11e3-89fb-e0b9a54a6d93) |     VALUES ('John O''Reilly', 42, 2644bada-852c-11e3-89fb-e0b9a54a6d93) | ||||||
|   | |||||||
| @@ -50,6 +50,7 @@ class MockCluster(object): | |||||||
|     reconnection_policy = ConstantReconnectionPolicy(2) |     reconnection_policy = ConstantReconnectionPolicy(2) | ||||||
|     down_host = None |     down_host = None | ||||||
|     contact_points = [] |     contact_points = [] | ||||||
|  |     is_shutdown = False | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.metadata = MockMetadata() |         self.metadata = MockMetadata() | ||||||
|   | |||||||
| @@ -1,8 +1,14 @@ | |||||||
| import unittest | try: | ||||||
|  |     import unittest2 as unittest | ||||||
|  | except ImportError: | ||||||
|  |     import unittest  # noqa | ||||||
|  |  | ||||||
|  | from binascii import unhexlify | ||||||
| import datetime | import datetime | ||||||
| import cassandra | import cassandra | ||||||
| from cassandra.cqltypes import (BooleanType, lookup_casstype_simple, lookup_casstype, | from cassandra.cqltypes import (BooleanType, lookup_casstype_simple, lookup_casstype, | ||||||
|                                 LongType, DecimalType, SetType, cql_typename) |                                 LongType, DecimalType, SetType, cql_typename, | ||||||
|  |                                 CassandraType, UTF8Type, parse_casstype_args) | ||||||
| from cassandra.query import named_tuple_factory | from cassandra.query import named_tuple_factory | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -83,9 +89,11 @@ class TypeTests(unittest.TestCase): | |||||||
|         self.assertEqual(SetType.cass_parameterized_type_with([DecimalType], full=True), 'org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.DecimalType)') |         self.assertEqual(SetType.cass_parameterized_type_with([DecimalType], full=True), 'org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.DecimalType)') | ||||||
|  |  | ||||||
|         self.assertEqual(LongType.cql_parameterized_type(), 'bigint') |         self.assertEqual(LongType.cql_parameterized_type(), 'bigint') | ||||||
|         self.assertEqual(cassandra.cqltypes.MapType.apply_parameters( |  | ||||||
|                          cassandra.cqltypes.UTF8Type, cassandra.cqltypes.UTF8Type).cql_parameterized_type(), |         subtypes = (cassandra.cqltypes.UTF8Type, cassandra.cqltypes.UTF8Type) | ||||||
|                          'map<text, text>') |         self.assertEqual( | ||||||
|  |                 'map<text, text>', | ||||||
|  |                 cassandra.cqltypes.MapType.apply_parameters(subtypes).cql_parameterized_type()) | ||||||
|  |  | ||||||
|     def test_datetype(self): |     def test_datetype(self): | ||||||
|         """ |         """ | ||||||
| @@ -113,3 +121,38 @@ class TypeTests(unittest.TestCase): | |||||||
|         self.assertEqual(result[1], result.applied) |         self.assertEqual(result[1], result.applied) | ||||||
|         self.assertEqual(result[2], result.func_func_abc) |         self.assertEqual(result[2], result.func_func_abc) | ||||||
|         self.assertEqual(result[3], result.foo_bar) |         self.assertEqual(result[3], result.foo_bar) | ||||||
|  |  | ||||||
|  |     def test_parse_casstype_args(self): | ||||||
|  |         class FooType(CassandraType): | ||||||
|  |             typename = 'org.apache.cassandra.db.marshal.FooType' | ||||||
|  |  | ||||||
|  |             def __init__(self, subtypes, names): | ||||||
|  |                 self.subtypes = subtypes | ||||||
|  |                 self.names = names | ||||||
|  |  | ||||||
|  |             @classmethod | ||||||
|  |             def apply_parameters(cls, subtypes, names): | ||||||
|  |                 return cls(subtypes, [unhexlify(name) if name is not None else name for name in names]) | ||||||
|  |  | ||||||
|  |         class BarType(FooType): | ||||||
|  |             typename = 'org.apache.cassandra.db.marshal.BarType' | ||||||
|  |  | ||||||
|  |         ctype = parse_casstype_args(''.join(( | ||||||
|  |             'org.apache.cassandra.db.marshal.FooType(', | ||||||
|  |                 '63697479:org.apache.cassandra.db.marshal.UTF8Type,', | ||||||
|  |                 'BarType(61646472657373:org.apache.cassandra.db.marshal.UTF8Type),', | ||||||
|  |                 '7a6970:org.apache.cassandra.db.marshal.UTF8Type', | ||||||
|  |             ')'))) | ||||||
|  |  | ||||||
|  |         self.assertEquals(FooType, ctype.__class__) | ||||||
|  |  | ||||||
|  |         self.assertEquals(UTF8Type, ctype.subtypes[0]) | ||||||
|  |  | ||||||
|  |         # middle subtype should be a BarType instance with its own subtypes and names | ||||||
|  |         self.assertIsInstance(ctype.subtypes[1], BarType) | ||||||
|  |         self.assertEquals([UTF8Type], ctype.subtypes[1].subtypes) | ||||||
|  |         self.assertEquals(["address"], ctype.subtypes[1].names) | ||||||
|  |  | ||||||
|  |         self.assertEquals(UTF8Type, ctype.subtypes[2]) | ||||||
|  |  | ||||||
|  |         self.assertEquals(['city', None, 'zip'], ctype.names) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Tyler Hobbs
					Tyler Hobbs