1use {
10 super::{Segment, header_walk::HeaderWalk as _},
11 crate::{
12 config::DEFAULT_TIMESTAMP_SCAN_WINDOW, error::LogError, record_limit::check_records_read,
13 },
14 krabka_ids::Offset,
15 krabka_protocol::records::{RecordBatch, TimestampType},
16 krabka_units::prelude::{ByteSize, ByteSizeExt, bytes},
17 krabka_verified::timestamp::{first_timestamp_record_index, timestamp_record_time},
18 std::ops::ControlFlow,
19};
20
21impl Segment {
22 pub(crate) fn first_record_timestamp(&mut self) -> Option<i64> {
25 if self.first_timestamp.is_none() {
26 self.first_timestamp = self.offset_for_timestamp(i64::MIN).map(|(_, ts)| ts);
27 }
28 self.first_timestamp
29 }
30
31 #[must_use]
45 pub fn offset_for_timestamp(&self, target_ts: i64) -> Option<(Offset, i64)> {
46 self.offset_for_timestamp_with_window(target_ts, DEFAULT_TIMESTAMP_SCAN_WINDOW, None)
48 .ok()
49 .flatten()
50 }
51
52 pub(crate) fn offset_for_timestamp_with_window(
66 &self,
67 target_ts: i64,
68 scan_window: ByteSize,
69 limit: Option<ByteSize>,
70 ) -> Result<Option<(Offset, i64)>, LogError> {
71 self.offset_for_timestamp_with_window_from(target_ts, scan_window, limit, self.base_offset)
72 }
73
74 pub(crate) fn offset_for_timestamp_with_window_from(
82 &self,
83 target_ts: i64,
84 scan_window: ByteSize,
85 limit: Option<ByteSize>,
86 minimum: Offset,
87 ) -> Result<Option<(Offset, i64)>, LogError> {
88 let Some(scan_from) = self.timestamp_scan_floor(target_ts) else {
89 return Ok(None);
90 };
91 self.scan_from_floor_windowed_from(
92 scan_from.max(minimum),
93 scan_window,
94 target_ts,
95 limit,
96 minimum,
97 )
98 }
99
100 #[cfg(test)]
103 pub(crate) fn offset_of_max_timestamp(&self) -> Option<(Offset, i64)> {
104 self.offset_of_max_timestamp_with_window(DEFAULT_TIMESTAMP_SCAN_WINDOW, None)
105 .expect("a lookup with no limit refuses nothing")
106 }
107
108 pub(crate) fn offset_of_max_timestamp_with_window(
124 &self,
125 scan_window: ByteSize,
126 limit: Option<ByteSize>,
127 ) -> Result<Option<(Offset, i64)>, LogError> {
128 if self.max_timestamp == i64::MIN {
129 let found = self.scan_max_timestamp_windowed(scan_window);
130 if let Some((_, timestamp)) = found
134 && limit.is_some()
135 {
136 self.scan_from_floor_windowed(self.base_offset, scan_window, timestamp, limit)?;
137 }
138 return Ok(found);
139 }
140 let Some(scan_from) = self.timestamp_scan_floor(self.max_timestamp) else {
141 return Ok(None);
142 };
143 self.scan_from_floor_windowed(scan_from, scan_window, self.max_timestamp, limit)
148 }
149
150 fn timestamp_scan_floor(&self, target: i64) -> Option<Offset> {
151 self.base_offset
152 .0
153 .checked_add(i64::from(self.time_index.scan_start(target)))
154 .map(Offset)
155 }
156
157 fn scan_max_timestamp_windowed(&self, window_size: ByteSize) -> Option<(Offset, i64)> {
162 let mut cursor = self.base_offset;
163 let mut window = window_size.max(bytes(1));
164 let mut best: Option<(Offset, i64)> = None;
165 loop {
166 if cursor > self.last_offset {
167 return best;
168 }
169 let batches = self.read(cursor, window).ok()?;
170 let Some(last) = batches.last() else {
171 let current = u32::try_from(window.bytes_u64()).ok()?;
172 window = bytes(krabka_verified::timestamp_scan_window(current)?);
173 continue;
174 };
175 for batch in &batches {
176 let records = Self::timestamp_records(batch)?;
177 let timestamps: Vec<_> = records.iter().map(|(_, timestamp)| *timestamp).collect();
178 if let Some(&candidate) = krabka_verified::earliest_max_timestamp_index(×tamps)
179 .and_then(|index| records.get(index))
180 && best.is_none_or(|(_, best_timestamp)| candidate.1 > best_timestamp)
181 {
182 best = Some((Offset(candidate.0), candidate.1));
183 }
184 }
185 cursor = Offset(krabka_verified::timestamp_scan_next(
186 cursor.0,
187 last.base_offset,
188 last.last_offset_delta,
189 )?);
190 }
191 }
192
193 fn scan_from_floor_windowed(
214 &self,
215 floor_offset: Offset,
216 window_size: ByteSize,
217 target_ts: i64,
218 limit: Option<ByteSize>,
219 ) -> Result<Option<(Offset, i64)>, LogError> {
220 self.scan_from_floor_windowed_from(
221 floor_offset,
222 window_size,
223 target_ts,
224 limit,
225 self.base_offset,
226 )
227 }
228
229 fn scan_from_floor_windowed_from(
230 &self,
231 floor_offset: Offset,
232 window_size: ByteSize,
233 target_ts: i64,
234 limit: Option<ByteSize>,
235 minimum: Offset,
236 ) -> Result<Option<(Offset, i64)>, LogError> {
237 let mut cursor = floor_offset;
238 let mut window = window_size.max(bytes(1));
239 loop {
240 if cursor > self.last_offset {
241 return Ok(None);
242 }
243 let Ok(batches) = self.read(cursor, window) else {
244 return Ok(None);
245 };
246 let Some(last) = batches.last() else {
247 let Some(grown) = u32::try_from(window.bytes_u64())
251 .ok()
252 .and_then(krabka_verified::timestamp_scan_window)
253 else {
254 return Ok(None);
255 };
256 window = bytes(grown);
257 continue;
258 };
259 for batch in &batches {
260 let Some(records) = Self::timestamp_records(batch) else {
261 return Ok(None);
262 };
263 let found = first_timestamp_record_index(&records, minimum.0, target_ts);
264 if batch.max_timestamp >= target_ts {
265 let read = found.map_or(batch.records.len(), |index| index + 1);
266 check_records_read(batch, read, limit)?;
267 }
268 if let Some(&(offset, timestamp)) = found.and_then(|index| records.get(index)) {
269 return Ok(Some((Offset(offset), timestamp)));
270 }
271 }
272 let Some(next) = krabka_verified::timestamp_scan_next(
276 cursor.0,
277 last.base_offset,
278 last.last_offset_delta,
279 ) else {
280 return Ok(None);
281 };
282 cursor = Offset(next);
283 }
284 }
285
286 fn timestamp_records(batch: &RecordBatch) -> Option<Vec<(i64, i64)>> {
299 let append_time = (batch.attributes.timestamp_type() == TimestampType::LogAppendTime)
300 .then_some(batch.max_timestamp);
301 batch
302 .records
303 .iter()
304 .map(|record| {
305 let timestamp = timestamp_record_time(
306 batch.base_timestamp,
307 record.timestamp_delta,
308 append_time,
309 )?;
310 krabka_verified::timestamp_record_coordinates(
311 batch.base_offset,
312 record.offset_delta,
313 timestamp,
314 0,
315 )
316 })
317 .collect()
318 }
319
320 pub fn restore_max_timestamp(&mut self) -> Result<(), LogError> {
339 let (mut max_timestamp, mut max_timestamp_offset) =
343 self.time_index
344 .last_entry()
345 .map_or((i64::MIN, self.base_offset - 1), |entry| {
346 (
347 entry.timestamp,
348 self.base_offset + i64::from(entry.relative_offset),
349 )
350 });
351 let scan_from = self
352 .offset_index
353 .last_entry()
354 .map_or(0, |entry| u64::from(entry.position));
355 self.walk_batch_headers(scan_from, |view| {
356 if view.max_timestamp > max_timestamp {
357 max_timestamp = view.max_timestamp;
358 max_timestamp_offset = view.last_offset;
359 }
360 ControlFlow::Continue(())
361 })?;
362 if max_timestamp > self.max_timestamp {
363 self.max_timestamp = max_timestamp;
364 self.max_timestamp_offset = max_timestamp_offset;
365 }
366 Ok(())
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use {
373 super::*,
374 crate::segment::test_support::{
375 DENSE_INDEX, FIVE_OFFSET_TIMESTAMP_BATCHES, ONE_RECORD_BATCH, TempSegment,
376 sample_batch, seeded_fixture, segment_at, test_segment,
377 },
378 assert2::check,
379 bytes::Bytes,
380 krabka_protocol::records::{Record, RecordBatch},
381 krabka_units::prelude::kibibytes,
382 tempfile::tempdir,
383 };
384
385 fn one_record_batch() -> RecordBatch {
386 sample_batch()
387 .offset(ONE_RECORD_BATCH.offset.0)
388 .records(ONE_RECORD_BATCH.records.0)
389 .timestamp(ONE_RECORD_BATCH.timestamp.0)
390 .call()
391 }
392
393 #[yare::parameterized(
394 none_create_time = {
395 krabka_compression::CompressionType::None,
396 TimestampType::CreateTime,
397 },
398 none_log_append_time = {
399 krabka_compression::CompressionType::None,
400 TimestampType::LogAppendTime,
401 },
402 lz4_create_time = {
403 krabka_compression::CompressionType::Lz4,
404 TimestampType::CreateTime,
405 },
406 lz4_log_append_time = {
407 krabka_compression::CompressionType::Lz4,
408 TimestampType::LogAppendTime,
409 },
410 )]
411 fn first_record_timestamp_survives_appends_and_reloads_after_truncation(
412 codec: krabka_compression::CompressionType,
413 timestamp_type: TimestampType,
414 ) {
415 let TempSegment {
416 dir,
417 segment: mut seg,
418 } = test_segment();
419 assert2::assert!(seg.first_record_timestamp().is_none());
420 let mut batch = sample_batch().records(2).call();
421 batch.records[0].timestamp_delta = -5;
422 batch.attributes = batch
423 .attributes
424 .with_compression(codec)
425 .with_timestamp_type(timestamp_type);
426 let expected = if timestamp_type == TimestampType::LogAppendTime {
427 batch.max_timestamp
428 } else {
429 -5
430 };
431 seg.append(&batch, DENSE_INDEX).unwrap();
432 assert2::assert!(seg.first_record_timestamp() == Some(expected));
433 seg.append(&sample_batch().offset(2).timestamp(100).call(), DENSE_INDEX)
434 .unwrap();
435 assert2::assert!(seg.first_record_timestamp() == Some(expected));
436 drop(seg);
437 let mut seg = Segment::open_active(dir.path(), Offset(0), true).unwrap();
438 assert2::assert!(seg.first_record_timestamp() == Some(expected));
439 seg.truncate_to_relative(2).unwrap();
440 assert2::assert!(seg.first_record_timestamp() == Some(expected));
441 seg.truncate_to_relative(0).unwrap();
442 assert2::assert!(seg.first_record_timestamp().is_none());
443 seg.append(&sample_batch().timestamp(200).call(), DENSE_INDEX)
444 .unwrap();
445 assert2::assert!(seg.first_record_timestamp() == Some(200));
446 }
447
448 #[test]
452 fn the_windowed_scan_reports_the_first_offset_at_the_maximum() {
453 let dir = tempdir().unwrap();
454 let mut seg = Segment::create(dir.path(), Offset(10)).unwrap();
457 let mut batch = RecordBatch {
458 base_offset: 10,
459 base_timestamp: 500,
460 max_timestamp: 502,
461 last_offset_delta: 2,
462 ..RecordBatch::default()
463 };
464 for (delta, ts_delta) in [(0i32, 0i64), (1, 2), (2, 2)] {
467 batch.records.push(Record {
468 offset_delta: delta,
469 timestamp_delta: ts_delta,
470 key: Some(Bytes::from(format!("k{delta}"))),
471 value: Some(Bytes::from(format!("v{delta}"))),
472 ..Default::default()
473 });
474 }
475 seg.append(&batch, DENSE_INDEX).unwrap();
476
477 let found = seg.scan_max_timestamp_windowed(kibibytes(64));
478 check!(found == Some((Offset(11), 502)), "got {found:?}");
479
480 seg.append(
482 &sample_batch().offset(13).timestamp(600).call(),
483 DENSE_INDEX,
484 )
485 .unwrap();
486 seg.max_timestamp = i64::MIN;
487 let found_last = seg.scan_max_timestamp_windowed(bytes(1));
488 check!(found_last == Some((Offset(13), 600)));
489
490 let found_via_api = seg.offset_of_max_timestamp();
492 check!(found_via_api == Some((Offset(13), 600)));
493 }
494
495 #[test]
496 fn malformed_record_coordinates_fail_the_scan_closed() {
497 let offset_overflow = RecordBatch {
498 base_offset: i64::MAX,
499 base_timestamp: 0,
500 records: vec![Record {
501 offset_delta: 1,
502 ..Default::default()
503 }],
504 ..Default::default()
505 };
506 let timestamp_overflow = RecordBatch {
507 base_offset: 0,
508 base_timestamp: i64::MAX,
509 records: vec![Record {
510 timestamp_delta: 1,
511 ..Default::default()
512 }],
513 ..Default::default()
514 };
515
516 assert2::assert!(Segment::timestamp_records(&offset_overflow).is_none());
517 assert2::assert!(Segment::timestamp_records(×tamp_overflow).is_none());
518 let mut append_time = timestamp_overflow;
519 append_time.attributes = append_time
520 .attributes
521 .with_timestamp_type(TimestampType::LogAppendTime);
522 append_time.max_timestamp = 2_400;
523 assert2::assert!(Segment::timestamp_records(&append_time) == Some(vec![(0, 2_400)]));
524 append_time.base_offset = i64::MAX;
525 append_time.records[0].offset_delta = 1;
526 assert2::assert!(Segment::timestamp_records(&append_time).is_none());
527 }
528
529 #[test]
530 fn malformed_or_stale_time_index_floor_fails_closed() {
531 let TempSegment {
532 dir: _dir,
533 segment: mut stale,
534 } = test_segment();
535 stale.append(&one_record_batch(), DENSE_INDEX).unwrap();
536 stale.time_index.append(200, u32::MAX).unwrap();
537 assert2::assert!(stale.offset_for_timestamp(200).is_none());
538
539 let TempSegment {
540 dir: _dir2,
541 segment: mut overflowing,
542 } = segment_at(Offset(i64::MAX));
543 overflowing.last_offset = Offset(i64::MAX);
544 overflowing.time_index.append(0, 1).unwrap();
545 assert2::assert!(overflowing.offset_for_timestamp(0).is_none());
546 }
547
548 #[test]
549 fn truncated_log_failure_exhausts_the_retry_window() {
550 let TempSegment {
551 dir: _dir,
552 segment: seg,
553 } = seeded_fixture().batches(&[ONE_RECORD_BATCH]).call();
554 seg.log_file.set_len(1).unwrap();
555
556 assert2::assert!(
557 seg.offset_for_timestamp_with_window(100, bytes(u32::MAX), None)
558 .unwrap()
559 .is_none()
560 );
561 }
562
563 #[yare::parameterized(
564 first_exact = { 100, Some((Offset(0), 100)) },
565 within_first_batch = { 101, Some((Offset(1), 101)) },
566 between_batches = { 150, Some((Offset(3), 200)) },
567 last_exact = { 201, Some((Offset(4), 201)) },
568 past_end = { 202, None },
569 )]
570 fn offset_for_timestamp_finds_first_ge(ts: i64, want: Option<(Offset, i64)>) {
571 let TempSegment { dir, segment: seg } = seeded_fixture()
573 .batches(FIVE_OFFSET_TIMESTAMP_BATCHES)
574 .call();
575 check!(seg.offset_for_timestamp(ts) == want, "ts={ts}");
579 drop(dir);
580 }
581
582 #[yare::parameterized(
583 match_at_final_record = { 1_049, Some((Offset(49), 1_049)) },
584 no_matching_record = { 10_001, None },
585 )]
586 fn scan_from_floor_finds_match_beyond_first_window(
587 threshold: i64,
588 expected: Option<(Offset, i64)>,
589 ) {
590 let TempSegment {
591 dir,
592 segment: mut seg,
593 } = test_segment();
594 let n = 50i64;
598 for off in 0..n {
599 let mut b = RecordBatch {
600 base_offset: off,
601 base_timestamp: 1_000 + off,
602 max_timestamp: 1_000 + off,
603 last_offset_delta: 0,
604 ..RecordBatch::default()
605 };
606 b.records.push(Record {
607 offset_delta: 0,
608 timestamp_delta: 0,
609 value: Some(Bytes::from(format!("v{off}"))),
610 ..Default::default()
611 });
612 seg.append(&b, DENSE_INDEX).unwrap();
613 }
614 assert2::assert!(
618 seg.scan_from_floor_windowed(Offset(0), bytes(1), threshold, None)
619 .unwrap()
620 == expected
621 );
622 drop(dir);
623 }
624
625 #[test]
626 fn scan_returns_absolute_offset_of_matching_record() {
627 const WINDOW: ByteSize = kibibytes(64);
630 let TempSegment {
631 dir,
632 segment: mut seg,
633 } = test_segment();
634 seg.append(&one_record_batch(), DENSE_INDEX).unwrap();
641 seg.append(
642 &sample_batch().offset(1).records(3).timestamp(200).call(),
643 DENSE_INDEX,
644 )
645 .unwrap();
646 let got = seg
647 .scan_from_floor_windowed(Offset(0), WINDOW, 202, None)
648 .unwrap();
649 assert2::assert!(got == Some((Offset(3), 202)));
650 drop(dir);
651 }
652
653 #[test]
654 fn offset_of_max_timestamp_earliest_on_tie() {
655 let TempSegment { dir, segment: seg } = seeded_fixture()
658 .batches(FIVE_OFFSET_TIMESTAMP_BATCHES)
659 .call();
660 assert2::assert!(seg.offset_of_max_timestamp() == Some((Offset(4), 201)));
661
662 let TempSegment {
664 dir: dir2,
665 segment: empty,
666 } = test_segment();
667 assert2::assert!(empty.offset_of_max_timestamp() == None);
668 drop(dir);
669 drop(dir2);
670 }
671
672 #[test]
673 fn offset_of_max_timestamp_tie_picks_earliest() {
674 let TempSegment {
675 dir,
676 segment: mut seg,
677 } = test_segment();
678 let mut b = RecordBatch {
680 base_offset: 0,
681 base_timestamp: 500,
682 max_timestamp: 500,
683 last_offset_delta: 2,
684 ..RecordBatch::default()
685 };
686 for i in 0..3 {
687 b.records.push(Record {
688 offset_delta: i,
689 timestamp_delta: 0,
690 value: Some(Bytes::from("v")),
691 ..Default::default()
692 });
693 }
694 seg.append(&b, DENSE_INDEX).unwrap();
695 assert2::assert!(seg.offset_of_max_timestamp() == Some((Offset(0), 500)));
696 drop(dir);
697 }
698}
699
700#[cfg(test)]
703mod record_limit_tests {
704 use {
705 super::*,
706 crate::segment::test_support::{DENSE_INDEX, TempSegment, test_segment},
707 assert2::check,
708 bytes::Bytes,
709 krabka_compression::CompressionType,
710 krabka_protocol::records::{Attributes, Record, RecordBatch},
711 krabka_units::prelude::{ByteSize, bytes, kibibytes},
712 };
713
714 const LARGE: usize = 1_007;
718 const LIMIT: ByteSize = bytes(100);
719 const WINDOW: ByteSize = kibibytes(64);
720
721 #[derive(Clone, Copy)]
723 struct TimedRecord {
724 timestamp: i64,
725 value_len: usize,
726 }
727
728 fn batch(base_offset: i64, records: &[TimedRecord], codec: CompressionType) -> RecordBatch {
731 let base_timestamp = records[0].timestamp;
732 RecordBatch {
733 base_offset,
734 base_timestamp,
735 max_timestamp: records.iter().map(|record| record.timestamp).max().unwrap(),
736 last_offset_delta: i32::try_from(records.len()).unwrap() - 1,
737 attributes: Attributes::default().with_compression(codec),
738 records: records
739 .iter()
740 .enumerate()
741 .map(|(delta, record)| Record {
742 offset_delta: i32::try_from(delta).unwrap(),
743 timestamp_delta: record.timestamp - base_timestamp,
744 value: Some(Bytes::from(vec![7_u8; record.value_len])),
745 ..Default::default()
746 })
747 .collect(),
748 ..Default::default()
749 }
750 }
751
752 fn segment(batches: &[RecordBatch]) -> TempSegment {
753 let TempSegment {
754 dir,
755 segment: mut seg,
756 } = test_segment();
757 for batch in batches {
758 seg.append(batch, DENSE_INDEX).unwrap();
759 }
760 TempSegment { dir, segment: seg }
761 }
762
763 type Lookup = Result<Option<(Offset, i64)>, usize>;
766
767 fn answer(result: Result<Option<(Offset, i64)>, LogError>) -> Lookup {
768 match result {
769 Ok(found) => Ok(found),
770 Err(LogError::RecordTooLarge { size, limit }) => {
771 check!(limit == LIMIT.bytes_usize());
772 Err(size)
773 }
774 Err(other) => panic!("unexpected error: {other}"),
775 }
776 }
777
778 #[yare::parameterized(
783 before_oversized_batch = {
784 "found before the oversized batch",
785 CompressionType::Gzip,
786 Some(LIMIT),
787 1_000,
788 Ok(Some((Offset(0), 1_000))),
789 },
790 oversized_match = {
791 "the oversized record is the match",
792 CompressionType::Gzip,
793 Some(LIMIT),
794 1_001,
795 Err(LARGE),
796 },
797 gzip_without_limit = {
798 "no limit",
799 CompressionType::Gzip,
800 None,
801 1_001,
802 Ok(Some((Offset(1), 1_001))),
803 },
804 uncompressed = {
805 "an uncompressed batch is never held to it",
806 CompressionType::None,
807 Some(LIMIT),
808 1_001,
809 Ok(Some((Offset(1), 1_001))),
810 },
811 )]
812 fn a_lookup_by_timestamp_refuses_only_the_records_kafka_reads(
813 name: &str,
814 codec: CompressionType,
815 limit: Option<ByteSize>,
816 target: i64,
817 expected: Lookup,
818 ) {
819 let TempSegment {
820 dir: _dir,
821 segment: seg,
822 } = segment(&[
823 batch(
824 0,
825 &[TimedRecord {
826 timestamp: 1_000,
827 value_len: 10,
828 }],
829 codec,
830 ),
831 batch(
832 1,
833 &[TimedRecord {
834 timestamp: 1_001,
835 value_len: 1_000,
836 }],
837 codec,
838 ),
839 ]);
840 let found = seg.offset_for_timestamp_with_window(target, WINDOW, limit);
841 check!(answer(found) == expected, "{name}");
842 }
843
844 #[yare::parameterized(
847 first_record = { 1_000, Ok(Some((Offset(0), 1_000))) },
848 oversized_match = { 1_001, Err(LARGE) },
849 oversized_before_match = { 1_002, Err(LARGE) },
850 skip_below_maximum = { 1_003, Ok(None) },
851 )]
852 fn a_lookup_stops_reading_a_batch_at_the_record_it_returns(target: i64, expected: Lookup) {
853 let TempSegment {
854 dir: _dir,
855 segment: seg,
856 } = segment(&[batch(
857 0,
858 &[
859 TimedRecord {
860 timestamp: 1_000,
861 value_len: 10,
862 },
863 TimedRecord {
864 timestamp: 1_001,
865 value_len: 1_000,
866 },
867 TimedRecord {
868 timestamp: 1_002,
869 value_len: 10,
870 },
871 ],
872 CompressionType::Gzip,
873 )]);
874 let found = seg.offset_for_timestamp_with_window(target, WINDOW, Some(LIMIT));
875 check!(answer(found) == expected, "target {target}");
876 }
877
878 #[test]
881 fn a_max_timestamp_lookup_reads_only_the_batch_holding_the_maximum() {
882 let gzip = CompressionType::Gzip;
883 let TempSegment {
884 dir: _dir,
885 segment: mut seg,
886 } = segment(&[
887 batch(
888 0,
889 &[TimedRecord {
890 timestamp: 1_000,
891 value_len: 1_000,
892 }],
893 gzip,
894 ),
895 batch(
896 1,
897 &[TimedRecord {
898 timestamp: 1_001,
899 value_len: 10,
900 }],
901 gzip,
902 ),
903 ]);
904 let lookup =
905 |seg: &Segment| answer(seg.offset_of_max_timestamp_with_window(WINDOW, Some(LIMIT)));
906 check!(lookup(&seg) == Ok(Some((Offset(1), 1_001))));
908
909 seg.append(
911 &batch(
912 2,
913 &[TimedRecord {
914 timestamp: 1_002,
915 value_len: 1_000,
916 }],
917 gzip,
918 ),
919 DENSE_INDEX,
920 )
921 .unwrap();
922 check!(lookup(&seg) == Err(LARGE));
923
924 seg.max_timestamp = i64::MIN;
927 check!(lookup(&seg) == Err(LARGE));
928 check!(
929 seg.offset_of_max_timestamp_with_window(WINDOW, None)
930 .unwrap()
931 == Some((Offset(2), 1_002))
932 );
933 }
934}