@@ -31,6 +31,8 @@ static const char *progname;
3131static int WalSegSz ;
3232static volatile sig_atomic_t time_to_stop = false;
3333
34+ static const RelFileNode emptyRelFileNode = {0 , 0 , 0 };
35+
3436typedef struct XLogDumpPrivate
3537{
3638 TimeLineID timeline ;
@@ -55,6 +57,13 @@ typedef struct XLogDumpConfig
5557 bool filter_by_rmgr_enabled ;
5658 TransactionId filter_by_xid ;
5759 bool filter_by_xid_enabled ;
60+ RelFileNode filter_by_relation ;
61+ bool filter_by_extended ;
62+ bool filter_by_relation_enabled ;
63+ BlockNumber filter_by_relation_block ;
64+ bool filter_by_relation_block_enabled ;
65+ ForkNumber filter_by_relation_forknum ;
66+ bool filter_by_fpw ;
5867} XLogDumpConfig ;
5968
6069typedef struct Stats
@@ -391,6 +400,59 @@ WALDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
391400 return count ;
392401}
393402
403+ /*
404+ * Boolean to return whether the given WAL record matches a specific relation
405+ * and optionally block.
406+ */
407+ static bool
408+ XLogRecordMatchesRelationBlock (XLogReaderState * record ,
409+ RelFileNode matchRnode ,
410+ BlockNumber matchBlock ,
411+ ForkNumber matchFork )
412+ {
413+ int block_id ;
414+
415+ for (block_id = 0 ; block_id <= XLogRecMaxBlockId (record ); block_id ++ )
416+ {
417+ RelFileNode rnode ;
418+ ForkNumber forknum ;
419+ BlockNumber blk ;
420+
421+ if (!XLogRecHasBlockRef (record , block_id ))
422+ continue ;
423+
424+ XLogRecGetBlockTag (record , block_id , & rnode , & forknum , & blk );
425+
426+ if ((matchFork == InvalidForkNumber || matchFork == forknum ) &&
427+ (RelFileNodeEquals (matchRnode , emptyRelFileNode ) ||
428+ RelFileNodeEquals (matchRnode , rnode )) &&
429+ (matchBlock == InvalidBlockNumber || matchBlock == blk ))
430+ return true;
431+ }
432+
433+ return false;
434+ }
435+
436+ /*
437+ * Boolean to return whether the given WAL record contains a full page write.
438+ */
439+ static bool
440+ XLogRecordHasFPW (XLogReaderState * record )
441+ {
442+ int block_id ;
443+
444+ for (block_id = 0 ; block_id <= XLogRecMaxBlockId (record ); block_id ++ )
445+ {
446+ if (!XLogRecHasBlockRef (record , block_id ))
447+ continue ;
448+
449+ if (XLogRecHasBlockImage (record , block_id ))
450+ return true;
451+ }
452+
453+ return false;
454+ }
455+
394456/*
395457 * Calculate the size of a record, split into !FPI and FPI parts.
396458 */
@@ -765,6 +827,10 @@ usage(void)
765827 printf (_ (" -b, --bkp-details output detailed information about backup blocks\n" ));
766828 printf (_ (" -e, --end=RECPTR stop reading at WAL location RECPTR\n" ));
767829 printf (_ (" -f, --follow keep retrying after reaching end of WAL\n" ));
830+ printf (_ (" -k, --block=N with --relation, only show records matching this block\n" ));
831+ printf (_ (" -F, --fork=N only show records matching a specific fork number\n"
832+ " (defaults to showing all)\n" ));
833+ printf (_ (" -l, --relation=N/N/N only show records that affect a specific relation\n" ));
768834 printf (_ (" -n, --limit=N number of records to display\n" ));
769835 printf (_ (" -p, --path=PATH directory in which to find log segment files or a\n"
770836 " directory with a ./pg_wal that contains such files\n"
@@ -777,6 +843,7 @@ usage(void)
777843 " (default: 1 or the value used in STARTSEG)\n" ));
778844 printf (_ (" -V, --version output version information, then exit\n" ));
779845 printf (_ (" -x, --xid=XID only show records with transaction ID XID\n" ));
846+ printf (_ (" -w, --fullpage only show records with a full page write\n" ));
780847 printf (_ (" -z, --stats[=record] show statistics instead of records\n"
781848 " (optionally, show per-record statistics)\n" ));
782849 printf (_ (" -?, --help show this help, then exit\n" ));
@@ -800,12 +867,16 @@ main(int argc, char **argv)
800867
801868 static struct option long_options [] = {
802869 {"bkp-details" , no_argument , NULL , 'b' },
870+ {"block" , required_argument , NULL , 'k' },
803871 {"end" , required_argument , NULL , 'e' },
804872 {"follow" , no_argument , NULL , 'f' },
873+ {"fork" , required_argument , NULL , 'F' },
874+ {"fullpage" , no_argument , NULL , 'w' },
805875 {"help" , no_argument , NULL , '?' },
806876 {"limit" , required_argument , NULL , 'n' },
807877 {"path" , required_argument , NULL , 'p' },
808878 {"quiet" , no_argument , NULL , 'q' },
879+ {"relation" , required_argument , NULL , 'l' },
809880 {"rmgr" , required_argument , NULL , 'r' },
810881 {"start" , required_argument , NULL , 's' },
811882 {"timeline" , required_argument , NULL , 't' },
@@ -858,6 +929,11 @@ main(int argc, char **argv)
858929 config .filter_by_rmgr_enabled = false;
859930 config .filter_by_xid = InvalidTransactionId ;
860931 config .filter_by_xid_enabled = false;
932+ config .filter_by_extended = false;
933+ config .filter_by_relation_enabled = false;
934+ config .filter_by_relation_block_enabled = false;
935+ config .filter_by_relation_forknum = InvalidForkNumber ;
936+ config .filter_by_fpw = false;
861937 config .stats = false;
862938 config .stats_per_record = false;
863939
@@ -870,7 +946,7 @@ main(int argc, char **argv)
870946 goto bad_argument ;
871947 }
872948
873- while ((option = getopt_long (argc , argv , "be:fn: p:qr:s:t:x :z" ,
949+ while ((option = getopt_long (argc , argv , "be:fF:k:l:n: p:qr:s:t:wx :z" ,
874950 long_options , & optindex )) != -1 )
875951 {
876952 switch (option )
@@ -890,6 +966,47 @@ main(int argc, char **argv)
890966 case 'f' :
891967 config .follow = true;
892968 break ;
969+ case 'F' :
970+ {
971+ unsigned int forknum ;
972+
973+ if (sscanf (optarg , "%u" , & forknum ) != 1 ||
974+ forknum > MAX_FORKNUM )
975+ {
976+ pg_log_error ("could not parse valid fork number (0..%d) \"%s\"" ,
977+ MAX_FORKNUM , optarg );
978+ goto bad_argument ;
979+ }
980+ config .filter_by_relation_forknum = (ForkNumber ) forknum ;
981+ config .filter_by_extended = true;
982+ }
983+ break ;
984+ case 'k' :
985+ if (sscanf (optarg , "%u" , & config .filter_by_relation_block ) != 1 ||
986+ !BlockNumberIsValid (config .filter_by_relation_block ))
987+ {
988+ pg_log_error ("could not parse valid block number \"%s\"" , optarg );
989+ goto bad_argument ;
990+ }
991+ config .filter_by_relation_block_enabled = true;
992+ config .filter_by_extended = true;
993+ break ;
994+ case 'l' :
995+ if (sscanf (optarg , "%u/%u/%u" ,
996+ & config .filter_by_relation .spcNode ,
997+ & config .filter_by_relation .dbNode ,
998+ & config .filter_by_relation .relNode ) != 3 ||
999+ !OidIsValid (config .filter_by_relation .spcNode ) ||
1000+ !OidIsValid (config .filter_by_relation .relNode ))
1001+ {
1002+ pg_log_error ("could not parse valid relation from \"%s\""
1003+ " (expecting \"tablespace OID/database OID/"
1004+ "relation filenode\")" , optarg );
1005+ goto bad_argument ;
1006+ }
1007+ config .filter_by_relation_enabled = true;
1008+ config .filter_by_extended = true;
1009+ break ;
8931010 case 'n' :
8941011 if (sscanf (optarg , "%d" , & config .stop_after_records ) != 1 )
8951012 {
@@ -947,6 +1064,9 @@ main(int argc, char **argv)
9471064 goto bad_argument ;
9481065 }
9491066 break ;
1067+ case 'w' :
1068+ config .filter_by_fpw = true;
1069+ break ;
9501070 case 'x' :
9511071 if (sscanf (optarg , "%u" , & config .filter_by_xid ) != 1 )
9521072 {
@@ -976,6 +1096,13 @@ main(int argc, char **argv)
9761096 }
9771097 }
9781098
1099+ if (config .filter_by_relation_block_enabled &&
1100+ !config .filter_by_relation_enabled )
1101+ {
1102+ pg_log_error ("--block option requires --relation option to be specified" );
1103+ goto bad_argument ;
1104+ }
1105+
9791106 if ((optind + 2 ) < argc )
9801107 {
9811108 pg_log_error ("too many command-line arguments (first is \"%s\")" ,
@@ -1148,6 +1275,21 @@ main(int argc, char **argv)
11481275 config .filter_by_xid != record -> xl_xid )
11491276 continue ;
11501277
1278+ /* check for extended filtering */
1279+ if (config .filter_by_extended &&
1280+ !XLogRecordMatchesRelationBlock (xlogreader_state ,
1281+ config .filter_by_relation_enabled ?
1282+ config .filter_by_relation :
1283+ emptyRelFileNode ,
1284+ config .filter_by_relation_block_enabled ?
1285+ config .filter_by_relation_block :
1286+ InvalidBlockNumber ,
1287+ config .filter_by_relation_forknum ))
1288+ continue ;
1289+
1290+ if (config .filter_by_fpw && !XLogRecordHasFPW (xlogreader_state ))
1291+ continue ;
1292+
11511293 /* perform any per-record work */
11521294 if (!config .quiet )
11531295 {
0 commit comments