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.client;
019
020import static org.hamcrest.MatcherAssert.assertThat;
021import static org.hamcrest.Matchers.*;
022import static org.hamcrest.Matchers.lessThan;
023import static org.junit.jupiter.api.Assertions.assertArrayEquals;
024import static org.junit.jupiter.api.Assertions.assertEquals;
025import static org.junit.jupiter.api.Assertions.assertFalse;
026import static org.junit.jupiter.api.Assertions.assertNotSame;
027import static org.junit.jupiter.api.Assertions.assertNull;
028import static org.junit.jupiter.api.Assertions.assertSame;
029import static org.junit.jupiter.api.Assertions.assertThrows;
030import static org.junit.jupiter.api.Assertions.assertTrue;
031import static org.junit.jupiter.api.Assertions.fail;
032
033import java.io.IOException;
034import java.nio.ByteBuffer;
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.List;
038import org.apache.hadoop.hbase.ArrayBackedTag;
039import org.apache.hadoop.hbase.ByteBufferKeyValue;
040import org.apache.hadoop.hbase.Cell;
041import org.apache.hadoop.hbase.CellComparator;
042import org.apache.hadoop.hbase.CellScanner;
043import org.apache.hadoop.hbase.CellUtil;
044import org.apache.hadoop.hbase.KeyValue;
045import org.apache.hadoop.hbase.Tag;
046import org.apache.hadoop.hbase.testclassification.ClientTests;
047import org.apache.hadoop.hbase.testclassification.SmallTests;
048import org.apache.hadoop.hbase.util.ByteBufferUtils;
049import org.apache.hadoop.hbase.util.Bytes;
050import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
051import org.junit.jupiter.api.Test;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055@org.junit.jupiter.api.Tag(SmallTests.TAG)
056@org.junit.jupiter.api.Tag(ClientTests.TAG)
057public class TestResult {
058
059  private static final Logger LOG = LoggerFactory.getLogger(TestResult.class.getName());
060
061  static KeyValue[] genKVs(final byte[] row, final byte[] family, final byte[] value,
062    final long timestamp, final int cols) {
063    KeyValue[] kvs = new KeyValue[cols];
064
065    for (int i = 0; i < cols; i++) {
066      kvs[i] =
067        new KeyValue(row, family, Bytes.toBytes(i), timestamp, Bytes.add(value, Bytes.toBytes(i)));
068    }
069    return kvs;
070  }
071
072  static final byte[] row = Bytes.toBytes("row");
073  static final byte[] family = Bytes.toBytes("family");
074  static final byte[] value = Bytes.toBytes("value");
075  static final byte[] qual = Bytes.toBytes("qual");
076
077  /**
078   * Run some tests to ensure Result acts like a proper CellScanner.
079   */
080  @Test
081  public void testResultAsCellScanner() throws IOException {
082    Cell[] cells = genKVs(row, family, value, 1, 10);
083    Arrays.sort(cells, CellComparator.getInstance());
084    Result r = Result.create(cells);
085    assertCellsSame(r, cells);
086    // Assert I run over same result multiple times.
087    assertCellsSame(r.cellScanner(), cells);
088    assertCellsSame(r.cellScanner(), cells);
089    // Assert we are not creating new object when doing cellscanner
090    assertSame(r, r.cellScanner());
091  }
092
093  private void assertCellsSame(final CellScanner cellScanner, final Cell[] cells)
094    throws IOException {
095    int count = 0;
096    while (cellScanner.advance()) {
097      assertTrue(cells[count].equals(cellScanner.current()));
098      count++;
099    }
100    assertEquals(cells.length, count);
101  }
102
103  @Test
104  public void testBasicGetColumn() throws Exception {
105    KeyValue[] kvs = genKVs(row, family, value, 1, 100);
106
107    Arrays.sort(kvs, CellComparator.getInstance());
108
109    Result r = Result.create(kvs);
110
111    for (int i = 0; i < 100; ++i) {
112      final byte[] qf = Bytes.toBytes(i);
113
114      List<Cell> ks = r.getColumnCells(family, qf);
115      assertEquals(1, ks.size());
116      assertTrue(CellUtil.matchingQualifier(ks.get(0), qf));
117      assertEquals(ks.get(0), r.getColumnLatestCell(family, qf));
118    }
119  }
120
121  @Test
122  public void testCurrentOnEmptyCell() throws IOException {
123    Result r = Result.create(new Cell[0]);
124    assertFalse(r.advance());
125    assertNull(r.current());
126  }
127
128  @Test
129  public void testAdvanceMultipleOnEmptyCell() throws IOException {
130    Result r = Result.create(new Cell[0]);
131    // After HBASE-26688, advance of result with empty cell list will always return false.
132    // Here 10 is an arbitrary number to test the logic.
133    for (int i = 0; i < 10; i++) {
134      assertFalse(r.advance());
135    }
136  }
137
138  @Test
139  public void testMultiVersionGetColumn() throws Exception {
140    KeyValue[] kvs1 = genKVs(row, family, value, 1, 100);
141    KeyValue[] kvs2 = genKVs(row, family, value, 200, 100);
142
143    KeyValue[] kvs = new KeyValue[kvs1.length + kvs2.length];
144    System.arraycopy(kvs1, 0, kvs, 0, kvs1.length);
145    System.arraycopy(kvs2, 0, kvs, kvs1.length, kvs2.length);
146
147    Arrays.sort(kvs, CellComparator.getInstance());
148
149    Result r = Result.create(kvs);
150    for (int i = 0; i < 100; ++i) {
151      final byte[] qf = Bytes.toBytes(i);
152
153      List<Cell> ks = r.getColumnCells(family, qf);
154      assertEquals(2, ks.size());
155      assertTrue(CellUtil.matchingQualifier(ks.get(0), qf));
156      assertEquals(200, ks.get(0).getTimestamp());
157      assertEquals(ks.get(0), r.getColumnLatestCell(family, qf));
158    }
159  }
160
161  @Test
162  public void testBasicGetValue() throws Exception {
163    KeyValue[] kvs = genKVs(row, family, value, 1, 100);
164
165    Arrays.sort(kvs, CellComparator.getInstance());
166
167    Result r = Result.create(kvs);
168
169    for (int i = 0; i < 100; ++i) {
170      final byte[] qf = Bytes.toBytes(i);
171
172      assertArrayEquals(Bytes.add(value, Bytes.toBytes(i)), r.getValue(family, qf));
173      assertTrue(r.containsColumn(family, qf));
174    }
175  }
176
177  @Test
178  public void testMultiVersionGetValue() throws Exception {
179    KeyValue[] kvs1 = genKVs(row, family, value, 1, 100);
180    KeyValue[] kvs2 = genKVs(row, family, value, 200, 100);
181
182    KeyValue[] kvs = new KeyValue[kvs1.length + kvs2.length];
183    System.arraycopy(kvs1, 0, kvs, 0, kvs1.length);
184    System.arraycopy(kvs2, 0, kvs, kvs1.length, kvs2.length);
185
186    Arrays.sort(kvs, CellComparator.getInstance());
187
188    Result r = Result.create(kvs);
189    for (int i = 0; i < 100; ++i) {
190      final byte[] qf = Bytes.toBytes(i);
191
192      assertArrayEquals(Bytes.add(value, Bytes.toBytes(i)), r.getValue(family, qf));
193      assertTrue(r.containsColumn(family, qf));
194    }
195  }
196
197  @Test
198  public void testBasicLoadValue() throws Exception {
199    KeyValue[] kvs = genKVs(row, family, value, 1, 100);
200
201    Arrays.sort(kvs, CellComparator.getInstance());
202
203    Result r = Result.create(kvs);
204    ByteBuffer loadValueBuffer = ByteBuffer.allocate(1024);
205
206    for (int i = 0; i < 100; ++i) {
207      final byte[] qf = Bytes.toBytes(i);
208
209      loadValueBuffer.clear();
210      r.loadValue(family, qf, loadValueBuffer);
211      loadValueBuffer.flip();
212      assertEquals(loadValueBuffer, ByteBuffer.wrap(Bytes.add(value, Bytes.toBytes(i))));
213      assertEquals(ByteBuffer.wrap(Bytes.add(value, Bytes.toBytes(i))),
214        r.getValueAsByteBuffer(family, qf));
215    }
216  }
217
218  @Test
219  public void testMultiVersionLoadValue() throws Exception {
220    KeyValue[] kvs1 = genKVs(row, family, value, 1, 100);
221    KeyValue[] kvs2 = genKVs(row, family, value, 200, 100);
222
223    KeyValue[] kvs = new KeyValue[kvs1.length + kvs2.length];
224    System.arraycopy(kvs1, 0, kvs, 0, kvs1.length);
225    System.arraycopy(kvs2, 0, kvs, kvs1.length, kvs2.length);
226
227    Arrays.sort(kvs, CellComparator.getInstance());
228
229    ByteBuffer loadValueBuffer = ByteBuffer.allocate(1024);
230
231    Result r = Result.create(kvs);
232    for (int i = 0; i < 100; ++i) {
233      final byte[] qf = Bytes.toBytes(i);
234
235      loadValueBuffer.clear();
236      r.loadValue(family, qf, loadValueBuffer);
237      loadValueBuffer.flip();
238      assertEquals(loadValueBuffer, ByteBuffer.wrap(Bytes.add(value, Bytes.toBytes(i))));
239      assertEquals(ByteBuffer.wrap(Bytes.add(value, Bytes.toBytes(i))),
240        r.getValueAsByteBuffer(family, qf));
241    }
242  }
243
244  /**
245   * Verify that Result.compareResults(...) behaves correctly.
246   */
247  @Test
248  public void testCompareResults() throws Exception {
249    byte[] value1 = Bytes.toBytes("value1");
250    byte[] qual = Bytes.toBytes("qual");
251
252    KeyValue kv1 = new KeyValue(row, family, qual, value);
253    KeyValue kv2 = new KeyValue(row, family, qual, value1);
254
255    Result r1 = Result.create(new KeyValue[] { kv1 });
256    Result r2 = Result.create(new KeyValue[] { kv2 });
257    // no exception thrown
258    Result.compareResults(r1, r1);
259    // these are different (HBASE-4800)
260    Exception x = assertThrows(Exception.class, () -> Result.compareResults(r1, r2));
261    assertThat(x.getMessage(), startsWith("This result was different:"));
262  }
263
264  @Test
265  public void testCompareResultsWithTags() throws Exception {
266    Tag t1 = new ArrayBackedTag((byte) 1, Bytes.toBytes("TAG1"));
267    Tag t2 = new ArrayBackedTag((byte) 2, Bytes.toBytes("TAG2"));
268    // Both BB backed tags KV are null
269    Result result1 = getByteBufferBackedTagResult(null);
270    Result result2 = getByteBufferBackedTagResult(null);
271    Result.compareResults(result1, result2);
272
273    // Test both byte buffer backed tags KeyValue
274    result1 = getByteBufferBackedTagResult(t1);
275    result2 = getByteBufferBackedTagResult(t1);
276    Result.compareResults(result1, result2);
277
278    // Both array backed tags KV are null
279    result1 = getArrayBackedTagResult(null);
280    result2 = getArrayBackedTagResult(null);
281    Result.compareResults(result1, result2);
282
283    // Test both array backed tags KeyValue
284    result1 = getArrayBackedTagResult(t1);
285    result2 = getArrayBackedTagResult(t1);
286    Result.compareResults(result1, result2);
287
288    // left instance of byte buffer and right instance of array backed
289    result1 = getByteBufferBackedTagResult(t1);
290    result2 = getArrayBackedTagResult(t1);
291    Result.compareResults(result1, result2);
292
293    // left instance of array backed and right instance of byte buffer backed.
294    result1 = getArrayBackedTagResult(t1);
295    result2 = getByteBufferBackedTagResult(t1);
296    Result.compareResults(result1, result2);
297
298    // Left BB backed null tag and right BB backed non null tag
299    result1 = getByteBufferBackedTagResult(null);
300    result2 = getByteBufferBackedTagResult(t2);
301    try {
302      Result.compareResults(result1, result2);
303      fail();
304    } catch (Exception e) {
305      // Expected
306    }
307
308    // Left BB backed non null tag and right BB backed null tag
309    result1 = getByteBufferBackedTagResult(t1);
310    result2 = getByteBufferBackedTagResult(null);
311    try {
312      Result.compareResults(result1, result2);
313      fail();
314    } catch (Exception e) {
315      // Expected
316    }
317
318    // Both byte buffer backed tags KV are different
319    result1 = getByteBufferBackedTagResult(t1);
320    result2 = getByteBufferBackedTagResult(t2);
321    try {
322      Result.compareResults(result1, result2);
323      fail();
324    } catch (Exception e) {
325      // Expected
326    }
327
328    // Left array backed non null tag and right array backed null tag
329    result1 = getArrayBackedTagResult(t1);
330    result2 = getArrayBackedTagResult(null);
331    try {
332      Result.compareResults(result1, result2);
333      fail();
334    } catch (Exception e) {
335      // Expected
336    }
337
338    // Left array backed null tag and right array backed non null tag
339    result1 = getByteBufferBackedTagResult(null);
340    result2 = getByteBufferBackedTagResult(t2);
341    try {
342      Result.compareResults(result1, result2);
343      fail();
344    } catch (Exception e) {
345      // Expected
346    }
347
348    // Both array backed tags KV are different
349    result1 = getArrayBackedTagResult(t1);
350    result2 = getArrayBackedTagResult(t2);
351    try {
352      Result.compareResults(result1, result2);
353      fail();
354    } catch (Exception e) {
355      // Expected
356    }
357
358    // left instance of byte buffer and right instance of array backed are different
359    result1 = getByteBufferBackedTagResult(t1);
360    result2 = getArrayBackedTagResult(t2);
361    try {
362      Result.compareResults(result1, result2);
363      fail();
364    } catch (Exception e) {
365      // Expected
366    }
367
368    // left instance of array backed and right instance of byte buffer backed are different
369    result1 = getArrayBackedTagResult(t1);
370    result2 = getByteBufferBackedTagResult(t2);
371    try {
372      Result.compareResults(result1, result2);
373      fail();
374    } catch (Exception e) {
375      // Expected
376    }
377  }
378
379  @Test
380  public void testCompareResultMemoryUsage() {
381    List<Cell> cells1 = new ArrayList<>();
382    for (long i = 0; i < 100; i++) {
383      cells1.add(new KeyValue(row, family, Bytes.toBytes(i), value));
384    }
385
386    List<Cell> cells2 = new ArrayList<>();
387    for (long i = 0; i < 100; i++) {
388      cells2.add(new KeyValue(row, family, Bytes.toBytes(i), Bytes.toBytes(i)));
389    }
390
391    Result r1 = Result.create(cells1);
392    Result r2 = Result.create(cells2);
393    try {
394      Result.compareResults(r1, r2);
395      fail();
396    } catch (Exception x) {
397      assertTrue(x.getMessage().startsWith("This result was different:"));
398      assertThat(x.getMessage().length(), greaterThan(100));
399    }
400
401    try {
402      Result.compareResults(r1, r2, false);
403      fail();
404    } catch (Exception x) {
405      assertEquals("This result was different: row=row", x.getMessage());
406      assertThat(x.getMessage().length(), lessThan(100));
407    }
408  }
409
410  private Result getArrayBackedTagResult(Tag tag) {
411    List<Tag> tags = null;
412    if (tag != null) {
413      tags = Arrays.asList(tag);
414    }
415    KeyValue kvCell = new KeyValue(row, family, qual, 0L, KeyValue.Type.Put, value, tags);
416    return Result.create(new Cell[] { kvCell });
417  }
418
419  private Result getByteBufferBackedTagResult(Tag tag) {
420    List<Tag> tags = null;
421    if (tag != null) {
422      tags = Arrays.asList(tag);
423    }
424    KeyValue kvCell = new KeyValue(row, family, qual, 0L, KeyValue.Type.Put, value, tags);
425    ByteBuffer buf = ByteBuffer.allocateDirect(kvCell.getBuffer().length);
426    ByteBufferUtils.copyFromArrayToBuffer(buf, kvCell.getBuffer(), 0, kvCell.getBuffer().length);
427    ByteBufferKeyValue bbKV = new ByteBufferKeyValue(buf, 0, buf.capacity(), 0L);
428    return Result.create(new Cell[] { bbKV });
429  }
430
431  /**
432   * Verifies that one can't modify instance of EMPTY_RESULT.
433   */
434  @Test
435  public void testEmptyResultIsReadonly() {
436    Result emptyResult = Result.EMPTY_RESULT;
437    Result otherResult = new Result();
438
439    try {
440      emptyResult.copyFrom(otherResult);
441      fail("UnsupportedOperationException should have been thrown!");
442    } catch (UnsupportedOperationException ex) {
443      LOG.debug("As expected: " + ex.getMessage());
444    }
445    try {
446      emptyResult.setExists(true);
447      fail("UnsupportedOperationException should have been thrown!");
448    } catch (UnsupportedOperationException ex) {
449      LOG.debug("As expected: " + ex.getMessage());
450    }
451  }
452
453  /**
454   * Microbenchmark that compares {@link Result#getValue} and {@link Result#loadValue} performance.
455   */
456  public void doReadBenchmark() throws Exception {
457    final int n = 5;
458    final int m = 100000000;
459
460    StringBuilder valueSB = new StringBuilder();
461    for (int i = 0; i < 100; i++) {
462      valueSB.append((byte) (Math.random() * 10));
463    }
464
465    StringBuilder rowSB = new StringBuilder();
466    for (int i = 0; i < 50; i++) {
467      rowSB.append((byte) (Math.random() * 10));
468    }
469
470    KeyValue[] kvs =
471      genKVs(Bytes.toBytes(rowSB.toString()), family, Bytes.toBytes(valueSB.toString()), 1, n);
472    Arrays.sort(kvs, CellComparator.getInstance());
473    ByteBuffer loadValueBuffer = ByteBuffer.allocate(1024);
474    Result r = Result.create(kvs);
475
476    byte[][] qfs = new byte[n][Bytes.SIZEOF_INT];
477    for (int i = 0; i < n; ++i) {
478      System.arraycopy(qfs[i], 0, Bytes.toBytes(i), 0, Bytes.SIZEOF_INT);
479    }
480
481    // warm up
482    for (int k = 0; k < 100000; k++) {
483      for (int i = 0; i < n; ++i) {
484        r.getValue(family, qfs[i]);
485        loadValueBuffer.clear();
486        r.loadValue(family, qfs[i], loadValueBuffer);
487        loadValueBuffer.flip();
488      }
489    }
490
491    System.gc();
492    long start = System.nanoTime();
493    for (int k = 0; k < m; k++) {
494      for (int i = 0; i < n; ++i) {
495        loadValueBuffer.clear();
496        r.loadValue(family, qfs[i], loadValueBuffer);
497        loadValueBuffer.flip();
498      }
499    }
500    long stop = System.nanoTime();
501    LOG.info("loadValue(): " + (stop - start));
502
503    System.gc();
504    start = System.nanoTime();
505    for (int k = 0; k < m; k++) {
506      for (int i = 0; i < n; i++) {
507        r.getValue(family, qfs[i]);
508      }
509    }
510    stop = System.nanoTime();
511    LOG.info("getValue():  " + (stop - start));
512  }
513
514  @Test
515  public void testCreateResultWithCellArray() {
516    Cell[] cells = genKVs(row, family, value, EnvironmentEdgeManager.currentTime(), 5);
517    Result r = Result.create(cells);
518    // the cells is actually a KeyValue[], which can be cast to ExtendedCell[] directly, so we
519    // should get the same one without copying
520    assertSame(cells, r.rawCells());
521
522    Cell[] emptyCells = new Cell[0];
523    Result emptyResult = Result.create(emptyCells);
524    // emptyCells is a Cell[] instead of ExtendedCell[], so we need to copy it to a new array
525    assertNotSame(emptyCells, emptyResult.rawCells());
526  }
527
528  /**
529   * Calls non-functional test methods.
530   */
531  public static void main(String[] args) {
532    TestResult testResult = new TestResult();
533    try {
534      testResult.doReadBenchmark();
535    } catch (Exception e) {
536      LOG.error("Unexpected exception", e);
537    }
538  }
539}