1use reddb_file::{
2 decode_main_wal_record_frame_with_authority, encode_main_wal_record_frame_into,
3 encode_main_wal_record_frame_with_authority_into, MainWalRecordFrame, MainWalRecordFrameRef,
4 WAL_FILE_VERSION,
5};
6use std::io::{self, Read};
7
8pub const WAL_DEFAULT_TERM: u64 = crate::replication::DEFAULT_REPLICATION_TERM;
9
10pub use reddb_file::MainWalRecordAuthority as WalRecordAuthority;
11pub use reddb_file::MainWalRecordType as RecordType;
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum WalRecord {
16 PageOwnership {
18 record: reddb_file::page_ownership_journal::PageOwnershipRecord,
19 },
20 Begin { tx_id: u64 },
22 Commit { tx_id: u64 },
24 Rollback { tx_id: u64 },
26 PageWrite {
29 tx_id: u64,
30 page_id: u32,
31 data: Vec<u8>,
32 },
33 TxCommitBatch { tx_id: u64, actions: Vec<Vec<u8>> },
36 FullPageImage {
40 tx_id: u64,
41 page_id: u32,
42 ckpt_epoch: u64,
43 data: Vec<u8>,
44 },
45 VectorInsert {
48 collection: String,
49 entity_id: u64,
50 vector: Vec<f32>,
51 },
52 ProbabilisticDelta {
56 kind: u8,
57 operation: u8,
58 name: String,
59 operands: Vec<Vec<u8>>,
60 },
61 Checkpoint { lsn: u64 },
63}
64
65impl WalRecord {
66 pub fn encode(&self) -> Vec<u8> {
70 self.encode_with_term(WAL_DEFAULT_TERM)
71 }
72
73 pub fn encode_with_term(&self, term: u64) -> Vec<u8> {
76 let mut buf = Vec::new();
77 self.encode_with_term_into(&mut buf, term);
78 buf
79 }
80
81 pub fn encode_into(&self, out: &mut Vec<u8>) {
92 self.encode_with_term_into(out, WAL_DEFAULT_TERM)
93 }
94
95 pub fn encode_with_term_into(&self, out: &mut Vec<u8>, term: u64) {
106 encode_main_wal_record_frame_into(self.to_file_frame_ref(), term, out)
107 .expect("main WAL record cannot be encoded");
108 }
109
110 pub fn encode_with_authority_into(&self, out: &mut Vec<u8>, authority: WalRecordAuthority) {
111 encode_main_wal_record_frame_with_authority_into(self.to_file_frame_ref(), authority, out)
112 .expect("main WAL record cannot be encoded");
113 }
114
115 pub fn read<R: Read>(reader: &mut R) -> io::Result<Option<WalRecord>> {
120 Ok(Self::read_with_term(reader)?.map(|(_, record)| record))
121 }
122
123 pub fn read_with_term<R: Read>(reader: &mut R) -> io::Result<Option<(u64, WalRecord)>> {
125 Self::read_with_format_version(reader, WAL_FILE_VERSION)
126 }
127
128 pub fn read_with_authority<R: Read>(
131 reader: &mut R,
132 ) -> io::Result<Option<(WalRecordAuthority, WalRecord)>> {
133 Self::read_with_authority_format_version(reader, WAL_FILE_VERSION)
134 }
135
136 pub(crate) fn read_with_format_version<R: Read>(
137 reader: &mut R,
138 format_version: u8,
139 ) -> io::Result<Option<(u64, WalRecord)>> {
140 Ok(
141 Self::read_with_authority_format_version(reader, format_version)?
142 .map(|(authority, record)| (authority.term, record)),
143 )
144 }
145
146 pub(crate) fn read_with_authority_format_version<R: Read>(
147 reader: &mut R,
148 format_version: u8,
149 ) -> io::Result<Option<(WalRecordAuthority, WalRecord)>> {
150 Ok(decode_main_wal_record_frame_with_authority(
151 reader,
152 format_version,
153 WalRecordAuthority {
154 term: WAL_DEFAULT_TERM,
155 ownership_epoch: None,
156 },
157 )?
158 .map(|(authority, frame)| (authority, WalRecord::from_file_frame(frame))))
159 }
160}
161
162impl WalRecord {
163 fn to_file_frame_ref(&self) -> MainWalRecordFrameRef<'_> {
167 match self {
168 WalRecord::PageOwnership { record } => {
169 MainWalRecordFrameRef::PageOwnership { record: *record }
170 }
171 WalRecord::Begin { tx_id } => MainWalRecordFrameRef::Begin { tx_id: *tx_id },
172 WalRecord::Commit { tx_id } => MainWalRecordFrameRef::Commit { tx_id: *tx_id },
173 WalRecord::Rollback { tx_id } => MainWalRecordFrameRef::Rollback { tx_id: *tx_id },
174 WalRecord::PageWrite {
175 tx_id,
176 page_id,
177 data,
178 } => MainWalRecordFrameRef::PageWrite {
179 tx_id: *tx_id,
180 page_id: *page_id,
181 data,
182 },
183 WalRecord::TxCommitBatch { tx_id, actions } => MainWalRecordFrameRef::TxCommitBatch {
184 tx_id: *tx_id,
185 actions,
186 },
187 WalRecord::FullPageImage {
188 tx_id,
189 page_id,
190 ckpt_epoch,
191 data,
192 } => MainWalRecordFrameRef::FullPageImage {
193 tx_id: *tx_id,
194 page_id: *page_id,
195 ckpt_epoch: *ckpt_epoch,
196 data,
197 },
198 WalRecord::VectorInsert {
199 collection,
200 entity_id,
201 vector,
202 } => MainWalRecordFrameRef::VectorInsert {
203 collection,
204 entity_id: *entity_id,
205 vector,
206 },
207 WalRecord::ProbabilisticDelta {
208 kind,
209 operation,
210 name,
211 operands,
212 } => MainWalRecordFrameRef::ProbabilisticDelta {
213 kind: *kind,
214 operation: *operation,
215 name,
216 operands,
217 },
218 WalRecord::Checkpoint { lsn } => MainWalRecordFrameRef::Checkpoint { lsn: *lsn },
219 }
220 }
221
222 #[cfg(test)]
225 fn to_file_frame(&self) -> MainWalRecordFrame {
226 match self {
227 WalRecord::PageOwnership { record } => {
228 MainWalRecordFrame::PageOwnership { record: *record }
229 }
230 WalRecord::Begin { tx_id } => MainWalRecordFrame::Begin { tx_id: *tx_id },
231 WalRecord::Commit { tx_id } => MainWalRecordFrame::Commit { tx_id: *tx_id },
232 WalRecord::Rollback { tx_id } => MainWalRecordFrame::Rollback { tx_id: *tx_id },
233 WalRecord::PageWrite {
234 tx_id,
235 page_id,
236 data,
237 } => MainWalRecordFrame::PageWrite {
238 tx_id: *tx_id,
239 page_id: *page_id,
240 data: data.clone(),
241 },
242 WalRecord::TxCommitBatch { tx_id, actions } => MainWalRecordFrame::TxCommitBatch {
243 tx_id: *tx_id,
244 actions: actions.clone(),
245 },
246 WalRecord::FullPageImage {
247 tx_id,
248 page_id,
249 ckpt_epoch,
250 data,
251 } => MainWalRecordFrame::FullPageImage {
252 tx_id: *tx_id,
253 page_id: *page_id,
254 ckpt_epoch: *ckpt_epoch,
255 data: data.clone(),
256 },
257 WalRecord::VectorInsert {
258 collection,
259 entity_id,
260 vector,
261 } => MainWalRecordFrame::VectorInsert {
262 collection: collection.clone(),
263 entity_id: *entity_id,
264 vector: vector.clone(),
265 },
266 WalRecord::ProbabilisticDelta {
267 kind,
268 operation,
269 name,
270 operands,
271 } => MainWalRecordFrame::ProbabilisticDelta {
272 kind: *kind,
273 operation: *operation,
274 name: name.clone(),
275 operands: operands.clone(),
276 },
277 WalRecord::Checkpoint { lsn } => MainWalRecordFrame::Checkpoint { lsn: *lsn },
278 }
279 }
280
281 fn from_file_frame(frame: MainWalRecordFrame) -> Self {
282 match frame {
283 MainWalRecordFrame::PageOwnership { record } => WalRecord::PageOwnership { record },
284 MainWalRecordFrame::Begin { tx_id } => WalRecord::Begin { tx_id },
285 MainWalRecordFrame::Commit { tx_id } => WalRecord::Commit { tx_id },
286 MainWalRecordFrame::Rollback { tx_id } => WalRecord::Rollback { tx_id },
287 MainWalRecordFrame::PageWrite {
288 tx_id,
289 page_id,
290 data,
291 } => WalRecord::PageWrite {
292 tx_id,
293 page_id,
294 data,
295 },
296 MainWalRecordFrame::TxCommitBatch { tx_id, actions } => {
297 WalRecord::TxCommitBatch { tx_id, actions }
298 }
299 MainWalRecordFrame::FullPageImage {
300 tx_id,
301 page_id,
302 ckpt_epoch,
303 data,
304 } => WalRecord::FullPageImage {
305 tx_id,
306 page_id,
307 ckpt_epoch,
308 data,
309 },
310 MainWalRecordFrame::VectorInsert {
311 collection,
312 entity_id,
313 vector,
314 } => WalRecord::VectorInsert {
315 collection,
316 entity_id,
317 vector,
318 },
319 MainWalRecordFrame::ProbabilisticDelta {
320 kind,
321 operation,
322 name,
323 operands,
324 } => WalRecord::ProbabilisticDelta {
325 kind,
326 operation,
327 name,
328 operands,
329 },
330 MainWalRecordFrame::Checkpoint { lsn } => WalRecord::Checkpoint { lsn },
331 }
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use std::io::Cursor;
339
340 #[test]
341 fn page_ownership_intent_round_trips_through_native_wal() {
342 let record = WalRecord::PageOwnership {
343 record: reddb_file::page_ownership_journal::PageOwnershipRecord::Reserve {
344 epoch: 7,
345 page_id: 67,
346 },
347 };
348 let bytes = record.encode();
349 let decoded = WalRecord::read(&mut Cursor::new(bytes))
350 .expect("decode WAL record")
351 .expect("record is present");
352 assert_eq!(decoded, record);
353 }
354
355 #[test]
358 fn test_encode_begin() {
359 let record = WalRecord::Begin { tx_id: 12345 };
360 let encoded = record.encode();
361 assert!(!encoded.is_empty());
362 }
363
364 #[test]
365 fn test_encode_commit() {
366 let record = WalRecord::Commit { tx_id: 99999 };
367 let encoded = record.encode();
368 assert!(!encoded.is_empty());
369 }
370
371 #[test]
372 fn test_encode_rollback() {
373 let record = WalRecord::Rollback { tx_id: 54321 };
374 let encoded = record.encode();
375 assert!(!encoded.is_empty());
376 }
377
378 #[test]
379 fn test_encode_checkpoint() {
380 let record = WalRecord::Checkpoint { lsn: 1000000 };
381 let encoded = record.encode();
382 assert!(!encoded.is_empty());
383 }
384
385 #[test]
386 fn test_encode_page_write_small() {
387 let data = vec![1, 2, 3, 4, 5];
389 let record = WalRecord::PageWrite {
390 tx_id: 100,
391 page_id: 42,
392 data: data.clone(),
393 };
394 let encoded = record.encode();
395 assert!(!encoded.is_empty());
396 }
397
398 #[test]
399 fn test_encode_page_write_empty_data() {
400 let record = WalRecord::PageWrite {
401 tx_id: 1,
402 page_id: 0,
403 data: vec![],
404 };
405 let encoded = record.encode();
406 assert!(!encoded.is_empty());
407 }
408
409 #[test]
410 fn test_encode_tx_commit_batch() {
411 let record = WalRecord::TxCommitBatch {
412 tx_id: 7,
413 actions: vec![b"insert".to_vec(), b"update".to_vec()],
414 };
415 let encoded = record.encode();
416 assert!(!encoded.is_empty());
417 }
418
419 #[test]
422 fn test_read_begin_roundtrip() {
423 let original = WalRecord::Begin { tx_id: 42 };
424 let encoded = original.encode();
425
426 let mut cursor = Cursor::new(encoded);
427 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
428
429 assert_eq!(decoded, original);
430 }
431
432 #[test]
433 fn test_read_begin_roundtrip_preserves_term() {
434 let original = WalRecord::Begin { tx_id: 42 };
435 let encoded = original.encode_with_term(9);
436
437 let mut cursor = Cursor::new(encoded);
438 let (term, decoded) = WalRecord::read_with_term(&mut cursor).unwrap().unwrap();
439
440 assert_eq!(term, 9);
441 assert_eq!(decoded, original);
442 }
443
444 #[test]
445 fn test_read_begin_roundtrip_preserves_authority_epoch() {
446 let original = WalRecord::Begin { tx_id: 42 };
447 let authority = WalRecordAuthority {
448 term: 9,
449 ownership_epoch: Some(12),
450 };
451 let mut encoded = Vec::new();
452 original.encode_with_authority_into(&mut encoded, authority);
453
454 let mut cursor = Cursor::new(encoded);
455 let (decoded_authority, decoded) = WalRecord::read_with_authority(&mut cursor)
456 .unwrap()
457 .unwrap();
458
459 assert_eq!(decoded_authority, authority);
460 assert_eq!(decoded, original);
461 }
462
463 #[test]
464 fn test_read_commit_roundtrip() {
465 let original = WalRecord::Commit { tx_id: 999 };
466 let encoded = original.encode();
467
468 let mut cursor = Cursor::new(encoded);
469 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
470
471 assert_eq!(decoded, original);
472 }
473
474 #[test]
475 fn test_read_rollback_roundtrip() {
476 let original = WalRecord::Rollback { tx_id: 777 };
477 let encoded = original.encode();
478
479 let mut cursor = Cursor::new(encoded);
480 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
481
482 assert_eq!(decoded, original);
483 }
484
485 #[test]
486 fn test_read_checkpoint_roundtrip() {
487 let original = WalRecord::Checkpoint { lsn: 123456789 };
488 let encoded = original.encode();
489
490 let mut cursor = Cursor::new(encoded);
491 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
492
493 assert_eq!(decoded, original);
494 }
495
496 #[test]
497 fn test_read_page_write_roundtrip() {
498 let original = WalRecord::PageWrite {
499 tx_id: 50,
500 page_id: 100,
501 data: vec![10, 20, 30, 40, 50, 60, 70, 80],
502 };
503 let encoded = original.encode();
504
505 let mut cursor = Cursor::new(encoded);
506 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
507
508 assert_eq!(decoded, original);
509 }
510
511 #[test]
512 fn test_read_tx_commit_batch_roundtrip() {
513 let original = WalRecord::TxCommitBatch {
514 tx_id: 42,
515 actions: vec![b"old-version".to_vec(), b"new-version".to_vec()],
516 };
517 let encoded = original.encode();
518
519 let mut cursor = Cursor::new(encoded);
520 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
521
522 assert_eq!(decoded, original);
523 }
524
525 #[test]
526 fn test_vector_insert_roundtrip() {
527 let original = WalRecord::VectorInsert {
528 collection: "turbo".to_string(),
529 entity_id: 42,
530 vector: vec![1.0, -0.5, 0.25],
531 };
532 let encoded = original.encode();
533
534 let mut cursor = Cursor::new(encoded);
535 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
536
537 assert_eq!(decoded, original);
538 }
539
540 #[test]
541 fn test_read_page_write_large_data() {
542 let data: Vec<u8> = (0..4096).map(|i| (i % 256) as u8).collect();
544 let original = WalRecord::PageWrite {
545 tx_id: 1,
546 page_id: 0,
547 data,
548 };
549 let encoded = original.encode();
550
551 let mut cursor = Cursor::new(encoded);
552 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
553
554 assert_eq!(decoded, original);
556 }
557
558 #[test]
559 fn page_write_compressed_roundtrip() {
560 let data = vec![0xABu8; 1024];
562 let record = WalRecord::PageWrite {
563 tx_id: 7,
564 page_id: 3,
565 data: data.clone(),
566 };
567 let encoded = record.encode();
568
569 let mut cursor = Cursor::new(encoded);
570 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
571 assert_eq!(
572 decoded,
573 WalRecord::PageWrite {
574 tx_id: 7,
575 page_id: 3,
576 data
577 }
578 );
579 }
580
581 #[test]
582 fn full_page_image_roundtrip() {
583 let data: Vec<u8> = (0..4096).map(|i| (i % 251) as u8).collect();
584 let original = WalRecord::FullPageImage {
585 tx_id: 11,
586 page_id: 9,
587 ckpt_epoch: 42,
588 data: data.clone(),
589 };
590 let encoded = original.encode();
591
592 let mut cursor = Cursor::new(encoded);
593 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
594 assert_eq!(decoded, original);
595 }
596
597 #[test]
598 fn full_page_image_checksum_mismatch_detected() {
599 let original = WalRecord::FullPageImage {
600 tx_id: 1,
601 page_id: 2,
602 ckpt_epoch: 3,
603 data: vec![0xAA; 32],
604 };
605 let mut encoded = original.encode();
606 let mid = encoded.len() / 2;
607 encoded[mid] ^= 0xFF;
608 let mut cursor = Cursor::new(encoded);
609 assert!(WalRecord::read(&mut cursor).is_err());
610 }
611
612 #[test]
613 fn test_read_eof() {
614 let mut cursor = Cursor::new(Vec::<u8>::new());
615 let result = WalRecord::read(&mut cursor).unwrap();
616 assert!(result.is_none());
617 }
618
619 #[test]
620 fn test_read_invalid_record_type() {
621 let buf = vec![99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let mut cursor = Cursor::new(buf);
623 let result = WalRecord::read(&mut cursor);
624 assert!(result.is_err());
625 }
626
627 #[test]
628 fn test_read_checksum_mismatch() {
629 let record = WalRecord::Begin { tx_id: 42 };
630 let mut encoded = record.encode();
631
632 let len = encoded.len();
634 encoded[len - 1] ^= 0xFF;
635
636 let mut cursor = Cursor::new(encoded);
637 let result = WalRecord::read(&mut cursor);
638 assert!(result.is_err());
639 }
640
641 #[test]
642 fn test_read_data_corruption() {
643 let record = WalRecord::PageWrite {
644 tx_id: 1,
645 page_id: 2,
646 data: vec![1, 2, 3, 4],
647 };
648 let mut encoded = record.encode();
649
650 encoded[15] ^= 0xFF;
652
653 let mut cursor = Cursor::new(encoded);
654 let result = WalRecord::read(&mut cursor);
655 assert!(result.is_err()); }
657
658 #[test]
661 fn test_multiple_records_sequential() {
662 let records = vec![
663 WalRecord::Begin { tx_id: 1 },
664 WalRecord::PageWrite {
665 tx_id: 1,
666 page_id: 10,
667 data: vec![1, 2, 3],
668 },
669 WalRecord::PageWrite {
670 tx_id: 1,
671 page_id: 20,
672 data: vec![4, 5, 6],
673 },
674 WalRecord::Commit { tx_id: 1 },
675 WalRecord::Checkpoint { lsn: 100 },
676 ];
677
678 let mut buf = Vec::new();
680 for r in &records {
681 buf.extend_from_slice(&r.encode());
682 }
683
684 let mut cursor = Cursor::new(buf);
686 for expected in &records {
687 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
688 assert_eq!(&decoded, expected);
689 }
690
691 assert!(WalRecord::read(&mut cursor).unwrap().is_none());
693 }
694
695 #[test]
702 fn test_encode_into_matches_encode_for_all_variants() {
703 let records = vec![
704 WalRecord::Begin { tx_id: 12345 },
705 WalRecord::Commit { tx_id: 99999 },
706 WalRecord::Rollback { tx_id: 54321 },
707 WalRecord::Checkpoint { lsn: 1_000_000 },
708 WalRecord::PageWrite {
709 tx_id: 100,
710 page_id: 42,
711 data: vec![1, 2, 3, 4, 5],
712 },
713 WalRecord::PageWrite {
716 tx_id: 7,
717 page_id: 3,
718 data: vec![0xABu8; 1024],
719 },
720 WalRecord::TxCommitBatch {
721 tx_id: 7,
722 actions: vec![b"insert".to_vec(), b"update".to_vec()],
723 },
724 WalRecord::FullPageImage {
725 tx_id: 11,
726 page_id: 9,
727 ckpt_epoch: 42,
728 data: (0..4096).map(|i| (i % 251) as u8).collect(),
729 },
730 WalRecord::VectorInsert {
731 collection: "turbo".to_string(),
732 entity_id: 42,
733 vector: vec![1.0, -0.5, 0.25],
734 },
735 ];
736
737 for record in &records {
738 let baseline = record.encode();
739 let mut scratch = Vec::new();
740 record.encode_into(&mut scratch);
741 assert_eq!(scratch, baseline, "encode_into mismatch for {record:?}");
742 }
743 }
744
745 #[test]
749 fn test_encode_into_reuses_scratch_across_records() {
750 let records = vec![
751 WalRecord::Begin { tx_id: 1 },
752 WalRecord::PageWrite {
753 tx_id: 1,
754 page_id: 10,
755 data: vec![1, 2, 3],
756 },
757 WalRecord::Commit { tx_id: 1 },
758 ];
759
760 let mut expected = Vec::new();
761 for r in &records {
762 expected.extend_from_slice(&r.encode());
763 }
764
765 let mut scratch = Vec::new();
767 for r in &records {
768 r.encode_into(&mut scratch);
769 }
770
771 assert_eq!(scratch, expected);
772
773 let mut cursor = Cursor::new(scratch);
775 for expected in &records {
776 let decoded = WalRecord::read(&mut cursor).unwrap().unwrap();
777 assert_eq!(&decoded, expected);
778 }
779 assert!(WalRecord::read(&mut cursor).unwrap().is_none());
780 }
781
782 fn every_record_variant() -> Vec<WalRecord> {
785 vec![
786 WalRecord::Begin { tx_id: 12345 },
787 WalRecord::Commit { tx_id: 99999 },
788 WalRecord::Rollback { tx_id: 54321 },
789 WalRecord::Checkpoint { lsn: 1_000_000 },
790 WalRecord::PageWrite {
792 tx_id: 100,
793 page_id: 42,
794 data: vec![1, 2, 3, 4, 5],
795 },
796 WalRecord::PageWrite {
797 tx_id: 1,
798 page_id: 0,
799 data: Vec::new(),
800 },
801 WalRecord::PageWrite {
803 tx_id: 7,
804 page_id: 3,
805 data: vec![0xABu8; 1024],
806 },
807 WalRecord::PageWrite {
809 tx_id: 8,
810 page_id: 4,
811 data: (0..300u32)
812 .map(|i| i.wrapping_mul(2_654_435_761) as u8)
813 .collect(),
814 },
815 WalRecord::TxCommitBatch {
816 tx_id: 7,
817 actions: vec![b"insert".to_vec(), Vec::new(), b"update".to_vec()],
818 },
819 WalRecord::TxCommitBatch {
820 tx_id: 8,
821 actions: Vec::new(),
822 },
823 WalRecord::FullPageImage {
824 tx_id: 11,
825 page_id: 9,
826 ckpt_epoch: 42,
827 data: (0..4096).map(|i| (i % 251) as u8).collect(),
828 },
829 WalRecord::VectorInsert {
830 collection: "turbo".to_string(),
831 entity_id: 42,
832 vector: vec![1.0, -0.5, 0.25],
833 },
834 WalRecord::ProbabilisticDelta {
835 kind: 1,
836 operation: 2,
837 name: "visitors".to_string(),
838 operands: vec![b"alice".to_vec(), b"bob".to_vec()],
839 },
840 ]
841 }
842
843 #[test]
848 fn borrowed_encode_is_byte_identical_to_the_owning_frame_path() {
849 for record in every_record_variant() {
850 let mut owned_bytes = Vec::new();
852 encode_main_wal_record_frame_into(&record.to_file_frame(), 17, &mut owned_bytes)
853 .expect("owning encode");
854
855 let mut borrowed_bytes = Vec::new();
857 record.encode_with_term_into(&mut borrowed_bytes, 17);
858
859 assert_eq!(
860 borrowed_bytes, owned_bytes,
861 "borrowed encode diverged from the owning baseline for {record:?}"
862 );
863
864 let authority = WalRecordAuthority {
866 term: 17,
867 ownership_epoch: Some(5),
868 };
869 let mut owned_auth = Vec::new();
870 encode_main_wal_record_frame_with_authority_into(
871 &record.to_file_frame(),
872 authority,
873 &mut owned_auth,
874 )
875 .expect("owning authority encode");
876
877 let mut borrowed_auth = Vec::new();
878 record.encode_with_authority_into(&mut borrowed_auth, authority);
879
880 assert_eq!(
881 borrowed_auth, owned_auth,
882 "borrowed authority encode diverged from the owning baseline for {record:?}"
883 );
884
885 let mut cursor = Cursor::new(borrowed_bytes);
887 let (term, decoded) = WalRecord::read_with_term(&mut cursor).unwrap().unwrap();
888 assert_eq!(term, 17);
889 assert_eq!(decoded, record);
890 }
891 }
892
893 #[test]
896 fn test_encode_with_term_into_matches_and_preserves_prefix() {
897 let prefix = b"PREFIX-BYTES".to_vec();
898 let record = WalRecord::Begin { tx_id: 42 };
899
900 let mut scratch = prefix.clone();
901 record.encode_with_term_into(&mut scratch, 9);
902
903 assert_eq!(&scratch[..prefix.len()], &prefix[..]);
905 assert_eq!(&scratch[prefix.len()..], &record.encode_with_term(9)[..]);
906 }
907}