Skip to main content

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}