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) {
154 self.encode_type_info_with_collation(buf, None);
155 }
156
157 pub fn encode_type_info_with_collation(
165 &self,
166 buf: &mut BytesMut,
167 collation: Option<&crate::token::Collation>,
168 ) {
169 buf.put_u8(self.type_id());
170
171 let collation_bytes = collation.map_or(DEFAULT_COLLATION, |c| c.to_bytes());
172
173 match self {
174 Self::Bit => {
175 buf.put_u8(1); }
177 Self::Int { size } | Self::Float { size } => {
178 buf.put_u8(*size);
179 }
180 Self::Decimal { precision, scale } => {
181 buf.put_u8(17); buf.put_u8(*precision);
183 buf.put_u8(*scale);
184 }
185 Self::NVarChar { max_length } => {
186 buf.put_u16_le(*max_length);
187 buf.put_slice(&collation_bytes);
188 }
189 Self::VarChar { max_length } => {
190 buf.put_u16_le(*max_length);
191 buf.put_slice(&collation_bytes);
192 }
193 Self::VarBinary { max_length } => {
194 buf.put_u16_le(*max_length);
195 }
196 Self::Guid => {
197 buf.put_u8(16); }
199 Self::Date => {
200 }
202 Self::Time { scale } | Self::DateTime2 { scale } | Self::DateTimeOffset { scale } => {
203 buf.put_u8(*scale);
204 }
205 Self::Money | Self::DateTime => {
206 buf.put_u8(8); }
208 Self::SmallMoney | Self::SmallDateTime => {
209 buf.put_u8(4); }
211 Self::Xml => {
212 buf.put_u8(0); }
215 }
216 }
217}
218
219#[derive(Debug, Clone, Copy, Default)]
221#[non_exhaustive]
222pub struct TvpColumnFlags {
223 pub nullable: bool,
225}
226
227impl TvpColumnFlags {
228 #[must_use]
230 pub const fn new(nullable: bool) -> Self {
231 Self { nullable }
232 }
233
234 #[must_use]
236 pub const fn to_bits(&self) -> u16 {
237 let mut flags = 0u16;
238 if self.nullable {
239 flags |= 0x0001;
240 }
241 flags
242 }
243}
244
245#[derive(Debug, Clone)]
247#[non_exhaustive]
248pub struct TvpColumnDef {
249 pub wire_type: TvpWireType,
251 pub flags: TvpColumnFlags,
253}
254
255impl TvpColumnDef {
256 #[must_use]
258 pub const fn new(wire_type: TvpWireType) -> Self {
259 Self {
260 wire_type,
261 flags: TvpColumnFlags { nullable: false },
262 }
263 }
264
265 #[must_use]
267 pub const fn nullable(wire_type: TvpWireType) -> Self {
268 Self {
269 wire_type,
270 flags: TvpColumnFlags { nullable: true },
271 }
272 }
273
274 pub fn encode(&self, buf: &mut BytesMut) {
278 self.encode_with_collation(buf, None);
279 }
280
281 pub fn encode_with_collation(
283 &self,
284 buf: &mut BytesMut,
285 collation: Option<&crate::token::Collation>,
286 ) {
287 buf.put_u32_le(0);
289
290 buf.put_u16_le(self.flags.to_bits());
292
293 self.wire_type
295 .encode_type_info_with_collation(buf, collation);
296
297 buf.put_u8(0);
299 }
300}
301
302#[derive(Debug)]
306pub struct TvpEncoder<'a> {
307 pub schema: &'a str,
309 pub type_name: &'a str,
311 pub columns: &'a [TvpColumnDef],
313}
314
315impl<'a> TvpEncoder<'a> {
316 #[must_use]
318 pub const fn new(schema: &'a str, type_name: &'a str, columns: &'a [TvpColumnDef]) -> Self {
319 Self {
320 schema,
321 type_name,
322 columns,
323 }
324 }
325
326 pub fn encode_metadata(&self, buf: &mut BytesMut) {
341 self.encode_metadata_with_collation(buf, None);
342 }
343
344 pub fn encode_metadata_with_collation(
351 &self,
352 buf: &mut BytesMut,
353 collation: Option<&crate::token::Collation>,
354 ) {
355 buf.put_u8(TVP_TYPE_ID);
357
358 buf.put_u8(0);
361
362 let schema_len = self.schema.encode_utf16().count() as u8;
364 buf.put_u8(schema_len);
365 if schema_len > 0 {
366 write_utf16_string(buf, self.schema);
367 }
368
369 let type_len = self.type_name.encode_utf16().count() as u8;
371 buf.put_u8(type_len);
372 if type_len > 0 {
373 write_utf16_string(buf, self.type_name);
374 }
375
376 if self.columns.is_empty() {
378 buf.put_u16_le(TVP_NULL_TOKEN);
380 } else {
381 buf.put_u16_le(self.columns.len() as u16);
383
384 for col in self.columns {
386 col.encode_with_collation(buf, collation);
387 }
388 }
389
390 buf.put_u8(TVP_END_TOKEN);
395 }
396
397 pub fn encode_row<F>(&self, buf: &mut BytesMut, encode_values: F)
404 where
405 F: FnOnce(&mut BytesMut),
406 {
407 buf.put_u8(TVP_ROW_TOKEN);
409
410 encode_values(buf);
412 }
413
414 pub fn encode_end(&self, buf: &mut BytesMut) {
418 buf.put_u8(TVP_END_TOKEN);
419 }
420}
421
422pub fn encode_tvp_null(wire_type: &TvpWireType, buf: &mut BytesMut) {
426 match wire_type {
427 TvpWireType::NVarChar { max_length } | TvpWireType::VarChar { max_length } => {
428 if *max_length == 0xFFFF {
429 buf.put_u64_le(0xFFFFFFFFFFFFFFFF);
431 } else {
432 buf.put_u16_le(0xFFFF);
434 }
435 }
436 TvpWireType::VarBinary { max_length } => {
437 if *max_length == 0xFFFF {
438 buf.put_u64_le(0xFFFFFFFFFFFFFFFF);
439 } else {
440 buf.put_u16_le(0xFFFF);
441 }
442 }
443 TvpWireType::Xml => {
444 buf.put_u64_le(0xFFFFFFFFFFFFFFFF);
446 }
447 _ => {
448 buf.put_u8(0);
450 }
451 }
452}
453
454pub fn encode_tvp_bit(value: bool, buf: &mut BytesMut) {
456 buf.put_u8(1); buf.put_u8(if value { 1 } else { 0 });
458}
459
460pub fn encode_tvp_int(value: i64, size: u8, buf: &mut BytesMut) {
467 buf.put_u8(size); match size {
469 1 => buf.put_i8(value as i8),
470 2 => buf.put_i16_le(value as i16),
471 4 => buf.put_i32_le(value as i32),
472 8 => buf.put_i64_le(value),
473 _ => unreachable!("encode_tvp_int called with invalid size {size}; expected 1, 2, 4, or 8"),
474 }
475}
476
477pub fn encode_tvp_float(value: f64, size: u8, buf: &mut BytesMut) {
484 buf.put_u8(size); match size {
486 4 => buf.put_f32_le(value as f32),
487 8 => buf.put_f64_le(value),
488 _ => unreachable!("encode_tvp_float called with invalid size {size}; expected 4 or 8"),
489 }
490}
491
492pub fn encode_tvp_nvarchar(value: &str, max_length: u16, buf: &mut BytesMut) {
494 let utf16: Vec<u16> = value.encode_utf16().collect();
495 let byte_len = utf16.len() * 2;
496
497 if max_length == 0xFFFF {
498 buf.put_u64_le(byte_len as u64); buf.put_u32_le(byte_len as u32); for code_unit in utf16 {
502 buf.put_u16_le(code_unit);
503 }
504 buf.put_u32_le(0); } else {
506 buf.put_u16_le(byte_len as u16);
508 for code_unit in utf16 {
509 buf.put_u16_le(code_unit);
510 }
511 }
512}
513
514pub fn encode_tvp_varchar(value: &str, max_length: u16, buf: &mut BytesMut) {
525 encode_tvp_varchar_with_collation(value, max_length, None, buf);
526}
527
528pub fn encode_tvp_varchar_with_collation(
535 value: &str,
536 max_length: u16,
537 collation: Option<&crate::token::Collation>,
538 buf: &mut BytesMut,
539) {
540 let encoded = crate::collation::encode_str_for_collation(value, collation);
541 let byte_len = encoded.len();
542
543 if max_length == 0xFFFF {
544 buf.put_u64_le(byte_len as u64); buf.put_u32_le(byte_len as u32); buf.put_slice(&encoded);
548 buf.put_u32_le(0); } else {
550 buf.put_u16_le(byte_len as u16);
552 buf.put_slice(&encoded);
553 }
554}
555
556pub fn encode_tvp_varbinary(value: &[u8], max_length: u16, buf: &mut BytesMut) {
558 if max_length == 0xFFFF {
559 buf.put_u64_le(value.len() as u64);
561 buf.put_u32_le(value.len() as u32);
562 buf.put_slice(value);
563 buf.put_u32_le(0); } else {
565 buf.put_u16_le(value.len() as u16);
566 buf.put_slice(value);
567 }
568}
569
570pub fn encode_tvp_guid(uuid_bytes: &[u8; 16], buf: &mut BytesMut) {
574 buf.put_u8(16); buf.put_u8(uuid_bytes[3]);
578 buf.put_u8(uuid_bytes[2]);
579 buf.put_u8(uuid_bytes[1]);
580 buf.put_u8(uuid_bytes[0]);
581
582 buf.put_u8(uuid_bytes[5]);
583 buf.put_u8(uuid_bytes[4]);
584
585 buf.put_u8(uuid_bytes[7]);
586 buf.put_u8(uuid_bytes[6]);
587
588 buf.put_slice(&uuid_bytes[8..16]);
589}
590
591pub fn encode_tvp_date(days: u32, buf: &mut BytesMut) {
593 buf.put_u8((days & 0xFF) as u8);
595 buf.put_u8(((days >> 8) & 0xFF) as u8);
596 buf.put_u8(((days >> 16) & 0xFF) as u8);
597}
598
599pub fn encode_tvp_time(intervals: u64, scale: u8, buf: &mut BytesMut) {
603 let len = match scale {
605 0..=2 => 3,
606 3..=4 => 4,
607 5..=7 => 5,
608 _ => 5,
609 };
610 buf.put_u8(len);
611
612 for i in 0..len {
613 buf.put_u8((intervals >> (8 * i)) as u8);
614 }
615}
616
617pub fn encode_tvp_datetime2(time_intervals: u64, days: u32, scale: u8, buf: &mut BytesMut) {
621 let time_len = match scale {
623 0..=2 => 3,
624 3..=4 => 4,
625 5..=7 => 5,
626 _ => 5,
627 };
628 buf.put_u8(time_len + 3);
629
630 for i in 0..time_len {
632 buf.put_u8((time_intervals >> (8 * i)) as u8);
633 }
634
635 buf.put_u8((days & 0xFF) as u8);
637 buf.put_u8(((days >> 8) & 0xFF) as u8);
638 buf.put_u8(((days >> 16) & 0xFF) as u8);
639}
640
641pub fn encode_tvp_datetimeoffset(
652 time_intervals: u64,
653 days: u32,
654 offset_minutes: i16,
655 scale: u8,
656 buf: &mut BytesMut,
657) {
658 let time_len = match scale {
660 0..=2 => 3,
661 3..=4 => 4,
662 5..=7 => 5,
663 _ => 5,
664 };
665 buf.put_u8(time_len + 3 + 2); for i in 0..time_len {
669 buf.put_u8((time_intervals >> (8 * i)) as u8);
670 }
671
672 buf.put_u8((days & 0xFF) as u8);
674 buf.put_u8(((days >> 8) & 0xFF) as u8);
675 buf.put_u8(((days >> 16) & 0xFF) as u8);
676
677 buf.put_i16_le(offset_minutes);
679}
680
681pub fn encode_tvp_decimal(sign: u8, mantissa: u128, buf: &mut BytesMut) {
688 buf.put_u8(17); buf.put_u8(sign);
690 buf.put_u128_le(mantissa);
691}
692
693pub fn encode_tvp_money(scaled: i64, buf: &mut BytesMut) {
701 buf.put_u8(8); let high = (scaled >> 32) as i32;
703 let low = (scaled & 0xFFFF_FFFF) as u32;
704 buf.put_i32_le(high);
705 buf.put_u32_le(low);
706}
707
708pub fn encode_tvp_smallmoney(scaled: i32, buf: &mut BytesMut) {
713 buf.put_u8(4); buf.put_i32_le(scaled);
715}
716
717pub fn encode_tvp_datetime(days: i32, ticks: u32, buf: &mut BytesMut) {
722 buf.put_u8(8); buf.put_i32_le(days);
724 buf.put_u32_le(ticks);
725}
726
727pub fn encode_tvp_smalldatetime(days: u16, minutes: u16, buf: &mut BytesMut) {
732 buf.put_u8(4); buf.put_u16_le(days);
734 buf.put_u16_le(minutes);
735}
736
737#[cfg(test)]
738#[allow(clippy::unwrap_used, clippy::expect_used)]
739mod tests {
740 use super::*;
741
742 #[test]
743 fn test_tvp_metadata_encoding() {
744 let columns = vec![TvpColumnDef::new(TvpWireType::Int { size: 4 })];
745
746 let encoder = TvpEncoder::new("dbo", "UserIdList", &columns);
747 let mut buf = BytesMut::new();
748
749 encoder.encode_metadata(&mut buf);
750
751 assert_eq!(buf[0], TVP_TYPE_ID);
753
754 assert_eq!(buf[1], 0);
756 }
757
758 #[test]
759 fn test_tvp_column_def_encoding() {
760 let col = TvpColumnDef::nullable(TvpWireType::Int { size: 4 });
761 let mut buf = BytesMut::new();
762
763 col.encode(&mut buf);
764
765 assert!(buf.len() >= 9);
767
768 assert_eq!(&buf[0..4], &[0, 0, 0, 0]);
770
771 assert_eq!(buf[4], 0x01);
773 assert_eq!(buf[5], 0x00);
774 }
775
776 #[test]
777 fn test_tvp_nvarchar_encoding() {
778 let mut buf = BytesMut::new();
779 encode_tvp_nvarchar("test", 100, &mut buf);
780
781 assert_eq!(buf.len(), 2 + 8);
783 assert_eq!(buf[0], 8); assert_eq!(buf[1], 0);
785 }
786
787 #[test]
788 fn test_tvp_int_encoding() {
789 let mut buf = BytesMut::new();
790 encode_tvp_int(42, 4, &mut buf);
791
792 assert_eq!(buf.len(), 5);
794 assert_eq!(buf[0], 4);
795 assert_eq!(buf[1], 42);
796 }
797
798 #[test]
799 fn test_tvp_money_encoding_matches_rpc_layout() {
800 let mut buf = BytesMut::new();
802 encode_tvp_money(123_400, &mut buf);
803
804 assert_eq!(buf.len(), 9, "length byte + 8-byte payload");
805 assert_eq!(buf[0], 8, "MONEYN length byte is 8 for MONEY");
806 assert_eq!(&buf[1..5], &[0, 0, 0, 0], "high word zero for small value");
808 assert_eq!(&buf[5..9], &123_400i32.to_le_bytes());
809 }
810
811 #[test]
812 fn test_tvp_money_encoding_negative_value() {
813 let mut buf = BytesMut::new();
815 encode_tvp_money(-12_300, &mut buf);
816
817 assert_eq!(buf.len(), 9);
818 assert_eq!(buf[0], 8);
819 let high = i32::from_le_bytes(buf[1..5].try_into().unwrap());
821 let low = u32::from_le_bytes(buf[5..9].try_into().unwrap());
822 let reconstructed = ((high as i64) << 32) | (low as i64 & 0xFFFF_FFFF);
823 assert_eq!(reconstructed, -12_300i64);
824 }
825
826 #[test]
827 fn test_tvp_money_encoding_max_value() {
828 let mut buf = BytesMut::new();
830 encode_tvp_money(i64::MAX, &mut buf);
831
832 assert_eq!(buf.len(), 9);
833 let high = i32::from_le_bytes(buf[1..5].try_into().unwrap());
834 let low = u32::from_le_bytes(buf[5..9].try_into().unwrap());
835 let reconstructed = ((high as i64) << 32) | (low as i64 & 0xFFFF_FFFF);
836 assert_eq!(reconstructed, i64::MAX);
837 }
838
839 #[test]
840 fn test_tvp_smallmoney_encoding() {
841 let mut buf = BytesMut::new();
843 encode_tvp_smallmoney(12_345, &mut buf);
844
845 assert_eq!(buf.len(), 5, "length byte + 4-byte payload");
846 assert_eq!(buf[0], 4);
847 assert_eq!(&buf[1..5], &12_345i32.to_le_bytes());
848 }
849
850 #[test]
851 fn test_tvp_smallmoney_encoding_negative() {
852 let mut buf = BytesMut::new();
853 encode_tvp_smallmoney(-1, &mut buf);
854
855 assert_eq!(buf.len(), 5);
856 assert_eq!(buf[0], 4);
857 assert_eq!(
858 i32::from_le_bytes(buf[1..5].try_into().unwrap()),
859 -1,
860 "SMALLMONEY wraps as signed 32-bit LE"
861 );
862 }
863
864 #[test]
865 fn test_tvp_datetime_encoding() {
866 let mut buf = BytesMut::new();
868 encode_tvp_datetime(41_275, 0, &mut buf);
869
870 assert_eq!(buf.len(), 9, "length byte + 8-byte payload");
871 assert_eq!(buf[0], 8);
872 assert_eq!(&buf[1..5], &41_275i32.to_le_bytes());
873 assert_eq!(&buf[5..9], &0u32.to_le_bytes());
874 }
875
876 #[test]
877 fn test_tvp_datetime_encoding_pre_1900() {
878 let mut buf = BytesMut::new();
880 encode_tvp_datetime(-1, 0, &mut buf);
881
882 assert_eq!(buf.len(), 9);
883 assert_eq!(
884 i32::from_le_bytes(buf[1..5].try_into().unwrap()),
885 -1,
886 "pre-1900 DATETIME uses negative days"
887 );
888 }
889
890 #[test]
891 fn test_tvp_smalldatetime_encoding() {
892 let mut buf = BytesMut::new();
894 encode_tvp_smalldatetime(43_830, 0, &mut buf);
895
896 assert_eq!(buf.len(), 5, "length byte + 4-byte payload");
897 assert_eq!(buf[0], 4);
898 assert_eq!(&buf[1..3], &43_830u16.to_le_bytes());
899 assert_eq!(&buf[3..5], &0u16.to_le_bytes());
900 }
901
902 #[test]
903 fn test_tvp_money_type_info_encoding() {
904 let mut buf = BytesMut::new();
905 TvpWireType::Money.encode_type_info(&mut buf);
906 assert_eq!(
907 &buf[..],
908 &[0x6E, 8],
909 "MONEY = MONEYN type_id with max_length 8"
910 );
911 }
912
913 #[test]
914 fn test_tvp_smallmoney_type_info_encoding() {
915 let mut buf = BytesMut::new();
916 TvpWireType::SmallMoney.encode_type_info(&mut buf);
917 assert_eq!(
918 &buf[..],
919 &[0x6E, 4],
920 "SMALLMONEY = MONEYN type_id with max_length 4"
921 );
922 }
923
924 #[test]
925 fn test_tvp_datetime_type_info_encoding() {
926 let mut buf = BytesMut::new();
927 TvpWireType::DateTime.encode_type_info(&mut buf);
928 assert_eq!(
929 &buf[..],
930 &[0x6F, 8],
931 "DATETIME = DATETIMEN type_id with max_length 8"
932 );
933 }
934
935 #[test]
936 fn test_tvp_smalldatetime_type_info_encoding() {
937 let mut buf = BytesMut::new();
938 TvpWireType::SmallDateTime.encode_type_info(&mut buf);
939 assert_eq!(
940 &buf[..],
941 &[0x6F, 4],
942 "SMALLDATETIME = DATETIMEN type_id with max_length 4"
943 );
944 }
945
946 #[test]
947 fn test_tvp_null_for_money_is_length_zero() {
948 let mut buf = BytesMut::new();
949 encode_tvp_null(&TvpWireType::Money, &mut buf);
950 assert_eq!(&buf[..], &[0], "MONEYN NULL is a single length-zero byte");
951
952 let mut buf = BytesMut::new();
953 encode_tvp_null(&TvpWireType::SmallDateTime, &mut buf);
954 assert_eq!(
955 &buf[..],
956 &[0],
957 "DATETIMEN NULL is a single length-zero byte"
958 );
959 }
960
961 #[test]
967 #[cfg(feature = "encoding")]
968 fn test_tvp_varchar_with_collation_declares_and_encodes() {
969 let chinese = crate::token::Collation {
970 lcid: 0x0804, sort_id: 0,
972 };
973
974 let mut buf = BytesMut::new();
976 TvpWireType::VarChar { max_length: 100 }
977 .encode_type_info_with_collation(&mut buf, Some(&chinese));
978 assert_eq!(buf[0], 0xA7, "BIGVARCHARTYPE");
979 assert_eq!(&buf[1..3], &100u16.to_le_bytes());
980 assert_eq!(&buf[3..8], &chinese.to_bytes());
981
982 let mut buf = BytesMut::new();
984 TvpWireType::VarChar { max_length: 100 }.encode_type_info(&mut buf);
985 assert_eq!(&buf[3..8], &DEFAULT_COLLATION);
986
987 let mut buf = BytesMut::new();
989 encode_tvp_varchar_with_collation("你好", 100, Some(&chinese), &mut buf);
990 let (expected, _, _) = encoding_rs::GB18030.encode("你好");
991 assert_eq!(&buf[..2], &(expected.len() as u16).to_le_bytes());
992 assert_eq!(&buf[2..], &expected[..]);
993 }
994}