Skip to content

Commit d5fb42a

Browse files
committed
perf: pre-allocate values list in BoundStatement.bind()
Replace empty list + repeated append() with pre-allocated list and index assignment in BoundStatement.bind(). For protocol v4+, the list is initialized to [UNSET_VALUE] * col_meta_len, eliminating the separate trailing UNSET_VALUE padding loop entirely. For protocol v3, the list is initialized to [None] * value_len. This avoids repeated list resizing and reduces Python bytecode overhead per bound value (index assignment vs method lookup + call for append). The routing key validation for UNSET_VALUE is preserved: explicit UNSET_VALUE binds are checked inline, and implicitly padded trailing columns are validated in a separate loop after the main bind loop. Part of: #751
1 parent 9c53d78 commit d5fb42a

1 file changed

Lines changed: 24 additions & 10 deletions

File tree

cassandra/query.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -635,13 +635,24 @@ def bind(self, values):
635635
(value_len, len(self.prepared_statement.routing_key_indexes)))
636636

637637
self.raw_values = values
638-
self.values = []
639-
for value, col_spec in zip(values, col_meta):
638+
639+
# Pre-allocate the values list to avoid repeated append() calls and
640+
# list resizing. For proto v4+, trailing unbound columns default to
641+
# UNSET_VALUE; for proto v3, we only allocate for provided values.
642+
if proto_version >= 4:
643+
result = [UNSET_VALUE] * col_meta_len
644+
else:
645+
result = [None] * value_len
646+
647+
for i, (value, col_spec) in enumerate(zip(values, col_meta)):
640648
if value is None:
641-
self.values.append(None)
649+
result[i] = None
642650
elif value is UNSET_VALUE:
643651
if proto_version >= 4:
644-
self._append_unset_value()
652+
if self.prepared_statement.is_routing_key_index(i):
653+
raise ValueError(
654+
"Cannot bind UNSET_VALUE as a part of the routing key '%s'" % col_spec.name)
655+
# result[i] is already UNSET_VALUE from pre-allocation
645656
else:
646657
raise ValueError("Attempt to bind UNSET_VALUE while using unsuitable protocol version (%d < 4)" % proto_version)
647658
else:
@@ -652,18 +663,21 @@ def bind(self, values):
652663
col_bytes = col_type.serialize(value, proto_version)
653664
if uses_ce:
654665
col_bytes = ce_policy.encrypt(col_desc, col_bytes)
655-
self.values.append(col_bytes)
666+
result[i] = col_bytes
656667
except (TypeError, struct.error) as exc:
657668
actual_type = type(value)
658669
message = ('Received an argument of invalid type for column "%s". '
659670
'Expected: %s, Got: %s; (%s)' % (col_spec.name, col_spec.type, actual_type, exc))
660671
raise TypeError(message)
661672

662-
if proto_version >= 4:
663-
diff = col_meta_len - len(self.values)
664-
if diff:
665-
for _ in range(diff):
666-
self._append_unset_value()
673+
# Validate that trailing UNSET_VALUE padding doesn't cover routing key columns
674+
if proto_version >= 4 and value_len < col_meta_len:
675+
for i in range(value_len, col_meta_len):
676+
if self.prepared_statement.is_routing_key_index(i):
677+
raise ValueError(
678+
"Cannot bind UNSET_VALUE as a part of the routing key '%s'" % col_meta[i].name)
679+
680+
self.values = result
667681

668682
return self
669683

0 commit comments

Comments
 (0)