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