Line data Source code
1 : //
2 : // This file contains common utilities for dealing with PostgreSQL WAL files and
3 : // LSNs.
4 : //
5 : // Many of these functions have been copied from PostgreSQL, and rewritten in
6 : // Rust. That's why they don't follow the usual Rust naming conventions, they
7 : // have been named the same as the corresponding PostgreSQL functions instead.
8 : //
9 :
10 : use crc32c::crc32c_append;
11 :
12 : use super::super::waldecoder::WalStreamDecoder;
13 : use super::bindings::{
14 : CheckPoint, ControlFileData, DBState_DB_SHUTDOWNED, FullTransactionId, TimeLineID, TimestampTz,
15 : XLogLongPageHeaderData, XLogPageHeaderData, XLogRecPtr, XLogRecord, XLogSegNo, XLOG_PAGE_MAGIC,
16 : };
17 : use super::PG_MAJORVERSION;
18 : use crate::pg_constants;
19 : use crate::PG_TLI;
20 : use crate::{uint32, uint64, Oid};
21 : use crate::{WAL_SEGMENT_SIZE, XLOG_BLCKSZ};
22 :
23 : use bytes::BytesMut;
24 : use bytes::{Buf, Bytes};
25 :
26 : use log::*;
27 :
28 : use serde::Serialize;
29 : use std::fs::File;
30 : use std::io::prelude::*;
31 : use std::io::ErrorKind;
32 : use std::io::SeekFrom;
33 : use std::path::{Path, PathBuf};
34 : use std::time::SystemTime;
35 : use utils::bin_ser::DeserializeError;
36 : use utils::bin_ser::SerializeError;
37 :
38 : use utils::lsn::Lsn;
39 :
40 : pub const XLOG_FNAME_LEN: usize = 24;
41 : pub const XLP_FIRST_IS_CONTRECORD: u16 = 0x0001;
42 : pub const XLP_REM_LEN_OFFS: usize = 2 + 2 + 4 + 8;
43 : pub const XLOG_RECORD_CRC_OFFS: usize = 4 + 4 + 8 + 1 + 1 + 2;
44 :
45 : pub const XLOG_SIZE_OF_XLOG_SHORT_PHD: usize = size_of::<XLogPageHeaderData>();
46 : pub const XLOG_SIZE_OF_XLOG_LONG_PHD: usize = size_of::<XLogLongPageHeaderData>();
47 : pub const XLOG_SIZE_OF_XLOG_RECORD: usize = size_of::<XLogRecord>();
48 : #[allow(clippy::identity_op)]
49 : pub const SIZE_OF_XLOG_RECORD_DATA_HEADER_SHORT: usize = 1 * 2;
50 :
51 : /// Interval of checkpointing metadata file. We should store metadata file to enforce
52 : /// predicate that checkpoint.nextXid is larger than any XID in WAL.
53 : /// But flushing checkpoint file for each transaction seems to be too expensive,
54 : /// so XID_CHECKPOINT_INTERVAL is used to forward align nextXid and so perform
55 : /// metadata checkpoint only once per XID_CHECKPOINT_INTERVAL transactions.
56 : /// XID_CHECKPOINT_INTERVAL should not be larger than BLCKSZ*CLOG_XACTS_PER_BYTE
57 : /// in order to let CLOG_TRUNCATE mechanism correctly extend CLOG.
58 : const XID_CHECKPOINT_INTERVAL: u32 = 1024;
59 :
60 148 : pub fn XLogSegmentsPerXLogId(wal_segsz_bytes: usize) -> XLogSegNo {
61 148 : (0x100000000u64 / wal_segsz_bytes as u64) as XLogSegNo
62 148 : }
63 :
64 33472 : pub fn XLogSegNoOffsetToRecPtr(
65 33472 : segno: XLogSegNo,
66 33472 : offset: u32,
67 33472 : wal_segsz_bytes: usize,
68 33472 : ) -> XLogRecPtr {
69 33472 : segno * (wal_segsz_bytes as u64) + (offset as u64)
70 33472 : }
71 :
72 46 : pub fn XLogFileName(tli: TimeLineID, logSegNo: XLogSegNo, wal_segsz_bytes: usize) -> String {
73 46 : format!(
74 46 : "{:>08X}{:>08X}{:>08X}",
75 46 : tli,
76 46 : logSegNo / XLogSegmentsPerXLogId(wal_segsz_bytes),
77 46 : logSegNo % XLogSegmentsPerXLogId(wal_segsz_bytes)
78 46 : )
79 46 : }
80 :
81 56 : pub fn XLogFromFileName(fname: &str, wal_seg_size: usize) -> (XLogSegNo, TimeLineID) {
82 56 : let tli = u32::from_str_radix(&fname[0..8], 16).unwrap();
83 56 : let log = u32::from_str_radix(&fname[8..16], 16).unwrap() as XLogSegNo;
84 56 : let seg = u32::from_str_radix(&fname[16..24], 16).unwrap() as XLogSegNo;
85 56 : (log * XLogSegmentsPerXLogId(wal_seg_size) + seg, tli)
86 56 : }
87 :
88 131 : pub fn IsXLogFileName(fname: &str) -> bool {
89 1824 : return fname.len() == XLOG_FNAME_LEN && fname.chars().all(|c| c.is_ascii_hexdigit());
90 131 : }
91 :
92 0 : pub fn IsPartialXLogFileName(fname: &str) -> bool {
93 0 : fname.ends_with(".partial") && IsXLogFileName(&fname[0..fname.len() - 8])
94 0 : }
95 :
96 : /// If LSN points to the beginning of the page, then shift it to first record,
97 : /// otherwise align on 8-bytes boundary (required for WAL records)
98 0 : pub fn normalize_lsn(lsn: Lsn, seg_sz: usize) -> Lsn {
99 0 : if lsn.0 % XLOG_BLCKSZ as u64 == 0 {
100 0 : let hdr_size = if lsn.0 % seg_sz as u64 == 0 {
101 0 : XLOG_SIZE_OF_XLOG_LONG_PHD
102 : } else {
103 0 : XLOG_SIZE_OF_XLOG_SHORT_PHD
104 : };
105 0 : lsn + hdr_size as u64
106 : } else {
107 0 : lsn.align()
108 : }
109 0 : }
110 :
111 0 : pub fn generate_pg_control(
112 0 : pg_control_bytes: &[u8],
113 0 : checkpoint_bytes: &[u8],
114 0 : lsn: Lsn,
115 0 : ) -> anyhow::Result<(Bytes, u64)> {
116 0 : let mut pg_control = ControlFileData::decode(pg_control_bytes)?;
117 0 : let mut checkpoint = CheckPoint::decode(checkpoint_bytes)?;
118 :
119 : // Generate new pg_control needed for bootstrap
120 0 : checkpoint.redo = normalize_lsn(lsn, WAL_SEGMENT_SIZE).0;
121 0 :
122 0 : //save new values in pg_control
123 0 : pg_control.checkPoint = 0;
124 0 : pg_control.checkPointCopy = checkpoint;
125 0 : pg_control.state = DBState_DB_SHUTDOWNED;
126 0 :
127 0 : Ok((pg_control.encode(), pg_control.system_identifier))
128 0 : }
129 :
130 4 : pub fn get_current_timestamp() -> TimestampTz {
131 4 : to_pg_timestamp(SystemTime::now())
132 4 : }
133 :
134 : // Module to reduce the scope of the constants
135 : mod timestamp_conversions {
136 : use std::time::Duration;
137 :
138 : use anyhow::Context;
139 :
140 : use super::*;
141 :
142 : const UNIX_EPOCH_JDATE: u64 = 2440588; // == date2j(1970, 1, 1)
143 : const POSTGRES_EPOCH_JDATE: u64 = 2451545; // == date2j(2000, 1, 1)
144 : const SECS_PER_DAY: u64 = 86400;
145 : const USECS_PER_SEC: u64 = 1000000;
146 : const SECS_DIFF_UNIX_TO_POSTGRES_EPOCH: u64 =
147 : (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY;
148 :
149 12 : pub fn to_pg_timestamp(time: SystemTime) -> TimestampTz {
150 12 : match time.duration_since(SystemTime::UNIX_EPOCH) {
151 12 : Ok(n) => {
152 12 : ((n.as_secs() - SECS_DIFF_UNIX_TO_POSTGRES_EPOCH) * USECS_PER_SEC
153 12 : + n.subsec_micros() as u64) as i64
154 : }
155 0 : Err(_) => panic!("SystemTime before UNIX EPOCH!"),
156 : }
157 12 : }
158 :
159 32 : pub fn try_from_pg_timestamp(time: TimestampTz) -> anyhow::Result<SystemTime> {
160 32 : let time: u64 = time
161 32 : .try_into()
162 32 : .context("timestamp before millenium (postgres epoch)")?;
163 32 : let since_unix_epoch = time + SECS_DIFF_UNIX_TO_POSTGRES_EPOCH * USECS_PER_SEC;
164 32 : SystemTime::UNIX_EPOCH
165 32 : .checked_add(Duration::from_micros(since_unix_epoch))
166 32 : .context("SystemTime overflow")
167 32 : }
168 : }
169 :
170 : pub use timestamp_conversions::{to_pg_timestamp, try_from_pg_timestamp};
171 :
172 : // Returns (aligned) end_lsn of the last record in data_dir with WAL segments.
173 : // start_lsn must point to some previously known record boundary (beginning of
174 : // the next record). If no valid record after is found, start_lsn is returned
175 : // back.
176 32 : pub fn find_end_of_wal(
177 32 : data_dir: &Path,
178 32 : wal_seg_size: usize,
179 32 : start_lsn: Lsn, // start reading WAL at this point; must point at record start_lsn.
180 32 : ) -> anyhow::Result<Lsn> {
181 32 : let mut result = start_lsn;
182 32 : let mut curr_lsn = start_lsn;
183 32 : let mut buf = [0u8; XLOG_BLCKSZ];
184 32 : let pg_version = PG_MAJORVERSION[1..3].parse::<u32>().unwrap();
185 32 : debug!("find_end_of_wal PG_VERSION: {}", pg_version);
186 :
187 32 : let mut decoder = WalStreamDecoder::new(start_lsn, pg_version);
188 :
189 : // loop over segments
190 : loop {
191 40 : let segno = curr_lsn.segment_number(wal_seg_size);
192 40 : let seg_file_name = XLogFileName(PG_TLI, segno, wal_seg_size);
193 40 : let seg_file_path = data_dir.join(seg_file_name);
194 40 : match open_wal_segment(&seg_file_path)? {
195 : None => {
196 : // no more segments
197 0 : debug!(
198 0 : "find_end_of_wal reached end at {:?}, segment {:?} doesn't exist",
199 : result, seg_file_path
200 : );
201 0 : return Ok(result);
202 : }
203 40 : Some(mut segment) => {
204 40 : let seg_offs = curr_lsn.segment_offset(wal_seg_size);
205 40 : segment.seek(SeekFrom::Start(seg_offs as u64))?;
206 : // loop inside segment
207 16504 : while curr_lsn.segment_number(wal_seg_size) == segno {
208 16496 : let bytes_read = segment.read(&mut buf)?;
209 16496 : if bytes_read == 0 {
210 0 : debug!(
211 0 : "find_end_of_wal reached end at {:?}, EOF in segment {:?} at offset {}",
212 0 : result,
213 0 : seg_file_path,
214 0 : curr_lsn.segment_offset(wal_seg_size)
215 : );
216 0 : return Ok(result);
217 16496 : }
218 16496 : curr_lsn += bytes_read as u64;
219 16496 : decoder.feed_bytes(&buf[0..bytes_read]);
220 :
221 : // advance result past all completely read records
222 : loop {
223 16664 : match decoder.poll_decode() {
224 168 : Ok(Some(record)) => result = record.0,
225 32 : Err(e) => {
226 32 : debug!(
227 32 : "find_end_of_wal reached end at {:?}, decode error: {:?}",
228 : result, e
229 : );
230 32 : return Ok(result);
231 : }
232 16464 : Ok(None) => break, // need more data
233 : }
234 : }
235 : }
236 : }
237 : }
238 : }
239 32 : }
240 :
241 : // Open .partial or full WAL segment file, if present.
242 40 : fn open_wal_segment(seg_file_path: &Path) -> anyhow::Result<Option<File>> {
243 40 : let mut partial_path = seg_file_path.to_owned();
244 40 : partial_path.set_extension("partial");
245 40 : match File::open(partial_path) {
246 32 : Ok(file) => Ok(Some(file)),
247 8 : Err(e) => match e.kind() {
248 : ErrorKind::NotFound => {
249 : // .partial not found, try full
250 8 : match File::open(seg_file_path) {
251 8 : Ok(file) => Ok(Some(file)),
252 0 : Err(e) => match e.kind() {
253 0 : ErrorKind::NotFound => Ok(None),
254 0 : _ => Err(e.into()),
255 : },
256 : }
257 : }
258 0 : _ => Err(e.into()),
259 : },
260 : }
261 40 : }
262 :
263 0 : pub fn main() {
264 0 : let mut data_dir = PathBuf::new();
265 0 : data_dir.push(".");
266 0 : let wal_end = find_end_of_wal(&data_dir, WAL_SEGMENT_SIZE, Lsn(0)).unwrap();
267 0 : println!("wal_end={:?}", wal_end);
268 0 : }
269 :
270 : impl XLogRecord {
271 477542 : pub fn from_slice(buf: &[u8]) -> Result<XLogRecord, DeserializeError> {
272 : use utils::bin_ser::LeSer;
273 477542 : XLogRecord::des(buf)
274 477542 : }
275 :
276 437556 : pub fn from_bytes<B: Buf>(buf: &mut B) -> Result<XLogRecord, DeserializeError> {
277 : use utils::bin_ser::LeSer;
278 437556 : XLogRecord::des_from(&mut buf.reader())
279 437556 : }
280 :
281 33424 : pub fn encode(&self) -> Result<Bytes, SerializeError> {
282 : use utils::bin_ser::LeSer;
283 33424 : Ok(self.ser()?.into())
284 33424 : }
285 :
286 : // Is this record an XLOG_SWITCH record? They need some special processing,
287 477542 : pub fn is_xlog_switch_record(&self) -> bool {
288 477542 : self.xl_info == pg_constants::XLOG_SWITCH && self.xl_rmid == pg_constants::RM_XLOG_ID
289 477542 : }
290 : }
291 :
292 : impl XLogPageHeaderData {
293 25364 : pub fn from_bytes<B: Buf>(buf: &mut B) -> Result<XLogPageHeaderData, DeserializeError> {
294 : use utils::bin_ser::LeSer;
295 25364 : XLogPageHeaderData::des_from(&mut buf.reader())
296 25364 : }
297 :
298 93 : pub fn encode(&self) -> Result<Bytes, SerializeError> {
299 : use utils::bin_ser::LeSer;
300 93 : self.ser().map(|b| b.into())
301 93 : }
302 : }
303 :
304 : impl XLogLongPageHeaderData {
305 8 : pub fn from_bytes<B: Buf>(buf: &mut B) -> Result<XLogLongPageHeaderData, DeserializeError> {
306 : use utils::bin_ser::LeSer;
307 8 : XLogLongPageHeaderData::des_from(&mut buf.reader())
308 8 : }
309 :
310 0 : pub fn encode(&self) -> Result<Bytes, SerializeError> {
311 : use utils::bin_ser::LeSer;
312 0 : self.ser().map(|b| b.into())
313 0 : }
314 : }
315 :
316 : pub const SIZEOF_CHECKPOINT: usize = size_of::<CheckPoint>();
317 :
318 : impl CheckPoint {
319 24 : pub fn encode(&self) -> Result<Bytes, SerializeError> {
320 : use utils::bin_ser::LeSer;
321 24 : Ok(self.ser()?.into())
322 24 : }
323 :
324 68 : pub fn decode(buf: &[u8]) -> Result<CheckPoint, DeserializeError> {
325 : use utils::bin_ser::LeSer;
326 68 : CheckPoint::des(buf)
327 68 : }
328 :
329 : /// Update next XID based on provided new_xid and stored epoch.
330 : /// Next XID should be greater than new_xid. This handles 32-bit
331 : /// XID wraparound correctly.
332 : ///
333 : /// Returns 'true' if the XID was updated.
334 437518 : pub fn update_next_xid(&mut self, xid: u32) -> bool {
335 437518 : // nextXid should be greater than any XID in WAL, so increment provided XID and check for wraparround.
336 437518 : let mut new_xid = std::cmp::max(
337 437518 : xid.wrapping_add(1),
338 437518 : pg_constants::FIRST_NORMAL_TRANSACTION_ID,
339 437518 : );
340 437518 : // To reduce number of metadata checkpoints, we forward align XID on XID_CHECKPOINT_INTERVAL.
341 437518 : // XID_CHECKPOINT_INTERVAL should not be larger than BLCKSZ*CLOG_XACTS_PER_BYTE
342 437518 : new_xid =
343 437518 : new_xid.wrapping_add(XID_CHECKPOINT_INTERVAL - 1) & !(XID_CHECKPOINT_INTERVAL - 1);
344 437518 : let full_xid = self.nextXid.value;
345 437518 : let old_xid = full_xid as u32;
346 437518 : if new_xid.wrapping_sub(old_xid) as i32 > 0 {
347 14 : let mut epoch = full_xid >> 32;
348 14 : if new_xid < old_xid {
349 0 : // wrap-around
350 0 : epoch += 1;
351 14 : }
352 14 : let nextXid = (epoch << 32) | new_xid as u64;
353 14 :
354 14 : if nextXid != self.nextXid.value {
355 14 : self.nextXid = FullTransactionId { value: nextXid };
356 14 : return true;
357 0 : }
358 437504 : }
359 437504 : false
360 437518 : }
361 :
362 : /// Advance next multi-XID/offset to those given in arguments.
363 : ///
364 : /// It's important that this handles wraparound correctly. This should match the
365 : /// MultiXactAdvanceNextMXact() logic in PostgreSQL's xlog_redo() function.
366 : ///
367 : /// Returns 'true' if the Checkpoint was updated.
368 24 : pub fn update_next_multixid(&mut self, multi_xid: u32, multi_offset: u32) -> bool {
369 24 : let mut modified = false;
370 24 :
371 24 : if multi_xid.wrapping_sub(self.nextMulti) as i32 > 0 {
372 16 : self.nextMulti = multi_xid;
373 16 : modified = true;
374 16 : }
375 :
376 24 : if multi_offset.wrapping_sub(self.nextMultiOffset) as i32 > 0 {
377 20 : self.nextMultiOffset = multi_offset;
378 20 : modified = true;
379 20 : }
380 :
381 24 : modified
382 24 : }
383 : }
384 :
385 : /// Generate new, empty WAL segment, with correct block headers at the first
386 : /// page of the segment and the page that contains the given LSN.
387 : /// We need this segment to start compute node.
388 0 : pub fn generate_wal_segment(segno: u64, system_id: u64, lsn: Lsn) -> Result<Bytes, SerializeError> {
389 0 : let mut seg_buf = BytesMut::with_capacity(WAL_SEGMENT_SIZE);
390 0 :
391 0 : let pageaddr = XLogSegNoOffsetToRecPtr(segno, 0, WAL_SEGMENT_SIZE);
392 0 :
393 0 : let page_off = lsn.block_offset();
394 0 : let seg_off = lsn.segment_offset(WAL_SEGMENT_SIZE);
395 0 :
396 0 : let first_page_only = seg_off < XLOG_BLCKSZ;
397 : // If first records starts in the middle of the page, pretend in page header
398 : // there is a fake record which ends where first real record starts. This
399 : // makes pg_waldump etc happy.
400 0 : let (shdr_rem_len, infoflags) = if first_page_only && seg_off > 0 {
401 0 : assert!(seg_off >= XLOG_SIZE_OF_XLOG_LONG_PHD);
402 : // xlp_rem_len doesn't include page header, hence the subtraction.
403 0 : (
404 0 : seg_off - XLOG_SIZE_OF_XLOG_LONG_PHD,
405 0 : pg_constants::XLP_FIRST_IS_CONTRECORD,
406 0 : )
407 : } else {
408 0 : (0, 0)
409 : };
410 :
411 0 : let hdr = XLogLongPageHeaderData {
412 0 : std: {
413 0 : XLogPageHeaderData {
414 0 : xlp_magic: XLOG_PAGE_MAGIC as u16,
415 0 : xlp_info: pg_constants::XLP_LONG_HEADER | infoflags,
416 0 : xlp_tli: PG_TLI,
417 0 : xlp_pageaddr: pageaddr,
418 0 : xlp_rem_len: shdr_rem_len as u32,
419 0 : ..Default::default() // Put 0 in padding fields.
420 0 : }
421 0 : },
422 0 : xlp_sysid: system_id,
423 0 : xlp_seg_size: WAL_SEGMENT_SIZE as u32,
424 0 : xlp_xlog_blcksz: XLOG_BLCKSZ as u32,
425 0 : };
426 :
427 0 : let hdr_bytes = hdr.encode()?;
428 0 : seg_buf.extend_from_slice(&hdr_bytes);
429 0 :
430 0 : //zero out the rest of the file
431 0 : seg_buf.resize(WAL_SEGMENT_SIZE, 0);
432 0 :
433 0 : if !first_page_only {
434 0 : let block_offset = lsn.page_offset_in_segment(WAL_SEGMENT_SIZE) as usize;
435 : // see comments above about XLP_FIRST_IS_CONTRECORD and xlp_rem_len.
436 0 : let (xlp_rem_len, xlp_info) = if page_off > 0 {
437 0 : assert!(page_off >= XLOG_SIZE_OF_XLOG_SHORT_PHD as u64);
438 0 : (
439 0 : (page_off - XLOG_SIZE_OF_XLOG_SHORT_PHD as u64) as u32,
440 0 : pg_constants::XLP_FIRST_IS_CONTRECORD,
441 0 : )
442 : } else {
443 0 : (0, 0)
444 : };
445 0 : let header = XLogPageHeaderData {
446 0 : xlp_magic: XLOG_PAGE_MAGIC as u16,
447 0 : xlp_info,
448 0 : xlp_tli: PG_TLI,
449 0 : xlp_pageaddr: lsn.page_lsn().0,
450 0 : xlp_rem_len,
451 0 : ..Default::default() // Put 0 in padding fields.
452 0 : };
453 0 : let hdr_bytes = header.encode()?;
454 :
455 0 : debug_assert!(seg_buf.len() > block_offset + hdr_bytes.len());
456 0 : debug_assert_ne!(block_offset, 0);
457 :
458 0 : seg_buf[block_offset..block_offset + hdr_bytes.len()].copy_from_slice(&hdr_bytes[..]);
459 0 : }
460 :
461 0 : Ok(seg_buf.freeze())
462 0 : }
463 :
464 : #[repr(C)]
465 : #[derive(Serialize)]
466 : pub struct XlLogicalMessage {
467 : pub db_id: Oid,
468 : pub transactional: uint32, // bool, takes 4 bytes due to alignment in C structures
469 : pub prefix_size: uint64,
470 : pub message_size: uint64,
471 : }
472 :
473 : impl XlLogicalMessage {
474 16712 : pub fn encode(&self) -> Bytes {
475 : use utils::bin_ser::LeSer;
476 16712 : self.ser().unwrap().into()
477 16712 : }
478 : }
479 :
480 : /// Create new WAL record for non-transactional logical message.
481 : /// Used for creating artificial WAL for tests, as LogicalMessage
482 : /// record is basically no-op.
483 : ///
484 : /// NOTE: This leaves the xl_prev field zero. The safekeeper and
485 : /// pageserver tolerate that, but PostgreSQL does not.
486 4 : pub fn encode_logical_message(prefix: &str, message: &str) -> Vec<u8> {
487 4 : let mut prefix_bytes: Vec<u8> = Vec::with_capacity(prefix.len() + 1);
488 4 : prefix_bytes.write_all(prefix.as_bytes()).unwrap();
489 4 : prefix_bytes.push(0);
490 4 :
491 4 : let message_bytes = message.as_bytes();
492 4 :
493 4 : let logical_message = XlLogicalMessage {
494 4 : db_id: 0,
495 4 : transactional: 0,
496 4 : prefix_size: prefix_bytes.len() as u64,
497 4 : message_size: message_bytes.len() as u64,
498 4 : };
499 4 :
500 4 : let mainrdata = logical_message.encode();
501 4 : let mainrdata_len: usize = mainrdata.len() + prefix_bytes.len() + message_bytes.len();
502 4 : // only short mainrdata is supported for now
503 4 : assert!(mainrdata_len <= 255);
504 4 : let mainrdata_len = mainrdata_len as u8;
505 4 :
506 4 : let mut data: Vec<u8> = vec![pg_constants::XLR_BLOCK_ID_DATA_SHORT, mainrdata_len];
507 4 : data.extend_from_slice(&mainrdata);
508 4 : data.extend_from_slice(&prefix_bytes);
509 4 : data.extend_from_slice(message_bytes);
510 4 :
511 4 : let total_len = XLOG_SIZE_OF_XLOG_RECORD + data.len();
512 4 :
513 4 : let mut header = XLogRecord {
514 4 : xl_tot_len: total_len as u32,
515 4 : xl_xid: 0,
516 4 : xl_prev: 0,
517 4 : xl_info: 0,
518 4 : xl_rmid: 21,
519 4 : __bindgen_padding_0: [0u8; 2usize],
520 4 : xl_crc: 0, // crc will be calculated later
521 4 : };
522 4 :
523 4 : let header_bytes = header.encode().expect("failed to encode header");
524 4 : let crc = crc32c_append(0, &data);
525 4 : let crc = crc32c_append(crc, &header_bytes[0..XLOG_RECORD_CRC_OFFS]);
526 4 : header.xl_crc = crc;
527 4 :
528 4 : let mut wal: Vec<u8> = Vec::new();
529 4 : wal.extend_from_slice(&header.encode().expect("failed to encode header"));
530 4 : wal.extend_from_slice(&data);
531 :
532 : // WAL start position must be aligned at 8 bytes,
533 : // this will add padding for the next WAL record.
534 : const PADDING: usize = 8;
535 4 : let padding_rem = wal.len() % PADDING;
536 4 : if padding_rem != 0 {
537 0 : wal.resize(wal.len() + PADDING - padding_rem, 0);
538 4 : }
539 :
540 4 : wal
541 4 : }
542 :
543 : #[cfg(test)]
544 : mod tests {
545 : use super::*;
546 :
547 : #[test]
548 4 : fn test_ts_conversion() {
549 4 : let now = SystemTime::now();
550 4 : let round_trip = try_from_pg_timestamp(to_pg_timestamp(now)).unwrap();
551 4 :
552 4 : let now_since = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
553 4 : let round_trip_since = round_trip.duration_since(SystemTime::UNIX_EPOCH).unwrap();
554 4 : assert_eq!(now_since.as_micros(), round_trip_since.as_micros());
555 :
556 4 : let now_pg = get_current_timestamp();
557 4 : let round_trip_pg = to_pg_timestamp(try_from_pg_timestamp(now_pg).unwrap());
558 4 :
559 4 : assert_eq!(now_pg, round_trip_pg);
560 4 : }
561 :
562 : // If you need to craft WAL and write tests for this module, put it at wal_craft crate.
563 : }
|