1use crate::mysql::protocol::{Column, Reader};
9use crate::value::Value;
10use rustlavel_core::{Json, Result};
11
12pub const DECIMAL: u8 = 0x00;
15pub const TINY: u8 = 0x01;
16pub const SHORT: u8 = 0x02;
17pub const LONG: u8 = 0x03;
18pub const FLOAT: u8 = 0x04;
19pub const DOUBLE: u8 = 0x05;
20pub const NULL: u8 = 0x06;
21pub const TIMESTAMP: u8 = 0x07;
22pub const LONGLONG: u8 = 0x08;
23pub const INT24: u8 = 0x09;
24pub const DATE: u8 = 0x0A;
25pub const TIME: u8 = 0x0B;
26pub const DATETIME: u8 = 0x0C;
27pub const YEAR: u8 = 0x0D;
28pub const VARCHAR: u8 = 0x0F;
29pub const BIT: u8 = 0x10;
30pub const JSON: u8 = 0xF5;
31pub const NEWDECIMAL: u8 = 0xF6;
32pub const ENUM: u8 = 0xF7;
33pub const SET: u8 = 0xF8;
34pub const TINY_BLOB: u8 = 0xF9;
35pub const MEDIUM_BLOB: u8 = 0xFA;
36pub const LONG_BLOB: u8 = 0xFB;
37pub const BLOB: u8 = 0xFC;
38pub const VAR_STRING: u8 = 0xFD;
39pub const STRING: u8 = 0xFE;
40pub const GEOMETRY: u8 = 0xFF;
41
42pub fn decode_text(column: &Column, raw: Option<&[u8]>) -> Value {
56 let Some(bytes) = raw else { return Value::Null };
57
58 match column.column_type {
59 TINY | SHORT | LONG | INT24 | LONGLONG | YEAR => decode_integer_text(bytes, column),
60 DECIMAL | NEWDECIMAL => Value::Text(String::from_utf8_lossy(bytes).into_owned()),
62 FLOAT | DOUBLE => {
63 let text = String::from_utf8_lossy(bytes);
64 text.parse::<f64>().map_or_else(|_| Value::Text(text.into_owned()), Value::Float)
65 }
66 JSON => {
67 let text = String::from_utf8_lossy(bytes);
68 Json::parse(&text).map_or_else(|_| Value::Text(text.into_owned()), Value::Json)
69 }
70 BIT => Value::Int(bits_to_int(bytes)),
71 NULL => Value::Null,
72 _ if column.is_binary() && is_string_type(column.column_type) => {
73 Value::Bytes(bytes.to_vec())
74 }
75 _ => Value::Text(String::from_utf8_lossy(bytes).into_owned()),
76 }
77}
78
79pub fn decode_binary(column: &Column, reader: &mut Reader<'_>) -> Result<Value> {
84 Ok(match column.column_type {
85 NULL => Value::Null,
86 TINY => {
87 let byte = reader.u8()?;
88 if column.is_unsigned() { Value::Int(byte as i64) } else { Value::Int(byte as i8 as i64) }
91 }
92 SHORT | YEAR => {
93 let value = reader.u16()?;
94 if column.is_unsigned() { Value::Int(value as i64) } else { Value::Int(value as i16 as i64) }
95 }
96 LONG | INT24 => {
97 let value = reader.u32()?;
98 if column.is_unsigned() { Value::Int(value as i64) } else { Value::Int(value as i32 as i64) }
99 }
100 LONGLONG => {
101 let value = reader.u64()?;
102 if column.is_unsigned() && value > i64::MAX as u64 {
105 Value::Text(value.to_string())
106 } else {
107 Value::Int(value as i64)
108 }
109 }
110 FLOAT => Value::Float(f32::from_le_bytes(reader.take(4)?.try_into().expect("4 bytes")) as f64),
111 DOUBLE => Value::Float(f64::from_le_bytes(reader.take(8)?.try_into().expect("8 bytes"))),
112 DATE | DATETIME | TIMESTAMP => Value::Text(decode_binary_datetime(reader, column.column_type)?),
113 TIME => Value::Text(decode_binary_time(reader)?),
114 DECIMAL | NEWDECIMAL => {
117 Value::Text(String::from_utf8_lossy(reader.lenenc_bytes()?).into_owned())
118 }
119 JSON => {
120 let bytes = reader.lenenc_bytes()?;
121 let text = String::from_utf8_lossy(bytes);
122 Json::parse(&text).map_or_else(|_| Value::Text(text.into_owned()), Value::Json)
123 }
124 BIT => Value::Int(bits_to_int(reader.lenenc_bytes()?)),
125 _ => {
126 let bytes = reader.lenenc_bytes()?;
127 if column.is_binary() && is_string_type(column.column_type) {
131 Value::Bytes(bytes.to_vec())
132 } else {
133 Value::Text(String::from_utf8_lossy(bytes).into_owned())
134 }
135 }
136 })
137}
138
139pub fn bind_type(value: &Value) -> (u8, bool) {
146 match value {
147 Value::Null => (NULL, false),
148 Value::Bool(_) => (TINY, false),
151 Value::Int(_) => (LONGLONG, false),
152 Value::Float(_) => (DOUBLE, false),
153 Value::Text(_) | Value::Json(_) => (VAR_STRING, false),
154 Value::Bytes(_) => (BLOB, false),
155 }
156}
157
158pub fn encode_bind(value: &Value, out: &mut Vec<u8>) {
163 match value {
164 Value::Null => {}
165 Value::Bool(flag) => out.push(u8::from(*flag)),
166 Value::Int(number) => out.extend_from_slice(&number.to_le_bytes()),
167 Value::Float(number) => out.extend_from_slice(&number.to_le_bytes()),
168 Value::Text(text) => encode_lenenc(text.as_bytes(), out),
169 Value::Json(json) => encode_lenenc(json.to_string().as_bytes(), out),
170 Value::Bytes(bytes) => encode_lenenc(bytes, out),
171 }
172}
173
174pub fn type_name(column_type: u8) -> &'static str {
176 match column_type {
177 DECIMAL | NEWDECIMAL => "decimal",
178 TINY => "tinyint",
179 SHORT => "smallint",
180 LONG => "int",
181 FLOAT => "float",
182 DOUBLE => "double",
183 NULL => "null",
184 TIMESTAMP => "timestamp",
185 LONGLONG => "bigint",
186 INT24 => "mediumint",
187 DATE => "date",
188 TIME => "time",
189 DATETIME => "datetime",
190 YEAR => "year",
191 VARCHAR | VAR_STRING => "varchar",
192 BIT => "bit",
193 JSON => "json",
194 ENUM => "enum",
195 SET => "set",
196 TINY_BLOB | MEDIUM_BLOB | LONG_BLOB | BLOB => "blob",
197 STRING => "char",
198 GEOMETRY => "geometry",
199 _ => "unknown",
200 }
201}
202
203fn is_string_type(column_type: u8) -> bool {
204 matches!(
205 column_type,
206 VARCHAR | VAR_STRING | STRING | TINY_BLOB | MEDIUM_BLOB | LONG_BLOB | BLOB | GEOMETRY
207 )
208}
209
210fn decode_integer_text(bytes: &[u8], column: &Column) -> Value {
211 let text = String::from_utf8_lossy(bytes);
212
213 if column.is_unsigned()
215 && let Ok(large) = text.parse::<u64>()
216 {
217 return if large > i64::MAX as u64 {
218 Value::Text(text.into_owned())
219 } else {
220 Value::Int(large as i64)
221 };
222 }
223
224 text.parse::<i64>().map_or_else(|_| Value::Text(text.into_owned()), Value::Int)
225}
226
227fn bits_to_int(bytes: &[u8]) -> i64 {
229 bytes.iter().fold(0i64, |accumulated, byte| (accumulated << 8) | *byte as i64)
230}
231
232fn decode_binary_datetime(reader: &mut Reader<'_>, column_type: u8) -> Result<String> {
235 let length = reader.u8()?;
236 let date_only = column_type == DATE;
237
238 if length == 0 {
239 return Ok(if date_only { "0000-00-00".into() } else { "0000-00-00 00:00:00".into() });
241 }
242
243 let year = reader.u16()?;
244 let month = reader.u8()?;
245 let day = reader.u8()?;
246 let date = format!("{year:04}-{month:02}-{day:02}");
247
248 if length == 4 {
249 return Ok(if date_only { date } else { format!("{date} 00:00:00") });
250 }
251
252 let hour = reader.u8()?;
253 let minute = reader.u8()?;
254 let second = reader.u8()?;
255 let time = format!("{hour:02}:{minute:02}:{second:02}");
256
257 if length == 7 {
258 return Ok(format!("{date} {time}"));
259 }
260
261 let microseconds = reader.u32()?;
262 Ok(format!("{date} {time}.{microseconds:06}"))
263}
264
265fn decode_binary_time(reader: &mut Reader<'_>) -> Result<String> {
268 let length = reader.u8()?;
269 if length == 0 {
270 return Ok("00:00:00".into());
271 }
272
273 let negative = reader.u8()? == 1;
274 let days = reader.u32()?;
275 let hour = reader.u8()? as u32;
276 let minute = reader.u8()?;
277 let second = reader.u8()?;
278 let sign = if negative { "-" } else { "" };
279 let hours = days * 24 + hour;
280
281 if length == 8 {
282 return Ok(format!("{sign}{hours:02}:{minute:02}:{second:02}"));
283 }
284
285 let microseconds = reader.u32()?;
286 Ok(format!("{sign}{hours:02}:{minute:02}:{second:02}.{microseconds:06}"))
287}
288
289fn encode_lenenc(bytes: &[u8], out: &mut Vec<u8>) {
290 match bytes.len() as u64 {
291 length @ 0..=0xFA => out.push(length as u8),
292 length @ 0xFB..=0xFFFF => {
293 out.push(0xFC);
294 out.extend_from_slice(&(length as u16).to_le_bytes());
295 }
296 length @ 0x1_0000..=0xFF_FFFF => {
297 out.push(0xFD);
298 out.extend_from_slice(&(length as u32).to_le_bytes()[..3]);
299 }
300 length => {
301 out.push(0xFE);
302 out.extend_from_slice(&length.to_le_bytes());
303 }
304 }
305 out.extend_from_slice(bytes);
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::mysql::protocol::{CHARSET_BINARY, CHARSET_UTF8MB4, UNSIGNED_FLAG};
312
313 fn column(column_type: u8) -> Column {
314 Column { column_type, charset: CHARSET_UTF8MB4 as u16, ..Column::default() }
315 }
316
317 fn unsigned(column_type: u8) -> Column {
318 Column { flags: UNSIGNED_FLAG, ..column(column_type) }
319 }
320
321 fn binary(column_type: u8) -> Column {
322 Column { charset: CHARSET_BINARY, ..column(column_type) }
323 }
324
325 #[test]
326 fn decodes_the_scalar_types_from_text() {
327 assert_eq!(decode_text(&column(LONG), Some(b"42")), Value::Int(42));
328 assert_eq!(decode_text(&column(LONGLONG), Some(b"-7")), Value::Int(-7));
329 assert_eq!(decode_text(&column(DOUBLE), Some(b"1.5")), Value::Float(1.5));
330 assert_eq!(
331 decode_text(&column(VAR_STRING), Some(b"hello")),
332 Value::Text("hello".into())
333 );
334 }
335
336 #[test]
337 fn a_null_column_decodes_to_null_whatever_its_type() {
338 assert_eq!(decode_text(&column(LONG), None), Value::Null);
339 assert_eq!(decode_text(&column(VAR_STRING), None), Value::Null);
340 }
341
342 #[test]
343 fn a_tinyint_one_stays_an_integer_because_the_dialect_says_so() {
344 use crate::dialect::{Dialect, MySql};
348 assert!(MySql.booleans_are_integers());
349
350 assert_eq!(decode_text(&column(TINY), Some(b"1")), Value::Int(1));
351 assert_eq!(decode_text(&column(TINY), Some(b"0")), Value::Int(0));
352
353 let mut reader = Reader::new(&[1]);
354 assert_eq!(decode_binary(&column(TINY), &mut reader).unwrap(), Value::Int(1));
355 }
356
357 #[test]
358 fn decimals_and_timestamps_stay_text_so_precision_survives() {
359 assert_eq!(
360 decode_text(&column(NEWDECIMAL), Some(b"12345.678901234567890")),
361 Value::Text("12345.678901234567890".into())
362 );
363 assert_eq!(
364 decode_text(&column(DATETIME), Some(b"2026-08-29 10:00:00.123456")),
365 Value::Text("2026-08-29 10:00:00.123456".into())
366 );
367 assert_eq!(
368 decode_text(&column(DATE), Some(b"2026-08-29")),
369 Value::Text("2026-08-29".into())
370 );
371 }
372
373 #[test]
374 fn a_decimal_is_text_even_though_the_server_calls_it_binary() {
375 let money = Column { charset: CHARSET_BINARY, ..column(NEWDECIMAL) };
378
379 assert_eq!(decode_text(&money, Some(b"12345.6789")), Value::Text("12345.6789".into()));
380
381 let mut reader = Reader::new(b"\x0a12345.6789");
382 assert_eq!(
383 decode_binary(&money, &mut reader).unwrap(),
384 Value::Text("12345.6789".into())
385 );
386 }
387
388 #[test]
389 fn decodes_json_columns_into_parsed_values() {
390 match decode_text(&column(JSON), Some(br#"{"a":1}"#)) {
391 Value::Json(json) => assert_eq!(json.get("a").unwrap().as_i64(), Some(1)),
392 other => panic!("expected parsed JSON, got {other:?}"),
393 }
394 }
395
396 #[test]
397 fn a_binary_collation_makes_a_string_column_bytes() {
398 assert_eq!(
399 decode_text(&binary(BLOB), Some(&[0xDE, 0xAD])),
400 Value::Bytes(vec![0xDE, 0xAD])
401 );
402 assert_eq!(decode_text(&column(BLOB), Some(b"note")), Value::Text("note".into()));
404 }
405
406 #[test]
407 fn signedness_comes_from_the_column_flag_not_the_type() {
408 let mut reader = Reader::new(&[0xFF]);
409 assert_eq!(decode_binary(&column(TINY), &mut reader).unwrap(), Value::Int(-1));
410
411 let mut reader = Reader::new(&[0xFF]);
412 assert_eq!(decode_binary(&unsigned(TINY), &mut reader).unwrap(), Value::Int(255));
413
414 assert_eq!(decode_text(&unsigned(LONGLONG), Some(b"255")), Value::Int(255));
415 }
416
417 #[test]
418 fn an_unsigned_bigint_too_large_for_i64_stays_text_rather_than_wrapping() {
419 let huge = u64::MAX;
420 assert_eq!(
421 decode_text(&unsigned(LONGLONG), Some(huge.to_string().as_bytes())),
422 Value::Text(huge.to_string())
423 );
424
425 let bytes = huge.to_le_bytes();
426 let mut reader = Reader::new(&bytes);
427 assert_eq!(
428 decode_binary(&unsigned(LONGLONG), &mut reader).unwrap(),
429 Value::Text(huge.to_string())
430 );
431 }
432
433 #[test]
434 fn decodes_binary_integers_and_floats() {
435 let big = 9_000_000_000i64.to_le_bytes();
436 let mut reader = Reader::new(&big);
437 assert_eq!(
438 decode_binary(&column(LONGLONG), &mut reader).unwrap(),
439 Value::Int(9_000_000_000)
440 );
441
442 let double = 1.5f64.to_le_bytes();
443 let mut reader = Reader::new(&double);
444 assert_eq!(decode_binary(&column(DOUBLE), &mut reader).unwrap(), Value::Float(1.5));
445
446 let single = 0.5f32.to_le_bytes();
447 let mut reader = Reader::new(&single);
448 assert_eq!(decode_binary(&column(FLOAT), &mut reader).unwrap(), Value::Float(0.5));
449 }
450
451 #[test]
452 fn decodes_a_binary_datetime_at_each_of_its_lengths() {
453 let mut reader = Reader::new(&[0]);
455 assert_eq!(
456 decode_binary(&column(DATETIME), &mut reader).unwrap(),
457 Value::Text("0000-00-00 00:00:00".into())
458 );
459
460 let date = [4u8, 0xEA, 0x07, 8, 29];
462 let mut reader = Reader::new(&date);
463 assert_eq!(
464 decode_binary(&column(DATE), &mut reader).unwrap(),
465 Value::Text("2026-08-29".into())
466 );
467 let mut reader = Reader::new(&date);
468 assert_eq!(
469 decode_binary(&column(DATETIME), &mut reader).unwrap(),
470 Value::Text("2026-08-29 00:00:00".into())
471 );
472
473 let mut reader = Reader::new(&[7u8, 0xEA, 0x07, 8, 29, 10, 30, 5]);
475 assert_eq!(
476 decode_binary(&column(TIMESTAMP), &mut reader).unwrap(),
477 Value::Text("2026-08-29 10:30:05".into())
478 );
479
480 let mut full = vec![11u8, 0xEA, 0x07, 8, 29, 10, 30, 5];
482 full.extend_from_slice(&123_456u32.to_le_bytes());
483 let mut reader = Reader::new(&full);
484 assert_eq!(
485 decode_binary(&column(DATETIME), &mut reader).unwrap(),
486 Value::Text("2026-08-29 10:30:05.123456".into())
487 );
488 }
489
490 #[test]
491 fn a_binary_time_is_a_duration_so_it_can_be_negative_and_pass_a_day() {
492 let mut payload = vec![8u8, 1];
493 payload.extend_from_slice(&2u32.to_le_bytes()); payload.extend_from_slice(&[3, 4, 5]);
495
496 let mut reader = Reader::new(&payload);
497 assert_eq!(
498 decode_binary(&column(TIME), &mut reader).unwrap(),
499 Value::Text("-51:04:05".into())
500 );
501 }
502
503 #[test]
504 fn a_bit_column_reads_as_the_number_its_bits_spell() {
505 assert_eq!(decode_text(&column(BIT), Some(&[0x01, 0x00])), Value::Int(256));
506
507 let mut reader = Reader::new(&[2, 0x01, 0x00]);
508 assert_eq!(decode_binary(&column(BIT), &mut reader).unwrap(), Value::Int(256));
509 }
510
511 #[test]
512 fn binds_each_value_as_the_widest_type_that_holds_it() {
513 assert_eq!(bind_type(&Value::Null), (NULL, false));
514 assert_eq!(bind_type(&Value::Bool(true)), (TINY, false));
515 assert_eq!(bind_type(&Value::Int(1)), (LONGLONG, false));
516 assert_eq!(bind_type(&Value::Float(1.0)), (DOUBLE, false));
517 assert_eq!(bind_type(&Value::Text("a".into())), (VAR_STRING, false));
518 assert_eq!(bind_type(&Value::Bytes(vec![1])), (BLOB, false));
519 }
520
521 #[test]
522 fn encodes_bound_parameters_in_binary() {
523 let mut out = Vec::new();
524 encode_bind(&Value::Int(42), &mut out);
525 assert_eq!(out, 42i64.to_le_bytes());
526
527 let mut out = Vec::new();
528 encode_bind(&Value::Text("ada".into()), &mut out);
529 assert_eq!(out, b"\x03ada");
530
531 let mut out = Vec::new();
532 encode_bind(&Value::Bool(true), &mut out);
533 assert_eq!(out, [1]);
534
535 let mut out = Vec::new();
537 encode_bind(&Value::Null, &mut out);
538 assert!(out.is_empty());
539 }
540
541 #[test]
542 fn a_bound_string_longer_than_a_byte_length_still_encodes() {
543 let long = "x".repeat(300);
544 let mut out = Vec::new();
545 encode_bind(&Value::Text(long.clone()), &mut out);
546
547 assert_eq!(out[0], 0xFC);
548 assert_eq!(u16::from_le_bytes([out[1], out[2]]), 300);
549 assert_eq!(&out[3..], long.as_bytes());
550 }
551
552 #[test]
553 fn a_hostile_string_is_encoded_as_data_not_as_syntax() {
554 let hostile = "'; drop table users; --";
557 let mut out = Vec::new();
558 encode_bind(&Value::Text(hostile.into()), &mut out);
559
560 assert_eq!(out[0] as usize, hostile.len());
561 assert_eq!(&out[1..], hostile.as_bytes());
562 }
563
564 #[test]
565 fn names_the_types_it_knows() {
566 assert_eq!(type_name(LONGLONG), "bigint");
567 assert_eq!(type_name(NEWDECIMAL), "decimal");
568 assert_eq!(type_name(JSON), "json");
569 assert_eq!(type_name(0x77), "unknown");
570 }
571}