1use bytes::{BufMut, BytesMut};
35
36use crate::codec::write_utf16_string;
37use crate::prelude::*;
38
39pub const TVP_TYPE_ID: u8 = 0xF3;
41
42pub const TVP_END_TOKEN: u8 = 0x00;
44
45pub const TVP_ROW_TOKEN: u8 = 0x01;
47
48pub const TVP_NULL_TOKEN: u16 = 0xFFFF;
50
51pub const DEFAULT_COLLATION: [u8; 5] = [0x09, 0x04, 0xD0, 0x00, 0x34];
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[non_exhaustive]
59pub enum TvpWireType {
60 Bit,
62 Int {
64 size: u8,
66 },
67 Float {
69 size: u8,
71 },
72 Decimal {
74 precision: u8,
76 scale: u8,
78 },
79 NVarChar {
81 max_length: u16,
83 },
84 VarChar {
86 max_length: u16,
88 },
89 VarBinary {
91 max_length: u16,
93 },
94 Guid,
96 Date,
98 Time {
100 scale: u8,
102 },
103 DateTime2 {
105 scale: u8,
107 },
108 DateTimeOffset {
110 scale: u8,
112 },
113 Money,
115 SmallMoney,
117 DateTime,
119 SmallDateTime,
121 Xml,
123}
124
125impl TvpWireType {
126 #[must_use]
128 pub const fn type_id(&self) -> u8 {
129 match self {
130 Self::Bit => 0x68, Self::Int { .. } => 0x26, Self::Float { .. } => 0x6D, Self::Decimal { .. } => 0x6C, Self::NVarChar { .. } => 0xE7, Self::VarChar { .. } => 0xA7, Self::VarBinary { .. } => 0xA5, Self::Guid => 0x24, Self::Date => 0x28, Self::Time { .. } => 0x29, Self::DateTime2 { .. } => 0x2A, Self::DateTimeOffset { .. } => 0x2B, Self::Money | Self::SmallMoney => 0x6E, Self::DateTime | Self::SmallDateTime => 0x6F, Self::Xml => 0xF1, }
146 }
147
148 pub fn encode_type_info(&self, buf: &mut BytesMut) {
150 buf.put_u8(self.type_id());
151
152 match self {
153 Self::Bit => {
154 buf.put_u8(1); }
156 Self::Int { size } | Self::Float { size } => {
157 buf.put_u8(*size);
158 }
159 Self::Decimal { precision, scale } => {
160 buf.put_u8(17); buf.put_u8(*precision);
162 buf.put_u8(*scale);
163 }
164 Self::NVarChar { max_length } => {
165 buf.put_u16_le(*max_length);
166 buf.put_slice(&DEFAULT_COLLATION);
167 }
168 Self::VarChar { max_length } => {
169 buf.put_u16_le(*max_length);
170 buf.put_slice(&DEFAULT_COLLATION);
171 }
172 Self::VarBinary { max_length } => {
173 buf.put_u16_le(*max_length);
174 }
175 Self::Guid => {
176 buf.put_u8(16); }
178 Self::Date => {
179 }
181 Self::Time { scale } | Self::DateTime2 { scale } | Self::DateTimeOffset { scale } => {
182 buf.put_u8(*scale);
183 }
184 Self::Money | Self::DateTime => {
185 buf.put_u8(8); }
187 Self::SmallMoney | Self::SmallDateTime => {
188 buf.put_u8(4); }
190 Self::Xml => {
191 buf.put_u8(0); }
194 }
195 }
196}
197
198#[derive(Debug, Clone, Copy, Default)]
200#[non_exhaustive]
201pub struct TvpColumnFlags {
202 pub nullable: bool,
204}
205
206impl TvpColumnFlags {
207 #[must_use]
209 pub const fn new(nullable: bool) -> Self {
210 Self { nullable }
211 }
212
213 #[must_use]
215 pub const fn to_bits(&self) -> u16 {
216 let mut flags = 0u16;
217 if self.nullable {
218 flags |= 0x0001;
219 }
220 flags
221 }
222}
223
224#[derive(Debug, Clone)]
226#[non_exhaustive]
227pub struct TvpColumnDef {
228 pub wire_type: TvpWireType,
230 pub flags: TvpColumnFlags,
232}
233
234impl TvpColumnDef {
235 #[must_use]
237 pub const fn new(wire_type: TvpWireType) -> Self {
238 Self {
239 wire_type,
240 flags: TvpColumnFlags { nullable: false },
241 }
242 }
243
244 #[must_use]
246 pub const fn nullable(wire_type: TvpWireType) -> Self {
247 Self {
248 wire_type,
249 flags: TvpColumnFlags { nullable: true },
250 }
251 }
252
253 pub fn encode(&self, buf: &mut BytesMut) {
257 buf.put_u32_le(0);
259
260 buf.put_u16_le(self.flags.to_bits());
262
263 self.wire_type.encode_type_info(buf);
265
266 buf.put_u8(0);
268 }
269}
270
271#[derive(Debug)]
275pub struct TvpEncoder<'a> {
276 pub schema: &'a str,
278 pub type_name: &'a str,
280 pub columns: &'a [TvpColumnDef],
282}
283
284impl<'a> TvpEncoder<'a> {
285 #[must_use]
287 pub const fn new(schema: &'a str, type_name: &'a str, columns: &'a [TvpColumnDef]) -> Self {
288 Self {
289 schema,
290 type_name,
291 columns,
292 }
293 }
294
295 pub fn encode_metadata(&self, buf: &mut BytesMut) {
306 buf.put_u8(TVP_TYPE_ID);
308
309 buf.put_u8(0);
312
313 let schema_len = self.schema.encode_utf16().count() as u8;
315 buf.put_u8(schema_len);
316 if schema_len > 0 {
317 write_utf16_string(buf, self.schema);
318 }
319
320 let type_len = self.type_name.encode_utf16().count() as u8;
322 buf.put_u8(type_len);
323 if type_len > 0 {
324 write_utf16_string(buf, self.type_name);
325 }
326
327 if self.columns.is_empty() {
329 buf.put_u16_le(TVP_NULL_TOKEN);
331 } else {
332 buf.put_u16_le(self.columns.len() as u16);
334
335 for col in self.columns {
337 col.encode(buf);
338 }
339 }
340
341 buf.put_u8(TVP_END_TOKEN);
346 }
347
348 pub fn encode_row<F>(&self, buf: &mut BytesMut, encode_values: F)
355 where
356 F: FnOnce(&mut BytesMut),
357 {
358 buf.put_u8(TVP_ROW_TOKEN);
360
361 encode_values(buf);
363 }
364
365 pub fn encode_end(&self, buf: &mut BytesMut) {
369 buf.put_u8(TVP_END_TOKEN);
370 }
371}
372
373pub fn encode_tvp_null(wire_type: &TvpWireType, buf: &mut BytesMut) {
377 match wire_type {
378 TvpWireType::NVarChar { max_length } | TvpWireType::VarChar { max_length } => {
379 if *max_length == 0xFFFF {
380 buf.put_u64_le(0xFFFFFFFFFFFFFFFF);
382 } else {
383 buf.put_u16_le(0xFFFF);
385 }
386 }
387 TvpWireType::VarBinary { max_length } => {
388 if *max_length == 0xFFFF {
389 buf.put_u64_le(0xFFFFFFFFFFFFFFFF);
390 } else {
391 buf.put_u16_le(0xFFFF);
392 }
393 }
394 TvpWireType::Xml => {
395 buf.put_u64_le(0xFFFFFFFFFFFFFFFF);
397 }
398 _ => {
399 buf.put_u8(0);
401 }
402 }
403}
404
405pub fn encode_tvp_bit(value: bool, buf: &mut BytesMut) {
407 buf.put_u8(1); buf.put_u8(if value { 1 } else { 0 });
409}
410
411pub fn encode_tvp_int(value: i64, size: u8, buf: &mut BytesMut) {
418 buf.put_u8(size); match size {
420 1 => buf.put_i8(value as i8),
421 2 => buf.put_i16_le(value as i16),
422 4 => buf.put_i32_le(value as i32),
423 8 => buf.put_i64_le(value),
424 _ => unreachable!("encode_tvp_int called with invalid size {size}; expected 1, 2, 4, or 8"),
425 }
426}
427
428pub fn encode_tvp_float(value: f64, size: u8, buf: &mut BytesMut) {
435 buf.put_u8(size); match size {
437 4 => buf.put_f32_le(value as f32),
438 8 => buf.put_f64_le(value),
439 _ => unreachable!("encode_tvp_float called with invalid size {size}; expected 4 or 8"),
440 }
441}
442
443pub fn encode_tvp_nvarchar(value: &str, max_length: u16, buf: &mut BytesMut) {
445 let utf16: Vec<u16> = value.encode_utf16().collect();
446 let byte_len = utf16.len() * 2;
447
448 if max_length == 0xFFFF {
449 buf.put_u64_le(byte_len as u64); buf.put_u32_le(byte_len as u32); for code_unit in utf16 {
453 buf.put_u16_le(code_unit);
454 }
455 buf.put_u32_le(0); } else {
457 buf.put_u16_le(byte_len as u16);
459 for code_unit in utf16 {
460 buf.put_u16_le(code_unit);
461 }
462 }
463}
464
465pub fn encode_tvp_varchar(value: &str, max_length: u16, buf: &mut BytesMut) {
474 let encoded = crate::collation::encode_str_for_collation(value, None);
475 let byte_len = encoded.len();
476
477 if max_length == 0xFFFF {
478 buf.put_u64_le(byte_len as u64); buf.put_u32_le(byte_len as u32); buf.put_slice(&encoded);
482 buf.put_u32_le(0); } else {
484 buf.put_u16_le(byte_len as u16);
486 buf.put_slice(&encoded);
487 }
488}
489
490pub fn encode_tvp_varbinary(value: &[u8], max_length: u16, buf: &mut BytesMut) {
492 if max_length == 0xFFFF {
493 buf.put_u64_le(value.len() as u64);
495 buf.put_u32_le(value.len() as u32);
496 buf.put_slice(value);
497 buf.put_u32_le(0); } else {
499 buf.put_u16_le(value.len() as u16);
500 buf.put_slice(value);
501 }
502}
503
504pub fn encode_tvp_guid(uuid_bytes: &[u8; 16], buf: &mut BytesMut) {
508 buf.put_u8(16); buf.put_u8(uuid_bytes[3]);
512 buf.put_u8(uuid_bytes[2]);
513 buf.put_u8(uuid_bytes[1]);
514 buf.put_u8(uuid_bytes[0]);
515
516 buf.put_u8(uuid_bytes[5]);
517 buf.put_u8(uuid_bytes[4]);
518
519 buf.put_u8(uuid_bytes[7]);
520 buf.put_u8(uuid_bytes[6]);
521
522 buf.put_slice(&uuid_bytes[8..16]);
523}
524
525pub fn encode_tvp_date(days: u32, buf: &mut BytesMut) {
527 buf.put_u8((days & 0xFF) as u8);
529 buf.put_u8(((days >> 8) & 0xFF) as u8);
530 buf.put_u8(((days >> 16) & 0xFF) as u8);
531}
532
533pub fn encode_tvp_time(intervals: u64, scale: u8, buf: &mut BytesMut) {
537 let len = match scale {
539 0..=2 => 3,
540 3..=4 => 4,
541 5..=7 => 5,
542 _ => 5,
543 };
544 buf.put_u8(len);
545
546 for i in 0..len {
547 buf.put_u8((intervals >> (8 * i)) as u8);
548 }
549}
550
551pub fn encode_tvp_datetime2(time_intervals: u64, days: u32, scale: u8, buf: &mut BytesMut) {
555 let time_len = match scale {
557 0..=2 => 3,
558 3..=4 => 4,
559 5..=7 => 5,
560 _ => 5,
561 };
562 buf.put_u8(time_len + 3);
563
564 for i in 0..time_len {
566 buf.put_u8((time_intervals >> (8 * i)) as u8);
567 }
568
569 buf.put_u8((days & 0xFF) as u8);
571 buf.put_u8(((days >> 8) & 0xFF) as u8);
572 buf.put_u8(((days >> 16) & 0xFF) as u8);
573}
574
575pub fn encode_tvp_datetimeoffset(
586 time_intervals: u64,
587 days: u32,
588 offset_minutes: i16,
589 scale: u8,
590 buf: &mut BytesMut,
591) {
592 let time_len = match scale {
594 0..=2 => 3,
595 3..=4 => 4,
596 5..=7 => 5,
597 _ => 5,
598 };
599 buf.put_u8(time_len + 3 + 2); for i in 0..time_len {
603 buf.put_u8((time_intervals >> (8 * i)) as u8);
604 }
605
606 buf.put_u8((days & 0xFF) as u8);
608 buf.put_u8(((days >> 8) & 0xFF) as u8);
609 buf.put_u8(((days >> 16) & 0xFF) as u8);
610
611 buf.put_i16_le(offset_minutes);
613}
614
615pub fn encode_tvp_decimal(sign: u8, mantissa: u128, buf: &mut BytesMut) {
622 buf.put_u8(17); buf.put_u8(sign);
624 buf.put_u128_le(mantissa);
625}
626
627pub fn encode_tvp_money(scaled: i64, buf: &mut BytesMut) {
635 buf.put_u8(8); let high = (scaled >> 32) as i32;
637 let low = (scaled & 0xFFFF_FFFF) as u32;
638 buf.put_i32_le(high);
639 buf.put_u32_le(low);
640}
641
642pub fn encode_tvp_smallmoney(scaled: i32, buf: &mut BytesMut) {
647 buf.put_u8(4); buf.put_i32_le(scaled);
649}
650
651pub fn encode_tvp_datetime(days: i32, ticks: u32, buf: &mut BytesMut) {
656 buf.put_u8(8); buf.put_i32_le(days);
658 buf.put_u32_le(ticks);
659}
660
661pub fn encode_tvp_smalldatetime(days: u16, minutes: u16, buf: &mut BytesMut) {
666 buf.put_u8(4); buf.put_u16_le(days);
668 buf.put_u16_le(minutes);
669}
670
671#[cfg(test)]
672#[allow(clippy::unwrap_used, clippy::expect_used)]
673mod tests {
674 use super::*;
675
676 #[test]
677 fn test_tvp_metadata_encoding() {
678 let columns = vec![TvpColumnDef::new(TvpWireType::Int { size: 4 })];
679
680 let encoder = TvpEncoder::new("dbo", "UserIdList", &columns);
681 let mut buf = BytesMut::new();
682
683 encoder.encode_metadata(&mut buf);
684
685 assert_eq!(buf[0], TVP_TYPE_ID);
687
688 assert_eq!(buf[1], 0);
690 }
691
692 #[test]
693 fn test_tvp_column_def_encoding() {
694 let col = TvpColumnDef::nullable(TvpWireType::Int { size: 4 });
695 let mut buf = BytesMut::new();
696
697 col.encode(&mut buf);
698
699 assert!(buf.len() >= 9);
701
702 assert_eq!(&buf[0..4], &[0, 0, 0, 0]);
704
705 assert_eq!(buf[4], 0x01);
707 assert_eq!(buf[5], 0x00);
708 }
709
710 #[test]
711 fn test_tvp_nvarchar_encoding() {
712 let mut buf = BytesMut::new();
713 encode_tvp_nvarchar("test", 100, &mut buf);
714
715 assert_eq!(buf.len(), 2 + 8);
717 assert_eq!(buf[0], 8); assert_eq!(buf[1], 0);
719 }
720
721 #[test]
722 fn test_tvp_int_encoding() {
723 let mut buf = BytesMut::new();
724 encode_tvp_int(42, 4, &mut buf);
725
726 assert_eq!(buf.len(), 5);
728 assert_eq!(buf[0], 4);
729 assert_eq!(buf[1], 42);
730 }
731
732 #[test]
733 fn test_tvp_money_encoding_matches_rpc_layout() {
734 let mut buf = BytesMut::new();
736 encode_tvp_money(123_400, &mut buf);
737
738 assert_eq!(buf.len(), 9, "length byte + 8-byte payload");
739 assert_eq!(buf[0], 8, "MONEYN length byte is 8 for MONEY");
740 assert_eq!(&buf[1..5], &[0, 0, 0, 0], "high word zero for small value");
742 assert_eq!(&buf[5..9], &123_400i32.to_le_bytes());
743 }
744
745 #[test]
746 fn test_tvp_money_encoding_negative_value() {
747 let mut buf = BytesMut::new();
749 encode_tvp_money(-12_300, &mut buf);
750
751 assert_eq!(buf.len(), 9);
752 assert_eq!(buf[0], 8);
753 let high = i32::from_le_bytes(buf[1..5].try_into().unwrap());
755 let low = u32::from_le_bytes(buf[5..9].try_into().unwrap());
756 let reconstructed = ((high as i64) << 32) | (low as i64 & 0xFFFF_FFFF);
757 assert_eq!(reconstructed, -12_300i64);
758 }
759
760 #[test]
761 fn test_tvp_money_encoding_max_value() {
762 let mut buf = BytesMut::new();
764 encode_tvp_money(i64::MAX, &mut buf);
765
766 assert_eq!(buf.len(), 9);
767 let high = i32::from_le_bytes(buf[1..5].try_into().unwrap());
768 let low = u32::from_le_bytes(buf[5..9].try_into().unwrap());
769 let reconstructed = ((high as i64) << 32) | (low as i64 & 0xFFFF_FFFF);
770 assert_eq!(reconstructed, i64::MAX);
771 }
772
773 #[test]
774 fn test_tvp_smallmoney_encoding() {
775 let mut buf = BytesMut::new();
777 encode_tvp_smallmoney(12_345, &mut buf);
778
779 assert_eq!(buf.len(), 5, "length byte + 4-byte payload");
780 assert_eq!(buf[0], 4);
781 assert_eq!(&buf[1..5], &12_345i32.to_le_bytes());
782 }
783
784 #[test]
785 fn test_tvp_smallmoney_encoding_negative() {
786 let mut buf = BytesMut::new();
787 encode_tvp_smallmoney(-1, &mut buf);
788
789 assert_eq!(buf.len(), 5);
790 assert_eq!(buf[0], 4);
791 assert_eq!(
792 i32::from_le_bytes(buf[1..5].try_into().unwrap()),
793 -1,
794 "SMALLMONEY wraps as signed 32-bit LE"
795 );
796 }
797
798 #[test]
799 fn test_tvp_datetime_encoding() {
800 let mut buf = BytesMut::new();
802 encode_tvp_datetime(41_275, 0, &mut buf);
803
804 assert_eq!(buf.len(), 9, "length byte + 8-byte payload");
805 assert_eq!(buf[0], 8);
806 assert_eq!(&buf[1..5], &41_275i32.to_le_bytes());
807 assert_eq!(&buf[5..9], &0u32.to_le_bytes());
808 }
809
810 #[test]
811 fn test_tvp_datetime_encoding_pre_1900() {
812 let mut buf = BytesMut::new();
814 encode_tvp_datetime(-1, 0, &mut buf);
815
816 assert_eq!(buf.len(), 9);
817 assert_eq!(
818 i32::from_le_bytes(buf[1..5].try_into().unwrap()),
819 -1,
820 "pre-1900 DATETIME uses negative days"
821 );
822 }
823
824 #[test]
825 fn test_tvp_smalldatetime_encoding() {
826 let mut buf = BytesMut::new();
828 encode_tvp_smalldatetime(43_830, 0, &mut buf);
829
830 assert_eq!(buf.len(), 5, "length byte + 4-byte payload");
831 assert_eq!(buf[0], 4);
832 assert_eq!(&buf[1..3], &43_830u16.to_le_bytes());
833 assert_eq!(&buf[3..5], &0u16.to_le_bytes());
834 }
835
836 #[test]
837 fn test_tvp_money_type_info_encoding() {
838 let mut buf = BytesMut::new();
839 TvpWireType::Money.encode_type_info(&mut buf);
840 assert_eq!(
841 &buf[..],
842 &[0x6E, 8],
843 "MONEY = MONEYN type_id with max_length 8"
844 );
845 }
846
847 #[test]
848 fn test_tvp_smallmoney_type_info_encoding() {
849 let mut buf = BytesMut::new();
850 TvpWireType::SmallMoney.encode_type_info(&mut buf);
851 assert_eq!(
852 &buf[..],
853 &[0x6E, 4],
854 "SMALLMONEY = MONEYN type_id with max_length 4"
855 );
856 }
857
858 #[test]
859 fn test_tvp_datetime_type_info_encoding() {
860 let mut buf = BytesMut::new();
861 TvpWireType::DateTime.encode_type_info(&mut buf);
862 assert_eq!(
863 &buf[..],
864 &[0x6F, 8],
865 "DATETIME = DATETIMEN type_id with max_length 8"
866 );
867 }
868
869 #[test]
870 fn test_tvp_smalldatetime_type_info_encoding() {
871 let mut buf = BytesMut::new();
872 TvpWireType::SmallDateTime.encode_type_info(&mut buf);
873 assert_eq!(
874 &buf[..],
875 &[0x6F, 4],
876 "SMALLDATETIME = DATETIMEN type_id with max_length 4"
877 );
878 }
879
880 #[test]
881 fn test_tvp_null_for_money_is_length_zero() {
882 let mut buf = BytesMut::new();
883 encode_tvp_null(&TvpWireType::Money, &mut buf);
884 assert_eq!(&buf[..], &[0], "MONEYN NULL is a single length-zero byte");
885
886 let mut buf = BytesMut::new();
887 encode_tvp_null(&TvpWireType::SmallDateTime, &mut buf);
888 assert_eq!(
889 &buf[..],
890 &[0],
891 "DATETIMEN NULL is a single length-zero byte"
892 );
893 }
894}