accumulo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From els...@apache.org
Subject accumulo git commit: ACCUMULO-4376 add KeyBuilder
Date Thu, 08 Sep 2016 02:23:48 GMT
Repository: accumulo
Updated Branches:
  refs/heads/master f41181190 -> 1a663143c


ACCUMULO-4376 add KeyBuilder

Closes apache/accumulo#145

Signed-off-by: Josh Elser <elserj@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo
Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/1a663143
Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/1a663143
Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/1a663143

Branch: refs/heads/master
Commit: 1a663143c4d81367f7703bceb8b24374d59c154e
Parents: f411811
Author: Mario Pastorelli <mario.pastorelli@teralytics.ch>
Authored: Wed Sep 7 18:55:03 2016 -0400
Committer: Josh Elser <elserj@apache.org>
Committed: Wed Sep 7 22:22:08 2016 -0400

----------------------------------------------------------------------
 .../java/org/apache/accumulo/core/data/Key.java |  64 ++-
 .../apache/accumulo/core/data/KeyBuilder.java   | 439 +++++++++++++++++++
 .../accumulo/core/data/KeyBuilderTest.java      | 292 ++++++++++++
 3 files changed, 794 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/accumulo/blob/1a663143/core/src/main/java/org/apache/accumulo/core/data/Key.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/data/Key.java b/core/src/main/java/org/apache/accumulo/core/data/Key.java
