Skip to main content

sqlx_odbc/
arguments.rs

1use crate::DataTypeExt;
2use odbc_api::{
3    parameter::{InputParameter, VarBinaryBox, VarCharBox, VarWCharBox, WithDataType},
4    IntoParameter, Nullable,
5};
6
7/// Values that can currently be bound to ODBC parameters.
8#[derive(Debug, Clone, PartialEq)]
9pub enum OdbcArgumentValue {
10    /// UTF-8 text parameter.
11    Text(String),
12    /// Binary parameter.
13    Bytes(Vec<u8>),
14    /// Signed integer parameter.
15    Int(i64),
16    /// Unsigned integer parameter.
17    UInt(u64),
18    /// Boolean parameter.
19    Bit(bool),
20    /// Floating point parameter.
21    Float(f64),
22    /// Date parameter.
23    Date(odbc_api::sys::Date),
24    /// Time parameter.
25    Time(odbc_api::sys::Time),
26    /// Timestamp parameter.
27    Timestamp(odbc_api::sys::Timestamp),
28    /// Typed NULL parameter.
29    Null(crate::OdbcTypeInfo),
30}
31
32/// Argument buffer for ODBC queries.
33#[derive(Debug, Default, Clone, PartialEq)]
34pub struct OdbcArguments {
35    values: Vec<OdbcArgumentValue>,
36}
37
38/// Owned ODBC parameter storage ready to bind with `odbc-api`.
39///
40/// `odbc-api` implements `ParameterCollectionRef` for `&[Box<dyn InputParameter>]`, so executor
41/// code can pass `collection.as_slice()` to `Connection::execute` or `Preallocated::execute`.
42#[derive(Default)]
43pub struct OdbcParameterCollection {
44    parameters: Vec<Box<dyn InputParameter>>,
45}
46
47impl std::fmt::Debug for OdbcParameterCollection {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.debug_struct("OdbcParameterCollection")
50            .field("len", &self.parameters.len())
51            .finish()
52    }
53}
54
55impl OdbcParameterCollection {
56    /// Converts raw SQLx ODBC argument values into owned `odbc-api` input parameters.
57    pub fn from_values(values: &[OdbcArgumentValue]) -> Self {
58        let parameters = values.iter().map(value_to_parameter).collect();
59
60        Self { parameters }
61    }
62
63    /// Returns the number of parameters.
64    pub fn len(&self) -> usize {
65        self.parameters.len()
66    }
67
68    /// Returns `true` when no parameters are present.
69    pub fn is_empty(&self) -> bool {
70        self.parameters.is_empty()
71    }
72
73    /// Returns the parameter slice accepted by `odbc-api` execution methods.
74    pub fn as_slice(&self) -> &[Box<dyn InputParameter>] {
75        &self.parameters
76    }
77}
78
79impl OdbcArguments {
80    /// Adds a raw ODBC argument value.
81    pub fn add_value(&mut self, value: OdbcArgumentValue) {
82        self.values.push(value);
83    }
84
85    /// Returns the number of arguments.
86    pub fn len(&self) -> usize {
87        self.values.len()
88    }
89
90    /// Returns `true` when no arguments have been added.
91    pub fn is_empty(&self) -> bool {
92        self.values.is_empty()
93    }
94
95    /// Returns the raw argument values.
96    pub fn values(&self) -> &[OdbcArgumentValue] {
97        &self.values
98    }
99
100    /// Converts these arguments into owned `odbc-api` parameters.
101    pub fn to_odbc_parameter_collection(&self) -> OdbcParameterCollection {
102        OdbcParameterCollection::from_values(&self.values)
103    }
104}
105
106impl sqlx_core::arguments::Arguments for OdbcArguments {
107    type Database = crate::Odbc;
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!(OdbcArguments);
127
128impl<'q, T> sqlx_core::encode::Encode<'q, crate::Odbc> for Option<T>
129where
130    T: sqlx_core::encode::Encode<'q, crate::Odbc> + sqlx_core::types::Type<crate::Odbc> + 'q,
131{
132    fn encode(
133        self,
134        buf: &mut Vec<OdbcArgumentValue>,
135    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
136        match self {
137            Some(value) => value.encode(buf),
138            None => {
139                buf.push(OdbcArgumentValue::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<OdbcArgumentValue>,
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(OdbcArgumentValue::Null(T::type_info()));
153                Ok(sqlx_core::encode::IsNull::Yes)
154            }
155        }
156    }
157
158    fn produces(&self) -> Option<crate::OdbcTypeInfo> {
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::Odbc> for $ty {
169            fn type_info() -> crate::OdbcTypeInfo {
170                crate::OdbcTypeInfo::new($type_info)
171            }
172
173            fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
174                matches!(
175                    ty.data_type(),
176                    $($compatible)|+
177                        | odbc_api::DataType::Numeric { .. }
178                        | odbc_api::DataType::Decimal { .. }
179                ) || ty.data_type().accepts_character_data()
180            }
181        }
182
183        impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for $ty {
184            fn encode_by_ref(
185                &self,
186                buf: &mut Vec<OdbcArgumentValue>,
187            ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
188                buf.push(OdbcArgumentValue::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::Odbc> for $ty {
231            fn type_info() -> crate::OdbcTypeInfo {
232                crate::OdbcTypeInfo::new($type_info)
233            }
234
235            fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
236                matches!(
237                    ty.data_type(),
238                    $($compatible)|+
239                        | odbc_api::DataType::Numeric { .. }
240                        | odbc_api::DataType::Decimal { .. }
241                ) || ty.data_type().accepts_character_data()
242            }
243        }
244
245        impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for $ty {
246            fn encode_by_ref(
247                &self,
248                buf: &mut Vec<OdbcArgumentValue>,
249            ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
250                buf.push(OdbcArgumentValue::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::Odbc> for u64 {
277    fn type_info() -> crate::OdbcTypeInfo {
278        crate::OdbcTypeInfo::BIGINT
279    }
280
281    fn compatible(ty: &crate::OdbcTypeInfo) -> 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_character_data()
289    }
290}
291
292impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for u64 {
293    fn encode_by_ref(
294        &self,
295        buf: &mut Vec<OdbcArgumentValue>,
296    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
297        if let Ok(value) = i64::try_from(*self) {
298            buf.push(OdbcArgumentValue::Int(value));
299        } else {
300            buf.push(OdbcArgumentValue::UInt(*self));
301        }
302
303        Ok(sqlx_core::encode::IsNull::No)
304    }
305}
306
307impl sqlx_core::types::Type<crate::Odbc> for bool {
308    fn type_info() -> crate::OdbcTypeInfo {
309        crate::OdbcTypeInfo::new(odbc_api::DataType::Bit)
310    }
311
312    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
313        ty.data_type().accepts_numeric_data() || ty.data_type().accepts_character_data()
314    }
315}
316
317impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for bool {
318    fn encode_by_ref(
319        &self,
320        buf: &mut Vec<OdbcArgumentValue>,
321    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
322        buf.push(OdbcArgumentValue::Bit(*self));
323        Ok(sqlx_core::encode::IsNull::No)
324    }
325}
326
327impl sqlx_core::types::Type<crate::Odbc> for f32 {
328    fn type_info() -> crate::OdbcTypeInfo {
329        crate::OdbcTypeInfo::new(odbc_api::DataType::Real)
330    }
331
332    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
333        ty.data_type().accepts_numeric_data() || ty.data_type().accepts_character_data()
334    }
335}
336
337impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for f32 {
338    fn encode_by_ref(
339        &self,
340        buf: &mut Vec<OdbcArgumentValue>,
341    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
342        buf.push(OdbcArgumentValue::Float(f64::from(*self)));
343        Ok(sqlx_core::encode::IsNull::No)
344    }
345}
346
347impl sqlx_core::types::Type<crate::Odbc> for f64 {
348    fn type_info() -> crate::OdbcTypeInfo {
349        crate::OdbcTypeInfo::new(odbc_api::DataType::Double)
350    }
351
352    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
353        ty.data_type().accepts_numeric_data() || ty.data_type().accepts_character_data()
354    }
355}
356
357impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for f64 {
358    fn encode_by_ref(
359        &self,
360        buf: &mut Vec<OdbcArgumentValue>,
361    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
362        buf.push(OdbcArgumentValue::Float(*self));
363        Ok(sqlx_core::encode::IsNull::No)
364    }
365}
366
367impl sqlx_core::types::Type<crate::Odbc> for str {
368    fn type_info() -> crate::OdbcTypeInfo {
369        crate::OdbcTypeInfo::new(odbc_api::DataType::WVarchar { length: None })
370    }
371
372    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
373        ty.data_type().accepts_character_data()
374    }
375}
376
377impl sqlx_core::types::Type<crate::Odbc> for String {
378    fn type_info() -> crate::OdbcTypeInfo {
379        <str as sqlx_core::types::Type<crate::Odbc>>::type_info()
380    }
381}
382
383impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for &'q str {
384    fn encode_by_ref(
385        &self,
386        buf: &mut Vec<OdbcArgumentValue>,
387    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
388        buf.push(OdbcArgumentValue::Text((*self).to_owned()));
389        Ok(sqlx_core::encode::IsNull::No)
390    }
391}
392
393impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for String {
394    fn encode_by_ref(
395        &self,
396        buf: &mut Vec<OdbcArgumentValue>,
397    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
398        buf.push(OdbcArgumentValue::Text(self.clone()));
399        Ok(sqlx_core::encode::IsNull::No)
400    }
401}
402
403impl sqlx_core::types::Type<crate::Odbc> for [u8] {
404    fn type_info() -> crate::OdbcTypeInfo {
405        crate::OdbcTypeInfo::new(odbc_api::DataType::Varbinary { length: None })
406    }
407
408    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
409        ty.data_type().accepts_binary_data() || ty.data_type().accepts_character_data()
410    }
411}
412
413impl sqlx_core::types::Type<crate::Odbc> for Vec<u8> {
414    fn type_info() -> crate::OdbcTypeInfo {
415        <[u8] as sqlx_core::types::Type<crate::Odbc>>::type_info()
416    }
417
418    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
419        <[u8] as sqlx_core::types::Type<crate::Odbc>>::compatible(ty)
420    }
421}
422
423impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for &'q [u8] {
424    fn encode_by_ref(
425        &self,
426        buf: &mut Vec<OdbcArgumentValue>,
427    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
428        buf.push(OdbcArgumentValue::Bytes((*self).to_owned()));
429        Ok(sqlx_core::encode::IsNull::No)
430    }
431}
432
433impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for Vec<u8> {
434    fn encode_by_ref(
435        &self,
436        buf: &mut Vec<OdbcArgumentValue>,
437    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
438        buf.push(OdbcArgumentValue::Bytes(self.clone()));
439        Ok(sqlx_core::encode::IsNull::No)
440    }
441}
442
443impl sqlx_core::types::Type<crate::Odbc> for odbc_api::sys::Date {
444    fn type_info() -> crate::OdbcTypeInfo {
445        crate::OdbcTypeInfo::DATE
446    }
447
448    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
449        matches!(ty.data_type(), odbc_api::DataType::Date)
450    }
451}
452
453impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for odbc_api::sys::Date {
454    fn encode_by_ref(
455        &self,
456        buf: &mut Vec<OdbcArgumentValue>,
457    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
458        buf.push(OdbcArgumentValue::Date(*self));
459        Ok(sqlx_core::encode::IsNull::No)
460    }
461}
462
463impl sqlx_core::types::Type<crate::Odbc> for odbc_api::sys::Time {
464    fn type_info() -> crate::OdbcTypeInfo {
465        crate::OdbcTypeInfo::TIME
466    }
467
468    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
469        matches!(ty.data_type(), odbc_api::DataType::Time { .. })
470    }
471}
472
473impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for odbc_api::sys::Time {
474    fn encode_by_ref(
475        &self,
476        buf: &mut Vec<OdbcArgumentValue>,
477    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
478        buf.push(OdbcArgumentValue::Time(*self));
479        Ok(sqlx_core::encode::IsNull::No)
480    }
481}
482
483impl sqlx_core::types::Type<crate::Odbc> for odbc_api::sys::Timestamp {
484    fn type_info() -> crate::OdbcTypeInfo {
485        crate::OdbcTypeInfo::TIMESTAMP
486    }
487
488    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
489        matches!(ty.data_type(), odbc_api::DataType::Timestamp { .. })
490    }
491}
492
493impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for odbc_api::sys::Timestamp {
494    fn encode_by_ref(
495        &self,
496        buf: &mut Vec<OdbcArgumentValue>,
497    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
498        buf.push(OdbcArgumentValue::Timestamp(*self));
499        Ok(sqlx_core::encode::IsNull::No)
500    }
501}
502
503fn value_to_parameter(value: &OdbcArgumentValue) -> Box<dyn InputParameter> {
504    match value {
505        OdbcArgumentValue::Text(value) => Box::new(value.clone().into_parameter()),
506        OdbcArgumentValue::Bytes(value) => Box::new(value.clone().into_parameter()),
507        OdbcArgumentValue::Int(value) => Box::new(Some(*value).into_parameter()),
508        OdbcArgumentValue::UInt(value) => Box::new(
509            WithDataType::new(Nullable::new(*value), odbc_api::DataType::BigInt).into_parameter(),
510        ),
511        OdbcArgumentValue::Bit(value) => Box::new(odbc_api::Bit::from_bool(*value)),
512        OdbcArgumentValue::Float(value) => Box::new(Some(*value).into_parameter()),
513        OdbcArgumentValue::Date(value) => Box::new(Nullable::new(*value).into_parameter()),
514        OdbcArgumentValue::Time(value) => Box::new(
515            WithDataType::new(
516                Nullable::new(*value),
517                odbc_api::DataType::Time { precision: 0 },
518            )
519            .into_parameter(),
520        ),
521        OdbcArgumentValue::Timestamp(value) => Box::new(
522            WithDataType::new(
523                Nullable::new(*value),
524                odbc_api::DataType::Timestamp { precision: 6 },
525            )
526            .into_parameter(),
527        ),
528        OdbcArgumentValue::Null(type_info) => null_parameter(type_info.data_type()),
529    }
530}
531
532fn null_parameter(data_type: odbc_api::DataType) -> Box<dyn InputParameter> {
533    match data_type {
534        odbc_api::DataType::TinyInt => Box::new(Nullable::<i8>::null()),
535        odbc_api::DataType::SmallInt => Box::new(Nullable::<i16>::null()),
536        odbc_api::DataType::Integer => Box::new(Nullable::<i32>::null()),
537        odbc_api::DataType::BigInt => Box::new(Nullable::<i64>::null()),
538        odbc_api::DataType::Bit => Box::new(Nullable::<odbc_api::Bit>::null()),
539        odbc_api::DataType::Real => Box::new(Nullable::<f32>::null()),
540        odbc_api::DataType::Double => Box::new(Nullable::<f64>::null()),
541        odbc_api::DataType::Float { .. } => {
542            Box::new(WithDataType::new(Nullable::<f64>::null(), data_type))
543        }
544        odbc_api::DataType::Date => Box::new(Nullable::<odbc_api::sys::Date>::null()),
545        odbc_api::DataType::Time { .. } => Box::new(WithDataType::new(
546            Nullable::<odbc_api::sys::Time>::null(),
547            data_type,
548        )),
549        odbc_api::DataType::Timestamp { .. } => Box::new(WithDataType::new(
550            Nullable::<odbc_api::sys::Timestamp>::null(),
551            data_type,
552        )),
553        odbc_api::DataType::Varbinary { .. }
554        | odbc_api::DataType::LongVarbinary { .. }
555        | odbc_api::DataType::Binary { .. } => {
556            Box::new(WithDataType::new(VarBinaryBox::null(), data_type))
557        }
558        odbc_api::DataType::WVarchar { .. }
559        | odbc_api::DataType::WLongVarchar { .. }
560        | odbc_api::DataType::WChar { .. } => {
561            Box::new(WithDataType::new(VarWCharBox::null(), data_type))
562        }
563        odbc_api::DataType::Char { .. }
564        | odbc_api::DataType::Varchar { .. }
565        | odbc_api::DataType::LongVarchar { .. }
566        | odbc_api::DataType::Numeric { .. }
567        | odbc_api::DataType::Decimal { .. }
568        | odbc_api::DataType::Unknown
569        | odbc_api::DataType::Other { .. } => {
570            Box::new(WithDataType::new(VarCharBox::null(), data_type))
571        }
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578    use odbc_api::{
579        handles::{CData, HasDataType},
580        ParameterCollectionRef,
581    };
582
583    #[test]
584    fn argument_buffer_tracks_values_in_order() {
585        let mut arguments = OdbcArguments::default();
586
587        arguments.add_value(OdbcArgumentValue::Int(7));
588        arguments.add_value(OdbcArgumentValue::Text("abc".to_owned()));
589        arguments.add_value(OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(
590            odbc_api::DataType::Integer,
591        )));
592
593        assert_eq!(arguments.len(), 3);
594        assert_eq!(
595            arguments.values(),
596            &[
597                OdbcArgumentValue::Int(7),
598                OdbcArgumentValue::Text("abc".to_owned()),
599                OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::Integer))
600            ]
601        );
602    }
603
604    #[test]
605    fn sqlx_arguments_add_encodes_basic_scalars() {
606        let mut arguments = OdbcArguments::default();
607
608        sqlx_core::arguments::Arguments::add(&mut arguments, 7_i32).unwrap();
609        sqlx_core::arguments::Arguments::add(&mut arguments, "abc").unwrap();
610        sqlx_core::arguments::Arguments::add(&mut arguments, vec![1_u8, 2, 3]).unwrap();
611
612        assert_eq!(
613            arguments.values(),
614            &[
615                OdbcArgumentValue::Int(7),
616                OdbcArgumentValue::Text("abc".to_owned()),
617                OdbcArgumentValue::Bytes(vec![1, 2, 3])
618            ]
619        );
620    }
621
622    #[test]
623    fn sqlx_arguments_add_encodes_large_text_and_binary_slices() {
624        let mut arguments = OdbcArguments::default();
625        let text = "abc123".repeat(16 * 1024);
626        let bytes = [0_u8, 1, 2, 127, 128, 254, 255];
627
628        sqlx_core::arguments::Arguments::add(&mut arguments, text.as_str()).unwrap();
629        sqlx_core::arguments::Arguments::add(&mut arguments, &bytes[..]).unwrap();
630
631        assert_eq!(
632            arguments.values(),
633            &[
634                OdbcArgumentValue::Text(text),
635                OdbcArgumentValue::Bytes(bytes.to_vec())
636            ]
637        );
638    }
639
640    #[test]
641    fn byte_types_are_compatible_with_text_and_binary_columns() {
642        use sqlx_core::types::Type;
643
644        let binary = crate::OdbcTypeInfo::new(odbc_api::DataType::Varbinary { length: None });
645        let text = crate::OdbcTypeInfo::new(odbc_api::DataType::WVarchar { length: None });
646        let integer = crate::OdbcTypeInfo::new(odbc_api::DataType::Integer);
647
648        assert!(<[u8] as Type<crate::Odbc>>::compatible(&binary));
649        assert!(<[u8] as Type<crate::Odbc>>::compatible(&text));
650        assert!(!<[u8] as Type<crate::Odbc>>::compatible(&integer));
651        assert!(<Vec<u8> as Type<crate::Odbc>>::compatible(&binary));
652        assert!(<Vec<u8> as Type<crate::Odbc>>::compatible(&text));
653        assert!(!<Vec<u8> as Type<crate::Odbc>>::compatible(&integer));
654    }
655
656    #[test]
657    fn sqlx_arguments_add_preserves_large_unsigned_values() {
658        let mut arguments = OdbcArguments::default();
659
660        sqlx_core::arguments::Arguments::add(&mut arguments, u64::MAX).unwrap();
661
662        assert_eq!(arguments.values(), &[OdbcArgumentValue::UInt(u64::MAX)]);
663    }
664
665    #[test]
666    fn sqlx_arguments_add_encodes_temporal_scalars() {
667        let mut arguments = OdbcArguments::default();
668        let date = odbc_api::sys::Date {
669            year: 2026,
670            month: 5,
671            day: 29,
672        };
673        let time = odbc_api::sys::Time {
674            hour: 12,
675            minute: 30,
676            second: 45,
677        };
678        let timestamp = odbc_api::sys::Timestamp {
679            year: 2026,
680            month: 5,
681            day: 29,
682            hour: 12,
683            minute: 30,
684            second: 45,
685            fraction: 123_456_000,
686        };
687
688        sqlx_core::arguments::Arguments::add(&mut arguments, date).unwrap();
689        sqlx_core::arguments::Arguments::add(&mut arguments, time).unwrap();
690        sqlx_core::arguments::Arguments::add(&mut arguments, timestamp).unwrap();
691
692        assert_eq!(
693            arguments.values(),
694            &[
695                OdbcArgumentValue::Date(date),
696                OdbcArgumentValue::Time(time),
697                OdbcArgumentValue::Timestamp(timestamp)
698            ]
699        );
700    }
701
702    #[test]
703    fn sqlx_arguments_add_encodes_typed_null_option() {
704        let mut arguments = OdbcArguments::default();
705
706        sqlx_core::arguments::Arguments::add(&mut arguments, Option::<i32>::None).unwrap();
707
708        assert_eq!(
709            arguments.values(),
710            &[OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(
711                odbc_api::DataType::Integer
712            ))]
713        );
714
715        let collection = arguments.to_odbc_parameter_collection();
716        assert_eq!(
717            collection.as_slice()[0].data_type(),
718            odbc_api::DataType::Integer
719        );
720    }
721
722    #[test]
723    fn sqlx_arguments_reserve_and_len_work() {
724        let mut arguments = OdbcArguments::default();
725
726        sqlx_core::arguments::Arguments::reserve(&mut arguments, 2, 16);
727        sqlx_core::arguments::Arguments::add(&mut arguments, true).unwrap();
728        sqlx_core::arguments::Arguments::add(&mut arguments, 1.5_f64).unwrap();
729
730        assert_eq!(sqlx_core::arguments::Arguments::len(&arguments), 2);
731        assert_eq!(
732            arguments.values(),
733            &[OdbcArgumentValue::Bit(true), OdbcArgumentValue::Float(1.5)]
734        );
735    }
736
737    #[test]
738    fn parameter_collection_converts_basic_values_to_odbc_parameters() {
739        let values = [
740            OdbcArgumentValue::Text("abc".to_owned()),
741            OdbcArgumentValue::Bytes(vec![1, 2, 3]),
742            OdbcArgumentValue::Int(7),
743            OdbcArgumentValue::UInt(8),
744            OdbcArgumentValue::Bit(true),
745            OdbcArgumentValue::Float(1.5),
746        ];
747
748        let collection = OdbcParameterCollection::from_values(&values);
749
750        assert_eq!(collection.len(), values.len());
751        assert!(matches!(
752            collection.as_slice()[0].data_type(),
753            odbc_api::DataType::Varchar { .. }
754                | odbc_api::DataType::WVarchar { .. }
755                | odbc_api::DataType::WLongVarchar { .. }
756        ));
757        assert!(matches!(
758            collection.as_slice()[1].data_type(),
759            odbc_api::DataType::Varbinary { .. }
760        ));
761        assert_eq!(
762            collection.as_slice()[2].data_type(),
763            odbc_api::DataType::BigInt
764        );
765        assert_eq!(
766            collection.as_slice()[3].data_type(),
767            odbc_api::DataType::BigInt
768        );
769        assert_eq!(
770            collection.as_slice()[4].data_type(),
771            odbc_api::DataType::Bit
772        );
773        assert_eq!(
774            collection.as_slice()[5].data_type(),
775            odbc_api::DataType::Double
776        );
777    }
778
779    #[test]
780    fn parameter_collection_converts_temporal_values_to_typed_odbc_parameters() {
781        let values = [
782            OdbcArgumentValue::Date(odbc_api::sys::Date {
783                year: 2026,
784                month: 5,
785                day: 29,
786            }),
787            OdbcArgumentValue::Time(odbc_api::sys::Time {
788                hour: 12,
789                minute: 30,
790                second: 45,
791            }),
792            OdbcArgumentValue::Timestamp(odbc_api::sys::Timestamp {
793                year: 2026,
794                month: 5,
795                day: 29,
796                hour: 12,
797                minute: 30,
798                second: 45,
799                fraction: 123_456_789,
800            }),
801        ];
802
803        let collection = OdbcParameterCollection::from_values(&values);
804
805        assert_eq!(
806            collection.as_slice()[0].data_type(),
807            odbc_api::DataType::Date
808        );
809        assert_eq!(
810            collection.as_slice()[1].data_type(),
811            odbc_api::DataType::Time { precision: 0 }
812        );
813        assert_eq!(
814            collection.as_slice()[2].data_type(),
815            odbc_api::DataType::Timestamp { precision: 6 }
816        );
817    }
818
819    #[test]
820    fn parameter_collection_converts_typed_nulls_to_requested_data_types() {
821        let values = [
822            OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::Integer)),
823            OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::WVarchar {
824                length: None,
825            })),
826            OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::Decimal {
827                precision: 10,
828                scale: 2,
829            })),
830        ];
831
832        let collection = OdbcParameterCollection::from_values(&values);
833
834        assert_eq!(
835            collection.as_slice()[0].data_type(),
836            odbc_api::DataType::Integer
837        );
838        assert_eq!(
839            collection.as_slice()[1].data_type(),
840            odbc_api::DataType::WVarchar { length: None }
841        );
842        assert_eq!(
843            collection.as_slice()[2].data_type(),
844            odbc_api::DataType::Decimal {
845                precision: 10,
846                scale: 2
847            }
848        );
849    }
850
851    #[test]
852    fn parameter_collection_slice_matches_odbc_api_binding_shape() {
853        fn assert_parameter_collection_ref<T: ParameterCollectionRef>(_parameters: T) {}
854
855        let mut arguments = OdbcArguments::default();
856        sqlx_core::arguments::Arguments::add(&mut arguments, "abc").unwrap();
857        let collection = arguments.to_odbc_parameter_collection();
858
859        assert_parameter_collection_ref(collection.as_slice());
860    }
861
862    #[test]
863    fn fixed_sized_parameter_uses_explicit_non_null_indicator() {
864        let mut arguments = OdbcArguments::default();
865
866        sqlx_core::arguments::Arguments::add(&mut arguments, 5_i32).unwrap();
867
868        let collection = arguments.to_odbc_parameter_collection();
869        assert_eq!(collection.len(), 1);
870        assert!(!collection.as_slice()[0].indicator_ptr().is_null());
871    }
872}