/** * Copyright 2009 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.client.tableindexed; import java.io.IOException; import java.util.Random; import junit.framework.Assert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.PerformanceEvaluation; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.RowLock; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.regionserver.tableindexed.IndexedRegionServer; import org.apache.hadoop.hbase.util.Bytes; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; public class TestIndexedTable { private static final Log LOG = LogFactory.getLog(TestIndexedTable.class); private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static final String TABLE_NAME = "table1"; private static final byte[] FAMILY = Bytes.toBytes("family"); private static final byte[] QUAL_A = Bytes.toBytes("a"); private static final byte[] COL_A = Bytes.toBytes("family:a"); private static final String INDEX_COL_A = "A"; private static final int NUM_ROWS = 10; private static final int MAX_VAL = 10000; private static IndexedTableAdmin admin; private static IndexedTable table; private Random random = new Random(); private static HTableDescriptor desc; /** * @throws java.lang.Exception */ @BeforeClass public static void setUpBeforeClass() throws Exception { TEST_UTIL.getConfiguration().set(HConstants.REGION_SERVER_IMPL, IndexedRegionServer.class.getName()); TEST_UTIL.startMiniCluster(3); setupTables(); } /** * @throws java.lang.Exception */ @AfterClass public static void tearDownAfterClass() throws Exception { TEST_UTIL.shutdownMiniCluster(); } /** * @throws java.lang.Exception */ public static void setupTables() throws Exception { desc = new HTableDescriptor(TABLE_NAME); desc.addFamily(new HColumnDescriptor(FAMILY)); IndexedTableDescriptor indexDesc = new IndexedTableDescriptor(desc); // Create a new index that does lexicographic ordering on COL_A IndexSpecification colAIndex = new IndexSpecification(INDEX_COL_A, COL_A); indexDesc.addIndex(colAIndex); admin = new IndexedTableAdmin(TEST_UTIL.getConfiguration()); admin.createIndexedTable(indexDesc); table = new IndexedTable(TEST_UTIL.getConfiguration(), desc.getName()); } private void writeInitalRows() throws IOException { for (int i = 0; i < NUM_ROWS; i++) { Put update = new Put(PerformanceEvaluation.format(i)); byte[] valueA = PerformanceEvaluation.format(random.nextInt(MAX_VAL)); update.add(FAMILY, QUAL_A, valueA); table.put(update); LOG.info("Inserted row [" + Bytes.toString(update.getRow()) + "] val: [" + Bytes.toString(valueA) + "]"); } } @Test public void testInitialWrites() throws IOException { writeInitalRows(); assertRowsInOrder(NUM_ROWS); } private void assertRowsInOrder(final int numRowsExpected) throws IndexNotFoundException, IOException { ResultScanner scanner = table.getIndexedScanner(INDEX_COL_A, null, null, null, null, null); int numRows = 0; byte[] lastColA = null; for (Result rowResult : scanner) { byte[] colA = rowResult.getValue(FAMILY, QUAL_A); LOG.info("index scan : row [" + Bytes.toString(rowResult.getRow()) + "] value [" + Bytes.toString(colA) + "]"); if (lastColA != null) { Assert.assertTrue(Bytes.compareTo(lastColA, colA) <= 0); } lastColA = colA; numRows++; } scanner.close(); Assert.assertEquals(numRowsExpected, numRows); } private void assertRowUpdated(final int updatedRow, final int expectedRowValue) throws IndexNotFoundException, IOException { ResultScanner scanner = table.getIndexedScanner(INDEX_COL_A, null, null, null, null, null); byte[] persistedRowValue = null; for (Result rowResult : scanner) { byte[] row = rowResult.getRow(); byte[] value = rowResult.getValue(FAMILY, QUAL_A); if (Bytes.toString(row).equals(Bytes.toString(PerformanceEvaluation.format(updatedRow)))) { persistedRowValue = value; LOG.info("update found: row [" + Bytes.toString(row) + "] value [" + Bytes.toString(value) + "]"); } else LOG.info("updated index scan : row [" + Bytes.toString(row) + "] value [" + Bytes.toString(value) + "]"); } scanner.close(); Assert.assertEquals(Bytes.toString(PerformanceEvaluation.format(expectedRowValue)), Bytes .toString(persistedRowValue)); } private void assertRowDeleted(final int numRowsExpected) throws IndexNotFoundException, IOException { // Check the size of the primary table ResultScanner scanner = table.getScanner(new Scan()); int numRows = 0; for (Result rowResult : scanner) { byte[] colA = rowResult.getValue(FAMILY, QUAL_A); LOG.info("primary scan : row [" + Bytes.toString(rowResult.getRow()) + "] value [" + Bytes.toString(colA) + "]"); numRows++; } scanner.close(); Assert.assertEquals(numRowsExpected, numRows); // Check the size of the index tables assertRowsInOrder(numRowsExpected); } private void updateRow(final int row, final int newValue) throws IOException { Put update = new Put(PerformanceEvaluation.format(row)); byte[] valueA = PerformanceEvaluation.format(newValue); update.add(FAMILY, QUAL_A, valueA); table.put(update); LOG.info("Updated row [" + Bytes.toString(update.getRow()) + "] val: [" + Bytes.toString(valueA) + "]"); } private void updateLockedRow(final int row, final int newValue) throws IOException { RowLock lock = table.lockRow(PerformanceEvaluation.format(row)); Put update = new Put(PerformanceEvaluation.format(row), lock); byte[] valueA = PerformanceEvaluation.format(newValue); update.add(FAMILY, QUAL_A, valueA); LOG.info("Updating row [" + Bytes.toString(update.getRow()) + "] val: [" + Bytes.toString(valueA) + "]"); table.put(update); LOG.info("Updated row [" + Bytes.toString(update.getRow()) + "] val: [" + Bytes.toString(valueA) + "]"); table.unlockRow(lock); } private void updateLockedRowNoAutoFlush(final int row, final int newValue) throws IOException { table.flushCommits(); table.setAutoFlush(false); RowLock lock = table.lockRow(PerformanceEvaluation.format(row)); Put update = new Put(PerformanceEvaluation.format(row), lock); byte[] valueA = PerformanceEvaluation.format(newValue); update.add(FAMILY, QUAL_A, valueA); LOG.info("Updating row [" + Bytes.toString(update.getRow()) + "] val: [" + Bytes.toString(valueA) + "]"); table.put(update); LOG.info("Updated row [" + Bytes.toString(update.getRow()) + "] val: [" + Bytes.toString(valueA) + "]"); table.flushCommits(); table.close(); table = new IndexedTable(TEST_UTIL.getConfiguration(), desc.getName()); } @Test public void testMultipleWrites() throws IOException { writeInitalRows(); writeInitalRows(); // Update the rows. assertRowsInOrder(NUM_ROWS); } @Test public void testDelete() throws IOException { writeInitalRows(); // Delete the first row; table.delete(new Delete(PerformanceEvaluation.format(0))); assertRowsInOrder(NUM_ROWS - 1); } @Test public void testRowUpdate() throws IOException { writeInitalRows(); int row = NUM_ROWS - 2; int value = MAX_VAL + 111; updateRow(row, value); assertRowUpdated(row, value); } @Test public void testLockedRowUpdate() throws IOException { writeInitalRows(); int row = NUM_ROWS - 2; int value = MAX_VAL + 111; updateLockedRow(row, value); assertRowUpdated(row, value); } @Test public void testLockedRowUpdateNoAutoFlush() throws IOException { writeInitalRows(); int row = NUM_ROWS - 4; int value = MAX_VAL + 2222; updateLockedRowNoAutoFlush(row, value); assertRowUpdated(row, value); } @Test public void testLockedRowDelete() throws IOException { writeInitalRows(); // Delete the first row; byte[] row = PerformanceEvaluation.format(0); RowLock lock = table.lockRow(row); table.delete(new Delete(row, HConstants.LATEST_TIMESTAMP, lock)); table.unlockRow(lock); assertRowDeleted(NUM_ROWS - 1); } }