@@ -94,9 +94,10 @@ public void testSkipsRedundantDeleteMarkers() throws IOException {
9494 assertRetainDeletes (new Type [] { Type .DeleteFamily , Type .DeleteFamily , Type .DeleteFamily },
9595 INCLUDE , SEEK_NEXT_COL , SEEK_NEXT_COL );
9696
97- // DF + DFV interleaved. DF included, DFV at same ts redundant, older DF redundant.
97+ // DF + DFV interleaved. DF included, DFV redundant (SKIP because empty qualifier),
98+ // older DF redundant (SEEK_NEXT_COL), older DFV redundant (SKIP).
9899 assertRetainDeletes (new Type [] { Type .DeleteFamily , Type .DeleteFamilyVersion , Type .DeleteFamily ,
99- Type .DeleteFamilyVersion }, INCLUDE , SEEK_NEXT_COL , SEEK_NEXT_COL , SEEK_NEXT_COL );
100+ Type .DeleteFamilyVersion }, INCLUDE , SKIP , SEEK_NEXT_COL , SKIP );
100101
101102 // Delete (version) covered by DeleteColumn.
102103 assertRetainDeletes (new Type [] { Type .DeleteColumn , Type .Delete , Type .Delete , Type .Delete },
@@ -108,17 +109,51 @@ public void testSkipsRedundantDeleteMarkers() throws IOException {
108109 INCLUDE );
109110 }
110111
112+ /**
113+ * Redundant column-level deletes with empty qualifier must not seek past a subsequent
114+ * DeleteFamily. getKeyForNextColumn treats empty qualifier as "no column" and returns
115+ * SEEK_NEXT_ROW, which would skip the DF and all remaining cells in the row.
116+ */
117+ @ Test
118+ public void testEmptyQualifierDeleteDoesNotSkipDeleteFamily () throws IOException {
119+ byte [] emptyQualifier = HConstants .EMPTY_BYTE_ARRAY ;
120+
121+ // DC(empty) + DC(empty) redundant + DF must still be reachable.
122+ assertRetainDeletes (emptyQualifier ,
123+ new Type [] { Type .DeleteColumn , Type .DeleteColumn , Type .DeleteFamily }, INCLUDE , SKIP ,
124+ INCLUDE );
125+
126+ // DC(empty) + Delete(empty) redundant + DF must still be reachable.
127+ assertRetainDeletes (emptyQualifier ,
128+ new Type [] { Type .DeleteColumn , Type .Delete , Type .DeleteFamily }, INCLUDE , SKIP , INCLUDE );
129+ }
130+
111131 private void assertRetainDeletes (Type [] types , MatchCode ... expected ) throws IOException {
112132 assertRetainDeletes (KeepDeletedCells .FALSE , types , expected );
113133 }
114134
135+ private void assertRetainDeletes (byte [] qualifier , Type [] types , MatchCode ... expected )
136+ throws IOException {
137+ assertRetainDeletes (KeepDeletedCells .FALSE , qualifier , types , expected );
138+ }
139+
115140 /**
116141 * Build cells from the given types with decrementing timestamps (same ts for adjacent
117142 * family-level and column-level types at the same position). Family-level types (DeleteFamily,
118143 * DeleteFamilyVersion) use empty qualifier; others use col1.
119144 */
120145 private void assertRetainDeletes (KeepDeletedCells keepDeletedCells , Type [] types ,
121146 MatchCode ... expected ) throws IOException {
147+ assertRetainDeletes (keepDeletedCells , null , types , expected );
148+ }
149+
150+ /**
151+ * Build cells from the given types with decrementing timestamps. If qualifier is null,
152+ * family-level types use empty qualifier and others use col1. If qualifier is specified, all
153+ * types use that qualifier.
154+ */
155+ private void assertRetainDeletes (KeepDeletedCells keepDeletedCells , byte [] qualifier ,
156+ Type [] types , MatchCode ... expected ) throws IOException {
122157 long now = EnvironmentEdgeManager .currentTime ();
123158 ScanInfo scanInfo = new ScanInfo (this .conf , fam1 , 0 , 1 , ttl , keepDeletedCells ,
124159 HConstants .DEFAULT_BLOCKSIZE , 0 , rowComparator , false );
@@ -130,8 +165,13 @@ private void assertRetainDeletes(KeepDeletedCells keepDeletedCells, Type[] types
130165 long ts = now ;
131166 List <MatchCode > actual = new ArrayList <>(expected .length );
132167 for (int i = 0 ; i < types .length ; i ++) {
133- boolean familyLevel = types [i ] == Type .DeleteFamily || types [i ] == Type .DeleteFamilyVersion ;
134- byte [] qual = familyLevel ? HConstants .EMPTY_BYTE_ARRAY : col1 ;
168+ byte [] qual ;
169+ if (qualifier != null ) {
170+ qual = qualifier ;
171+ } else {
172+ boolean familyLevel = types [i ] == Type .DeleteFamily || types [i ] == Type .DeleteFamilyVersion ;
173+ qual = familyLevel ? HConstants .EMPTY_BYTE_ARRAY : col1 ;
174+ }
135175 KeyValue kv = types [i ] == Type .Put
136176 ? new KeyValue (row1 , fam1 , qual , ts , types [i ], data )
137177 : new KeyValue (row1 , fam1 , qual , ts , types [i ]);
0 commit comments