Skip to main content

sqlx_core_oldapi/mssql/
arguments.rs

1use crate::arguments::Arguments;
2use crate::encode::Encode;
3use crate::mssql::database::Mssql;
4use crate::mssql::io::MssqlBufMutExt;
5use crate::mssql::protocol::rpc::StatusFlags;
6use crate::types::Type;
7use std::fmt::{self, Write};
8
9#[derive(Default, Clone)]
10pub struct MssqlArguments {
11    // next ordinal to be used when formatting a positional parameter name
12    pub(crate) ordinal: usize,
13    // temporary string buffer used to format parameter names
14    name: String,
15    pub(crate) data: Vec<u8>,
16    pub(crate) declarations: String,
17}
18
19impl MssqlArguments {
20    pub(crate) fn add_named<'q, T: Encode<'q, Mssql> + Type<Mssql>>(
21        &mut self,
22        name: &str,
23        value: T,
24    ) {
25        let ty = value.produces().unwrap_or_else(T::type_info);
26
27        let mut ty_name = String::new();
28        ty.0.fmt(&mut ty_name);
29
30        self.data.put_b_varchar(name); // [ParamName]
31        self.data.push(0); // [StatusFlags]
32
33        ty.0.put(&mut self.data); // [TYPE_INFO]
34        ty.0.put_value(&mut self.data, value); // [ParamLenData]
35    }
36
37    pub(crate) fn add_unnamed<'q, T: Encode<'q, Mssql> + Type<Mssql>>(&mut self, value: T) {
38        self.add_named("", value);
39    }
40
41    pub(crate) fn declare<'q, T: Encode<'q, Mssql> + Type<Mssql>>(
42        &mut self,
43        name: &str,
44        initial_value: T,
45    ) {
46        let ty = initial_value.produces().unwrap_or_else(T::type_info);
47
48        let mut ty_name = String::new();
49        ty.0.fmt(&mut ty_name);
50
51        self.data.put_b_varchar(name); // [ParamName]
52        self.data.push(StatusFlags::BY_REF_VALUE.bits()); // [StatusFlags]
53
54        ty.0.put(&mut self.data); // [TYPE_INFO]
55        ty.0.put_value(&mut self.data, initial_value); // [ParamLenData]
56    }
57
58    pub(crate) fn append(&mut self, arguments: &mut MssqlArguments) {
59        self.ordinal += arguments.ordinal;
60        self.data.append(&mut arguments.data);
61    }
62
63    pub(crate) fn add<'q, T>(&mut self, value: T)
64    where
65        T: Encode<'q, Mssql> + Type<Mssql>,
66    {
67        let ty = value.produces().unwrap_or_else(T::type_info);
68        // `DataType::Null` is required in TDS for null values, but the
69        // `sp_executesql` declaration needs the parameter's Rust type.
70        let declaration_ty = if ty.0.is_null() {
71            T::type_info()
72        } else {
73            ty.clone()
74        };
75
76        // produce an ordinal parameter name
77        //  @p1, @p2, ... @pN
78
79        self.name.clear();
80        self.name.push_str("@p");
81
82        self.ordinal += 1;
83        self.name.push_str(itoa::Buffer::new().format(self.ordinal));
84
85        let MssqlArguments {
86            ref name,
87            ref mut declarations,
88            ref mut data,
89            ..
90        } = self;
91
92        // add this to our variable declaration list
93        //  @p1 int, @p2 nvarchar(10), ...
94
95        if !declarations.is_empty() {
96            declarations.push(',');
97        }
98
99        declarations.push_str(name);
100        declarations.push(' ');
101        declaration_ty.0.fmt(declarations);
102
103        // write out the parameter
104
105        data.put_b_varchar(name); // [ParamName]
106        data.push(0); // [StatusFlags]
107
108        ty.0.put(data); // [TYPE_INFO]
109        ty.0.put_value(data, value); // [ParamLenData]
110    }
111}
112
113impl<'q> Arguments<'q> for MssqlArguments {
114    type Database = Mssql;
115
116    fn reserve(&mut self, _additional: usize, size: usize) {
117        self.data.reserve(size + 10); // est. 4 chars for name, 1 for status, 1 for TYPE_INFO
118    }
119
120    fn add<T>(&mut self, value: T)
121    where
122        T: 'q + Encode<'q, Self::Database> + Type<Mssql>,
123    {
124        self.add(value)
125    }
126
127    fn format_placeholder<W: Write>(&self, writer: &mut W) -> fmt::Result {
128        // self.ordinal is incremented by the `MssqlArguments::add` method (the inherent one)
129        // *before* this `format_placeholder` method is called by QueryBuilder.
130        // So, `self.ordinal` correctly represents the number of the current parameter (e.g., 1 for @p1).
131        writer.write_str("@p")?;
132        writer.write_str(itoa::Buffer::new().format(self.ordinal))
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::query_builder::QueryBuilder;
140
141    #[test]
142    fn test_format_placeholder_method() {
143        let mut args = MssqlArguments::default(); // ordinal = 0 initially
144        let mut buffer = String::new();
145
146        // Simulate first bind operation sequence as done by QueryBuilder:
147        // 1. QueryBuilder calls MssqlArguments::add (via trait)
148        // 2. QueryBuilder calls MssqlArguments::format_placeholder (via trait)
149
150        // First bind:
151        args.add(123i32); // This calls the inherent `MssqlArguments::add`, which increments ordinal to 1.
152        args.format_placeholder(&mut buffer).unwrap(); // This should now use ordinal = 1.
153        assert_eq!(buffer, "@p1");
154
155        buffer.clear();
156
157        // Second bind:
158        args.add("test_val".to_string()); // Inherent `add` increments ordinal to 2.
159        args.format_placeholder(&mut buffer).unwrap(); // This should use ordinal = 2.
160        assert_eq!(buffer, "@p2");
161    }
162
163    #[test]
164    fn test_query_builder_with_mssql_placeholders() {
165        // This test replicates the scenario from GitHub issue #11
166        let id = 100;
167        let mut builder = QueryBuilder::<Mssql>::new("SELECT * FROM table ");
168        builder
169            .push("WHERE id=")
170            .push_bind(id)
171            .push(" AND name=")
172            .push_bind("test");
173        let sql = builder.sql(); // Get the generated SQL string
174
175        assert_eq!(sql, "SELECT * FROM table WHERE id=@p1 AND name=@p2");
176    }
177}