1use std::net::Ipv4Addr;
52
53use crate::egress::column_kind::ColumnKind;
54use crate::egress::wire::varint;
55use crate::error::{Result, fmt};
56
57pub const DECIMAL64_MAX_SCALE: i8 = 18;
101pub const DECIMAL128_MAX_SCALE: i8 = 38;
102pub const DECIMAL256_MAX_SCALE: i8 = 76;
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[non_exhaustive]
116pub enum SimpleNullKind {
117 Boolean,
118 Byte,
119 Short,
120 Int,
121 Long,
122 Float,
123 Double,
124 Timestamp,
125 TimestampNanos,
126 Date,
127 Uuid,
128 Long256,
129 Char,
130 Ipv4,
131}
132
133impl SimpleNullKind {
134 pub fn as_column_kind(self) -> ColumnKind {
136 match self {
137 SimpleNullKind::Boolean => ColumnKind::Boolean,
138 SimpleNullKind::Byte => ColumnKind::Byte,
139 SimpleNullKind::Short => ColumnKind::Short,
140 SimpleNullKind::Int => ColumnKind::Int,
141 SimpleNullKind::Long => ColumnKind::Long,
142 SimpleNullKind::Float => ColumnKind::Float,
143 SimpleNullKind::Double => ColumnKind::Double,
144 SimpleNullKind::Timestamp => ColumnKind::Timestamp,
145 SimpleNullKind::TimestampNanos => ColumnKind::TimestampNanos,
146 SimpleNullKind::Date => ColumnKind::Date,
147 SimpleNullKind::Uuid => ColumnKind::Uuid,
148 SimpleNullKind::Long256 => ColumnKind::Long256,
149 SimpleNullKind::Char => ColumnKind::Char,
150 SimpleNullKind::Ipv4 => ColumnKind::Ipv4,
151 }
152 }
153}
154
155impl TryFrom<ColumnKind> for SimpleNullKind {
156 type Error = ColumnKind;
157
158 fn try_from(k: ColumnKind) -> std::result::Result<Self, Self::Error> {
162 Ok(match k {
163 ColumnKind::Boolean => SimpleNullKind::Boolean,
164 ColumnKind::Byte => SimpleNullKind::Byte,
165 ColumnKind::Short => SimpleNullKind::Short,
166 ColumnKind::Int => SimpleNullKind::Int,
167 ColumnKind::Long => SimpleNullKind::Long,
168 ColumnKind::Float => SimpleNullKind::Float,
169 ColumnKind::Double => SimpleNullKind::Double,
170 ColumnKind::Timestamp => SimpleNullKind::Timestamp,
171 ColumnKind::TimestampNanos => SimpleNullKind::TimestampNanos,
172 ColumnKind::Date => SimpleNullKind::Date,
173 ColumnKind::Uuid => SimpleNullKind::Uuid,
174 ColumnKind::Long256 => SimpleNullKind::Long256,
175 ColumnKind::Char => SimpleNullKind::Char,
176 ColumnKind::Ipv4 => SimpleNullKind::Ipv4,
177 other => return Err(other),
178 })
179 }
180}
181
182#[derive(Debug, Clone, PartialEq)]
194#[non_exhaustive]
195pub enum Bind {
196 Null(SimpleNullKind),
202 NullVarchar,
204 NullBinary,
206 NullDecimal64 {
208 scale: i8,
209 },
210 NullDecimal128 {
212 scale: i8,
213 },
214 NullDecimal256 {
216 scale: i8,
217 },
218 NullGeohash {
220 precision_bits: u8,
221 },
222
223 Bool(bool),
225 I8(i8),
227 I16(i16),
229 I32(i32),
231 I64(i64),
233 F32(f32),
234 F64(f64),
235 Varchar(String),
236 Binary(Vec<u8>),
237 TimestampMicros(i64),
239 TimestampNanos(i64),
241 DateMillis(i64),
243 Uuid([u8; 16]),
245 Long256([u8; 32]),
247 Char(u16),
249 Ipv4(Ipv4Addr),
250 Decimal64 {
252 value: i64,
253 scale: i8,
254 },
255 Decimal128 {
257 value: i128,
258 scale: i8,
259 },
260 Decimal256 {
262 bytes: [u8; 32],
263 scale: i8,
264 },
265 Geohash {
268 value: u64,
269 precision_bits: u8,
270 },
271}
272
273impl Bind {
274 pub fn kind(&self) -> ColumnKind {
276 match self {
277 Bind::Null(s) => s.as_column_kind(),
278 Bind::NullVarchar => ColumnKind::Varchar,
279 Bind::NullBinary => ColumnKind::Binary,
280 Bind::NullDecimal64 { .. } => ColumnKind::Decimal64,
281 Bind::NullDecimal128 { .. } => ColumnKind::Decimal128,
282 Bind::NullDecimal256 { .. } => ColumnKind::Decimal256,
283 Bind::NullGeohash { .. } => ColumnKind::Geohash,
284 Bind::Bool(_) => ColumnKind::Boolean,
285 Bind::I8(_) => ColumnKind::Byte,
286 Bind::I16(_) => ColumnKind::Short,
287 Bind::I32(_) => ColumnKind::Int,
288 Bind::I64(_) => ColumnKind::Long,
289 Bind::F32(_) => ColumnKind::Float,
290 Bind::F64(_) => ColumnKind::Double,
291 Bind::Varchar(_) => ColumnKind::Varchar,
292 Bind::Binary(_) => ColumnKind::Binary,
293 Bind::TimestampMicros(_) => ColumnKind::Timestamp,
294 Bind::TimestampNanos(_) => ColumnKind::TimestampNanos,
295 Bind::DateMillis(_) => ColumnKind::Date,
296 Bind::Uuid(_) => ColumnKind::Uuid,
297 Bind::Long256(_) => ColumnKind::Long256,
298 Bind::Char(_) => ColumnKind::Char,
299 Bind::Ipv4(_) => ColumnKind::Ipv4,
300 Bind::Decimal64 { .. } => ColumnKind::Decimal64,
301 Bind::Decimal128 { .. } => ColumnKind::Decimal128,
302 Bind::Decimal256 { .. } => ColumnKind::Decimal256,
303 Bind::Geohash { .. } => ColumnKind::Geohash,
304 }
305 }
306
307 fn is_null(&self) -> bool {
308 matches!(
309 self,
310 Bind::Null(_)
311 | Bind::NullVarchar
312 | Bind::NullBinary
313 | Bind::NullDecimal64 { .. }
314 | Bind::NullDecimal128 { .. }
315 | Bind::NullDecimal256 { .. }
316 | Bind::NullGeohash { .. }
317 )
318 }
319}
320
321pub fn encode_bind(bind: &Bind, out: &mut Vec<u8>) -> Result<()> {
323 out.push(bind.kind().as_u8());
330
331 let null = bind.is_null();
332 if null {
333 out.push(0x01); out.push(0x01); } else {
336 out.push(0x00);
337 }
338
339 match bind {
342 Bind::Decimal64 { scale, .. }
344 | Bind::Decimal128 { scale, .. }
345 | Bind::Decimal256 { scale, .. }
346 | Bind::NullDecimal64 { scale }
347 | Bind::NullDecimal128 { scale }
348 | Bind::NullDecimal256 { scale } => {
349 let max_scale = match bind {
350 Bind::Decimal64 { .. } | Bind::NullDecimal64 { .. } => DECIMAL64_MAX_SCALE,
351 Bind::Decimal128 { .. } | Bind::NullDecimal128 { .. } => DECIMAL128_MAX_SCALE,
352 _ => DECIMAL256_MAX_SCALE,
353 };
354 if *scale < 0 || *scale > max_scale {
355 return Err(fmt!(
356 InvalidBind,
357 "decimal scale {} outside 0..={}",
358 scale,
359 max_scale
360 ));
361 }
362 out.push(*scale as u8);
363 }
364 Bind::Geohash { precision_bits, .. } | Bind::NullGeohash { precision_bits } => {
366 if *precision_bits == 0 || *precision_bits > 60 {
367 return Err(fmt!(
368 InvalidBind,
369 "geohash precision_bits {} outside 1..=60",
370 precision_bits
371 ));
372 }
373 if let Bind::Geohash {
374 value,
375 precision_bits,
376 } = bind
377 {
378 if value >> precision_bits != 0 {
382 return Err(fmt!(
383 InvalidBind,
384 "geohash value 0x{:X} has bits set above precision_bits {}",
385 value,
386 precision_bits
387 ));
388 }
389 }
390 varint::encode_u64(*precision_bits as u64, out);
391 }
392 Bind::Varchar(s) => write_varlen_offsets(&[s.len()], out)?,
398 Bind::Binary(b) => write_varlen_offsets(&[b.len()], out)?,
399 _ => {}
400 }
401
402 if null {
403 return Ok(());
404 }
405
406 match bind {
408 Bind::Null(_)
409 | Bind::NullVarchar
410 | Bind::NullBinary
411 | Bind::NullDecimal64 { .. }
412 | Bind::NullDecimal128 { .. }
413 | Bind::NullDecimal256 { .. }
414 | Bind::NullGeohash { .. } => unreachable!("handled above"),
415
416 Bind::Bool(v) => out.push(if *v { 0x01 } else { 0x00 }),
418 Bind::I8(v) => out.push(*v as u8),
419 Bind::I16(v) => out.extend_from_slice(&v.to_le_bytes()),
420 Bind::I32(v) => out.extend_from_slice(&v.to_le_bytes()),
421 Bind::I64(v) => out.extend_from_slice(&v.to_le_bytes()),
422 Bind::F32(v) => out.extend_from_slice(&v.to_le_bytes()),
423 Bind::F64(v) => out.extend_from_slice(&v.to_le_bytes()),
424 Bind::Char(v) => out.extend_from_slice(&v.to_le_bytes()),
425 Bind::TimestampMicros(v) | Bind::TimestampNanos(v) | Bind::DateMillis(v) => {
426 out.extend_from_slice(&v.to_le_bytes());
427 }
428 Bind::Uuid(b) => out.extend_from_slice(b),
429 Bind::Long256(b) => out.extend_from_slice(b),
430 Bind::Ipv4(addr) => out.extend_from_slice(&u32::from(*addr).to_le_bytes()),
436 Bind::Decimal64 { value, .. } => out.extend_from_slice(&value.to_le_bytes()),
437 Bind::Decimal128 { value, .. } => out.extend_from_slice(&value.to_le_bytes()),
438 Bind::Decimal256 { bytes, .. } => out.extend_from_slice(bytes),
439 Bind::Geohash {
440 value,
441 precision_bits,
442 } => {
443 let bw = (*precision_bits as usize).div_ceil(8);
444 let bytes = value.to_le_bytes();
445 out.extend_from_slice(&bytes[..bw]);
446 }
447 Bind::Varchar(s) => out.extend_from_slice(s.as_bytes()),
448 Bind::Binary(b) => out.extend_from_slice(b),
449 }
450
451 Ok(())
452}
453
454fn write_varlen_offsets(byte_lens: &[usize], out: &mut Vec<u8>) -> Result<()> {
455 let mut total: u32 = 0;
456 out.extend_from_slice(&total.to_le_bytes());
457 for &len in byte_lens {
458 let len32 = u32::try_from(len)
459 .map_err(|_| fmt!(InvalidBind, "varlen bind value too large: {} bytes", len))?;
460 total = total
461 .checked_add(len32)
462 .ok_or_else(|| fmt!(InvalidBind, "varlen bind offsets overflow u32"))?;
463 out.extend_from_slice(&total.to_le_bytes());
464 }
465 Ok(())
466}
467
468pub fn check_bindable(kind: ColumnKind) -> Result<()> {
477 match kind {
478 ColumnKind::Symbol
479 | ColumnKind::Binary
480 | ColumnKind::Ipv4
481 | ColumnKind::DoubleArray
482 | ColumnKind::LongArray => Err(fmt!(
483 InvalidBind,
484 "bind not supported for type {} (0x{:02X})",
485 kind.name(),
486 kind.as_u8()
487 )),
488 _ => Ok(()),
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 fn enc(b: Bind) -> Vec<u8> {
497 let mut out = Vec::new();
498 encode_bind(&b, &mut out).unwrap();
499 out
500 }
501
502 #[test]
505 fn simple_null_layout() {
506 assert_eq!(
508 enc(Bind::Null(SimpleNullKind::Long)),
509 vec![0x05, 0x01, 0x01]
510 );
511 }
512
513 #[test]
514 fn bool_layout() {
515 assert_eq!(enc(Bind::Bool(true)), vec![0x01, 0x00, 0x01]);
516 assert_eq!(enc(Bind::Bool(false)), vec![0x01, 0x00, 0x00]);
517 }
518
519 #[test]
520 fn i32_le() {
521 assert_eq!(
522 enc(Bind::I32(0x01020304)),
523 vec![0x04, 0x00, 0x04, 0x03, 0x02, 0x01]
524 );
525 }
526
527 #[test]
528 fn i64_le() {
529 assert_eq!(
530 enc(Bind::I64(0x0102_0304_0506_0708)),
531 vec![0x05, 0x00, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
532 );
533 }
534
535 #[test]
536 fn f64_le() {
537 let mut expected = vec![0x07, 0x00];
538 expected.extend_from_slice(&1.0f64.to_le_bytes());
539 assert_eq!(enc(Bind::F64(1.0)), expected);
540 }
541
542 #[test]
543 fn ipv4_le() {
544 let bytes = enc(Bind::Ipv4(Ipv4Addr::new(192, 168, 1, 1)));
545 assert_eq!(bytes, vec![0x18, 0x00, 0x01, 0x01, 0xA8, 0xC0]);
546 }
547
548 #[test]
549 fn uuid_passthrough() {
550 let raw = [
551 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
552 0x0F, 0x10,
553 ];
554 let bytes = enc(Bind::Uuid(raw));
555 assert_eq!(bytes[0], 0x0C);
556 assert_eq!(bytes[1], 0x00);
557 assert_eq!(&bytes[2..], &raw);
558 }
559
560 #[test]
561 fn long256_passthrough() {
562 let raw: [u8; 32] = std::array::from_fn(|i| i as u8);
563 let bytes = enc(Bind::Long256(raw));
564 assert_eq!(bytes[0], 0x0D);
565 assert_eq!(bytes[1], 0x00);
566 assert_eq!(&bytes[2..], &raw);
567 }
568
569 #[test]
570 fn char_layout() {
571 assert_eq!(enc(Bind::Char(b'A' as u16)), vec![0x16, 0x00, 0x41, 0x00]);
573 }
574
575 #[test]
578 fn decimal64_value_layout() {
579 let bytes = enc(Bind::Decimal64 {
580 value: 12345,
581 scale: 2,
582 });
583 assert_eq!(bytes[0], 0x13);
584 assert_eq!(bytes[1], 0x00);
585 assert_eq!(bytes[2], 0x02);
586 assert_eq!(&bytes[3..], &12345i64.to_le_bytes());
587 }
588
589 #[test]
590 fn decimal64_null_carries_scale() {
591 assert_eq!(
593 enc(Bind::NullDecimal64 { scale: 4 }),
594 vec![0x13, 0x01, 0x01, 0x04]
595 );
596 }
597
598 #[test]
599 fn decimal_scale_negative_rejected() {
600 for bind in [
604 Bind::Decimal64 {
605 value: 0,
606 scale: -1,
607 },
608 Bind::Decimal128 {
609 value: 0,
610 scale: -1,
611 },
612 Bind::Decimal256 {
613 bytes: [0; 32],
614 scale: -1,
615 },
616 Bind::NullDecimal64 { scale: -1 },
617 Bind::NullDecimal128 { scale: -1 },
618 Bind::NullDecimal256 { scale: -1 },
619 ] {
620 let mut out = Vec::new();
621 let err = encode_bind(&bind, &mut out).unwrap_err();
622 assert_eq!(err.code(), crate::ErrorCode::InvalidBind);
623 assert!(
624 err.msg().contains("decimal scale"),
625 "expected scale error msg, got: {}",
626 err.msg()
627 );
628 }
629 }
630
631 #[test]
632 fn decimal_scale_above_max_rejected() {
633 for bind in [
636 Bind::Decimal64 {
637 value: 0,
638 scale: DECIMAL64_MAX_SCALE + 1,
639 },
640 Bind::NullDecimal128 {
641 scale: DECIMAL128_MAX_SCALE + 1,
642 },
643 Bind::NullDecimal256 {
644 scale: DECIMAL256_MAX_SCALE + 1,
645 },
646 ] {
647 let mut out = Vec::new();
648 let err = encode_bind(&bind, &mut out).unwrap_err();
649 assert_eq!(err.code(), crate::ErrorCode::InvalidBind);
650 }
651 }
652
653 #[test]
654 fn decimal_scale_at_boundaries_accepted() {
655 let cases = [
657 Bind::NullDecimal64 { scale: 0 },
658 Bind::NullDecimal64 {
659 scale: DECIMAL64_MAX_SCALE,
660 },
661 Bind::NullDecimal128 {
662 scale: DECIMAL128_MAX_SCALE,
663 },
664 Bind::NullDecimal256 {
665 scale: DECIMAL256_MAX_SCALE,
666 },
667 ];
668 for bind in cases {
669 let scale = match bind {
670 Bind::NullDecimal64 { scale }
671 | Bind::NullDecimal128 { scale }
672 | Bind::NullDecimal256 { scale } => scale,
673 _ => unreachable!(),
674 };
675 let mut out = Vec::new();
676 encode_bind(&bind, &mut out).unwrap();
677 assert_eq!(out.last().copied(), Some(scale as u8));
678 }
679 }
680
681 #[test]
682 fn decimal128_value_layout() {
683 let bytes = enc(Bind::Decimal128 {
684 value: -42,
685 scale: 6,
686 });
687 assert_eq!(bytes[0], 0x14);
688 assert_eq!(bytes[1], 0x00);
689 assert_eq!(bytes[2], 0x06);
690 assert_eq!(&bytes[3..], &(-42i128).to_le_bytes());
691 }
692
693 #[test]
694 fn decimal128_null_carries_scale() {
695 assert_eq!(
696 enc(Bind::NullDecimal128 { scale: 8 }),
697 vec![0x14, 0x01, 0x01, 0x08]
698 );
699 }
700
701 #[test]
702 fn decimal256_value_layout() {
703 let raw: [u8; 32] = std::array::from_fn(|i| (i + 1) as u8);
704 let bytes = enc(Bind::Decimal256 {
705 bytes: raw,
706 scale: 12,
707 });
708 assert_eq!(bytes[0], 0x15);
709 assert_eq!(bytes[1], 0x00);
710 assert_eq!(bytes[2], 0x0C);
711 assert_eq!(&bytes[3..], &raw);
712 }
713
714 #[test]
715 fn decimal256_null_carries_scale() {
716 assert_eq!(
717 enc(Bind::NullDecimal256 { scale: 18 }),
718 vec![0x15, 0x01, 0x01, 0x12]
719 );
720 }
721
722 #[test]
725 fn geohash_value_layout() {
726 let bytes = enc(Bind::Geohash {
728 value: 0xAB,
729 precision_bits: 8,
730 });
731 assert_eq!(bytes, vec![0x0E, 0x00, 0x08, 0xAB]);
732 }
733
734 #[test]
735 fn geohash_60_bits_writes_8_bytes() {
736 let bytes = enc(Bind::Geohash {
737 value: 0x0102_0304_0506_0708,
738 precision_bits: 60,
739 });
740 let mut expected = vec![0x0E, 0x00, 0x3C];
742 expected.extend_from_slice(&0x0102_0304_0506_0708u64.to_le_bytes());
743 assert_eq!(bytes, expected);
744 }
745
746 #[test]
747 fn geohash_null_carries_precision() {
748 assert_eq!(
750 enc(Bind::NullGeohash { precision_bits: 20 }),
751 vec![0x0E, 0x01, 0x01, 0x14]
752 );
753 }
754
755 #[test]
756 fn geohash_invalid_precision_rejected() {
757 let mut out = Vec::new();
758 let err = encode_bind(
759 &Bind::Geohash {
760 value: 0,
761 precision_bits: 0,
762 },
763 &mut out,
764 )
765 .unwrap_err();
766 assert_eq!(err.code(), crate::ErrorCode::InvalidBind);
767 }
768
769 #[test]
770 fn geohash_value_above_precision_rejected() {
771 let mut out = Vec::new();
772 let err = encode_bind(
773 &Bind::Geohash {
774 value: u64::MAX,
775 precision_bits: 8,
776 },
777 &mut out,
778 )
779 .unwrap_err();
780 assert_eq!(err.code(), crate::ErrorCode::InvalidBind);
781 }
782
783 #[test]
786 fn varchar_value_layout() {
787 let bytes = enc(Bind::Varchar("hi".into()));
788 let expected = vec![0x0F, 0x00, 0, 0, 0, 0, 2, 0, 0, 0, b'h', b'i'];
790 assert_eq!(bytes, expected);
791 }
792
793 #[test]
794 fn varchar_null_emits_no_offsets_array() {
795 assert_eq!(enc(Bind::NullVarchar), vec![0x0F, 0x01, 0x01]);
799 }
800
801 #[test]
802 fn binary_value_layout() {
803 let bytes = enc(Bind::Binary(vec![0xDE, 0xAD]));
804 let expected = vec![0x17, 0x00, 0, 0, 0, 0, 2, 0, 0, 0, 0xDE, 0xAD];
806 assert_eq!(bytes, expected);
807 }
808
809 #[test]
810 fn binary_null_emits_no_offsets_array() {
811 assert_eq!(enc(Bind::NullBinary), vec![0x17, 0x01, 0x01]);
813 }
814
815 #[test]
816 fn null_varchar_then_i32_concatenates_cleanly() {
817 let mut out = Vec::new();
821 encode_bind(&Bind::NullVarchar, &mut out).unwrap();
822 encode_bind(&Bind::I32(7), &mut out).unwrap();
823 assert_eq!(
825 out,
826 vec![0x0F, 0x01, 0x01, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00]
827 );
828 }
829
830 #[test]
833 fn check_bindable_rejects_server_unsupported() {
834 assert!(check_bindable(ColumnKind::Symbol).is_err());
836 assert!(check_bindable(ColumnKind::Binary).is_err());
837 assert!(check_bindable(ColumnKind::Ipv4).is_err());
838 assert!(check_bindable(ColumnKind::DoubleArray).is_err());
839 assert!(check_bindable(ColumnKind::LongArray).is_err());
840 }
841
842 #[test]
843 fn check_bindable_accepts_remaining_types() {
844 for k in [
845 ColumnKind::Boolean,
846 ColumnKind::Byte,
847 ColumnKind::Short,
848 ColumnKind::Int,
849 ColumnKind::Long,
850 ColumnKind::Float,
851 ColumnKind::Double,
852 ColumnKind::Timestamp,
853 ColumnKind::TimestampNanos,
854 ColumnKind::Date,
855 ColumnKind::Uuid,
856 ColumnKind::Long256,
857 ColumnKind::Char,
858 ColumnKind::Varchar,
859 ColumnKind::Decimal64,
860 ColumnKind::Decimal128,
861 ColumnKind::Decimal256,
862 ColumnKind::Geohash,
863 ] {
864 check_bindable(k).unwrap_or_else(|_| panic!("{}", k.name()));
865 }
866 }
867
868 #[test]
869 fn simple_null_kind_try_from_rejects_kinds_with_column_args() {
870 for kind in [
878 ColumnKind::Varchar,
879 ColumnKind::Binary,
880 ColumnKind::Decimal64,
881 ColumnKind::Decimal128,
882 ColumnKind::Decimal256,
883 ColumnKind::Geohash,
884 ColumnKind::Symbol,
885 ColumnKind::DoubleArray,
886 ColumnKind::LongArray,
887 ] {
888 let r = SimpleNullKind::try_from(kind);
889 assert!(
890 r.is_err(),
891 "{} must not convert to SimpleNullKind",
892 kind.name()
893 );
894 }
895 }
896
897 #[test]
898 fn null_bind_accepts_simple_kinds() {
899 for kind in [
900 SimpleNullKind::Boolean,
901 SimpleNullKind::Byte,
902 SimpleNullKind::Short,
903 SimpleNullKind::Int,
904 SimpleNullKind::Long,
905 SimpleNullKind::Float,
906 SimpleNullKind::Double,
907 SimpleNullKind::Timestamp,
908 SimpleNullKind::TimestampNanos,
909 SimpleNullKind::Date,
910 SimpleNullKind::Uuid,
911 SimpleNullKind::Long256,
912 SimpleNullKind::Char,
913 SimpleNullKind::Ipv4,
914 ] {
915 let mut out = Vec::new();
916 encode_bind(&Bind::Null(kind), &mut out).unwrap_or_else(|_| {
917 panic!("Bind::Null({}) should encode", kind.as_column_kind().name())
918 });
919 assert_eq!(out, vec![kind.as_column_kind().as_u8(), 0x01, 0x01]);
921 }
922 }
923
924 #[test]
925 fn null_bind_kind_preserved() {
926 assert_eq!(
927 Bind::NullDecimal64 { scale: 0 }.kind(),
928 ColumnKind::Decimal64
929 );
930 assert_eq!(Bind::NullVarchar.kind(), ColumnKind::Varchar);
931 assert_eq!(
932 Bind::NullGeohash { precision_bits: 8 }.kind(),
933 ColumnKind::Geohash
934 );
935 }
936
937 mod fuzz {
949 use super::*;
950 use proptest::prelude::*;
951
952 fn body_of_non_null(expected_kind: ColumnKind, encoded: &[u8]) -> &[u8] {
957 assert!(encoded.len() >= 2, "encoded bind too short");
958 assert_eq!(
959 encoded[0],
960 expected_kind.as_u8(),
961 "type code mismatch: encoded={:02x} expected={:02x} ({})",
962 encoded[0],
963 expected_kind.as_u8(),
964 expected_kind.name()
965 );
966 assert_eq!(encoded[1], 0x00, "null_flag must be 0x00 for non-null bind");
967 &encoded[2..]
968 }
969
970 proptest! {
974 #![proptest_config(ProptestConfig {
975 cases: 200,
976 .. ProptestConfig::default()
977 })]
978
979 #[test]
980 fn fuzz_bool(v: bool) {
981 let bytes = enc(Bind::Bool(v));
982 let body = body_of_non_null(ColumnKind::Boolean, &bytes);
983 prop_assert_eq!(body, &[v as u8][..]);
984 }
985
986 #[test]
987 fn fuzz_i8(v: i8) {
988 let bytes = enc(Bind::I8(v));
989 let body = body_of_non_null(ColumnKind::Byte, &bytes);
990 prop_assert_eq!(body, &[v as u8][..]);
991 }
992
993 #[test]
994 fn fuzz_i16(v: i16) {
995 let bytes = enc(Bind::I16(v));
996 let body = body_of_non_null(ColumnKind::Short, &bytes);
997 prop_assert_eq!(body.len(), 2);
998 let got = i16::from_le_bytes(body.try_into().unwrap());
999 prop_assert_eq!(got, v);
1000 }
1001
1002 #[test]
1003 fn fuzz_i32(v: i32) {
1004 let bytes = enc(Bind::I32(v));
1005 let body = body_of_non_null(ColumnKind::Int, &bytes);
1006 prop_assert_eq!(body.len(), 4);
1007 let got = i32::from_le_bytes(body.try_into().unwrap());
1008 prop_assert_eq!(got, v);
1009 }
1010
1011 #[test]
1012 fn fuzz_i64(v: i64) {
1013 let bytes = enc(Bind::I64(v));
1014 let body = body_of_non_null(ColumnKind::Long, &bytes);
1015 prop_assert_eq!(body.len(), 8);
1016 let got = i64::from_le_bytes(body.try_into().unwrap());
1017 prop_assert_eq!(got, v);
1018 }
1019
1020 #[test]
1026 fn fuzz_f32_bits(bits: u32) {
1027 let v = f32::from_bits(bits);
1028 let bytes = enc(Bind::F32(v));
1029 let body = body_of_non_null(ColumnKind::Float, &bytes);
1030 prop_assert_eq!(body.len(), 4);
1031 let got = f32::from_le_bytes(body.try_into().unwrap());
1032 prop_assert_eq!(got.to_bits(), v.to_bits());
1033 }
1034
1035 #[test]
1036 fn fuzz_f64_bits(bits: u64) {
1037 let v = f64::from_bits(bits);
1038 let bytes = enc(Bind::F64(v));
1039 let body = body_of_non_null(ColumnKind::Double, &bytes);
1040 prop_assert_eq!(body.len(), 8);
1041 let got = f64::from_le_bytes(body.try_into().unwrap());
1042 prop_assert_eq!(got.to_bits(), v.to_bits());
1043 }
1044
1045 #[test]
1048 fn fuzz_timestamp_micros(v: i64) {
1049 let bytes = enc(Bind::TimestampMicros(v));
1050 let body = body_of_non_null(ColumnKind::Timestamp, &bytes);
1051 prop_assert_eq!(i64::from_le_bytes(body.try_into().unwrap()), v);
1052 }
1053
1054 #[test]
1055 fn fuzz_timestamp_nanos(v: i64) {
1056 let bytes = enc(Bind::TimestampNanos(v));
1057 let body = body_of_non_null(ColumnKind::TimestampNanos, &bytes);
1058 prop_assert_eq!(i64::from_le_bytes(body.try_into().unwrap()), v);
1059 }
1060
1061 #[test]
1062 fn fuzz_date_millis(v: i64) {
1063 let bytes = enc(Bind::DateMillis(v));
1064 let body = body_of_non_null(ColumnKind::Date, &bytes);
1065 prop_assert_eq!(i64::from_le_bytes(body.try_into().unwrap()), v);
1066 }
1067
1068 #[test]
1071 fn fuzz_char(v: u16) {
1072 let bytes = enc(Bind::Char(v));
1073 let body = body_of_non_null(ColumnKind::Char, &bytes);
1074 prop_assert_eq!(body.len(), 2);
1075 let got = u16::from_le_bytes(body.try_into().unwrap());
1076 prop_assert_eq!(got, v);
1077 }
1078
1079 #[test]
1082 fn fuzz_ipv4(octets: [u8; 4]) {
1083 let addr = Ipv4Addr::from(u32::from_be_bytes(octets));
1084 let bytes = enc(Bind::Ipv4(addr));
1085 let body = body_of_non_null(ColumnKind::Ipv4, &bytes);
1086 prop_assert_eq!(body.len(), 4);
1087 let got = Ipv4Addr::from(u32::from_le_bytes(body.try_into().unwrap()));
1088 prop_assert_eq!(got, addr);
1089 }
1090
1091 #[test]
1094 fn fuzz_uuid(raw in proptest::array::uniform16(any::<u8>())) {
1095 let bytes = enc(Bind::Uuid(raw));
1096 let body = body_of_non_null(ColumnKind::Uuid, &bytes);
1097 prop_assert_eq!(body, &raw[..]);
1098 }
1099
1100 #[test]
1101 fn fuzz_long256(raw in proptest::array::uniform32(any::<u8>())) {
1102 let bytes = enc(Bind::Long256(raw));
1103 let body = body_of_non_null(ColumnKind::Long256, &bytes);
1104 prop_assert_eq!(body, &raw[..]);
1105 }
1106
1107 #[test]
1112 fn fuzz_decimal64(value: i64, scale in 0i8..=DECIMAL64_MAX_SCALE) {
1113 let bytes = enc(Bind::Decimal64 { value, scale });
1114 let body = body_of_non_null(ColumnKind::Decimal64, &bytes);
1115 prop_assert_eq!(body.len(), 1 + 8);
1116 prop_assert_eq!(body[0] as i8, scale);
1117 prop_assert_eq!(i64::from_le_bytes(body[1..].try_into().unwrap()), value);
1118 }
1119
1120 #[test]
1121 fn fuzz_decimal128(value: i128, scale in 0i8..=DECIMAL128_MAX_SCALE) {
1122 let bytes = enc(Bind::Decimal128 { value, scale });
1123 let body = body_of_non_null(ColumnKind::Decimal128, &bytes);
1124 prop_assert_eq!(body.len(), 1 + 16);
1125 prop_assert_eq!(body[0] as i8, scale);
1126 prop_assert_eq!(i128::from_le_bytes(body[1..].try_into().unwrap()), value);
1127 }
1128
1129 #[test]
1130 fn fuzz_decimal256(
1131 raw in proptest::array::uniform32(any::<u8>()),
1132 scale in 0i8..=DECIMAL256_MAX_SCALE,
1133 ) {
1134 let bytes = enc(Bind::Decimal256 { bytes: raw, scale });
1135 let body = body_of_non_null(ColumnKind::Decimal256, &bytes);
1136 prop_assert_eq!(body.len(), 1 + 32);
1137 prop_assert_eq!(body[0] as i8, scale);
1138 prop_assert_eq!(&body[1..], &raw[..]);
1139 }
1140
1141 #[test]
1144 fn fuzz_geohash(raw_value: u64, precision_bits in 1u8..=60) {
1145 let mask = if precision_bits == 64 {
1153 !0u64
1154 } else {
1155 (1u64 << precision_bits) - 1
1156 };
1157 let value = raw_value & mask;
1158 let bytes = enc(Bind::Geohash { value, precision_bits });
1159 let body = body_of_non_null(ColumnKind::Geohash, &bytes);
1160 prop_assert_eq!(body[0], precision_bits);
1164 let byte_width = (precision_bits as usize).div_ceil(8);
1165 prop_assert_eq!(body.len(), 1 + byte_width);
1166 let mut buf = [0u8; 8];
1167 buf[..byte_width].copy_from_slice(&body[1..]);
1168 let got = u64::from_le_bytes(buf);
1169 prop_assert_eq!(got, value);
1170 }
1171
1172 #[test]
1179 fn fuzz_varchar(s in ".{0,32}") {
1180 let bytes = enc(Bind::Varchar(s.clone()));
1181 let body = body_of_non_null(ColumnKind::Varchar, &bytes);
1182 let utf8_bytes = s.as_bytes();
1183 prop_assert_eq!(body.len(), 8 + utf8_bytes.len());
1184 let offset0 = u32::from_le_bytes(body[0..4].try_into().unwrap());
1185 let offset1 = u32::from_le_bytes(body[4..8].try_into().unwrap());
1186 prop_assert_eq!(offset0, 0);
1187 prop_assert_eq!(offset1 as usize, utf8_bytes.len());
1188 prop_assert_eq!(&body[8..], utf8_bytes);
1189 }
1190
1191 #[test]
1192 fn fuzz_binary(buf in proptest::collection::vec(any::<u8>(), 0..32)) {
1193 let bytes = enc(Bind::Binary(buf.clone()));
1194 let body = body_of_non_null(ColumnKind::Binary, &bytes);
1195 prop_assert_eq!(body.len(), 8 + buf.len());
1196 let offset0 = u32::from_le_bytes(body[0..4].try_into().unwrap());
1197 let offset1 = u32::from_le_bytes(body[4..8].try_into().unwrap());
1198 prop_assert_eq!(offset0, 0);
1199 prop_assert_eq!(offset1 as usize, buf.len());
1200 prop_assert_eq!(&body[8..], &buf[..]);
1201 }
1202 }
1203 }
1204}