scylla/statement/prepared.rs
1//! Defines the [`PreparedStatement`] type, which represents a statement
2//! that has been prepared in advance on the server.
3
4use arc_swap::{ArcSwap, Guard};
5use bytes::{Bytes, BytesMut};
6use scylla_cql::frame::response::result::{
7 ColumnSpec, PartitionKeyIndex, ResultMetadata, TableSpec,
8};
9use scylla_cql::frame::types::RawValue;
10use scylla_cql::serialize::SerializationError;
11use scylla_cql::serialize::row::{RowSerializationContext, SerializeRow, SerializedValues};
12use smallvec::{SmallVec, smallvec};
13use std::convert::TryInto;
14use std::sync::Arc;
15use std::time::Duration;
16use thiserror::Error;
17use uuid::Uuid;
18
19use super::{PageSize, StatementConfig};
20use crate::client::execution_profile::ExecutionProfileHandle;
21use crate::errors::{BadQuery, ExecutionError};
22use crate::frame::response::result::{self, PreparedMetadata};
23use crate::frame::types::{Consistency, SerialConsistency};
24use crate::observability::history::HistoryListener;
25use crate::policies::load_balancing::LoadBalancingPolicy;
26use crate::policies::retry::RetryPolicy;
27use crate::response::query_result::ColumnSpecs;
28use crate::routing::Token;
29use crate::routing::partitioner::{Partitioner, PartitionerHasher, PartitionerName};
30use crate::statement::Statement;
31
32/// Parts which are needed to construct [PreparedStatement].
33///
34/// Kept separate for performance reasons, because constructing
35/// [PreparedStatement] involves allocations.
36///
37/// # Lifecycle of prepared statement
38///
39/// When PREPARE request is issued, RawPreparedStatement is returned,
40/// and later converted to PreparedStatement.
41/// PreparedStatement can be cloned. Clone is a new handle to the same
42/// underlying shared data - if the result metadata is updated, it is
43/// updated for all clones.
44/// PreparedStatement can be turned into UnconfiguredPreparedStatement.
45/// Similarly to clone, unconfigured statement is also a handle to the
46/// same shared data.
47/// UnconfiguredPreparedStatement can be used to create new PreparedStatement
48/// objects. Those are also handles to the same shared data.
49pub(crate) struct RawPreparedStatement<'statement> {
50 statement: &'statement Statement,
51 prepared_response: result::Prepared,
52 is_lwt: bool,
53 tracing_id: Option<Uuid>,
54}
55
56impl<'statement> RawPreparedStatement<'statement> {
57 pub(crate) fn new(
58 statement: &'statement Statement,
59 prepared_response: result::Prepared,
60 is_lwt: bool,
61 tracing_id: Option<Uuid>,
62 ) -> Self {
63 Self {
64 statement,
65 prepared_response,
66 is_lwt,
67 tracing_id,
68 }
69 }
70
71 pub(crate) fn get_id(&self) -> &Bytes {
72 &self.prepared_response.id
73 }
74
75 pub(crate) fn tracing_id(&self) -> Option<Uuid> {
76 self.tracing_id
77 }
78
79 pub(crate) fn into_response(self) -> result::Prepared {
80 self.prepared_response
81 }
82}
83
84/// Constructs the fully-fledged [PreparedStatement].
85///
86/// This involves allocations.
87impl RawPreparedStatement<'_> {
88 pub(crate) fn into_prepared_statement(self) -> PreparedStatement {
89 let Self {
90 statement,
91 prepared_response,
92 is_lwt,
93 tracing_id,
94 } = self;
95 let mut prepared_statement = PreparedStatement::new(
96 prepared_response.id,
97 is_lwt,
98 prepared_response.prepared_metadata,
99 Arc::new(prepared_response.result_metadata),
100 statement.contents.clone(),
101 statement.get_validated_page_size(),
102 statement.config.clone(),
103 );
104
105 if let Some(tracing_id) = tracing_id {
106 prepared_statement.prepare_tracing_ids.push(tracing_id);
107 }
108
109 prepared_statement
110 }
111}
112
113/// Represents a statement prepared on the server.
114///
115/// To prepare a statement, simply execute [`Session::prepare`](crate::client::session::Session::prepare).
116///
117/// If you plan on reusing the statement, or bounding some values to it during execution, always
118/// prefer using prepared statements over `Session::query_*` methods,
119/// e.g. [`Session::query_unpaged`](crate::client::session::Session::query_unpaged).
120///
121/// Benefits that prepared statements have to offer:
122/// * Performance - a prepared statement holds information about metadata
123/// that allows to carry out a statement execution in a type safe manner.
124/// When any of `Session::query_*` methods is called with non-empty bound values,
125/// the driver has to prepare the statement before execution (to provide type safety).
126/// This implies 2 round trips per [`Session::query_unpaged`](crate::client::session::Session::query_unpaged).
127/// On the other hand, the cost of [`Session::execute_unpaged`](crate::client::session::Session::execute_unpaged)
128/// is only 1 round trip.
129/// * Increased type-safety - bound values' types are validated with
130/// the [`PreparedMetadata`] received from the server during the serialization.
131/// * Improved load balancing - thanks to statement metadata, the driver is able
132/// to compute a set of destined replicas for the statement execution. These replicas
133/// will be preferred when choosing the node (and shard) to send the request to.
134/// * Result deserialization optimization - see [`PreparedStatement::set_use_cached_result_metadata`].
135///
136/// # Clone implementation
137/// Cloning a prepared statement is a cheap operation. It only
138/// requires copying a couple of small fields and some [Arc] pointers.
139/// Always prefer cloning over executing [`Session::prepare`](crate::client::session::Session::prepare)
140/// multiple times to save some roundtrips.
141///
142/// # Statement repreparation
143/// When schema is updated, the server is supposed to invalidate its
144/// prepared statement caches. Then, if client tries to execute a given statement,
145/// the server will respond with an error. Users should not worry about it, since
146/// the driver handles it properly and tries to reprepare the statement.
147/// However, there are some cases when client-side prepared statement should be dropped
148/// and prepared once again via [`Session::prepare`](crate::client::session::Session::prepare) -
149/// see the mention about altering schema below.
150///
151/// # Altering schema
152///
153/// PreparedStatement contains two types of metadata that are dependent on the schema:
154/// - Prepared metadata, which contains information about bind variables (names, types, pk indices).
155/// - Result metadata, which contains information about columns produced by executing the statement.
156///
157/// ## Prepared metadata
158///
159/// Prepared metadata is currently immutable in the driver, because there is no mechanism
160/// in CQL protocol (v4 or v5) to update it. The most typical reason for it to become stale
161/// is when one of bind markers is UDT, and you add a field to this UDT. If you then try
162/// to insert a value containing this new field, driver will return an error. You will need to drop
163/// the statement and prepare it again. Note that inserting UDT values not containing the new field
164/// will continue to work.
165/// More obscure way to make prepared metadata stale is to drop a column (that is one of bind markers)
166/// and re-add it with a different type.
167/// This is not allowed by Cassandra, but is by Scylla.
168///
169/// ## Result metadata
170///
171/// Result metadata is mutable in the driver. CQLv4 provides no reliable way to update it, but CQLv5 does.
172/// When executing a statement in CQLv5 driver sends ID of result metadata. If server detects that ID is stale,
173/// it will send new metadata.
174/// CQLv5 is not currently supported by the driver, but there is a Scylla protocol extension,
175/// supported by the driver, that backports this mechanism to CQLv4.
176///
177/// For DB implementations that don't support the extension (Cassandra, older versions of Scylla),
178/// you can use [`PreparedStatement::set_use_cached_result_metadata`] to decide if metadata
179/// should be sent with each response (performance penalty), or if the local metadata should be used.
180/// Rust Driver errs on the safe side here, so the option is disabled by default.
181/// Note that if you enable this option, and result metadata changes on server side, you risk various
182/// errors during deserialization, including but not limited to, silent data corruption.
183///
184/// Most common scenario where result metadata changes, is when you use `SELECT *` queries. Any
185/// column added to the table will result in incompatible data being returned. Other scenarios include
186/// adding / renaming UDT fields and changing type of a column.
187///
188/// For DB implementations supporting the extension, [`PreparedStatement::set_use_cached_result_metadata`]
189/// is ignored, and driver will always use cached metadata, because it is safe and more efficient.
190///
191/// ### CQL v4 protocol limitations
192///
193/// Why is the protocol-level support even needed? Why is it not enough to just reprepare
194/// the statement after server drops it, and update local metadatas then?
195/// It is because of multi-client scenarios.
196///
197/// In multi-client scenario, only the first client which reprepares the statement
198/// will receive the updated metadata from the server.
199/// The rest of the clients will still hold on the outdated metadata, and not even notice
200/// that statement was invalidated at some point.
201#[derive(Debug)]
202pub struct PreparedStatement {
203 pub(crate) config: StatementConfig,
204 /// Tracing IDs of all queries used to prepare this statement.
205 // TODO(2.0): Unpub this, move this to PreparedStatementSharedData, and expose a getter.
206 pub prepare_tracing_ids: Vec<Uuid>,
207
208 shared: Arc<PreparedStatementSharedData>,
209 page_size: PageSize,
210 partitioner_name: PartitionerName,
211}
212
213#[derive(Debug)]
214struct PreparedStatementSharedData {
215 id: Bytes,
216 metadata: PreparedMetadata,
217 initial_result_metadata: Arc<ResultMetadata<'static>>,
218 current_result_metadata: ArcSwap<ResultMetadata<'static>>,
219 statement: String,
220 is_confirmed_lwt: bool,
221}
222
223impl Clone for PreparedStatement {
224 fn clone(&self) -> Self {
225 Self {
226 config: self.config.clone(),
227 prepare_tracing_ids: Vec::new(),
228 shared: self.shared.clone(),
229 page_size: self.page_size,
230 partitioner_name: self.partitioner_name.clone(),
231 }
232 }
233}
234
235/// Stores a snapshot of current result metadata column specs.
236#[derive(Debug)]
237pub struct ColumnSpecsGuard {
238 result: Guard<Arc<ResultMetadata<'static>>>,
239}
240
241impl ColumnSpecsGuard {
242 /// Retrieves current result metadata column specs.
243 pub fn get(&self) -> ColumnSpecs<'_, 'static> {
244 ColumnSpecs::new(self.result.col_specs())
245 }
246}
247
248impl PreparedStatement {
249 fn new(
250 id: Bytes,
251 is_lwt: bool,
252 metadata: PreparedMetadata,
253 result_metadata: Arc<ResultMetadata<'static>>,
254 statement: String,
255 page_size: PageSize,
256 config: StatementConfig,
257 ) -> Self {
258 Self {
259 shared: Arc::new(PreparedStatementSharedData {
260 id,
261 metadata,
262 initial_result_metadata: Arc::clone(&result_metadata),
263 current_result_metadata: ArcSwap::from(result_metadata),
264 statement,
265 is_confirmed_lwt: is_lwt,
266 }),
267 prepare_tracing_ids: Vec::new(),
268 page_size,
269 partitioner_name: Default::default(),
270 config,
271 }
272 }
273
274 /// Retrieves the ID of this prepared statement.
275 pub fn get_id(&self) -> &Bytes {
276 &self.shared.id
277 }
278
279 /// Retrieves the statement string of this prepared statement.
280 pub fn get_statement(&self) -> &str {
281 &self.shared.statement
282 }
283
284 /// Sets the page size for this CQL query.
285 ///
286 /// Panics if given number is nonpositive.
287 pub fn set_page_size(&mut self, page_size: i32) {
288 self.page_size = page_size
289 .try_into()
290 .unwrap_or_else(|err| panic!("PreparedStatement::set_page_size: {err}"));
291 }
292
293 /// Returns the page size for this CQL query.
294 pub(crate) fn get_validated_page_size(&self) -> PageSize {
295 self.page_size
296 }
297
298 /// Returns the page size for this CQL query.
299 pub fn get_page_size(&self) -> i32 {
300 self.page_size.inner()
301 }
302
303 /// Gets tracing ids of queries used to prepare this statement
304 pub fn get_prepare_tracing_ids(&self) -> &[Uuid] {
305 &self.prepare_tracing_ids
306 }
307
308 /// Returns true if the prepared statement has necessary information
309 /// to be routed in a token-aware manner. If false, the query
310 /// will always be sent to a random node/shard.
311 pub fn is_token_aware(&self) -> bool {
312 !self.get_prepared_metadata().pk_indexes.is_empty()
313 }
314
315 /// Returns true if it is known that the prepared statement contains
316 /// a Lightweight Transaction. If so, the optimisation can be performed:
317 /// the query should be routed to the replicas in a predefined order
318 /// (i. e. always try first to contact replica A, then B if it fails,
319 /// then C, etc.). If false, the query should be routed normally.
320 /// Note: this a Scylla-specific optimisation. Therefore, the result
321 /// will be always false for Cassandra.
322 pub fn is_confirmed_lwt(&self) -> bool {
323 self.shared.is_confirmed_lwt
324 }
325
326 /// Computes the partition key of the target table from given values —
327 /// it assumes that all partition key columns are passed in values.
328 /// Partition keys have specific serialization rules.
329 /// Ref: <https://github.com/scylladb/scylla/blob/40adf38915b6d8f5314c621a94d694d172360833/compound_compat.hh#L33-L47>
330 ///
331 /// Note: this is no longer required to compute a token. This is because partitioners
332 /// are now stateful and stream-based, so they do not require materialised partition key.
333 /// Therefore, for computing a token, there are more efficient methods, such as
334 /// [Self::calculate_token()].
335 pub fn compute_partition_key(
336 &self,
337 bound_values: &impl SerializeRow,
338 ) -> Result<Bytes, PartitionKeyError> {
339 let serialized = self.serialize_values(bound_values)?;
340 let partition_key = self.extract_partition_key(&serialized)?;
341 let mut buf = BytesMut::new();
342 let mut writer = |chunk: &[u8]| buf.extend_from_slice(chunk);
343
344 partition_key.write_encoded_partition_key(&mut writer)?;
345
346 Ok(buf.freeze())
347 }
348
349 /// Determines which values constitute the partition key and puts them in order.
350 ///
351 /// This is a preparation step necessary for calculating token based on a prepared statement.
352 pub(crate) fn extract_partition_key<'ps>(
353 &'ps self,
354 bound_values: &'ps SerializedValues,
355 ) -> Result<PartitionKey<'ps>, PartitionKeyExtractionError> {
356 PartitionKey::new(self.get_prepared_metadata(), bound_values)
357 }
358
359 pub(crate) fn extract_partition_key_and_calculate_token<'ps>(
360 &'ps self,
361 partitioner_name: &'ps PartitionerName,
362 serialized_values: &'ps SerializedValues,
363 ) -> Result<Option<(PartitionKey<'ps>, Token)>, PartitionKeyError> {
364 if !self.is_token_aware() {
365 return Ok(None);
366 }
367
368 let partition_key = self.extract_partition_key(serialized_values)?;
369 let token = partition_key.calculate_token(partitioner_name)?;
370
371 Ok(Some((partition_key, token)))
372 }
373
374 /// Calculates the token for given prepared statement and values.
375 ///
376 /// Returns the token that would be computed for executing the provided
377 /// prepared statement with the provided values.
378 // As this function creates a `PartitionKey`, it is intended rather for external usage (by users).
379 // For internal purposes, `PartitionKey::calculate_token()` is preferred, as `PartitionKey`
380 // is either way used internally, among others for display in traces.
381 pub fn calculate_token(
382 &self,
383 values: &impl SerializeRow,
384 ) -> Result<Option<Token>, PartitionKeyError> {
385 self.calculate_token_untyped(&self.serialize_values(values)?)
386 }
387
388 // A version of calculate_token which skips serialization and uses SerializedValues directly.
389 // Not type-safe, so not exposed to users.
390 pub(crate) fn calculate_token_untyped(
391 &self,
392 values: &SerializedValues,
393 ) -> Result<Option<Token>, PartitionKeyError> {
394 self.extract_partition_key_and_calculate_token(&self.partitioner_name, values)
395 .map(|opt| opt.map(|(_pk, token)| token))
396 }
397
398 /// Return keyspace name and table name this statement is operating on.
399 pub fn get_table_spec(&self) -> Option<&TableSpec<'_>> {
400 self.get_prepared_metadata()
401 .col_specs
402 .first()
403 .map(|spec| spec.table_spec())
404 }
405
406 /// Returns the name of the keyspace this statement is operating on.
407 pub fn get_keyspace_name(&self) -> Option<&str> {
408 self.get_prepared_metadata()
409 .col_specs
410 .first()
411 .map(|col_spec| col_spec.table_spec().ks_name())
412 }
413
414 /// Returns the name of the table this statement is operating on.
415 pub fn get_table_name(&self) -> Option<&str> {
416 self.get_prepared_metadata()
417 .col_specs
418 .first()
419 .map(|col_spec| col_spec.table_spec().table_name())
420 }
421
422 /// Sets the consistency to be used when executing this statement.
423 pub fn set_consistency(&mut self, c: Consistency) {
424 self.config.consistency = Some(c);
425 }
426
427 /// Unsets the consistency overridden on this statement.
428 /// This means that consistency will be derived from the execution profile
429 /// (per-statement or, if absent, the default one).
430 pub fn unset_consistency(&mut self) {
431 self.config.consistency = None;
432 }
433
434 /// Gets the consistency to be used when executing this prepared statement if it is filled.
435 /// If this is empty, the default_consistency of the session will be used.
436 pub fn get_consistency(&self) -> Option<Consistency> {
437 self.config.consistency
438 }
439
440 /// Sets the serial consistency to be used when executing this statement.
441 /// (Ignored unless the statement is an LWT)
442 pub fn set_serial_consistency(&mut self, sc: Option<SerialConsistency>) {
443 self.config.serial_consistency = Some(sc);
444 }
445
446 /// Unsets the serial consistency overridden on this statement.
447 /// This means that serial consistency will be derived from the execution profile
448 /// (per-statement or, if absent, the default one).
449 pub fn unset_serial_consistency(&mut self) {
450 self.config.serial_consistency = None;
451 }
452
453 /// Gets the serial consistency to be used when executing this statement.
454 /// (Ignored unless the statement is an LWT)
455 pub fn get_serial_consistency(&self) -> Option<SerialConsistency> {
456 self.config.serial_consistency.flatten()
457 }
458
459 /// Sets the idempotence of this statement
460 /// A query is idempotent if it can be applied multiple times without changing the result of the initial application
461 /// If set to `true` we can be sure that it is idempotent
462 /// If set to `false` it is unknown whether it is idempotent
463 /// This is used in [`RetryPolicy`] to decide if retrying a query is safe
464 pub fn set_is_idempotent(&mut self, is_idempotent: bool) {
465 self.config.is_idempotent = is_idempotent;
466 }
467
468 /// Gets the idempotence of this statement
469 pub fn get_is_idempotent(&self) -> bool {
470 self.config.is_idempotent
471 }
472
473 /// Enable or disable CQL Tracing for this statement
474 /// If enabled session.execute() will return a QueryResult containing tracing_id
475 /// which can be used to query tracing information about the execution of this query
476 pub fn set_tracing(&mut self, should_trace: bool) {
477 self.config.tracing = should_trace;
478 }
479
480 /// Gets whether tracing is enabled for this statement
481 pub fn get_tracing(&self) -> bool {
482 self.config.tracing
483 }
484
485 /// Make use of cached metadata to decode results
486 /// of the statement's execution.
487 ///
488 /// If true, the driver will request the server not to
489 /// attach the result metadata in response to the statement execution.
490 ///
491 /// The driver will cache the result metadata received from the server
492 /// after statement preparation and will use it
493 /// to deserialize the results of statement execution.
494 ///
495 /// This option is false by default for DBs that don't support result metadata id extension.
496 /// For DBs that do (modern Scylla), this option is ignored, and cached result metadata is always used.
497 pub fn set_use_cached_result_metadata(&mut self, use_cached_metadata: bool) {
498 self.config.skip_result_metadata = use_cached_metadata;
499 }
500
501 /// Gets the information whether the driver uses cached metadata
502 /// to decode the results of the statement's execution.
503 pub fn get_use_cached_result_metadata(&self) -> bool {
504 self.config.skip_result_metadata
505 }
506
507 /// Sets the default timestamp for this statement in microseconds.
508 /// If not None, it will replace the server side assigned timestamp as default timestamp
509 /// If a statement contains a `USING TIMESTAMP` clause, calling this method won't change
510 /// anything
511 pub fn set_timestamp(&mut self, timestamp: Option<i64>) {
512 self.config.timestamp = timestamp
513 }
514
515 /// Gets the default timestamp for this statement in microseconds.
516 pub fn get_timestamp(&self) -> Option<i64> {
517 self.config.timestamp
518 }
519
520 /// Sets the client-side timeout for this statement.
521 /// If not None, the driver will stop waiting for the request
522 /// to finish after `timeout` passed.
523 /// Otherwise, execution profile timeout will be applied.
524 pub fn set_request_timeout(&mut self, timeout: Option<Duration>) {
525 self.config.request_timeout = timeout
526 }
527
528 /// Gets client timeout associated with this query
529 pub fn get_request_timeout(&self) -> Option<Duration> {
530 self.config.request_timeout
531 }
532
533 /// Sets the name of the partitioner used for this statement.
534 pub(crate) fn set_partitioner_name(&mut self, partitioner_name: PartitionerName) {
535 self.partitioner_name = partitioner_name;
536 }
537
538 /// Access metadata about the bind variables of this statement as returned by the database
539 pub(crate) fn get_prepared_metadata(&self) -> &PreparedMetadata {
540 &self.shared.metadata
541 }
542
543 /// Access column specifications of the bind variables of this statement
544 pub fn get_variable_col_specs(&self) -> ColumnSpecs<'_, 'static> {
545 ColumnSpecs::new(&self.shared.metadata.col_specs)
546 }
547
548 /// Access info about partition key indexes of the bind variables of this statement
549 pub fn get_variable_pk_indexes(&self) -> &[PartitionKeyIndex] {
550 &self.shared.metadata.pk_indexes
551 }
552
553 /// Access metadata about the result of prepared statement returned by the database
554 pub(crate) fn get_current_result_metadata(&self) -> Arc<ResultMetadata<'static>> {
555 self.shared.current_result_metadata.load_full()
556 }
557
558 /// Update metadata about the result of prepared statement.
559 // Will be used when we implement support for metadata id extension.
560 #[allow(dead_code)]
561 pub(crate) fn update_current_result_metadata(
562 &self,
563 new_metadata: Arc<ResultMetadata<'static>>,
564 ) {
565 self.shared.current_result_metadata.store(new_metadata);
566 }
567
568 /// Access column specifications of the result set returned after the preparation of this statement
569 ///
570 /// In 1.4.0, result metadata became mutable to allow us to update it when server
571 /// sends a new one (which happens in CQLv5, or CQLv4 with Scylla's metadata id extension). This method can't
572 /// be changed to support it because of Copy bound on ColumnSpecs. This method now uses metadata initially sent
573 /// by the server, which may be different than the one currently used. Please use get_current_result_set_col_specs instead."
574 // TODO(2.0): Remove this
575 #[deprecated(
576 since = "1.4.0",
577 note = "This method may return outdated metadata. Use get_current_result_set_col_specs() instead."
578 )]
579 pub fn get_result_set_col_specs(&self) -> ColumnSpecs<'_, 'static> {
580 ColumnSpecs::new(self.shared.initial_result_metadata.col_specs())
581 }
582
583 /// Access column specifications of the result set returned after the execution of this statement
584 pub fn get_current_result_set_col_specs(&self) -> ColumnSpecsGuard {
585 ColumnSpecsGuard {
586 result: self.shared.current_result_metadata.load(),
587 }
588 }
589
590 /// Get the name of the partitioner used for this statement.
591 pub fn get_partitioner_name(&self) -> &PartitionerName {
592 &self.partitioner_name
593 }
594
595 /// Set the retry policy for this statement, overriding the one from execution profile if not None.
596 #[inline]
597 pub fn set_retry_policy(&mut self, retry_policy: Option<Arc<dyn RetryPolicy>>) {
598 self.config.retry_policy = retry_policy;
599 }
600
601 /// Get the retry policy set for the statement.
602 ///
603 /// This method returns the retry policy that is **overridden** on this statement.
604 /// In other words, it returns the retry policy set using [`PreparedStatement::set_retry_policy`].
605 /// This does not take the retry policy from the set execution profile into account.
606 #[inline]
607 pub fn get_retry_policy(&self) -> Option<&Arc<dyn RetryPolicy>> {
608 self.config.retry_policy.as_ref()
609 }
610
611 /// Set the load balancing policy for this statement, overriding the one from execution profile if not None.
612 #[inline]
613 pub fn set_load_balancing_policy(
614 &mut self,
615 load_balancing_policy: Option<Arc<dyn LoadBalancingPolicy>>,
616 ) {
617 self.config.load_balancing_policy = load_balancing_policy;
618 }
619
620 /// Get the load balancing policy set for the statement.
621 ///
622 /// This method returns the load balancing policy that is **overridden** on this statement.
623 /// In other words, it returns the load balancing policy set using [`PreparedStatement::set_load_balancing_policy`].
624 /// This does not take the load balancing policy from the set execution profile into account.
625 #[inline]
626 pub fn get_load_balancing_policy(&self) -> Option<&Arc<dyn LoadBalancingPolicy>> {
627 self.config.load_balancing_policy.as_ref()
628 }
629
630 /// Sets the listener capable of listening what happens during query execution.
631 pub fn set_history_listener(&mut self, history_listener: Arc<dyn HistoryListener>) {
632 self.config.history_listener = Some(history_listener);
633 }
634
635 /// Removes the listener set by `set_history_listener`.
636 pub fn remove_history_listener(&mut self) -> Option<Arc<dyn HistoryListener>> {
637 self.config.history_listener.take()
638 }
639
640 /// Associates the query with execution profile referred by the provided handle.
641 /// Handle may be later remapped to another profile, and query will reflect those changes.
642 pub fn set_execution_profile_handle(&mut self, profile_handle: Option<ExecutionProfileHandle>) {
643 self.config.execution_profile_handle = profile_handle;
644 }
645
646 /// Borrows the execution profile handle associated with this query.
647 pub fn get_execution_profile_handle(&self) -> Option<&ExecutionProfileHandle> {
648 self.config.execution_profile_handle.as_ref()
649 }
650
651 pub(crate) fn serialize_values(
652 &self,
653 values: &impl SerializeRow,
654 ) -> Result<SerializedValues, SerializationError> {
655 let ctx = RowSerializationContext::from_prepared(self.get_prepared_metadata());
656 SerializedValues::from_serializable(&ctx, values)
657 }
658
659 pub(crate) fn make_unconfigured_handle(&self) -> UnconfiguredPreparedStatement {
660 UnconfiguredPreparedStatement {
661 shared: Arc::clone(&self.shared),
662 partitioner_name: self.get_partitioner_name().clone(),
663 }
664 }
665}
666
667/// This is a [PreparedStatement] without parts that are available to be configured
668/// on an unprepared [Statement]. It is intended to be used in [CachingSession] cache,
669/// as a type safety measue: it first needs to be configured with config taken from
670/// statement provided by the user.
671/// It contains partitioner_name field. It is configurable on prepared statement,
672/// but not on unprepared statement, so we need to keep it.
673#[derive(Debug)]
674pub(crate) struct UnconfiguredPreparedStatement {
675 shared: Arc<PreparedStatementSharedData>,
676 partitioner_name: PartitionerName,
677}
678
679impl UnconfiguredPreparedStatement {
680 pub(crate) fn make_configured_handle(
681 &self,
682 config: StatementConfig,
683 page_size: PageSize,
684 ) -> PreparedStatement {
685 PreparedStatement {
686 shared: Arc::clone(&self.shared),
687 prepare_tracing_ids: Vec::new(),
688 page_size,
689 partitioner_name: self.partitioner_name.clone(),
690 config,
691 }
692 }
693}
694
695/// Error when extracting partition key from bound values.
696#[derive(Clone, Debug, Error, PartialEq, Eq, PartialOrd, Ord)]
697#[non_exhaustive]
698pub enum PartitionKeyExtractionError {
699 /// No value with given partition key index was found in bound values.
700 #[error("No value with given pk_index! pk_index: {0}, values.len(): {1}")]
701 NoPkIndexValue(u16, u16),
702}
703
704/// Error when calculating token from partition key values.
705#[derive(Clone, Debug, Error, PartialEq, Eq, PartialOrd, Ord)]
706#[non_exhaustive]
707pub enum TokenCalculationError {
708 /// Value was too long to be used in partition key.
709 #[error("Value bytes too long to create partition key, max 65 535 allowed! value.len(): {0}")]
710 ValueTooLong(usize),
711}
712
713/// An error returned by [`PreparedStatement::compute_partition_key()`].
714#[derive(Clone, Debug, Error)]
715#[non_exhaustive]
716pub enum PartitionKeyError {
717 /// Failed to extract partition key.
718 #[error(transparent)]
719 PartitionKeyExtraction(#[from] PartitionKeyExtractionError),
720
721 /// Failed to calculate token.
722 #[error(transparent)]
723 TokenCalculation(#[from] TokenCalculationError),
724
725 /// Failed to serialize values required to compute partition key.
726 #[error(transparent)]
727 Serialization(#[from] SerializationError),
728}
729
730impl PartitionKeyError {
731 /// Converts the error to [`ExecutionError`].
732 pub fn into_execution_error(self) -> ExecutionError {
733 match self {
734 PartitionKeyError::PartitionKeyExtraction(_) => {
735 ExecutionError::BadQuery(BadQuery::PartitionKeyExtraction)
736 }
737 PartitionKeyError::TokenCalculation(TokenCalculationError::ValueTooLong(
738 values_len,
739 )) => {
740 ExecutionError::BadQuery(BadQuery::ValuesTooLongForKey(values_len, u16::MAX.into()))
741 }
742 PartitionKeyError::Serialization(err) => {
743 ExecutionError::BadQuery(BadQuery::SerializationError(err))
744 }
745 }
746 }
747}
748
749pub(crate) type PartitionKeyValue<'ps> = (&'ps [u8], &'ps ColumnSpec<'ps>);
750
751pub(crate) struct PartitionKey<'ps> {
752 pk_values: SmallVec<[Option<PartitionKeyValue<'ps>>; PartitionKey::SMALLVEC_ON_STACK_SIZE]>,
753}
754
755impl<'ps> PartitionKey<'ps> {
756 const SMALLVEC_ON_STACK_SIZE: usize = 8;
757
758 fn new(
759 prepared_metadata: &'ps PreparedMetadata,
760 bound_values: &'ps SerializedValues,
761 ) -> Result<Self, PartitionKeyExtractionError> {
762 // Iterate on values using sorted pk_indexes (see deser_prepared_metadata),
763 // and use PartitionKeyIndex.sequence to insert the value in pk_values with the correct order.
764 let mut pk_values: SmallVec<[_; PartitionKey::SMALLVEC_ON_STACK_SIZE]> =
765 smallvec![None; prepared_metadata.pk_indexes.len()];
766 let mut values_iter = bound_values.iter();
767 // pk_indexes contains the indexes of the partition key value, so the current offset of the
768 // iterator must be kept, in order to compute the next position of the pk in the iterator.
769 // At each iteration values_iter.nth(0) will roughly correspond to values[values_iter_offset],
770 // so values[pk_index.index] will be retrieved with values_iter.nth(pk_index.index - values_iter_offset)
771 let mut values_iter_offset = 0;
772 for pk_index in prepared_metadata.pk_indexes.iter().copied() {
773 // Find value matching current pk_index
774 let next_val = values_iter
775 .nth((pk_index.index - values_iter_offset) as usize)
776 .ok_or_else(|| {
777 PartitionKeyExtractionError::NoPkIndexValue(
778 pk_index.index,
779 bound_values.element_count(),
780 )
781 })?;
782 // Add it in sequence order to pk_values
783 if let RawValue::Value(v) = next_val {
784 let spec = &prepared_metadata.col_specs[pk_index.index as usize];
785 pk_values[pk_index.sequence as usize] = Some((v, spec));
786 }
787 values_iter_offset = pk_index.index + 1;
788 }
789 Ok(Self { pk_values })
790 }
791
792 pub(crate) fn iter(
793 &self,
794 ) -> impl Iterator<Item = PartitionKeyValue<'ps>> + Clone + use<'ps, '_> {
795 self.pk_values.iter().flatten().copied()
796 }
797
798 fn write_encoded_partition_key(
799 &self,
800 writer: &mut impl FnMut(&[u8]),
801 ) -> Result<(), TokenCalculationError> {
802 let mut pk_val_iter = self.iter().map(|(val, _spec)| val);
803 if let Some(first_value) = pk_val_iter.next() {
804 if let Some(second_value) = pk_val_iter.next() {
805 // Composite partition key case
806 for value in std::iter::once(first_value)
807 .chain(std::iter::once(second_value))
808 .chain(pk_val_iter)
809 {
810 let v_len_u16: u16 = value
811 .len()
812 .try_into()
813 .map_err(|_| TokenCalculationError::ValueTooLong(value.len()))?;
814 writer(&v_len_u16.to_be_bytes());
815 writer(value);
816 writer(&[0u8]);
817 }
818 } else {
819 // Single-value partition key case
820 writer(first_value);
821 }
822 }
823 Ok(())
824 }
825
826 pub(crate) fn calculate_token(
827 &self,
828 partitioner_name: &PartitionerName,
829 ) -> Result<Token, TokenCalculationError> {
830 let mut partitioner_hasher = partitioner_name.build_hasher();
831 let mut writer = |chunk: &[u8]| partitioner_hasher.write(chunk);
832
833 self.write_encoded_partition_key(&mut writer)?;
834
835 Ok(partitioner_hasher.finish())
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use scylla_cql::frame::response::result::{
842 ColumnSpec, ColumnType, NativeType, PartitionKeyIndex, PreparedMetadata, TableSpec,
843 };
844 use scylla_cql::serialize::row::SerializedValues;
845
846 use crate::statement::prepared::PartitionKey;
847 use crate::test_utils::setup_tracing;
848
849 fn make_meta(
850 cols: impl IntoIterator<Item = ColumnType<'static>>,
851 idx: impl IntoIterator<Item = usize>,
852 ) -> PreparedMetadata {
853 let table_spec = TableSpec::owned("ks".to_owned(), "t".to_owned());
854 let col_specs: Vec<_> = cols
855 .into_iter()
856 .enumerate()
857 .map(|(i, typ)| ColumnSpec::owned(format!("col_{i}"), typ, table_spec.clone()))
858 .collect();
859 let mut pk_indexes = idx
860 .into_iter()
861 .enumerate()
862 .map(|(sequence, index)| PartitionKeyIndex {
863 index: index as u16,
864 sequence: sequence as u16,
865 })
866 .collect::<Vec<_>>();
867 pk_indexes.sort_unstable_by_key(|pki| pki.index);
868 PreparedMetadata {
869 flags: 0,
870 col_count: col_specs.len(),
871 col_specs,
872 pk_indexes,
873 }
874 }
875
876 #[test]
877 fn test_partition_key_multiple_columns_shuffled() {
878 setup_tracing();
879 let meta = make_meta(
880 [
881 ColumnType::Native(NativeType::TinyInt),
882 ColumnType::Native(NativeType::SmallInt),
883 ColumnType::Native(NativeType::Int),
884 ColumnType::Native(NativeType::BigInt),
885 ColumnType::Native(NativeType::Blob),
886 ],
887 [4, 0, 3],
888 );
889 let mut values = SerializedValues::new();
890 values
891 .add_value(&67i8, &ColumnType::Native(NativeType::TinyInt))
892 .unwrap();
893 values
894 .add_value(&42i16, &ColumnType::Native(NativeType::SmallInt))
895 .unwrap();
896 values
897 .add_value(&23i32, &ColumnType::Native(NativeType::Int))
898 .unwrap();
899 values
900 .add_value(&89i64, &ColumnType::Native(NativeType::BigInt))
901 .unwrap();
902 values
903 .add_value(&[1u8, 2, 3, 4, 5], &ColumnType::Native(NativeType::Blob))
904 .unwrap();
905
906 let pk = PartitionKey::new(&meta, &values).unwrap();
907 let pk_cols = Vec::from_iter(pk.iter());
908 assert_eq!(
909 pk_cols,
910 vec![
911 ([1u8, 2, 3, 4, 5].as_slice(), &meta.col_specs[4]),
912 (67i8.to_be_bytes().as_ref(), &meta.col_specs[0]),
913 (89i64.to_be_bytes().as_ref(), &meta.col_specs[3]),
914 ]
915 );
916 }
917
918 #[test]
919 fn test_column_specs_guard_debug() {
920 use crate::statement::prepared::PreparedStatement;
921 use bytes::Bytes;
922 use scylla_cql::frame::response::result::ResultMetadata;
923
924 setup_tracing();
925
926 // Create a simple prepared statement with result metadata
927 let meta = make_meta([ColumnType::Native(NativeType::Int)], [0]);
928
929 // Create result metadata with actual column specs
930 let table_spec = TableSpec::owned("test_ks".to_owned(), "test_table".to_owned());
931 let col_specs = vec![ColumnSpec::owned(
932 "test_column_name".to_string(),
933 ColumnType::Native(NativeType::Text),
934 table_spec,
935 )];
936 let result_metadata = ResultMetadata::new_for_test(1, col_specs);
937 let prepared = PreparedStatement::new(
938 Bytes::from_static(b"test_id"),
939 false,
940 meta,
941 std::sync::Arc::new(result_metadata),
942 "SELECT * FROM test".to_string(),
943 crate::statement::PageSize::new(100).unwrap(),
944 Default::default(),
945 );
946
947 // Get the ColumnSpecsGuard and test Debug formatting
948 let guard = prepared.get_current_result_set_col_specs();
949 let debug_output = format!("{:?}", guard);
950
951 // Verify that Debug output contains expected structure and column name
952 assert!(debug_output.contains("ColumnSpecsGuard"));
953 assert!(debug_output.contains("test_column_name"));
954 }
955}