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 org.junit.jupiter.api.Assertions.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.HBaseTestingUtil;
028import org.apache.hadoop.hbase.TableExistsException;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.TableNotFoundException;
031import org.apache.hadoop.hbase.client.Admin;
032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
034import org.apache.hadoop.hbase.client.Durability;
035import org.apache.hadoop.hbase.client.Put;
036import org.apache.hadoop.hbase.client.Result;
037import org.apache.hadoop.hbase.client.ResultScanner;
038import org.apache.hadoop.hbase.client.Scan;
039import org.apache.hadoop.hbase.client.Table;
040import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
041import org.apache.hadoop.hbase.filter.BinaryComparator;
042import org.apache.hadoop.hbase.filter.Filter;
043import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
044import org.apache.hadoop.hbase.io.compress.Compression.Algorithm;
045import org.apache.hadoop.hbase.testclassification.MediumTests;
046import org.apache.hadoop.hbase.testclassification.RegionServerTests;
047import org.apache.hadoop.hbase.util.Bytes;
048import org.junit.jupiter.api.AfterAll;
049import org.junit.jupiter.api.BeforeAll;
050import org.junit.jupiter.api.Tag;
051import org.junit.jupiter.api.Test;
052
053/*
054 * This test verifies that the scenarios illustrated by HBASE-10850 work w.r.t. essential column
055 * family optimization
056 */
057@Tag(RegionServerTests.TAG)
058@Tag(MediumTests.TAG)
059public class TestSCVFWithMiniCluster {
060
061  private static final TableName HBASE_TABLE_NAME = TableName.valueOf("TestSCVFWithMiniCluster");
062
063  private static final byte[] FAMILY_A = Bytes.toBytes("a");
064  private static final byte[] FAMILY_B = Bytes.toBytes("b");
065
066  private static final byte[] QUALIFIER_FOO = Bytes.toBytes("foo");
067  private static final byte[] QUALIFIER_BAR = Bytes.toBytes("bar");
068
069  private static Table htable;
070
071  private static Filter scanFilter;
072
073  private int expected = 1;
074
075  @BeforeAll
076  public static void setUp() throws Exception {
077    HBaseTestingUtil util = new HBaseTestingUtil();
078
079    util.startMiniCluster(1);
080
081    Admin admin = util.getAdmin();
082    destroy(admin, HBASE_TABLE_NAME);
083    create(admin, HBASE_TABLE_NAME, FAMILY_A, FAMILY_B);
084    admin.close();
085    htable = util.getConnection().getTable(HBASE_TABLE_NAME);
086
087    /* Add some values */
088    List<Put> puts = new ArrayList<>();
089
090    /* Add a row with 'a:foo' = false */
091    Put put = new Put(Bytes.toBytes("1"));
092    put.setDurability(Durability.SKIP_WAL);
093    put.addColumn(FAMILY_A, QUALIFIER_FOO, Bytes.toBytes("false"));
094    put.addColumn(FAMILY_A, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
095    put.addColumn(FAMILY_B, QUALIFIER_FOO, Bytes.toBytes("_flag_"));
096    put.addColumn(FAMILY_B, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
097    puts.add(put);
098
099    /* Add a row with 'a:foo' = true */
100    put = new Put(Bytes.toBytes("2"));
101    put.setDurability(Durability.SKIP_WAL);
102    put.addColumn(FAMILY_A, QUALIFIER_FOO, Bytes.toBytes("true"));
103    put.addColumn(FAMILY_A, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
104    put.addColumn(FAMILY_B, QUALIFIER_FOO, Bytes.toBytes("_flag_"));
105    put.addColumn(FAMILY_B, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
106    puts.add(put);
107
108    /* Add a row with 'a:foo' qualifier not set */
109    put = new Put(Bytes.toBytes("3"));
110    put.setDurability(Durability.SKIP_WAL);
111    put.addColumn(FAMILY_A, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
112    put.addColumn(FAMILY_B, QUALIFIER_FOO, Bytes.toBytes("_flag_"));
113    put.addColumn(FAMILY_B, QUALIFIER_BAR, Bytes.toBytes("_flag_"));
114    puts.add(put);
115
116    htable.put(puts);
117    /*
118     * We want to filter out from the scan all rows that do not have the column 'a:foo' with value
119     * 'false'. Only row with key '1' should be returned in the scan.
120     */
121    scanFilter = new SingleColumnValueFilter(FAMILY_A, QUALIFIER_FOO, CompareOperator.EQUAL,
122      new BinaryComparator(Bytes.toBytes("false")));
123    ((SingleColumnValueFilter) scanFilter).setFilterIfMissing(true);
124  }
125
126  @AfterAll
127  public static void tearDown() throws Exception {
128    htable.close();
129  }
130
131  private void verify(Scan scan) throws IOException {
132    ResultScanner scanner = htable.getScanner(scan);
133    Iterator<Result> it = scanner.iterator();
134
135    /* Then */
136    int count = 0;
137    try {
138      while (it.hasNext()) {
139        it.next();
140        count++;
141      }
142    } finally {
143      scanner.close();
144    }
145    assertEquals(expected, count);
146  }
147
148  /**
149   * Test the filter by adding all columns of family A in the scan. (OK)
150   */
151  @Test
152  public void scanWithAllQualifiersOfFamiliyA() throws IOException {
153    /* Given */
154    Scan scan = new Scan();
155    scan.addFamily(FAMILY_A);
156    scan.setFilter(scanFilter);
157
158    verify(scan);
159  }
160
161  /**
162   * Test the filter by adding all columns of family A and B in the scan. (KO: row '3' without
163   * 'a:foo' qualifier is returned)
164   */
165  @Test
166  public void scanWithAllQualifiersOfBothFamilies() throws IOException {
167    /* When */
168    Scan scan = new Scan();
169    scan.setFilter(scanFilter);
170
171    verify(scan);
172  }
173
174  /**
175   * Test the filter by adding 2 columns of family A and 1 column of family B in the scan. (KO: row
176   * '3' without 'a:foo' qualifier is returned)
177   */
178  @Test
179  public void scanWithSpecificQualifiers1() throws IOException {
180    /* When */
181    Scan scan = new Scan();
182    scan.addColumn(FAMILY_A, QUALIFIER_FOO);
183    scan.addColumn(FAMILY_A, QUALIFIER_BAR);
184    scan.addColumn(FAMILY_B, QUALIFIER_BAR);
185    scan.addColumn(FAMILY_B, QUALIFIER_FOO);
186    scan.setFilter(scanFilter);
187
188    verify(scan);
189  }
190
191  /**
192   * Test the filter by adding 1 column of family A (the one used in the filter) and 1 column of
193   * family B in the scan. (OK)
194   */
195  @Test
196  public void scanWithSpecificQualifiers2() throws IOException {
197    /* When */
198    Scan scan = new Scan();
199    scan.addColumn(FAMILY_A, QUALIFIER_FOO);
200    scan.addColumn(FAMILY_B, QUALIFIER_BAR);
201    scan.setFilter(scanFilter);
202
203    verify(scan);
204  }
205
206  /**
207   * Test the filter by adding 2 columns of family A in the scan. (OK)
208   */
209  @Test
210  public void scanWithSpecificQualifiers3() throws IOException {
211    /* When */
212    Scan scan = new Scan();
213    scan.addColumn(FAMILY_A, QUALIFIER_FOO);
214    scan.addColumn(FAMILY_A, QUALIFIER_BAR);
215    scan.setFilter(scanFilter);
216
217    verify(scan);
218  }
219
220  private static void create(Admin admin, TableName tableName, byte[]... families)
221    throws IOException {
222    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
223    for (byte[] family : families) {
224      ColumnFamilyDescriptor familyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(family)
225        .setMaxVersions(1).setCompressionType(Algorithm.GZ).build();
226      builder.setColumnFamily(familyDescriptor);
227    }
228    try {
229      admin.createTable(builder.build());
230    } catch (TableExistsException tee) {
231      /* Ignore */
232    }
233  }
234
235  private static void destroy(Admin admin, TableName tableName) throws IOException {
236    try {
237      admin.disableTable(tableName);
238      admin.deleteTable(tableName);
239    } catch (TableNotFoundException tnfe) {
240      /* Ignore */
241    }
242  }
243}