// Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
package org.rocksdb;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.rocksdb.util.TestUtil;

public class MultiGetTest {
  @ClassRule
  public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE =
      new RocksNativeLibraryResource();

  @Rule public TemporaryFolder dbFolder = new TemporaryFolder();

  @Test
  public void putNThenMultiGet() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      db.put("key1".getBytes(), "value1ForKey1".getBytes());
      db.put("key2".getBytes(), "value2ForKey2".getBytes());
      db.put("key3".getBytes(), "value3ForKey3".getBytes());
      final List<byte[]> keys =
          Arrays.asList("key1".getBytes(), "key2".getBytes(), "key3".getBytes());
      final List<byte[]> values = db.multiGetAsList(keys);
      assertThat(values.size()).isEqualTo(keys.size());
      assertThat(values.get(0)).isEqualTo("value1ForKey1".getBytes());
      assertThat(values.get(1)).isEqualTo("value2ForKey2".getBytes());
      assertThat(values.get(2)).isEqualTo("value3ForKey3".getBytes());
    }
  }

  @Test
  public void putNThenMultiGetDirect() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      db.put("key1".getBytes(), "value1ForKey1".getBytes());
      db.put("key2".getBytes(), "value2ForKey2".getBytes());
      db.put("key3".getBytes(), "value3ForKey3".getBytes());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }
      final List<ByteBuffer> values = new ArrayList<>();
      for (int i = 0; i < keys.size(); i++) {
        values.add(ByteBuffer.allocateDirect(24));
      }

      {
        final List<ByteBufferGetStatus> results = db.multiGetByteBuffers(keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);
        assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);
        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value1ForKey1".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("value2ForKey2".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value3ForKey3".getBytes());
      }

      {
        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(new ReadOptions(), keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);
        assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);
        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value1ForKey1".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("value2ForKey2".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value3ForKey3".getBytes());
      }
    }
  }

  @Test
  public void putNThenMultiGetDirectSliced() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      db.put("key1".getBytes(), "value1ForKey1".getBytes());
      db.put("key2".getBytes(), "value2ForKey2".getBytes());
      db.put("key3".getBytes(), "value3ForKey3".getBytes());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      keys.add(
          ByteBuffer.allocateDirect(12).put("prefix1".getBytes()).slice().put("key1".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }
      final List<ByteBuffer> values = new ArrayList<>();
      for (int i = 0; i < keys.size(); i++) {
        values.add(ByteBuffer.allocateDirect(24));
      }

      {
        final List<ByteBufferGetStatus> results = db.multiGetByteBuffers(keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);
        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);
        assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value2ForKey2".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("value3ForKey3".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value1ForKey1".getBytes());
      }
    }
  }

  @Test
  public void putNThenMultiGetDirectBadValuesArray() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      db.put("key1".getBytes(), "value1ForKey1".getBytes());
      db.put("key2".getBytes(), "value2ForKey2".getBytes());
      db.put("key3".getBytes(), "value3ForKey3".getBytes());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }

      {
        final List<ByteBuffer> values = new ArrayList<>();
        for (int i = 0; i < keys.size(); i++) {
          values.add(ByteBuffer.allocateDirect(24));
        }

        values.remove(0);

        try {
          db.multiGetByteBuffers(keys, values);
          fail("Expected exception when not enough value ByteBuffers supplied");
        } catch (final IllegalArgumentException e) {
          assertThat(e.getMessage()).contains("For each key there must be a corresponding value");
        }
      }

      {
        final List<ByteBuffer> values = new ArrayList<>();
        for (int i = 0; i < keys.size(); i++) {
          values.add(ByteBuffer.allocateDirect(24));
        }

        values.add(ByteBuffer.allocateDirect(24));

        try {
          db.multiGetByteBuffers(keys, values);
          fail("Expected exception when too many value ByteBuffers supplied");
        } catch (final IllegalArgumentException e) {
          assertThat(e.getMessage()).contains("For each key there must be a corresponding value");
        }
      }
    }
  }

  @Test
  public void putNThenMultiGetDirectShortValueBuffers() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      db.put("key1".getBytes(), "value1ForKey1".getBytes());
      db.put("key2".getBytes(), "value2ForKey2".getBytes());
      db.put("key3".getBytes(), "value3ForKey3".getBytes());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }

      {
        final List<ByteBuffer> values = new ArrayList<>();
        for (int i = 0; i < keys.size(); i++) {
          values.add(ByteBuffer.allocateDirect(4));
        }

        final List<ByteBufferGetStatus> statii = db.multiGetByteBuffers(keys, values);
        assertThat(statii.size()).isEqualTo(values.size());
        for (final ByteBufferGetStatus status : statii) {
          assertThat(status.status.getCode()).isEqualTo(Status.Code.Ok);
          assertThat(status.requiredSize).isEqualTo("value3ForKey3".getBytes().length);
          final ByteBuffer expected =
              ByteBuffer.allocateDirect(24).put(Arrays.copyOf("valueX".getBytes(), 4));
          expected.flip();
          assertThat(status.value).isEqualTo(expected);
        }
      }
    }
  }

  @Test
  public void putNThenMultiGetDirectNondefaultCF() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>(0);
      cfDescriptors.add(new ColumnFamilyDescriptor("cf0".getBytes()));
      cfDescriptors.add(new ColumnFamilyDescriptor("cf1".getBytes()));
      cfDescriptors.add(new ColumnFamilyDescriptor("cf2".getBytes()));

      final List<ColumnFamilyHandle> cf = db.createColumnFamilies(cfDescriptors);

      db.put(cf.get(0), "key1".getBytes(), "value1ForKey1".getBytes());
      db.put(cf.get(0), "key2".getBytes(), "value2ForKey2".getBytes());
      db.put(cf.get(0), "key3".getBytes(), "value3ForKey3".getBytes());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }
      final List<ByteBuffer> values = new ArrayList<>();
      for (int i = 0; i < keys.size(); i++) {
        values.add(ByteBuffer.allocateDirect(24));
      }

      {
        final List<ByteBufferGetStatus> results = db.multiGetByteBuffers(keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.NotFound);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.NotFound);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.NotFound);
      }

      {
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        columnFamilyHandles.add(cf.get(0));
        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(columnFamilyHandles, keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);
        assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);
        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value1ForKey1".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("value2ForKey2".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value3ForKey3".getBytes());
      }

      {
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        columnFamilyHandles.add(cf.get(0));
        columnFamilyHandles.add(cf.get(0));
        columnFamilyHandles.add(cf.get(0));
        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(columnFamilyHandles, keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);
        assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);
        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value1ForKey1".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("value2ForKey2".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value3ForKey3".getBytes());
      }
    }
  }

  @Test
  public void putNThenMultiGetDirectCFParams() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      db.put("key1".getBytes(), "value1ForKey1".getBytes());
      db.put("key2".getBytes(), "value2ForKey2".getBytes());
      db.put("key3".getBytes(), "value3ForKey3".getBytes());

      final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
      columnFamilyHandles.add(db.getDefaultColumnFamily());
      columnFamilyHandles.add(db.getDefaultColumnFamily());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }
      final List<ByteBuffer> values = new ArrayList<>();
      for (int i = 0; i < keys.size(); i++) {
        values.add(ByteBuffer.allocateDirect(24));
      }
      try {
        db.multiGetByteBuffers(columnFamilyHandles, keys, values);
        fail("Expected exception when 2 column families supplied");
      } catch (final IllegalArgumentException e) {
        assertThat(e.getMessage()).contains("Wrong number of ColumnFamilyHandle(s) supplied");
      }

      columnFamilyHandles.clear();
      columnFamilyHandles.add(db.getDefaultColumnFamily());
      final List<ByteBufferGetStatus> results =
          db.multiGetByteBuffers(columnFamilyHandles, keys, values);

      assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
      assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
      assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

      assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);
      assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);
      assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

      assertThat(TestUtil.bufferBytes(results.get(0).value)).isEqualTo("value1ForKey1".getBytes());
      assertThat(TestUtil.bufferBytes(results.get(1).value)).isEqualTo("value2ForKey2".getBytes());
      assertThat(TestUtil.bufferBytes(results.get(2).value)).isEqualTo("value3ForKey3".getBytes());
    }
  }

  @Test
  public void putNThenMultiGetDirectMixedCF() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
      cfDescriptors.add(new ColumnFamilyDescriptor("cf0".getBytes()));
      cfDescriptors.add(new ColumnFamilyDescriptor("cf1".getBytes()));
      cfDescriptors.add(new ColumnFamilyDescriptor("cf2".getBytes()));
      cfDescriptors.add(new ColumnFamilyDescriptor("cf3".getBytes()));

      final List<ColumnFamilyHandle> cf = db.createColumnFamilies(cfDescriptors);

      db.put(cf.get(1), "key1".getBytes(), "value1ForKey1".getBytes());
      db.put("key2".getBytes(), "value2ForKey2".getBytes());
      db.put(cf.get(3), "key3".getBytes(), "value3ForKey3".getBytes());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }
      final List<ByteBuffer> values = new ArrayList<>();
      for (int i = 0; i < keys.size(); i++) {
        values.add(ByteBuffer.allocateDirect(24));
      }

      {
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        columnFamilyHandles.add(db.getDefaultColumnFamily());

        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(columnFamilyHandles, keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.NotFound);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.NotFound);

        assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("value2ForKey2".getBytes());
      }

      {
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        columnFamilyHandles.add(cf.get(1));

        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(columnFamilyHandles, keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.NotFound);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.NotFound);

        assertThat(results.get(0).requiredSize).isEqualTo("value2ForKey2".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value1ForKey1".getBytes());
      }

      {
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        columnFamilyHandles.add(cf.get(1));
        columnFamilyHandles.add(db.getDefaultColumnFamily());
        columnFamilyHandles.add(cf.get(3));

        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(columnFamilyHandles, keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);
        assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length);
        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value1ForKey1".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("value2ForKey2".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value3ForKey3".getBytes());
      }

      {
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        columnFamilyHandles.add(db.getDefaultColumnFamily());
        columnFamilyHandles.add(cf.get(1));
        columnFamilyHandles.add(cf.get(3));

        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(columnFamilyHandles, keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.NotFound);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.NotFound);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value3ForKey3".getBytes());
      }
    }
  }

  @Test
  public void putNThenMultiGetDirectTruncateCF() throws RocksDBException {
    try (final Options opt = new Options().setCreateIfMissing(true);
         final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) {
      final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
      cfDescriptors.add(new ColumnFamilyDescriptor("cf0".getBytes()));

      final List<ColumnFamilyHandle> cf = db.createColumnFamilies(cfDescriptors);

      db.put(cf.get(0), "key1".getBytes(), "value1ForKey1".getBytes());
      db.put(cf.get(0), "key2".getBytes(), "value2ForKey2WithLotsOfTrailingGarbage".getBytes());
      db.put(cf.get(0), "key3".getBytes(), "value3ForKey3".getBytes());

      final List<ByteBuffer> keys = new ArrayList<>();
      keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes()));
      keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes()));
      // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\
      for (final ByteBuffer key : keys) {
        key.flip();
      }
      final List<ByteBuffer> values = new ArrayList<>();
      for (int i = 0; i < keys.size(); i++) {
        values.add(ByteBuffer.allocateDirect(24));
      }

      {
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        columnFamilyHandles.add(cf.get(0));
        final List<ByteBufferGetStatus> results =
            db.multiGetByteBuffers(columnFamilyHandles, keys, values);

        assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok);
        assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok);

        assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length);
        assertThat(results.get(1).requiredSize)
            .isEqualTo("value2ForKey2WithLotsOfTrailingGarbage".getBytes().length);
        assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length);

        assertThat(TestUtil.bufferBytes(results.get(0).value))
            .isEqualTo("value1ForKey1".getBytes());
        assertThat(TestUtil.bufferBytes(results.get(1).value))
            .isEqualTo("valu e2Fo rKey 2Wit hLot sOfT".replace(" ", "").getBytes());
        assertThat(TestUtil.bufferBytes(results.get(2).value))
            .isEqualTo("value3ForKey3".getBytes());
      }
    }
  }
}
