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 _ => Value::Text(text.into_owned()),
407 }
408}
409
410pub fn decode_binary_value(field_type: FieldType, data: &[u8], is_unsigned: bool) -> Value {
414 match field_type {
415 FieldType::Tiny => {
417 if data.is_empty() {
418 return Value::Null;
419 }
420 let _ = is_unsigned;
422 Value::TinyInt(data[0] as i8)
423 }
424
425 FieldType::Short | FieldType::Year => {
427 if data.len() < 2 {
428 return Value::Null;
429 }
430 let val = u16::from_le_bytes([data[0], data[1]]);
431 let _ = is_unsigned;
433 Value::SmallInt(val as i16)
434 }
435
436 FieldType::Long | FieldType::Int24 => {
438 if data.len() < 4 {
439 return Value::Null;
440 }
441 let val = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
442 let _ = is_unsigned;
444 Value::Int(val as i32)
445 }
446
447 FieldType::LongLong => {
449 if data.len() < 8 {
450 return Value::Null;
451 }
452 let val = u64::from_le_bytes([
453 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
454 ]);
455 let _ = is_unsigned;
457 Value::BigInt(val as i64)
458 }
459
460 FieldType::Float => {
462 if data.len() < 4 {
463 return Value::Null;
464 }
465 let val = f32::from_le_bytes([data[0], data[1], data[2], data[3]]);
466 Value::Float(val)
467 }
468
469 FieldType::Double => {
471 if data.len() < 8 {
472 return Value::Null;
473 }
474 let val = f64::from_le_bytes([
475 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
476 ]);
477 Value::Double(val)
478 }
479
480 FieldType::TinyBlob
482 | FieldType::MediumBlob
483 | FieldType::LongBlob
484 | FieldType::Blob
485 | FieldType::Geometry
486 | FieldType::Bit => Value::Bytes(data.to_vec()),
487
488 FieldType::Json => {
490 let text = String::from_utf8_lossy(data);
491 serde_json::from_str(&text).map_or_else(|_| Value::Bytes(data.to_vec()), Value::Json)
492 }
493
494 FieldType::Date
496 | FieldType::NewDate
497 | FieldType::Time
498 | FieldType::DateTime
499 | FieldType::Timestamp
500 | FieldType::Time2
501 | FieldType::DateTime2
502 | FieldType::Timestamp2 => {
503 Value::Text(decode_binary_datetime(field_type, data))
506 }
507
508 FieldType::Decimal | FieldType::NewDecimal => {
510 Value::Text(String::from_utf8_lossy(data).into_owned())
511 }
512
513 _ => Value::Text(String::from_utf8_lossy(data).into_owned()),
515 }
516}
517
518fn decode_binary_datetime(field_type: FieldType, data: &[u8]) -> String {
520 match field_type {
521 FieldType::Date | FieldType::NewDate => {
522 if data.len() >= 4 {
523 let year = u16::from_le_bytes([data[0], data[1]]);
524 let month = data[2];
525 let day = data[3];
526 format!("{year:04}-{month:02}-{day:02}")
527 } else {
528 "0000-00-00".to_string()
530 }
531 }
532
533 FieldType::Time | FieldType::Time2 => {
534 if data.len() >= 8 {
535 let is_negative = data[0] != 0;
536 let days = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
537 let hours = data[5];
538 let minutes = data[6];
539 let seconds = data[7];
540 let total_hours = days * 24 + u32::from(hours);
541 let sign = if is_negative { "-" } else { "" };
542 format!("{sign}{total_hours:02}:{minutes:02}:{seconds:02}")
543 } else {
544 "00:00:00".to_string()
546 }
547 }
548
549 FieldType::DateTime
550 | FieldType::Timestamp
551 | FieldType::DateTime2
552 | FieldType::Timestamp2 => {
553 if data.len() >= 7 {
554 let year = u16::from_le_bytes([data[0], data[1]]);
555 let month = data[2];
556 let day = data[3];
557 let hour = data[4];
558 let minute = data[5];
559 let second = data[6];
560
561 if data.len() >= 11 {
562 let microseconds = u32::from_le_bytes([data[7], data[8], data[9], data[10]]);
563 format!(
564 "{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}.{microseconds:06}"
565 )
566 } else {
567 format!("{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}")
568 }
569 } else if data.len() >= 4 {
570 let year = u16::from_le_bytes([data[0], data[1]]);
571 let month = data[2];
572 let day = data[3];
573 format!("{year:04}-{month:02}-{day:02} 00:00:00")
574 } else {
575 "0000-00-00 00:00:00".to_string()
576 }
577 }
578
579 _ => String::from_utf8_lossy(data).into_owned(),
580 }
581}
582
583pub fn encode_binary_value(value: &Value, field_type: FieldType) -> Vec<u8> {
587 match value {
588 Value::Null => vec![],
589
590 Value::Bool(b) => vec![u8::from(*b)],
591
592 Value::TinyInt(i) => vec![*i as u8],
593
594 Value::SmallInt(i) => i.to_le_bytes().to_vec(),
595
596 Value::Int(i) => i.to_le_bytes().to_vec(),
597
598 Value::BigInt(i) => match field_type {
599 FieldType::Tiny => vec![*i as u8],
600 FieldType::Short | FieldType::Year => (*i as i16).to_le_bytes().to_vec(),
601 FieldType::Long | FieldType::Int24 => (*i as i32).to_le_bytes().to_vec(),
602 _ => i.to_le_bytes().to_vec(),
603 },
604
605 Value::Float(f) => f.to_le_bytes().to_vec(),
606
607 Value::Double(f) => f.to_le_bytes().to_vec(),
608
609 Value::Decimal(s) => encode_length_prefixed_bytes(s.as_bytes()),
610
611 Value::Text(s) => {
612 let bytes = s.as_bytes();
613 encode_length_prefixed_bytes(bytes)
614 }
615
616 Value::Bytes(b) => encode_length_prefixed_bytes(b),
617
618 Value::Json(j) => {
619 let s = j.to_string();
620 encode_length_prefixed_bytes(s.as_bytes())
621 }
622
623 Value::Date(d) => d.to_le_bytes().to_vec(),
625
626 Value::Time(t) => t.to_le_bytes().to_vec(),
628
629 Value::Timestamp(t) | Value::TimestampTz(t) => t.to_le_bytes().to_vec(),
631
632 Value::Uuid(u) => encode_length_prefixed_bytes(u),
634
635 Value::Array(arr) => {
637 let json = serde_json::to_string(arr).unwrap_or_default();
638 encode_length_prefixed_bytes(json.as_bytes())
639 }
640
641 Value::Default => vec![],
644 }
645}
646
647pub fn decode_binary_value_with_len(
656 data: &[u8],
657 field_type: FieldType,
658 _is_unsigned: bool,
659) -> (Value, usize) {
660 match field_type {
661 FieldType::Tiny => {
663 if data.is_empty() {
664 return (Value::Null, 0);
665 }
666 (Value::TinyInt(data[0] as i8), 1)
667 }
668
669 FieldType::Short | FieldType::Year => {
670 if data.len() < 2 {
671 return (Value::Null, 0);
672 }
673 let val = u16::from_le_bytes([data[0], data[1]]);
674 (Value::SmallInt(val as i16), 2)
675 }
676
677 FieldType::Long | FieldType::Int24 => {
678 if data.len() < 4 {
679 return (Value::Null, 0);
680 }
681 let val = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
682 (Value::Int(val as i32), 4)
683 }
684
685 FieldType::LongLong => {
686 if data.len() < 8 {
687 return (Value::Null, 0);
688 }
689 let val = u64::from_le_bytes([
690 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
691 ]);
692 (Value::BigInt(val as i64), 8)
693 }
694
695 FieldType::Float => {
696 if data.len() < 4 {
697 return (Value::Null, 0);
698 }
699 let val = f32::from_le_bytes([data[0], data[1], data[2], data[3]]);
700 (Value::Float(val), 4)
701 }
702
703 FieldType::Double => {
704 if data.len() < 8 {
705 return (Value::Null, 0);
706 }
707 let val = f64::from_le_bytes([
708 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
709 ]);
710 (Value::Double(val), 8)
711 }
712
713 FieldType::Date | FieldType::NewDate => {
715 if data.is_empty() {
716 return (Value::Null, 0);
717 }
718 let len = data[0] as usize;
719 if len == 0 {
720 return (Value::Text("0000-00-00".to_string()), 1);
721 }
722 if data.len() < 1 + len || len < 4 {
723 return (Value::Null, 1);
724 }
725 let year = u16::from_le_bytes([data[1], data[2]]);
726 let month = data[3];
727 let day = data[4];
728 (
729 Value::Text(format!("{year:04}-{month:02}-{day:02}")),
730 1 + len,
731 )
732 }
733
734 FieldType::Time | FieldType::Time2 => {
735 if data.is_empty() {
736 return (Value::Null, 0);
737 }
738 let len = data[0] as usize;
739 if len == 0 {
740 return (Value::Text("00:00:00".to_string()), 1);
741 }
742 if data.len() < 1 + len || len < 8 {
743 return (Value::Text("00:00:00".to_string()), 1);
744 }
745 let is_negative = data[1] != 0;
746 let days = u32::from_le_bytes([data[2], data[3], data[4], data[5]]);
747 let hours = data[6];
748 let minutes = data[7];
749 let seconds = data[8];
750 let total_hours = days * 24 + u32::from(hours);
751 let sign = if is_negative { "-" } else { "" };
752 (
753 Value::Text(format!("{sign}{total_hours:02}:{minutes:02}:{seconds:02}")),
754 1 + len,
755 )
756 }
757
758 FieldType::DateTime
759 | FieldType::Timestamp
760 | FieldType::DateTime2
761 | FieldType::Timestamp2 => {
762 if data.is_empty() {
763 return (Value::Null, 0);
764 }
765 let len = data[0] as usize;
766 if len == 0 {
767 return (Value::Text("0000-00-00 00:00:00".to_string()), 1);
768 }
769 if data.len() < 1 + len {
770 return (Value::Null, 1);
771 }
772 if len >= 4 {
773 let year = u16::from_le_bytes([data[1], data[2]]);
774 let month = data[3];
775 let day = data[4];
776 if len >= 7 {
777 let hour = data[5];
778 let minute = data[6];
779 let second = data[7];
780 if len >= 11 {
781 let microseconds =
782 u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
783 return (
784 Value::Text(format!(
785 "{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}.{microseconds:06}"
786 )),
787 1 + len,
788 );
789 }
790 return (
791 Value::Text(format!(
792 "{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}"
793 )),
794 1 + len,
795 );
796 }
797 return (
798 Value::Text(format!("{year:04}-{month:02}-{day:02} 00:00:00")),
799 1 + len,
800 );
801 }
802 (Value::Text("0000-00-00 00:00:00".to_string()), 1 + len)
803 }
804
805 FieldType::Decimal
807 | FieldType::NewDecimal
808 | FieldType::VarChar
809 | FieldType::VarString
810 | FieldType::String
811 | FieldType::Enum
812 | FieldType::Set
813 | FieldType::TinyBlob
814 | FieldType::MediumBlob
815 | FieldType::LongBlob
816 | FieldType::Blob
817 | FieldType::Json
818 | FieldType::Geometry
819 | FieldType::Bit => {
820 let (str_len, prefix_len) = read_lenenc_int(data);
821 if str_len == 0 && prefix_len == 0 {
822 return (Value::Null, 0);
823 }
824 let total_len = prefix_len + str_len;
825 if data.len() < total_len {
826 return (Value::Null, prefix_len);
827 }
828 let str_data = &data[prefix_len..total_len];
829 let value = match field_type {
830 FieldType::TinyBlob
831 | FieldType::MediumBlob
832 | FieldType::LongBlob
833 | FieldType::Blob
834 | FieldType::Geometry
835 | FieldType::Bit => Value::Bytes(str_data.to_vec()),
836 FieldType::Json => {
837 let text = String::from_utf8_lossy(str_data);
838 serde_json::from_str(&text)
839 .map_or_else(|_| Value::Bytes(str_data.to_vec()), Value::Json)
840 }
841 _ => Value::Text(String::from_utf8_lossy(str_data).into_owned()),
842 };
843 (value, total_len)
844 }
845
846 FieldType::Null => (Value::Null, 0),
848 }
849}
850
851fn read_lenenc_int(data: &[u8]) -> (usize, usize) {
855 if data.is_empty() {
856 return (0, 0);
857 }
858 match data[0] {
859 0..=250 => (data[0] as usize, 1),
860 0xFC => {
861 if data.len() < 3 {
862 return (0, 1);
863 }
864 let val = u16::from_le_bytes([data[1], data[2]]) as usize;
865 (val, 3)
866 }
867 0xFD => {
868 if data.len() < 4 {
869 return (0, 1);
870 }
871 let val = u32::from_le_bytes([data[1], data[2], data[3], 0]) as usize;
872 (val, 4)
873 }
874 0xFE => {
875 if data.len() < 9 {
876 return (0, 1);
877 }
878 let val = u64::from_le_bytes([
879 data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
880 ]) as usize;
881 (val, 9)
882 }
883 251..=255 => (0, 1),
885 }
886}
887
888fn encode_length_prefixed_bytes(data: &[u8]) -> Vec<u8> {
890 let len = data.len();
891 let mut result = Vec::with_capacity(len + 9);
892
893 if len < 251 {
894 result.push(len as u8);
895 } else if len < 0x10000 {
896 result.push(0xFC);
897 result.extend_from_slice(&(len as u16).to_le_bytes());
898 } else if len < 0x0100_0000 {
899 result.push(0xFD);
900 result.push((len & 0xFF) as u8);
901 result.push(((len >> 8) & 0xFF) as u8);
902 result.push(((len >> 16) & 0xFF) as u8);
903 } else {
904 result.push(0xFE);
905 result.extend_from_slice(&(len as u64).to_le_bytes());
906 }
907
908 result.extend_from_slice(data);
909 result
910}
911
912fn escape_string(s: &str) -> String {
916 let mut result = String::with_capacity(s.len() + 2);
917 result.push('\'');
918 for ch in s.chars() {
919 match ch {
920 '\'' => result.push_str("''"),
921 '\\' => result.push_str("\\\\"),
922 '\0' => result.push_str("\\0"),
923 '\n' => result.push_str("\\n"),
924 '\r' => result.push_str("\\r"),
925 '\x1a' => result.push_str("\\Z"), _ => result.push(ch),
927 }
928 }
929 result.push('\'');
930 result
931}
932
933fn escape_bytes(data: &[u8]) -> String {
935 let mut result = String::with_capacity(data.len() * 2 + 3);
936 result.push_str("X'");
937 for byte in data {
938 result.push_str(&format!("{byte:02X}"));
939 }
940 result.push('\'');
941 result
942}
943
944pub fn format_value_for_sql(value: &Value) -> String {
948 match value {
949 Value::Null => "NULL".to_string(),
950 Value::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
951 Value::TinyInt(i) => i.to_string(),
952 Value::SmallInt(i) => i.to_string(),
953 Value::Int(i) => i.to_string(),
954 Value::BigInt(i) => i.to_string(),
955 Value::Float(f) => {
956 if f.is_nan() {
957 "NULL".to_string()
958 } else if f.is_infinite() {
959 if f.is_sign_positive() {
960 "1e308".to_string() } else {
962 "-1e308".to_string()
963 }
964 } else {
965 f.to_string()
966 }
967 }
968 Value::Double(f) => {
969 if f.is_nan() {
970 "NULL".to_string()
971 } else if f.is_infinite() {
972 if f.is_sign_positive() {
973 "1e308".to_string()
974 } else {
975 "-1e308".to_string()
976 }
977 } else {
978 f.to_string()
979 }
980 }
981 Value::Decimal(s) => s.clone(),
982 Value::Text(s) => escape_string(s),
983 Value::Bytes(b) => escape_bytes(b),
984 Value::Json(j) => escape_string(&j.to_string()),
985 Value::Date(d) => format!("'{}'", d), Value::Time(t) => format!("'{}'", t), Value::Timestamp(t) | Value::TimestampTz(t) => format!("'{}'", t),
988 Value::Uuid(u) => escape_bytes(u),
989 Value::Array(arr) => {
990 let json = serde_json::to_string(arr).unwrap_or_default();
992 escape_string(&json)
993 }
994 Value::Default => "DEFAULT".to_string(),
995 }
996}
997
998pub fn interpolate_params(sql: &str, params: &[Value]) -> String {
1003 if params.is_empty() {
1004 return sql.to_string();
1005 }
1006
1007 let mut result = String::with_capacity(sql.len() + params.len() * 20);
1008 let mut chars = sql.chars().peekable();
1009 let mut param_index = 0;
1010
1011 while let Some(ch) = chars.next() {
1012 match ch {
1013 '?' => {
1015 if param_index < params.len() {
1016 result.push_str(&format_value_for_sql(¶ms[param_index]));
1017 param_index += 1;
1018 } else {
1019 result.push('?');
1020 }
1021 }
1022 '$' => {
1024 let mut num_str = String::new();
1025 while let Some(&next_ch) = chars.peek() {
1026 if next_ch.is_ascii_digit() {
1027 num_str.push(chars.next().unwrap());
1028 } else {
1029 break;
1030 }
1031 }
1032 if num_str.is_empty() {
1033 result.push('$');
1034 } else if let Ok(n) = num_str.parse::<usize>() {
1035 if n > 0 && n <= params.len() {
1036 result.push_str(&format_value_for_sql(¶ms[n - 1]));
1037 } else {
1038 result.push('$');
1039 result.push_str(&num_str);
1040 }
1041 } else {
1042 result.push('$');
1043 result.push_str(&num_str);
1044 }
1045 }
1046 '\'' => {
1048 result.push(ch);
1049 while let Some(next_ch) = chars.next() {
1050 result.push(next_ch);
1051 if next_ch == '\'' {
1052 if chars.peek() == Some(&'\'') {
1054 result.push(chars.next().unwrap());
1055 } else {
1056 break;
1057 }
1058 }
1059 }
1060 }
1061 '"' => {
1063 result.push(ch);
1064 while let Some(next_ch) = chars.next() {
1065 result.push(next_ch);
1066 if next_ch == '"' {
1067 if chars.peek() == Some(&'"') {
1068 result.push(chars.next().unwrap());
1069 } else {
1070 break;
1071 }
1072 }
1073 }
1074 }
1075 '`' => {
1077 result.push(ch);
1078 while let Some(next_ch) = chars.next() {
1079 result.push(next_ch);
1080 if next_ch == '`' {
1081 if chars.peek() == Some(&'`') {
1082 result.push(chars.next().unwrap());
1083 } else {
1084 break;
1085 }
1086 }
1087 }
1088 }
1089 _ => result.push(ch),
1090 }
1091 }
1092
1093 result
1094}
1095
1096#[cfg(test)]
1097mod tests {
1098 use super::*;
1099
1100 #[test]
1101 fn test_escape_string() {
1102 assert_eq!(escape_string("hello"), "'hello'");
1103 assert_eq!(escape_string("it's"), "'it''s'");
1104 assert_eq!(escape_string("a\\b"), "'a\\\\b'");
1105 assert_eq!(escape_string("line\nbreak"), "'line\\nbreak'");
1106 }
1107
1108 #[test]
1109 fn test_format_value() {
1110 assert_eq!(format_value_for_sql(&Value::Null), "NULL");
1111 assert_eq!(format_value_for_sql(&Value::Int(42)), "42");
1112 assert_eq!(
1113 format_value_for_sql(&Value::Text("hello".to_string())),
1114 "'hello'"
1115 );
1116 assert_eq!(format_value_for_sql(&Value::Bool(true)), "TRUE");
1117 }
1118
1119 #[test]
1120 fn test_interpolate_params_question_mark() {
1121 let sql = "SELECT * FROM users WHERE id = ? AND name = ?";
1122 let params = vec![Value::Int(1), Value::Text("Alice".to_string())];
1123 let result = interpolate_params(sql, ¶ms);
1124 assert_eq!(
1125 result,
1126 "SELECT * FROM users WHERE id = 1 AND name = 'Alice'"
1127 );
1128 }
1129
1130 #[test]
1131 fn test_interpolate_params_dollar() {
1132 let sql = "SELECT * FROM users WHERE id = $1 AND name = $2";
1133 let params = vec![Value::Int(1), Value::Text("Alice".to_string())];
1134 let result = interpolate_params(sql, ¶ms);
1135 assert_eq!(
1136 result,
1137 "SELECT * FROM users WHERE id = 1 AND name = 'Alice'"
1138 );
1139 }
1140
1141 #[test]
1142 fn test_interpolate_no_replace_in_string() {
1143 let sql = "SELECT * FROM users WHERE name = '$1' AND id = ?";
1144 let params = vec![Value::Int(42)];
1145 let result = interpolate_params(sql, ¶ms);
1146 assert_eq!(result, "SELECT * FROM users WHERE name = '$1' AND id = 42");
1147 }
1148
1149 #[test]
1150 fn test_field_type_from_u8() {
1151 assert_eq!(FieldType::from_u8(0x01), FieldType::Tiny);
1152 assert_eq!(FieldType::from_u8(0x03), FieldType::Long);
1153 assert_eq!(FieldType::from_u8(0x08), FieldType::LongLong);
1154 assert_eq!(FieldType::from_u8(0xFC), FieldType::Blob);
1155 assert_eq!(FieldType::from_u8(0xF5), FieldType::Json);
1156 }
1157
1158 #[test]
1159 fn test_field_type_categories() {
1160 assert!(FieldType::Tiny.is_integer());
1161 assert!(FieldType::Long.is_integer());
1162 assert!(FieldType::LongLong.is_integer());
1163
1164 assert!(FieldType::Float.is_float());
1165 assert!(FieldType::Double.is_float());
1166
1167 assert!(FieldType::Decimal.is_decimal());
1168 assert!(FieldType::NewDecimal.is_decimal());
1169
1170 assert!(FieldType::VarChar.is_string());
1171 assert!(FieldType::String.is_string());
1172
1173 assert!(FieldType::Blob.is_blob());
1174 assert!(FieldType::TinyBlob.is_blob());
1175
1176 assert!(FieldType::Date.is_temporal());
1177 assert!(FieldType::DateTime.is_temporal());
1178 assert!(FieldType::Timestamp.is_temporal());
1179 }
1180
1181 #[test]
1182 fn test_decode_text_integer() {
1183 let val = decode_text_value(FieldType::Long, b"42", false);
1184 assert!(matches!(val, Value::Int(42)));
1185
1186 let val = decode_text_value(FieldType::LongLong, b"-100", false);
1187 assert!(matches!(val, Value::BigInt(-100)));
1188 }
1189
1190 #[test]
1191 #[allow(clippy::approx_constant)]
1192 fn test_decode_text_float() {
1193 let val = decode_text_value(FieldType::Double, b"3.14", false);
1194 if let Value::Double(f) = val {
1195 assert!((f - 3.14).abs() < 0.001);
1196 } else {
1197 panic!("Expected double");
1198 }
1199 }
1200
1201 #[test]
1202 fn test_decode_text_string() {
1203 let val = decode_text_value(FieldType::VarChar, b"hello", false);
1204 assert!(matches!(val, Value::Text(s) if s == "hello"));
1205 }
1206
1207 #[test]
1208 fn test_decode_binary_tiny() {
1209 let val = decode_binary_value(FieldType::Tiny, &[42], false);
1210 assert!(matches!(val, Value::TinyInt(42)));
1211
1212 let val = decode_binary_value(FieldType::Tiny, &[255u8], true);
1213 assert!(matches!(val, Value::TinyInt(-1))); let val = decode_binary_value(FieldType::Tiny, &[255], false);
1216 assert!(matches!(val, Value::TinyInt(-1)));
1217 }
1218
1219 #[test]
1220 fn test_decode_binary_long() {
1221 let val = decode_binary_value(FieldType::Long, &[0x2A, 0x00, 0x00, 0x00], false);
1222 assert!(matches!(val, Value::Int(42)));
1223 }
1224
1225 #[test]
1226 #[allow(clippy::approx_constant)]
1227 fn test_decode_binary_double() {
1228 let pi_bytes = 3.14159_f64.to_le_bytes();
1229 let val = decode_binary_value(FieldType::Double, &pi_bytes, false);
1230 if let Value::Double(f) = val {
1231 assert!((f - 3.14159).abs() < 0.00001);
1232 } else {
1233 panic!("Expected double");
1234 }
1235 }
1236
1237 #[test]
1238 fn test_column_flags() {
1239 let col = ColumnDef {
1240 catalog: "def".to_string(),
1241 schema: "test".to_string(),
1242 table: "users".to_string(),
1243 org_table: "users".to_string(),
1244 name: "id".to_string(),
1245 org_name: "id".to_string(),
1246 charset: 33,
1247 column_length: 11,
1248 column_type: FieldType::Long,
1249 flags: column_flags::NOT_NULL
1250 | column_flags::PRIMARY_KEY
1251 | column_flags::AUTO_INCREMENT
1252 | column_flags::UNSIGNED,
1253 decimals: 0,
1254 };
1255
1256 assert!(col.is_not_null());
1257 assert!(col.is_primary_key());
1258 assert!(col.is_auto_increment());
1259 assert!(col.is_unsigned());
1260 assert!(!col.is_binary());
1261 }
1262
1263 #[test]
1264 fn test_encode_length_prefixed() {
1265 let result = encode_length_prefixed_bytes(b"hello");
1267 assert_eq!(result[0], 5);
1268 assert_eq!(&result[1..], b"hello");
1269
1270 let result = encode_length_prefixed_bytes(b"");
1272 assert_eq!(result, vec![0]);
1273 }
1274}