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) => {
92 encode_date(*d, buf);
93 Ok(())
94 }
95 #[cfg(feature = "chrono")]
96 SqlValue::Time(t) => {
97 encode_time(*t, buf);
98 Ok(())
99 }
100 #[cfg(feature = "chrono")]
101 SqlValue::DateTime(dt) => {
102 encode_datetime2(*dt, buf);
103 Ok(())
104 }
105 #[cfg(feature = "chrono")]
106 SqlValue::SmallDateTime(dt) => encode_smalldatetime(*dt, buf),
107 #[cfg(feature = "chrono")]
108 SqlValue::DateTimeOffset(dto) => {
109 encode_datetimeoffset(*dto, buf);
110 Ok(())
111 }
112 #[cfg(feature = "json")]
113 SqlValue::Json(j) => {
114 let s = j.to_string();
116 encode_utf16_string(&s, buf);
117 Ok(())
118 }
119 SqlValue::Xml(x) => {
120 encode_utf16_string(x, buf);
122 Ok(())
123 }
124 SqlValue::Tvp(_) => {
125 Err(TypeError::UnsupportedConversion {
130 from: "TvpData".to_string(),
131 to: "raw bytes (use RPC parameter encoding)",
132 })
133 }
134 }
135 }
136
137 fn type_id(&self) -> u8 {
138 match self {
139 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")]
150 SqlValue::Decimal(_) => 0x6C, #[cfg(feature = "decimal")]
152 SqlValue::Money(_) => 0x6E, #[cfg(feature = "decimal")]
154 SqlValue::SmallMoney(_) => 0x6E, #[cfg(feature = "uuid")]
156 SqlValue::Uuid(_) => 0x24, #[cfg(feature = "chrono")]
158 SqlValue::Date(_) => 0x28, #[cfg(feature = "chrono")]
160 SqlValue::Time(_) => 0x29, #[cfg(feature = "chrono")]
162 SqlValue::DateTime(_) => 0x2A, #[cfg(feature = "chrono")]
164 SqlValue::SmallDateTime(_) => 0x6F, #[cfg(feature = "chrono")]
166 SqlValue::DateTimeOffset(_) => 0x2B, #[cfg(feature = "json")]
168 SqlValue::Json(_) => 0xE7, SqlValue::Xml(_) => 0xF1, SqlValue::Tvp(_) => 0xF3, }
172 }
173}
174
175pub fn encode_utf16_string(s: &str, buf: &mut BytesMut) {
177 let utf16: Vec<u16> = s.encode_utf16().collect();
178 let byte_len = utf16.len() * 2;
179
180 buf.put_u16_le(byte_len as u16);
182
183 for code_unit in utf16 {
185 buf.put_u16_le(code_unit);
186 }
187}
188
189pub fn encode_utf16_string_no_len(s: &str, buf: &mut BytesMut) {
191 for code_unit in s.encode_utf16() {
192 buf.put_u16_le(code_unit);
193 }
194}
195
196#[cfg(feature = "uuid")]
204pub fn encode_uuid(uuid: uuid::Uuid, buf: &mut BytesMut) {
205 let bytes = uuid.as_bytes();
206
207 buf.put_u8(bytes[3]);
209 buf.put_u8(bytes[2]);
210 buf.put_u8(bytes[1]);
211 buf.put_u8(bytes[0]);
212
213 buf.put_u8(bytes[5]);
215 buf.put_u8(bytes[4]);
216
217 buf.put_u8(bytes[7]);
219 buf.put_u8(bytes[6]);
220
221 buf.put_slice(&bytes[8..16]);
223}
224
225#[cfg(feature = "decimal")]
231pub fn encode_decimal(decimal: rust_decimal::Decimal, buf: &mut BytesMut) {
232 let sign = if decimal.is_sign_negative() { 0u8 } else { 1u8 };
233 buf.put_u8(sign);
234
235 let mantissa = decimal.mantissa().unsigned_abs();
237 buf.put_u128_le(mantissa);
238}
239
240#[cfg(feature = "decimal")]
245fn decimal_to_money_cents(value: rust_decimal::Decimal) -> Result<i128, TypeError> {
246 let mantissa: i128 = value.mantissa();
247 let scale: u32 = value.scale();
248 if scale <= 4 {
249 let factor = 10_i128.pow(4 - scale);
250 mantissa.checked_mul(factor).ok_or(TypeError::OutOfRange {
251 target_type: "MONEY",
252 })
253 } else {
254 let factor = 10_i128.pow(scale - 4);
255 Ok(mantissa / factor)
256 }
257}
258
259#[cfg(feature = "decimal")]
265pub fn decimal_to_money_cents_i64(value: rust_decimal::Decimal) -> Result<i64, TypeError> {
266 let cents_i128 = decimal_to_money_cents(value)?;
267 i64::try_from(cents_i128).map_err(|_| TypeError::OutOfRange {
268 target_type: "MONEY",
269 })
270}
271
272#[cfg(feature = "decimal")]
274pub fn decimal_to_smallmoney_cents_i32(value: rust_decimal::Decimal) -> Result<i32, TypeError> {
275 let cents_i128 = decimal_to_money_cents(value)?;
276 i32::try_from(cents_i128).map_err(|_| TypeError::OutOfRange {
277 target_type: "SMALLMONEY",
278 })
279}
280
281#[cfg(feature = "decimal")]
285pub fn encode_money(value: rust_decimal::Decimal, buf: &mut BytesMut) -> Result<(), TypeError> {
286 let cents = decimal_to_money_cents_i64(value)?;
287 let high = (cents >> 32) as i32;
288 let low = (cents & 0xFFFF_FFFF) as u32;
289 buf.put_i32_le(high);
290 buf.put_u32_le(low);
291 Ok(())
292}
293
294#[cfg(feature = "decimal")]
297pub fn encode_smallmoney(
298 value: rust_decimal::Decimal,
299 buf: &mut BytesMut,
300) -> Result<(), TypeError> {
301 let cents = decimal_to_smallmoney_cents_i32(value)?;
302 buf.put_i32_le(cents);
303 Ok(())
304}
305
306#[cfg(feature = "chrono")]
312pub fn datetime_to_legacy_days_ticks(dt: chrono::NaiveDateTime) -> (i32, u32) {
313 use chrono::Timelike;
314 let epoch = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("epoch 1900-01-01 is valid");
315 let days = (dt.date() - epoch).num_days() as i32;
316
317 let since_midnight = dt.time().num_seconds_from_midnight() as u64 * 1000
318 + u64::from(dt.time().nanosecond()) / 1_000_000;
319 let ticks = ((since_midnight * 3 + 5) / 10) as u32;
321 (days, ticks)
322}
323
324#[cfg(feature = "chrono")]
327pub fn encode_datetime_legacy(dt: chrono::NaiveDateTime, buf: &mut BytesMut) {
328 let (days, ticks) = datetime_to_legacy_days_ticks(dt);
329 buf.put_i32_le(days);
330 buf.put_u32_le(ticks);
331}
332
333#[cfg(feature = "chrono")]
342pub fn datetime_to_smalldatetime_days_minutes(
343 dt: chrono::NaiveDateTime,
344) -> Result<(u16, u16), TypeError> {
345 use chrono::Timelike;
346 let epoch = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("epoch 1900-01-01 is valid");
347
348 let total_seconds = dt.time().hour() * 3600 + dt.time().minute() * 60 + dt.time().second();
349 let minutes_raw = (total_seconds + 30) / 60;
350 let (day_carry, minutes) = if minutes_raw >= 1440 {
356 (1i64, 0u16)
357 } else {
358 (0i64, minutes_raw as u16)
359 };
360
361 let days_i64 = (dt.date() - epoch).num_days() + day_carry;
362 let days: u16 = u16::try_from(days_i64).map_err(|_| {
363 TypeError::InvalidDateTime(format!(
364 "SMALLDATETIME year must be 1900-2079, got date with {days_i64} days since 1900-01-01"
365 ))
366 })?;
367
368 Ok((days, minutes))
369}
370
371#[cfg(feature = "chrono")]
378pub fn encode_smalldatetime(
379 dt: chrono::NaiveDateTime,
380 buf: &mut BytesMut,
381) -> Result<(), TypeError> {
382 let (days, minutes) = datetime_to_smalldatetime_days_minutes(dt)?;
383 buf.put_u16_le(days);
384 buf.put_u16_le(minutes);
385 Ok(())
386}
387
388#[cfg(feature = "chrono")]
392pub fn encode_date(date: chrono::NaiveDate, buf: &mut BytesMut) {
393 let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
395 let days = date.signed_duration_since(base).num_days() as u32;
396
397 buf.put_u8((days & 0xFF) as u8);
399 buf.put_u8(((days >> 8) & 0xFF) as u8);
400 buf.put_u8(((days >> 16) & 0xFF) as u8);
401}
402
403#[cfg(feature = "chrono")]
407pub fn encode_time(time: chrono::NaiveTime, buf: &mut BytesMut) {
408 use chrono::Timelike;
409
410 let nanos = time.num_seconds_from_midnight() as u64 * 1_000_000_000 + time.nanosecond() as u64;
413 let intervals = nanos / 100;
414
415 buf.put_u8((intervals & 0xFF) as u8);
417 buf.put_u8(((intervals >> 8) & 0xFF) as u8);
418 buf.put_u8(((intervals >> 16) & 0xFF) as u8);
419 buf.put_u8(((intervals >> 24) & 0xFF) as u8);
420 buf.put_u8(((intervals >> 32) & 0xFF) as u8);
421}
422
423#[cfg(feature = "chrono")]
427pub fn encode_datetime2(datetime: chrono::NaiveDateTime, buf: &mut BytesMut) {
428 encode_time(datetime.time(), buf);
429 encode_date(datetime.date(), buf);
430}
431
432#[cfg(feature = "chrono")]
436pub fn encode_datetimeoffset(datetime: chrono::DateTime<chrono::FixedOffset>, buf: &mut BytesMut) {
437 use chrono::Offset;
438
439 encode_time(datetime.time(), buf);
441 encode_date(datetime.date_naive(), buf);
442
443 let offset_seconds = datetime.offset().fix().local_minus_utc();
445 let offset_minutes = (offset_seconds / 60) as i16;
446 buf.put_i16_le(offset_minutes);
447}
448
449#[cfg(test)]
450#[allow(clippy::unwrap_used)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn test_encode_int() {
456 let mut buf = BytesMut::new();
457 SqlValue::Int(42).encode(&mut buf).unwrap();
458 assert_eq!(&buf[..], &[42, 0, 0, 0]);
459 }
460
461 #[test]
462 fn test_encode_bigint() {
463 let mut buf = BytesMut::new();
464 SqlValue::BigInt(0x0102030405060708)
465 .encode(&mut buf)
466 .unwrap();
467 assert_eq!(&buf[..], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
468 }
469
470 #[test]
471 fn test_encode_utf16_string() {
472 let mut buf = BytesMut::new();
473 encode_utf16_string("AB", &mut buf);
474 assert_eq!(&buf[..], &[4, 0, 0x41, 0, 0x42, 0]);
476 }
477
478 #[cfg(feature = "uuid")]
479 #[test]
480 fn test_encode_uuid() {
481 let mut buf = BytesMut::new();
482 let uuid = uuid::Uuid::parse_str("12345678-1234-5678-1234-567812345678").unwrap();
483 encode_uuid(uuid, &mut buf);
484 assert_eq!(
486 &buf[..],
487 &[
488 0x78, 0x56, 0x34, 0x12, 0x34, 0x12, 0x78, 0x56, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78 ]
493 );
494 }
495
496 #[cfg(feature = "chrono")]
497 #[test]
498 fn test_encode_date() {
499 let mut buf = BytesMut::new();
500 let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
501 encode_date(date, &mut buf);
502 assert_eq!(buf.len(), 3);
504 }
505}