Skip to content

Commit 8b859fd

Browse files
committed
(improvement) query: add Cython-aware serializer path in BoundStatement.bind()
When Cython serializers (from cassandra.serializers) are available and no column encryption policy is active, BoundStatement.bind() now uses pre-built Serializer objects cached on the PreparedStatement instead of calling cqltype classmethods. This avoids per-value Python method dispatch overhead and enables the ~30x vector serialization speedup from the Cython serializers module. The bind loop is split into three paths: 1. Column encryption policy path (unchanged behavior) 2. Cython serializers path (new fast path) 3. Plain Python path (no CE, no Cython -- removes per-value ColDesc/CE check) Depends on PR scylladb#748 (Cython serializers module) and PR scylladb#630 (CE-policy bind split).
1 parent 70a0193 commit 8b859fd

1 file changed

Lines changed: 87 additions & 20 deletions

File tree

cassandra/query.py

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
from cassandra.protocol import _UNSET_VALUE
3434
from cassandra.util import OrderedDict, _sanitize_identifiers
3535

36+
try:
37+
from cassandra.serializers import make_serializers as _cython_make_serializers
38+
_HAVE_CYTHON_SERIALIZERS = True
39+
except ImportError:
40+
_HAVE_CYTHON_SERIALIZERS = False
41+
3642
import logging
3743
log = logging.getLogger(__name__)
3844

@@ -474,6 +480,24 @@ def __init__(self, column_metadata, query_id, routing_key_indexes, query,
474480
self.is_idempotent = False
475481
self._is_lwt = is_lwt
476482

483+
@property
484+
def _serializers(self):
485+
"""Lazily create and cache Cython serializers for column types.
486+
487+
Returns a list of Serializer objects if Cython serializers are available
488+
and there is no column encryption policy, otherwise returns None.
489+
"""
490+
try:
491+
return self.__serializers
492+
except AttributeError:
493+
pass
494+
if _HAVE_CYTHON_SERIALIZERS and not self.column_encryption_policy and self.column_metadata:
495+
self.__serializers = _cython_make_serializers(
496+
[col.type for col in self.column_metadata])
497+
else:
498+
self.__serializers = None
499+
return self.__serializers
500+
477501
@classmethod
478502
def from_message(cls, query_id, column_metadata, pk_indexes, cluster_metadata,
479503
query, prepared_keyspace, protocol_version, result_metadata,
@@ -636,28 +660,71 @@ def bind(self, values):
636660

637661
self.raw_values = values
638662
self.values = []
639-
for value, col_spec in zip(values, col_meta):
640-
if value is None:
641-
self.values.append(None)
642-
elif value is UNSET_VALUE:
643-
if proto_version >= 4:
644-
self._append_unset_value()
663+
if ce_policy:
664+
# Column encryption path: check each column for CE policy
665+
for value, col_spec in zip(values, col_meta):
666+
if value is None:
667+
self.values.append(None)
668+
elif value is UNSET_VALUE:
669+
if proto_version >= 4:
670+
self._append_unset_value()
671+
else:
672+
raise ValueError("Attempt to bind UNSET_VALUE while using unsuitable protocol version (%d < 4)" % proto_version)
645673
else:
646-
raise ValueError("Attempt to bind UNSET_VALUE while using unsuitable protocol version (%d < 4)" % proto_version)
674+
try:
675+
col_desc = ColDesc(col_spec.keyspace_name, col_spec.table_name, col_spec.name)
676+
uses_ce = ce_policy.contains_column(col_desc)
677+
if uses_ce:
678+
col_type = ce_policy.column_type(col_desc)
679+
col_bytes = col_type.serialize(value, proto_version)
680+
col_bytes = ce_policy.encrypt(col_desc, col_bytes)
681+
else:
682+
col_bytes = col_spec.type.serialize(value, proto_version)
683+
self.values.append(col_bytes)
684+
except (TypeError, struct.error) as exc:
685+
actual_type = type(value)
686+
message = ('Received an argument of invalid type for column "%s". '
687+
'Expected: %s, Got: %s; (%s)' % (col_spec.name, col_spec.type, actual_type, exc))
688+
raise TypeError(message)
689+
else:
690+
# Fast path: no column encryption, use Cython serializers if available
691+
serializers = self.prepared_statement._serializers
692+
if serializers is not None:
693+
for ser, value, col_spec in zip(serializers, values, col_meta):
694+
if value is None:
695+
self.values.append(None)
696+
elif value is UNSET_VALUE:
697+
if proto_version >= 4:
698+
self._append_unset_value()
699+
else:
700+
raise ValueError("Attempt to bind UNSET_VALUE while using unsuitable protocol version (%d < 4)" % proto_version)
701+
else:
702+
try:
703+
col_bytes = ser.serialize(value, proto_version)
704+
self.values.append(col_bytes)
705+
except (TypeError, struct.error) as exc:
706+
actual_type = type(value)
707+
message = ('Received an argument of invalid type for column "%s". '
708+
'Expected: %s, Got: %s; (%s)' % (col_spec.name, col_spec.type, actual_type, exc))
709+
raise TypeError(message)
647710
else:
648-
try:
649-
col_desc = ColDesc(col_spec.keyspace_name, col_spec.table_name, col_spec.name)
650-
uses_ce = ce_policy and ce_policy.contains_column(col_desc)
651-
col_type = ce_policy.column_type(col_desc) if uses_ce else col_spec.type
652-
col_bytes = col_type.serialize(value, proto_version)
653-
if uses_ce:
654-
col_bytes = ce_policy.encrypt(col_desc, col_bytes)
655-
self.values.append(col_bytes)
656-
except (TypeError, struct.error) as exc:
657-
actual_type = type(value)
658-
message = ('Received an argument of invalid type for column "%s". '
659-
'Expected: %s, Got: %s; (%s)' % (col_spec.name, col_spec.type, actual_type, exc))
660-
raise TypeError(message)
711+
for value, col_spec in zip(values, col_meta):
712+
if value is None:
713+
self.values.append(None)
714+
elif value is UNSET_VALUE:
715+
if proto_version >= 4:
716+
self._append_unset_value()
717+
else:
718+
raise ValueError("Attempt to bind UNSET_VALUE while using unsuitable protocol version (%d < 4)" % proto_version)
719+
else:
720+
try:
721+
col_bytes = col_spec.type.serialize(value, proto_version)
722+
self.values.append(col_bytes)
723+
except (TypeError, struct.error) as exc:
724+
actual_type = type(value)
725+
message = ('Received an argument of invalid type for column "%s". '
726+
'Expected: %s, Got: %s; (%s)' % (col_spec.name, col_spec.type, actual_type, exc))
727+
raise TypeError(message)
661728

662729
if proto_version >= 4:
663730
diff = col_meta_len - len(self.values)

0 commit comments

Comments
 (0)