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()
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
419impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for &'q [u8] {
420    fn encode_by_ref(
421        &self,
422        buf: &mut Vec<OdbcArgumentValue>,
423    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
424        buf.push(OdbcArgumentValue::Bytes((*self).to_owned()));
425        Ok(sqlx_core::encode::IsNull::No)
426    }
427}
428
429impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for Vec<u8> {
430    fn encode_by_ref(
431        &self,
432        buf: &mut Vec<OdbcArgumentValue>,
433    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
434        buf.push(OdbcArgumentValue::Bytes(self.clone()));
435        Ok(sqlx_core::encode::IsNull::No)
436    }
437}
438
439impl sqlx_core::types::Type<crate::Odbc> for odbc_api::sys::Date {
440    fn type_info() -> crate::OdbcTypeInfo {
441        crate::OdbcTypeInfo::DATE
442    }
443
444    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
445        matches!(ty.data_type(), odbc_api::DataType::Date)
446    }
447}
448
449impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for odbc_api::sys::Date {
450    fn encode_by_ref(
451        &self,
452        buf: &mut Vec<OdbcArgumentValue>,
453    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
454        buf.push(OdbcArgumentValue::Date(*self));
455        Ok(sqlx_core::encode::IsNull::No)
456    }
457}
458
459impl sqlx_core::types::Type<crate::Odbc> for odbc_api::sys::Time {
460    fn type_info() -> crate::OdbcTypeInfo {
461        crate::OdbcTypeInfo::TIME
462    }
463
464    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
465        matches!(ty.data_type(), odbc_api::DataType::Time { .. })
466    }
467}
468
469impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for odbc_api::sys::Time {
470    fn encode_by_ref(
471        &self,
472        buf: &mut Vec<OdbcArgumentValue>,
473    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
474        buf.push(OdbcArgumentValue::Time(*self));
475        Ok(sqlx_core::encode::IsNull::No)
476    }
477}
478
479impl sqlx_core::types::Type<crate::Odbc> for odbc_api::sys::Timestamp {
480    fn type_info() -> crate::OdbcTypeInfo {
481        crate::OdbcTypeInfo::TIMESTAMP
482    }
483
484    fn compatible(ty: &crate::OdbcTypeInfo) -> bool {
485        matches!(ty.data_type(), odbc_api::DataType::Timestamp { .. })
486    }
487}
488
489impl<'q> sqlx_core::encode::Encode<'q, crate::Odbc> for odbc_api::sys::Timestamp {
490    fn encode_by_ref(
491        &self,
492        buf: &mut Vec<OdbcArgumentValue>,
493    ) -> Result<sqlx_core::encode::IsNull, sqlx_core::error::BoxDynError> {
494        buf.push(OdbcArgumentValue::Timestamp(*self));
495        Ok(sqlx_core::encode::IsNull::No)
496    }
497}
498
499fn value_to_parameter(value: &OdbcArgumentValue) -> Box<dyn InputParameter> {
500    match value {
501        OdbcArgumentValue::Text(value) => Box::new(value.clone().into_parameter()),
502        OdbcArgumentValue::Bytes(value) => Box::new(value.clone().into_parameter()),
503        OdbcArgumentValue::Int(value) => Box::new(Some(*value).into_parameter()),
504        OdbcArgumentValue::UInt(value) => Box::new(
505            WithDataType::new(Nullable::new(*value), odbc_api::DataType::BigInt).into_parameter(),
506        ),
507        OdbcArgumentValue::Bit(value) => Box::new(odbc_api::Bit::from_bool(*value)),
508        OdbcArgumentValue::Float(value) => Box::new(Some(*value).into_parameter()),
509        OdbcArgumentValue::Date(value) => Box::new(Nullable::new(*value).into_parameter()),
510        OdbcArgumentValue::Time(value) => Box::new(
511            WithDataType::new(
512                Nullable::new(*value),
513                odbc_api::DataType::Time { precision: 0 },
514            )
515            .into_parameter(),
516        ),
517        OdbcArgumentValue::Timestamp(value) => Box::new(
518            WithDataType::new(
519                Nullable::new(*value),
520                odbc_api::DataType::Timestamp { precision: 6 },
521            )
522            .into_parameter(),
523        ),
524        OdbcArgumentValue::Null(type_info) => null_parameter(type_info.data_type()),
525    }
526}
527
528fn null_parameter(data_type: odbc_api::DataType) -> Box<dyn InputParameter> {
529    match data_type {
530        odbc_api::DataType::TinyInt => Box::new(Nullable::<i8>::null()),
531        odbc_api::DataType::SmallInt => Box::new(Nullable::<i16>::null()),
532        odbc_api::DataType::Integer => Box::new(Nullable::<i32>::null()),
533        odbc_api::DataType::BigInt => Box::new(Nullable::<i64>::null()),
534        odbc_api::DataType::Bit => Box::new(Nullable::<odbc_api::Bit>::null()),
535        odbc_api::DataType::Real => Box::new(Nullable::<f32>::null()),
536        odbc_api::DataType::Double => Box::new(Nullable::<f64>::null()),
537        odbc_api::DataType::Float { .. } => {
538            Box::new(WithDataType::new(Nullable::<f64>::null(), data_type))
539        }
540        odbc_api::DataType::Date => Box::new(Nullable::<odbc_api::sys::Date>::null()),
541        odbc_api::DataType::Time { .. } => Box::new(WithDataType::new(
542            Nullable::<odbc_api::sys::Time>::null(),
543            data_type,
544        )),
545        odbc_api::DataType::Timestamp { .. } => Box::new(WithDataType::new(
546            Nullable::<odbc_api::sys::Timestamp>::null(),
547            data_type,
548        )),
549        odbc_api::DataType::Varbinary { .. }
550        | odbc_api::DataType::LongVarbinary { .. }
551        | odbc_api::DataType::Binary { .. } => {
552            Box::new(WithDataType::new(VarBinaryBox::null(), data_type))
553        }
554        odbc_api::DataType::WVarchar { .. }
555        | odbc_api::DataType::WLongVarchar { .. }
556        | odbc_api::DataType::WChar { .. } => {
557            Box::new(WithDataType::new(VarWCharBox::null(), data_type))
558        }
559        odbc_api::DataType::Char { .. }
560        | odbc_api::DataType::Varchar { .. }
561        | odbc_api::DataType::LongVarchar { .. }
562        | odbc_api::DataType::Numeric { .. }
563        | odbc_api::DataType::Decimal { .. }
564        | odbc_api::DataType::Unknown
565        | odbc_api::DataType::Other { .. } => {
566            Box::new(WithDataType::new(VarCharBox::null(), data_type))
567        }
568    }
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574    use odbc_api::{
575        handles::{CData, HasDataType},
576        ParameterCollectionRef,
577    };
578
579    #[test]
580    fn argument_buffer_tracks_values_in_order() {
581        let mut arguments = OdbcArguments::default();
582
583        arguments.add_value(OdbcArgumentValue::Int(7));
584        arguments.add_value(OdbcArgumentValue::Text("abc".to_owned()));
585        arguments.add_value(OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(
586            odbc_api::DataType::Integer,
587        )));
588
589        assert_eq!(arguments.len(), 3);
590        assert_eq!(
591            arguments.values(),
592            &[
593                OdbcArgumentValue::Int(7),
594                OdbcArgumentValue::Text("abc".to_owned()),
595                OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::Integer))
596            ]
597        );
598    }
599
600    #[test]
601    fn sqlx_arguments_add_encodes_basic_scalars() {
602        let mut arguments = OdbcArguments::default();
603
604        sqlx_core::arguments::Arguments::add(&mut arguments, 7_i32).unwrap();
605        sqlx_core::arguments::Arguments::add(&mut arguments, "abc").unwrap();
606        sqlx_core::arguments::Arguments::add(&mut arguments, vec![1_u8, 2, 3]).unwrap();
607
608        assert_eq!(
609            arguments.values(),
610            &[
611                OdbcArgumentValue::Int(7),
612                OdbcArgumentValue::Text("abc".to_owned()),
613                OdbcArgumentValue::Bytes(vec![1, 2, 3])
614            ]
615        );
616    }
617
618    #[test]
619    fn sqlx_arguments_add_encodes_large_text_and_binary_slices() {
620        let mut arguments = OdbcArguments::default();
621        let text = "abc123".repeat(16 * 1024);
622        let bytes = [0_u8, 1, 2, 127, 128, 254, 255];
623
624        sqlx_core::arguments::Arguments::add(&mut arguments, text.as_str()).unwrap();
625        sqlx_core::arguments::Arguments::add(&mut arguments, &bytes[..]).unwrap();
626
627        assert_eq!(
628            arguments.values(),
629            &[
630                OdbcArgumentValue::Text(text),
631                OdbcArgumentValue::Bytes(bytes.to_vec())
632            ]
633        );
634    }
635
636    #[test]
637    fn sqlx_arguments_add_preserves_large_unsigned_values() {
638        let mut arguments = OdbcArguments::default();
639
640        sqlx_core::arguments::Arguments::add(&mut arguments, u64::MAX).unwrap();
641
642        assert_eq!(arguments.values(), &[OdbcArgumentValue::UInt(u64::MAX)]);
643    }
644
645    #[test]
646    fn sqlx_arguments_add_encodes_temporal_scalars() {
647        let mut arguments = OdbcArguments::default();
648        let date = odbc_api::sys::Date {
649            year: 2026,
650            month: 5,
651            day: 29,
652        };
653        let time = odbc_api::sys::Time {
654            hour: 12,
655            minute: 30,
656            second: 45,
657        };
658        let timestamp = odbc_api::sys::Timestamp {
659            year: 2026,
660            month: 5,
661            day: 29,
662            hour: 12,
663            minute: 30,
664            second: 45,
665            fraction: 123_456_000,
666        };
667
668        sqlx_core::arguments::Arguments::add(&mut arguments, date).unwrap();
669        sqlx_core::arguments::Arguments::add(&mut arguments, time).unwrap();
670        sqlx_core::arguments::Arguments::add(&mut arguments, timestamp).unwrap();
671
672        assert_eq!(
673            arguments.values(),
674            &[
675                OdbcArgumentValue::Date(date),
676                OdbcArgumentValue::Time(time),
677                OdbcArgumentValue::Timestamp(timestamp)
678            ]
679        );
680    }
681
682    #[test]
683    fn sqlx_arguments_add_encodes_typed_null_option() {
684        let mut arguments = OdbcArguments::default();
685
686        sqlx_core::arguments::Arguments::add(&mut arguments, Option::<i32>::None).unwrap();
687
688        assert_eq!(
689            arguments.values(),
690            &[OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(
691                odbc_api::DataType::Integer
692            ))]
693        );
694
695        let collection = arguments.to_odbc_parameter_collection();
696        assert_eq!(
697            collection.as_slice()[0].data_type(),
698            odbc_api::DataType::Integer
699        );
700    }
701
702    #[test]
703    fn sqlx_arguments_reserve_and_len_work() {
704        let mut arguments = OdbcArguments::default();
705
706        sqlx_core::arguments::Arguments::reserve(&mut arguments, 2, 16);
707        sqlx_core::arguments::Arguments::add(&mut arguments, true).unwrap();
708        sqlx_core::arguments::Arguments::add(&mut arguments, 1.5_f64).unwrap();
709
710        assert_eq!(sqlx_core::arguments::Arguments::len(&arguments), 2);
711        assert_eq!(
712            arguments.values(),
713            &[OdbcArgumentValue::Bit(true), OdbcArgumentValue::Float(1.5)]
714        );
715    }
716
717    #[test]
718    fn parameter_collection_converts_basic_values_to_odbc_parameters() {
719        let values = [
720            OdbcArgumentValue::Text("abc".to_owned()),
721            OdbcArgumentValue::Bytes(vec![1, 2, 3]),
722            OdbcArgumentValue::Int(7),
723            OdbcArgumentValue::UInt(8),
724            OdbcArgumentValue::Bit(true),
725            OdbcArgumentValue::Float(1.5),
726        ];
727
728        let collection = OdbcParameterCollection::from_values(&values);
729
730        assert_eq!(collection.len(), values.len());
731        assert!(matches!(
732            collection.as_slice()[0].data_type(),
733            odbc_api::DataType::Varchar { .. }
734                | odbc_api::DataType::WVarchar { .. }
735                | odbc_api::DataType::WLongVarchar { .. }
736        ));
737        assert!(matches!(
738            collection.as_slice()[1].data_type(),
739            odbc_api::DataType::Varbinary { .. }
740        ));
741        assert_eq!(
742            collection.as_slice()[2].data_type(),
743            odbc_api::DataType::BigInt
744        );
745        assert_eq!(
746            collection.as_slice()[3].data_type(),
747            odbc_api::DataType::BigInt
748        );
749        assert_eq!(
750            collection.as_slice()[4].data_type(),
751            odbc_api::DataType::Bit
752        );
753        assert_eq!(
754            collection.as_slice()[5].data_type(),
755            odbc_api::DataType::Double
756        );
757    }
758
759    #[test]
760    fn parameter_collection_converts_temporal_values_to_typed_odbc_parameters() {
761        let values = [
762            OdbcArgumentValue::Date(odbc_api::sys::Date {
763                year: 2026,
764                month: 5,
765                day: 29,
766            }),
767            OdbcArgumentValue::Time(odbc_api::sys::Time {
768                hour: 12,
769                minute: 30,
770                second: 45,
771            }),
772            OdbcArgumentValue::Timestamp(odbc_api::sys::Timestamp {
773                year: 2026,
774                month: 5,
775                day: 29,
776                hour: 12,
777                minute: 30,
778                second: 45,
779                fraction: 123_456_789,
780            }),
781        ];
782
783        let collection = OdbcParameterCollection::from_values(&values);
784
785        assert_eq!(
786            collection.as_slice()[0].data_type(),
787            odbc_api::DataType::Date
788        );
789        assert_eq!(
790            collection.as_slice()[1].data_type(),
791            odbc_api::DataType::Time { precision: 0 }
792        );
793        assert_eq!(
794            collection.as_slice()[2].data_type(),
795            odbc_api::DataType::Timestamp { precision: 6 }
796        );
797    }
798
799    #[test]
800    fn parameter_collection_converts_typed_nulls_to_requested_data_types() {
801        let values = [
802            OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::Integer)),
803            OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::WVarchar {
804                length: None,
805            })),
806            OdbcArgumentValue::Null(crate::OdbcTypeInfo::new(odbc_api::DataType::Decimal {
807                precision: 10,
808                scale: 2,
809            })),
810        ];
811
812        let collection = OdbcParameterCollection::from_values(&values);
813
814        assert_eq!(
815            collection.as_slice()[0].data_type(),
816            odbc_api::DataType::Integer
817        );
818        assert_eq!(
819            collection.as_slice()[1].data_type(),
820            odbc_api::DataType::WVarchar { length: None }
821        );
822        assert_eq!(
823            collection.as_slice()[2].data_type(),
824            odbc_api::DataType::Decimal {
825                precision: 10,
826                scale: 2
827            }
828        );
829    }
830
831    #[test]
832    fn parameter_collection_slice_matches_odbc_api_binding_shape() {
833        fn assert_parameter_collection_ref<T: ParameterCollectionRef>(_parameters: T) {}
834
835        let mut arguments = OdbcArguments::default();
836        sqlx_core::arguments::Arguments::add(&mut arguments, "abc").unwrap();
837        let collection = arguments.to_odbc_parameter_collection();
838
839        assert_parameter_collection_ref(collection.as_slice());
840    }
841
842    #[test]
843    fn fixed_sized_parameter_uses_explicit_non_null_indicator() {
844        let mut arguments = OdbcArguments::default();
845
846        sqlx_core::arguments::Arguments::add(&mut arguments, 5_i32).unwrap();
847
848        let collection = arguments.to_odbc_parameter_collection();
849        assert_eq!(collection.len(), 1);
850        assert!(!collection.as_slice()[0].indicator_ptr().is_null());
851    }
852}