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()
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}