1use crate::DataTypeExt;
2use odbc_api::{
3 IntoParameter, Nullable,
4 parameter::{InputParameter, VarBinaryBox, VarCharBox, VarWCharBox, WithDataType},
5};
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum MssqlArgumentValue {
10 Text(String),
12 Bytes(Vec<u8>),
14 Int(i64),
18 Bit(bool),
20 Float(f64),
22 Date(odbc_api::sys::Date),
24 Time(odbc_api::sys::Time),
26 Timestamp(odbc_api::sys::Timestamp),
28 Null(crate::MssqlTypeInfo),
30}
31
32#[derive(Debug, Default, Clone, PartialEq)]
34pub struct MssqlArguments {
35 values: Vec<MssqlArgumentValue>,
36}
37
38#[derive(Default)]
43pub struct MssqlParameterCollection {
44 parameters: Vec<Box<dyn InputParameter>>,
45}
46
47impl std::fmt::Debug for MssqlParameterCollection {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("MssqlParameterCollection")
50 .field("len", &self.parameters.len())
51 .finish()
52 }
53}
54
55impl MssqlParameterCollection {
56 pub fn from_values(values: &[MssqlArgumentValue]) -> Self {
58 let parameters = values.iter().map(value_to_parameter).collect();
59
60 Self { parameters }
61 }
62
63 pub fn len(&self) -> usize {
65 self.parameters.len()
66 }
67
68 pub fn is_empty(&self) -> bool {
70 self.parameters.is_empty()
71 }
72
73 pub fn as_slice(&self) -> &[Box<dyn InputParameter>] {
75 &self.parameters
76 }
77}
78
79impl MssqlArguments {
80 pub fn add_value(&mut self, value: MssqlArgumentValue) {
82 self.values.push(value);
83 }
84
85 pub fn len(&self) -> usize {
87 self.values.len()
88 }
89
90 pub fn is_empty(&self) -> bool {
92 self.values.is_empty()
93 }
94
95 pub fn values(&self) -> &[MssqlArgumentValue] {
97 &self.values
98 }
99
100 pub fn to_odbc_parameter_collection(&self) -> MssqlParameterCollection {
102 MssqlParameterCollection::from_values(&self.values)
103 }
104}
105
106impl sqlx_core::arguments::Arguments for MssqlArguments {
107 type Database = crate::Mssql;
108
109 fn reserve(&mut self, additional: usize, _size: usize) {
110 self.values.reserve(additional);
111 }
112
113 fn add<'t, T>(&mut self, value: T) -> Result<(), sqlx_core::error::BoxDynError>
114 where
115 T: sqlx_core::encode::Encode<'t, Self::Database> + sqlx_core::types::Type<Self::Database>,
116 {
117 let _ = value.encode(&mut self.values)?;
118 Ok(())
119 }
120
121 fn len(&self) -> usize {
122 self.values.len()
123 }
124}
125
126sqlx_core::impl_into_arguments_for_arguments!(MssqlArguments);
127
128impl<'q, T> sqlx_core::encode::Encode<'q, crate::Mssql> for Option<T>
129where
130 T: sqlx_core::encode::Encode<'q, crate::Mssql> + sqlx_core::types::Type<crate::Mssql> + 'q,
131{
132 fn encode(
133 self,
134 buf: &mut Vec<MssqlArgumentValue>,
135 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
136 match self {
137 Some(value) => value.encode(buf),
138 None => {
139 buf.push(MssqlArgumentValue::Null(T::type_info()));
140 Ok(sqlx_core::encode::IsNull::Yes)
141 }
142 }
143 }
144
145 fn encode_by_ref(
146 &self,
147 buf: &mut Vec<MssqlArgumentValue>,
148 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
149 match self {
150 Some(value) => value.encode_by_ref(buf),
151 None => {
152 buf.push(MssqlArgumentValue::Null(T::type_info()));
153 Ok(sqlx_core::encode::IsNull::Yes)
154 }
155 }
156 }
157
158 fn produces(&self) -> Option<crate::MssqlTypeInfo> {
159 match self {
160 Some(value) => value.produces(),
161 None => Some(T::type_info()),
162 }
163 }
164}
165
166macro_rules! impl_integer {
167 ($ty:ty, $type_info:expr, $($compatible:pat_param)|+ $(,)?) => {
168 impl sqlx_core::types::Type<crate::Mssql> for $ty {
169 fn type_info() -> crate::MssqlTypeInfo {
170 crate::MssqlTypeInfo::new($type_info)
171 }
172
173 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
174 matches!(
175 ty.data_type(),
176 $($compatible)|+
177 | odbc_api::DataType::Numeric { .. }
178 | odbc_api::DataType::Decimal { .. }
179 ) || ty.data_type().accepts_numeric_data()
180 }
181 }
182
183 impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for $ty {
184 fn encode_by_ref(
185 &self,
186 buf: &mut Vec<MssqlArgumentValue>,
187 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
188 buf.push(MssqlArgumentValue::Int(i64::from(*self)));
189 Ok(sqlx_core::encode::IsNull::No)
190 }
191 }
192 };
193}
194
195impl_integer!(
196 i8,
197 odbc_api::DataType::TinyInt,
198 odbc_api::DataType::TinyInt
199 | odbc_api::DataType::SmallInt
200 | odbc_api::DataType::Integer
201 | odbc_api::DataType::BigInt,
202);
203impl_integer!(
204 i16,
205 odbc_api::DataType::SmallInt,
206 odbc_api::DataType::TinyInt
207 | odbc_api::DataType::SmallInt
208 | odbc_api::DataType::Integer
209 | odbc_api::DataType::BigInt,
210);
211impl_integer!(
212 i32,
213 odbc_api::DataType::Integer,
214 odbc_api::DataType::TinyInt
215 | odbc_api::DataType::SmallInt
216 | odbc_api::DataType::Integer
217 | odbc_api::DataType::BigInt,
218);
219impl_integer!(
220 i64,
221 odbc_api::DataType::BigInt,
222 odbc_api::DataType::TinyInt
223 | odbc_api::DataType::SmallInt
224 | odbc_api::DataType::Integer
225 | odbc_api::DataType::BigInt,
226);
227
228macro_rules! impl_unsigned {
229 ($ty:ty, $type_info:expr, $($compatible:pat_param)|+ $(,)?) => {
230 impl sqlx_core::types::Type<crate::Mssql> for $ty {
231 fn type_info() -> crate::MssqlTypeInfo {
232 crate::MssqlTypeInfo::new($type_info)
233 }
234
235 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
236 matches!(
237 ty.data_type(),
238 $($compatible)|+
239 | odbc_api::DataType::Numeric { .. }
240 | odbc_api::DataType::Decimal { .. }
241 ) || ty.data_type().accepts_numeric_data()
242 }
243 }
244
245 impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for $ty {
246 fn encode_by_ref(
247 &self,
248 buf: &mut Vec<MssqlArgumentValue>,
249 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
250 buf.push(MssqlArgumentValue::Int(i64::from(*self)));
251 Ok(sqlx_core::encode::IsNull::No)
252 }
253 }
254 };
255}
256
257impl_unsigned!(
258 u8,
259 odbc_api::DataType::TinyInt,
260 odbc_api::DataType::TinyInt
261 | odbc_api::DataType::SmallInt
262 | odbc_api::DataType::Integer
263 | odbc_api::DataType::BigInt,
264);
265impl_unsigned!(
266 u16,
267 odbc_api::DataType::SmallInt,
268 odbc_api::DataType::SmallInt | odbc_api::DataType::Integer | odbc_api::DataType::BigInt,
269);
270impl_unsigned!(
271 u32,
272 odbc_api::DataType::Integer,
273 odbc_api::DataType::Integer | odbc_api::DataType::BigInt,
274);
275
276impl sqlx_core::types::Type<crate::Mssql> for u64 {
277 fn type_info() -> crate::MssqlTypeInfo {
278 crate::MssqlTypeInfo::BIGINT
279 }
280
281 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
282 matches!(
283 ty.data_type(),
284 odbc_api::DataType::Integer
285 | odbc_api::DataType::BigInt
286 | odbc_api::DataType::Numeric { .. }
287 | odbc_api::DataType::Decimal { .. }
288 ) || ty.data_type().accepts_numeric_data()
289 }
290}
291
292impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for u64 {
293 fn encode_by_ref(
294 &self,
295 buf: &mut Vec<MssqlArgumentValue>,
296 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
297 let value = i64::try_from(*self).map_err(|_| {
298 format!(
299 "u64 value {self} exceeds BIGINT range (max {}) and cannot be \
300 encoded for MSSQL; use NUMERIC/DECIMAL via rust_decimal for \
301 values above 2^63-1",
302 i64::MAX
303 )
304 })?;
305 buf.push(MssqlArgumentValue::Int(value));
306 Ok(sqlx_core::encode::IsNull::No)
307 }
308}
309
310impl sqlx_core::types::Type<crate::Mssql> for bool {
311 fn type_info() -> crate::MssqlTypeInfo {
312 crate::MssqlTypeInfo::new(odbc_api::DataType::Bit)
313 }
314
315 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
316 ty.data_type().accepts_numeric_data()
317 }
318}
319
320impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for bool {
321 fn encode_by_ref(
322 &self,
323 buf: &mut Vec<MssqlArgumentValue>,
324 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
325 buf.push(MssqlArgumentValue::Bit(*self));
326 Ok(sqlx_core::encode::IsNull::No)
327 }
328}
329
330impl sqlx_core::types::Type<crate::Mssql> for f32 {
331 fn type_info() -> crate::MssqlTypeInfo {
332 crate::MssqlTypeInfo::new(odbc_api::DataType::Real)
333 }
334
335 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
336 ty.data_type().accepts_numeric_data()
337 }
338}
339
340impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for f32 {
341 fn encode_by_ref(
342 &self,
343 buf: &mut Vec<MssqlArgumentValue>,
344 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
345 buf.push(MssqlArgumentValue::Float(f64::from(*self)));
346 Ok(sqlx_core::encode::IsNull::No)
347 }
348}
349
350impl sqlx_core::types::Type<crate::Mssql> for f64 {
351 fn type_info() -> crate::MssqlTypeInfo {
352 crate::MssqlTypeInfo::new(odbc_api::DataType::Double)
353 }
354
355 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
356 ty.data_type().accepts_numeric_data()
357 }
358}
359
360impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for f64 {
361 fn encode_by_ref(
362 &self,
363 buf: &mut Vec<MssqlArgumentValue>,
364 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
365 buf.push(MssqlArgumentValue::Float(*self));
366 Ok(sqlx_core::encode::IsNull::No)
367 }
368}
369
370impl sqlx_core::types::Type<crate::Mssql> for str {
371 fn type_info() -> crate::MssqlTypeInfo {
372 crate::MssqlTypeInfo::new(odbc_api::DataType::WVarchar { length: None })
373 }
374
375 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
376 if ty.data_type().accepts_character_data() {
377 return true;
378 }
379 matches!(
382 ty.data_type(),
383 odbc_api::DataType::Other { data_type, .. }
384 if matches!(data_type.0, -150 | -151 | -152 | -156)
385 )
386 }
387}
388
389impl sqlx_core::types::Type<crate::Mssql> for String {
390 fn type_info() -> crate::MssqlTypeInfo {
391 <str as sqlx_core::types::Type<crate::Mssql>>::type_info()
392 }
393
394 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
395 <str as sqlx_core::types::Type<crate::Mssql>>::compatible(ty)
396 }
397}
398
399impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for &'q str {
400 fn encode_by_ref(
401 &self,
402 buf: &mut Vec<MssqlArgumentValue>,
403 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
404 buf.push(MssqlArgumentValue::Text((*self).to_owned()));
405 Ok(sqlx_core::encode::IsNull::No)
406 }
407}
408
409impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for String {
410 fn encode_by_ref(
411 &self,
412 buf: &mut Vec<MssqlArgumentValue>,
413 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
414 buf.push(MssqlArgumentValue::Text(self.clone()));
415 Ok(sqlx_core::encode::IsNull::No)
416 }
417}
418
419impl sqlx_core::types::Type<crate::Mssql> for [u8] {
420 fn type_info() -> crate::MssqlTypeInfo {
421 crate::MssqlTypeInfo::new(odbc_api::DataType::Varbinary { length: None })
422 }
423
424 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
425 ty.data_type().accepts_binary_data()
426 }
427}
428
429impl sqlx_core::types::Type<crate::Mssql> for Vec<u8> {
430 fn type_info() -> crate::MssqlTypeInfo {
431 <[u8] as sqlx_core::types::Type<crate::Mssql>>::type_info()
432 }
433
434 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
435 <[u8] as sqlx_core::types::Type<crate::Mssql>>::compatible(ty)
436 }
437}
438
439impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for &'q [u8] {
440 fn encode_by_ref(
441 &self,
442 buf: &mut Vec<MssqlArgumentValue>,
443 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
444 buf.push(MssqlArgumentValue::Bytes((*self).to_owned()));
445 Ok(sqlx_core::encode::IsNull::No)
446 }
447}
448
449impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for Vec<u8> {
450 fn encode_by_ref(
451 &self,
452 buf: &mut Vec<MssqlArgumentValue>,
453 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
454 buf.push(MssqlArgumentValue::Bytes(self.clone()));
455 Ok(sqlx_core::encode::IsNull::No)
456 }
457}
458
459impl sqlx_core::types::Type<crate::Mssql> for odbc_api::sys::Date {
460 fn type_info() -> crate::MssqlTypeInfo {
461 crate::MssqlTypeInfo::DATE
462 }
463
464 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
465 matches!(ty.data_type(), odbc_api::DataType::Date)
466 }
467}
468
469impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for odbc_api::sys::Date {
470 fn encode_by_ref(
471 &self,
472 buf: &mut Vec<MssqlArgumentValue>,
473 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
474 buf.push(MssqlArgumentValue::Date(*self));
475 Ok(sqlx_core::encode::IsNull::No)
476 }
477}
478
479impl sqlx_core::types::Type<crate::Mssql> for odbc_api::sys::Time {
480 fn type_info() -> crate::MssqlTypeInfo {
481 crate::MssqlTypeInfo::TIME
482 }
483
484 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
485 matches!(ty.data_type(), odbc_api::DataType::Time { .. })
486 }
487}
488
489impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for odbc_api::sys::Time {
490 fn encode_by_ref(
491 &self,
492 buf: &mut Vec<MssqlArgumentValue>,
493 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
494 buf.push(MssqlArgumentValue::Time(*self));
495 Ok(sqlx_core::encode::IsNull::No)
496 }
497}
498
499impl sqlx_core::types::Type<crate::Mssql> for odbc_api::sys::Timestamp {
500 fn type_info() -> crate::MssqlTypeInfo {
501 crate::MssqlTypeInfo::TIMESTAMP
502 }
503
504 fn compatible(ty: &crate::MssqlTypeInfo) -> bool {
505 matches!(ty.data_type(), odbc_api::DataType::Timestamp { .. })
506 }
507}
508
509impl<'q> sqlx_core::encode::Encode<'q, crate::Mssql> for odbc_api::sys::Timestamp {
510 fn encode_by_ref(
511 &self,
512 buf: &mut Vec<MssqlArgumentValue>,
513 ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
514 buf.push(MssqlArgumentValue::Timestamp(*self));
515 Ok(sqlx_core::encode::IsNull::No)
516 }
517}
518
519fn value_to_parameter(value: &MssqlArgumentValue) -> Box<dyn InputParameter> {
520 match value {
521 MssqlArgumentValue::Text(value) => Box::new(VarWCharBox::from_str_slice(value)),
524 MssqlArgumentValue::Bytes(value) => Box::new(value.clone().into_parameter()),
525 MssqlArgumentValue::Int(value) => Box::new(Some(*value).into_parameter()),
526 MssqlArgumentValue::Bit(value) => Box::new(odbc_api::Bit::from_bool(*value)),
527 MssqlArgumentValue::Float(value) => Box::new(Some(*value).into_parameter()),
528 MssqlArgumentValue::Date(value) => Box::new(Nullable::new(*value).into_parameter()),
529 MssqlArgumentValue::Time(value) => Box::new(
530 WithDataType::new(
531 Nullable::new(*value),
532 odbc_api::DataType::Time { precision: 0 },
533 )
534 .into_parameter(),
535 ),
536 MssqlArgumentValue::Timestamp(value) => Box::new(
537 WithDataType::new(
538 Nullable::new(*value),
539 odbc_api::DataType::Timestamp { precision: 6 },
540 )
541 .into_parameter(),
542 ),
543 MssqlArgumentValue::Null(type_info) => null_parameter(type_info.data_type()),
544 }
545}
546
547fn null_parameter(data_type: odbc_api::DataType) -> Box<dyn InputParameter> {
548 match data_type {
549 odbc_api::DataType::TinyInt => Box::new(Nullable::<i8>::null()),
550 odbc_api::DataType::SmallInt => Box::new(Nullable::<i16>::null()),
551 odbc_api::DataType::Integer => Box::new(Nullable::<i32>::null()),
552 odbc_api::DataType::BigInt => Box::new(Nullable::<i64>::null()),
553 odbc_api::DataType::Bit => Box::new(Nullable::<odbc_api::Bit>::null()),
554 odbc_api::DataType::Real => Box::new(Nullable::<f32>::null()),
555 odbc_api::DataType::Double => Box::new(Nullable::<f64>::null()),
556 odbc_api::DataType::Float { .. } => {
560 Box::new(WithDataType::new(Nullable::<f64>::null(), data_type))
561 }
562 odbc_api::DataType::Date => Box::new(Nullable::<odbc_api::sys::Date>::null()),
563 odbc_api::DataType::Time { .. } => Box::new(WithDataType::new(
564 Nullable::<odbc_api::sys::Time>::null(),
565 data_type,
566 )),
567 odbc_api::DataType::Timestamp { .. } => Box::new(WithDataType::new(
568 Nullable::<odbc_api::sys::Timestamp>::null(),
569 data_type,
570 )),
571 odbc_api::DataType::Varbinary { .. }
572 | odbc_api::DataType::LongVarbinary { .. }
573 | odbc_api::DataType::Binary { .. } => {
574 Box::new(WithDataType::new(VarBinaryBox::null(), data_type))
575 }
576 odbc_api::DataType::WVarchar { .. }
577 | odbc_api::DataType::WLongVarchar { .. }
578 | odbc_api::DataType::WChar { .. } => {
579 Box::new(WithDataType::new(VarWCharBox::null(), data_type))
580 }
581 odbc_api::DataType::Char { .. }
582 | odbc_api::DataType::Varchar { .. }
583 | odbc_api::DataType::LongVarchar { .. }
584 | odbc_api::DataType::Numeric { .. }
585 | odbc_api::DataType::Decimal { .. }
586 | odbc_api::DataType::Unknown
587 | odbc_api::DataType::Other { .. } => {
588 Box::new(WithDataType::new(VarCharBox::null(), data_type))
589 }
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use odbc_api::{
597 ParameterCollectionRef,
598 handles::{CData, HasDataType},
599 };
600
601 #[test]
602 fn argument_buffer_tracks_values_in_order() {
603 let mut arguments = MssqlArguments::default();
604
605 arguments.add_value(MssqlArgumentValue::Int(7));
606 arguments.add_value(MssqlArgumentValue::Text("abc".to_owned()));
607 arguments.add_value(MssqlArgumentValue::Null(crate::MssqlTypeInfo::new(
608 odbc_api::DataType::Integer,
609 )));
610
611 assert_eq!(arguments.len(), 3);
612 assert_eq!(
613 arguments.values(),
614 &[
615 MssqlArgumentValue::Int(7),
616 MssqlArgumentValue::Text("abc".to_owned()),
617 MssqlArgumentValue::Null(crate::MssqlTypeInfo::new(odbc_api::DataType::Integer))
618 ]
619 );
620 }
621
622 #[test]
623 fn sqlx_arguments_add_encodes_basic_scalars() {
624 let mut arguments = MssqlArguments::default();
625
626 sqlx_core::arguments::Arguments::add(&mut arguments, 7_i32).unwrap();
627 sqlx_core::arguments::Arguments::add(&mut arguments, "abc").unwrap();
628 sqlx_core::arguments::Arguments::add(&mut arguments, vec![1_u8, 2, 3]).unwrap();
629
630 assert_eq!(
631 arguments.values(),
632 &[
633 MssqlArgumentValue::Int(7),
634 MssqlArgumentValue::Text("abc".to_owned()),
635 MssqlArgumentValue::Bytes(vec![1, 2, 3])
636 ]
637 );
638 }
639
640 #[test]
641 fn sqlx_arguments_add_encodes_large_text_and_binary_slices() {
642 let mut arguments = MssqlArguments::default();
643 let text = "abc123".repeat(16 * 1024);
644 let bytes = [0_u8, 1, 2, 127, 128, 254, 255];
645
646 sqlx_core::arguments::Arguments::add(&mut arguments, text.as_str()).unwrap();
647 sqlx_core::arguments::Arguments::add(&mut arguments, &bytes[..]).unwrap();
648
649 assert_eq!(
650 arguments.values(),
651 &[
652 MssqlArgumentValue::Text(text),
653 MssqlArgumentValue::Bytes(bytes.to_vec())
654 ]
655 );
656 }
657
658 #[test]
659 fn byte_types_are_compatible_with_text_and_binary_columns() {
660 use sqlx_core::types::Type;
661
662 let binary = crate::MssqlTypeInfo::new(odbc_api::DataType::Varbinary { length: None });
663 let text = crate::MssqlTypeInfo::new(odbc_api::DataType::WVarchar { length: None });
664 let integer = crate::MssqlTypeInfo::new(odbc_api::DataType::Integer);
665
666 assert!(<[u8] as Type<crate::Mssql>>::compatible(&binary));
667 assert!(<[u8] as Type<crate::Mssql>>::compatible(&text));
668 assert!(!<[u8] as Type<crate::Mssql>>::compatible(&integer));
669 assert!(<Vec<u8> as Type<crate::Mssql>>::compatible(&binary));
670 assert!(<Vec<u8> as Type<crate::Mssql>>::compatible(&text));
671 assert!(!<Vec<u8> as Type<crate::Mssql>>::compatible(&integer));
672 }
673
674 #[test]
675 fn sqlx_arguments_add_rejects_unsigned_values_exceeding_bigint_range() {
676 let mut arguments = MssqlArguments::default();
677
678 let result = sqlx_core::arguments::Arguments::add(&mut arguments, u64::MAX);
679
680 assert!(result.is_err());
681 let error = result.unwrap_err();
682 let msg = format!("{error}");
683 assert!(
684 msg.contains("exceeds BIGINT range"),
685 "expected range error, got: {msg}"
686 );
687 }
688
689 #[test]
690 fn sqlx_arguments_add_encodes_unsigned_values_within_bigint_range() {
691 let mut arguments = MssqlArguments::default();
692
693 sqlx_core::arguments::Arguments::add(&mut arguments, 42_u64).unwrap();
694
695 assert_eq!(arguments.values(), &[MssqlArgumentValue::Int(42)]);
696 }
697
698 #[test]
699 fn sqlx_arguments_add_encodes_temporal_scalars() {
700 let mut arguments = MssqlArguments::default();
701 let date = odbc_api::sys::Date {
702 year: 2026,
703 month: 5,
704 day: 29,
705 };
706 let time = odbc_api::sys::Time {
707 hour: 12,
708 minute: 30,
709 second: 45,
710 };
711 let timestamp = odbc_api::sys::Timestamp {
712 year: 2026,
713 month: 5,
714 day: 29,
715 hour: 12,
716 minute: 30,
717 second: 45,
718 fraction: 123_456_000,
719 };
720
721 sqlx_core::arguments::Arguments::add(&mut arguments, date).unwrap();
722 sqlx_core::arguments::Arguments::add(&mut arguments, time).unwrap();
723 sqlx_core::arguments::Arguments::add(&mut arguments, timestamp).unwrap();
724
725 assert_eq!(
726 arguments.values(),
727 &[
728 MssqlArgumentValue::Date(date),
729 MssqlArgumentValue::Time(time),
730 MssqlArgumentValue::Timestamp(timestamp)
731 ]
732 );
733 }
734
735 #[test]
736 fn sqlx_arguments_add_encodes_typed_null_option() {
737 let mut arguments = MssqlArguments::default();
738
739 sqlx_core::arguments::Arguments::add(&mut arguments, Option::<i32>::None).unwrap();
740
741 assert_eq!(
742 arguments.values(),
743 &[MssqlArgumentValue::Null(crate::MssqlTypeInfo::new(
744 odbc_api::DataType::Integer
745 ))]
746 );
747
748 let collection = arguments.to_odbc_parameter_collection();
749 assert_eq!(
750 collection.as_slice()[0].data_type(),
751 odbc_api::DataType::Integer
752 );
753 }
754
755 #[test]
756 fn sqlx_arguments_reserve_and_len_work() {
757 let mut arguments = MssqlArguments::default();
758
759 sqlx_core::arguments::Arguments::reserve(&mut arguments, 2, 16);
760 sqlx_core::arguments::Arguments::add(&mut arguments, true).unwrap();
761 sqlx_core::arguments::Arguments::add(&mut arguments, 1.5_f64).unwrap();
762
763 assert_eq!(sqlx_core::arguments::Arguments::len(&arguments), 2);
764 assert_eq!(
765 arguments.values(),
766 &[
767 MssqlArgumentValue::Bit(true),
768 MssqlArgumentValue::Float(1.5)
769 ]
770 );
771 }
772
773 #[test]
774 fn parameter_collection_converts_basic_values_to_odbc_parameters() {
775 let values = [
776 MssqlArgumentValue::Text("abc".to_owned()),
777 MssqlArgumentValue::Bytes(vec![1, 2, 3]),
778 MssqlArgumentValue::Int(7),
779 MssqlArgumentValue::Int(8),
780 MssqlArgumentValue::Bit(true),
781 MssqlArgumentValue::Float(1.5),
782 ];
783
784 let collection = MssqlParameterCollection::from_values(&values);
785
786 assert_eq!(collection.len(), values.len());
787 assert!(matches!(
788 collection.as_slice()[0].data_type(),
789 odbc_api::DataType::Varchar { .. }
790 | odbc_api::DataType::WVarchar { .. }
791 | odbc_api::DataType::WLongVarchar { .. }
792 ));
793 assert!(matches!(
794 collection.as_slice()[1].data_type(),
795 odbc_api::DataType::Varbinary { .. }
796 ));
797 assert_eq!(
798 collection.as_slice()[2].data_type(),
799 odbc_api::DataType::BigInt
800 );
801 assert_eq!(
802 collection.as_slice()[3].data_type(),
803 odbc_api::DataType::BigInt
804 );
805 assert_eq!(
806 collection.as_slice()[4].data_type(),
807 odbc_api::DataType::Bit
808 );
809 assert_eq!(
810 collection.as_slice()[5].data_type(),
811 odbc_api::DataType::Double
812 );
813 }
814
815 #[test]
816 fn parameter_collection_converts_temporal_values_to_typed_odbc_parameters() {
817 let values = [
818 MssqlArgumentValue::Date(odbc_api::sys::Date {
819 year: 2026,
820 month: 5,
821 day: 29,
822 }),
823 MssqlArgumentValue::Time(odbc_api::sys::Time {
824 hour: 12,
825 minute: 30,
826 second: 45,
827 }),
828 MssqlArgumentValue::Timestamp(odbc_api::sys::Timestamp {
829 year: 2026,
830 month: 5,
831 day: 29,
832 hour: 12,
833 minute: 30,
834 second: 45,
835 fraction: 123_456_789,
836 }),
837 ];
838
839 let collection = MssqlParameterCollection::from_values(&values);
840
841 assert_eq!(
842 collection.as_slice()[0].data_type(),
843 odbc_api::DataType::Date
844 );
845 assert_eq!(
846 collection.as_slice()[1].data_type(),
847 odbc_api::DataType::Time { precision: 0 }
848 );
849 assert_eq!(
850 collection.as_slice()[2].data_type(),
851 odbc_api::DataType::Timestamp { precision: 6 }
852 );
853 }
854
855 #[test]
856 fn parameter_collection_converts_typed_nulls_to_requested_data_types() {
857 let values = [
858 MssqlArgumentValue::Null(crate::MssqlTypeInfo::new(odbc_api::DataType::Integer)),
859 MssqlArgumentValue::Null(crate::MssqlTypeInfo::new(odbc_api::DataType::WVarchar {
860 length: None,
861 })),
862 MssqlArgumentValue::Null(crate::MssqlTypeInfo::new(odbc_api::DataType::Decimal {
863 precision: 10,
864 scale: 2,
865 })),
866 ];
867
868 let collection = MssqlParameterCollection::from_values(&values);
869
870 assert_eq!(
871 collection.as_slice()[0].data_type(),
872 odbc_api::DataType::Integer
873 );
874 assert_eq!(
875 collection.as_slice()[1].data_type(),
876 odbc_api::DataType::WVarchar { length: None }
877 );
878 assert_eq!(
879 collection.as_slice()[2].data_type(),
880 odbc_api::DataType::Decimal {
881 precision: 10,
882 scale: 2
883 }
884 );
885 }
886
887 #[test]
888 fn parameter_collection_slice_matches_odbc_api_binding_shape() {
889 fn assert_parameter_collection_ref<T: ParameterCollectionRef>(_parameters: T) {}
890
891 let mut arguments = MssqlArguments::default();
892 sqlx_core::arguments::Arguments::add(&mut arguments, "abc").unwrap();
893 let collection = arguments.to_odbc_parameter_collection();
894
895 assert_parameter_collection_ref(collection.as_slice());
896 }
897
898 #[test]
899 fn fixed_sized_parameter_uses_explicit_non_null_indicator() {
900 let mut arguments = MssqlArguments::default();
901
902 sqlx_core::arguments::Arguments::add(&mut arguments, 5_i32).unwrap();
903
904 let collection = arguments.to_odbc_parameter_collection();
905 assert_eq!(collection.len(), 1);
906 assert!(!collection.as_slice()[0].indicator_ptr().is_null());
907 }
908}