001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import static junit.framework.Assert.assertEquals;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Iterator;
025import java.util.List;
026import org.apache.hadoop.hbase.CompareOperator;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.HBaseTestingUtil;
029import org.apache.hadoop.hbase.TableExistsException;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.TableNotFoundException;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
035import org.apache.hadoop.hbase.client.Durability;
036import org.apache.hadoop.hbase.client.Put;
037import org.apache.hadoop.hbase.client.Result;
038import org.apache.hadoop.hbase.client.ResultScanner;
039import org.apache.hadoop.hbase.client.Scan;
040import org.apache.hadoop.hbase.client.Table;
041import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
042import org.apache.hadoop.hbase.filter.BinaryComparator;
043import org.apache.hadoop.hbase.filter.Filter;
044import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
045import org.apache.hadoop.hbase.io.compress.Compression.Algorithm;
046import org.apache.hadoop.hbase.testclassification.MediumTests;
047import org.apache.hadoop.hbase.testclassification.RegionServerTests;
048import org.apache.hadoop.hbase.util.Bytes;
049import org.junit.AfterClass;
050import org.junit.BeforeClass;
051import org.junit.ClassRule;
052import org.junit.Test;
053import org.junit.experimental.categories.Category;
054
055@Category({ RegionServerTests.class, MediumTests.class })
056/*
057 * This test verifies that the scenarios illustrated by HBASE-10850 work w.r.t. essential column
058 * family optimization
059 */
060public class TestSCVFWithMiniCluster {
061
062  @ClassRule
063  public static final HBaseClassTestRule CLASS_RULE =
064    HBaseClassTestRule.forClass(TestSCVFWithMiniCluster.class);
065
066  private static final TableName HBASE_TABLE_NAME = TableName.valueOf("TestSCVFWithMiniCluster");
067
068  private static final byte[] FAMILY_A = Bytes.toBytes("a");
069  private static final byte[] FAMILY_B = Bytes.toBytes("b");
070
071  private static final byte[] QUALIFIER_FOO = Bytes.toBytes("foo");
072  private static final byte[] QUALIFIER_BAR = Bytes.toBytes("bar");
073
074  private static Table htable;
075
076  private static Filter scanFilter;
077
078  private int expected = 1;
079
080  @BeforeClass
081  public static void setUp() throws Exception {
082    HBaseTestingUtil util = new HBaseTestingUtil();
083
084    util.startMiniCluster(1);
085
086    Admin admin = util.getAdmin();
087    destroy(admin, HBASE_TABLE_NAME);
088    create(admin, HBASE_TABLE_NAME, FAMILY_A, FAMILY_B);
089    admin.close();
090    htable = util.getConnection().getTable(HBASE_TABLE_NAME);
091
092    /* Add some values */
093    List<Put> puts = new ArrayList<>();
094
095    /* Add a row with 'a:foo' = false */
096    Put put = new Put(Bytes.toBytes("1"));
097    put.setDurability(Durability.SKIP_WAL);
098    put.addColumn(FAMILY_A, QUALIFIER_FOO, Bytes.toBytes("false"));
099    put.addColumn(FAMILY_A, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
100    put.addColumn(FAMILY_B, QUALIFIER_FOO, Bytes.toBytes("_flag_"));
101    put.addColumn(FAMILY_B, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
102    puts.add(put);
103
104    /* Add a row with 'a:foo' = true */
105    put = new Put(Bytes.toBytes("2"));
106    put.setDurability(Durability.SKIP_WAL);
107    put.addColumn(FAMILY_A, QUALIFIER_FOO, Bytes.toBytes("true"));
108    put.addColumn(FAMILY_A, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
109    put.addColumn(FAMILY_B, QUALIFIER_FOO, Bytes.toBytes("_flag_"));
110    put.addColumn(FAMILY_B, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
111    puts.add(put);
112
113    /* Add a row with 'a:foo' qualifier not set */
114    put = new Put(Bytes.toBytes("3"));
115    put.setDurability(Durability.SKIP_WAL);
116    put.addColumn(FAMILY_A, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
117    put.addColumn(FAMILY_B, QUALIFIER_FOO, Bytes.toBytes("_flag_"));
118    put.addColumn(FAMILY_B, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
119    puts.add(put);
120
121    htable.put(puts);
122    /*
123     * We want to filter out from the scan all rows that do not have the column 'a:foo' with value
124     * 'false'. Only row with key '1' should be returned in the scan.
125     */
126    scanFilter = new SingleColumnValueFilter(FAMILY_A, QUALIFIER_FOO, CompareOperator.EQUAL,
127      new BinaryComparator(Bytes.toBytes("false")));
128    ((SingleColumnValueFilter) scanFilter).setFilterIfMissing(true);
129  }
130
131  @AfterClass
132  public static void tearDown() throws Exception {
133    htable.close();
134  }
135
136  private void verify(Scan scan) throws IOException {
137    ResultScanner scanner = htable.getScanner(scan);
138    Iterator<Result> it = scanner.iterator();
139
140    /* Then */
141    int count = 0;
142    try {
143      while (it.hasNext()) {
144        it.next();
145        count++;
146      }
147    } finally {
148      scanner.close();
149    }
150    assertEquals(expected, count);
151  }
152
153  /**
154   * Test the filter by adding all columns of family A in the scan. (OK)
155   */
156  @Test
157  public void scanWithAllQualifiersOfFamiliyA() throws IOException {
158    /* Given */
159    Scan scan = new Scan();
160    scan.addFamily(FAMILY_A);
161    scan.setFilter(scanFilter);
162
163    verify(scan);
164  }
165
166  /**
167   * Test the filter by adding all columns of family A and B in the scan. (KO: row '3' without
168   * 'a:foo' qualifier is returned)
169   */
170  @Test
171  public void scanWithAllQualifiersOfBothFamilies() throws IOException {
172    /* When */
173    Scan scan = new Scan();
174    scan.setFilter(scanFilter);
175
176    verify(scan);
177  }
178
179  /**
180   * Test the filter by adding 2 columns of family A and 1 column of family B in the scan. (KO: row
181   * '3' without 'a:foo' qualifier is returned)
182   */
183  @Test
184  public void scanWithSpecificQualifiers1() throws IOException {
185    /* When */
186    Scan scan = new Scan();
187    scan.addColumn(FAMILY_A, QUALIFIER_FOO);
188    scan.addColumn(FAMILY_A, QUALIFIER_BAR);
189    scan.addColumn(FAMILY_B, QUALIFIER_BAR);
190    scan.addColumn(FAMILY_B, QUALIFIER_FOO);
191    scan.setFilter(scanFilter);
192
193    verify(scan);
194  }
195
196  /**
197   * Test the filter by adding 1 column of family A (the one used in the filter) and 1 column of
198   * family B in the scan. (OK)
199   */
200  @Test
201  public void scanWithSpecificQualifiers2() throws IOException {
202    /* When */
203    Scan scan = new Scan();
204    scan.addColumn(FAMILY_A, QUALIFIER_FOO);
205    scan.addColumn(FAMILY_B, QUALIFIER_BAR);
206    scan.setFilter(scanFilter);
207
208    verify(scan);
209  }
210
211  /**
212   * Test the filter by adding 2 columns of family A in the scan. (OK)
213   */
214  @Test
215  public void scanWithSpecificQualifiers3() throws IOException {
216    /* When */
217    Scan scan = new Scan();
218    scan.addColumn(FAMILY_A, QUALIFIER_FOO);
219    scan.addColumn(FAMILY_A, QUALIFIER_BAR);
220    scan.setFilter(scanFilter);
221
222    verify(scan);
223  }
224
225  private static void create(Admin admin, TableName tableName, byte[]... families)
226    throws IOException {
227    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
228    for (byte[] family : families) {
229      ColumnFamilyDescriptor familyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(family)
230        .setMaxVersions(1).setCompressionType(Algorithm.GZ).build();
231      builder.setColumnFamily(familyDescriptor);
232    }
233    try {
234      admin.createTable(builder.build());
235    } catch (TableExistsException tee) {
236      /* Ignore */
237    }
238  }
239
240  private static void destroy(Admin admin, TableName tableName) throws IOException {
241    try {
242      admin.disableTable(tableName);
243      admin.deleteTable(tableName);
244    } catch (TableNotFoundException tnfe) {
245      /* Ignore */
246    }
247  }
248}