Read the next record from the WAL. Returns `None` at EOF (clean end) or at the first corruption point. Returns `Err` only for I/O errors or unknown required record types.
(&mut self)
| 81 | /// Returns `None` at EOF (clean end) or at the first corruption point. |
| 82 | /// Returns `Err` only for I/O errors or unknown required record types. |
| 83 | pub fn next_record(&mut self) -> Result<Option<WalRecord>> { |
| 84 | loop { |
| 85 | // Read header. |
| 86 | let mut header_buf = [0u8; HEADER_SIZE]; |
| 87 | match self.read_exact(&mut header_buf) { |
| 88 | Ok(()) => {} |
| 89 | Err(WalError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => { |
| 90 | return Ok(None); // Clean EOF. |
| 91 | } |
| 92 | Err(e) => return Err(e), |
| 93 | } |
| 94 | |
| 95 | let header = RecordHeader::from_bytes(&header_buf); |
| 96 | |
| 97 | // Validate magic and version. |
| 98 | if header.validate(self.offset - HEADER_SIZE as u64).is_err() { |
| 99 | // Corruption or end of valid data — treat as end of committed prefix. |
| 100 | return Ok(None); |
| 101 | } |
| 102 | |
| 103 | // Read payload. |
| 104 | let mut payload = vec![0u8; header.payload_len as usize]; |
| 105 | if !payload.is_empty() { |
| 106 | match self.read_exact(&mut payload) { |
| 107 | Ok(()) => {} |
| 108 | Err(WalError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => { |
| 109 | return Ok(None); |
| 110 | } |
| 111 | Err(e) => return Err(e), |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | let record = WalRecord { header, payload }; |
| 116 | |
| 117 | // Verify checksum. |
| 118 | if record.verify_checksum().is_err() { |
| 119 | if let Some(dwb) = &mut self.double_write |
| 120 | && let Ok(Some(recovered)) = dwb.recover_record(header.lsn) |
| 121 | { |
| 122 | tracing::info!( |
| 123 | lsn = header.lsn, |
| 124 | "recovered torn write from double-write buffer" |
| 125 | ); |
| 126 | self.offset += recovered.payload.len() as u64; |
| 127 | return Ok(Some(recovered)); |
| 128 | } |
| 129 | return Ok(None); |
| 130 | } |
| 131 | |
| 132 | // Check if the record type is known (strip encrypted flag for lookup). |
| 133 | let logical_type = record.logical_record_type(); |
| 134 | if RecordType::from_raw(logical_type).is_none() { |
| 135 | if RecordType::is_required(logical_type) { |
| 136 | return Err(WalError::UnknownRequiredRecordType { |
| 137 | record_type: header.record_type, |
| 138 | lsn: header.lsn, |
| 139 | }); |
| 140 | } |
no test coverage detected