Skip to main content

bunnydb_http/
params.rs

1use crate::Value;
2
3/// SQL parameter container.
4#[derive(Clone, Debug, PartialEq)]
5pub enum Params {
6    /// Positional values mapped to `?` placeholders.
7    Positional(Vec<Value>),
8    /// Named values mapped to `:name` style placeholders.
9    Named(Vec<(String, Value)>),
10}
11
12impl Params {
13    /// Builds positional parameters.
14    pub fn positional(values: impl Into<Vec<Value>>) -> Self {
15        Self::Positional(values.into())
16    }
17
18    /// Builds named parameters.
19    ///
20    /// Names can be provided with or without prefix (`:`, `@`, `$`).
21    pub fn named<I, K>(pairs: I) -> Self
22    where
23        I: IntoIterator<Item = (K, Value)>,
24        K: Into<String>,
25    {
26        Self::Named(
27            pairs
28                .into_iter()
29                .map(|(name, value)| (name.into(), value))
30                .collect(),
31        )
32    }
33}
34
35impl Default for Params {
36    fn default() -> Self {
37        Self::Positional(Vec::new())
38    }
39}
40
41impl From<()> for Params {
42    fn from(_: ()) -> Self {
43        Self::default()
44    }
45}
46
47impl From<Vec<Value>> for Params {
48    fn from(values: Vec<Value>) -> Self {
49        Self::Positional(values)
50    }
51}
52
53impl<const N: usize> From<[Value; N]> for Params {
54    fn from(values: [Value; N]) -> Self {
55        Self::Positional(values.into())
56    }
57}
58
59impl From<Vec<(String, Value)>> for Params {
60    fn from(values: Vec<(String, Value)>) -> Self {
61        Self::Named(values)
62    }
63}
64
65/// Single statement inside a batch request.
66#[derive(Clone, Debug, PartialEq)]
67pub struct Statement {
68    /// SQL text.
69    pub sql: String,
70    /// Statement parameters.
71    pub params: Params,
72    /// Whether the statement should return rows.
73    pub want_rows: bool,
74}
75
76impl Statement {
77    /// Creates a row-returning statement.
78    pub fn query<P: Into<Params>>(sql: impl Into<String>, params: P) -> Self {
79        Self {
80            sql: sql.into(),
81            params: params.into(),
82            want_rows: true,
83        }
84    }
85
86    /// Creates an execution-only statement.
87    pub fn execute<P: Into<Params>>(sql: impl Into<String>, params: P) -> Self {
88        Self {
89            sql: sql.into(),
90            params: params.into(),
91            want_rows: false,
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use crate::{Params, Statement, Value};
99
100    #[test]
101    fn positional_from_array() {
102        let params: Params = [Value::integer(1), Value::text("kit")].into();
103        match params {
104            Params::Positional(values) => assert_eq!(values.len(), 2),
105            _ => panic!("expected positional"),
106        }
107    }
108
109    #[test]
110    fn named_builder() {
111        let params = Params::named([("name", Value::text("kit"))]);
112        match params {
113            Params::Named(values) => {
114                assert_eq!(values.len(), 1);
115                assert_eq!(values[0].0, "name");
116            }
117            _ => panic!("expected named"),
118        }
119    }
120
121    #[test]
122    fn statement_constructors() {
123        let query = Statement::query("SELECT 1", ());
124        let exec = Statement::execute("DELETE FROM t", ());
125        assert!(query.want_rows);
126        assert!(!exec.want_rows);
127    }
128}