@@ -46,6 +46,7 @@ typedef enum XidBoundsViolation
46
46
typedef enum XidCommitStatus
47
47
{
48
48
XID_COMMITTED ,
49
+ XID_IS_CURRENT_XID ,
49
50
XID_IN_PROGRESS ,
50
51
XID_ABORTED
51
52
} XidCommitStatus ;
@@ -72,6 +73,8 @@ typedef struct HeapCheckContext
72
73
TransactionId oldest_xid ; /* ShmemVariableCache->oldestXid */
73
74
FullTransactionId oldest_fxid ; /* 64-bit version of oldest_xid, computed
74
75
* relative to next_fxid */
76
+ TransactionId safe_xmin ; /* this XID and newer ones can't become
77
+ * all-visible while we're running */
75
78
76
79
/*
77
80
* Cached copy of value from MultiXactState
@@ -113,6 +116,9 @@ typedef struct HeapCheckContext
113
116
uint32 offset ; /* offset in tuple data */
114
117
AttrNumber attnum ;
115
118
119
+ /* True if tuple's xmax makes it eligible for pruning */
120
+ bool tuple_could_be_pruned ;
121
+
116
122
/* Values for iterating over toast for the attribute */
117
123
int32 chunkno ;
118
124
int32 attrsize ;
@@ -133,8 +139,8 @@ static void check_tuple(HeapCheckContext *ctx);
133
139
static void check_toast_tuple (HeapTuple toasttup , HeapCheckContext * ctx );
134
140
135
141
static bool check_tuple_attribute (HeapCheckContext * ctx );
136
- static bool check_tuple_header_and_visibilty ( HeapTupleHeader tuphdr ,
137
- HeapCheckContext * ctx );
142
+ static bool check_tuple_header ( HeapCheckContext * ctx );
143
+ static bool check_tuple_visibility ( HeapCheckContext * ctx );
138
144
139
145
static void report_corruption (HeapCheckContext * ctx , char * msg );
140
146
static TupleDesc verify_heapam_tupdesc (void );
@@ -248,6 +254,12 @@ verify_heapam(PG_FUNCTION_ARGS)
248
254
memset (& ctx , 0 , sizeof (HeapCheckContext ));
249
255
ctx .cached_xid = InvalidTransactionId ;
250
256
257
+ /*
258
+ * Any xmin newer than the xmin of our snapshot can't become all-visible
259
+ * while we're running.
260
+ */
261
+ ctx .safe_xmin = GetTransactionSnapshot ()-> xmin ;
262
+
251
263
/*
252
264
* If we report corruption when not examining some individual attribute,
253
265
* we need attnum to be reported as NULL. Set that up before any
@@ -555,16 +567,11 @@ verify_heapam_tupdesc(void)
555
567
}
556
568
557
569
/*
558
- * Check for tuple header corruption and tuple visibility.
559
- *
560
- * Since we do not hold a snapshot, tuple visibility is not a question of
561
- * whether we should be able to see the tuple relative to any particular
562
- * snapshot, but rather a question of whether it is safe and reasonable to
563
- * check the tuple attributes.
570
+ * Check for tuple header corruption.
564
571
*
565
572
* Some kinds of corruption make it unsafe to check the tuple attributes, for
566
573
* example when the line pointer refers to a range of bytes outside the page.
567
- * In such cases, we return false (not visible ) after recording appropriate
574
+ * In such cases, we return false (not checkable ) after recording appropriate
568
575
* corruption messages.
569
576
*
570
577
* Some other kinds of tuple header corruption confuse the question of where
@@ -576,37 +583,26 @@ verify_heapam_tupdesc(void)
576
583
*
577
584
* Other kinds of tuple header corruption do not bear on the question of
578
585
* whether the tuple attributes can be checked, so we record corruption
579
- * messages for them but do not base our visibility determination on them. (In
580
- * other words, we do not return false merely because we detected them.)
581
- *
582
- * For visibility determination not specifically related to corruption, what we
583
- * want to know is if a tuple is potentially visible to any running
584
- * transaction. If you are tempted to replace this function's visibility logic
585
- * with a call to another visibility checking function, keep in mind that this
586
- * function does not update hint bits, as it seems imprudent to write hint bits
587
- * (or anything at all) to a table during a corruption check. Nor does this
588
- * function bother classifying tuple visibility beyond a boolean visible vs.
589
- * not visible.
590
- *
591
- * The caller should already have checked that xmin and xmax are not out of
592
- * bounds for the relation.
586
+ * messages for them but we do not return false merely because we detected
587
+ * them.
593
588
*
594
- * Returns whether the tuple is both visible and sufficiently sensible to
595
- * undergo attribute checks.
589
+ * Returns whether the tuple is sufficiently sensible to undergo visibility and
590
+ * attribute checks.
596
591
*/
597
592
static bool
598
- check_tuple_header_and_visibilty ( HeapTupleHeader tuphdr , HeapCheckContext * ctx )
593
+ check_tuple_header ( HeapCheckContext * ctx )
599
594
{
595
+ HeapTupleHeader tuphdr = ctx -> tuphdr ;
600
596
uint16 infomask = tuphdr -> t_infomask ;
601
- bool header_garbled = false ;
597
+ bool result = true ;
602
598
unsigned expected_hoff ;
603
599
604
600
if (ctx -> tuphdr -> t_hoff > ctx -> lp_len )
605
601
{
606
602
report_corruption (ctx ,
607
603
psprintf ("data begins at offset %u beyond the tuple length %u" ,
608
604
ctx -> tuphdr -> t_hoff , ctx -> lp_len ));
609
- header_garbled = true ;
605
+ result = false ;
610
606
}
611
607
612
608
if ((ctx -> tuphdr -> t_infomask & HEAP_XMAX_COMMITTED ) &&
@@ -616,9 +612,9 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
616
612
pstrdup ("multixact should not be marked committed" ));
617
613
618
614
/*
619
- * This condition is clearly wrong, but we do not consider the header
620
- * garbled , because we don't rely on this property for determining if
621
- * the tuple is visible or for interpreting other relevant header
615
+ * This condition is clearly wrong, but it's not enough to justify
616
+ * skipping further checks , because we don't rely on this to determine
617
+ * whether the tuple is visible or to interpret other relevant header
622
618
* fields.
623
619
*/
624
620
}
@@ -645,175 +641,449 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
645
641
report_corruption (ctx ,
646
642
psprintf ("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)" ,
647
643
expected_hoff , ctx -> tuphdr -> t_hoff , ctx -> natts ));
648
- header_garbled = true ;
644
+ result = false ;
649
645
}
650
646
651
- if (header_garbled )
652
- return false; /* checking of this tuple should not continue */
647
+ return result ;
648
+ }
649
+
650
+ /*
651
+ * Checks tuple visibility so we know which further checks are safe to
652
+ * perform.
653
+ *
654
+ * If a tuple could have been inserted by a transaction that also added a
655
+ * column to the table, but which ultimately did not commit, or which has not
656
+ * yet committed, then the table's current TupleDesc might differ from the one
657
+ * used to construct this tuple, so we must not check it.
658
+ *
659
+ * As a special case, if our own transaction inserted the tuple, even if we
660
+ * added a column to the table, our TupleDesc should match. We could check the
661
+ * tuple, but choose not to do so.
662
+ *
663
+ * If a tuple has been updated or deleted, we can still read the old tuple for
664
+ * corruption checking purposes, as long as we are careful about concurrent
665
+ * vacuums. The main table tuple itself cannot be vacuumed away because we
666
+ * hold a buffer lock on the page, but if the deleting transaction is older
667
+ * than our transaction snapshot's xmin, then vacuum could remove the toast at
668
+ * any time, so we must not try to follow TOAST pointers.
669
+ *
670
+ * If xmin or xmax values are older than can be checked against clog, or appear
671
+ * to be in the future (possibly due to wrap-around), then we cannot make a
672
+ * determination about the visibility of the tuple, so we skip further checks.
673
+ *
674
+ * Returns true if the tuple itself should be checked, false otherwise. Sets
675
+ * ctx->tuple_could_be_pruned if the tuple -- and thus also any associated
676
+ * TOAST tuples -- are eligible for pruning.
677
+ */
678
+ static bool
679
+ check_tuple_visibility (HeapCheckContext * ctx )
680
+ {
681
+ TransactionId xmin ;
682
+ TransactionId xvac ;
683
+ TransactionId xmax ;
684
+ XidCommitStatus xmin_status ;
685
+ XidCommitStatus xvac_status ;
686
+ XidCommitStatus xmax_status ;
687
+ HeapTupleHeader tuphdr = ctx -> tuphdr ;
688
+
689
+ ctx -> tuple_could_be_pruned = true; /* have not yet proven otherwise */
690
+
691
+ /* If xmin is normal, it should be within valid range */
692
+ xmin = HeapTupleHeaderGetXmin (tuphdr );
693
+ switch (get_xid_status (xmin , ctx , & xmin_status ))
694
+ {
695
+ case XID_INVALID :
696
+ case XID_BOUNDS_OK :
697
+ break ;
698
+ case XID_IN_FUTURE :
699
+ report_corruption (ctx ,
700
+ psprintf ("xmin %u equals or exceeds next valid transaction ID %u:%u" ,
701
+ xmin ,
702
+ EpochFromFullTransactionId (ctx -> next_fxid ),
703
+ XidFromFullTransactionId (ctx -> next_fxid )));
704
+ return false;
705
+ case XID_PRECEDES_CLUSTERMIN :
706
+ report_corruption (ctx ,
707
+ psprintf ("xmin %u precedes oldest valid transaction ID %u:%u" ,
708
+ xmin ,
709
+ EpochFromFullTransactionId (ctx -> oldest_fxid ),
710
+ XidFromFullTransactionId (ctx -> oldest_fxid )));
711
+ return false;
712
+ case XID_PRECEDES_RELMIN :
713
+ report_corruption (ctx ,
714
+ psprintf ("xmin %u precedes relation freeze threshold %u:%u" ,
715
+ xmin ,
716
+ EpochFromFullTransactionId (ctx -> relfrozenfxid ),
717
+ XidFromFullTransactionId (ctx -> relfrozenfxid )));
718
+ return false;
719
+ }
653
720
654
721
/*
655
- * Ok, we can examine the header for tuple visibility purposes, though we
656
- * still need to be careful about a few remaining types of header
657
- * corruption. This logic roughly follows that of
658
- * HeapTupleSatisfiesVacuum. Where possible the comments indicate which
659
- * HTSV_Result we think that function might return for this tuple.
722
+ * Has inserting transaction committed?
660
723
*/
661
724
if (!HeapTupleHeaderXminCommitted (tuphdr ))
662
725
{
663
- TransactionId raw_xmin = HeapTupleHeaderGetRawXmin (tuphdr );
664
-
665
726
if (HeapTupleHeaderXminInvalid (tuphdr ))
666
- return false; /* HEAPTUPLE_DEAD */
727
+ return false; /* inserter aborted, don't check */
667
728
/* Used by pre-9.0 binary upgrades */
668
- else if (infomask & HEAP_MOVED_OFF ||
669
- infomask & HEAP_MOVED_IN )
729
+ else if (tuphdr -> t_infomask & HEAP_MOVED_OFF )
670
730
{
671
- XidCommitStatus status ;
672
- TransactionId xvac = HeapTupleHeaderGetXvac (tuphdr );
731
+ xvac = HeapTupleHeaderGetXvac (tuphdr );
673
732
674
- switch (get_xid_status (xvac , ctx , & status ))
733
+ switch (get_xid_status (xvac , ctx , & xvac_status ))
675
734
{
676
735
case XID_INVALID :
677
736
report_corruption (ctx ,
678
- pstrdup ("old-style VACUUM FULL transaction ID is invalid" ));
679
- return false; /* corrupt */
737
+ pstrdup ("old-style VACUUM FULL transaction ID for moved off tuple is invalid" ));
738
+ return false;
680
739
case XID_IN_FUTURE :
681
740
report_corruption (ctx ,
682
- psprintf ("old-style VACUUM FULL transaction ID %u equals or exceeds next valid transaction ID %u:%u" ,
741
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u" ,
683
742
xvac ,
684
743
EpochFromFullTransactionId (ctx -> next_fxid ),
685
744
XidFromFullTransactionId (ctx -> next_fxid )));
686
- return false; /* corrupt */
745
+ return false;
687
746
case XID_PRECEDES_RELMIN :
688
747
report_corruption (ctx ,
689
- psprintf ("old-style VACUUM FULL transaction ID %u precedes relation freeze threshold %u:%u" ,
748
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u" ,
690
749
xvac ,
691
750
EpochFromFullTransactionId (ctx -> relfrozenfxid ),
692
751
XidFromFullTransactionId (ctx -> relfrozenfxid )));
693
- return false; /* corrupt */
694
- break ;
752
+ return false;
695
753
case XID_PRECEDES_CLUSTERMIN :
696
754
report_corruption (ctx ,
697
- psprintf ("old-style VACUUM FULL transaction ID %u precedes oldest valid transaction ID %u:%u" ,
755
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u" ,
698
756
xvac ,
699
757
EpochFromFullTransactionId (ctx -> oldest_fxid ),
700
758
XidFromFullTransactionId (ctx -> oldest_fxid )));
701
- return false; /* corrupt */
702
- break ;
759
+ return false;
703
760
case XID_BOUNDS_OK :
704
- switch (status )
705
- {
706
- case XID_IN_PROGRESS :
707
- return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
708
- case XID_COMMITTED :
709
- case XID_ABORTED :
710
- return false; /* HEAPTUPLE_DEAD */
711
- }
761
+ break ;
762
+ }
763
+
764
+ switch (xvac_status )
765
+ {
766
+ case XID_IS_CURRENT_XID :
767
+ report_corruption (ctx ,
768
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID" ,
769
+ xvac ));
770
+ return false;
771
+ case XID_IN_PROGRESS :
772
+ report_corruption (ctx ,
773
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress" ,
774
+ xvac ));
775
+ return false;
776
+
777
+ case XID_COMMITTED :
778
+ /*
779
+ * The tuple is dead, because the xvac transaction moved
780
+ * it off and comitted. It's checkable, but also prunable.
781
+ */
782
+ return true;
783
+
784
+ case XID_ABORTED :
785
+ /*
786
+ * The original xmin must have committed, because the xvac
787
+ * transaction tried to move it later. Since xvac is
788
+ * aborted, whether it's still alive now depends on the
789
+ * status of xmax.
790
+ */
791
+ break ;
712
792
}
713
793
}
714
- else
794
+ /* Used by pre-9.0 binary upgrades */
795
+ else if (tuphdr -> t_infomask & HEAP_MOVED_IN )
715
796
{
716
- XidCommitStatus status ;
797
+ xvac = HeapTupleHeaderGetXvac ( tuphdr ) ;
717
798
718
- switch (get_xid_status (raw_xmin , ctx , & status ))
799
+ switch (get_xid_status (xvac , ctx , & xvac_status ))
719
800
{
720
801
case XID_INVALID :
721
802
report_corruption (ctx ,
722
- pstrdup ("raw xmin is invalid" ));
803
+ pstrdup ("old-style VACUUM FULL transaction ID for moved in tuple is invalid" ));
723
804
return false;
724
805
case XID_IN_FUTURE :
725
806
report_corruption (ctx ,
726
- psprintf ("raw xmin %u equals or exceeds next valid transaction ID %u:%u" ,
727
- raw_xmin ,
807
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u" ,
808
+ xvac ,
728
809
EpochFromFullTransactionId (ctx -> next_fxid ),
729
810
XidFromFullTransactionId (ctx -> next_fxid )));
730
- return false; /* corrupt */
811
+ return false;
731
812
case XID_PRECEDES_RELMIN :
732
813
report_corruption (ctx ,
733
- psprintf ("raw xmin %u precedes relation freeze threshold %u:%u" ,
734
- raw_xmin ,
814
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u" ,
815
+ xvac ,
735
816
EpochFromFullTransactionId (ctx -> relfrozenfxid ),
736
817
XidFromFullTransactionId (ctx -> relfrozenfxid )));
737
- return false; /* corrupt */
818
+ return false;
738
819
case XID_PRECEDES_CLUSTERMIN :
739
820
report_corruption (ctx ,
740
- psprintf ("raw xmin %u precedes oldest valid transaction ID %u:%u" ,
741
- raw_xmin ,
821
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u" ,
822
+ xvac ,
742
823
EpochFromFullTransactionId (ctx -> oldest_fxid ),
743
824
XidFromFullTransactionId (ctx -> oldest_fxid )));
744
- return false; /* corrupt */
825
+ return false;
745
826
case XID_BOUNDS_OK :
746
- switch (status )
747
- {
748
- case XID_COMMITTED :
749
- break ;
750
- case XID_IN_PROGRESS :
751
- return true; /* insert or delete in progress */
752
- case XID_ABORTED :
753
- return false; /* HEAPTUPLE_DEAD */
754
- }
827
+ break ;
755
828
}
829
+
830
+ switch (xvac_status )
831
+ {
832
+ case XID_IS_CURRENT_XID :
833
+ report_corruption (ctx ,
834
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID" ,
835
+ xvac ));
836
+ return false;
837
+ case XID_IN_PROGRESS :
838
+ report_corruption (ctx ,
839
+ psprintf ("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress" ,
840
+ xvac ));
841
+ return false;
842
+
843
+ case XID_COMMITTED :
844
+ /*
845
+ * The original xmin must have committed, because the xvac
846
+ * transaction moved it later. Whether it's still alive
847
+ * now depends on the status of xmax.
848
+ */
849
+ break ;
850
+
851
+ case XID_ABORTED :
852
+ /*
853
+ * The tuple is dead, because the xvac transaction moved
854
+ * it off and comitted. It's checkable, but also prunable.
855
+ */
856
+ return true;
857
+ }
858
+ }
859
+ else if (xmin_status != XID_COMMITTED )
860
+ {
861
+ /*
862
+ * Inserting transaction is not in progress, and not committed, so
863
+ * it might have changed the TupleDesc in ways we don't know about.
864
+ * Thus, don't try to check the tuple structure.
865
+ *
866
+ * If xmin_status happens to be XID_IS_CURRENT_XID, then in theory
867
+ * any such DDL changes ought to be visible to us, so perhaps
868
+ * we could check anyway in that case. But, for now, let's be
869
+ * conservate and treat this like any other uncommitted insert.
870
+ */
871
+ return false;
756
872
}
757
873
}
758
874
759
- if (!(infomask & HEAP_XMAX_INVALID ) && !HEAP_XMAX_IS_LOCKED_ONLY (infomask ))
875
+ /*
876
+ * Okay, the inserter committed, so it was good at some point. Now what
877
+ * about the deleting transaction?
878
+ */
879
+
880
+ if (tuphdr -> t_infomask & HEAP_XMAX_IS_MULTI )
760
881
{
761
- if (infomask & HEAP_XMAX_IS_MULTI )
882
+ /*
883
+ * xmax is a multixact, so sanity-check the MXID. Note that we do this
884
+ * prior to checking for HEAP_XMAX_INVALID or HEAP_XMAX_IS_LOCKED_ONLY.
885
+ * This might therefore complain about things that wouldn't actually
886
+ * be a problem during a normal scan, but eventually we're going to
887
+ * have to freeze, and that process will ignore hint bits.
888
+ *
889
+ * Even if the MXID is out of range, we still know that the original
890
+ * insert committed, so we can check the tuple itself. However, we
891
+ * can't rule out the possibility that this tuple is dead, so don't
892
+ * clear ctx->tuple_could_be_pruned. Possibly we should go ahead and
893
+ * clear that flag anyway if HEAP_XMAX_INVALID is set or if
894
+ * HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side
895
+ * of avoiding possibly-bogus complaints about missing TOAST entries.
896
+ */
897
+ xmax = HeapTupleHeaderGetRawXmax (tuphdr );
898
+ switch (check_mxid_valid_in_rel (xmax , ctx ))
762
899
{
763
- XidCommitStatus status ;
764
- TransactionId xmax = HeapTupleGetUpdateXid (tuphdr );
900
+ case XID_INVALID :
901
+ report_corruption (ctx ,
902
+ pstrdup ("multitransaction ID is invalid" ));
903
+ return true;
904
+ case XID_PRECEDES_RELMIN :
905
+ report_corruption (ctx ,
906
+ psprintf ("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u" ,
907
+ xmax , ctx -> relminmxid ));
908
+ return true;
909
+ case XID_PRECEDES_CLUSTERMIN :
910
+ report_corruption (ctx ,
911
+ psprintf ("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u" ,
912
+ xmax , ctx -> oldest_mxact ));
913
+ return true;
914
+ case XID_IN_FUTURE :
915
+ report_corruption (ctx ,
916
+ psprintf ("multitransaction ID %u equals or exceeds next valid multitransaction ID %u" ,
917
+ xmax ,
918
+ ctx -> next_mxact ));
919
+ return true;
920
+ case XID_BOUNDS_OK :
921
+ break ;
922
+ }
923
+ }
765
924
766
- switch (get_xid_status (xmax , ctx , & status ))
767
- {
768
- /* not LOCKED_ONLY, so it has to have an xmax */
769
- case XID_INVALID :
770
- report_corruption (ctx ,
771
- pstrdup ("xmax is invalid" ));
772
- return false; /* corrupt */
773
- case XID_IN_FUTURE :
774
- report_corruption (ctx ,
775
- psprintf ("xmax %u equals or exceeds next valid transaction ID %u:%u" ,
776
- xmax ,
777
- EpochFromFullTransactionId (ctx -> next_fxid ),
778
- XidFromFullTransactionId (ctx -> next_fxid )));
779
- return false; /* corrupt */
780
- case XID_PRECEDES_RELMIN :
781
- report_corruption (ctx ,
782
- psprintf ("xmax %u precedes relation freeze threshold %u:%u" ,
783
- xmax ,
784
- EpochFromFullTransactionId (ctx -> relfrozenfxid ),
785
- XidFromFullTransactionId (ctx -> relfrozenfxid )));
786
- return false; /* corrupt */
787
- case XID_PRECEDES_CLUSTERMIN :
788
- report_corruption (ctx ,
789
- psprintf ("xmax %u precedes oldest valid transaction ID %u:%u" ,
790
- xmax ,
791
- EpochFromFullTransactionId (ctx -> oldest_fxid ),
792
- XidFromFullTransactionId (ctx -> oldest_fxid )));
793
- return false; /* corrupt */
794
- case XID_BOUNDS_OK :
795
- switch (status )
796
- {
797
- case XID_IN_PROGRESS :
798
- return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
799
- case XID_COMMITTED :
800
- case XID_ABORTED :
801
- return false; /* HEAPTUPLE_RECENTLY_DEAD or
802
- * HEAPTUPLE_DEAD */
803
- }
804
- }
925
+ if (tuphdr -> t_infomask & HEAP_XMAX_INVALID )
926
+ {
927
+ /*
928
+ * This tuple is live. A concurrently running transaction could
929
+ * delete it before we get around to checking the toast, but any such
930
+ * running transaction is surely not less than our safe_xmin, so the
931
+ * toast cannot be vacuumed out from under us.
932
+ */
933
+ ctx -> tuple_could_be_pruned = false;
934
+ return true;
935
+ }
805
936
806
- /* Ok, the tuple is live */
937
+ if (HEAP_XMAX_IS_LOCKED_ONLY (tuphdr -> t_infomask ))
938
+ {
939
+ /*
940
+ * "Deleting" xact really only locked it, so the tuple is live in any
941
+ * case. As above, a concurrently running transaction could delete
942
+ * it, but it cannot be vacuumed out from under us.
943
+ */
944
+ ctx -> tuple_could_be_pruned = false;
945
+ return true;
946
+ }
947
+
948
+ if (tuphdr -> t_infomask & HEAP_XMAX_IS_MULTI )
949
+ {
950
+ /*
951
+ * We already checked above that this multixact is within limits for
952
+ * this table. Now check the update xid from this multixact.
953
+ */
954
+ xmax = HeapTupleGetUpdateXid (tuphdr );
955
+ switch (get_xid_status (xmax , ctx , & xmax_status ))
956
+ {
957
+ case XID_INVALID :
958
+ /* not LOCKED_ONLY, so it has to have an xmax */
959
+ report_corruption (ctx ,
960
+ pstrdup ("update xid is invalid" ));
961
+ return true;
962
+ case XID_IN_FUTURE :
963
+ report_corruption (ctx ,
964
+ psprintf ("update xid %u equals or exceeds next valid transaction ID %u:%u" ,
965
+ xmax ,
966
+ EpochFromFullTransactionId (ctx -> next_fxid ),
967
+ XidFromFullTransactionId (ctx -> next_fxid )));
968
+ return true;
969
+ case XID_PRECEDES_RELMIN :
970
+ report_corruption (ctx ,
971
+ psprintf ("update xid %u precedes relation freeze threshold %u:%u" ,
972
+ xmax ,
973
+ EpochFromFullTransactionId (ctx -> relfrozenfxid ),
974
+ XidFromFullTransactionId (ctx -> relfrozenfxid )));
975
+ return true;
976
+ case XID_PRECEDES_CLUSTERMIN :
977
+ report_corruption (ctx ,
978
+ psprintf ("update xid %u precedes oldest valid transaction ID %u:%u" ,
979
+ xmax ,
980
+ EpochFromFullTransactionId (ctx -> oldest_fxid ),
981
+ XidFromFullTransactionId (ctx -> oldest_fxid )));
982
+ return true;
983
+ case XID_BOUNDS_OK :
984
+ break ;
807
985
}
808
- else if (!(infomask & HEAP_XMAX_COMMITTED ))
809
- return true; /* HEAPTUPLE_DELETE_IN_PROGRESS or
810
- * HEAPTUPLE_LIVE */
811
- else
812
- return false; /* HEAPTUPLE_RECENTLY_DEAD or HEAPTUPLE_DEAD */
986
+
987
+ switch (xmax_status )
988
+ {
989
+ case XID_IS_CURRENT_XID :
990
+ case XID_IN_PROGRESS :
991
+
992
+ /*
993
+ * The delete is in progress, so it cannot be visible to our
994
+ * snapshot.
995
+ */
996
+ ctx -> tuple_could_be_pruned = false;
997
+ break ;
998
+ case XID_COMMITTED :
999
+
1000
+ /*
1001
+ * The delete committed. Whether the toast can be vacuumed
1002
+ * away depends on how old the deleting transaction is.
1003
+ */
1004
+ ctx -> tuple_could_be_pruned = TransactionIdPrecedes (xmax ,
1005
+ ctx -> safe_xmin );
1006
+ break ;
1007
+ case XID_ABORTED :
1008
+ /*
1009
+ * The delete aborted or crashed. The tuple is still live.
1010
+ */
1011
+ ctx -> tuple_could_be_pruned = false;
1012
+ break ;
1013
+ }
1014
+
1015
+ /* Tuple itself is checkable even if it's dead. */
1016
+ return true;
1017
+ }
1018
+
1019
+ /* xmax is an XID, not a MXID. Sanity check it. */
1020
+ xmax = HeapTupleHeaderGetRawXmax (tuphdr );
1021
+ switch (get_xid_status (xmax , ctx , & xmax_status ))
1022
+ {
1023
+ case XID_IN_FUTURE :
1024
+ report_corruption (ctx ,
1025
+ psprintf ("xmax %u equals or exceeds next valid transaction ID %u:%u" ,
1026
+ xmax ,
1027
+ EpochFromFullTransactionId (ctx -> next_fxid ),
1028
+ XidFromFullTransactionId (ctx -> next_fxid )));
1029
+ return false; /* corrupt */
1030
+ case XID_PRECEDES_RELMIN :
1031
+ report_corruption (ctx ,
1032
+ psprintf ("xmax %u precedes relation freeze threshold %u:%u" ,
1033
+ xmax ,
1034
+ EpochFromFullTransactionId (ctx -> relfrozenfxid ),
1035
+ XidFromFullTransactionId (ctx -> relfrozenfxid )));
1036
+ return false; /* corrupt */
1037
+ case XID_PRECEDES_CLUSTERMIN :
1038
+ report_corruption (ctx ,
1039
+ psprintf ("xmax %u precedes oldest valid transaction ID %u:%u" ,
1040
+ xmax ,
1041
+ EpochFromFullTransactionId (ctx -> oldest_fxid ),
1042
+ XidFromFullTransactionId (ctx -> oldest_fxid )));
1043
+ return false; /* corrupt */
1044
+ case XID_BOUNDS_OK :
1045
+ case XID_INVALID :
1046
+ break ;
813
1047
}
814
- return true; /* not dead */
1048
+
1049
+ /*
1050
+ * Whether the toast can be vacuumed away depends on how old the deleting
1051
+ * transaction is.
1052
+ */
1053
+ switch (xmax_status )
1054
+ {
1055
+ case XID_IS_CURRENT_XID :
1056
+ case XID_IN_PROGRESS :
1057
+
1058
+ /*
1059
+ * The delete is in progress, so it cannot be visible to our
1060
+ * snapshot.
1061
+ */
1062
+ ctx -> tuple_could_be_pruned = false;
1063
+ break ;
1064
+
1065
+ case XID_COMMITTED :
1066
+ /*
1067
+ * The delete committed. Whether the toast can be vacuumed away
1068
+ * depends on how old the deleting transaction is.
1069
+ */
1070
+ ctx -> tuple_could_be_pruned = TransactionIdPrecedes (xmax ,
1071
+ ctx -> safe_xmin );
1072
+ break ;
1073
+
1074
+ case XID_ABORTED :
1075
+ /*
1076
+ * The delete aborted or crashed. The tuple is still live.
1077
+ */
1078
+ ctx -> tuple_could_be_pruned = false;
1079
+ break ;
1080
+ }
1081
+
1082
+ /* Tuple itself is checkable even if it's dead. */
1083
+ return true;
815
1084
}
816
1085
1086
+
817
1087
/*
818
1088
* Check the current toast tuple against the state tracked in ctx, recording
819
1089
* any corruption found in ctx->tupstore.
@@ -1247,7 +1517,10 @@ check_tuple(HeapCheckContext *ctx)
1247
1517
* corrupt to continue checking, or if the tuple is not visible to anyone,
1248
1518
* we cannot continue with other checks.
1249
1519
*/
1250
- if (!check_tuple_header_and_visibilty (ctx -> tuphdr , ctx ))
1520
+ if (!check_tuple_header (ctx ))
1521
+ return ;
1522
+
1523
+ if (!check_tuple_visibility (ctx ))
1251
1524
return ;
1252
1525
1253
1526
/*
@@ -1448,13 +1721,13 @@ get_xid_status(TransactionId xid, HeapCheckContext *ctx,
1448
1721
if (FullTransactionIdPrecedesOrEquals (clog_horizon , fxid ))
1449
1722
{
1450
1723
if (TransactionIdIsCurrentTransactionId (xid ))
1724
+ * status = XID_IS_CURRENT_XID ;
1725
+ else if (TransactionIdIsInProgress (xid ))
1451
1726
* status = XID_IN_PROGRESS ;
1452
1727
else if (TransactionIdDidCommit (xid ))
1453
1728
* status = XID_COMMITTED ;
1454
- else if (TransactionIdDidAbort (xid ))
1455
- * status = XID_ABORTED ;
1456
1729
else
1457
- * status = XID_IN_PROGRESS ;
1730
+ * status = XID_ABORTED ;
1458
1731
}
1459
1732
LWLockRelease (XactTruncationLock );
1460
1733
ctx -> cached_xid = xid ;
0 commit comments