1use std::borrow::Cow;
5
6use reifydb_codec::key::{deserializer::KeyDeserializer, encoded::EncodedKey, serializer::KeySerializer};
7use reifydb_macro::KeyCodec;
8use reifydb_value::value::{datetime::DateTime, row_number::RowNumber};
9use smallvec::{SmallVec, smallvec};
10
11use super::KeyTag;
12use crate::{
13 interface::catalog::id::QueueId,
14 key::{
15 any::{ByteEncoding, Field, KeyFields, Width},
16 bound::TaggedKeyBoundRange,
17 },
18};
19
20#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
21#[key(tag = Queue)]
22pub struct QueueKey {
23 pub queue: QueueId,
24}
25
26impl QueueKey {
27 pub fn new(queue: QueueId) -> Self {
28 Self {
29 queue,
30 }
31 }
32
33 pub fn encoded(queue: impl Into<QueueId>) -> EncodedKey {
34 Self::new(queue.into()).encode()
35 }
36
37 pub fn full_scan() -> TaggedKeyBoundRange {
38 TaggedKeyBoundRange::kind(Self::TAG)
39 }
40}
41
42#[cfg(test)]
43mod queue_key_tests {
44 use std::ops::Bound;
45
46 use super::*;
47
48 #[test]
49 fn test_encode_decode_roundtrip() {
50 let encoded = QueueKey::encoded(QueueId(42));
52 let decoded = QueueKey::decode(&encoded).unwrap();
53 assert_eq!(decoded.queue, QueueId(42));
54 }
55
56 #[test]
57 fn test_decode_rejects_foreign_kind() {
58 let mut serializer = KeySerializer::with_capacity(9);
61 serializer.extend_u8(KeyTag::NamespaceQueue as u8).extend_u64(7u64);
62 assert!(QueueKey::decode(&serializer.to_encoded_key()).is_none());
63 }
64
65 #[test]
66 fn test_full_scan_brackets_every_queue_key() {
67 let range = QueueKey::full_scan().encode();
70
71 let Bound::Included(start) = &range.start else {
72 panic!("expected an included start bound")
73 };
74 let Bound::Included(end) = &range.end else {
75 panic!("expected an included end bound")
76 };
77
78 assert_eq!(start.as_slice(), &[!(KeyTag::Queue as u8)]);
79 assert_eq!(end.as_slice(), &[!(KeyTag::Queue as u8 - 1)]);
80 assert!(start.as_slice() < end.as_slice(), "the range must be non-empty under byte order");
81
82 for id in [QueueId(1), QueueId(u64::MAX)] {
83 let key = QueueKey::encoded(id);
84 assert!(
85 key.as_slice() >= start.as_slice() && key.as_slice() <= end.as_slice(),
86 "queue {id:?} must fall inside the scan range"
87 );
88 }
89 }
90
91 #[test]
92 fn test_full_scan_excludes_the_neighbouring_kind() {
93 let range = QueueKey::full_scan().encode();
96 let Bound::Included(start) = &range.start else {
97 panic!("expected an included start bound")
98 };
99 let Bound::Included(end) = &range.end else {
100 panic!("expected an included end bound")
101 };
102
103 let mut serializer = KeySerializer::with_capacity(9);
104 serializer.extend_u8(KeyTag::NamespaceQueue as u8).extend_u64(1u64);
105 let foreign = serializer.to_encoded_key();
106
107 assert!(
108 foreign.as_slice() < start.as_slice() || foreign.as_slice() > end.as_slice(),
109 "a NamespaceQueue key must fall outside the QueueKey scan range"
110 );
111 }
112}
113
114#[cfg(test)]
115mod byte_identical_check_queue_key {
116 use reifydb_codec::key::serializer::KeySerializer;
117
118 use super::*;
119
120 fn legacy_encode(key: &QueueKey) -> EncodedKey {
121 let mut serializer = KeySerializer::with_capacity(9);
122 serializer.extend_u8(KeyTag::Queue as u8).extend_u64(key.queue);
123 serializer.to_encoded_key()
124 }
125
126 #[test]
127 fn matches_legacy_byte_layout() {
128 for id in [QueueId(0), QueueId(1), QueueId(u64::MAX)] {
129 let key = QueueKey {
130 queue: id,
131 };
132 assert_eq!(legacy_encode(&key).as_slice(), key.encode().as_slice());
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
138#[key(tag = QueueAttempt)]
139pub struct QueueAttemptKey {
140 pub queue: QueueId,
141 pub row: RowNumber,
142 pub attempt: u32,
143}
144
145impl QueueAttemptKey {
146 pub fn new(queue: impl Into<QueueId>, row: impl Into<RowNumber>, attempt: u32) -> Self {
147 Self {
148 queue: queue.into(),
149 row: row.into(),
150 attempt,
151 }
152 }
153
154 pub fn encoded(queue: impl Into<QueueId>, row: impl Into<RowNumber>, attempt: u32) -> EncodedKey {
155 Self::new(queue, row, attempt).encode()
156 }
157
158 pub fn item_scan(queue: QueueId, row: RowNumber) -> TaggedKeyBoundRange {
159 TaggedKeyBoundRange::prefix(
160 Self::TAG,
161 [Field::UDesc(Width::U64, queue.0 as u128), Field::UDesc(Width::U64, row.0 as u128)],
162 )
163 }
164
165 pub fn queue_scan(queue: QueueId) -> TaggedKeyBoundRange {
166 TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, queue.0 as u128)])
167 }
168
169 pub fn full_scan() -> TaggedKeyBoundRange {
170 TaggedKeyBoundRange::kind(Self::TAG)
171 }
172}
173
174#[cfg(test)]
175mod queue_item_state_key_tests {
176 use std::ops::Bound;
177
178 use reifydb_codec::key::encoded::EncodedKeyRange;
179
180 use super::*;
181
182 fn contains(range: &EncodedKeyRange, key: &EncodedKey) -> bool {
183 let after_start = match &range.start {
184 Bound::Included(start) => key.as_slice() >= start.as_slice(),
185 Bound::Excluded(start) => key.as_slice() > start.as_slice(),
186 Bound::Unbounded => true,
187 };
188 let before_end = match &range.end {
189 Bound::Included(end) => key.as_slice() <= end.as_slice(),
190 Bound::Excluded(end) => key.as_slice() < end.as_slice(),
191 Bound::Unbounded => true,
192 };
193 after_start && before_end
194 }
195
196 #[test]
197 fn test_attempt_key_roundtrips() {
198 let key = QueueAttemptKey {
202 queue: QueueId(7),
203 row: RowNumber(42),
204 attempt: u32::MAX,
205 };
206
207 assert_eq!(QueueAttemptKey::decode(&key.encode()), Some(key));
208 }
209
210 #[test]
211 fn test_attempt_zero_roundtrips() {
212 let key = QueueAttemptKey {
216 queue: QueueId(0),
217 row: RowNumber(0),
218 attempt: 0,
219 };
220
221 assert_eq!(QueueAttemptKey::decode(&key.encode()), Some(key));
222 }
223
224 #[test]
225 fn test_item_scan_excludes_neighbouring_items_and_queues() {
226 let range = QueueAttemptKey::item_scan(QueueId(3), RowNumber(5)).encode();
230
231 assert!(contains(&range, &QueueAttemptKey::encoded(QueueId(3), RowNumber(5), 0)));
232 assert!(contains(&range, &QueueAttemptKey::encoded(QueueId(3), RowNumber(5), u32::MAX)));
233 assert!(!contains(&range, &QueueAttemptKey::encoded(QueueId(3), RowNumber(6), 0)));
234 assert!(!contains(&range, &QueueAttemptKey::encoded(QueueId(3), RowNumber(4), 0)));
235 assert!(!contains(&range, &QueueAttemptKey::encoded(QueueId(4), RowNumber(5), 0)));
236 }
237
238 #[test]
239 fn test_queue_scan_covers_every_item_of_one_queue_only() {
240 let range = QueueAttemptKey::queue_scan(QueueId(3)).encode();
244
245 assert!(contains(&range, &QueueAttemptKey::encoded(QueueId(3), RowNumber(0), 0)));
246 assert!(contains(&range, &QueueAttemptKey::encoded(QueueId(3), RowNumber(u64::MAX), 9)));
247 assert!(!contains(&range, &QueueAttemptKey::encoded(QueueId(2), RowNumber(1), 0)));
248 assert!(!contains(&range, &QueueAttemptKey::encoded(QueueId(4), RowNumber(1), 0)));
249 }
250
251 #[test]
252 fn test_a_foreign_kind_does_not_decode() {
253 let foreign = QueueItemStateKey::encoded(QueueId(1), 0, RowNumber(1));
256
257 assert_eq!(QueueAttemptKey::decode(&foreign), None);
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Hash)]
262pub struct QueueDeduplicationKey {
263 pub queue: QueueId,
264 pub tail: EncodedKey,
265}
266
267impl QueueDeduplicationKey {
268 pub fn new(queue: impl Into<QueueId>, tail: impl AsRef<[u8]>) -> Self {
269 Self {
270 queue: queue.into(),
271 tail: EncodedKey::new(tail),
272 }
273 }
274
275 pub fn encoded(queue: impl Into<QueueId>, tail: impl AsRef<[u8]>) -> EncodedKey {
276 Self::new(queue, tail).encode()
277 }
278
279 pub fn full_scan(queue: QueueId) -> TaggedKeyBoundRange {
280 TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, queue.0 as u128)])
281 }
282}
283
284impl QueueDeduplicationKey {
285 pub const TAG: KeyTag = KeyTag::QueueDeduplication;
286
287 pub fn encode(&self) -> EncodedKey {
288 let mut serializer = KeySerializer::with_capacity(9 + self.tail.len() + 1);
289 serializer.extend_u8(Self::TAG as u8).extend_u64(self.queue).extend_bytes(&self.tail);
290 serializer.to_encoded_key()
291 }
292
293 pub fn decode(key: &EncodedKey) -> Option<Self> {
294 let mut de = KeyDeserializer::from_bytes(key.as_slice());
295
296 let kind: KeyTag = de.read_u8().ok()?.try_into().ok()?;
297 if kind != Self::TAG {
298 return None;
299 }
300
301 let queue = de.read_u64().ok()?;
302 let tail = de.read_bytes().ok()?;
303
304 Some(Self {
305 queue: QueueId(queue),
306 tail: EncodedKey::new(tail),
307 })
308 }
309}
310
311#[cfg(test)]
312mod queue_deduplication_key_tests {
313 use std::ops::Bound;
314
315 use super::*;
316
317 #[test]
318 fn test_encode_decode_roundtrip() {
319 let encoded = QueueDeduplicationKey::encoded(QueueId(3), b"invoice-42".to_vec());
322 let decoded = QueueDeduplicationKey::decode(&encoded).unwrap();
323 assert_eq!(decoded.queue, QueueId(3));
324 assert_eq!(decoded.tail.as_slice(), b"invoice-42");
325 }
326
327 #[test]
328 fn test_arbitrary_bytes_survive_the_tail_encoding() {
329 for key in [
332 vec![],
333 vec![0x00],
334 vec![0xff],
335 vec![0xff, 0x00, 0xff],
336 "order/\u{00e9}\u{4e2d}".as_bytes().to_vec(),
337 ] {
338 let encoded = QueueDeduplicationKey::encoded(QueueId(1), key.clone());
339 let decoded = QueueDeduplicationKey::decode(&encoded).unwrap();
340 assert_eq!(decoded.tail.as_slice(), key.as_slice(), "tail {key:?} must round-trip unchanged");
341 }
342 }
343
344 #[test]
345 fn test_the_same_key_in_two_queues_encodes_differently() {
346 let a = QueueDeduplicationKey::encoded(QueueId(1), b"same".to_vec());
349 let b = QueueDeduplicationKey::encoded(QueueId(2), b"same".to_vec());
350 assert_ne!(a, b);
351 }
352
353 #[test]
354 fn test_full_scan_contains_only_the_target_queue() {
355 let range = QueueDeduplicationKey::full_scan(QueueId(3)).encode();
358 let Bound::Included(start) = &range.start else {
359 panic!("expected an included start bound")
360 };
361 let Bound::Excluded(end) = &range.end else {
362 panic!("expected an excluded end bound")
363 };
364
365 assert!(start.as_slice() < end.as_slice(), "the range must be non-empty under byte order");
366
367 for key in [vec![], b"a".to_vec(), vec![0xff; 64]] {
368 let inside = QueueDeduplicationKey::encoded(QueueId(3), key.clone());
369 assert!(
370 inside.as_slice() >= start.as_slice() && inside.as_slice() < end.as_slice(),
371 "key {key:?} in queue 3 must fall inside the scan range"
372 );
373 }
374
375 for queue in [QueueId(2), QueueId(4)] {
376 let neighbour = QueueDeduplicationKey::encoded(queue, b"a".to_vec());
377 assert!(
378 neighbour.as_slice() < start.as_slice() || neighbour.as_slice() >= end.as_slice(),
379 "queue {queue:?} must fall outside queue 3's scan range"
380 );
381 }
382 }
383
384 #[test]
385 fn test_a_foreign_or_truncated_key_does_not_decode() {
386 let encoded = QueueDeduplicationKey::encoded(QueueId(3), b"invoice-42".to_vec());
388
389 let mut wrong_kind = encoded.as_slice().to_vec();
390 wrong_kind[0] = KeyTag::Queue as u8;
391 assert_eq!(QueueDeduplicationKey::decode(&EncodedKey::new(wrong_kind)), None);
392
393 let truncated = encoded.as_slice()[..5].to_vec();
394 assert_eq!(QueueDeduplicationKey::decode(&EncodedKey::new(truncated)), None);
395 }
396}
397
398#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
399#[key(tag = QueuePartition)]
400pub struct QueuePartitionKey {
401 pub queue: QueueId,
402 pub partition: u16,
403}
404
405impl QueuePartitionKey {
406 pub fn new(queue: impl Into<QueueId>, partition: u16) -> Self {
407 Self {
408 queue: queue.into(),
409 partition,
410 }
411 }
412
413 pub fn encoded(queue: impl Into<QueueId>, partition: u16) -> EncodedKey {
414 Self {
415 queue: queue.into(),
416 partition,
417 }
418 .encode()
419 }
420
421 pub fn queue_scan(queue: QueueId) -> TaggedKeyBoundRange {
422 TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, queue.0 as u128)])
423 }
424
425 pub fn full_scan() -> TaggedKeyBoundRange {
426 TaggedKeyBoundRange::kind(Self::TAG)
427 }
428}
429
430#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
431#[key(tag = QueueItemState)]
432pub struct QueueItemStateKey {
433 pub queue: QueueId,
434 pub partition: u16,
435 pub row: RowNumber,
436}
437
438impl QueueItemStateKey {
439 pub fn new(queue: impl Into<QueueId>, partition: u16, row: impl Into<RowNumber>) -> Self {
440 Self {
441 queue: queue.into(),
442 partition,
443 row: row.into(),
444 }
445 }
446
447 pub fn encoded(queue: impl Into<QueueId>, partition: u16, row: impl Into<RowNumber>) -> EncodedKey {
448 Self {
449 queue: queue.into(),
450 partition,
451 row: row.into(),
452 }
453 .encode()
454 }
455
456 pub fn partition_scan(queue: QueueId, partition: u16) -> TaggedKeyBoundRange {
457 TaggedKeyBoundRange::prefix(
458 Self::TAG,
459 [Field::UDesc(Width::U64, queue.0 as u128), Field::UDesc(Width::U16, partition as u128)],
460 )
461 }
462
463 pub fn queue_scan(queue: QueueId) -> TaggedKeyBoundRange {
464 TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, queue.0 as u128)])
465 }
466
467 pub fn full_scan() -> TaggedKeyBoundRange {
468 TaggedKeyBoundRange::kind(Self::TAG)
469 }
470}
471
472#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
473#[key(tag = QueueDue)]
474pub struct QueueDueKey {
475 pub queue: QueueId,
476 pub partition: u16,
477 pub due: DateTime,
478 pub row: RowNumber,
479}
480
481impl QueueDueKey {
482 pub fn new(queue: impl Into<QueueId>, partition: u16, due: DateTime, row: impl Into<RowNumber>) -> Self {
483 Self {
484 queue: queue.into(),
485 partition,
486 due,
487 row: row.into(),
488 }
489 }
490
491 pub fn encoded(
492 queue: impl Into<QueueId>,
493 partition: u16,
494 due: DateTime,
495 row: impl Into<RowNumber>,
496 ) -> EncodedKey {
497 Self::new(queue, partition, due, row).encode()
498 }
499
500 pub fn partition_scan(queue: QueueId, partition: u16) -> TaggedKeyBoundRange {
501 TaggedKeyBoundRange::prefix(
502 Self::TAG,
503 [Field::UDesc(Width::U64, queue.0 as u128), Field::UDesc(Width::U16, partition as u128)],
504 )
505 }
506
507 pub fn queue_scan(queue: QueueId) -> TaggedKeyBoundRange {
508 TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, queue.0 as u128)])
509 }
510
511 pub fn full_scan() -> TaggedKeyBoundRange {
512 TaggedKeyBoundRange::kind(Self::TAG)
513 }
514}
515
516#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
517#[key(tag = QueueKeyActive)]
518pub struct QueueKeyActiveKey {
519 pub queue: QueueId,
520 pub partition: u16,
521 pub key_hash: u64,
522 pub row: RowNumber,
523}
524
525impl QueueKeyActiveKey {
526 pub fn new(queue: impl Into<QueueId>, partition: u16, key_hash: u64, row: impl Into<RowNumber>) -> Self {
527 Self {
528 queue: queue.into(),
529 partition,
530 key_hash,
531 row: row.into(),
532 }
533 }
534
535 pub fn encoded(
536 queue: impl Into<QueueId>,
537 partition: u16,
538 key_hash: u64,
539 row: impl Into<RowNumber>,
540 ) -> EncodedKey {
541 Self {
542 queue: queue.into(),
543 partition,
544 key_hash,
545 row: row.into(),
546 }
547 .encode()
548 }
549
550 pub fn key_scan(queue: QueueId, partition: u16, key_hash: u64) -> TaggedKeyBoundRange {
551 TaggedKeyBoundRange::prefix(
552 Self::TAG,
553 [
554 Field::UDesc(Width::U64, queue.0 as u128),
555 Field::UDesc(Width::U16, partition as u128),
556 Field::UDesc(Width::U64, key_hash as u128),
557 ],
558 )
559 }
560
561 pub fn partition_scan(queue: QueueId, partition: u16) -> TaggedKeyBoundRange {
562 TaggedKeyBoundRange::prefix(
563 Self::TAG,
564 [Field::UDesc(Width::U64, queue.0 as u128), Field::UDesc(Width::U16, partition as u128)],
565 )
566 }
567
568 pub fn queue_scan(queue: QueueId) -> TaggedKeyBoundRange {
569 TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, queue.0 as u128)])
570 }
571
572 pub fn full_scan() -> TaggedKeyBoundRange {
573 TaggedKeyBoundRange::kind(Self::TAG)
574 }
575}
576
577#[cfg(test)]
578mod queue_partition_key_tests {
579 use std::ops::Bound;
580
581 use reifydb_codec::key::encoded::EncodedKeyRange;
582
583 use super::*;
584
585 fn contains(range: &EncodedKeyRange, key: &EncodedKey) -> bool {
586 let after_start = match &range.start {
587 Bound::Included(start) => key.as_slice() >= start.as_slice(),
588 Bound::Excluded(start) => key.as_slice() > start.as_slice(),
589 Bound::Unbounded => true,
590 };
591 let before_end = match &range.end {
592 Bound::Included(end) => key.as_slice() <= end.as_slice(),
593 Bound::Excluded(end) => key.as_slice() < end.as_slice(),
594 Bound::Unbounded => true,
595 };
596 after_start && before_end
597 }
598
599 #[test]
600 fn test_partition_key_roundtrips_at_both_partition_bounds() {
601 for partition in [0u16, 1, 1023] {
605 let encoded = QueuePartitionKey::encoded(QueueId(7), partition);
606 let decoded = QueuePartitionKey::decode(&encoded).unwrap();
607 assert_eq!(decoded.queue, QueueId(7));
608 assert_eq!(decoded.partition, partition);
609 }
610 }
611
612 #[test]
613 fn test_item_state_key_roundtrips() {
614 let encoded = QueueItemStateKey::encoded(QueueId(3), 5, RowNumber(42));
617 let decoded = QueueItemStateKey::decode(&encoded).unwrap();
618 assert_eq!(decoded.queue, QueueId(3));
619 assert_eq!(decoded.partition, 5);
620 assert_eq!(decoded.row, RowNumber(42));
621 }
622
623 #[test]
624 fn test_due_key_roundtrips_at_epoch_and_far_future() {
625 for nanos in [0u64, 1, 4_102_444_800_000_000_000, u64::MAX] {
629 let due = DateTime::from_nanos(nanos);
630 let encoded = QueueDueKey::encoded(QueueId(1), 2, due, RowNumber(9));
631 let decoded = QueueDueKey::decode(&encoded).unwrap();
632 assert_eq!(decoded.due.to_nanos(), nanos);
633 assert_eq!(decoded.row, RowNumber(9));
634 assert_eq!(decoded.partition, 2);
635 }
636 }
637
638 #[test]
639 fn test_partition_scan_excludes_neighbouring_partitions() {
640 for partition in [0u16, 1, 1023] {
644 let range = QueueItemStateKey::partition_scan(QueueId(4), partition).encode();
645
646 assert!(contains(&range, &QueueItemStateKey::encoded(QueueId(4), partition, RowNumber(0))));
647 assert!(contains(
648 &range,
649 &QueueItemStateKey::encoded(QueueId(4), partition, RowNumber(u64::MAX))
650 ));
651
652 for other in [partition.wrapping_sub(1), partition + 1] {
653 if other == partition {
654 continue;
655 }
656 let neighbour = QueueItemStateKey::encoded(QueueId(4), other, RowNumber(1));
657 assert!(
658 !contains(&range, &neighbour),
659 "partition {other} must fall outside {partition}"
660 );
661 }
662
663 let other_queue = QueueItemStateKey::encoded(QueueId(5), partition, RowNumber(1));
664 assert!(!contains(&range, &other_queue), "queue 5 must fall outside queue 4's partition scan");
665 }
666 }
667
668 #[test]
669 fn test_queue_scan_covers_every_partition_of_one_queue_only() {
670 let range = QueueDueKey::queue_scan(QueueId(4)).encode();
673
674 for partition in [0u16, 1, 1023] {
675 let inside = QueueDueKey::encoded(QueueId(4), partition, DateTime::from_nanos(7), RowNumber(1));
676 assert!(contains(&range, &inside), "partition {partition} must fall inside the queue scan");
677 }
678
679 for queue in [QueueId(3), QueueId(5)] {
680 let outside = QueueDueKey::encoded(queue, 0, DateTime::from_nanos(7), RowNumber(1));
681 assert!(!contains(&range, &outside), "queue {queue:?} must fall outside queue 4's scan");
682 }
683 }
684
685 #[test]
686 fn test_due_keys_sort_latest_due_first() {
687 let earlier = QueueDueKey::encoded(QueueId(1), 0, DateTime::from_nanos(1_000), RowNumber(1));
691 let later = QueueDueKey::encoded(QueueId(1), 0, DateTime::from_nanos(2_000), RowNumber(1));
692
693 assert!(later.as_slice() < earlier.as_slice(), "the later due time must encode to the smaller key");
694 }
695
696 #[test]
697 fn test_a_foreign_kind_does_not_decode() {
698 let encoded = QueueItemStateKey::encoded(QueueId(1), 0, RowNumber(1));
701
702 assert_eq!(QueuePartitionKey::decode(&encoded), None);
703 assert_eq!(QueueDueKey::decode(&encoded), None);
704 assert_eq!(QueueKeyActiveKey::decode(&EncodedKey::new(encoded.as_slice()[..3].to_vec())), None);
705 }
706
707 #[test]
708 fn test_key_active_key_roundtrips() {
709 let encoded = QueueKeyActiveKey::encoded(QueueId(3), 5, 0xDEAD_BEEF_CAFE_F00D, RowNumber(42));
710 let decoded = QueueKeyActiveKey::decode(&encoded).unwrap();
711 assert_eq!(decoded.queue, QueueId(3));
712 assert_eq!(decoded.partition, 5);
713 assert_eq!(decoded.key_hash, 0xDEAD_BEEF_CAFE_F00D);
714 assert_eq!(decoded.row, RowNumber(42));
715 }
716
717 #[test]
718 fn test_key_active_keys_sort_largest_row_first() {
719 let first = QueueKeyActiveKey::encoded(QueueId(1), 0, 77, RowNumber(1));
720 let middle = QueueKeyActiveKey::encoded(QueueId(1), 0, 77, RowNumber(5));
721 let last = QueueKeyActiveKey::encoded(QueueId(1), 0, 77, RowNumber(9));
722
723 assert!(last.as_slice() < middle.as_slice());
724 assert!(middle.as_slice() < first.as_slice());
725 }
726
727 #[test]
728 fn test_key_scan_excludes_neighbouring_keys_and_partitions() {
729 let range = QueueKeyActiveKey::key_scan(QueueId(4), 2, 77).encode();
730
731 assert!(contains(&range, &QueueKeyActiveKey::encoded(QueueId(4), 2, 77, RowNumber(0))));
732 assert!(contains(&range, &QueueKeyActiveKey::encoded(QueueId(4), 2, 77, RowNumber(u64::MAX))));
733
734 for other_hash in [76u64, 78, 0, u64::MAX] {
735 let neighbour = QueueKeyActiveKey::encoded(QueueId(4), 2, other_hash, RowNumber(1));
736 assert!(!contains(&range, &neighbour), "key hash {other_hash} must fall outside key 77");
737 }
738
739 let other_partition = QueueKeyActiveKey::encoded(QueueId(4), 3, 77, RowNumber(1));
740 assert!(!contains(&range, &other_partition), "partition 3 must fall outside partition 2");
741
742 let other_queue = QueueKeyActiveKey::encoded(QueueId(5), 2, 77, RowNumber(1));
743 assert!(!contains(&range, &other_queue), "queue 5 must fall outside queue 4");
744 }
745
746 #[test]
747 fn test_partition_scan_covers_every_key_of_one_partition_only() {
748 let range = QueueKeyActiveKey::partition_scan(QueueId(4), 2).encode();
749
750 for key_hash in [0u64, 77, u64::MAX] {
751 let inside = QueueKeyActiveKey::encoded(QueueId(4), 2, key_hash, RowNumber(1));
752 assert!(contains(&range, &inside), "key hash {key_hash} must fall inside the partition scan");
753 }
754
755 let other_partition = QueueKeyActiveKey::encoded(QueueId(4), 3, 77, RowNumber(1));
756 assert!(!contains(&range, &other_partition));
757 }
758
759 #[test]
760 fn test_schedule_keys_match_legacy_byte_layout() {
761 let queue = QueueId(7);
762 let partition = 3u16;
763 let row = RowNumber(42);
764
765 let mut legacy = KeySerializer::with_capacity(11);
766 legacy.extend_u8(KeyTag::QueuePartition as u8).extend_u64(queue).extend_u16(partition);
767 assert_eq!(legacy.to_encoded_key().as_slice(), QueuePartitionKey::encoded(queue, partition).as_slice());
768
769 let mut legacy = KeySerializer::with_capacity(19);
770 legacy.extend_u8(KeyTag::QueueItemState as u8)
771 .extend_u64(queue)
772 .extend_u16(partition)
773 .extend_u64(row.0);
774 assert_eq!(
775 legacy.to_encoded_key().as_slice(),
776 QueueItemStateKey::encoded(queue, partition, row).as_slice()
777 );
778
779 let due = DateTime::from_nanos(1_000);
780 let mut legacy = KeySerializer::with_capacity(27);
781 legacy.extend_u8(KeyTag::QueueDue as u8)
782 .extend_u64(queue)
783 .extend_u16(partition)
784 .extend_datetime(&due)
785 .extend_u64(row.0);
786 assert_eq!(
787 legacy.to_encoded_key().as_slice(),
788 QueueDueKey::encoded(queue, partition, due, row).as_slice()
789 );
790
791 let key_hash = 0xDEAD_BEEFu64;
792 let mut legacy = KeySerializer::with_capacity(28);
793 legacy.extend_u8(KeyTag::QueueKeyActive as u8)
794 .extend_u64(queue)
795 .extend_u16(partition)
796 .extend_u64(key_hash)
797 .extend_u64(row.0);
798 assert_eq!(
799 legacy.to_encoded_key().as_slice(),
800 QueueKeyActiveKey::encoded(queue, partition, key_hash, row).as_slice()
801 );
802 }
803}
804
805impl KeyFields for QueueDeduplicationKey {
806 fn fields(&self) -> SmallVec<[Field<'_>; 6]> {
807 smallvec![
808 Field::UDesc(Width::U64, self.queue.0 as u128),
809 Field::BytesDesc(ByteEncoding::Escaped, Cow::Borrowed(self.tail.as_slice())),
810 ]
811 }
812}