Skip to main content

drizzle_core/prepared/
owned.rs

1use crate::prelude::*;
2use crate::{
3    OwnedParam, ParamBind, SQL, SQLChunk, ToSQL,
4    prepared::{PreparedStatement, bind_values_internal},
5    traits::SQLParam,
6};
7use compact_str::CompactString;
8use core::fmt;
9use hashbrown::HashMap;
10use smallvec::SmallVec;
11
12/// A [`PreparedStatement`] that owns all its data, so it can be stored
13/// without a lifetime (for example in a cache).
14#[derive(Debug, Clone)]
15pub struct OwnedPreparedStatement<V: SQLParam> {
16    /// Rendered SQL text between the parameters; one more than `params`.
17    pub text_segments: Box<[CompactString]>,
18    /// The parameters, in order, with any values fixed at render time.
19    pub params: Box<[OwnedParam<V>]>,
20    /// The full SQL text, with the dialect's placeholders.
21    pub sql: CompactString,
22}
23impl<V: SQLParam> core::fmt::Display for OwnedPreparedStatement<V> {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "{}", self.sql())
26    }
27}
28
29impl<'a, V: SQLParam> From<PreparedStatement<'a, V>> for OwnedPreparedStatement<V> {
30    fn from(prepared: PreparedStatement<'a, V>) -> Self {
31        Self {
32            text_segments: prepared.text_segments,
33            params: prepared
34                .params
35                .into_iter()
36                .map(core::convert::Into::into)
37                .collect(),
38            sql: prepared.sql,
39        }
40    }
41}
42
43impl<V: SQLParam> OwnedPreparedStatement<V> {
44    /// Returns how many bindings [`bind`](Self::bind) expects.
45    ///
46    /// Counts parameters without a value, with each placeholder name counted
47    /// once, since one binding fills every use of a name.
48    #[must_use]
49    pub fn external_param_count(&self) -> usize {
50        let mut named = HashMap::<&str, ()>::new();
51        let mut positional = 0usize;
52        for param in &self.params {
53            if param.value.is_some() {
54                continue;
55            }
56            match param.placeholder.name {
57                Some(name) if !name.is_empty() => {
58                    named.entry(name).or_insert(());
59                }
60                _ => positional += 1,
61            }
62        }
63        named.len() + positional
64    }
65
66    /// Binds values to the placeholders and returns the SQL text with the
67    /// values to send, in order. Works like [`PreparedStatement::bind`].
68    ///
69    /// # Errors
70    ///
71    /// Returns [`DrizzleError::ParameterError`](crate::error::DrizzleError::ParameterError)
72    /// when a name is bound twice, a placeholder has no binding, or a binding
73    /// matches no placeholder.
74    pub fn bind<'a, T: SQLParam + Into<V>>(
75        &self,
76        param_binds: impl IntoIterator<Item = ParamBind<'a, T>>,
77    ) -> crate::error::Result<(&str, impl Iterator<Item = V>)> {
78        let bound_params = bind_values_internal(
79            &self.params,
80            param_binds,
81            |p| p.placeholder.name,
82            |p| p.value.as_ref(), // OwnedParam can store values
83        )?;
84
85        Ok((self.sql.as_str(), bound_params.into_iter()))
86    }
87
88    /// Returns the SQL text, with the dialect's placeholders.
89    #[must_use]
90    pub fn sql(&self) -> &str {
91        self.sql.as_str()
92    }
93}
94
95impl<'a, V: SQLParam> ToSQL<'a, V> for OwnedPreparedStatement<V> {
96    fn to_sql(&self) -> SQL<'a, V> {
97        // Calculate exact capacity needed: text_segments.len() + params.len()
98        let capacity = self.text_segments.len() + self.params.len();
99        let mut chunks = SmallVec::with_capacity(capacity);
100
101        // Interleave text segments and params: text[0], param[0], text[1], param[1], ..., text[n]
102        // Use iterators to avoid bounds checking and minimize allocations
103        let mut param_iter = self.params.iter();
104
105        for text_segment in &self.text_segments {
106            chunks.push(SQLChunk::Raw(Cow::Owned(text_segment.to_string())));
107
108            // Add corresponding param if available
109            if let Some(param) = param_iter.next() {
110                chunks.push(SQLChunk::Param(param.clone().into()));
111            }
112        }
113
114        SQL { chunks }
115    }
116}