1use std::mem::size_of;
11
12use bytes::Bytes;
13use storekey::{Decode, Encode};
14
15use crate::{
16 error::{Error, Result},
17 store::key::{INDEX_ENTRY_PREFIX_LEN, INDEX_SUBSPACE_TAG, index_kind_tag},
18};
19
20pub(crate) const MAX_INDEX_KEY_BYTES: usize = 1024;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum IntWidth {
27 I8,
29 I16,
31 I32,
33 I64,
36 I128,
38}
39
40impl IntWidth {
41 const fn bytes(self) -> usize {
43 match self {
44 Self::I8 => 1,
45 Self::I16 => 2,
46 Self::I32 => 4,
47 Self::I64 => 8,
48 Self::I128 => 16,
49 }
50 }
51
52 const fn holds_signed(self, value: i128) -> bool {
54 if matches!(self, Self::I128) {
55 return true;
56 }
57 let bits = self.bytes() * 8;
58 value >= -(1_i128 << (bits - 1)) && value < (1_i128 << (bits - 1))
59 }
60
61 const fn holds_unsigned(self, value: u128) -> bool {
64 matches!(self, Self::I128) || value < (1_u128 << (self.bytes() * 8))
65 }
66}
67
68#[derive(Debug, Clone, PartialEq)]
72pub enum IndexKeyValue {
73 Int {
75 value: i128,
77 width: IntWidth,
79 },
80 UInt {
82 value: u128,
84 width: IntWidth,
86 },
87 F32(f32),
89 F64(f64),
91 Bool(bool),
93 Str(String),
95 Bytes(Vec<u8>),
97}
98
99#[derive(Debug, Clone, Copy, PartialEq)]
105pub(crate) enum BorrowedIndexKeyValue<'a> {
106 Int { value: i128, width: IntWidth },
108 UInt { value: u128, width: IntWidth },
110 F32(f32),
112 F64(f64),
114 Bool(bool),
116 Str(&'a str),
118 Bytes(&'a [u8]),
120}
121
122impl<'a> From<&'a IndexKeyValue> for BorrowedIndexKeyValue<'a> {
123 fn from(value: &'a IndexKeyValue) -> Self {
124 match value {
125 IndexKeyValue::Int { value, width } => Self::Int {
126 value: *value,
127 width: *width,
128 },
129 IndexKeyValue::UInt { value, width } => Self::UInt {
130 value: *value,
131 width: *width,
132 },
133 IndexKeyValue::F32(value) => Self::F32(*value),
134 IndexKeyValue::F64(value) => Self::F64(*value),
135 IndexKeyValue::Bool(value) => Self::Bool(*value),
136 IndexKeyValue::Str(value) => Self::Str(value),
137 IndexKeyValue::Bytes(value) => Self::Bytes(value),
138 }
139 }
140}
141
142const F32_CANONICAL_NAN: u32 = 0x7fc0_0000;
144const F64_CANONICAL_NAN: u64 = 0x7ff8_0000_0000_0000;
146
147impl IndexKeyValue {
148 #[cfg(test)]
150 pub(crate) fn encode(&self) -> Vec<u8> {
151 match self {
152 Self::Int { value, width } => {
153 let width = width.bytes();
154 let mut bytes = value.to_be_bytes()[size_of::<i128>() - width..].to_vec();
155 bytes[0] ^= 0x80;
158 bytes
159 }
160 Self::UInt { value, width } => {
161 let width = width.bytes();
162 value.to_be_bytes()[size_of::<u128>() - width..].to_vec()
163 }
164 Self::F32(value) => {
165 let bits = if value.is_nan() {
166 F32_CANONICAL_NAN
167 } else {
168 (value + 0.0).to_bits()
171 };
172 let mask = 0x8000_0000_u32 | 0_u32.wrapping_sub(bits >> 31);
176 (bits ^ mask).to_be_bytes().to_vec()
177 }
178 Self::F64(value) => {
179 let bits = if value.is_nan() {
180 F64_CANONICAL_NAN
181 } else {
182 (value + 0.0).to_bits()
183 };
184 let mask = 0x8000_0000_0000_0000_u64 | 0_u64.wrapping_sub(bits >> 63);
185 (bits ^ mask).to_be_bytes().to_vec()
186 }
187 Self::Bool(value) => vec![u8::from(*value)],
188 Self::Str(value) => value.as_bytes().to_vec(),
192 Self::Bytes(value) => value.clone(),
193 }
194 }
195
196 #[cfg(test)]
199 const fn encoded_len(&self) -> usize {
200 match self {
201 Self::Int { width, .. } | Self::UInt { width, .. } => width.bytes(),
202 Self::F32(_) => size_of::<f32>(),
203 Self::F64(_) => size_of::<f64>(),
204 Self::Bool(_) => 1,
205 Self::Str(value) => value.len(),
206 Self::Bytes(value) => value.len(),
207 }
208 }
209}
210
211#[cfg(test)]
214pub(crate) fn nominal_key_bytes(values: &[Option<IndexKeyValue>]) -> usize {
215 values
216 .iter()
217 .map(|value| match value {
218 Some(value) => 1 + value.encoded_len() + 1,
219 None => 1,
220 })
221 .sum()
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum Direction {
227 Ascending,
229 Descending,
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum NullOrder {
238 First,
240 Last,
242}
243
244#[derive(Debug, Clone, PartialEq)]
247#[cfg(test)]
248pub struct OrderedColumn {
249 pub value: Option<IndexKeyValue>,
251 pub direction: Direction,
253 pub nulls: NullOrder,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
261pub(crate) struct CanonicalKey(Bytes);
262
263impl<F> Encode<F> for CanonicalKey {
271 fn encode<W: std::io::Write>(
272 &self,
273 w: &mut storekey::Writer<W>,
274 ) -> std::result::Result<(), storekey::EncodeError> {
275 w.write_slice(&self.0)
276 }
277}
278
279impl<F> Decode<F> for CanonicalKey {
280 fn decode<R: std::io::BufRead>(
281 r: &mut storekey::Reader<R>,
282 ) -> std::result::Result<Self, storekey::DecodeError> {
283 Ok(Self(Bytes::from(r.read_vec()?)))
284 }
285}
286
287impl CanonicalKey {
288 pub(crate) fn retained_bytes(&self) -> usize {
290 self.0.len()
291 }
292
293 pub(crate) const fn empty() -> Self {
295 Self(Bytes::new())
296 }
297
298 #[cfg(test)]
300 pub(crate) fn as_bytes(&self) -> &[u8] {
301 &self.0
302 }
303}
304
305pub(crate) struct CanonicalKeyBuilder {
308 bytes: Vec<u8>,
309 raw_bytes: usize,
310 has_null: bool,
311 entry_bytes: Option<usize>,
312 entry_unique: bool,
313}
314
315impl CanonicalKeyBuilder {
316 pub(crate) const fn new() -> Self {
318 Self {
319 bytes: Vec::new(),
320 raw_bytes: 0,
321 has_null: false,
322 entry_bytes: None,
323 entry_unique: false,
324 }
325 }
326
327 pub(crate) const fn for_index_entry(unique: bool) -> Self {
330 Self {
331 bytes: Vec::new(),
332 raw_bytes: 0,
333 has_null: false,
334 entry_bytes: Some(
335 INDEX_ENTRY_PREFIX_LEN + 1 + if unique { 0 } else { size_of::<u64>() },
336 ),
337 entry_unique: unique,
338 }
339 }
340
341 fn reserve_component(&mut self, canonical_bytes: usize, outer_escapes: usize) {
342 if let Some(entry_bytes) = &mut self.entry_bytes {
343 *entry_bytes += canonical_bytes + outer_escapes;
344 self.bytes
345 .reserve_exact(entry_bytes.saturating_sub(self.bytes.len()));
346 } else {
347 self.bytes.reserve(canonical_bytes);
348 }
349 }
350
351 pub(crate) fn append(
354 &mut self,
355 value: Option<BorrowedIndexKeyValue<'_>>,
356 direction: Direction,
357 nulls: NullOrder,
358 ) -> Result<()> {
359 let (null_flag, non_null_flag) = null_flags(nulls);
360 let Some(value) = value else {
361 if self.entry_unique {
362 if let Some(entry_bytes) = &mut self.entry_bytes {
363 *entry_bytes += size_of::<u64>();
364 }
365 self.entry_unique = false;
366 }
367 self.reserve_component(1, 1);
368 self.bytes.push(null_flag);
369 self.has_null = true;
370 return Ok(());
371 };
372
373 let raw_len = value.encoded_len();
374 let total = self.raw_bytes.saturating_add(raw_len);
375 if total > MAX_INDEX_KEY_BYTES {
376 return Err(Error::Constraint(format!(
377 "index key of {total} bytes exceeds the {MAX_INDEX_KEY_BYTES}-byte limit"
378 )));
379 }
380 value.validate_width()?;
381
382 self.raw_bytes = total;
383 match value {
384 BorrowedIndexKeyValue::Int { value, width } => {
385 let mut raw = value.to_be_bytes();
386 let start = raw.len() - width.bytes();
387 raw[start] ^= 0x80;
388 self.append_framed(&raw[start..], direction, non_null_flag);
389 }
390 BorrowedIndexKeyValue::UInt { value, width } => {
391 let raw = value.to_be_bytes();
392 self.append_framed(&raw[raw.len() - width.bytes()..], direction, non_null_flag);
393 }
394 BorrowedIndexKeyValue::F32(value) => {
395 let bits = if value.is_nan() {
396 F32_CANONICAL_NAN
397 } else {
398 (value + 0.0).to_bits()
399 };
400 let mask = 0x8000_0000_u32 | 0_u32.wrapping_sub(bits >> 31);
401 self.append_framed(&(bits ^ mask).to_be_bytes(), direction, non_null_flag);
402 }
403 BorrowedIndexKeyValue::F64(value) => {
404 let bits = if value.is_nan() {
405 F64_CANONICAL_NAN
406 } else {
407 (value + 0.0).to_bits()
408 };
409 let mask = 0x8000_0000_0000_0000_u64 | 0_u64.wrapping_sub(bits >> 63);
410 self.append_framed(&(bits ^ mask).to_be_bytes(), direction, non_null_flag);
411 }
412 BorrowedIndexKeyValue::Bool(value) => {
413 self.append_framed(&[u8::from(value)], direction, non_null_flag);
414 }
415 BorrowedIndexKeyValue::Str(value) => {
416 self.append_framed(value.as_bytes(), direction, non_null_flag);
417 }
418 BorrowedIndexKeyValue::Bytes(value) => {
419 self.append_framed(value, direction, non_null_flag);
420 }
421 }
422 Ok(())
423 }
424
425 pub(crate) fn finish(self) -> (CanonicalKey, bool) {
427 (CanonicalKey(Bytes::from(self.bytes)), self.has_null)
428 }
429
430 pub(crate) fn finish_index_entry(
433 mut self,
434 index_id: u64,
435 requested_unique: bool,
436 row_id: u64,
437 ) -> (Bytes, bool) {
438 let unique = requested_unique && !self.has_null;
439 let canonical_len = self.bytes.len();
440 let escapes = self.bytes.iter().filter(|byte| **byte <= 1).count();
441 let framed_end = INDEX_ENTRY_PREFIX_LEN
442 .saturating_add(canonical_len)
443 .saturating_add(escapes);
444 let suffix = 1 + if unique { 0 } else { size_of::<u64>() };
445 self.bytes.resize(framed_end.saturating_add(suffix), 0);
446
447 let mut write = framed_end;
448 for read in (0..canonical_len).rev() {
449 let byte = self.bytes[read];
450 write -= 1;
451 self.bytes[write] = byte;
452 if byte <= 1 {
453 write -= 1;
454 self.bytes[write] = 1;
455 }
456 }
457 self.bytes[0] = INDEX_SUBSPACE_TAG;
458 self.bytes[1] = index_kind_tag(unique);
459 self.bytes[2..INDEX_ENTRY_PREFIX_LEN].copy_from_slice(&index_id.to_be_bytes());
460 self.bytes[framed_end] = 0;
461 if !unique {
462 self.bytes[framed_end + 1..].copy_from_slice(&row_id.to_be_bytes());
463 }
464
465 (Bytes::from(self.bytes), unique)
466 }
467
468 fn append_framed(&mut self, raw: &[u8], direction: Direction, flag: u8) {
469 let low_bytes = raw.iter().filter(|byte| **byte <= 1).count();
470 let outer_escapes = 1 + match direction {
471 Direction::Ascending => low_bytes * 2 + 1,
472 Direction::Descending => raw.iter().filter(|byte| **byte >= 254).count(),
473 };
474 self.reserve_component(raw.len() + low_bytes + 2, outer_escapes);
475 self.bytes.push(flag);
476 let start = self.bytes.len();
477 for &byte in raw {
478 if byte <= 1 {
479 self.bytes.push(1);
480 }
481 self.bytes.push(byte);
482 }
483 self.bytes.push(0);
484 if direction == Direction::Descending {
485 for byte in &mut self.bytes[start..] {
486 *byte = !*byte;
487 }
488 }
489 }
490}
491
492impl BorrowedIndexKeyValue<'_> {
493 pub(crate) fn into_owned(self) -> IndexKeyValue {
495 match self {
496 Self::Int { value, width } => IndexKeyValue::Int { value, width },
497 Self::UInt { value, width } => IndexKeyValue::UInt { value, width },
498 Self::F32(value) => IndexKeyValue::F32(value),
499 Self::F64(value) => IndexKeyValue::F64(value),
500 Self::Bool(value) => IndexKeyValue::Bool(value),
501 Self::Str(value) => IndexKeyValue::Str(value.to_owned()),
502 Self::Bytes(value) => IndexKeyValue::Bytes(value.to_vec()),
503 }
504 }
505
506 const fn encoded_len(self) -> usize {
507 match self {
508 Self::Int { width, .. } | Self::UInt { width, .. } => width.bytes(),
509 Self::F32(_) => size_of::<f32>(),
510 Self::F64(_) => size_of::<f64>(),
511 Self::Bool(_) => 1,
512 Self::Str(value) => value.len(),
513 Self::Bytes(value) => value.len(),
514 }
515 }
516
517 fn validate_width(self) -> Result<()> {
518 let fits = match self {
519 Self::Int { value, width } => width.holds_signed(value),
520 Self::UInt { value, width } => width.holds_unsigned(value),
521 _ => true,
522 };
523 if fits {
524 Ok(())
525 } else {
526 Err(Error::Constraint(format!(
527 "index key value {self:?} does not fit its declared integer width"
528 )))
529 }
530 }
531}
532
533#[cfg(test)]
538pub(crate) fn encode_key(values: &[IndexKeyValue]) -> Result<CanonicalKey> {
539 let columns: Vec<OrderedColumn> = values
540 .iter()
541 .map(|value| OrderedColumn {
542 value: Some(value.clone()),
543 direction: Direction::Ascending,
544 nulls: NullOrder::Last,
545 })
546 .collect();
547 encode_ordered_key(&columns)
548}
549
550const fn null_flags(nulls: NullOrder) -> (u8, u8) {
553 match nulls {
554 NullOrder::First => (0x00, 0x01),
555 NullOrder::Last => (0x01, 0x00),
556 }
557}
558
559pub(crate) fn non_null_flag_key(nulls: NullOrder) -> CanonicalKey {
563 let (_, non_null_flag) = null_flags(nulls);
564 CanonicalKey(Bytes::from_static(match non_null_flag {
565 0 => &[0],
566 _ => &[1],
567 }))
568}
569
570#[cfg(test)]
584pub(crate) fn encode_ordered_key(columns: &[OrderedColumn]) -> Result<CanonicalKey> {
585 let mut builder = CanonicalKeyBuilder::new();
586 for column in columns {
587 builder.append(
588 column.value.as_ref().map(BorrowedIndexKeyValue::from),
589 column.direction,
590 column.nulls,
591 )?;
592 }
593 Ok(builder.finish().0)
594}
595
596fn build(
600 values: &[Option<IndexKeyValue>],
601 directions: &[Direction],
602 nulls: &[NullOrder],
603 mut builder: CanonicalKeyBuilder,
604) -> Result<CanonicalKeyBuilder> {
605 for (index, value) in values.iter().enumerate() {
606 builder.append(
607 value.as_ref().map(BorrowedIndexKeyValue::from),
608 directions
609 .get(index)
610 .copied()
611 .unwrap_or(Direction::Ascending),
612 nulls.get(index).copied().unwrap_or(NullOrder::Last),
613 )?;
614 }
615 Ok(builder)
616}
617
618pub(crate) fn encode_ordered_values(
622 values: &[Option<IndexKeyValue>],
623 directions: &[Direction],
624 nulls: &[NullOrder],
625) -> Result<CanonicalKey> {
626 Ok(
627 build(values, directions, nulls, CanonicalKeyBuilder::new())?
628 .finish()
629 .0,
630 )
631}
632
633pub(crate) fn encode_ordered_index_entry(
637 values: &[Option<IndexKeyValue>],
638 directions: &[Direction],
639 nulls: &[NullOrder],
640 index_id: u64,
641 requested_unique: bool,
642 row_id: u64,
643) -> Result<(Bytes, bool)> {
644 Ok(build(
645 values,
646 directions,
647 nulls,
648 CanonicalKeyBuilder::for_index_entry(requested_unique),
649 )?
650 .finish_index_entry(index_id, requested_unique, row_id))
651}
652
653#[cfg(test)]
658fn frame_bytes(raw: &[u8]) -> Vec<u8> {
659 #[allow(clippy::expect_used)]
663 storekey::encode_vec(&raw.to_vec()).expect("storekey encode into a Vec cannot fail")
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669
670 fn legacy_encode_ordered_key(columns: &[OrderedColumn]) -> Result<CanonicalKey> {
674 let mut total = 0usize;
675 let mut out = Vec::new();
676 for column in columns {
677 let (null_flag, non_null_flag) = null_flags(column.nulls);
678 let Some(value) = &column.value else {
679 out.push(null_flag);
680 continue;
681 };
682 BorrowedIndexKeyValue::from(value).validate_width()?;
683 let raw = value.encode();
684 total = total.saturating_add(raw.len());
685 let framed = frame_bytes(&raw);
686 out.push(non_null_flag);
687 match column.direction {
688 Direction::Ascending => out.extend_from_slice(&framed),
689 Direction::Descending => out.extend(framed.iter().map(|byte| !byte)),
690 }
691 }
692 if total > MAX_INDEX_KEY_BYTES {
693 return Err(Error::Constraint(format!(
694 "index key of {total} bytes exceeds the {MAX_INDEX_KEY_BYTES}-byte limit"
695 )));
696 }
697 Ok(CanonicalKey(Bytes::from(out)))
698 }
699
700 fn build_borrowed(columns: &[OrderedColumn]) -> Result<(CanonicalKey, bool)> {
701 let mut builder = CanonicalKeyBuilder::new();
702 for column in columns {
703 builder.append(
704 column.value.as_ref().map(BorrowedIndexKeyValue::from),
705 column.direction,
706 column.nulls,
707 )?;
708 }
709 Ok(builder.finish())
710 }
711
712 #[test]
713 fn golden_signed_int_flips_sign_bit() {
714 assert_eq!(
716 IndexKeyValue::Int {
717 value: 0,
718 width: IntWidth::I64,
719 }
720 .encode(),
721 vec![0x80, 0, 0, 0, 0, 0, 0, 0],
722 );
723 assert_eq!(
725 IndexKeyValue::Int {
726 value: 1,
727 width: IntWidth::I64,
728 }
729 .encode(),
730 vec![0x80, 0, 0, 0, 0, 0, 0, 1],
731 );
732 assert_eq!(
734 IndexKeyValue::Int {
735 value: -1,
736 width: IntWidth::I64,
737 }
738 .encode(),
739 vec![0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
740 );
741 }
742
743 #[test]
744 fn golden_signed_int_widths_are_normalized() {
745 assert_eq!(
746 IndexKeyValue::Int {
747 value: 1,
748 width: IntWidth::I8,
749 }
750 .encode(),
751 vec![0x81],
752 );
753 assert_eq!(
754 IndexKeyValue::Int {
755 value: 1,
756 width: IntWidth::I16,
757 }
758 .encode(),
759 vec![0x80, 0x01],
760 );
761 assert_eq!(
762 IndexKeyValue::Int {
763 value: 1,
764 width: IntWidth::I32,
765 }
766 .encode(),
767 vec![0x80, 0, 0, 0x01],
768 );
769 assert_eq!(
770 IndexKeyValue::Int {
771 value: 1,
772 width: IntWidth::I128,
773 }
774 .encode()
775 .len(),
776 16,
777 );
778 }
779
780 #[test]
781 fn golden_unsigned_int_is_plain_big_endian() {
782 assert_eq!(
783 IndexKeyValue::UInt {
784 value: 5,
785 width: IntWidth::I32,
786 }
787 .encode(),
788 vec![0, 0, 0, 5],
789 );
790 assert_eq!(
791 IndexKeyValue::UInt {
792 value: u128::from(u64::MAX),
793 width: IntWidth::I64,
794 }
795 .encode(),
796 vec![0xff; 8],
797 );
798 }
799
800 fn signed(value: i128) -> Vec<u8> {
804 IndexKeyValue::Int {
805 value,
806 width: IntWidth::I64,
807 }
808 .encode()
809 }
810
811 #[test]
812 fn equal_values_encode_equally_distinct_values_distinctly() {
813 assert_eq!(signed(42), signed(42));
814 assert_ne!(signed(42), signed(43));
815 }
816
817 #[test]
818 fn signed_encoding_is_order_compatible() {
819 let ordered = [i64::MIN, -2, -1, 0, 1, 2, i64::MAX];
820 for pair in ordered.windows(2) {
821 let lower = signed(i128::from(pair[0]));
822 let higher = signed(i128::from(pair[1]));
823 assert!(lower < higher, "{:?} !< {:?}", pair[0], pair[1]);
824 }
825 }
826
827 #[test]
828 fn unsigned_encoding_is_order_compatible() {
829 let value = |v: u128| {
830 IndexKeyValue::UInt {
831 value: v,
832 width: IntWidth::I64,
833 }
834 .encode()
835 };
836 assert!(value(0) < value(1));
837 assert!(value(1) < value(u128::from(u64::MAX)));
838 }
839
840 #[test]
841 fn golden_bool_is_one_byte() {
842 assert_eq!(IndexKeyValue::Bool(false).encode(), vec![0]);
843 assert_eq!(IndexKeyValue::Bool(true).encode(), vec![1]);
844 }
845
846 #[test]
847 fn float_negative_zero_normalizes_to_positive_zero() {
848 assert_eq!(
849 IndexKeyValue::F64(-0.0).encode(),
850 IndexKeyValue::F64(0.0).encode(),
851 );
852 assert_eq!(
853 IndexKeyValue::F32(-0.0).encode(),
854 IndexKeyValue::F32(0.0).encode(),
855 );
856 }
857
858 #[test]
859 fn float_nans_collapse_to_one_pattern() {
860 let quiet = IndexKeyValue::F64(f64::from_bits(0x7ff8_0000_0000_0001)).encode();
863 let signalling = IndexKeyValue::F64(f64::from_bits(0xfff0_0000_0000_0001)).encode();
864 let canonical = IndexKeyValue::F64(f64::NAN).encode();
865 assert_eq!(quiet, signalling);
866 assert_eq!(signalling, canonical);
867
868 let f32_quiet = IndexKeyValue::F32(f32::from_bits(0x7fc0_0001)).encode();
869 let f32_signed = IndexKeyValue::F32(f32::from_bits(0xffc0_0001)).encode();
870 assert_eq!(f32_quiet, f32_signed);
871 }
872
873 #[test]
874 fn distinct_floats_encode_distinctly() {
875 assert_ne!(
876 IndexKeyValue::F64(1.0).encode(),
877 IndexKeyValue::F64(2.0).encode(),
878 );
879 assert_ne!(
880 IndexKeyValue::F32(1.5).encode(),
881 IndexKeyValue::F32(-1.5).encode(),
882 );
883 }
884
885 #[test]
886 fn golden_string_and_blob_are_raw_bytes() {
887 assert_eq!(IndexKeyValue::Str("abc".into()).encode(), b"abc".to_vec(),);
888 assert_eq!(
889 IndexKeyValue::Bytes(vec![0, 1, 2, 0xff]).encode(),
890 vec![0, 1, 2, 0xff],
891 );
892 }
893
894 #[test]
895 fn borrowed_builder_matches_legacy_for_escape_bytes_floats_and_nulls() {
896 let columns = [
897 OrderedColumn {
898 value: Some(IndexKeyValue::Str("a\0\u{1}z".to_owned())),
899 direction: Direction::Descending,
900 nulls: NullOrder::First,
901 },
902 OrderedColumn {
903 value: Some(IndexKeyValue::Bytes(vec![0, 1, 0xff])),
904 direction: Direction::Ascending,
905 nulls: NullOrder::Last,
906 },
907 OrderedColumn {
908 value: Some(IndexKeyValue::F64(-0.0)),
909 direction: Direction::Ascending,
910 nulls: NullOrder::Last,
911 },
912 OrderedColumn {
913 value: Some(IndexKeyValue::F32(f32::from_bits(0xffc0_0001))),
914 direction: Direction::Descending,
915 nulls: NullOrder::First,
916 },
917 OrderedColumn {
918 value: None,
919 direction: Direction::Descending,
920 nulls: NullOrder::Last,
921 },
922 ];
923
924 let (built, has_null) = build_borrowed(&columns).unwrap();
925 assert_eq!(built, legacy_encode_ordered_key(&columns).unwrap());
926 assert!(has_null);
927 }
928
929 #[test]
930 fn encode_key_is_deterministic_and_injective() {
931 let value = |s: &str| {
932 encode_key(&[
933 IndexKeyValue::Int {
934 value: 7,
935 width: IntWidth::I64,
936 },
937 IndexKeyValue::Str(s.into()),
938 ])
939 .unwrap()
940 };
941 assert_eq!(value("x"), value("x"));
942 assert_ne!(value("x"), value("y"));
943 assert!(!value("x").as_bytes().is_empty());
945 }
946
947 #[test]
948 fn composite_framing_distinguishes_component_splits() {
949 let ab_c = encode_key(&[
953 IndexKeyValue::Str("ab".into()),
954 IndexKeyValue::Str("c".into()),
955 ])
956 .unwrap();
957 let a_bc = encode_key(&[
958 IndexKeyValue::Str("a".into()),
959 IndexKeyValue::Str("bc".into()),
960 ])
961 .unwrap();
962 assert_ne!(ab_c, a_bc);
963 }
964
965 #[test]
966 fn oversized_key_is_refused() {
967 let big = IndexKeyValue::Bytes(vec![0; MAX_INDEX_KEY_BYTES + 1]);
968 let err = encode_key(std::slice::from_ref(&big)).unwrap_err();
969 assert!(matches!(err, Error::Constraint(_)), "got {err:?}");
970 }
971
972 #[test]
973 fn key_at_the_cap_is_accepted() {
974 let at_cap = IndexKeyValue::Bytes(vec![0; MAX_INDEX_KEY_BYTES]);
975 assert!(encode_key(std::slice::from_ref(&at_cap)).is_ok());
976 }
977
978 #[test]
979 fn out_of_width_integers_are_refused() {
980 let narrow = |value| {
982 encode_key(&[IndexKeyValue::Int {
983 value,
984 width: IntWidth::I8,
985 }])
986 };
987 assert!(matches!(narrow(300).unwrap_err(), Error::Constraint(_)));
988 assert!(matches!(narrow(-129).unwrap_err(), Error::Constraint(_)));
989 assert!(narrow(i128::from(i8::MIN)).is_ok());
990 assert!(narrow(i128::from(i8::MAX)).is_ok());
991
992 let unsigned = |value| {
993 encode_key(&[IndexKeyValue::UInt {
994 value,
995 width: IntWidth::I8,
996 }])
997 };
998 assert!(matches!(unsigned(256).unwrap_err(), Error::Constraint(_)));
999 assert!(unsigned(u128::from(u8::MAX)).is_ok());
1000
1001 assert!(
1003 encode_key(&[IndexKeyValue::Int {
1004 value: i128::MIN,
1005 width: IntWidth::I128,
1006 }])
1007 .is_ok()
1008 );
1009 assert!(
1010 encode_key(&[IndexKeyValue::UInt {
1011 value: u128::MAX,
1012 width: IntWidth::I128,
1013 }])
1014 .is_ok()
1015 );
1016 }
1017
1018 fn int(value: i128) -> IndexKeyValue {
1019 IndexKeyValue::Int {
1020 value,
1021 width: IntWidth::I64,
1022 }
1023 }
1024
1025 fn ordered(value: Option<IndexKeyValue>, direction: Direction, nulls: NullOrder) -> Vec<u8> {
1026 encode_ordered_key(&[OrderedColumn {
1027 value,
1028 direction,
1029 nulls,
1030 }])
1031 .unwrap()
1032 .as_bytes()
1033 .to_vec()
1034 }
1035
1036 #[test]
1037 fn float_encoding_is_order_preserving() {
1038 let enc = |v: f64| IndexKeyValue::F64(v).encode();
1039 let rising = [
1040 f64::NEG_INFINITY,
1041 -1e300,
1042 -1.0,
1043 -0.0,
1044 0.0,
1045 1.0,
1046 1e300,
1047 f64::INFINITY,
1048 ];
1049 for pair in rising.windows(2) {
1050 assert!(enc(pair[0]) <= enc(pair[1]), "{} !<= {}", pair[0], pair[1]);
1051 }
1052 assert!(enc(-1.0) < enc(1.0));
1054 assert!(enc(f64::NEG_INFINITY) < enc(-1e300));
1055 assert!(enc(f64::INFINITY) < enc(f64::NAN));
1056 assert_eq!(enc(-0.0), enc(0.0));
1058 }
1059
1060 #[test]
1061 fn descending_reverses_numeric_order() {
1062 let asc = |v: i128| ordered(Some(int(v)), Direction::Ascending, NullOrder::Last);
1063 let desc = |v: i128| ordered(Some(int(v)), Direction::Descending, NullOrder::Last);
1064 assert!(asc(1) < asc(2));
1065 assert!(desc(2) < desc(1));
1067 }
1068
1069 #[test]
1070 fn descending_reverses_variable_length_strings() {
1071 let string = |s: &str| IndexKeyValue::Str(s.to_owned());
1072 let asc = |s: &str| ordered(Some(string(s)), Direction::Ascending, NullOrder::Last);
1073 let desc = |s: &str| ordered(Some(string(s)), Direction::Descending, NullOrder::Last);
1074 assert!(asc("a") < asc("ab"));
1077 assert!(desc("ab") < desc("a"));
1078 }
1079
1080 #[test]
1081 fn null_placement_respects_first_and_last_under_both_directions() {
1082 for direction in [Direction::Ascending, Direction::Descending] {
1083 let null_first = ordered(None, direction, NullOrder::First);
1084 let value_first = ordered(Some(int(0)), direction, NullOrder::First);
1085 assert!(null_first < value_first, "NULLS FIRST under {direction:?}");
1086
1087 let null_last = ordered(None, direction, NullOrder::Last);
1088 let value_last = ordered(Some(int(0)), direction, NullOrder::Last);
1089 assert!(value_last < null_last, "NULLS LAST under {direction:?}");
1090 }
1091 }
1092
1093 #[test]
1094 fn composite_order_matches_declared_per_column_directions() {
1095 let key = |a: i128, b: i128| {
1097 encode_ordered_key(&[
1098 OrderedColumn {
1099 value: Some(int(a)),
1100 direction: Direction::Ascending,
1101 nulls: NullOrder::Last,
1102 },
1103 OrderedColumn {
1104 value: Some(int(b)),
1105 direction: Direction::Descending,
1106 nulls: NullOrder::Last,
1107 },
1108 ])
1109 .unwrap()
1110 };
1111 assert!(key(1, 100) < key(2, 100));
1113 assert!(key(1, 0) < key(2, 100));
1114 assert!(key(1, 100) < key(1, 50));
1116 assert!(key(1, 50) < key(1, 10));
1117 }
1118
1119 #[test]
1120 fn ordered_encoding_is_deterministic_for_uniqueness() {
1121 let one = ordered(Some(int(7)), Direction::Descending, NullOrder::First);
1124 assert_eq!(
1125 one,
1126 ordered(Some(int(7)), Direction::Descending, NullOrder::First)
1127 );
1128 assert_ne!(
1129 one,
1130 ordered(Some(int(8)), Direction::Descending, NullOrder::First)
1131 );
1132 }
1133
1134 mod properties {
1135 use proptest::prelude::*;
1136
1137 use super::*;
1138
1139 fn index_value() -> impl Strategy<Value = IndexKeyValue> {
1140 prop_oneof![
1141 any::<i8>().prop_map(|value| IndexKeyValue::Int {
1142 value: i128::from(value),
1143 width: IntWidth::I8,
1144 }),
1145 any::<i16>().prop_map(|value| IndexKeyValue::Int {
1146 value: i128::from(value),
1147 width: IntWidth::I16,
1148 }),
1149 any::<i32>().prop_map(|value| IndexKeyValue::Int {
1150 value: i128::from(value),
1151 width: IntWidth::I32,
1152 }),
1153 any::<i64>().prop_map(|value| IndexKeyValue::Int {
1154 value: i128::from(value),
1155 width: IntWidth::I64,
1156 }),
1157 any::<i128>().prop_map(|value| IndexKeyValue::Int {
1158 value,
1159 width: IntWidth::I128,
1160 }),
1161 any::<u8>().prop_map(|value| IndexKeyValue::UInt {
1162 value: u128::from(value),
1163 width: IntWidth::I8,
1164 }),
1165 any::<u16>().prop_map(|value| IndexKeyValue::UInt {
1166 value: u128::from(value),
1167 width: IntWidth::I16,
1168 }),
1169 any::<u32>().prop_map(|value| IndexKeyValue::UInt {
1170 value: u128::from(value),
1171 width: IntWidth::I32,
1172 }),
1173 any::<u64>().prop_map(|value| IndexKeyValue::UInt {
1174 value: u128::from(value),
1175 width: IntWidth::I64,
1176 }),
1177 any::<u128>().prop_map(|value| IndexKeyValue::UInt {
1178 value,
1179 width: IntWidth::I128,
1180 }),
1181 any::<u32>().prop_map(|bits| IndexKeyValue::F32(f32::from_bits(bits))),
1182 any::<u64>().prop_map(|bits| IndexKeyValue::F64(f64::from_bits(bits))),
1183 any::<bool>().prop_map(IndexKeyValue::Bool),
1184 ".{0,32}".prop_map(IndexKeyValue::Str),
1185 prop::collection::vec(any::<u8>(), 0..32).prop_map(IndexKeyValue::Bytes),
1186 ]
1187 }
1188
1189 fn ordered_column() -> impl Strategy<Value = OrderedColumn> {
1190 (
1191 prop::option::of(index_value()),
1192 any::<bool>(),
1193 any::<bool>(),
1194 )
1195 .prop_map(|(value, descending, nulls_first)| OrderedColumn {
1196 value,
1197 direction: if descending {
1198 Direction::Descending
1199 } else {
1200 Direction::Ascending
1201 },
1202 nulls: if nulls_first {
1203 NullOrder::First
1204 } else {
1205 NullOrder::Last
1206 },
1207 })
1208 }
1209
1210 proptest! {
1211 #[test]
1214 fn borrowed_builder_matches_legacy_encoder(
1215 columns in prop::collection::vec(ordered_column(), 0..8),
1216 ) {
1217 let expected = legacy_encode_ordered_key(&columns).unwrap();
1218 let (actual, has_null) = build_borrowed(&columns).unwrap();
1219 prop_assert_eq!(actual, expected);
1220 prop_assert_eq!(has_null, columns.iter().any(|column| column.value.is_none()));
1221 }
1222
1223 #[test]
1227 fn physical_builder_matches_existing_entry_encoder(
1228 columns in prop::collection::vec(ordered_column(), 0..8),
1229 index_id in any::<u64>(),
1230 requested_unique in any::<bool>(),
1231 row_id in any::<u64>(),
1232 ) {
1233 let canonical = legacy_encode_ordered_key(&columns).unwrap();
1234 let actual_unique = requested_unique
1235 && columns.iter().all(|column| column.value.is_some());
1236 let expected = crate::store::key::encode_index_entry(
1237 index_id,
1238 actual_unique,
1239 &canonical,
1240 row_id,
1241 );
1242 let mut builder = CanonicalKeyBuilder::for_index_entry(requested_unique);
1243 for column in &columns {
1244 builder.append(
1245 column.value.as_ref().map(BorrowedIndexKeyValue::from),
1246 column.direction,
1247 column.nulls,
1248 ).unwrap();
1249 }
1250 let (actual, unique) = builder.finish_index_entry(
1251 index_id,
1252 requested_unique,
1253 row_id,
1254 );
1255
1256 prop_assert_eq!(actual.as_ref(), expected.as_slice());
1257 prop_assert_eq!(unique, actual_unique);
1258 }
1259
1260 #[test]
1263 fn signed_encoding_is_injective(a: i64, b: i64) {
1264 let one = signed(i128::from(a));
1265 let two = signed(i128::from(b));
1266 prop_assert_eq!(one.clone(), signed(i128::from(a)));
1267 prop_assert_eq!(a == b, one == two);
1268 }
1269
1270 #[test]
1273 fn signed_encoding_preserves_order(a: i64, b: i64) {
1274 let one = signed(i128::from(a));
1275 let two = signed(i128::from(b));
1276 prop_assert_eq!(a.cmp(&b), one.cmp(&two));
1277 }
1278
1279 #[test]
1280 fn unsigned_encoding_preserves_order(a: u64, b: u64) {
1281 let encode = |value| {
1282 IndexKeyValue::UInt {
1283 value,
1284 width: IntWidth::I64,
1285 }
1286 .encode()
1287 };
1288 let one = encode(u128::from(a));
1289 let two = encode(u128::from(b));
1290 prop_assert_eq!(a.cmp(&b), one.cmp(&two));
1291 }
1292
1293 #[test]
1296 fn float_encoding_preserves_total_order(a: f64, b: f64) {
1297 prop_assume!(!a.is_nan() && !b.is_nan());
1298 let one = IndexKeyValue::F64(a).encode();
1299 let two = IndexKeyValue::F64(b).encode();
1300 let norm = |v: f64| if v == 0.0 { 0.0 } else { v };
1301 let expected = norm(a).partial_cmp(&norm(b)).unwrap();
1302 prop_assert_eq!(expected, one.cmp(&two));
1303 }
1304
1305 #[test]
1308 fn narrow_signed_encoding_is_injective(a: i16, b: i16) {
1309 let encode = |value: i16| {
1310 encode_key(&[IndexKeyValue::Int {
1311 value: i128::from(value),
1312 width: IntWidth::I16,
1313 }])
1314 .unwrap()
1315 };
1316 prop_assert_eq!(a == b, encode(a) == encode(b));
1317 }
1318
1319 #[test]
1322 fn composite_framing_is_injective(
1323 a in prop::collection::vec(".{0,8}", 1..3),
1324 b in prop::collection::vec(".{0,8}", 1..3),
1325 ) {
1326 let encode = |parts: &[String]| {
1327 encode_key(
1328 &parts
1329 .iter()
1330 .map(|part| IndexKeyValue::Str(part.clone()))
1331 .collect::<Vec<_>>(),
1332 )
1333 .unwrap()
1334 };
1335 prop_assert_eq!(a == b, encode(&a) == encode(&b));
1336 }
1337
1338 #[test]
1342 fn nominal_size_brackets_the_encoded_key(
1343 values in prop::collection::vec(
1344 prop::option::of(prop::collection::vec(any::<u8>(), 0..24)),
1345 0..4,
1346 ),
1347 ) {
1348 let values: Vec<Option<IndexKeyValue>> = values
1349 .into_iter()
1350 .map(|value| value.map(IndexKeyValue::Bytes))
1351 .collect();
1352 let columns: Vec<OrderedColumn> = values
1353 .iter()
1354 .map(|value| OrderedColumn {
1355 value: value.clone(),
1356 direction: Direction::Ascending,
1357 nulls: NullOrder::Last,
1358 })
1359 .collect();
1360
1361 let nominal = nominal_key_bytes(&values);
1362 let encoded = encode_ordered_key(&columns).unwrap().as_bytes().len();
1363 prop_assert!(nominal <= encoded, "{nominal} > {encoded}");
1364 prop_assert!(encoded <= 2 * nominal, "{encoded} > 2 * {nominal}");
1365 }
1366 }
1367 }
1368}