Skip to main content

sql_middleware/sqlite/
params.rs

1use std::fmt::Write;
2
3use rusqlite;
4
5use crate::middleware::{ConversionMode, ParamConverter, RowValues, SqlMiddlewareDbError};
6
7// Thread-local buffer for efficient timestamp formatting
8thread_local! {
9    static TIMESTAMP_BUF: std::cell::RefCell<String> = std::cell::RefCell::new(String::with_capacity(32));
10}
11
12/// Convert a single `RowValue` to a rusqlite `Value`.
13#[must_use]
14pub fn row_value_to_sqlite_value(value: &RowValues, for_execute: bool) -> rusqlite::types::Value {
15    match value {
16        RowValues::Int(i) => rusqlite::types::Value::Integer(*i),
17        RowValues::Float(f) => rusqlite::types::Value::Real(*f),
18        RowValues::Text(s) => {
19            if for_execute {
20                // For execute, we can move the owned String directly
21                rusqlite::types::Value::Text(s.clone())
22            } else {
23                // For queries, we need to clone
24                rusqlite::types::Value::Text(s.clone())
25            }
26        }
27        RowValues::Bool(b) => rusqlite::types::Value::Integer(i64::from(*b)),
28        // Format timestamps once for better performance
29        RowValues::Timestamp(dt) => {
30            // Use a thread_local buffer for timestamp formatting to avoid allocation
31            TIMESTAMP_BUF.with(|buf| {
32                let mut borrow = buf.borrow_mut();
33                borrow.clear();
34                // Format directly into the string buffer
35                write!(borrow, "{}", dt.format("%F %T%.f")).unwrap();
36                rusqlite::types::Value::Text(borrow.clone())
37            })
38        }
39        RowValues::Null => rusqlite::types::Value::Null,
40        RowValues::JSON(jval) => {
41            // Only serialize once to avoid multiple allocations
42            let json_str = jval.to_string();
43            rusqlite::types::Value::Text(json_str)
44        }
45        RowValues::Blob(bytes) => {
46            if for_execute {
47                // For execute, we can directly use the bytes
48                rusqlite::types::Value::Blob(bytes.clone())
49            } else {
50                rusqlite::types::Value::Blob(bytes.clone())
51            }
52        }
53    }
54}
55
56/// Unified `SQLite` parameter container.
57pub struct Params(pub Vec<rusqlite::types::Value>);
58
59impl Params {
60    /// Convert middleware row values into `SQLite` values.
61    ///
62    /// # Errors
63    ///
64    /// Returns `SqlMiddlewareDbError::ConversionError` if parameter conversion fails.
65    pub fn convert(params: &[RowValues]) -> Result<Self, SqlMiddlewareDbError> {
66        let mut vec_values = Vec::with_capacity(params.len());
67        for p in params {
68            vec_values.push(row_value_to_sqlite_value(p, true));
69        }
70        Ok(Params(vec_values))
71    }
72
73    /// Borrow the underlying values.
74    #[must_use]
75    pub fn as_values(&self) -> &[rusqlite::types::Value] {
76        &self.0
77    }
78
79    /// Build a borrowed params slice suitable for rusqlite execution.
80    #[must_use]
81    pub fn as_refs(&self) -> Vec<&dyn rusqlite::ToSql> {
82        self.0.iter().map(|v| v as &dyn rusqlite::ToSql).collect()
83    }
84}
85
86/// Reusable SQLite parameter buffer for hot prepared-statement loops.
87///
88/// Callers can allocate once, mutate values in place with the `set_*` methods, and pass the
89/// buffer to `SqlitePreparedStatement::*_params` methods without rebuilding driver values from
90/// `RowValues` on every iteration.
91#[derive(Debug, Clone, Default)]
92pub struct SqliteParamsBuf {
93    values: Vec<rusqlite::types::Value>,
94}
95
96impl SqliteParamsBuf {
97    /// Create an empty buffer with room for `capacity` parameters.
98    #[must_use]
99    pub fn with_capacity(capacity: usize) -> Self {
100        Self {
101            values: Vec::with_capacity(capacity),
102        }
103    }
104
105    /// Remove all values while keeping allocated capacity.
106    pub fn clear(&mut self) {
107        self.values.clear();
108    }
109
110    /// Number of parameters currently in the buffer.
111    #[must_use]
112    pub fn len(&self) -> usize {
113        self.values.len()
114    }
115
116    /// Whether the buffer has no parameters.
117    #[must_use]
118    pub fn is_empty(&self) -> bool {
119        self.values.is_empty()
120    }
121
122    /// Borrow the driver-native values.
123    #[must_use]
124    pub fn as_values(&self) -> &[rusqlite::types::Value] {
125        &self.values
126    }
127
128    /// Set an integer parameter at zero-based `index`.
129    pub fn set_int(&mut self, index: usize, value: i64) {
130        self.set_value(index, rusqlite::types::Value::Integer(value));
131    }
132
133    /// Set a floating-point parameter at zero-based `index`.
134    pub fn set_float(&mut self, index: usize, value: f64) {
135        self.set_value(index, rusqlite::types::Value::Real(value));
136    }
137
138    /// Set a text parameter at zero-based `index`.
139    pub fn set_text(&mut self, index: usize, value: impl Into<String>) {
140        self.set_value(index, rusqlite::types::Value::Text(value.into()));
141    }
142
143    /// Set a boolean parameter at zero-based `index`.
144    pub fn set_bool(&mut self, index: usize, value: bool) {
145        self.set_value(index, rusqlite::types::Value::Integer(i64::from(value)));
146    }
147
148    /// Set a timestamp parameter at zero-based `index`.
149    pub fn set_timestamp(&mut self, index: usize, value: chrono::NaiveDateTime) {
150        self.set_value(
151            index,
152            row_value_to_sqlite_value(&RowValues::Timestamp(value), true),
153        );
154    }
155
156    /// Set a JSON parameter at zero-based `index`.
157    pub fn set_json(&mut self, index: usize, value: serde_json::Value) {
158        self.set_value(index, rusqlite::types::Value::Text(value.to_string()));
159    }
160
161    /// Set a blob parameter at zero-based `index`.
162    pub fn set_blob(&mut self, index: usize, value: impl Into<Vec<u8>>) {
163        self.set_value(index, rusqlite::types::Value::Blob(value.into()));
164    }
165
166    /// Set a NULL parameter at zero-based `index`.
167    pub fn set_null(&mut self, index: usize) {
168        self.set_value(index, rusqlite::types::Value::Null);
169    }
170
171    fn set_value(&mut self, index: usize, value: rusqlite::types::Value) {
172        if self.values.len() <= index {
173            self.values
174                .resize_with(index + 1, || rusqlite::types::Value::Null);
175        }
176        self.values[index] = value;
177    }
178}
179
180impl ParamConverter<'_> for Params {
181    type Converted = Params;
182
183    fn convert_sql_params(
184        params: &[RowValues],
185        _mode: ConversionMode,
186    ) -> Result<Self::Converted, SqlMiddlewareDbError> {
187        Self::convert(params)
188    }
189
190    fn supports_mode(mode: ConversionMode) -> bool {
191        // Single Params type supports both query and execute.
192        matches!(mode, ConversionMode::Query | ConversionMode::Execute)
193    }
194}