Line data Source code
1 : //! This module houses types used in decoding of PG WAL
2 : //! records.
3 : //!
4 : //! TODO: Generate separate types for each supported PG version
5 :
6 : use bytes::{Buf, Bytes};
7 : use serde::{Deserialize, Serialize};
8 : use utils::bin_ser::DeserializeError;
9 : use utils::lsn::Lsn;
10 :
11 : use crate::{
12 : BLCKSZ, BlockNumber, MultiXactId, MultiXactOffset, MultiXactStatus, Oid, PgMajorVersion,
13 : RepOriginId, TimestampTz, TransactionId, XLOG_SIZE_OF_XLOG_RECORD, XLogRecord, pg_constants,
14 : };
15 :
16 : #[repr(C)]
17 0 : #[derive(Clone, Debug, Serialize, Deserialize)]
18 : pub struct XlMultiXactCreate {
19 : pub mid: MultiXactId,
20 : /* new MultiXact's ID */
21 : pub moff: MultiXactOffset,
22 : /* its starting offset in members file */
23 : pub nmembers: u32,
24 : /* number of member XIDs */
25 : pub members: Vec<MultiXactMember>,
26 : }
27 :
28 : impl XlMultiXactCreate {
29 0 : pub fn decode(buf: &mut Bytes) -> XlMultiXactCreate {
30 0 : let mid = buf.get_u32_le();
31 0 : let moff = buf.get_u32_le();
32 0 : let nmembers = buf.get_u32_le();
33 0 : let mut members = Vec::new();
34 0 : for _ in 0..nmembers {
35 0 : members.push(MultiXactMember::decode(buf));
36 0 : }
37 0 : XlMultiXactCreate {
38 0 : mid,
39 0 : moff,
40 0 : nmembers,
41 0 : members,
42 0 : }
43 0 : }
44 : }
45 :
46 : #[repr(C)]
47 0 : #[derive(Clone, Debug, Serialize, Deserialize)]
48 : pub struct XlMultiXactTruncate {
49 : pub oldest_multi_db: Oid,
50 : /* to-be-truncated range of multixact offsets */
51 : pub start_trunc_off: MultiXactId,
52 : /* just for completeness' sake */
53 : pub end_trunc_off: MultiXactId,
54 :
55 : /* to-be-truncated range of multixact members */
56 : pub start_trunc_memb: MultiXactOffset,
57 : pub end_trunc_memb: MultiXactOffset,
58 : }
59 :
60 : impl XlMultiXactTruncate {
61 0 : pub fn decode(buf: &mut Bytes) -> XlMultiXactTruncate {
62 0 : XlMultiXactTruncate {
63 0 : oldest_multi_db: buf.get_u32_le(),
64 0 : start_trunc_off: buf.get_u32_le(),
65 0 : end_trunc_off: buf.get_u32_le(),
66 0 : start_trunc_memb: buf.get_u32_le(),
67 0 : end_trunc_memb: buf.get_u32_le(),
68 0 : }
69 0 : }
70 : }
71 :
72 : #[repr(C)]
73 0 : #[derive(Clone, Debug, Serialize, Deserialize)]
74 : pub struct XlRelmapUpdate {
75 : pub dbid: Oid, /* database ID, or 0 for shared map */
76 : pub tsid: Oid, /* database's tablespace, or pg_global */
77 : pub nbytes: i32, /* size of relmap data */
78 : }
79 :
80 : impl XlRelmapUpdate {
81 0 : pub fn decode(buf: &mut Bytes) -> XlRelmapUpdate {
82 0 : XlRelmapUpdate {
83 0 : dbid: buf.get_u32_le(),
84 0 : tsid: buf.get_u32_le(),
85 0 : nbytes: buf.get_i32_le(),
86 0 : }
87 0 : }
88 : }
89 :
90 : #[repr(C)]
91 0 : #[derive(Clone, Debug, Serialize, Deserialize)]
92 : pub struct XlReploriginDrop {
93 : pub node_id: RepOriginId,
94 : }
95 :
96 : impl XlReploriginDrop {
97 0 : pub fn decode(buf: &mut Bytes) -> XlReploriginDrop {
98 0 : XlReploriginDrop {
99 0 : node_id: buf.get_u16_le(),
100 0 : }
101 0 : }
102 : }
103 :
104 : #[repr(C)]
105 0 : #[derive(Clone, Debug, Serialize, Deserialize)]
106 : pub struct XlReploriginSet {
107 : pub remote_lsn: Lsn,
108 : pub node_id: RepOriginId,
109 : }
110 :
111 : impl XlReploriginSet {
112 0 : pub fn decode(buf: &mut Bytes) -> XlReploriginSet {
113 0 : XlReploriginSet {
114 0 : remote_lsn: Lsn(buf.get_u64_le()),
115 0 : node_id: buf.get_u16_le(),
116 0 : }
117 0 : }
118 : }
119 :
120 : #[repr(C)]
121 0 : #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
122 : pub struct RelFileNode {
123 : pub spcnode: Oid, /* tablespace */
124 : pub dbnode: Oid, /* database */
125 : pub relnode: Oid, /* relation */
126 : }
127 :
128 : #[repr(C)]
129 0 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
130 : pub struct MultiXactMember {
131 : pub xid: TransactionId,
132 : pub status: MultiXactStatus,
133 : }
134 :
135 : impl MultiXactMember {
136 0 : pub fn decode(buf: &mut Bytes) -> MultiXactMember {
137 0 : MultiXactMember {
138 0 : xid: buf.get_u32_le(),
139 0 : status: buf.get_u32_le(),
140 0 : }
141 0 : }
142 : }
143 :
144 : /// DecodedBkpBlock represents per-page data contained in a WAL record.
145 : #[derive(Default)]
146 : pub struct DecodedBkpBlock {
147 : /* Is this block ref in use? */
148 : //in_use: bool,
149 :
150 : /* Identify the block this refers to */
151 : pub rnode_spcnode: u32,
152 : pub rnode_dbnode: u32,
153 : pub rnode_relnode: u32,
154 : // Note that we have a few special forknum values for non-rel files.
155 : pub forknum: u8,
156 : pub blkno: u32,
157 :
158 : /* copy of the fork_flags field from the XLogRecordBlockHeader */
159 : pub flags: u8,
160 :
161 : /* Information on full-page image, if any */
162 : pub has_image: bool,
163 : /* has image, even for consistency checking */
164 : pub apply_image: bool,
165 : /* has image that should be restored */
166 : pub will_init: bool,
167 : /* record doesn't need previous page version to apply */
168 : //char *bkp_image;
169 : pub hole_offset: u16,
170 : pub hole_length: u16,
171 : pub bimg_offset: u32,
172 : pub bimg_len: u16,
173 : pub bimg_info: u8,
174 :
175 : /* Buffer holding the rmgr-specific data associated with this block */
176 : has_data: bool,
177 : data_len: u16,
178 : }
179 :
180 : impl DecodedBkpBlock {
181 72821 : pub fn new() -> DecodedBkpBlock {
182 72821 : Default::default()
183 72821 : }
184 : }
185 :
186 : #[derive(Default)]
187 : pub struct DecodedWALRecord {
188 : pub xl_xid: TransactionId,
189 : pub xl_info: u8,
190 : pub xl_rmid: u8,
191 : pub record: Bytes, // raw XLogRecord
192 :
193 : pub blocks: Vec<DecodedBkpBlock>,
194 : pub main_data_offset: usize,
195 : pub origin_id: u16,
196 : }
197 :
198 : impl DecodedWALRecord {
199 : /// Check if this WAL record represents a legacy "copy" database creation, which populates new relations
200 : /// by reading other existing relations' data blocks. This is more complex to apply than new-style database
201 : /// creations which simply include all the desired blocks in the WAL, so we need a helper function to detect this case.
202 73532 : pub fn is_dbase_create_copy(&self, pg_version: PgMajorVersion) -> bool {
203 73532 : if self.xl_rmid == pg_constants::RM_DBASE_ID {
204 0 : let info = self.xl_info & pg_constants::XLR_RMGR_INFO_MASK;
205 0 : match pg_version {
206 : PgMajorVersion::PG14 => {
207 : // Postgres 14 database creations are always the legacy kind
208 0 : info == crate::v14::bindings::XLOG_DBASE_CREATE
209 : }
210 0 : PgMajorVersion::PG15 => info == crate::v15::bindings::XLOG_DBASE_CREATE_FILE_COPY,
211 0 : PgMajorVersion::PG16 => info == crate::v16::bindings::XLOG_DBASE_CREATE_FILE_COPY,
212 0 : PgMajorVersion::PG17 => info == crate::v17::bindings::XLOG_DBASE_CREATE_FILE_COPY,
213 : }
214 : } else {
215 73532 : false
216 : }
217 73532 : }
218 : }
219 :
220 : /// Main routine to decode a WAL record and figure out which blocks are modified
221 : //
222 : // See xlogrecord.h for details
223 : // The overall layout of an XLOG record is:
224 : // Fixed-size header (XLogRecord struct)
225 : // XLogRecordBlockHeader struct
226 : // If pg_constants::BKPBLOCK_HAS_IMAGE, an XLogRecordBlockImageHeader struct follows
227 : // If pg_constants::BKPIMAGE_HAS_HOLE and pg_constants::BKPIMAGE_IS_COMPRESSED, an
228 : // XLogRecordBlockCompressHeader struct follows.
229 : // If pg_constants::BKPBLOCK_SAME_REL is not set, a RelFileNode follows
230 : // BlockNumber follows
231 : // XLogRecordBlockHeader struct
232 : // ...
233 : // XLogRecordDataHeader[Short|Long] struct
234 : // block data
235 : // block data
236 : // ...
237 : // main data
238 : //
239 : //
240 : // For performance reasons, the caller provides the DecodedWALRecord struct and the function just fills it in.
241 : // It would be more natural for this function to return a DecodedWALRecord as return value,
242 : // but reusing the caller-supplied struct avoids an allocation.
243 : // This code is in the hot path for digesting incoming WAL, and is very performance sensitive.
244 : //
245 73532 : pub fn decode_wal_record(
246 73532 : record: Bytes,
247 73532 : decoded: &mut DecodedWALRecord,
248 73532 : pg_version: PgMajorVersion,
249 73532 : ) -> anyhow::Result<()> {
250 73532 : let mut rnode_spcnode: u32 = 0;
251 73532 : let mut rnode_dbnode: u32 = 0;
252 73532 : let mut rnode_relnode: u32 = 0;
253 73532 : let mut got_rnode = false;
254 73532 : let mut origin_id: u16 = 0;
255 :
256 73532 : let mut buf = record.clone();
257 :
258 : // 1. Parse XLogRecord struct
259 :
260 : // FIXME: assume little-endian here
261 73532 : let xlogrec = XLogRecord::from_bytes(&mut buf)?;
262 :
263 73532 : tracing::trace!(
264 0 : "decode_wal_record xl_rmid = {} xl_info = {}",
265 : xlogrec.xl_rmid,
266 : xlogrec.xl_info
267 : );
268 :
269 73532 : let remaining: usize = xlogrec.xl_tot_len as usize - XLOG_SIZE_OF_XLOG_RECORD;
270 :
271 73532 : if buf.remaining() != remaining {
272 0 : //TODO error
273 73532 : }
274 :
275 73532 : let mut max_block_id = 0;
276 73532 : let mut blocks_total_len: u32 = 0;
277 73532 : let mut main_data_len = 0;
278 73532 : let mut datatotal: u32 = 0;
279 73532 : decoded.blocks.clear();
280 :
281 : // 2. Decode the headers.
282 : // XLogRecordBlockHeaders if any,
283 : // XLogRecordDataHeader[Short|Long]
284 219873 : while buf.remaining() > datatotal as usize {
285 146341 : let block_id = buf.get_u8();
286 :
287 146341 : match block_id {
288 72906 : pg_constants::XLR_BLOCK_ID_DATA_SHORT => {
289 72906 : /* XLogRecordDataHeaderShort */
290 72906 : main_data_len = buf.get_u8() as u32;
291 72906 : datatotal += main_data_len;
292 72906 : }
293 :
294 614 : pg_constants::XLR_BLOCK_ID_DATA_LONG => {
295 614 : /* XLogRecordDataHeaderLong */
296 614 : main_data_len = buf.get_u32_le();
297 614 : datatotal += main_data_len;
298 614 : }
299 :
300 0 : pg_constants::XLR_BLOCK_ID_ORIGIN => {
301 0 : // RepOriginId is uint16
302 0 : origin_id = buf.get_u16_le();
303 0 : }
304 :
305 0 : pg_constants::XLR_BLOCK_ID_TOPLEVEL_XID => {
306 0 : // TransactionId is uint32
307 0 : buf.advance(4);
308 0 : }
309 :
310 72821 : 0..=pg_constants::XLR_MAX_BLOCK_ID => {
311 : /* XLogRecordBlockHeader */
312 72821 : let mut blk = DecodedBkpBlock::new();
313 :
314 72821 : if block_id <= max_block_id {
315 72821 : // TODO
316 72821 : //report_invalid_record(state,
317 72821 : // "out-of-order block_id %u at %X/%X",
318 72821 : // block_id,
319 72821 : // (uint32) (state->ReadRecPtr >> 32),
320 72821 : // (uint32) state->ReadRecPtr);
321 72821 : // goto err;
322 72821 : }
323 72821 : max_block_id = block_id;
324 :
325 72821 : let fork_flags: u8 = buf.get_u8();
326 72821 : blk.forknum = fork_flags & pg_constants::BKPBLOCK_FORK_MASK;
327 72821 : blk.flags = fork_flags;
328 72821 : blk.has_image = (fork_flags & pg_constants::BKPBLOCK_HAS_IMAGE) != 0;
329 72821 : blk.has_data = (fork_flags & pg_constants::BKPBLOCK_HAS_DATA) != 0;
330 72821 : blk.will_init = (fork_flags & pg_constants::BKPBLOCK_WILL_INIT) != 0;
331 72821 : blk.data_len = buf.get_u16_le();
332 :
333 : /* TODO cross-check that the HAS_DATA flag is set iff data_length > 0 */
334 :
335 72821 : datatotal += blk.data_len as u32;
336 72821 : blocks_total_len += blk.data_len as u32;
337 :
338 72821 : if blk.has_image {
339 30 : blk.bimg_len = buf.get_u16_le();
340 30 : blk.hole_offset = buf.get_u16_le();
341 30 : blk.bimg_info = buf.get_u8();
342 :
343 30 : blk.apply_image = dispatch_pgversion!(
344 30 : pg_version,
345 0 : (blk.bimg_info & pgv::bindings::BKPIMAGE_APPLY) != 0
346 : );
347 :
348 30 : let blk_img_is_compressed =
349 30 : crate::bkpimage_is_compressed(blk.bimg_info, pg_version);
350 :
351 30 : if blk_img_is_compressed {
352 0 : tracing::debug!("compressed block image , pg_version = {}", pg_version);
353 30 : }
354 :
355 30 : if blk_img_is_compressed {
356 0 : if blk.bimg_info & pg_constants::BKPIMAGE_HAS_HOLE != 0 {
357 0 : blk.hole_length = buf.get_u16_le();
358 0 : } else {
359 0 : blk.hole_length = 0;
360 0 : }
361 30 : } else {
362 30 : blk.hole_length = BLCKSZ - blk.bimg_len;
363 30 : }
364 30 : datatotal += blk.bimg_len as u32;
365 30 : blocks_total_len += blk.bimg_len as u32;
366 :
367 : /*
368 : * cross-check that hole_offset > 0, hole_length > 0 and
369 : * bimg_len < BLCKSZ if the HAS_HOLE flag is set.
370 : */
371 30 : if blk.bimg_info & pg_constants::BKPIMAGE_HAS_HOLE != 0
372 18 : && (blk.hole_offset == 0 || blk.hole_length == 0 || blk.bimg_len == BLCKSZ)
373 0 : {
374 0 : // TODO
375 0 : /*
376 0 : report_invalid_record(state,
377 0 : "pg_constants::BKPIMAGE_HAS_HOLE set, but hole offset %u length %u block image length %u at %X/%X",
378 0 : (unsigned int) blk->hole_offset,
379 0 : (unsigned int) blk->hole_length,
380 0 : (unsigned int) blk->bimg_len,
381 0 : (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
382 0 : goto err;
383 0 : */
384 30 : }
385 :
386 : /*
387 : * cross-check that hole_offset == 0 and hole_length == 0 if
388 : * the HAS_HOLE flag is not set.
389 : */
390 30 : if blk.bimg_info & pg_constants::BKPIMAGE_HAS_HOLE == 0
391 12 : && (blk.hole_offset != 0 || blk.hole_length != 0)
392 0 : {
393 0 : // TODO
394 0 : /*
395 0 : report_invalid_record(state,
396 0 : "pg_constants::BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X",
397 0 : (unsigned int) blk->hole_offset,
398 0 : (unsigned int) blk->hole_length,
399 0 : (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
400 0 : goto err;
401 0 : */
402 30 : }
403 :
404 : /*
405 : * cross-check that bimg_len < BLCKSZ if the IS_COMPRESSED
406 : * flag is set.
407 : */
408 30 : if !blk_img_is_compressed && blk.bimg_len == BLCKSZ {
409 12 : // TODO
410 12 : /*
411 12 : report_invalid_record(state,
412 12 : "pg_constants::BKPIMAGE_IS_COMPRESSED set, but block image length %u at %X/%X",
413 12 : (unsigned int) blk->bimg_len,
414 12 : (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
415 12 : goto err;
416 12 : */
417 18 : }
418 :
419 : /*
420 : * cross-check that bimg_len = BLCKSZ if neither HAS_HOLE nor
421 : * IS_COMPRESSED flag is set.
422 : */
423 30 : if blk.bimg_info & pg_constants::BKPIMAGE_HAS_HOLE == 0
424 12 : && !blk_img_is_compressed
425 12 : && blk.bimg_len != BLCKSZ
426 0 : {
427 0 : // TODO
428 0 : /*
429 0 : report_invalid_record(state,
430 0 : "neither pg_constants::BKPIMAGE_HAS_HOLE nor pg_constants::BKPIMAGE_IS_COMPRESSED set, but block image length is %u at %X/%X",
431 0 : (unsigned int) blk->data_len,
432 0 : (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
433 0 : goto err;
434 0 : */
435 30 : }
436 72791 : }
437 72821 : if fork_flags & pg_constants::BKPBLOCK_SAME_REL == 0 {
438 72821 : rnode_spcnode = buf.get_u32_le();
439 72821 : rnode_dbnode = buf.get_u32_le();
440 72821 : rnode_relnode = buf.get_u32_le();
441 72821 : got_rnode = true;
442 72821 : } else if !got_rnode {
443 0 : // TODO
444 0 : /*
445 0 : report_invalid_record(state,
446 0 : "pg_constants::BKPBLOCK_SAME_REL set but no previous rel at %X/%X",
447 0 : (uint32) (state->ReadRecPtr >> 32), (uint32) state->ReadRecPtr);
448 0 : goto err; */
449 0 : }
450 :
451 72821 : blk.rnode_spcnode = rnode_spcnode;
452 72821 : blk.rnode_dbnode = rnode_dbnode;
453 72821 : blk.rnode_relnode = rnode_relnode;
454 :
455 72821 : blk.blkno = buf.get_u32_le();
456 72821 : tracing::trace!(
457 0 : "this record affects {}/{}/{} blk {}",
458 : rnode_spcnode,
459 : rnode_dbnode,
460 : rnode_relnode,
461 : blk.blkno
462 : );
463 :
464 72821 : decoded.blocks.push(blk);
465 : }
466 :
467 0 : _ => {
468 0 : // TODO: invalid block_id
469 0 : }
470 : }
471 : }
472 :
473 : // 3. Decode blocks.
474 73532 : let mut ptr = record.len() - buf.remaining();
475 73532 : for blk in decoded.blocks.iter_mut() {
476 72821 : if blk.has_image {
477 30 : blk.bimg_offset = ptr as u32;
478 30 : ptr += blk.bimg_len as usize;
479 72791 : }
480 72821 : if blk.has_data {
481 72791 : ptr += blk.data_len as usize;
482 72791 : }
483 : }
484 : // We don't need them, so just skip blocks_total_len bytes
485 73532 : buf.advance(blocks_total_len as usize);
486 73532 : assert_eq!(ptr, record.len() - buf.remaining());
487 :
488 73532 : let main_data_offset = (xlogrec.xl_tot_len - main_data_len) as usize;
489 :
490 : // 4. Decode main_data
491 73532 : if main_data_len > 0 {
492 73520 : assert_eq!(buf.remaining(), main_data_len as usize);
493 12 : }
494 :
495 73532 : decoded.xl_xid = xlogrec.xl_xid;
496 73532 : decoded.xl_info = xlogrec.xl_info;
497 73532 : decoded.xl_rmid = xlogrec.xl_rmid;
498 73532 : decoded.record = record;
499 73532 : decoded.origin_id = origin_id;
500 73532 : decoded.main_data_offset = main_data_offset;
501 :
502 73532 : Ok(())
503 73532 : }
504 :
505 : pub mod v14 {
506 : use bytes::{Buf, Bytes};
507 :
508 : use crate::{OffsetNumber, TransactionId};
509 :
510 : #[repr(C)]
511 : #[derive(Debug)]
512 : pub struct XlHeapInsert {
513 : pub offnum: OffsetNumber,
514 : pub flags: u8,
515 : }
516 :
517 : impl XlHeapInsert {
518 72638 : pub fn decode(buf: &mut Bytes) -> XlHeapInsert {
519 72638 : XlHeapInsert {
520 72638 : offnum: buf.get_u16_le(),
521 72638 : flags: buf.get_u8(),
522 72638 : }
523 72638 : }
524 : }
525 :
526 : #[repr(C)]
527 : #[derive(Debug)]
528 : pub struct XlHeapMultiInsert {
529 : pub flags: u8,
530 : pub _padding: u8,
531 : pub ntuples: u16,
532 : }
533 :
534 : impl XlHeapMultiInsert {
535 21 : pub fn decode(buf: &mut Bytes) -> XlHeapMultiInsert {
536 21 : XlHeapMultiInsert {
537 21 : flags: buf.get_u8(),
538 21 : _padding: buf.get_u8(),
539 21 : ntuples: buf.get_u16_le(),
540 21 : }
541 21 : }
542 : }
543 :
544 : #[repr(C)]
545 : #[derive(Debug)]
546 : pub struct XlHeapDelete {
547 : pub xmax: TransactionId,
548 : pub offnum: OffsetNumber,
549 : pub _padding: u16,
550 : pub t_cid: u32,
551 : pub infobits_set: u8,
552 : pub flags: u8,
553 : }
554 :
555 : impl XlHeapDelete {
556 0 : pub fn decode(buf: &mut Bytes) -> XlHeapDelete {
557 0 : XlHeapDelete {
558 0 : xmax: buf.get_u32_le(),
559 0 : offnum: buf.get_u16_le(),
560 0 : _padding: buf.get_u16_le(),
561 0 : t_cid: buf.get_u32_le(),
562 0 : infobits_set: buf.get_u8(),
563 0 : flags: buf.get_u8(),
564 0 : }
565 0 : }
566 : }
567 :
568 : #[repr(C)]
569 : #[derive(Debug)]
570 : pub struct XlHeapUpdate {
571 : pub old_xmax: TransactionId,
572 : pub old_offnum: OffsetNumber,
573 : pub old_infobits_set: u8,
574 : pub flags: u8,
575 : pub t_cid: u32,
576 : pub new_xmax: TransactionId,
577 : pub new_offnum: OffsetNumber,
578 : }
579 :
580 : impl XlHeapUpdate {
581 4 : pub fn decode(buf: &mut Bytes) -> XlHeapUpdate {
582 4 : XlHeapUpdate {
583 4 : old_xmax: buf.get_u32_le(),
584 4 : old_offnum: buf.get_u16_le(),
585 4 : old_infobits_set: buf.get_u8(),
586 4 : flags: buf.get_u8(),
587 4 : t_cid: buf.get_u32_le(),
588 4 : new_xmax: buf.get_u32_le(),
589 4 : new_offnum: buf.get_u16_le(),
590 4 : }
591 4 : }
592 : }
593 :
594 : #[repr(C)]
595 : #[derive(Debug)]
596 : pub struct XlHeapLock {
597 : pub locking_xid: TransactionId,
598 : pub offnum: OffsetNumber,
599 : pub _padding: u16,
600 : pub t_cid: u32,
601 : pub infobits_set: u8,
602 : pub flags: u8,
603 : }
604 :
605 : impl XlHeapLock {
606 0 : pub fn decode(buf: &mut Bytes) -> XlHeapLock {
607 0 : XlHeapLock {
608 0 : locking_xid: buf.get_u32_le(),
609 0 : offnum: buf.get_u16_le(),
610 0 : _padding: buf.get_u16_le(),
611 0 : t_cid: buf.get_u32_le(),
612 0 : infobits_set: buf.get_u8(),
613 0 : flags: buf.get_u8(),
614 0 : }
615 0 : }
616 : }
617 :
618 : #[repr(C)]
619 : #[derive(Debug)]
620 : pub struct XlHeapLockUpdated {
621 : pub xmax: TransactionId,
622 : pub offnum: OffsetNumber,
623 : pub infobits_set: u8,
624 : pub flags: u8,
625 : }
626 :
627 : impl XlHeapLockUpdated {
628 0 : pub fn decode(buf: &mut Bytes) -> XlHeapLockUpdated {
629 0 : XlHeapLockUpdated {
630 0 : xmax: buf.get_u32_le(),
631 0 : offnum: buf.get_u16_le(),
632 0 : infobits_set: buf.get_u8(),
633 0 : flags: buf.get_u8(),
634 0 : }
635 0 : }
636 : }
637 :
638 : #[repr(C)]
639 : #[derive(Debug)]
640 : pub struct XlParameterChange {
641 : pub max_connections: i32,
642 : pub max_worker_processes: i32,
643 : pub max_wal_senders: i32,
644 : pub max_prepared_xacts: i32,
645 : pub max_locks_per_xact: i32,
646 : pub wal_level: i32,
647 : pub wal_log_hints: bool,
648 : pub track_commit_timestamp: bool,
649 : pub _padding: [u8; 2],
650 : }
651 :
652 : impl XlParameterChange {
653 0 : pub fn decode(buf: &mut Bytes) -> XlParameterChange {
654 0 : XlParameterChange {
655 0 : max_connections: buf.get_i32_le(),
656 0 : max_worker_processes: buf.get_i32_le(),
657 0 : max_wal_senders: buf.get_i32_le(),
658 0 : max_prepared_xacts: buf.get_i32_le(),
659 0 : max_locks_per_xact: buf.get_i32_le(),
660 0 : wal_level: buf.get_i32_le(),
661 0 : wal_log_hints: buf.get_u8() != 0,
662 0 : track_commit_timestamp: buf.get_u8() != 0,
663 0 : _padding: [buf.get_u8(), buf.get_u8()],
664 0 : }
665 0 : }
666 : }
667 : }
668 :
669 : pub mod v15 {
670 : pub use super::v14::{
671 : XlHeapDelete, XlHeapInsert, XlHeapLock, XlHeapLockUpdated, XlHeapMultiInsert, XlHeapUpdate,
672 : XlParameterChange,
673 : };
674 : }
675 :
676 : pub mod v16 {
677 : use bytes::{Buf, Bytes};
678 :
679 : pub use super::v14::{XlHeapInsert, XlHeapLockUpdated, XlHeapMultiInsert, XlParameterChange};
680 : use crate::{OffsetNumber, TransactionId};
681 :
682 : pub struct XlHeapDelete {
683 : pub xmax: TransactionId,
684 : pub offnum: OffsetNumber,
685 : pub infobits_set: u8,
686 : pub flags: u8,
687 : }
688 :
689 : impl XlHeapDelete {
690 0 : pub fn decode(buf: &mut Bytes) -> XlHeapDelete {
691 0 : XlHeapDelete {
692 0 : xmax: buf.get_u32_le(),
693 0 : offnum: buf.get_u16_le(),
694 0 : infobits_set: buf.get_u8(),
695 0 : flags: buf.get_u8(),
696 0 : }
697 0 : }
698 : }
699 :
700 : #[repr(C)]
701 : #[derive(Debug)]
702 : pub struct XlHeapUpdate {
703 : pub old_xmax: TransactionId,
704 : pub old_offnum: OffsetNumber,
705 : pub old_infobits_set: u8,
706 : pub flags: u8,
707 : pub new_xmax: TransactionId,
708 : pub new_offnum: OffsetNumber,
709 : }
710 :
711 : impl XlHeapUpdate {
712 0 : pub fn decode(buf: &mut Bytes) -> XlHeapUpdate {
713 0 : XlHeapUpdate {
714 0 : old_xmax: buf.get_u32_le(),
715 0 : old_offnum: buf.get_u16_le(),
716 0 : old_infobits_set: buf.get_u8(),
717 0 : flags: buf.get_u8(),
718 0 : new_xmax: buf.get_u32_le(),
719 0 : new_offnum: buf.get_u16_le(),
720 0 : }
721 0 : }
722 : }
723 :
724 : #[repr(C)]
725 : #[derive(Debug)]
726 : pub struct XlHeapLock {
727 : pub locking_xid: TransactionId,
728 : pub offnum: OffsetNumber,
729 : pub infobits_set: u8,
730 : pub flags: u8,
731 : }
732 :
733 : impl XlHeapLock {
734 0 : pub fn decode(buf: &mut Bytes) -> XlHeapLock {
735 0 : XlHeapLock {
736 0 : locking_xid: buf.get_u32_le(),
737 0 : offnum: buf.get_u16_le(),
738 0 : infobits_set: buf.get_u8(),
739 0 : flags: buf.get_u8(),
740 0 : }
741 0 : }
742 : }
743 :
744 : /* Since PG16, we have the Neon RMGR (RM_NEON_ID) to manage Neon-flavored WAL. */
745 : pub mod rm_neon {
746 : use bytes::{Buf, Bytes};
747 :
748 : use crate::{OffsetNumber, TransactionId};
749 :
750 : #[repr(C)]
751 : #[derive(Debug)]
752 : pub struct XlNeonHeapInsert {
753 : pub offnum: OffsetNumber,
754 : pub flags: u8,
755 : }
756 :
757 : impl XlNeonHeapInsert {
758 0 : pub fn decode(buf: &mut Bytes) -> XlNeonHeapInsert {
759 0 : XlNeonHeapInsert {
760 0 : offnum: buf.get_u16_le(),
761 0 : flags: buf.get_u8(),
762 0 : }
763 0 : }
764 : }
765 :
766 : #[repr(C)]
767 : #[derive(Debug)]
768 : pub struct XlNeonHeapMultiInsert {
769 : pub flags: u8,
770 : pub _padding: u8,
771 : pub ntuples: u16,
772 : pub t_cid: u32,
773 : }
774 :
775 : impl XlNeonHeapMultiInsert {
776 0 : pub fn decode(buf: &mut Bytes) -> XlNeonHeapMultiInsert {
777 0 : XlNeonHeapMultiInsert {
778 0 : flags: buf.get_u8(),
779 0 : _padding: buf.get_u8(),
780 0 : ntuples: buf.get_u16_le(),
781 0 : t_cid: buf.get_u32_le(),
782 0 : }
783 0 : }
784 : }
785 :
786 : #[repr(C)]
787 : #[derive(Debug)]
788 : pub struct XlNeonHeapDelete {
789 : pub xmax: TransactionId,
790 : pub offnum: OffsetNumber,
791 : pub infobits_set: u8,
792 : pub flags: u8,
793 : pub t_cid: u32,
794 : }
795 :
796 : impl XlNeonHeapDelete {
797 0 : pub fn decode(buf: &mut Bytes) -> XlNeonHeapDelete {
798 0 : XlNeonHeapDelete {
799 0 : xmax: buf.get_u32_le(),
800 0 : offnum: buf.get_u16_le(),
801 0 : infobits_set: buf.get_u8(),
802 0 : flags: buf.get_u8(),
803 0 : t_cid: buf.get_u32_le(),
804 0 : }
805 0 : }
806 : }
807 :
808 : #[repr(C)]
809 : #[derive(Debug)]
810 : pub struct XlNeonHeapUpdate {
811 : pub old_xmax: TransactionId,
812 : pub old_offnum: OffsetNumber,
813 : pub old_infobits_set: u8,
814 : pub flags: u8,
815 : pub t_cid: u32,
816 : pub new_xmax: TransactionId,
817 : pub new_offnum: OffsetNumber,
818 : }
819 :
820 : impl XlNeonHeapUpdate {
821 0 : pub fn decode(buf: &mut Bytes) -> XlNeonHeapUpdate {
822 0 : XlNeonHeapUpdate {
823 0 : old_xmax: buf.get_u32_le(),
824 0 : old_offnum: buf.get_u16_le(),
825 0 : old_infobits_set: buf.get_u8(),
826 0 : flags: buf.get_u8(),
827 0 : t_cid: buf.get_u32(),
828 0 : new_xmax: buf.get_u32_le(),
829 0 : new_offnum: buf.get_u16_le(),
830 0 : }
831 0 : }
832 : }
833 :
834 : #[repr(C)]
835 : #[derive(Debug)]
836 : pub struct XlNeonHeapLock {
837 : pub locking_xid: TransactionId,
838 : pub t_cid: u32,
839 : pub offnum: OffsetNumber,
840 : pub infobits_set: u8,
841 : pub flags: u8,
842 : }
843 :
844 : impl XlNeonHeapLock {
845 0 : pub fn decode(buf: &mut Bytes) -> XlNeonHeapLock {
846 0 : XlNeonHeapLock {
847 0 : locking_xid: buf.get_u32_le(),
848 0 : t_cid: buf.get_u32_le(),
849 0 : offnum: buf.get_u16_le(),
850 0 : infobits_set: buf.get_u8(),
851 0 : flags: buf.get_u8(),
852 0 : }
853 0 : }
854 : }
855 : }
856 : }
857 :
858 : pub mod v17 {
859 : use bytes::{Buf, Bytes};
860 :
861 : pub use super::v14::XlHeapLockUpdated;
862 : pub use super::v16::{
863 : XlHeapDelete, XlHeapInsert, XlHeapLock, XlHeapMultiInsert, XlHeapUpdate, XlParameterChange,
864 : rm_neon,
865 : };
866 : pub use crate::{TimeLineID, TimestampTz};
867 :
868 : #[repr(C)]
869 : #[derive(Debug)]
870 : pub struct XlEndOfRecovery {
871 : pub end_time: TimestampTz,
872 : pub this_time_line_id: TimeLineID,
873 : pub prev_time_line_id: TimeLineID,
874 : pub wal_level: i32,
875 : }
876 :
877 : impl XlEndOfRecovery {
878 0 : pub fn decode(buf: &mut Bytes) -> XlEndOfRecovery {
879 0 : XlEndOfRecovery {
880 0 : end_time: buf.get_i64_le(),
881 0 : this_time_line_id: buf.get_u32_le(),
882 0 : prev_time_line_id: buf.get_u32_le(),
883 0 : wal_level: buf.get_i32_le(),
884 0 : }
885 0 : }
886 : }
887 : }
888 :
889 : #[repr(C)]
890 : #[derive(Debug)]
891 : pub struct XlSmgrCreate {
892 : pub rnode: RelFileNode,
893 : // FIXME: This is ForkNumber in storage_xlog.h. That's an enum. Does it have
894 : // well-defined size?
895 : pub forknum: u8,
896 : }
897 :
898 : impl XlSmgrCreate {
899 8 : pub fn decode(buf: &mut Bytes) -> XlSmgrCreate {
900 8 : XlSmgrCreate {
901 8 : rnode: RelFileNode {
902 8 : spcnode: buf.get_u32_le(), /* tablespace */
903 8 : dbnode: buf.get_u32_le(), /* database */
904 8 : relnode: buf.get_u32_le(), /* relation */
905 8 : },
906 8 : forknum: buf.get_u32_le() as u8,
907 8 : }
908 8 : }
909 : }
910 :
911 : #[repr(C)]
912 0 : #[derive(Clone, Debug, Serialize, Deserialize)]
913 : pub struct XlSmgrTruncate {
914 : pub blkno: BlockNumber,
915 : pub rnode: RelFileNode,
916 : pub flags: u32,
917 : }
918 :
919 : impl XlSmgrTruncate {
920 0 : pub fn decode(buf: &mut Bytes) -> XlSmgrTruncate {
921 0 : XlSmgrTruncate {
922 0 : blkno: buf.get_u32_le(),
923 0 : rnode: RelFileNode {
924 0 : spcnode: buf.get_u32_le(), /* tablespace */
925 0 : dbnode: buf.get_u32_le(), /* database */
926 0 : relnode: buf.get_u32_le(), /* relation */
927 0 : },
928 0 : flags: buf.get_u32_le(),
929 0 : }
930 0 : }
931 : }
932 :
933 : #[repr(C)]
934 : #[derive(Debug)]
935 : pub struct XlCreateDatabase {
936 : pub db_id: Oid,
937 : pub tablespace_id: Oid,
938 : pub src_db_id: Oid,
939 : pub src_tablespace_id: Oid,
940 : }
941 :
942 : impl XlCreateDatabase {
943 0 : pub fn decode(buf: &mut Bytes) -> XlCreateDatabase {
944 0 : XlCreateDatabase {
945 0 : db_id: buf.get_u32_le(),
946 0 : tablespace_id: buf.get_u32_le(),
947 0 : src_db_id: buf.get_u32_le(),
948 0 : src_tablespace_id: buf.get_u32_le(),
949 0 : }
950 0 : }
951 : }
952 :
953 : #[repr(C)]
954 : #[derive(Debug)]
955 : pub struct XlDropDatabase {
956 : pub db_id: Oid,
957 : pub n_tablespaces: Oid, /* number of tablespace IDs */
958 : pub tablespace_ids: Vec<Oid>,
959 : }
960 :
961 : impl XlDropDatabase {
962 0 : pub fn decode(buf: &mut Bytes) -> XlDropDatabase {
963 0 : let mut rec = XlDropDatabase {
964 0 : db_id: buf.get_u32_le(),
965 0 : n_tablespaces: buf.get_u32_le(),
966 0 : tablespace_ids: Vec::<Oid>::new(),
967 0 : };
968 :
969 0 : for _i in 0..rec.n_tablespaces {
970 0 : let id = buf.get_u32_le();
971 0 : rec.tablespace_ids.push(id);
972 0 : }
973 :
974 0 : rec
975 0 : }
976 : }
977 :
978 : ///
979 : /// Note: Parsing some fields is missing, because they're not needed.
980 : ///
981 : /// This is similar to the xl_xact_parsed_commit and
982 : /// xl_xact_parsed_abort structs in PostgreSQL, but we use the same
983 : /// struct for commits and aborts.
984 : ///
985 0 : #[derive(Clone, Debug, Serialize, Deserialize)]
986 : pub struct XlXactParsedRecord {
987 : pub xid: TransactionId,
988 : pub info: u8,
989 : pub xact_time: TimestampTz,
990 : pub xinfo: u32,
991 :
992 : pub db_id: Oid,
993 : /* MyDatabaseId */
994 : pub ts_id: Oid,
995 : /* MyDatabaseTableSpace */
996 : pub subxacts: Vec<TransactionId>,
997 :
998 : pub xnodes: Vec<RelFileNode>,
999 : pub origin_lsn: Lsn,
1000 : }
1001 :
1002 : impl XlXactParsedRecord {
1003 : /// Decode a XLOG_XACT_COMMIT/ABORT/COMMIT_PREPARED/ABORT_PREPARED
1004 : /// record. This should agree with the ParseCommitRecord and ParseAbortRecord
1005 : /// functions in PostgreSQL (in src/backend/access/rmgr/xactdesc.c)
1006 4 : pub fn decode(buf: &mut Bytes, mut xid: TransactionId, xl_info: u8) -> XlXactParsedRecord {
1007 4 : let info = xl_info & pg_constants::XLOG_XACT_OPMASK;
1008 : // The record starts with time of commit/abort
1009 4 : let xact_time = buf.get_i64_le();
1010 4 : let xinfo = if xl_info & pg_constants::XLOG_XACT_HAS_INFO != 0 {
1011 4 : buf.get_u32_le()
1012 : } else {
1013 0 : 0
1014 : };
1015 : let db_id;
1016 : let ts_id;
1017 4 : if xinfo & pg_constants::XACT_XINFO_HAS_DBINFO != 0 {
1018 4 : db_id = buf.get_u32_le();
1019 4 : ts_id = buf.get_u32_le();
1020 4 : } else {
1021 0 : db_id = 0;
1022 0 : ts_id = 0;
1023 0 : }
1024 4 : let mut subxacts = Vec::<TransactionId>::new();
1025 4 : if xinfo & pg_constants::XACT_XINFO_HAS_SUBXACTS != 0 {
1026 0 : let nsubxacts = buf.get_i32_le();
1027 0 : for _i in 0..nsubxacts {
1028 0 : let subxact = buf.get_u32_le();
1029 0 : subxacts.push(subxact);
1030 0 : }
1031 4 : }
1032 4 : let mut xnodes = Vec::<RelFileNode>::new();
1033 4 : if xinfo & pg_constants::XACT_XINFO_HAS_RELFILENODES != 0 {
1034 0 : let nrels = buf.get_i32_le();
1035 0 : for _i in 0..nrels {
1036 0 : let spcnode = buf.get_u32_le();
1037 0 : let dbnode = buf.get_u32_le();
1038 0 : let relnode = buf.get_u32_le();
1039 0 : tracing::trace!(
1040 0 : "XLOG_XACT_COMMIT relfilenode {}/{}/{}",
1041 : spcnode,
1042 : dbnode,
1043 : relnode
1044 : );
1045 0 : xnodes.push(RelFileNode {
1046 0 : spcnode,
1047 0 : dbnode,
1048 0 : relnode,
1049 0 : });
1050 : }
1051 4 : }
1052 :
1053 4 : if xinfo & crate::v15::bindings::XACT_XINFO_HAS_DROPPED_STATS != 0 {
1054 0 : let nitems = buf.get_i32_le();
1055 0 : tracing::debug!(
1056 0 : "XLOG_XACT_COMMIT-XACT_XINFO_HAS_DROPPED_STAT nitems {}",
1057 : nitems
1058 : );
1059 0 : let sizeof_xl_xact_stats_item = 12;
1060 0 : buf.advance((nitems * sizeof_xl_xact_stats_item).try_into().unwrap());
1061 4 : }
1062 :
1063 4 : if xinfo & pg_constants::XACT_XINFO_HAS_INVALS != 0 {
1064 4 : let nmsgs = buf.get_i32_le();
1065 4 : let sizeof_shared_invalidation_message = 16;
1066 4 : buf.advance(
1067 4 : (nmsgs * sizeof_shared_invalidation_message)
1068 4 : .try_into()
1069 4 : .unwrap(),
1070 4 : );
1071 4 : }
1072 :
1073 4 : if xinfo & pg_constants::XACT_XINFO_HAS_TWOPHASE != 0 {
1074 0 : xid = buf.get_u32_le();
1075 0 : tracing::debug!("XLOG_XACT_COMMIT-XACT_XINFO_HAS_TWOPHASE xid {}", xid);
1076 4 : }
1077 :
1078 4 : let origin_lsn = if xinfo & pg_constants::XACT_XINFO_HAS_ORIGIN != 0 {
1079 0 : Lsn(buf.get_u64_le())
1080 : } else {
1081 4 : Lsn::INVALID
1082 : };
1083 4 : XlXactParsedRecord {
1084 4 : xid,
1085 4 : info,
1086 4 : xact_time,
1087 4 : xinfo,
1088 4 : db_id,
1089 4 : ts_id,
1090 4 : subxacts,
1091 4 : xnodes,
1092 4 : origin_lsn,
1093 4 : }
1094 4 : }
1095 : }
1096 :
1097 : #[repr(C)]
1098 : #[derive(Debug)]
1099 : pub struct XlClogTruncate {
1100 : pub pageno: u32,
1101 : pub oldest_xid: TransactionId,
1102 : pub oldest_xid_db: Oid,
1103 : }
1104 :
1105 : impl XlClogTruncate {
1106 0 : pub fn decode(buf: &mut Bytes, pg_version: PgMajorVersion) -> XlClogTruncate {
1107 : XlClogTruncate {
1108 0 : pageno: if pg_version < PgMajorVersion::PG17 {
1109 0 : buf.get_u32_le()
1110 : } else {
1111 0 : buf.get_u64_le() as u32
1112 : },
1113 0 : oldest_xid: buf.get_u32_le(),
1114 0 : oldest_xid_db: buf.get_u32_le(),
1115 : }
1116 0 : }
1117 : }
1118 :
1119 : #[repr(C)]
1120 : #[derive(Debug)]
1121 : pub struct XlLogicalMessage {
1122 : pub db_id: Oid,
1123 : pub transactional: bool,
1124 : pub prefix_size: usize,
1125 : pub message_size: usize,
1126 : }
1127 :
1128 : impl XlLogicalMessage {
1129 606 : pub fn decode(buf: &mut Bytes) -> XlLogicalMessage {
1130 606 : XlLogicalMessage {
1131 606 : db_id: buf.get_u32_le(),
1132 606 : transactional: buf.get_u32_le() != 0, // 4-bytes alignment
1133 606 : prefix_size: buf.get_u64_le() as usize,
1134 606 : message_size: buf.get_u64_le() as usize,
1135 606 : }
1136 606 : }
1137 : }
1138 :
1139 : #[repr(C)]
1140 : #[derive(Debug)]
1141 : pub struct XlRunningXacts {
1142 : pub xcnt: u32,
1143 : pub subxcnt: u32,
1144 : pub subxid_overflow: bool,
1145 : pub next_xid: TransactionId,
1146 : pub oldest_running_xid: TransactionId,
1147 : pub latest_completed_xid: TransactionId,
1148 : pub xids: Vec<TransactionId>,
1149 : }
1150 :
1151 : impl XlRunningXacts {
1152 0 : pub fn decode(buf: &mut Bytes) -> XlRunningXacts {
1153 0 : let xcnt = buf.get_u32_le();
1154 0 : let subxcnt = buf.get_u32_le();
1155 0 : let subxid_overflow = buf.get_u32_le() != 0;
1156 0 : let next_xid = buf.get_u32_le();
1157 0 : let oldest_running_xid = buf.get_u32_le();
1158 0 : let latest_completed_xid = buf.get_u32_le();
1159 0 : let mut xids = Vec::new();
1160 0 : for _ in 0..(xcnt + subxcnt) {
1161 0 : xids.push(buf.get_u32_le());
1162 0 : }
1163 0 : XlRunningXacts {
1164 0 : xcnt,
1165 0 : subxcnt,
1166 0 : subxid_overflow,
1167 0 : next_xid,
1168 0 : oldest_running_xid,
1169 0 : latest_completed_xid,
1170 0 : xids,
1171 0 : }
1172 0 : }
1173 : }
1174 :
1175 0 : pub fn describe_postgres_wal_record(record: &Bytes) -> Result<String, DeserializeError> {
1176 : // TODO: It would be nice to use the PostgreSQL rmgrdesc infrastructure for this.
1177 : // Maybe use the postgres wal redo process, the same used for replaying WAL records?
1178 : // Or could we compile the rmgrdesc routines into the dump_layer_file() binary directly,
1179 : // without worrying about security?
1180 : //
1181 : // But for now, we have a hand-written code for a few common WAL record types here.
1182 :
1183 0 : let mut buf = record.clone();
1184 :
1185 : // 1. Parse XLogRecord struct
1186 :
1187 : // FIXME: assume little-endian here
1188 0 : let xlogrec = XLogRecord::from_bytes(&mut buf)?;
1189 :
1190 : let unknown_str: String;
1191 :
1192 0 : let result: &str = match xlogrec.xl_rmid {
1193 : pg_constants::RM_HEAP2_ID => {
1194 0 : let info = xlogrec.xl_info & pg_constants::XLOG_HEAP_OPMASK;
1195 0 : match info {
1196 0 : pg_constants::XLOG_HEAP2_MULTI_INSERT => "HEAP2 MULTI_INSERT",
1197 0 : pg_constants::XLOG_HEAP2_VISIBLE => "HEAP2 VISIBLE",
1198 : _ => {
1199 0 : unknown_str = format!("HEAP2 UNKNOWN_0x{info:02x}");
1200 0 : &unknown_str
1201 : }
1202 : }
1203 : }
1204 : pg_constants::RM_HEAP_ID => {
1205 0 : let info = xlogrec.xl_info & pg_constants::XLOG_HEAP_OPMASK;
1206 0 : match info {
1207 0 : pg_constants::XLOG_HEAP_INSERT => "HEAP INSERT",
1208 0 : pg_constants::XLOG_HEAP_DELETE => "HEAP DELETE",
1209 0 : pg_constants::XLOG_HEAP_UPDATE => "HEAP UPDATE",
1210 0 : pg_constants::XLOG_HEAP_HOT_UPDATE => "HEAP HOT_UPDATE",
1211 : _ => {
1212 0 : unknown_str = format!("HEAP2 UNKNOWN_0x{info:02x}");
1213 0 : &unknown_str
1214 : }
1215 : }
1216 : }
1217 : pg_constants::RM_XLOG_ID => {
1218 0 : let info = xlogrec.xl_info & pg_constants::XLR_RMGR_INFO_MASK;
1219 0 : match info {
1220 0 : pg_constants::XLOG_FPI => "XLOG FPI",
1221 0 : pg_constants::XLOG_FPI_FOR_HINT => "XLOG FPI_FOR_HINT",
1222 : _ => {
1223 0 : unknown_str = format!("XLOG UNKNOWN_0x{info:02x}");
1224 0 : &unknown_str
1225 : }
1226 : }
1227 : }
1228 0 : rmid => {
1229 0 : let info = xlogrec.xl_info & pg_constants::XLR_RMGR_INFO_MASK;
1230 :
1231 0 : unknown_str = format!("UNKNOWN_RM_{rmid} INFO_0x{info:02x}");
1232 0 : &unknown_str
1233 : }
1234 : };
1235 :
1236 0 : Ok(String::from(result))
1237 0 : }
|