drizzle-core 0.1.13

A type-safe SQL query builder for Rust
Documentation
use crate::prelude::*;
use crate::{
    OwnedParam, ParamBind, SQL, SQLChunk, ToSQL,
    prepared::{PreparedStatement, bind_values_internal},
    traits::SQLParam,
};
use compact_str::CompactString;
use core::fmt;
use hashbrown::HashMap;
use smallvec::SmallVec;

/// An owned version of `PreparedStatement` with no lifetime dependencies
#[derive(Debug, Clone)]
pub struct OwnedPreparedStatement<V: SQLParam> {
    /// Pre-rendered text segments
    pub text_segments: Box<[CompactString]>,
    /// Parameter placeholders (in order) - only placeholders, no values
    pub params: Box<[OwnedParam<V>]>,
    /// Fully rendered SQL with placeholders for this dialect
    pub sql: CompactString,
}
impl<V: SQLParam> core::fmt::Display for OwnedPreparedStatement<V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.sql())
    }
}

impl<'a, V: SQLParam> From<PreparedStatement<'a, V>> for OwnedPreparedStatement<V> {
    fn from(prepared: PreparedStatement<'a, V>) -> Self {
        Self {
            text_segments: prepared.text_segments,
            params: prepared
                .params
                .into_iter()
                .map(core::convert::Into::into)
                .collect(),
            sql: prepared.sql,
        }
    }
}

impl<V: SQLParam> OwnedPreparedStatement<V> {
    /// Returns the number of external parameter bindings expected.
    /// This counts params that need external binding (no pre-set value),
    /// deduplicating named params since one binding satisfies all uses.
    #[must_use]
    pub fn external_param_count(&self) -> usize {
        let mut named = HashMap::<&str, ()>::new();
        let mut positional = 0usize;
        for param in &self.params {
            if param.value.is_some() {
                continue;
            }
            match param.placeholder.name {
                Some(name) if !name.is_empty() => {
                    named.entry(name).or_insert(());
                }
                _ => positional += 1,
            }
        }
        named.len() + positional
    }

    /// Bind parameters and return SQL with dialect-appropriate placeholders.
    /// Uses `$1, $2, ...` for `PostgreSQL`, `?` for SQLite/MySQL.
    ///
    /// # Errors
    ///
    /// Returns an error if required parameters are missing or if a named
    /// placeholder cannot be resolved from the supplied bindings.
    pub fn bind<'a, T: SQLParam + Into<V>>(
        &self,
        param_binds: impl IntoIterator<Item = ParamBind<'a, T>>,
    ) -> crate::error::Result<(&str, impl Iterator<Item = V>)> {
        let bound_params = bind_values_internal(
            &self.params,
            param_binds,
            |p| p.placeholder.name,
            |p| p.value.as_ref(), // OwnedParam can store values
        )?;

        Ok((self.sql.as_str(), bound_params.into_iter()))
    }

    /// Returns the fully rendered SQL with placeholders.
    #[must_use]
    pub fn sql(&self) -> &str {
        self.sql.as_str()
    }
}

impl<'a, V: SQLParam> ToSQL<'a, V> for OwnedPreparedStatement<V> {
    fn to_sql(&self) -> SQL<'a, V> {
        // Calculate exact capacity needed: text_segments.len() + params.len()
        let capacity = self.text_segments.len() + self.params.len();
        let mut chunks = SmallVec::with_capacity(capacity);

        // Interleave text segments and params: text[0], param[0], text[1], param[1], ..., text[n]
        // Use iterators to avoid bounds checking and minimize allocations
        let mut param_iter = self.params.iter();

        for text_segment in &self.text_segments {
            chunks.push(SQLChunk::Raw(Cow::Owned(text_segment.to_string())));

            // Add corresponding param if available
            if let Some(param) = param_iter.next() {
                chunks.push(SQLChunk::Param(param.clone().into()));
            }
        }

        SQL { chunks }
    }
}