1#![allow(clippy::expect_used)]
8
9use bytes::{BufMut, BytesMut};
10
11use crate::error::TypeError;
12use crate::value::SqlValue;
13
14pub trait TdsEncode {
16 fn encode(&self, buf: &mut BytesMut) -> Result<(), TypeError>;
18
19 fn type_id(&self) -> u8;
21}
22
23impl TdsEncode for SqlValue {
24 fn encode(&self, buf: &mut BytesMut) -> Result<(), TypeError> {
25 match self {
26 SqlValue::Null => {
27 Ok(())
30 }
31 SqlValue::Bool(v) => {
32 buf.put_u8(if *v { 1 } else { 0 });
33 Ok(())
34 }
35 SqlValue::TinyInt(v) => {
36 buf.put_u8(*v);
37 Ok(())
38 }
39 SqlValue::SmallInt(v) => {
40 buf.put_i16_le(*v);
41 Ok(())
42 }
43 SqlValue::Int(v) => {
44 buf.put_i32_le(*v);
45 Ok(())
46 }
47 SqlValue::BigInt(v) => {
48 buf.put_i64_le(*v);
49 Ok(())
50 }
51 SqlValue::Float(v) => {
52 buf.put_f32_le(*v);
53 Ok(())
54 }
55 SqlValue::Double(v) => {
56 buf.put_f64_le(*v);
57 Ok(())
58 }
59 SqlValue::String(s) => {
60 encode_utf16_string(s, buf);
62 Ok(())
63 }
64 SqlValue::Binary(b) => {
65 if b.len() > u16::MAX as usize {
67 return Err(TypeError::BufferTooSmall {
68 needed: b.len(),
69 available: u16::MAX as usize,
70 });
71 }
72 buf.put_u16_le(b.len() as u16);
73 buf.put_slice(b);
74 Ok(())
75 }
76 #[cfg(feature = "decimal")]
77 SqlValue::Decimal(d) => {
78 encode_decimal(*d, buf);
79 Ok(())
80 }
81 #[cfg(feature = "decimal")]
82 SqlValue::Money(d) => encode_money(*d, buf),
83 #[cfg(feature = "decimal")]
84 SqlValue::SmallMoney(d) => encode_smallmoney(*d, buf),
85 #[cfg(feature = "uuid")]
86 SqlValue::Uuid(u) => {
87 encode_uuid(*u, buf);
88 Ok(())
89 }
90 #[cfg(feature = "chrono")]
91 SqlValue::Date(d) => encode_date(*d, buf),
92 #[cfg(feature = "chrono")]
93 SqlValue::Time(t) => {
94 encode_time(*t, buf);
95 Ok(())
96 }
97 #[cfg(feature = "chrono")]
98 SqlValue::DateTime(dt) => encode_datetime2(*dt, buf),
99 #[cfg(feature = "chrono")]
100 SqlValue::SmallDateTime(dt) => encode_smalldatetime(*dt, buf),
101 #[cfg(feature = "chrono")]
102 SqlValue::DateTimeOffset(dto) => encode_datetimeoffset(*dto, buf),
103 #[cfg(feature = "json")]
104 SqlValue::Json(j) => {
105 let s = j.to_string();
107 encode_utf16_string(&s, buf);
108 Ok(())
109 }
110 SqlValue::Xml(x) => {
111 encode_utf16_string(x, buf);
113 Ok(())
114 }
115 SqlValue::Tvp(_) => {
116 Err(TypeError::UnsupportedConversion {
121 from: "TvpData".to_string(),
122 to: "raw bytes (use RPC parameter encoding)",
123 })
124 }
125 }
126 }
127
128 fn type_id(&self) -> u8 {
129 match self {
130 SqlValue::Null => 0x1F, SqlValue::Bool(_) => 0x32, SqlValue::TinyInt(_) => 0x30, SqlValue::SmallInt(_) => 0x34, SqlValue::Int(_) => 0x38, SqlValue::BigInt(_) => 0x7F, SqlValue::Float(_) => 0x3B, SqlValue::Double(_) => 0x3E, SqlValue::String(_) => 0xE7, SqlValue::Binary(_) => 0xA5, #[cfg(feature = "decimal")]
141 SqlValue::Decimal(_) => 0x6C, #[cfg(feature = "decimal")]
143 SqlValue::Money(_) => 0x6E, #[cfg(feature = "decimal")]
145 SqlValue::SmallMoney(_) => 0x6E, #[cfg(feature = "uuid")]
147 SqlValue::Uuid(_) => 0x24, #[cfg(feature = "chrono")]
149 SqlValue::Date(_) => 0x28, #[cfg(feature = "chrono")]
151 SqlValue::Time(_) => 0x29, #[cfg(feature = "chrono")]
153 SqlValue::DateTime(_) => 0x2A, #[cfg(feature = "chrono")]
155 SqlValue::SmallDateTime(_) => 0x6F, #[cfg(feature = "chrono")]
157 SqlValue::DateTimeOffset(_) => 0x2B, #[cfg(feature = "json")]
159 SqlValue::Json(_) => 0xE7, SqlValue::Xml(_) => 0xF1, SqlValue::Tvp(_) => 0xF3, }
163 }
164}
165
166pub fn encode_utf16_string(s: &str, buf: &mut BytesMut) {
168 let utf16: Vec<u16> = s.encode_utf16().collect();
169 let byte_len = utf16.len() * 2;
170
171 buf.put_u16_le(byte_len as u16);
173
174 for code_unit in utf16 {
176 buf.put_u16_le(code_unit);
177 }
178}
179
180pub(crate) mod sealed {
185 #[allow(unused_imports)]
189 use super::*;
190
191 #[cfg(feature = "uuid")]
199 pub fn encode_uuid(uuid: uuid::Uuid, buf: &mut BytesMut) {
200 let bytes = uuid.as_bytes();
201
202 buf.put_u8(bytes[3]);
204 buf.put_u8(bytes[2]);
205 buf.put_u8(bytes[1]);
206 buf.put_u8(bytes[0]);
207
208 buf.put_u8(bytes[5]);
210 buf.put_u8(bytes[4]);
211
212 buf.put_u8(bytes[7]);
214 buf.put_u8(bytes[6]);
215
216 buf.put_slice(&bytes[8..16]);
218 }
219
220 #[cfg(feature = "decimal")]
226 pub fn encode_decimal(decimal: rust_decimal::Decimal, buf: &mut BytesMut) {
227 let sign = if decimal.is_sign_negative() { 0u8 } else { 1u8 };
228 buf.put_u8(sign);
229
230 let mantissa = decimal.mantissa().unsigned_abs();
232 buf.put_u128_le(mantissa);
233 }
234
235 #[cfg(feature = "decimal")]
240 fn decimal_to_money_cents(value: rust_decimal::Decimal) -> Result<i128, TypeError> {
241 let mantissa: i128 = value.mantissa();
242 let scale: u32 = value.scale();
243 if scale <= 4 {
244 let factor = 10_i128.pow(4 - scale);
245 mantissa.checked_mul(factor).ok_or(TypeError::OutOfRange {
246 target_type: "MONEY",
247 })
248 } else {
249 let factor = 10_i128.pow(scale - 4);
250 Ok(mantissa / factor)
251 }
252 }
253
254 #[cfg(feature = "decimal")]
260 pub fn decimal_to_money_cents_i64(value: rust_decimal::Decimal) -> Result<i64, TypeError> {
261 let cents_i128 = decimal_to_money_cents(value)?;
262 i64::try_from(cents_i128).map_err(|_| TypeError::OutOfRange {
263 target_type: "MONEY",
264 })
265 }
266
267 #[cfg(feature = "decimal")]
269 pub fn decimal_to_smallmoney_cents_i32(value: rust_decimal::Decimal) -> Result<i32, TypeError> {
270 let cents_i128 = decimal_to_money_cents(value)?;
271 i32::try_from(cents_i128).map_err(|_| TypeError::OutOfRange {
272 target_type: "SMALLMONEY",
273 })
274 }
275
276 #[cfg(feature = "decimal")]
280 pub fn encode_money(value: rust_decimal::Decimal, buf: &mut BytesMut) -> Result<(), TypeError> {
281 let cents = decimal_to_money_cents_i64(value)?;
282 let high = (cents >> 32) as i32;
283 let low = (cents & 0xFFFF_FFFF) as u32;
284 buf.put_i32_le(high);
285 buf.put_u32_le(low);
286 Ok(())
287 }
288
289 #[cfg(feature = "decimal")]
292 pub fn encode_smallmoney(
293 value: rust_decimal::Decimal,
294 buf: &mut BytesMut,
295 ) -> Result<(), TypeError> {
296 let cents = decimal_to_smallmoney_cents_i32(value)?;
297 buf.put_i32_le(cents);
298 Ok(())
299 }
300
301 #[cfg(feature = "chrono")]
307 pub fn datetime_to_legacy_days_ticks(dt: chrono::NaiveDateTime) -> (i32, u32) {
308 use chrono::Timelike;
309 let epoch = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("epoch 1900-01-01 is valid");
310 let days = (dt.date() - epoch).num_days() as i32;
311
312 let since_midnight = dt.time().num_seconds_from_midnight() as u64 * 1000
313 + u64::from(dt.time().nanosecond()) / 1_000_000;
314 let ticks = ((since_midnight * 3 + 5) / 10) as u32;
316 (days, ticks)
317 }
318
319 #[cfg(feature = "chrono")]
322 pub fn encode_datetime_legacy(dt: chrono::NaiveDateTime, buf: &mut BytesMut) {
323 let (days, ticks) = datetime_to_legacy_days_ticks(dt);
324 buf.put_i32_le(days);
325 buf.put_u32_le(ticks);
326 }
327
328 #[cfg(feature = "chrono")]
337 pub fn datetime_to_smalldatetime_days_minutes(
338 dt: chrono::NaiveDateTime,
339 ) -> Result<(u16, u16), TypeError> {
340 use chrono::Timelike;
341 let epoch = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("epoch 1900-01-01 is valid");
342
343 let total_seconds = dt.time().hour() * 3600 + dt.time().minute() * 60 + dt.time().second();
344 let minutes_raw = (total_seconds + 30) / 60;
345 let (day_carry, minutes) = if minutes_raw >= 1440 {
351 (1i64, 0u16)
352 } else {
353 (0i64, minutes_raw as u16)
354 };
355
356 let days_i64 = (dt.date() - epoch).num_days() + day_carry;
357 let days: u16 = u16::try_from(days_i64).map_err(|_| {
358 TypeError::InvalidDateTime(format!(
359 "SMALLDATETIME year must be 1900-2079, got date with {days_i64} days since 1900-01-01"
360 ))
361 })?;
362
363 Ok((days, minutes))
364 }
365
366 #[cfg(feature = "chrono")]
375 pub fn encode_smalldatetime(
376 dt: chrono::NaiveDateTime,
377 buf: &mut BytesMut,
378 ) -> Result<(), TypeError> {
379 let (days, minutes) = datetime_to_smalldatetime_days_minutes(dt)?;
380 buf.put_u16_le(days);
381 buf.put_u16_le(minutes);
382 Ok(())
383 }
384
385 #[cfg(feature = "chrono")]
395 pub fn encode_date(date: chrono::NaiveDate, buf: &mut BytesMut) -> Result<(), TypeError> {
396 const MAX_DAYS: i64 = 3_652_058;
398
399 let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
400 let days = date.signed_duration_since(base).num_days();
401 if !(0..=MAX_DAYS).contains(&days) {
402 return Err(TypeError::InvalidDateTime(format!(
403 "DATE must be between 0001-01-01 and 9999-12-31, got {date}"
404 )));
405 }
406 let days = days as u32;
407
408 buf.put_u8((days & 0xFF) as u8);
410 buf.put_u8(((days >> 8) & 0xFF) as u8);
411 buf.put_u8(((days >> 16) & 0xFF) as u8);
412 Ok(())
413 }
414
415 #[cfg(feature = "chrono")]
419 pub fn encode_time(time: chrono::NaiveTime, buf: &mut BytesMut) {
420 use chrono::Timelike;
421
422 let nanos =
425 time.num_seconds_from_midnight() as u64 * 1_000_000_000 + time.nanosecond() as u64;
426 let intervals = nanos / 100;
427
428 buf.put_u8((intervals & 0xFF) as u8);
430 buf.put_u8(((intervals >> 8) & 0xFF) as u8);
431 buf.put_u8(((intervals >> 16) & 0xFF) as u8);
432 buf.put_u8(((intervals >> 24) & 0xFF) as u8);
433 buf.put_u8(((intervals >> 32) & 0xFF) as u8);
434 }
435
436 #[cfg(feature = "chrono")]
445 pub fn encode_datetime2(
446 datetime: chrono::NaiveDateTime,
447 buf: &mut BytesMut,
448 ) -> Result<(), TypeError> {
449 encode_time(datetime.time(), buf);
450 encode_date(datetime.date(), buf)
451 }
452
453 #[cfg(feature = "chrono")]
459 pub fn encode_datetimeoffset(
460 datetime: chrono::DateTime<chrono::FixedOffset>,
461 buf: &mut BytesMut,
462 ) -> Result<(), TypeError> {
463 use chrono::Offset;
464
465 let utc = datetime.naive_utc();
467 encode_time(utc.time(), buf);
468 encode_date(utc.date(), buf)?;
469
470 let offset_seconds = datetime.offset().fix().local_minus_utc();
472 let offset_minutes = (offset_seconds / 60) as i16;
473 buf.put_i16_le(offset_minutes);
474 Ok(())
475 }
476}
477
478#[allow(unused_imports)]
482pub(crate) use sealed::*;
483
484#[cfg(test)]
485#[allow(clippy::unwrap_used)]
486mod tests {
487 use super::*;
488
489 #[cfg(feature = "json")]
492 #[test]
493 fn test_json_encodes_as_nvarchar() {
494 let value = serde_json::json!({"name": "Ada", "id": 42});
495
496 let mut got = BytesMut::new();
497 SqlValue::Json(value.clone()).encode(&mut got).unwrap();
498
499 let mut want = BytesMut::new();
500 encode_utf16_string(&value.to_string(), &mut want);
501 assert_eq!(got, want);
502 }
503
504 #[cfg(feature = "chrono")]
510 #[test]
511 fn test_datetimeoffset_encodes_utc_instant() {
512 use chrono::TimeZone;
513
514 let offset = chrono::FixedOffset::east_opt(2 * 3600).unwrap();
515 let dto = offset.with_ymd_and_hms(2024, 3, 15, 12, 0, 0).unwrap();
516
517 let mut buf = BytesMut::new();
518 encode_datetimeoffset(dto, &mut buf).unwrap();
519 assert_eq!(buf.len(), 10); let mut intervals: u64 = 0;
523 for i in 0..5 {
524 intervals |= u64::from(buf[i]) << (8 * i);
525 }
526 assert_eq!(intervals, 10 * 3600 * 10_000_000, "time must be 10:00 UTC");
527
528 let days = u32::from(buf[5]) | (u32::from(buf[6]) << 8) | (u32::from(buf[7]) << 16);
530 let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).unwrap();
531 let expected_days =
532 (chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap() - base).num_days() as u32;
533 assert_eq!(days, expected_days);
534
535 assert_eq!(i16::from_le_bytes([buf[8], buf[9]]), 120);
537 }
538
539 #[test]
540 fn test_encode_int() {
541 let mut buf = BytesMut::new();
542 SqlValue::Int(42).encode(&mut buf).unwrap();
543 assert_eq!(&buf[..], &[42, 0, 0, 0]);
544 }
545
546 #[test]
547 fn test_encode_bigint() {
548 let mut buf = BytesMut::new();
549 SqlValue::BigInt(0x0102030405060708)
550 .encode(&mut buf)
551 .unwrap();
552 assert_eq!(&buf[..], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
553 }
554
555 #[test]
556 fn test_encode_utf16_string() {
557 let mut buf = BytesMut::new();
558 encode_utf16_string("AB", &mut buf);
559 assert_eq!(&buf[..], &[4, 0, 0x41, 0, 0x42, 0]);
561 }
562
563 #[cfg(feature = "uuid")]
564 #[test]
565 fn test_encode_uuid() {
566 let mut buf = BytesMut::new();
567 let uuid = uuid::Uuid::parse_str("12345678-1234-5678-1234-567812345678").unwrap();
568 encode_uuid(uuid, &mut buf);
569 assert_eq!(
571 &buf[..],
572 &[
573 0x78, 0x56, 0x34, 0x12, 0x34, 0x12, 0x78, 0x56, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78 ]
578 );
579 }
580
581 #[cfg(feature = "chrono")]
582 #[test]
586 fn test_encode_date_range_enforced() {
587 let mut buf = BytesMut::new();
588
589 let min = chrono::NaiveDate::from_ymd_opt(1, 1, 1).unwrap();
591 encode_date(min, &mut buf).unwrap();
592 assert_eq!(&buf[buf.len() - 3..], &[0, 0, 0]);
593 let max = chrono::NaiveDate::from_ymd_opt(9999, 12, 31).unwrap();
594 encode_date(max, &mut buf).unwrap();
595 assert_eq!(&buf[buf.len() - 3..], &[0xDA, 0xB9, 0x37]);
597
598 let before = chrono::NaiveDate::from_ymd_opt(0, 12, 31).unwrap();
600 assert!(matches!(
601 encode_date(before, &mut buf),
602 Err(TypeError::InvalidDateTime(_))
603 ));
604 let after = chrono::NaiveDate::from_ymd_opt(10000, 1, 1).unwrap();
605 assert!(matches!(
606 encode_date(after, &mut buf),
607 Err(TypeError::InvalidDateTime(_))
608 ));
609
610 let dt = before.and_hms_opt(12, 0, 0).unwrap();
612 assert!(encode_datetime2(dt, &mut buf).is_err());
613 }
614
615 #[test]
616 fn test_encode_date() {
617 let mut buf = BytesMut::new();
618 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
619 encode_date(date, &mut buf).unwrap();
620 assert_eq!(buf.len(), 3);
622 }
623}