Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cqltypes: Serialize None values in collections as NULLs #1173

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions cassandra/cqltypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,9 +835,12 @@ def serialize_safe(cls, items, protocol_version):
buf.write(pack(len(items)))
inner_proto = max(3, protocol_version)
for item in items:
itembytes = subtype.to_binary(item, inner_proto)
buf.write(pack(len(itembytes)))
buf.write(itembytes)
if item is None:
buf.write(pack(-1))
else:
itembytes = subtype.to_binary(item, inner_proto)
buf.write(pack(len(itembytes)))
buf.write(itembytes)
return buf.getvalue()


Expand Down Expand Up @@ -905,12 +908,18 @@ def serialize_safe(cls, themap, protocol_version):
raise TypeError("Got a non-map object for a map value")
inner_proto = max(3, protocol_version)
for key, val in items:
keybytes = key_type.to_binary(key, inner_proto)
valbytes = value_type.to_binary(val, inner_proto)
buf.write(pack(len(keybytes)))
buf.write(keybytes)
buf.write(pack(len(valbytes)))
buf.write(valbytes)
if key is not None:
keybytes = key_type.to_binary(key, inner_proto)
buf.write(pack(len(keybytes)))
buf.write(keybytes)
else:
buf.write(pack(-1))
if val is not None:
valbytes = value_type.to_binary(val, inner_proto)
buf.write(pack(len(valbytes)))
buf.write(valbytes)
else:
buf.write(pack(-1))
return buf.getvalue()


Expand Down
47 changes: 46 additions & 1 deletion tests/integration/standard/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from cassandra.concurrent import execute_concurrent_with_args
from cassandra.cqltypes import Int32Type, EMPTY
from cassandra.query import dict_factory, ordered_dict_factory
from cassandra.util import sortedset, Duration
from cassandra.util import sortedset, Duration, OrderedMap
from tests.unit.cython.utils import cythontest

from tests.integration import use_singledc, execute_until_pass, notprotocolv1, \
Expand Down Expand Up @@ -723,6 +723,51 @@ def test_can_insert_tuples_with_nulls(self):
self.assertEqual(('', None, None, b''), result[0].t)
self.assertEqual(('', None, None, b''), s.execute(read)[0].t)

def test_insert_collection_with_null_fails(self):
"""
NULLs in list / sets / maps are forbidden.
This is a regression test - there was a bug that serialized None values
in collections as empty values instead of nulls.
"""
s = self.session
columns = []
for collection_type in ['list', 'set']:
for simple_type in PRIMITIVE_DATATYPES_KEYS:
columns.append(f'{collection_type}_{simple_type} {collection_type}<{simple_type}>')
for simple_type in PRIMITIVE_DATATYPES_KEYS:
columns.append(f'map_k_{simple_type} map<{simple_type}, ascii>')
columns.append(f'map_v_{simple_type} map<ascii, {simple_type}>')
s.execute(f'CREATE TABLE collection_nulls (k int PRIMARY KEY, {", ".join(columns)})')

def raises_simple_and_prepared(exc_type, query_str, args):
self.assertRaises(exc_type, lambda: s.execute(query_str, args))
p = s.prepare(query_str.replace('%s', '?'))
self.assertRaises(exc_type, lambda: s.execute(p, args))

i = 0
for simple_type in PRIMITIVE_DATATYPES_KEYS:
query_str = f'INSERT INTO collection_nulls (k, set_{simple_type}) VALUES (%s, %s)'
args = [i, sortedset([None, get_sample(simple_type)])]
raises_simple_and_prepared(InvalidRequest, query_str, args)
i += 1
for simple_type in PRIMITIVE_DATATYPES_KEYS:
query_str = f'INSERT INTO collection_nulls (k, list_{simple_type}) VALUES (%s, %s)'
args = [i, [None, get_sample(simple_type)]]
raises_simple_and_prepared(InvalidRequest, query_str, args)
i += 1
for simple_type in PRIMITIVE_DATATYPES_KEYS:
query_str = f'INSERT INTO collection_nulls (k, map_k_{simple_type}) VALUES (%s, %s)'
args = [i, OrderedMap([(get_sample(simple_type), 'abc'), (None, 'def')])]
raises_simple_and_prepared(InvalidRequest, query_str, args)
i += 1
for simple_type in PRIMITIVE_DATATYPES_KEYS:
query_str = f'INSERT INTO collection_nulls (k, map_v_{simple_type}) VALUES (%s, %s)'
args = [i, OrderedMap([('abc', None), ('def', get_sample(simple_type))])]
raises_simple_and_prepared(InvalidRequest, query_str, args)
i += 1



def test_can_insert_unicode_query_string(self):
"""
Test to ensure unicode strings can be used in a query
Expand Down