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