/**
* Copyright 2011 The Apache Software Foundation
*
* 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.index.coprocessor.regionserver;
import static org.apache.hadoop.hbase.util.Bytes.toBytes;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.LargeTests;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.index.ColumnQualifier.ValueType;
import org.apache.hadoop.hbase.index.Constants;
import org.apache.hadoop.hbase.index.IndexSpecification;
import org.apache.hadoop.hbase.index.util.IndexUtils;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@Category(LargeTests.class)
public class TestDelete {
private static final String CF_EMP = "emp";
private static final String CF_DEPT = "dept";
private static final String CQ_ENAME = "ename";
private static final String CQ_SAL = "salary";
private static final String CQ_DNO = "dno";
private static final String CQ_DNAME = "dname";
private static final String START_KEY = "100";
private static final String END_KEY = "200";
private HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private String DIR = TEST_UTIL.getDataTestDir("TestHRegion").toString();
private Path basedir;
private HRegion userRegion;
private HRegion indexRegion;
private Map<String, IndexSpecification> indexMap;
private IndexRegionObserver indexer;
private Collection<String> indexPuts;
private Collection<String> indexDeletes;
@Before
public void setup() throws Exception {
prepare();
indexMap = new HashMap<String, IndexSpecification>();
index("idx_ename", CF_EMP, CQ_ENAME, ValueType.String, 10);
index("idx_sal", CF_EMP, CQ_SAL, ValueType.String, 10);
index("idx_dname", CF_DEPT, CQ_DNAME, ValueType.String, 10);
index("idx_dno_ename", CF_DEPT, CQ_DNO, ValueType.String, 10);
index("idx_dno_ename", CF_EMP, CQ_ENAME, ValueType.String, 10);
indexer = new IndexRegionObserver();
indexPuts = new TreeSet<String>();
indexDeletes = new TreeSet<String>();
}
@After
public void teardown() throws IOException {
HRegion.deleteRegion(basedir.getFileSystem(TEST_UTIL.getConfiguration()), basedir,
userRegion.getRegionInfo());
}
@Test
public void testDeleteVersion() throws IOException {
// prepare test data
put(101, 1, 1230);
put(101, 1, 1240);
// Delete version. Boundary scenario
deleteVersion(101, CF_EMP, CQ_SAL, 1230);
// should delete only one cell - one version
assertTrue("Should delete exactly one nearest version of index entry (salary)",
indexDeletes.size() == 1);
// verify deletes against puts
assertTrue("Index-deletes should be a subset of index puts",
indexPuts.containsAll(indexDeletes));
}
@Test
public void testDeleteCells() throws IOException {
// prepare test data
put(101, 0, 1230);
put(101, 0, 1240);
put(102, 2, 1240);
// Delete cell - all versions of a qualifier
deleteColumn(101, CF_EMP, CQ_SAL);
assertTrue("Should delete all versions of a cell index entry (salary)",
indexDeletes.size() == 2);
// verify deletes against puts
assertTrue("Index-deletes should be a subset of index puts",
indexPuts.containsAll(indexDeletes));
}
@Test
public void testDeleteFamily() throws IOException {
// prepare test data
put(101, 1, 1230);
put(101, 2, 1240);
put(102, 1, 1230);
put(102, 0, 1240);
// Delete family - All cells of the family
deleteFamily(101, CF_EMP);
// verify deletes against puts
assertTrue("Index-deletes should be a subset of index puts",
indexPuts.size() > indexDeletes.size());
assertTrue("Index-deletes should be a subset of index puts",
indexPuts.containsAll(indexDeletes));
}
@Test
public void testDeleteRow() throws IOException {
// prepare test data
put(101, 1, 1230);
put(101, 2, 1240);
put(102, 1, 1250);
// Delete row - all families & all cells
deleteRow(101);
deleteRow(102);
// verify deletes against puts
assertEquals("Puts and deletes are not same", indexPuts, indexDeletes);
}
private void prepare() throws IOException {
basedir = new Path(DIR + "TestIndexDelete");
Configuration conf = TEST_UTIL.getConfiguration();
// Prepare the 'employee' table region
HTableDescriptor desc = new HTableDescriptor("employee");
desc.addFamily(new HColumnDescriptor(CF_EMP));
desc.addFamily(new HColumnDescriptor(CF_DEPT));
HRegionInfo info =
new HRegionInfo(desc.getName(), START_KEY.getBytes(), END_KEY.getBytes(), false);
userRegion = HRegion.createHRegion(info, basedir, conf, desc);
// Prepare the 'employee_idx' index table region
HTableDescriptor idxDesc = new HTableDescriptor("employee_idx");
idxDesc.addFamily(new HColumnDescriptor(Constants.IDX_COL_FAMILY));
HRegionInfo idxInfo =
new HRegionInfo(idxDesc.getName(), START_KEY.getBytes(), END_KEY.getBytes(), false);
indexRegion = HRegion.createHRegion(idxInfo, basedir, conf, idxDesc);
}
private void index(String name, String cf, String cq, ValueType type, int maxSize) {
IndexSpecification index = indexMap.get(name);
if (index == null) {
index = new IndexSpecification(name);
}
index.addIndexColumn(new HColumnDescriptor(cf), cq, type, maxSize);
indexMap.put(name, index);
}
// For simplicity try to derive all details from eno and dno
// Don't add department details when dno is 0 - Equivalent to adding EMP column family only
private void put(int eno, int dno, long ts) throws IOException {
Put put = new Put(toBytes("" + eno));
put.add(toBytes(CF_EMP), toBytes(CQ_ENAME), ts, toBytes("emp_" + eno));
put.add(toBytes(CF_EMP), toBytes(CQ_SAL), ts, toBytes("" + eno * 100));
// Don't add department details when dno is 0
// Equivalent to adding EMP column family only
if (dno != 0) {
put.add(toBytes(CF_DEPT), toBytes(CQ_DNO), ts, toBytes("" + dno));
put.add(toBytes(CF_DEPT), toBytes(CQ_DNAME), ts, toBytes("dept_" + dno));
}
put.setWriteToWAL(false);
userRegion.put(put);
for (IndexSpecification spec : indexMap.values()) {
Put idxPut = IndexUtils.prepareIndexPut(put, spec, indexRegion);
if (idxPut != null) {
KeyValue kv = idxPut.get(Constants.IDX_COL_FAMILY, new byte[0]).get(0);
indexPuts.add(Bytes.toString(idxPut.getRow()) + "_" + kv.getTimestamp());
}
}
}
private void deleteRow(int eno) throws IOException {
Delete delete = new Delete(toBytes("" + eno));
// Make the delete ready-to-eat by indexer
for (byte[] family : userRegion.getTableDesc().getFamiliesKeys()) {
delete.deleteFamily(family, delete.getTimeStamp());
}
delete(delete);
}
private void deleteFamily(int eno, String cf) throws IOException {
Delete delete = new Delete(toBytes("" + eno));
delete.deleteFamily(toBytes(cf));
delete(delete);
}
private void deleteColumn(int eno, String cf, String cq) throws IOException {
Delete delete = new Delete(toBytes("" + eno));
delete.deleteColumns(toBytes(cf), toBytes(cq));
delete(delete);
}
private void deleteVersion(int eno, String cf, String cq, long ts) throws IOException {
Delete delete = new Delete(toBytes("" + eno));
delete.deleteColumn(toBytes(cf), toBytes(cq), ts);
delete(delete);
}
private void delete(Delete delete) throws IOException {
Collection<? extends Mutation> deletes =
indexer.prepareIndexDeletes(delete, userRegion,
new ArrayList<IndexSpecification>(indexMap.values()), indexRegion);
for (Mutation idxdelete : deletes) {
KeyValue kv = idxdelete.getFamilyMap().get(Constants.IDX_COL_FAMILY).get(0);
indexDeletes.add(Bytes.toString(idxdelete.getRow()) + "_" + kv.getTimestamp());
}
}
}