mssql_client/
query.rs

1//! Query builder and prepared statement support.
2
3use mssql_types::ToSql;
4
5/// A prepared query builder.
6///
7/// Queries can be built incrementally and reused with different parameters.
8#[derive(Debug, Clone)]
9pub struct Query {
10    sql: String,
11    // Placeholder for prepared statement handle and metadata
12}
13
14impl Query {
15    /// Create a new query from SQL text.
16    #[must_use]
17    pub fn new(sql: impl Into<String>) -> Self {
18        Self { sql: sql.into() }
19    }
20
21    /// Get the SQL text.
22    #[must_use]
23    pub fn sql(&self) -> &str {
24        &self.sql
25    }
26}
27
28/// Extension trait for building parameterized queries.
29pub trait QueryExt {
30    /// Add a parameter to the query.
31    fn bind<T: ToSql>(self, value: &T) -> BoundQuery<'_>;
32}
33
34/// A query with bound parameters.
35pub struct BoundQuery<'a> {
36    sql: &'a str,
37    params: Vec<&'a dyn ToSql>,
38}
39
40impl<'a> BoundQuery<'a> {
41    /// Create a new bound query.
42    pub fn new(sql: &'a str) -> Self {
43        Self {
44            sql,
45            params: Vec::new(),
46        }
47    }
48
49    /// Add another parameter.
50    pub fn bind<T: ToSql>(mut self, value: &'a T) -> Self {
51        self.params.push(value);
52        self
53    }
54
55    /// Get the SQL text.
56    #[must_use]
57    pub fn sql(&self) -> &str {
58        self.sql
59    }
60
61    /// Get the bound parameters.
62    #[must_use]
63    pub fn params(&self) -> &[&dyn ToSql] {
64        &self.params
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_query_new() {
74        let query = Query::new("SELECT * FROM users");
75        assert_eq!(query.sql(), "SELECT * FROM users");
76    }
77
78    #[test]
79    fn test_query_new_from_string() {
80        let sql = String::from("SELECT id FROM products");
81        let query = Query::new(sql);
82        assert_eq!(query.sql(), "SELECT id FROM products");
83    }
84
85    #[test]
86    fn test_query_clone() {
87        let query = Query::new("SELECT 1");
88        let cloned = query.clone();
89        assert_eq!(cloned.sql(), "SELECT 1");
90    }
91
92    #[test]
93    fn test_query_debug() {
94        let query = Query::new("SELECT 1");
95        let debug = format!("{:?}", query);
96        assert!(debug.contains("SELECT 1"));
97    }
98
99    #[test]
100    fn test_bound_query_new() {
101        let bound = BoundQuery::new("SELECT * FROM users WHERE id = @p1");
102        assert_eq!(bound.sql(), "SELECT * FROM users WHERE id = @p1");
103        assert!(bound.params().is_empty());
104    }
105
106    #[test]
107    fn test_bound_query_bind_single() {
108        let id = 42i32;
109        let bound = BoundQuery::new("SELECT * FROM users WHERE id = @p1").bind(&id);
110        assert_eq!(bound.sql(), "SELECT * FROM users WHERE id = @p1");
111        assert_eq!(bound.params().len(), 1);
112    }
113
114    #[test]
115    fn test_bound_query_bind_multiple() {
116        let id = 42i32;
117        let name = "Alice";
118        let bound = BoundQuery::new("SELECT * FROM users WHERE id = @p1 AND name = @p2")
119            .bind(&id)
120            .bind(&name);
121        assert_eq!(bound.params().len(), 2);
122    }
123
124    #[test]
125    fn test_bound_query_chained_binds() {
126        let a = 1i32;
127        let b = 2i32;
128        let c = 3i32;
129        let bound = BoundQuery::new("INSERT INTO t VALUES (@p1, @p2, @p3)")
130            .bind(&a)
131            .bind(&b)
132            .bind(&c);
133        assert_eq!(bound.params().len(), 3);
134    }
135}