1use crate::DataTypeExt;
2use odbc_api::{
3 parameter::{InputParameter, VarBinaryBox, VarCharBox, VarWCharBox, WithDataType},
4 IntoParameter, Nullable,
5};
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum OdbcArgumentValue {
10 Text(String),
12 Bytes(Vec<u8>),
14 Int(i64),
16 UInt(u64),
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::OdbcTypeInfo),
30}
31
32#[derive(Debug, Default, Clone, PartialEq)]
34pub struct OdbcArguments {
35 values: Vec<OdbcArgumentValue>,
36}
37
38#[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 pub fn from_values(values: &[OdbcArgumentValue]) -> 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 OdbcArguments {
80 pub fn add_value(&mut self, value: OdbcArgumentValue) {
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) -> &[OdbcArgumentValue] {
97 &self.values
98 }
99
100 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}