Skip to content

Commit a4ece54

Browse files
committed
perf: use pre-allocated constant for null sentinel in collection serialization
Replace per-call int32_pack(-1) with a module-level _INT32_NULL constant in serialize methods of ListType, SetType, MapType, TupleType, and UserType. This avoids a struct.pack call on every null element during collection serialization. Affected sites: - _SimpleParameterizedType.serialize_safe (ListType/SetType) - MapType.serialize_safe (null key + null value) - TupleType.serialize_safe - UserType.serialize_safe
1 parent 153c913 commit a4ece54

2 files changed

Lines changed: 58 additions & 6 deletions

File tree

cassandra/cqltypes.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from cassandra import util
5353

5454
_little_endian_flag = 1 # we always serialize LE
55+
_INT32_NULL = int32_pack(-1) # pre-allocated null sentinel for collection serialization
5556
import ipaddress
5657

5758
apache_cassandra_type_prefix = 'org.apache.cassandra.db.marshal.'
@@ -841,7 +842,7 @@ def serialize_safe(cls, items, protocol_version):
841842
inner_proto = max(3, protocol_version)
842843
for item in items:
843844
if item is None:
844-
buf.write(int32_pack(-1))
845+
buf.write(_INT32_NULL)
845846
else:
846847
itembytes = subtype.to_binary(item, inner_proto)
847848
buf.write(int32_pack(len(itembytes)))
@@ -912,13 +913,13 @@ def serialize_safe(cls, themap, protocol_version):
912913
buf.write(int32_pack(len(keybytes)))
913914
buf.write(keybytes)
914915
else:
915-
buf.write(int32_pack(-1))
916+
buf.write(_INT32_NULL)
916917
if val is not None:
917918
valbytes = value_type.to_binary(val, inner_proto)
918919
buf.write(int32_pack(len(valbytes)))
919920
buf.write(valbytes)
920921
else:
921-
buf.write(int32_pack(-1))
922+
buf.write(_INT32_NULL)
922923
return buf.getvalue()
923924

924925

@@ -964,7 +965,7 @@ def serialize_safe(cls, val, protocol_version):
964965
buf.write(int32_pack(len(packed_item)))
965966
buf.write(packed_item)
966967
else:
967-
buf.write(int32_pack(-1))
968+
buf.write(_INT32_NULL)
968969
return buf.getvalue()
969970

970971
@classmethod
@@ -1041,7 +1042,7 @@ def serialize_safe(cls, val, protocol_version):
10411042
buf.write(int32_pack(len(packed_item)))
10421043
buf.write(packed_item)
10431044
else:
1044-
buf.write(int32_pack(-1))
1045+
buf.write(_INT32_NULL)
10451046
return buf.getvalue()
10461047

10471048
@classmethod

tests/unit/test_types.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
EmptyValue, LongType, SetType, UTF8Type,
2727
cql_typename, int8_pack, int64_pack, int64_unpack, lookup_casstype,
2828
lookup_casstype_simple, parse_casstype_args,
29-
int32_pack, Int32Type, ListType, MapType, VectorType,
29+
int32_pack, Int32Type, ListType, MapType, TupleType, UserType, VectorType,
3030
FloatType
3131
)
3232
from cassandra.encoder import cql_quote
@@ -1117,3 +1117,54 @@ def test_token_order(self):
11171117
tokens_equal = [Token(1), Token(1)]
11181118
check_sequence_consistency(tokens)
11191119
check_sequence_consistency(tokens_equal, equal=True)
1120+
1121+
1122+
class CollectionNullSentinelTests(unittest.TestCase):
1123+
"""
1124+
Tests that collection types correctly round-trip None/null elements
1125+
using the pre-allocated _INT32_NULL sentinel.
1126+
"""
1127+
1128+
def test_list_with_none_roundtrip(self):
1129+
proto = 4
1130+
parameterized = ListType.apply_parameters([Int32Type])
1131+
original = [1, None, 3]
1132+
serialized = parameterized.serialize(original, proto)
1133+
deserialized = parameterized.deserialize(serialized, proto)
1134+
self.assertEqual(deserialized, original)
1135+
1136+
def test_set_with_none_roundtrip(self):
1137+
proto = 4
1138+
parameterized = SetType.apply_parameters([Int32Type])
1139+
original = [1, None, 3]
1140+
serialized = parameterized.serialize(original, proto)
1141+
deserialized = parameterized.deserialize(serialized, proto)
1142+
self.assertEqual(set(deserialized), set(original))
1143+
1144+
def test_map_with_none_values_roundtrip(self):
1145+
proto = 4
1146+
parameterized = MapType.apply_parameters([Int32Type, Int32Type])
1147+
original = {1: None, 2: 10}
1148+
serialized = parameterized.serialize(original, proto)
1149+
deserialized = parameterized.deserialize(serialized, proto)
1150+
self.assertEqual(dict(deserialized.items()), original)
1151+
1152+
def test_tuple_with_none_roundtrip(self):
1153+
proto = 4
1154+
parameterized = TupleType.apply_parameters([Int32Type, UTF8Type, Int32Type])
1155+
original = (1, None, 3)
1156+
serialized = parameterized.serialize(original, proto)
1157+
deserialized = parameterized.deserialize(serialized, proto)
1158+
self.assertEqual(deserialized, original)
1159+
1160+
def test_usertype_with_none_roundtrip(self):
1161+
proto = 4
1162+
udt_class = UserType.make_udt_class(
1163+
'test_ks', 'test_udt',
1164+
('field_a', 'field_b', 'field_c'),
1165+
(Int32Type, UTF8Type, Int32Type)
1166+
)
1167+
original = (1, None, 3)
1168+
serialized = udt_class.serialize(original, proto)
1169+
deserialized = udt_class.deserialize(serialized, proto)
1170+
self.assertEqual(deserialized, original)

0 commit comments

Comments
 (0)