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/
|
||||
tests/integration/ccm
|
||||
setuptools*.tar.gz
|
||||
setuptools*.egg
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__))
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -25,5 +25,3 @@
|
||||
:members:
|
||||
|
||||
.. autoexception:: TraceUnavailable
|
||||
|
||||
.. autoexception:: InvalidParameterTypeError
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user