index 66ad5ca..2b61a72 100644
--- a/core/src/main/java/org/apache/accumulo/core/data/Key.java
+++ b/core/src/main/java/org/apache/accumulo/core/data/Key.java
@@ -51,6 +51,28 @@ public class Key implements WritableComparable<Key>, Cloneable {
   protected long timestamp;
   protected boolean deleted;
 
+  /**
+   * Create a {@link Key} builder.
+   *
+   * @since 2.0
+   * @param copyBytes
+   *          if the bytes of the {@link Key} components should be copied
+   * @return the builder at the {@link KeyBuilder.RowStep}
+   */
+  public static KeyBuilder.RowStep builder(boolean copyBytes) {
+    return new KeyBuilder.KeyBuilderImpl(copyBytes);
+  }
+
+  /**
+   * Create a {@link Key} builder. Copy bytes defaults to true.
+   *
+   * @since 2.0
+   * @return the builder at the {@link KeyBuilder.RowStep}
+   */
+  public static KeyBuilder.RowStep builder() {
+    return new KeyBuilder.KeyBuilderImpl(true);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (o instanceof Key)
@@ -60,7 +82,7 @@ public class Key implements WritableComparable<Key>, Cloneable {
 
   private static final byte EMPTY_BYTES[] = new byte[0];
 
-  private byte[] copyIfNeeded(byte ba[], int off, int len, boolean copyData) {
+  static byte[] copyIfNeeded(byte ba[], int off, int len, boolean copyData) {
     if (len == 0)
       return EMPTY_BYTES;
 
@@ -189,6 +211,46 @@ public class Key implements WritableComparable<Key>, Cloneable
{
    * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv,
long ts, boolean deleted, boolean copy)} instead.
    *
    * @param row
+   *          bytes containing row ID
+   * @param rOff
+   *          offset into row where key's row ID begins (inclusive)
+   * @param rLen
+   *          length of row ID in row
+   * @param cf
+   *          bytes containing column family
+   * @param cfOff
+   *          offset into cf where key's column family begins (inclusive)
+   * @param cfLen
+   *          length of column family in cf
+   * @param cq
+   *          bytes containing column qualifier
+   * @param cqOff
+   *          offset into cq where key's column qualifier begins (inclusive)
+   * @param cqLen
+   *          length of column qualifier in cq
+   * @param cv
+   *          bytes containing column visibility
+   * @param cvOff
+   *          offset into cv where key's column visibility begins (inclusive)
+   * @param cvLen
+   *          length of column visibility in cv
+   * @param ts
+   *          timestamp
+   * @param deleted
+   *          delete marker
+   * @param copy
+   *          if true, forces copy of byte array values into key
+   */
+  Key(byte row[], int rOff, int rLen, byte cf[], int cfOff, int cfLen, byte cq[], int cqOff,
int cqLen, byte cv[], int cvOff, int cvLen, long ts,
+      boolean deleted, boolean copy) {
+    init(row, rOff, rLen, cf, cfOff, cfLen, cq, cqOff, cqLen, cv, cvOff, cvLen, ts, deleted,
copy);
+  }
+
+  /**
+   * Creates a key. The delete marker defaults to false. This constructor creates a copy
of each specified array. If you don't want to create a copy of the
+   * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv,
long ts, boolean deleted, boolean copy)} instead.
+   *
+   * @param row
    *          row ID
    * @param colFamily
    *          column family

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1a663143/core/src/main/java/org/apache/accumulo/core/data/KeyBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/data/KeyBuilder.java b/core/src/main/java/org/apache/accumulo/core/data/KeyBuilder.java
new file mode 100644
index 0000000..870a584
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/data/KeyBuilder.java
@@ -0,0 +1,439 @@
+/*
+ * 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.accumulo.core.data;
+
+import org.apache.accumulo.core.security.ColumnVisibility;
+import org.apache.hadoop.io.Text;
+
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * A builder used to build {@link Key}s by defining their components.
+ *
+ * The rules are:
+ * <ul>
+ * <li>All components of the {@link Key} are optional except the row</li>
+ * <li>Components not explicitly set default to empty byte array except the timestamp
which defaults to <code>Long.MAX_VALUE</code></li>
+ * <li>The column qualifier can only be set if the column family has been set first</li>
+ * <li>The column visibility can only be set if at least the column family has been
set first</li>
+ * </ul>
+ *
+ * The builder supports three types of components: <code>byte[]</code>, <code>Text</code>
and <code>CharSequence</code>. <code>CharSequence</code>s must be
+ * UTF-8 encoded.
+ *
+ * The builder is mutable and not thread safe.
+ *
+ * @see org.apache.accumulo.core.data.Key
+ * @since 2.0
+ */
+public class KeyBuilder {
+
+  /**
+   * Base Builder interface which can be used to set the {@link Key} timestamp and delete
marker and to build the {@link Key}.
+   *
+   * @since 2.0
+   */
+  public interface Build {
+
+    /**
+     * Build a {@link Key} from this builder.
+     *
+     * @return the {@link Key} built from this builder
+     */
+    Key build();
+
+    /**
+     * Change the timestamp of the {@link Key} created.
+     *
+     * @param timestamp
+     *          the timestamp to use for the {@link Key}
+     * @return this builder
+     */
+    Build timestamp(long timestamp);
+
+    /**
+     * Set the deleted marker of the {@link Key} to the parameter.
+     *
+     * @param deleted
+     *          if the {@link Key} should be marked as deleted or not
+     * @return this builder
+     */
+    Build deleted(boolean deleted);
+  }
+
+  /**
+   * Builder step used to set the row part of the {@link Key}.
+   *
+   * @since 2.0
+   */
+  public interface RowStep extends Build {
+
+    /**
+     * Set the row of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param row
+     *          the row to use for the key
+     * @return this builder
+     */
+    ColumnFamilyStep row(final Text row);
+
+    /**
+     * Set the row of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param row
+     *          the row to use for the key
+     * @return this builder
+     */
+    ColumnFamilyStep row(final byte[] row);
+
+    /**
+     * Set the row of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param row
+     *          the row to use for the key
+     * @param offset
+     *          the offset within the array of the first byte to be read; must be non-negative
and no larger than row.length
+     * @param length
+     *          the number of bytes to be read from the given array; must be non-negative
and no larger than row.length - offset
+     * @return this builder
+     */
+    ColumnFamilyStep row(final byte[] row, int offset, int length);
+
+    /**
+     * Set the row of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param row
+     *          the row to use for the key. The encoding must be UTF-8
+     * @return this builder
+     */
+    ColumnFamilyStep row(final CharSequence row);
+  }
+
+  /**
+   * Builder step used to set the columnFamily part of the {@link Key}.
+   *
+   * @since 2.0
+   */
+  public interface ColumnFamilyStep extends ColumnVisibilityStep {
+
+    /**
+     * Set the column family of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnFamily
+     *          the column family to use for the {@link Key}
+     * @return this builder
+     */
+    ColumnQualifierStep family(final byte[] columnFamily);
+
+    /**
+     * Set the column family of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnFamily
+     *          the column family to use for the {@link Key}
+     * @param offset
+     *          the offset within the array of the first byte to be read; must be non-negative
and no larger than row.length
+     * @param length
+     *          the number of bytes to be read from the given array; must be non-negative
and no larger than row.length - offset
+     * @return this builder
+     */
+    ColumnQualifierStep family(final byte[] columnFamily, int offset, int length);
+
+    /**
+     * Set the column family of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnFamily
+     *          the column family to use for the {@link Key}
+     * @return this builder
+     */
+    ColumnQualifierStep family(final Text columnFamily);
+
+    /**
+     * Set the column family of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnFamily
+     *          the column family to use for the {@link Key}. The encoding must be UTF-8
+     * @return this builder
+     */
+    ColumnQualifierStep family(final CharSequence columnFamily);
+  }
+
+  /**
+   * Builder step used to set the column qualifier part of the {@link Key}.
+   *
+   * @since 2.0
+   */
+  public interface ColumnQualifierStep extends ColumnVisibilityStep {
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnQualifier
+     *          the column qualifier to use for the {@link Key}
+     * @return this builder
+     */
+    ColumnVisibilityStep qualifier(final byte[] columnQualifier);
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnQualifier
+     *          the column qualifier to use for the {@link Key}
+     * @param offset
+     *          the offset within the array of the first byte to be read; must be non-negative
and no larger than row.length
+     * @param length
+     *          the number of bytes to be read from the given array; must be non-negative
and no larger than row.length - offset
+     * @return this builder
+     */
+    ColumnVisibilityStep qualifier(final byte[] columnQualifier, int offset, int length);
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnQualifier
+     *          the column qualifier to use for the {@link Key}
+     * @return this builder
+     */
+    ColumnVisibilityStep qualifier(final Text columnQualifier);
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnQualifier
+     *          the column qualifier to use for the {@link Key}. The encoding must be UTF-8
+     * @return this builder
+     */
+    ColumnVisibilityStep qualifier(final CharSequence columnQualifier);
+  }
+
+  /**
+   * Builder step used to set the column visibility part of the {@link Key}.
+   *
+   * @since 2.0
+   */
+  public interface ColumnVisibilityStep extends Build {
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnVisibility
+     *          the column visibility to use for the {@link Key}
+     * @return this builder
+     */
+    Build visibility(final byte[] columnVisibility);
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnVisibility
+     *          the column visibility to use for the {@link Key}
+     * @param offset
+     *          the offset within the array of the first byte to be read; must be non-negative
and no larger than row.length
+     * @param length
+     *          the number of bytes to be read from the given array; must be non-negative
and no larger than row.length - offset
+     * @return this builder
+     */
+    Build visibility(final byte[] columnVisibility, int offset, int length);
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnVisibility
+     *          the column visibility to use for the {@link Key}
+     * @return this builder
+     */
+    Build visibility(final Text columnVisibility);
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnVisibility
+     *          the column visibility to use for the {@link Key}. The encoding must be UTF-8
+     * @return this builder
+     */
+    Build visibility(final CharSequence columnVisibility);
+
+    /**
+     * Set the column qualifier of the {@link Key} that this builder will build to the parameter.
+     *
+     * @param columnVisibility
+     *          the column visibility to use for the {@link Key}
+     * @return this builder
+     */
+    Build visibility(final ColumnVisibility columnVisibility);
+  }
+
+  /**
+   * @since 2.0
+   */
+  static class KeyBuilderImpl implements RowStep, ColumnFamilyStep, ColumnQualifierStep,
ColumnVisibilityStep {
+
+    protected static final byte EMPTY_BYTES[] = new byte[0];
+
+    private final boolean copyBytes;
+    private byte[] row = EMPTY_BYTES;
+    private int rowOffset = 0;
+    private int rowLength = 0;
+    private byte[] family = EMPTY_BYTES;
+    private int familyOffset = 0;
+    private int familyLength = 0;
+    private byte[] qualifier = EMPTY_BYTES;
+    private int qualifierOffset = 0;
+    private int qualifierLength = 0;
+    private byte[] visibility = EMPTY_BYTES;
+    private int visibilityOffset = 0;
+    private int visibilityLength = 0;
+    private long timestamp = Long.MAX_VALUE;
+    private boolean deleted = false;
+
+    KeyBuilderImpl(boolean copyBytes) {
+      this.copyBytes = copyBytes;
+    }
+
+    private byte[] copyBytesIfNeeded(final byte[] bytes, int offset, int length) {
+      return Key.copyIfNeeded(bytes, offset, length, this.copyBytes);
+    }
+
+    private byte[] encodeCharSequence(CharSequence chars) {
+      CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT);
+      try {
+        return encoder.encode(CharBuffer.wrap(chars)).array();
+      } catch (CharacterCodingException ex) {
+        throw new RuntimeException("KeyBuilder supports only CharSequences encoded in UTF-8",
ex);
+      }
+    }
+
+    @Override
+    public ColumnFamilyStep row(final byte[] row, int offset, int length) {
+      this.row = copyBytesIfNeeded(row, offset, length);
+      this.rowOffset = this.copyBytes ? 0 : offset;
+      this.rowLength = this.copyBytes ? this.row.length : length;
+      return this;
+    }
+
+    @Override
+    public ColumnFamilyStep row(final byte[] row) {
+      return row(row, 0, row.length);
+    }
+
+    @Override
+    public ColumnFamilyStep row(final Text row) {
+      return row(row.getBytes(), 0, row.getLength());
+    }
+
+    @Override
+    public ColumnFamilyStep row(final CharSequence row) {
+      return row(encodeCharSequence(row));
+    }
+
+    @Override
+    public ColumnQualifierStep family(final byte[] family, int offset, int length) {
+      this.family = copyBytesIfNeeded(family, offset, length);
+      this.familyOffset = this.copyBytes ? 0 : offset;
+      this.familyLength = this.copyBytes ? this.family.length : length;
+      return this;
+    }
+
+    @Override
+    public ColumnQualifierStep family(final byte[] family) {
+      return family(family, 0, family.length);
+    }
+
+    @Override
+    public ColumnQualifierStep family(Text family) {
+      return family(family.getBytes(), 0, family.getLength());
+    }
+
+    @Override
+    public ColumnQualifierStep family(CharSequence family) {
+      return family(encodeCharSequence(family));
+    }
+
+    @Override
+    public ColumnVisibilityStep qualifier(final byte[] qualifier, int offset, int length)
{
+      this.qualifier = copyBytesIfNeeded(qualifier, offset, length);
+      this.qualifierOffset = this.copyBytes ? 0 : offset;
+      this.qualifierLength = this.copyBytes ? this.qualifier.length : length;
+      return this;
+    }
+
+    @Override
+    public ColumnVisibilityStep qualifier(final byte[] qualifier) {
+      return qualifier(qualifier, 0, qualifier.length);
+    }
+
+    @Override
+    public ColumnVisibilityStep qualifier(Text qualifier) {
+      return qualifier(qualifier.getBytes(), 0, qualifier.getLength());
+    }
+
+    @Override
+    public ColumnVisibilityStep qualifier(CharSequence qualifier) {
+      return qualifier(encodeCharSequence(qualifier));
+    }
+
+    @Override
+    public Build visibility(final byte[] visibility, int offset, int length) {
+      this.visibility = copyBytesIfNeeded(visibility, offset, length);
+      this.visibilityOffset = this.copyBytes ? 0 : offset;
+      this.visibilityLength = this.copyBytes ? this.visibility.length : length;
+      return this;
+    }
+
+    @Override
+    public Build visibility(final byte[] visibility) {
+      return visibility(visibility, 0, visibility.length);
+    }
+
+    @Override
+    public Build visibility(Text visibility) {
+      return visibility(visibility.getBytes(), 0, visibility.getLength());
+    }
+
+    @Override
+    public Build visibility(CharSequence visibility) {
+      return visibility(encodeCharSequence(visibility));
+    }
+
+    @Override
+    public Build visibility(ColumnVisibility visibility) {
+      byte[] expr = visibility.getExpression();
+      return visibility(expr, 0, expr.length);
+    }
+
+    @Override
+    final public Build timestamp(long timestamp) {
+      this.timestamp = timestamp;
+      return this;
+    }
+
+    @Override
+    public Build deleted(boolean deleted) {
+      this.deleted = deleted;
+      return this;
+    }
+
+    @Override
+    public Key build() {
+      return new Key(this.row, this.rowOffset, this.rowLength, this.family, this.familyOffset,
this.familyLength, this.qualifier, this.qualifierOffset,
+          this.qualifierLength, this.visibility, this.visibilityOffset, this.visibilityLength,
this.timestamp, this.deleted, false);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1a663143/core/src/test/java/org/apache/accumulo/core/data/KeyBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/accumulo/core/data/KeyBuilderTest.java b/core/src/test/java/org/apache/accumulo/core/data/KeyBuilderTest.java
new file mode 100644
index 0000000..5fc4e60
--- /dev/null
+++ b/core/src/test/java/org/apache/accumulo/core/data/KeyBuilderTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.accumulo.core.data;
+
+import org.apache.accumulo.core.security.ColumnVisibility;
+import org.apache.hadoop.io.Text;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertSame;
+
+public class KeyBuilderTest {
+
+  private static final byte EMPTY_BYTES[] = new byte[0];
+  byte[] rowBytes = "row".getBytes(StandardCharsets.UTF_8);
+  byte[] familyBytes = "family".getBytes(StandardCharsets.UTF_8);
+  byte[] qualifierBytes = "qualifier".getBytes(StandardCharsets.UTF_8);
+  byte[] visibilityBytes = "visibility".getBytes(StandardCharsets.UTF_8);
+  Text rowText = new Text(rowBytes);
+  Text familyText = new Text(familyBytes);
+  Text qualifierText = new Text(qualifierBytes);
+  Text visibilityText = new Text(visibilityBytes);
+  ColumnVisibility visibilityVisibility = new ColumnVisibility(visibilityBytes);
+
+  @Test
+  public void testKeyBuildingFromRow() {
+    Key keyBuilt = Key.builder().row("foo").build();
+    Key keyExpected = new Key("foo");
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamily() {
+    Key keyBuilt = Key.builder().row("foo").family("bar").build();
+    Key keyExpected = new Key("foo", "bar");
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifier() {
+    Key keyBuilt = Key.builder().row("foo").family("bar").qualifier("baz").build();
+    Key keyExpected = new Key("foo", "bar", "baz");
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibility() {
+    Key keyBuilt = Key.builder().row("foo").family("bar").qualifier("baz").visibility("v").build();
+    Key keyExpected = new Key("foo", "bar", "baz", "v");
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityTimestamp() {
+    Key keyBuilt = Key.builder().row("foo").family("bar").qualifier("baz").visibility("v").timestamp(1L).build();
+    Key keyExpected = new Key("foo", "bar", "baz", "v", 1L);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityTimestampDeleted() {
+    Key keyBuilt = Key.builder().row("foo").family("bar").qualifier("baz").visibility("v").timestamp(10L).deleted(true).build();
+    Key keyExpected = new Key("foo", "bar", "baz", "v", 10L);
+    keyExpected.setDeleted(true);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowVisibility() {
+    Key keyBuilt = Key.builder().row("foo").visibility("v").build();
+    Key keyExpected = new Key("foo", "", "", "v");
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyVisibility() {
+    Key keyBuilt = Key.builder().row("foo").family("bar").visibility("v").build();
+    Key keyExpected = new Key("foo", "bar", "", "v");
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void textKeyBuildingFromRowTimestamp() {
+    Key keyBuilt = Key.builder().row("foo").timestamp(3L).build();
+    Key keyExpected = new Key("foo");
+    keyExpected.setTimestamp(3L);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).build();
+    Key keyExpected = new Key(rowBytes, EMPTY_BYTES, EMPTY_BYTES, EMPTY_BYTES, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).family(familyBytes).build();
+    Key keyExpected = new Key(rowBytes, familyBytes, EMPTY_BYTES, EMPTY_BYTES, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).family(familyBytes).qualifier(qualifierBytes).build();
+    Key keyExpected = new Key(rowBytes, familyBytes, qualifierBytes, EMPTY_BYTES, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).family(familyBytes).qualifier(qualifierBytes).visibility(visibilityBytes).build();
+    Key keyExpected = new Key(rowBytes, familyBytes, qualifierBytes, visibilityBytes, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityTimestampBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).family(familyBytes).qualifier(qualifierBytes).visibility(visibilityBytes).timestamp(1L).build();
+    Key keyExpected = new Key(rowBytes, familyBytes, qualifierBytes, visibilityBytes, 1L);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityTimestampDeletedBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).family(familyBytes).qualifier(qualifierBytes).visibility(visibilityBytes).timestamp(10L).deleted(true).build();
+    Key keyExpected = new Key(rowBytes, familyBytes, qualifierBytes, visibilityBytes, 10L);
+    keyExpected.setDeleted(true);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowVisibilityBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).visibility(visibilityBytes).build();
+    Key keyExpected = new Key(rowBytes, EMPTY_BYTES, EMPTY_BYTES, visibilityBytes, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyVisibilityBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).family(familyBytes).visibility(visibilityBytes).build();
+    Key keyExpected = new Key(rowBytes, familyBytes, EMPTY_BYTES, visibilityBytes, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void textKeyBuildingFromRowTimestampBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes).timestamp(3L).build();
+    Key keyExpected = new Key(rowBytes, EMPTY_BYTES, EMPTY_BYTES, EMPTY_BYTES, Long.MAX_VALUE);
+    keyExpected.setTimestamp(3L);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowText() {
+    Key keyBuilt = Key.builder().row(rowText).build();
+    Key keyExpected = new Key(rowText);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyText() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyText).build();
+    Key keyExpected = new Key(rowText, familyText);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierText() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyText).qualifier(qualifierText).build();
+    Key keyExpected = new Key(rowText, familyText, qualifierText);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityText() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyText).qualifier(qualifierText).visibility(visibilityText).build();
+    Key keyExpected = new Key(rowText, familyText, qualifierText, visibilityText);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityTimestampText() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyText).qualifier(qualifierText).visibility(visibilityText).timestamp(1L).build();
+    Key keyExpected = new Key(rowText, familyText, qualifierText, visibilityText, 1L);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyQualifierVisibilityTimestampDeletedText() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyText).qualifier(qualifierText).visibility(visibilityText).timestamp(10L).deleted(true).build();
+    Key keyExpected = new Key(rowText, familyText, qualifierText, visibilityText, 10L);
+    keyExpected.setDeleted(true);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowVisibilityText() {
+    Key keyBuilt = Key.builder().row(rowText).visibility(visibilityText).build();
+    Key keyExpected = new Key(rowText, new Text(), new Text(), visibilityText);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyVisibilityText() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyText).visibility(visibilityText).build();
+    Key keyExpected = new Key(rowText, familyText, new Text(), visibilityText);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowFamilyVisibilityVisibility() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyText).visibility(visibilityVisibility).build();
+    Key keyExpected = new Key(rowText, familyText, new Text(), visibilityVisibility, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingFromRowTimestampText() {
+    Key keyBuilt = Key.builder().row(rowText).timestamp(3L).build();
+    Key keyExpected = new Key(rowText);
+    keyExpected.setTimestamp(3L);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingReusingBytes() {
+    byte[] reuse = new byte[] {1, 2, 3};
+    KeyBuilder.Build keyBuilder = Key.builder(false).row(reuse);
+    Key keyBuilt = keyBuilder.build();
+    assertSame(reuse, keyBuilt.getRowBytes());
+  }
+
+  @Test
+  public void testKeyBuildingCopyBytes() {
+    byte[] reuse = new byte[] {1, 2, 3};
+    KeyBuilder.Build keyBuilder = Key.builder(true).row(reuse);
+    Key keyBuilt = keyBuilder.build();
+    assertNotEquals(reuse, keyBuilt.getRowBytes());
+    Key keyBuilt2 = keyBuilder.build();
+    assertNotEquals(reuse, keyBuilt2.getRowBytes());
+  }
+
+  @Test
+  public void testKeyHeterogeneous() {
+    Key keyBuilt = Key.builder().row(rowText).family(familyBytes).qualifier("foo").build();
+    Text fooText = new Text("foo");
+    Key keyExpected = new Key(rowText.getBytes(), 0, rowText.getLength(), familyBytes, 0,
familyBytes.length, fooText.getBytes(), 0, fooText.getLength(),
+        EMPTY_BYTES, 0, 0, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyUsingSubsetOfBytes() {
+    Key keyBuilt = Key.builder().row(rowBytes, 0, rowBytes.length - 1).build();
+    Key keyExpected = new Key(rowBytes, 0, rowBytes.length - 1, EMPTY_BYTES, 0, 0, EMPTY_BYTES,
0, 0, EMPTY_BYTES, 0, 0, Long.MAX_VALUE);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingWithMultipleTimestamps() {
+    Key keyBuilt = Key.builder().row("r").timestamp(44).timestamp(99).build();
+    Key keyExpected = new Key("r", "", "", 99);
+    assertEquals(keyExpected, keyBuilt);
+  }
+
+  @Test
+  public void testKeyBuildingWithMultipleDeleted() {
+    Key keyBuilt = Key.builder().row("r").deleted(true).deleted(false).build();
+    Key keyExpected = new Key("r");
+    keyExpected.setDeleted(false);
+    assertEquals(keyExpected, keyBuilt);
+  }
+}


Mime
View raw message