Merge branch 'master' into 2.0

Conflicts:
	tests/unit/test_types.py
This commit is contained in:
Tyler Hobbs
2014-03-06 18:33:16 -06:00
11 changed files with 112 additions and 41 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ cover/
docs/_build/
tests/integration/ccm
setuptools*.tar.gz
setuptools*.egg

View File

@@ -1,7 +1,21 @@
1.0.2
1.0.3
=====
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
---------
* With asyncorereactor, correctly handle EAGAIN/EWOULDBLOCK when the message from

View File

@@ -9,7 +9,7 @@ class NullHandler(logging.Handler):
logging.getLogger('cassandra').addHandler(NullHandler())
__version_info__ = (1, 0, 1, 'post')
__version_info__ = (1, 0, 2, 'post')
__version__ = '.'.join(map(str, __version_info__))

View File

@@ -254,7 +254,7 @@ class Cluster(object):
control_connection = None
scheduler = None
executor = None
_is_shutdown = False
is_shutdown = False
_is_setup = False
_prepared_statements = None
_prepared_statement_lock = Lock()
@@ -461,7 +461,7 @@ class Cluster(object):
operations on the ``Session``.
"""
with self._lock:
if self._is_shutdown:
if self.is_shutdown:
raise Exception("Cluster is already shut down")
if not self._is_setup:
@@ -502,10 +502,10 @@ class Cluster(object):
Once shutdown, a Cluster should not be used for any purpose.
"""
with self._lock:
if self._is_shutdown:
if self.is_shutdown:
raise Exception("The Cluster was already shutdown")
else:
self._is_shutdown = True
self.is_shutdown = True
if self.scheduler:
self.scheduler.shutdown()
@@ -525,7 +525,7 @@ class Cluster(object):
# Sessions while they are still being used (in case there are no
# longer any references to this Cluster object, but there are
# still references to the Session object)
if not self._is_shutdown:
if not self.is_shutdown:
if self.scheduler:
self.scheduler.shutdown()
if self.control_connection:
@@ -589,7 +589,7 @@ class Cluster(object):
"""
Intended for internal use only.
"""
if self._is_shutdown:
if self.is_shutdown:
return
host._handle_node_up_condition.acquire()
@@ -669,7 +669,7 @@ class Cluster(object):
"""
Intended for internal use only.
"""
if self._is_shutdown:
if self.is_shutdown:
return
with host.lock:
@@ -691,7 +691,7 @@ class Cluster(object):
self._start_reconnector(host, is_host_addition)
def on_add(self, host):
if self._is_shutdown:
if self.is_shutdown:
return
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()
def on_remove(self, host):
if self._is_shutdown:
if self.is_shutdown:
return
log.debug("Removing host %s", host)
@@ -1481,7 +1481,7 @@ class ControlConnection(object):
def _submit(self, *args, **kwargs):
try:
if not self._cluster._is_shutdown:
if not self._cluster.is_shutdown:
return self._cluster.executor.submit(*args, **kwargs)
except ReferenceError:
pass
@@ -1512,7 +1512,7 @@ class ControlConnection(object):
self._signal_error()
def _refresh_schema(self, connection, keyspace=None, table=None):
if self._cluster._is_shutdown:
if self._cluster.is_shutdown:
return
self.wait_for_schema_agreement(connection)

View File

@@ -108,24 +108,29 @@ def parse_casstype_args(typestring):
tokens, remainder = casstype_scanner.scan(typestring)
if remainder:
raise ValueError("weird characters %r at end" % remainder)
args = [[]]
# use a stack of (types, names) lists
args = [([], [])]
for tok in tokens:
if tok == '(':
args.append([])
args.append(([], []))
elif tok == ')':
arglist = args.pop()
ctype = args[-1].pop()
paramized = ctype.apply_parameters(*arglist)
args[-1].append(paramized)
types, names = args.pop()
prev_types, prev_names = args[-1]
prev_types[-1] = prev_types[-1].apply_parameters(types, names)
else:
types, names = args[-1]
if ':' in tok:
# ignore those column name hex encoding bit; we have the
# proper column name from elsewhere
tok = tok.rsplit(':', 1)[-1]
ctype = lookup_casstype_simple(tok)
args[-1].append(ctype)
name, tok = tok.rsplit(':', 1)
names.append(name)
else:
names.append(None)
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):
@@ -260,13 +265,16 @@ class _CassandraType(object):
return '%s(%s)' % (cname, sublist)
@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
using them as parameters. This is how composite types are constructed.
>>> MapType.apply_parameters(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:
raise ValueError("%s types require %d subtypes (%d given)"

View File

@@ -499,11 +499,11 @@ class ResultMessage(_MessageType):
" entire result set." % (optid,))
if typeclass in (ListType, SetType):
subtype = cls.read_type(f)
typeclass = typeclass.apply_parameters(subtype)
typeclass = typeclass.apply_parameters((subtype,))
elif typeclass == MapType:
keysubtype = 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:
classname = read_string(f)
typeclass = lookup_casstype(classname)

View File

@@ -283,7 +283,8 @@ class Metadata(object):
def _build_column_metadata(self, table_metadata, row):
name = row["column_name"]
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)
column_meta.index = index_meta
return column_meta
@@ -684,7 +685,7 @@ class TableMetadata(object):
columns = []
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:
columns[0] += " PRIMARY KEY"
@@ -807,11 +808,18 @@ class ColumnMetadata(object):
: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.name = column_name
self.data_type = data_type
self.index = index_metadata
self.is_static = is_static
@property
def typestring(self):

View File

@@ -25,5 +25,3 @@
:members:
.. autoexception:: TraceUnavailable
.. autoexception:: InvalidParameterTypeError

View File

@@ -121,9 +121,7 @@ when you execute:
("John O'Reilly", 42, uuid.uuid1())
)
It is translated to the following CQL query:
.. code-block::
It is translated to the following CQL query::
INSERT INTO users (name, credits, user_id)
VALUES ('John O''Reilly', 42, 2644bada-852c-11e3-89fb-e0b9a54a6d93)

View File

@@ -50,6 +50,7 @@ class MockCluster(object):
reconnection_policy = ConstantReconnectionPolicy(2)
down_host = None
contact_points = []
is_shutdown = False
def __init__(self):
self.metadata = MockMetadata()

View File

@@ -1,8 +1,14 @@
import unittest
try:
import unittest2 as unittest
except ImportError:
import unittest # noqa
from binascii import unhexlify
import datetime
import cassandra
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
@@ -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(LongType.cql_parameterized_type(), 'bigint')
self.assertEqual(cassandra.cqltypes.MapType.apply_parameters(
cassandra.cqltypes.UTF8Type, cassandra.cqltypes.UTF8Type).cql_parameterized_type(),
'map<text, text>')
subtypes = (cassandra.cqltypes.UTF8Type, cassandra.cqltypes.UTF8Type)
self.assertEqual(
'map<text, text>',
cassandra.cqltypes.MapType.apply_parameters(subtypes).cql_parameterized_type())
def test_datetype(self):
"""
@@ -113,3 +121,38 @@ class TypeTests(unittest.TestCase):
self.assertEqual(result[1], result.applied)
self.assertEqual(result[2], result.func_func_abc)
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)