1 /**
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20 package org.apache.hadoop.hbase.regionserver;
21
22 import org.apache.hadoop.classification.InterfaceAudience;
23 import org.apache.hadoop.hbase.KeyValue;
24 import org.apache.hadoop.hbase.util.Bytes;
25
26 /**
27 * This class is responsible for the tracking and enforcement of Deletes
28 * during the course of a Scan operation.
29 *
30 * It only has to enforce Delete and DeleteColumn, since the
31 * DeleteFamily is handled at a higher level.
32 *
33 * <p>
34 * This class is utilized through three methods:
35 * <ul><li>{@link #add} when encountering a Delete or DeleteColumn
36 * <li>{@link #isDeleted} when checking if a Put KeyValue has been deleted
37 * <li>{@link #update} when reaching the end of a StoreFile or row for scans
38 * <p>
39 * This class is NOT thread-safe as queries are never multi-threaded
40 */
41 @InterfaceAudience.Private
42 public class ScanDeleteTracker implements DeleteTracker {
43
44 private boolean hasFamilyStamp = false;
45 private long familyStamp = 0L;
46 private byte [] deleteBuffer = null;
47 private int deleteOffset = 0;
48 private int deleteLength = 0;
49 private byte deleteType = 0;
50 private long deleteTimestamp = 0L;
51
52 /**
53 * Constructor for ScanDeleteTracker
54 */
55 public ScanDeleteTracker() {
56 super();
57 }
58
59 /**
60 * Add the specified KeyValue to the list of deletes to check against for
61 * this row operation.
62 * <p>
63 * This is called when a Delete is encountered in a StoreFile.
64 * @param buffer KeyValue buffer
65 * @param qualifierOffset column qualifier offset
66 * @param qualifierLength column qualifier length
67 * @param timestamp timestamp
68 * @param type delete type as byte
69 */
70 @Override
71 public void add(byte[] buffer, int qualifierOffset, int qualifierLength,
72 long timestamp, byte type) {
73 if (!hasFamilyStamp || timestamp > familyStamp) {
74 if (type == KeyValue.Type.DeleteFamily.getCode()) {
75 hasFamilyStamp = true;
76 familyStamp = timestamp;
77 return;
78 }
79
80 if (deleteBuffer != null && type < deleteType) {
81 // same column, so ignore less specific delete
82 if (Bytes.equals(deleteBuffer, deleteOffset, deleteLength,
83 buffer, qualifierOffset, qualifierLength)){
84 return;
85 }
86 }
87 // new column, or more general delete type
88 deleteBuffer = buffer;
89 deleteOffset = qualifierOffset;
90 deleteLength = qualifierLength;
91 deleteType = type;
92 deleteTimestamp = timestamp;
93 }
94 // missing else is never called.
95 }
96
97 /**
98 * Check if the specified KeyValue buffer has been deleted by a previously
99 * seen delete.
100 *
101 * @param buffer KeyValue buffer
102 * @param qualifierOffset column qualifier offset
103 * @param qualifierLength column qualifier length
104 * @param timestamp timestamp
105 * @return deleteResult
106 */
107 @Override
108 public DeleteResult isDeleted(byte [] buffer, int qualifierOffset,
109 int qualifierLength, long timestamp) {
110 if (hasFamilyStamp && timestamp <= familyStamp) {
111 return DeleteResult.FAMILY_DELETED;
112 }
113
114 if (deleteBuffer != null) {
115 int ret = Bytes.compareTo(deleteBuffer, deleteOffset, deleteLength,
116 buffer, qualifierOffset, qualifierLength);
117
118 if (ret == 0) {
119 if (deleteType == KeyValue.Type.DeleteColumn.getCode()) {
120 return DeleteResult.COLUMN_DELETED;
121 }
122 // Delete (aka DeleteVersion)
123 // If the timestamp is the same, keep this one
124 if (timestamp == deleteTimestamp) {
125 return DeleteResult.VERSION_DELETED;
126 }
127 // use assert or not?
128 assert timestamp < deleteTimestamp;
129
130 // different timestamp, let's clear the buffer.
131 deleteBuffer = null;
132 } else if(ret < 0){
133 // Next column case.
134 deleteBuffer = null;
135 } else {
136 throw new IllegalStateException("isDelete failed: deleteBuffer="
137 + Bytes.toStringBinary(deleteBuffer, deleteOffset, deleteLength)
138 + ", qualifier="
139 + Bytes.toStringBinary(buffer, qualifierOffset, qualifierLength)
140 + ", timestamp=" + timestamp + ", comparison result: " + ret);
141 }
142 }
143
144 return DeleteResult.NOT_DELETED;
145 }
146
147 @Override
148 public boolean isEmpty() {
149 return deleteBuffer == null && !hasFamilyStamp;
150 }
151
152 @Override
153 // called between every row.
154 public void reset() {
155 hasFamilyStamp = false;
156 familyStamp = 0L;
157 deleteBuffer = null;
158 }
159
160 @Override
161 // should not be called at all even (!)
162 public void update() {
163 this.reset();
164 }
165 }