/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.coprocessor.example; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HConstants.OperationStatusCode; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.coprocessor.BaseEndpointCoprocessor; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.OperationStatus; import org.apache.hadoop.hbase.regionserver.RegionScanner; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; public class BulkDeleteEndpoint extends BaseEndpointCoprocessor implements BulkDeleteProtocol { private static final String NO_OF_VERSIONS_TO_DELETE = "noOfVersionsToDelete"; private static final Log LOG = LogFactory.getLog(BulkDeleteEndpoint.class); @Override public BulkDeleteResponse delete(Scan scan, byte deleteType, Long timestamp, int rowBatchSize) { long totalRowsDeleted = 0L; long totalVersionsDeleted = 0L; BulkDeleteResponse response = new BulkDeleteResponse(); HRegion region = ((RegionCoprocessorEnvironment) getEnvironment()).getRegion(); boolean hasMore = true; RegionScanner scanner = null; if (scan.getFilter() == null && deleteType == DeleteType.ROW) { // What we need is just the rowkeys. So only 1st KV from any row is enough. // Only when it is a row delete, we can apply this filter // In other types we rely on the scan to know which all columns to be deleted. scan.setFilter(new FirstKeyOnlyFilter()); } // When the delete is based on some conditions so that Filters are available in the scan, // we assume that the scan is perfect having necessary column(s) only. try { scanner = region.getScanner(scan); while (hasMore) { List<List<KeyValue>> deleteRows = new ArrayList<List<KeyValue>>(rowBatchSize); for (int i = 0; i < rowBatchSize; i++) { List<KeyValue> results = new ArrayList<KeyValue>(); hasMore = scanner.next(results); if (results.size() > 0) { deleteRows.add(results); } if (!hasMore) { // There are no more rows. break; } } if (deleteRows.size() > 0) { Pair<Mutation, Integer>[] deleteWithLockArr = new Pair[deleteRows.size()]; int i = 0; for (List<KeyValue> deleteRow : deleteRows) { Delete delete = createDeleteMutation(deleteRow, deleteType, timestamp); deleteWithLockArr[i++] = new Pair<Mutation, Integer>(delete, null); } OperationStatus[] opStatus = region.batchMutate(deleteWithLockArr); for (i = 0; i < opStatus.length; i++) { if (opStatus[i].getOperationStatusCode() != OperationStatusCode.SUCCESS) { break; } totalRowsDeleted++; if (deleteType == DeleteType.VERSION) { byte[] versionsDeleted = deleteWithLockArr[i].getFirst().getAttribute( NO_OF_VERSIONS_TO_DELETE); if (versionsDeleted != null) { totalVersionsDeleted += Bytes.toInt(versionsDeleted); } } } } } } catch (IOException ioe) { LOG.error(ioe); response.setIoException(ioe); } finally { if (scanner != null) { try { scanner.close(); } catch (IOException ioe) { LOG.error(ioe); } } } response.setRowsDeleted(totalRowsDeleted); response.setVersionsDeleted(totalVersionsDeleted); return response; } private Delete createDeleteMutation(List<KeyValue> deleteRow, byte deleteType, Long timestamp) { long ts; if (timestamp == null) { ts = HConstants.LATEST_TIMESTAMP; } else { ts = timestamp; } // We just need the rowkey. Get it from 1st KV. byte[] row = deleteRow.get(0).getRow(); Delete delete = new Delete(row, ts, null); if (deleteType != DeleteType.ROW) { switch (deleteType) { case DeleteType.FAMILY: Set<byte[]> families = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR); for (KeyValue kv : deleteRow) { if (families.add(kv.getFamily())) { delete.deleteFamily(kv.getFamily(), ts); } } break; case DeleteType.COLUMN: Set<Column> columns = new HashSet<Column>(); for (KeyValue kv : deleteRow) { Column column = new Column(kv.getFamily(), kv.getQualifier()); if (columns.add(column)) { // Making deleteColumns() calls more than once for the same cf:qualifier is not correct // Every call to deleteColumns() will add a new KV to the familymap which will finally // get written to the memstore as part of delete(). delete.deleteColumns(column.family, column.qualifier, ts); } } break; case DeleteType.VERSION: // When some timestamp was passed to the delete() call only one version of the column (with // given timestamp) will be deleted. If no timestamp passed, it will delete N versions. // How many versions will get deleted depends on the Scan being passed. All the KVs that // the scan fetched will get deleted. int noOfVersionsToDelete = 0; if (timestamp == null) { for (KeyValue kv : deleteRow) { delete.deleteColumn(kv.getFamily(), kv.getQualifier(), kv.getTimestamp()); noOfVersionsToDelete++; } } else { columns = new HashSet<Column>(); for (KeyValue kv : deleteRow) { Column column = new Column(kv.getFamily(), kv.getQualifier()); // Only one version of particular column getting deleted. if (columns.add(column)) { delete.deleteColumn(column.family, column.qualifier, ts); noOfVersionsToDelete++; } } } delete.setAttribute(NO_OF_VERSIONS_TO_DELETE, Bytes.toBytes(noOfVersionsToDelete)); } } return delete; } private static class Column { private byte[] family; private byte[] qualifier; public Column(byte[] family, byte[] qualifier) { this.family = family; this.qualifier = qualifier; } @Override public boolean equals(Object other) { if (!(other instanceof Column)) { return false; } Column column = (Column) other; return Bytes.equals(this.family, column.family) && Bytes.equals(this.qualifier, column.qualifier); } @Override public int hashCode() { int h = 31; h = h + 13 * Bytes.hashCode(this.family); h = h + 13 * Bytes.hashCode(this.qualifier); return h; } } }