1#![allow(clippy::cast_possible_truncation)]
15
16use sqlmodel_core::Value;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[repr(u8)]
23pub enum FieldType {
24 Decimal = 0x00,
26 Tiny = 0x01,
28 Short = 0x02,
30 Long = 0x03,
32 Float = 0x04,
34 Double = 0x05,
36 Null = 0x06,
38 Timestamp = 0x07,
40 LongLong = 0x08,
42 Int24 = 0x09,
44 Date = 0x0A,
46 Time = 0x0B,
48 DateTime = 0x0C,
50 Year = 0x0D,
52 NewDate = 0x0E,
54 VarChar = 0x0F,
56 Bit = 0x10,
58 Timestamp2 = 0x11,
60 DateTime2 = 0x12,
62 Time2 = 0x13,
64 Json = 0xF5,
66 NewDecimal = 0xF6,
68 Enum = 0xF7,
70 Set = 0xF8,
72 TinyBlob = 0xF9,
74 MediumBlob = 0xFA,
76 LongBlob = 0xFB,
78 Blob = 0xFC,
80 VarString = 0xFD,
82 String = 0xFE,
84 Geometry = 0xFF,
86}
87
88impl FieldType {
89 #[must_use]
91 pub fn from_u8(value: u8) -> Self {
92 match value {
93 0x00 => FieldType::Decimal,
94 0x01 => FieldType::Tiny,
95 0x02 => FieldType::Short,
96 0x03 => FieldType::Long,
97 0x04 => FieldType::Float,
98 0x05 => FieldType::Double,
99 0x06 => FieldType::Null,
100 0x07 => FieldType::Timestamp,
101 0x08 => FieldType::LongLong,
102 0x09 => FieldType::Int24,
103 0x0A => FieldType::Date,
104 0x0B => FieldType::Time,
105 0x0C => FieldType::DateTime,
106 0x0D => FieldType::Year,
107 0x0E => FieldType::NewDate,
108 0x0F => FieldType::VarChar,
109 0x10 => FieldType::Bit,
110 0x11 => FieldType::Timestamp2,
111 0x12 => FieldType::DateTime2,
112 0x13 => FieldType::Time2,
113 0xF5 => FieldType::Json,
114 0xF6 => FieldType::NewDecimal,
115 0xF7 => FieldType::Enum,
116 0xF8 => FieldType::Set,
117 0xF9 => FieldType::TinyBlob,
118 0xFA => FieldType::MediumBlob,
119 0xFB => FieldType::LongBlob,
120 0xFC => FieldType::Blob,
121 0xFD => FieldType::VarString,
122 0xFE => FieldType::String,
123 0xFF => FieldType::Geometry,
124 _ => FieldType::String, }
126 }
127
128 #[must_use]
130 pub const fn is_integer(self) -> bool {
131 matches!(
132 self,
133 FieldType::Tiny
134 | FieldType::Short
135 | FieldType::Long
136 | FieldType::LongLong
137 | FieldType::Int24
138 | FieldType::Year
139 )
140 }
141
142 #[must_use]
144 pub const fn is_float(self) -> bool {
145 matches!(self, FieldType::Float | FieldType::Double)
146 }
147
148 #[must_use]
150 pub const fn is_decimal(self) -> bool {
151 matches!(self, FieldType::Decimal | FieldType::NewDecimal)
152 }
153
154 #[must_use]
156 pub const fn is_string(self) -> bool {
157 matches!(
158 self,
159 FieldType::VarChar
160 | FieldType::VarString
161 | FieldType::String
162 | FieldType::Enum
163 | FieldType::Set
164 )
165 }
166
167 #[must_use]
169 pub const fn is_blob(self) -> bool {
170 matches!(
171 self,
172 FieldType::TinyBlob
173 | FieldType::MediumBlob
174 | FieldType::LongBlob
175 | FieldType::Blob
176 | FieldType::Geometry
177 )
178 }
179
180 #[must_use]
182 pub const fn is_temporal(self) -> bool {
183 matches!(
184 self,
185 FieldType::Date
186 | FieldType::Time
187 | FieldType::DateTime
188 | FieldType::Timestamp
189 | FieldType::NewDate
190 | FieldType::Timestamp2
191 | FieldType::DateTime2
192 | FieldType::Time2
193 )
194 }
195
196 #[must_use]
198 pub const fn name(self) -> &'static str {
199 match self {
200 FieldType::Decimal => "DECIMAL",
201 FieldType::Tiny => "TINYINT",
202 FieldType::Short => "SMALLINT",
203 FieldType::Long => "INT",
204 FieldType::Float => "FLOAT",
205 FieldType::Double => "DOUBLE",
206 FieldType::Null => "NULL",
207 FieldType::Timestamp => "TIMESTAMP",
208 FieldType::LongLong => "BIGINT",
209 FieldType::Int24 => "MEDIUMINT",
210 FieldType::Date => "DATE",
211 FieldType::Time => "TIME",
212 FieldType::DateTime => "DATETIME",
213 FieldType::Year => "YEAR",
214 FieldType::NewDate => "DATE",
215 FieldType::VarChar => "VARCHAR",
216 FieldType::Bit => "BIT",
217 FieldType::Timestamp2 => "TIMESTAMP",
218 FieldType::DateTime2 => "DATETIME",
219 FieldType::Time2 => "TIME",
220 FieldType::Json => "JSON",
221 FieldType::NewDecimal => "DECIMAL",
222 FieldType::Enum => "ENUM",
223 FieldType::Set => "SET",
224 FieldType::TinyBlob => "TINYBLOB",
225 FieldType::MediumBlob => "MEDIUMBLOB",
226 FieldType::LongBlob => "LONGBLOB",
227 FieldType::Blob => "BLOB",
228 FieldType::VarString => "VARCHAR",
229 FieldType::String => "CHAR",
230 FieldType::Geometry => "GEOMETRY",
231 }
232 }
233}
234
235#[allow(dead_code)]
237pub mod column_flags {
238 pub const NOT_NULL: u16 = 1;
239 pub const PRIMARY_KEY: u16 = 2;
240 pub const UNIQUE_KEY: u16 = 4;
241 pub const MULTIPLE_KEY: u16 = 8;
242 pub const BLOB: u16 = 16;
243 pub const UNSIGNED: u16 = 32;
244 pub const ZEROFILL: u16 = 64;
245 pub const BINARY: u16 = 128;
246 pub const ENUM: u16 = 256;
247 pub const AUTO_INCREMENT: u16 = 512;
248 pub const TIMESTAMP: u16 = 1024;
249 pub const SET: u16 = 2048;
250 pub const NO_DEFAULT_VALUE: u16 = 4096;
251 pub const ON_UPDATE_NOW: u16 = 8192;
252 pub const NUM: u16 = 32768;
253}
254
255#[derive(Debug, Clone)]
257pub struct ColumnDef {
258 pub catalog: String,
260 pub schema: String,
262 pub table: String,
264 pub org_table: String,
266 pub name: String,
268 pub org_name: String,
270 pub charset: u16,
272 pub column_length: u32,
274 pub column_type: FieldType,
276 pub flags: u16,
278 pub decimals: u8,
280}
281
282impl ColumnDef {
283 #[must_use]
285 pub const fn is_not_null(&self) -> bool {
286 self.flags & column_flags::NOT_NULL != 0
287 }
288
289 #[must_use]
291 pub const fn is_primary_key(&self) -> bool {
292 self.flags & column_flags::PRIMARY_KEY != 0
293 }
294
295 #[must_use]
297 pub const fn is_unsigned(&self) -> bool {
298 self.flags & column_flags::UNSIGNED != 0
299 }
300
301 #[must_use]
303 pub const fn is_auto_increment(&self) -> bool {
304 self.flags & column_flags::AUTO_INCREMENT != 0
305 }
306
307 #[must_use]
309 pub const fn is_binary(&self) -> bool {
310 self.flags & column_flags::BINARY != 0
311 }
312
313 #[must_use]
315 pub const fn is_blob(&self) -> bool {
316 self.flags & column_flags::BLOB != 0
317 }
318}
319
320pub fn decode_text_value(field_type: FieldType, data: &[u8], is_unsigned: bool) -> Value {
325 let text = String::from_utf8_lossy(data);
326
327 match field_type {
328 FieldType::Tiny => {
330 if is_unsigned {
331 text.parse::<u8>().map_or_else(
332 |_| Value::Text(text.into_owned()),
333 |v| Value::TinyInt(v as i8),
334 )
335 } else {
336 text.parse::<i8>()
337 .map_or_else(|_| Value::Text(text.into_owned()), Value::TinyInt)
338 }
339 }
340 FieldType::Short | FieldType::Year => {
342 if is_unsigned {
343 text.parse::<u16>().map_or_else(
344 |_| Value::Text(text.into_owned()),
345 |v| Value::SmallInt(v as i16),
346 )
347 } else {
348 text.parse::<i16>()
349 .map_or_else(|_| Value::Text(text.into_owned()), Value::SmallInt)
350 }
351 }
352 FieldType::Long | FieldType::Int24 => {
354 if is_unsigned {
355 text.parse::<u32>()
356 .map_or_else(|_| Value::Text(text.into_owned()), |v| Value::Int(v as i32))
357 } else {
358 text.parse::<i32>()
359 .map_or_else(|_| Value::Text(text.into_owned()), Value::Int)
360 }
361 }
362 FieldType::LongLong => {
364 if is_unsigned {
365 text.parse::<u64>().map_or_else(
366 |_| Value::Text(text.into_owned()),
367 |v| Value::BigInt(v as i64),
368 )
369 } else {
370 text.parse::<i64>()
371 .map_or_else(|_| Value::Text(text.into_owned()), Value::BigInt)
372 }
373 }
374
375 FieldType::Float => text
377 .parse::<f32>()
378 .map_or_else(|_| Value::Text(text.into_owned()), Value::Float),
379
380 FieldType::Double => text
382 .parse::<f64>()
383 .map_or_else(|_| Value::Text(text.into_owned()), Value::Double),
384
385 FieldType::Decimal | FieldType::NewDecimal => Value::Text(text.into_owned()),
387
388 FieldType::TinyBlob
390 | FieldType::MediumBlob
391 | FieldType::LongBlob
392 | FieldType::Blob
393 | FieldType::Geometry
394 | FieldType::Bit => Value::Bytes(data.to_vec()),
395
396 FieldType::Json => {
398 serde_json::from_str(&text).map_or_else(|_| Value::Text(text.into_owned()), Value::Json)
400 }
401
402 FieldType::Null => Value::Null,
404
405 FieldType::Date | FieldType::NewDate => decode_text_date(text.as_ref()),
407 FieldType::Time | FieldType::Time2 => decode_text_time(text.as_ref()),
408 FieldType::DateTime
409 | FieldType::Timestamp
410 | FieldType::DateTime2
411 | FieldType::Timestamp2 => decode_text_datetime_or_timestamp(text.as_ref()),
412
413 _ => Value::Text(text.into_owned()),
415 }
416}
417
418fn decode_text_date(original: &str) -> Value {
419 let trimmed = original.trim();
420 if trimmed.is_empty() {
421 return Value::Text(original.to_string());
422 }
423 if trimmed == "0000-00-00" {
425 return Value::Text("0000-00-00".to_string());
426 }
427
428 let bytes = trimmed.as_bytes();
429 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
430 return Value::Text(original.to_string());
431 }
432
433 let year_i32 = parse_4_digits(bytes, 0).and_then(|v| i32::try_from(v).ok());
434 let month_u32 = parse_2_digits(bytes, 5);
435 let day_u32 = parse_2_digits(bytes, 8);
436
437 match (year_i32, month_u32, day_u32) {
438 (Some(year), Some(month), Some(day)) => ymd_to_days_since_unix_epoch(year, month, day)
439 .map_or_else(|| Value::Text(original.to_string()), Value::Date),
440 _ => Value::Text(original.to_string()),
441 }
442}
443
444fn decode_text_time(original: &str) -> Value {
445 let trimmed = original.trim();
446 if trimmed.is_empty() {
447 return Value::Text(original.to_string());
448 }
449 if trimmed == "00:00:00" || trimmed == "-00:00:00" {
451 return Value::Text(trimmed.to_string());
452 }
453
454 let mut bytes = trimmed.as_bytes();
455 let mut negative = false;
456 if let Some((&first, rest)) = bytes.split_first() {
457 if first == b'-' {
458 negative = true;
459 bytes = rest;
460 }
461 }
462
463 let (days_part, time_part) = match bytes.iter().position(|&c| c == b' ') {
465 Some(sp) => (&bytes[..sp], &bytes[sp + 1..]),
466 None => (&[][..], bytes),
467 };
468
469 let days: i64 = if days_part.is_empty() {
470 0
471 } else {
472 let Ok(ds) = std::str::from_utf8(days_part) else {
473 return Value::Text(original.to_string());
474 };
475 let Ok(d) = ds.parse::<i64>() else {
476 return Value::Text(original.to_string());
477 };
478 d
479 };
480
481 let (hms, frac) = match time_part.iter().position(|&c| c == b'.') {
483 Some(dot) => (&time_part[..dot], Some(&time_part[dot + 1..])),
484 None => (time_part, None),
485 };
486
487 let mut it = hms.split(|&c| c == b':');
488 let Some(hh) = it.next() else {
489 return Value::Text(original.to_string());
490 };
491 let Some(mm) = it.next() else {
492 return Value::Text(original.to_string());
493 };
494 let Some(ss) = it.next() else {
495 return Value::Text(original.to_string());
496 };
497 if it.next().is_some() {
498 return Value::Text(original.to_string());
499 }
500
501 let Ok(hh_s) = std::str::from_utf8(hh) else {
502 return Value::Text(original.to_string());
503 };
504 let Ok(mm_s) = std::str::from_utf8(mm) else {
505 return Value::Text(original.to_string());
506 };
507 let Ok(ss_s) = std::str::from_utf8(ss) else {
508 return Value::Text(original.to_string());
509 };
510
511 let Ok(hours) = hh_s.parse::<i64>() else {
512 return Value::Text(original.to_string());
513 };
514 let Ok(minutes) = mm_s.parse::<i64>() else {
515 return Value::Text(original.to_string());
516 };
517 let Ok(seconds) = ss_s.parse::<i64>() else {
518 return Value::Text(original.to_string());
519 };
520
521 if !(0..=59).contains(&minutes) || !(0..=59).contains(&seconds) {
522 return Value::Text(original.to_string());
523 }
524
525 let micros: i64 = match frac {
526 None => 0,
527 Some(fr) => {
528 let mut us: i64 = 0;
530 let mut n = 0usize;
531 for &c in fr {
532 if n == 6 {
533 break;
534 }
535 if !c.is_ascii_digit() {
536 return Value::Text(original.to_string());
537 }
538 us = us.saturating_mul(10).saturating_add(i64::from(c - b'0'));
539 n += 1;
540 }
541 for _ in n..6 {
542 us = us.saturating_mul(10);
543 }
544 us
545 }
546 };
547
548 let total_seconds = days
549 .saturating_mul(24)
550 .saturating_add(hours)
551 .saturating_mul(3600)
552 .saturating_add(minutes.saturating_mul(60))
553 .saturating_add(seconds);
554 let total_micros = total_seconds
555 .saturating_mul(1_000_000)
556 .saturating_add(micros);
557
558 let signed = if negative {
559 -total_micros
560 } else {
561 total_micros
562 };
563 Value::Time(signed)
564}
565
566fn decode_text_datetime_or_timestamp(original: &str) -> Value {
567 let trimmed = original.trim();
568 if trimmed.is_empty() {
569 return Value::Text(original.to_string());
570 }
571 if trimmed.starts_with("0000-00-00") {
573 return Value::Text(trimmed.to_string());
574 }
575
576 let (date_part, rest) = if trimmed.len() >= 10 {
578 (&trimmed[..10], &trimmed[10..])
579 } else {
580 return Value::Text(original.to_string());
581 };
582 let date_b = date_part.as_bytes();
583 if date_b.len() != 10 || date_b[4] != b'-' || date_b[7] != b'-' {
584 return Value::Text(original.to_string());
585 }
586
587 let year = parse_4_digits(date_b, 0).and_then(|v| i32::try_from(v).ok());
588 let month = parse_2_digits(date_b, 5);
589 let day = parse_2_digits(date_b, 8);
590 let (Some(year), Some(month), Some(day)) = (year, month, day) else {
591 return Value::Text(original.to_string());
592 };
593
594 let Some(days) = ymd_to_days_since_unix_epoch(year, month, day) else {
595 return Value::Text(original.to_string());
596 };
597
598 let mut hour: u32 = 0;
600 let mut minute: u32 = 0;
601 let mut second: u32 = 0;
602 let mut micros: u32 = 0;
603
604 let rest = rest.trim_start();
605 if !rest.is_empty() {
606 let rest = rest.strip_prefix('T').unwrap_or(rest);
607 let rest = rest.trim_start();
608
609 let (hms_part, frac) = match rest.find('.') {
611 Some(dot) => (&rest[..dot], Some(&rest[dot + 1..])),
612 None => (rest, None),
613 };
614
615 if hms_part.len() < 8 {
616 return Value::Text(original.to_string());
617 }
618 let hms_b = hms_part.as_bytes();
619 if hms_b.len() != 8 || hms_b[2] != b':' || hms_b[5] != b':' {
620 return Value::Text(original.to_string());
621 }
622 let Some(hh) = parse_2_digits(hms_b, 0) else {
623 return Value::Text(original.to_string());
624 };
625 let Some(mm) = parse_2_digits(hms_b, 3) else {
626 return Value::Text(original.to_string());
627 };
628 let Some(ss) = parse_2_digits(hms_b, 6) else {
629 return Value::Text(original.to_string());
630 };
631 if hh > 23 || mm > 59 || ss > 59 {
632 return Value::Text(original.to_string());
633 }
634 hour = hh;
635 minute = mm;
636 second = ss;
637
638 if let Some(frac) = frac {
639 let frac = frac.trim();
640 let fb = frac.as_bytes();
641 let mut us: u32 = 0;
642 let mut n = 0usize;
643 for &c in fb {
644 if n == 6 {
645 break;
646 }
647 if !c.is_ascii_digit() {
648 return Value::Text(original.to_string());
649 }
650 us = us.saturating_mul(10).saturating_add(u32::from(c - b'0'));
651 n += 1;
652 }
653 for _ in n..6 {
654 us = us.saturating_mul(10);
655 }
656 micros = us;
657 }
658 }
659
660 let day_us = i128::from(days) * 86_400_i128 * 1_000_000_i128;
661 let tod_us =
662 (i128::from(hour) * 3_600_i128 + i128::from(minute) * 60_i128 + i128::from(second))
663 * 1_000_000_i128
664 + i128::from(micros);
665 let total = day_us + tod_us;
666 let Ok(total_i64) = i64::try_from(total) else {
667 return Value::Text(original.to_string());
668 };
669
670 Value::Timestamp(total_i64)
671}
672
673fn parse_2_digits(bytes: &[u8], offset: usize) -> Option<u32> {
674 if bytes.len() < offset + 2 {
675 return None;
676 }
677 let d0 = bytes[offset];
678 let d1 = bytes[offset + 1];
679 if !d0.is_ascii_digit() || !d1.is_ascii_digit() {
680 return None;
681 }
682 Some(u32::from(d0 - b'0') * 10 + u32::from(d1 - b'0'))
683}
684
685fn parse_4_digits(bytes: &[u8], offset: usize) -> Option<u32> {
686 if bytes.len() < offset + 4 {
687 return None;
688 }
689 let mut v: u32 = 0;
690 for i in 0..4 {
691 let d = bytes[offset + i];
692 if !d.is_ascii_digit() {
693 return None;
694 }
695 v = v * 10 + u32::from(d - b'0');
696 }
697 Some(v)
698}
699
700pub fn decode_binary_value(field_type: FieldType, data: &[u8], is_unsigned: bool) -> Value {
704 match field_type {
705 FieldType::Tiny => {
707 if data.is_empty() {
708 return Value::Null;
709 }
710 let _ = is_unsigned;
712 Value::TinyInt(data[0] as i8)
713 }
714
715 FieldType::Short | FieldType::Year => {
717 if data.len() < 2 {
718 return Value::Null;
719 }
720 let val = u16::from_le_bytes([data[0], data[1]]);
721 let _ = is_unsigned;
723 Value::SmallInt(val as i16)
724 }
725
726 FieldType::Long | FieldType::Int24 => {
728 if data.len() < 4 {
729 return Value::Null;
730 }
731 let val = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
732 let _ = is_unsigned;
734 Value::Int(val as i32)
735 }
736
737 FieldType::LongLong => {
739 if data.len() < 8 {
740 return Value::Null;
741 }
742 let val = u64::from_le_bytes([
743 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
744 ]);
745 let _ = is_unsigned;
747 Value::BigInt(val as i64)
748 }
749
750 FieldType::Float => {
752 if data.len() < 4 {
753 return Value::Null;
754 }
755 let val = f32::from_le_bytes([data[0], data[1], data[2], data[3]]);
756 Value::Float(val)
757 }
758
759 FieldType::Double => {
761 if data.len() < 8 {
762 return Value::Null;
763 }
764 let val = f64::from_le_bytes([
765 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
766 ]);
767 Value::Double(val)
768 }
769
770 FieldType::TinyBlob
772 | FieldType::MediumBlob
773 | FieldType::LongBlob
774 | FieldType::Blob
775 | FieldType::Geometry
776 | FieldType::Bit => Value::Bytes(data.to_vec()),
777
778 FieldType::Json => {
780 let text = String::from_utf8_lossy(data);
781 serde_json::from_str(&text).map_or_else(|_| Value::Bytes(data.to_vec()), Value::Json)
782 }
783
784 FieldType::Date
786 | FieldType::NewDate
787 | FieldType::Time
788 | FieldType::DateTime
789 | FieldType::Timestamp
790 | FieldType::Time2
791 | FieldType::DateTime2
792 | FieldType::Timestamp2 => decode_binary_temporal_value(field_type, data),
793
794 FieldType::Decimal | FieldType::NewDecimal => {
796 Value::Text(String::from_utf8_lossy(data).into_owned())
797 }
798
799 _ => Value::Text(String::from_utf8_lossy(data).into_owned()),
801 }
802}
803
804fn is_leap_year(year: i32) -> bool {
805 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
806}
807
808fn days_in_month(year: i32, month: u32) -> u32 {
809 match month {
810 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
811 4 | 6 | 9 | 11 => 30,
812 2 => {
813 if is_leap_year(year) {
814 29
815 } else {
816 28
817 }
818 }
819 _ => 0,
820 }
821}
822
823fn ymd_to_days_since_unix_epoch(year: i32, month: u32, day: u32) -> Option<i32> {
827 if year <= 0 || !(1..=12).contains(&month) {
828 return None;
829 }
830 let dim = days_in_month(year, month);
831 if day == 0 || day > dim {
832 return None;
833 }
834
835 let mut y = i64::from(year);
836 let m = i64::from(month);
837 let d = i64::from(day);
838
839 y -= if m <= 2 { 1 } else { 0 };
841
842 let era = if y >= 0 { y } else { y - 399 } / 400;
843 let yoe = y - era * 400; let mp = m + if m > 2 { -3 } else { 9 }; let doy = (153 * mp + 2) / 5 + d - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; let z = era * 146_097 + doe - 719_468; i32::try_from(z).ok()
850}
851
852fn decode_binary_temporal_value(field_type: FieldType, data: &[u8]) -> Value {
853 match field_type {
854 FieldType::Date | FieldType::NewDate => {
855 if data.len() < 4 {
856 return Value::Text("0000-00-00".to_string());
857 }
858 let year = i32::from(u16::from_le_bytes([data[0], data[1]]));
859 let month = u32::from(data[2]);
860 let day = u32::from(data[3]);
861
862 match ymd_to_days_since_unix_epoch(year, month, day) {
863 Some(days) => Value::Date(days),
864 None => Value::Text(format!("{year:04}-{month:02}-{day:02}")),
865 }
866 }
867
868 FieldType::Time | FieldType::Time2 => {
869 if data.len() < 8 {
870 return Value::Text("00:00:00".to_string());
871 }
872 let is_negative = data[0] != 0;
873 let days = i64::from(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
874 let hours = i64::from(data[5]);
875 let minutes = i64::from(data[6]);
876 let seconds = i64::from(data[7]);
877 let micros = if data.len() >= 12 {
878 i64::from(u32::from_le_bytes([data[8], data[9], data[10], data[11]]))
879 } else {
880 0
881 };
882
883 let total_seconds = days
884 .saturating_mul(24)
885 .saturating_add(hours) .saturating_mul(3600)
887 .saturating_add(minutes.saturating_mul(60))
888 .saturating_add(seconds);
889 let total_micros = total_seconds
890 .saturating_mul(1_000_000)
891 .saturating_add(micros);
892
893 let signed = if is_negative {
894 -total_micros
895 } else {
896 total_micros
897 };
898 Value::Time(signed)
899 }
900
901 FieldType::DateTime
902 | FieldType::Timestamp
903 | FieldType::DateTime2
904 | FieldType::Timestamp2 => {
905 if data.len() < 4 {
906 return Value::Text("0000-00-00 00:00:00".to_string());
907 }
908
909 let year = i32::from(u16::from_le_bytes([data[0], data[1]]));
910 let month = u32::from(data[2]);
911 let day = u32::from(data[3]);
912
913 let (hour, minute, second, micros) = if data.len() >= 7 {
914 let hour = u32::from(data[4]);
915 let minute = u32::from(data[5]);
916 let second = u32::from(data[6]);
917 let micros = if data.len() >= 11 {
918 u32::from_le_bytes([data[7], data[8], data[9], data[10]])
919 } else {
920 0
921 };
922 (hour, minute, second, micros)
923 } else {
924 (0, 0, 0, 0)
925 };
926
927 let Some(days) = ymd_to_days_since_unix_epoch(year, month, day) else {
928 return Value::Text(format!(
930 "{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}"
931 ));
932 };
933
934 let day_us = i128::from(days) * 86_400_i128 * 1_000_000_i128;
935 let tod_us =
936 (i128::from(hour) * 3_600_i128 + i128::from(minute) * 60_i128 + i128::from(second))
937 * 1_000_000_i128
938 + i128::from(micros);
939 let total = day_us + tod_us;
940 let Ok(total_i64) = i64::try_from(total) else {
941 return Value::Text(format!(
942 "{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}"
943 ));
944 };
945
946 Value::Timestamp(total_i64)
947 }
948
949 _ => Value::Text(String::from_utf8_lossy(data).into_owned()),
950 }
951}
952
953pub fn encode_binary_value(value: &Value, field_type: FieldType) -> Vec<u8> {
957 match value {
958 Value::Null => vec![],
959
960 Value::Bool(b) => vec![u8::from(*b)],
961
962 Value::TinyInt(i) => vec![*i as u8],
963
964 Value::SmallInt(i) => i.to_le_bytes().to_vec(),
965
966 Value::Int(i) => i.to_le_bytes().to_vec(),
967
968 Value::BigInt(i) => match field_type {
969 FieldType::Tiny => vec![*i as u8],
970 FieldType::Short | FieldType::Year => (*i as i16).to_le_bytes().to_vec(),
971 FieldType::Long | FieldType::Int24 => (*i as i32).to_le_bytes().to_vec(),
972 _ => i.to_le_bytes().to_vec(),
973 },
974
975 Value::Float(f) => f.to_le_bytes().to_vec(),
976
977 Value::Double(f) => f.to_le_bytes().to_vec(),
978
979 Value::Decimal(s) => encode_length_prefixed_bytes(s.as_bytes()),
980
981 Value::Text(s) => {
982 let bytes = s.as_bytes();
983 encode_length_prefixed_bytes(bytes)
984 }
985
986 Value::Bytes(b) => encode_length_prefixed_bytes(b),
987
988 Value::Json(j) => {
989 let s = j.to_string();
990 encode_length_prefixed_bytes(s.as_bytes())
991 }
992
993 Value::Date(d) => d.to_le_bytes().to_vec(),
995
996 Value::Time(t) => t.to_le_bytes().to_vec(),
998
999 Value::Timestamp(t) | Value::TimestampTz(t) => t.to_le_bytes().to_vec(),
1001
1002 Value::Uuid(u) => encode_length_prefixed_bytes(u),
1004
1005 Value::Array(arr) => {
1007 let json = serde_json::to_string(arr).unwrap_or_default();
1008 encode_length_prefixed_bytes(json.as_bytes())
1009 }
1010
1011 Value::Default => vec![],
1014 }
1015}
1016
1017pub fn decode_binary_value_with_len(
1026 data: &[u8],
1027 field_type: FieldType,
1028 _is_unsigned: bool,
1029) -> (Value, usize) {
1030 match field_type {
1031 FieldType::Tiny => {
1033 if data.is_empty() {
1034 return (Value::Null, 0);
1035 }
1036 (Value::TinyInt(data[0] as i8), 1)
1037 }
1038
1039 FieldType::Short | FieldType::Year => {
1040 if data.len() < 2 {
1041 return (Value::Null, 0);
1042 }
1043 let val = u16::from_le_bytes([data[0], data[1]]);
1044 (Value::SmallInt(val as i16), 2)
1045 }
1046
1047 FieldType::Long | FieldType::Int24 => {
1048 if data.len() < 4 {
1049 return (Value::Null, 0);
1050 }
1051 let val = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1052 (Value::Int(val as i32), 4)
1053 }
1054
1055 FieldType::LongLong => {
1056 if data.len() < 8 {
1057 return (Value::Null, 0);
1058 }
1059 let val = u64::from_le_bytes([
1060 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1061 ]);
1062 (Value::BigInt(val as i64), 8)
1063 }
1064
1065 FieldType::Float => {
1066 if data.len() < 4 {
1067 return (Value::Null, 0);
1068 }
1069 let val = f32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1070 (Value::Float(val), 4)
1071 }
1072
1073 FieldType::Double => {
1074 if data.len() < 8 {
1075 return (Value::Null, 0);
1076 }
1077 let val = f64::from_le_bytes([
1078 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1079 ]);
1080 (Value::Double(val), 8)
1081 }
1082
1083 FieldType::Date | FieldType::NewDate => {
1085 if data.is_empty() {
1086 return (Value::Null, 0);
1087 }
1088 let len = data[0] as usize;
1089 if len == 0 {
1090 return (Value::Text("0000-00-00".to_string()), 1);
1091 }
1092 if data.len() < 1 + len || len < 4 {
1093 return (Value::Null, 1);
1094 }
1095 let value = decode_binary_temporal_value(field_type, &data[1..=len]);
1096 (value, 1 + len)
1097 }
1098
1099 FieldType::Time | FieldType::Time2 => {
1100 if data.is_empty() {
1101 return (Value::Null, 0);
1102 }
1103 let len = data[0] as usize;
1104 if len == 0 {
1105 return (Value::Text("00:00:00".to_string()), 1);
1106 }
1107 if data.len() < 1 + len || len < 8 {
1108 return (Value::Text("00:00:00".to_string()), 1);
1109 }
1110 let value = decode_binary_temporal_value(field_type, &data[1..=len]);
1111 (value, 1 + len)
1112 }
1113
1114 FieldType::DateTime
1115 | FieldType::Timestamp
1116 | FieldType::DateTime2
1117 | FieldType::Timestamp2 => {
1118 if data.is_empty() {
1119 return (Value::Null, 0);
1120 }
1121 let len = data[0] as usize;
1122 if len == 0 {
1123 return (Value::Text("0000-00-00 00:00:00".to_string()), 1);
1124 }
1125 if data.len() < 1 + len {
1126 return (Value::Null, 1);
1127 }
1128 let value = decode_binary_temporal_value(field_type, &data[1..=len]);
1129 (value, 1 + len)
1130 }
1131
1132 FieldType::Decimal
1134 | FieldType::NewDecimal
1135 | FieldType::VarChar
1136 | FieldType::VarString
1137 | FieldType::String
1138 | FieldType::Enum
1139 | FieldType::Set
1140 | FieldType::TinyBlob
1141 | FieldType::MediumBlob
1142 | FieldType::LongBlob
1143 | FieldType::Blob
1144 | FieldType::Json
1145 | FieldType::Geometry
1146 | FieldType::Bit => {
1147 let (str_len, prefix_len) = read_lenenc_int(data);
1148 if str_len == 0 && prefix_len == 0 {
1149 return (Value::Null, 0);
1150 }
1151 let total_len = prefix_len + str_len;
1152 if data.len() < total_len {
1153 return (Value::Null, prefix_len);
1154 }
1155 let str_data = &data[prefix_len..total_len];
1156 let value = match field_type {
1157 FieldType::TinyBlob
1158 | FieldType::MediumBlob
1159 | FieldType::LongBlob
1160 | FieldType::Blob
1161 | FieldType::Geometry
1162 | FieldType::Bit => Value::Bytes(str_data.to_vec()),
1163 FieldType::Json => {
1164 let text = String::from_utf8_lossy(str_data);
1165 serde_json::from_str(&text)
1166 .map_or_else(|_| Value::Bytes(str_data.to_vec()), Value::Json)
1167 }
1168 _ => Value::Text(String::from_utf8_lossy(str_data).into_owned()),
1169 };
1170 (value, total_len)
1171 }
1172
1173 FieldType::Null => (Value::Null, 0),
1175 }
1176}
1177
1178fn read_lenenc_int(data: &[u8]) -> (usize, usize) {
1182 if data.is_empty() {
1183 return (0, 0);
1184 }
1185 match data[0] {
1186 0..=250 => (data[0] as usize, 1),
1187 0xFC => {
1188 if data.len() < 3 {
1189 return (0, 1);
1190 }
1191 let val = u16::from_le_bytes([data[1], data[2]]) as usize;
1192 (val, 3)
1193 }
1194 0xFD => {
1195 if data.len() < 4 {
1196 return (0, 1);
1197 }
1198 let val = u32::from_le_bytes([data[1], data[2], data[3], 0]) as usize;
1199 (val, 4)
1200 }
1201 0xFE => {
1202 if data.len() < 9 {
1203 return (0, 1);
1204 }
1205 let val = u64::from_le_bytes([
1206 data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
1207 ]) as usize;
1208 (val, 9)
1209 }
1210 251..=255 => (0, 1),
1212 }
1213}
1214
1215fn encode_length_prefixed_bytes(data: &[u8]) -> Vec<u8> {
1217 let len = data.len();
1218 let mut result = Vec::with_capacity(len + 9);
1219
1220 if len < 251 {
1221 result.push(len as u8);
1222 } else if len < 0x10000 {
1223 result.push(0xFC);
1224 result.extend_from_slice(&(len as u16).to_le_bytes());
1225 } else if len < 0x0100_0000 {
1226 result.push(0xFD);
1227 result.push((len & 0xFF) as u8);
1228 result.push(((len >> 8) & 0xFF) as u8);
1229 result.push(((len >> 16) & 0xFF) as u8);
1230 } else {
1231 result.push(0xFE);
1232 result.extend_from_slice(&(len as u64).to_le_bytes());
1233 }
1234
1235 result.extend_from_slice(data);
1236 result
1237}
1238
1239fn escape_string(s: &str) -> String {
1243 let mut result = String::with_capacity(s.len() + 2);
1244 result.push('\'');
1245 for ch in s.chars() {
1246 match ch {
1247 '\'' => result.push_str("''"),
1248 '\\' => result.push_str("\\\\"),
1249 '\0' => result.push_str("\\0"),
1250 '\n' => result.push_str("\\n"),
1251 '\r' => result.push_str("\\r"),
1252 '\x1a' => result.push_str("\\Z"), _ => result.push(ch),
1254 }
1255 }
1256 result.push('\'');
1257 result
1258}
1259
1260fn escape_bytes(data: &[u8]) -> String {
1262 let mut result = String::with_capacity(data.len() * 2 + 3);
1263 result.push_str("X'");
1264 for byte in data {
1265 result.push_str(&format!("{byte:02X}"));
1266 }
1267 result.push('\'');
1268 result
1269}
1270
1271pub fn format_value_for_sql(value: &Value) -> String {
1275 match value {
1276 Value::Null => "NULL".to_string(),
1277 Value::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
1278 Value::TinyInt(i) => i.to_string(),
1279 Value::SmallInt(i) => i.to_string(),
1280 Value::Int(i) => i.to_string(),
1281 Value::BigInt(i) => i.to_string(),
1282 Value::Float(f) => {
1283 if f.is_nan() {
1284 "NULL".to_string()
1285 } else if f.is_infinite() {
1286 if f.is_sign_positive() {
1287 "1e308".to_string() } else {
1289 "-1e308".to_string()
1290 }
1291 } else {
1292 f.to_string()
1293 }
1294 }
1295 Value::Double(f) => {
1296 if f.is_nan() {
1297 "NULL".to_string()
1298 } else if f.is_infinite() {
1299 if f.is_sign_positive() {
1300 "1e308".to_string()
1301 } else {
1302 "-1e308".to_string()
1303 }
1304 } else {
1305 f.to_string()
1306 }
1307 }
1308 Value::Decimal(s) => s.clone(),
1309 Value::Text(s) => escape_string(s),
1310 Value::Bytes(b) => escape_bytes(b),
1311 Value::Json(j) => escape_string(&j.to_string()),
1312 Value::Date(d) => format!("'{}'", d), Value::Time(t) => format!("'{}'", t), Value::Timestamp(t) | Value::TimestampTz(t) => format!("'{}'", t),
1315 Value::Uuid(u) => escape_bytes(u),
1316 Value::Array(arr) => {
1317 let json = serde_json::to_string(arr).unwrap_or_default();
1319 escape_string(&json)
1320 }
1321 Value::Default => "DEFAULT".to_string(),
1322 }
1323}
1324
1325pub fn interpolate_params(sql: &str, params: &[Value]) -> String {
1330 if params.is_empty() {
1331 return sql.to_string();
1332 }
1333
1334 let mut result = String::with_capacity(sql.len() + params.len() * 20);
1335 let mut chars = sql.chars().peekable();
1336 let mut param_index = 0;
1337
1338 while let Some(ch) = chars.next() {
1339 match ch {
1340 '?' => {
1342 if param_index < params.len() {
1343 result.push_str(&format_value_for_sql(¶ms[param_index]));
1344 param_index += 1;
1345 } else {
1346 result.push('?');
1347 }
1348 }
1349 '$' => {
1351 let mut num_str = String::new();
1352 while let Some(&next_ch) = chars.peek() {
1353 if next_ch.is_ascii_digit() {
1354 num_str.push(chars.next().unwrap());
1355 } else {
1356 break;
1357 }
1358 }
1359 if num_str.is_empty() {
1360 result.push('$');
1361 } else if let Ok(n) = num_str.parse::<usize>() {
1362 if n > 0 && n <= params.len() {
1363 result.push_str(&format_value_for_sql(¶ms[n - 1]));
1364 } else {
1365 result.push('$');
1366 result.push_str(&num_str);
1367 }
1368 } else {
1369 result.push('$');
1370 result.push_str(&num_str);
1371 }
1372 }
1373 '\'' => {
1375 result.push(ch);
1376 while let Some(next_ch) = chars.next() {
1377 result.push(next_ch);
1378 if next_ch == '\'' {
1379 if chars.peek() == Some(&'\'') {
1381 result.push(chars.next().unwrap());
1382 } else {
1383 break;
1384 }
1385 }
1386 }
1387 }
1388 '"' => {
1390 result.push(ch);
1391 while let Some(next_ch) = chars.next() {
1392 result.push(next_ch);
1393 if next_ch == '"' {
1394 if chars.peek() == Some(&'"') {
1395 result.push(chars.next().unwrap());
1396 } else {
1397 break;
1398 }
1399 }
1400 }
1401 }
1402 '`' => {
1404 result.push(ch);
1405 while let Some(next_ch) = chars.next() {
1406 result.push(next_ch);
1407 if next_ch == '`' {
1408 if chars.peek() == Some(&'`') {
1409 result.push(chars.next().unwrap());
1410 } else {
1411 break;
1412 }
1413 }
1414 }
1415 }
1416 _ => result.push(ch),
1417 }
1418 }
1419
1420 result
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425 use super::*;
1426
1427 #[test]
1428 fn test_escape_string() {
1429 assert_eq!(escape_string("hello"), "'hello'");
1430 assert_eq!(escape_string("it's"), "'it''s'");
1431 assert_eq!(escape_string("a\\b"), "'a\\\\b'");
1432 assert_eq!(escape_string("line\nbreak"), "'line\\nbreak'");
1433 }
1434
1435 #[test]
1436 fn test_format_value() {
1437 assert_eq!(format_value_for_sql(&Value::Null), "NULL");
1438 assert_eq!(format_value_for_sql(&Value::Int(42)), "42");
1439 assert_eq!(
1440 format_value_for_sql(&Value::Text("hello".to_string())),
1441 "'hello'"
1442 );
1443 assert_eq!(format_value_for_sql(&Value::Bool(true)), "TRUE");
1444 }
1445
1446 #[test]
1447 fn test_interpolate_params_question_mark() {
1448 let sql = "SELECT * FROM users WHERE id = ? AND name = ?";
1449 let params = vec![Value::Int(1), Value::Text("Alice".to_string())];
1450 let result = interpolate_params(sql, ¶ms);
1451 assert_eq!(
1452 result,
1453 "SELECT * FROM users WHERE id = 1 AND name = 'Alice'"
1454 );
1455 }
1456
1457 #[test]
1458 fn test_interpolate_params_dollar() {
1459 let sql = "SELECT * FROM users WHERE id = $1 AND name = $2";
1460 let params = vec![Value::Int(1), Value::Text("Alice".to_string())];
1461 let result = interpolate_params(sql, ¶ms);
1462 assert_eq!(
1463 result,
1464 "SELECT * FROM users WHERE id = 1 AND name = 'Alice'"
1465 );
1466 }
1467
1468 #[test]
1469 fn test_interpolate_no_replace_in_string() {
1470 let sql = "SELECT * FROM users WHERE name = '$1' AND id = ?";
1471 let params = vec![Value::Int(42)];
1472 let result = interpolate_params(sql, ¶ms);
1473 assert_eq!(result, "SELECT * FROM users WHERE name = '$1' AND id = 42");
1474 }
1475
1476 #[test]
1477 fn test_field_type_from_u8() {
1478 assert_eq!(FieldType::from_u8(0x01), FieldType::Tiny);
1479 assert_eq!(FieldType::from_u8(0x03), FieldType::Long);
1480 assert_eq!(FieldType::from_u8(0x08), FieldType::LongLong);
1481 assert_eq!(FieldType::from_u8(0xFC), FieldType::Blob);
1482 assert_eq!(FieldType::from_u8(0xF5), FieldType::Json);
1483 }
1484
1485 #[test]
1486 fn test_field_type_categories() {
1487 assert!(FieldType::Tiny.is_integer());
1488 assert!(FieldType::Long.is_integer());
1489 assert!(FieldType::LongLong.is_integer());
1490
1491 assert!(FieldType::Float.is_float());
1492 assert!(FieldType::Double.is_float());
1493
1494 assert!(FieldType::Decimal.is_decimal());
1495 assert!(FieldType::NewDecimal.is_decimal());
1496
1497 assert!(FieldType::VarChar.is_string());
1498 assert!(FieldType::String.is_string());
1499
1500 assert!(FieldType::Blob.is_blob());
1501 assert!(FieldType::TinyBlob.is_blob());
1502
1503 assert!(FieldType::Date.is_temporal());
1504 assert!(FieldType::DateTime.is_temporal());
1505 assert!(FieldType::Timestamp.is_temporal());
1506 }
1507
1508 #[test]
1509 fn test_decode_text_integer() {
1510 let val = decode_text_value(FieldType::Long, b"42", false);
1511 assert!(matches!(val, Value::Int(42)));
1512
1513 let val = decode_text_value(FieldType::LongLong, b"-100", false);
1514 assert!(matches!(val, Value::BigInt(-100)));
1515 }
1516
1517 #[test]
1518 #[allow(clippy::approx_constant)]
1519 fn test_decode_text_float() {
1520 let val = decode_text_value(FieldType::Double, b"3.14", false);
1521 assert!(matches!(val, Value::Double(f) if (f - 3.14).abs() < 0.001));
1522 }
1523
1524 #[test]
1525 fn test_decode_text_string() {
1526 let val = decode_text_value(FieldType::VarChar, b"hello", false);
1527 assert!(matches!(val, Value::Text(s) if s == "hello"));
1528 }
1529
1530 #[test]
1531 fn test_decode_text_date_to_value_date() {
1532 let val = decode_text_value(FieldType::Date, b"2024-02-29", false);
1533 let expected_days = ymd_to_days_since_unix_epoch(2024, 2, 29).unwrap();
1534 assert_eq!(val, Value::Date(expected_days));
1535 }
1536
1537 #[test]
1538 fn test_decode_text_date_zero_preserved_as_text() {
1539 let val = decode_text_value(FieldType::Date, b"0000-00-00", false);
1540 assert_eq!(val, Value::Text("0000-00-00".to_string()));
1541 }
1542
1543 #[test]
1544 fn test_decode_text_time_to_value_time() {
1545 let val = decode_text_value(FieldType::Time, b"25:00:00.000001", false);
1547 let expected = (25_i64 * 3600_i64) * 1_000_000_i64 + 1;
1548 assert_eq!(val, Value::Time(expected));
1549 }
1550
1551 #[test]
1552 fn test_decode_text_time_negative_to_value_time() {
1553 let val = decode_text_value(FieldType::Time, b"-01:02:03.4", false);
1554 let expected = -(((3600_i64 + 2 * 60 + 3) * 1_000_000_i64) + 400_000_i64);
1556 assert_eq!(val, Value::Time(expected));
1557 }
1558
1559 #[test]
1560 fn test_decode_text_time_zero_preserved_as_text() {
1561 let val = decode_text_value(FieldType::Time, b"00:00:00", false);
1562 assert_eq!(val, Value::Text("00:00:00".to_string()));
1563 }
1564
1565 #[test]
1566 fn test_decode_text_datetime_to_value_timestamp() {
1567 let val = decode_text_value(FieldType::DateTime, b"2020-01-02 03:04:05.000006", false);
1568
1569 let days = i64::from(ymd_to_days_since_unix_epoch(2020, 1, 2).unwrap());
1570 let tod_us = ((3_i64 * 3600 + 4 * 60 + 5) * 1_000_000) + 6;
1571 let expected = days * 86_400 * 1_000_000 + tod_us;
1572 assert_eq!(val, Value::Timestamp(expected));
1573 }
1574
1575 #[test]
1576 fn test_decode_text_timestamp_zero_preserved_as_text() {
1577 let val = decode_text_value(FieldType::Timestamp, b"0000-00-00 00:00:00", false);
1578 assert_eq!(val, Value::Text("0000-00-00 00:00:00".to_string()));
1579 }
1580
1581 #[test]
1582 fn test_decode_binary_tiny() {
1583 let val = decode_binary_value(FieldType::Tiny, &[42], false);
1584 assert!(matches!(val, Value::TinyInt(42)));
1585
1586 let val = decode_binary_value(FieldType::Tiny, &[255u8], true);
1587 assert!(matches!(val, Value::TinyInt(-1))); let val = decode_binary_value(FieldType::Tiny, &[255], false);
1590 assert!(matches!(val, Value::TinyInt(-1)));
1591 }
1592
1593 #[test]
1594 fn test_decode_binary_long() {
1595 let val = decode_binary_value(FieldType::Long, &[0x2A, 0x00, 0x00, 0x00], false);
1596 assert!(matches!(val, Value::Int(42)));
1597 }
1598
1599 #[test]
1600 #[allow(clippy::approx_constant)]
1601 fn test_decode_binary_double() {
1602 let pi_bytes = 3.14159_f64.to_le_bytes();
1603 let val = decode_binary_value(FieldType::Double, &pi_bytes, false);
1604 assert!(matches!(val, Value::Double(f) if (f - 3.14159).abs() < 0.00001));
1605 }
1606
1607 #[test]
1608 fn test_decode_binary_date_with_len_to_value_date() {
1609 let mut buf = Vec::new();
1611 buf.push(4);
1612 buf.extend_from_slice(&2024_u16.to_le_bytes());
1613 buf.push(2);
1614 buf.push(29);
1615
1616 let (val, consumed) = decode_binary_value_with_len(&buf, FieldType::Date, false);
1617 assert_eq!(consumed, 5);
1618
1619 let expected_days = ymd_to_days_since_unix_epoch(2024, 2, 29).unwrap();
1620 assert_eq!(val, Value::Date(expected_days));
1621 }
1622
1623 #[test]
1624 fn test_decode_binary_time_with_len_to_value_time() {
1625 let mut buf = Vec::new();
1627 buf.push(12);
1628 buf.push(0); buf.extend_from_slice(&1_u32.to_le_bytes());
1630 buf.push(2);
1631 buf.push(3);
1632 buf.push(4);
1633 buf.extend_from_slice(&5_u32.to_le_bytes());
1634
1635 let (val, consumed) = decode_binary_value_with_len(&buf, FieldType::Time, false);
1636 assert_eq!(consumed, 13);
1637
1638 let total_seconds = (24_i64 + 2) * 3600 + 3 * 60 + 4;
1639 let expected = total_seconds * 1_000_000 + 5;
1640 assert_eq!(val, Value::Time(expected));
1641 }
1642
1643 #[test]
1644 fn test_decode_binary_datetime_with_len_to_value_timestamp() {
1645 let mut buf = Vec::new();
1647 buf.push(11);
1648 buf.extend_from_slice(&2020_u16.to_le_bytes());
1649 buf.push(1);
1650 buf.push(2);
1651 buf.push(3);
1652 buf.push(4);
1653 buf.push(5);
1654 buf.extend_from_slice(&6_u32.to_le_bytes());
1655
1656 let (val, consumed) = decode_binary_value_with_len(&buf, FieldType::DateTime, false);
1657 assert_eq!(consumed, 12);
1658
1659 let days = i64::from(ymd_to_days_since_unix_epoch(2020, 1, 2).unwrap());
1660 let tod_us = ((3_i64 * 3600 + 4 * 60 + 5) * 1_000_000) + 6;
1661 let expected = days * 86_400 * 1_000_000 + tod_us;
1662 assert_eq!(val, Value::Timestamp(expected));
1663 }
1664
1665 #[test]
1666 fn test_column_flags() {
1667 let col = ColumnDef {
1668 catalog: "def".to_string(),
1669 schema: "test".to_string(),
1670 table: "users".to_string(),
1671 org_table: "users".to_string(),
1672 name: "id".to_string(),
1673 org_name: "id".to_string(),
1674 charset: 33,
1675 column_length: 11,
1676 column_type: FieldType::Long,
1677 flags: column_flags::NOT_NULL
1678 | column_flags::PRIMARY_KEY
1679 | column_flags::AUTO_INCREMENT
1680 | column_flags::UNSIGNED,
1681 decimals: 0,
1682 };
1683
1684 assert!(col.is_not_null());
1685 assert!(col.is_primary_key());
1686 assert!(col.is_auto_increment());
1687 assert!(col.is_unsigned());
1688 assert!(!col.is_binary());
1689 }
1690
1691 #[test]
1692 fn test_encode_length_prefixed() {
1693 let result = encode_length_prefixed_bytes(b"hello");
1695 assert_eq!(result[0], 5);
1696 assert_eq!(&result[1..], b"hello");
1697
1698 let result = encode_length_prefixed_bytes(b"");
1700 assert_eq!(result, vec![0]);
1701 }
1702}