You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by jd...@apache.org on 2016/07/25 17:15:15 UTC

[10/36] incubator-kudu git commit: [java-client] repackage to org.apache.kudu (Part 1)

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java
new file mode 100644
index 0000000..4915a18
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduPredicate.java
@@ -0,0 +1,628 @@
+// 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.kududb.client;
+
+import com.google.common.base.Preconditions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.kududb.ColumnSchema;
+import org.kududb.Type;
+
+import static org.kududb.client.KuduPredicate.ComparisonOp.EQUAL;
+import static org.kududb.client.KuduPredicate.ComparisonOp.GREATER;
+import static org.kududb.client.KuduPredicate.ComparisonOp.GREATER_EQUAL;
+import static org.kududb.client.KuduPredicate.ComparisonOp.LESS;
+import static org.kududb.client.KuduPredicate.ComparisonOp.LESS_EQUAL;
+import static org.kududb.client.KuduPredicate.PredicateType.RANGE;
+
+public class TestKuduPredicate {
+
+  private static final ColumnSchema boolCol =
+      new ColumnSchema.ColumnSchemaBuilder("bool", Type.BOOL).build();
+
+  private static final ColumnSchema byteCol =
+      new ColumnSchema.ColumnSchemaBuilder("byte", Type.INT8).build();
+
+  private static final ColumnSchema shortCol =
+      new ColumnSchema.ColumnSchemaBuilder("short", Type.INT16).build();
+
+  private static final ColumnSchema intCol =
+      new ColumnSchema.ColumnSchemaBuilder("int", Type.INT32).build();
+
+  private static final ColumnSchema longCol =
+      new ColumnSchema.ColumnSchemaBuilder("long", Type.INT64).build();
+
+  private static final ColumnSchema floatCol =
+      new ColumnSchema.ColumnSchemaBuilder("float", Type.FLOAT).build();
+
+  private static final ColumnSchema doubleCol =
+      new ColumnSchema.ColumnSchemaBuilder("double", Type.DOUBLE).build();
+
+  private static final ColumnSchema stringCol =
+      new ColumnSchema.ColumnSchemaBuilder("string", Type.STRING).build();
+
+  private static final ColumnSchema binaryCol =
+      new ColumnSchema.ColumnSchemaBuilder("binary", Type.BINARY).build();
+
+  private static KuduPredicate intRange(int lower, int upper) {
+    Preconditions.checkArgument(lower < upper);
+    return new KuduPredicate(RANGE, intCol, Bytes.fromInt(lower), Bytes.fromInt(upper));
+  }
+
+  private void testMerge(KuduPredicate a,
+                         KuduPredicate b,
+                         KuduPredicate expected) {
+
+    Assert.assertEquals(expected, a.merge(b));
+    Assert.assertEquals(expected, b.merge(a));
+  }
+
+  /**
+   * Tests merges on all types of integer predicates.
+   */
+  @Test
+  public void testMergeInt() {
+
+    // Equality + Equality
+
+    // |
+    // |
+    // =
+    // |
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0));
+    // |
+    //  |
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 1),
+              KuduPredicate.none(intCol));
+
+    // Range + Equality
+
+    // [-------->
+    //      |
+    // =
+    //      |
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 10),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 10));
+
+    //    [-------->
+    //  |
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 10),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0),
+              KuduPredicate.none(intCol));
+
+    // <--------)
+    //      |
+    // =
+    //      |
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 10),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5));
+
+    // <--------)
+    //            |
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 0),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 10),
+              KuduPredicate.none(intCol));
+
+    // Unbounded Range + Unbounded Range
+
+    // [--------> AND
+    // [-------->
+    // =
+    // [-------->
+
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0));
+
+    // [--------> AND
+    //    [----->
+    // =
+    //    [----->
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5),
+              KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5));
+
+    // <--------) AND
+    // <--------)
+    // =
+    // <--------)
+
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 0),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, 0),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, 0));
+
+    // <--------) AND
+    // <----)
+    // =
+    // <----)
+
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 0),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, -10),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, -10));
+
+    //    [--------> AND
+    // <-------)
+    // =
+    //    [----)
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, 10),
+              intRange(0, 10));
+
+    //     [-----> AND
+    // <----)
+    // =
+    //     |
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, 6),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5));
+
+    //     [-----> AND
+    // <---)
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, 5),
+              KuduPredicate.none(intCol));
+
+    //       [-----> AND
+    // <---)
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, 3),
+              KuduPredicate.none(intCol));
+
+    // Range + Range
+
+    // [--------) AND
+    // [--------)
+    // =
+    // [--------)
+
+    testMerge(intRange(0, 10),
+              intRange(0, 10),
+              intRange(0, 10));
+
+    // [--------) AND
+    // [----)
+    // =
+    // [----)
+    testMerge(intRange(0, 10),
+              intRange(0, 5),
+              intRange(0, 5));
+
+    // [--------) AND
+    //   [----)
+    // =
+    //   [----)
+    testMerge(intRange(0, 10),
+              intRange(3, 8),
+              intRange(3, 8));
+
+    // [-----) AND
+    //   [------)
+    // =
+    //   [---)
+    testMerge(intRange(0, 8),
+              intRange(3, 10),
+              intRange(3, 8));
+    // [--) AND
+    //    [---)
+    // =
+    // None
+    testMerge(intRange(0, 5),
+              intRange(5, 10),
+              KuduPredicate.none(intCol));
+
+    // [--) AND
+    //       [---)
+    // =
+    // None
+    testMerge(intRange(0, 3),
+              intRange(5, 10),
+              KuduPredicate.none(intCol));
+
+    // Lower Bound + Range
+
+    // [------------>
+    //       [---)
+    // =
+    //       [---)
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+              intRange(5, 10),
+              intRange(5, 10));
+
+    // [------------>
+    // [--------)
+    // =
+    // [--------)
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5),
+              intRange(5, 10),
+              intRange(5, 10));
+
+    //      [------------>
+    // [--------)
+    // =
+    //      [---)
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 5),
+              intRange(0, 10),
+              intRange(5, 10));
+
+    //          [------->
+    // [-----)
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 10),
+              intRange(0, 5),
+              KuduPredicate.none(intCol));
+
+    // Upper Bound + Range
+
+    // <------------)
+    //       [---)
+    // =
+    //       [---)
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 10),
+              intRange(3, 8),
+              intRange(3, 8));
+
+    // <------------)
+    //     [--------)
+    // =
+    //     [--------)
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 10),
+              intRange(5, 10),
+              intRange(5, 10));
+
+
+    // <------------)
+    //         [--------)
+    // =
+    //         [----)
+    testMerge(KuduPredicate.newComparisonPredicate(intCol, LESS, 5),
+              intRange(0, 10),
+              intRange(0, 5));
+
+    // Range + Equality
+
+    //   [---) AND
+    // |
+    // =
+    // None
+    testMerge(intRange(3, 5),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 1),
+              KuduPredicate.none(intCol));
+
+    // [---) AND
+    // |
+    // =
+    // |
+    testMerge(intRange(0, 5),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 0));
+
+    // [---) AND
+    //   |
+    // =
+    //   |
+    testMerge(intRange(0, 5),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 3),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 3));
+
+    // [---) AND
+    //     |
+    // =
+    // None
+    testMerge(intRange(0, 5),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5),
+              KuduPredicate.none(intCol));
+
+    // [---) AND
+    //       |
+    // =
+    // None
+    testMerge(intRange(0, 5),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 7),
+              KuduPredicate.none(intCol));
+
+    // None
+
+    // None AND
+    // [---->
+    // =
+    // None
+    testMerge(KuduPredicate.none(intCol),
+              KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 0),
+              KuduPredicate.none(intCol));
+    // None AND
+    // <----)
+    // =
+    // None
+    testMerge(KuduPredicate.none(intCol),
+              KuduPredicate.newComparisonPredicate(intCol, LESS, 0),
+              KuduPredicate.none(intCol));
+
+    // None AND
+    // [----)
+    // =
+    // None
+    testMerge(KuduPredicate.none(intCol),
+              intRange(3, 7),
+              KuduPredicate.none(intCol));
+
+    // None AND
+    //  |
+    // =
+    // None
+    testMerge(KuduPredicate.none(intCol),
+              KuduPredicate.newComparisonPredicate(intCol, EQUAL, 5),
+              KuduPredicate.none(intCol));
+
+    // None AND
+    // None
+    // =
+    // None
+    testMerge(KuduPredicate.none(intCol),
+              KuduPredicate.none(intCol),
+              KuduPredicate.none(intCol));
+  }
+
+  /**
+   * Tests tricky merges on a var length type.
+   */
+  @Test
+  public void testMergeString() {
+
+    //         [----->
+    //  <-----)
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "b\0"),
+              KuduPredicate.newComparisonPredicate(stringCol, LESS, "b"),
+              KuduPredicate.none(stringCol));
+
+    //        [----->
+    //  <-----)
+    // =
+    // None
+    testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "b"),
+              KuduPredicate.newComparisonPredicate(stringCol, LESS, "b"),
+              KuduPredicate.none(stringCol));
+
+    //       [----->
+    //  <----)
+    // =
+    //       |
+    testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "b"),
+              KuduPredicate.newComparisonPredicate(stringCol, LESS, "b\0"),
+              KuduPredicate.newComparisonPredicate(stringCol, EQUAL, "b"));
+
+    //     [----->
+    //  <-----)
+    // =
+    //     [--)
+    testMerge(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "a"),
+              KuduPredicate.newComparisonPredicate(stringCol, LESS, "a\0\0"),
+              new KuduPredicate(RANGE, stringCol,
+                                Bytes.fromString("a"), Bytes.fromString("a\0\0")));
+  }
+
+  @Test
+  public void testBoolean() {
+
+    // b >= false
+    Assert.assertEquals(KuduPredicate.newIsNotNullPredicate(boolCol),
+                        KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false));
+    // b > false
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true),
+                        KuduPredicate.newComparisonPredicate(boolCol, GREATER, false));
+    // b = false
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false),
+                        KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false));
+    // b < false
+    Assert.assertEquals(KuduPredicate.none(boolCol),
+                        KuduPredicate.newComparisonPredicate(boolCol, LESS, false));
+    // b <= false
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false),
+                        KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, false));
+
+    // b >= true
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true),
+                        KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, true));
+    // b > true
+    Assert.assertEquals(KuduPredicate.none(boolCol),
+                        KuduPredicate.newComparisonPredicate(boolCol, GREATER, true));
+    // b = true
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true),
+                        KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true));
+    // b < true
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(boolCol, EQUAL, false),
+                        KuduPredicate.newComparisonPredicate(boolCol, LESS, true));
+    // b <= true
+    Assert.assertEquals(KuduPredicate.newIsNotNullPredicate(boolCol),
+                        KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, true));
+  }
+
+  /**
+   * Tests basic predicate merges across all types.
+   */
+  @Test
+  public void testAllTypesMerge() {
+
+    testMerge(KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false),
+              KuduPredicate.newComparisonPredicate(boolCol, LESS, true),
+              new KuduPredicate(KuduPredicate.PredicateType.EQUALITY,
+                                boolCol,
+                                Bytes.fromBoolean(false),
+                                null));
+
+    testMerge(KuduPredicate.newComparisonPredicate(boolCol, GREATER_EQUAL, false),
+              KuduPredicate.newComparisonPredicate(boolCol, LESS_EQUAL, true),
+              KuduPredicate.newIsNotNullPredicate(boolCol));
+
+    testMerge(KuduPredicate.newComparisonPredicate(byteCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(byteCol, LESS, 10),
+              new KuduPredicate(RANGE,
+                                byteCol,
+                                new byte[] { (byte) 0 },
+                                new byte[] { (byte) 10 }));
+
+    testMerge(KuduPredicate.newComparisonPredicate(shortCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(shortCol, LESS, 10),
+              new KuduPredicate(RANGE,
+                                shortCol,
+                                Bytes.fromShort((short) 0),
+                                Bytes.fromShort((short) 10)));
+
+    testMerge(KuduPredicate.newComparisonPredicate(longCol, GREATER_EQUAL, 0),
+              KuduPredicate.newComparisonPredicate(longCol, LESS, 10),
+              new KuduPredicate(RANGE,
+                                longCol,
+                                Bytes.fromLong(0),
+                                Bytes.fromLong(10)));
+
+    testMerge(KuduPredicate.newComparisonPredicate(floatCol, GREATER_EQUAL, 123.45f),
+              KuduPredicate.newComparisonPredicate(floatCol, LESS, 678.90f),
+              new KuduPredicate(RANGE,
+                                floatCol,
+                                Bytes.fromFloat(123.45f),
+                                Bytes.fromFloat(678.90f)));
+
+    testMerge(KuduPredicate.newComparisonPredicate(doubleCol, GREATER_EQUAL, 123.45),
+              KuduPredicate.newComparisonPredicate(doubleCol, LESS, 678.90),
+              new KuduPredicate(RANGE,
+                                doubleCol,
+                                Bytes.fromDouble(123.45),
+                                Bytes.fromDouble(678.90)));
+
+    testMerge(KuduPredicate.newComparisonPredicate(binaryCol, GREATER_EQUAL,
+                                                   new byte[] { 0, 1, 2, 3, 4, 5, 6 }),
+              KuduPredicate.newComparisonPredicate(binaryCol, LESS, new byte[] { 10 }),
+              new KuduPredicate(RANGE,
+                                binaryCol,
+                                new byte[] { 0, 1, 2, 3, 4, 5, 6 },
+                                new byte[] { 10 }));
+  }
+
+  @Test
+  public void testLessEqual() {
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, LESS_EQUAL, 10),
+                        KuduPredicate.newComparisonPredicate(byteCol, LESS, 11));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, LESS_EQUAL, 10),
+                        KuduPredicate.newComparisonPredicate(shortCol, LESS, 11));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, LESS_EQUAL, 10),
+                        KuduPredicate.newComparisonPredicate(intCol, LESS, 11));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, LESS_EQUAL, 10),
+                        KuduPredicate.newComparisonPredicate(longCol, LESS, 11));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, LESS_EQUAL, 12.345f),
+                        KuduPredicate.newComparisonPredicate(floatCol, LESS, Math.nextAfter(12.345f, Float.POSITIVE_INFINITY)));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, LESS_EQUAL, 12.345),
+                        KuduPredicate.newComparisonPredicate(doubleCol, LESS, Math.nextAfter(12.345, Float.POSITIVE_INFINITY)));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(stringCol, LESS_EQUAL, "a"),
+                        KuduPredicate.newComparisonPredicate(stringCol, LESS, "a\0"));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(binaryCol, LESS_EQUAL, new byte[] { (byte) 10 }),
+                        KuduPredicate.newComparisonPredicate(binaryCol, LESS, new byte[] { (byte) 10, (byte) 0 }));
+
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, LESS_EQUAL, Byte.MAX_VALUE),
+                        KuduPredicate.newIsNotNullPredicate(byteCol));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, LESS_EQUAL, Short.MAX_VALUE),
+                        KuduPredicate.newIsNotNullPredicate(shortCol));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, LESS_EQUAL, Integer.MAX_VALUE),
+                        KuduPredicate.newIsNotNullPredicate(intCol));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, LESS_EQUAL, Long.MAX_VALUE),
+                        KuduPredicate.newIsNotNullPredicate(longCol));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, LESS_EQUAL, Float.MAX_VALUE),
+                        KuduPredicate.newComparisonPredicate(floatCol, LESS, Float.POSITIVE_INFINITY));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, LESS_EQUAL, Float.POSITIVE_INFINITY),
+                        KuduPredicate.newIsNotNullPredicate(floatCol));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, LESS_EQUAL, Double.MAX_VALUE),
+                        KuduPredicate.newComparisonPredicate(doubleCol, LESS, Double.POSITIVE_INFINITY));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, LESS_EQUAL, Double.POSITIVE_INFINITY),
+                        KuduPredicate.newIsNotNullPredicate(doubleCol));
+  }
+
+  @Test
+  public void testGreater() {
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(byteCol, GREATER_EQUAL, 11),
+                        KuduPredicate.newComparisonPredicate(byteCol, GREATER, 10));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(shortCol, GREATER_EQUAL, 11),
+                        KuduPredicate.newComparisonPredicate(shortCol, GREATER, 10));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(intCol, GREATER_EQUAL, 11),
+                        KuduPredicate.newComparisonPredicate(intCol, GREATER, 10));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(longCol, GREATER_EQUAL, 11),
+                        KuduPredicate.newComparisonPredicate(longCol, GREATER, 10));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, GREATER_EQUAL, Math.nextAfter(12.345f, Float.MAX_VALUE)),
+                        KuduPredicate.newComparisonPredicate(floatCol, GREATER, 12.345f));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, GREATER_EQUAL, Math.nextAfter(12.345, Float.MAX_VALUE)),
+                        KuduPredicate.newComparisonPredicate(doubleCol, GREATER, 12.345));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(stringCol, GREATER_EQUAL, "a\0"),
+                        KuduPredicate.newComparisonPredicate(stringCol, GREATER, "a"));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(binaryCol, GREATER_EQUAL, new byte[] { (byte) 10, (byte) 0 }),
+                        KuduPredicate.newComparisonPredicate(binaryCol, GREATER, new byte[] { (byte) 10 }));
+
+    Assert.assertEquals(KuduPredicate.none(byteCol),
+                        KuduPredicate.newComparisonPredicate(byteCol, GREATER, Byte.MAX_VALUE));
+    Assert.assertEquals(KuduPredicate.none(shortCol),
+                        KuduPredicate.newComparisonPredicate(shortCol, GREATER, Short.MAX_VALUE));
+    Assert.assertEquals(KuduPredicate.none(intCol),
+                        KuduPredicate.newComparisonPredicate(intCol, GREATER, Integer.MAX_VALUE));
+    Assert.assertEquals(KuduPredicate.none(longCol),
+                        KuduPredicate.newComparisonPredicate(longCol, GREATER, Long.MAX_VALUE));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(floatCol, GREATER_EQUAL, Float.POSITIVE_INFINITY),
+                        KuduPredicate.newComparisonPredicate(floatCol, GREATER, Float.MAX_VALUE));
+    Assert.assertEquals(KuduPredicate.none(floatCol),
+                        KuduPredicate.newComparisonPredicate(floatCol, GREATER, Float.POSITIVE_INFINITY));
+    Assert.assertEquals(KuduPredicate.newComparisonPredicate(doubleCol, GREATER_EQUAL, Double.POSITIVE_INFINITY),
+                        KuduPredicate.newComparisonPredicate(doubleCol, GREATER, Double.MAX_VALUE));
+    Assert.assertEquals(KuduPredicate.none(doubleCol),
+                        KuduPredicate.newComparisonPredicate(doubleCol, GREATER, Double.POSITIVE_INFINITY));
+  }
+
+  @Test
+  public void testToString() {
+    Assert.assertEquals("`bool` = true",
+                        KuduPredicate.newComparisonPredicate(boolCol, EQUAL, true).toString());
+    Assert.assertEquals("`byte` = 11",
+                        KuduPredicate.newComparisonPredicate(byteCol, EQUAL, 11).toString());
+    Assert.assertEquals("`short` = 11",
+                        KuduPredicate.newComparisonPredicate(shortCol, EQUAL, 11).toString());
+    Assert.assertEquals("`int` = -123",
+                        KuduPredicate.newComparisonPredicate(intCol, EQUAL, -123).toString());
+    Assert.assertEquals("`long` = 5454",
+                        KuduPredicate.newComparisonPredicate(longCol, EQUAL, 5454).toString());
+    Assert.assertEquals("`float` = 123.456",
+                        KuduPredicate.newComparisonPredicate(floatCol, EQUAL, 123.456f).toString());
+    Assert.assertEquals("`double` = 123.456",
+                        KuduPredicate.newComparisonPredicate(doubleCol, EQUAL, 123.456).toString());
+    Assert.assertEquals("`string` = \"my string\"",
+                        KuduPredicate.newComparisonPredicate(stringCol, EQUAL, "my string").toString());
+    Assert.assertEquals("`binary` = 0xAB01CD", KuduPredicate.newComparisonPredicate(
+        binaryCol, EQUAL, new byte[] { (byte) 0xAB, (byte) 0x01, (byte) 0xCD }).toString());
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java
new file mode 100644
index 0000000..df2367f
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduSession.java
@@ -0,0 +1,337 @@
+// 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.kududb.client;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import static org.junit.Assert.*;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestKuduSession extends BaseKuduTest {
+  @Rule
+  public TestName name = new TestName();
+
+  private KuduTable table;
+
+  @Test(timeout = 100000)
+  public void testBasicOps() throws Exception {
+    String tableName = name.getMethodName();
+    table = createTable(tableName, basicSchema, getBasicCreateTableOptions());
+
+    KuduSession session = syncClient.newSession();
+    for (int i = 0; i < 10; i++) {
+      session.apply(createInsert(i));
+    }
+    assertEquals(10, countRowsInScan(client.newScannerBuilder(table).build()));
+
+    OperationResponse resp = session.apply(createInsert(0));
+    assertTrue(resp.hasRowError());
+
+    session.setFlushMode(SessionConfiguration.FlushMode.MANUAL_FLUSH);
+
+    for (int i = 10; i < 20; i++) {
+      session.apply(createInsert(i));
+    }
+    session.flush();
+    assertEquals(20, countRowsInScan(client.newScannerBuilder(table).build()));
+  }
+
+  @Test(timeout = 100000)
+  public void testIgnoreAllDuplicateRows() throws Exception {
+    String tableName = name.getMethodName();
+    table = createTable(tableName, basicSchema, getBasicCreateTableOptions());
+
+    KuduSession session = syncClient.newSession();
+    session.setIgnoreAllDuplicateRows(true);
+    for (int i = 0; i < 10; i++) {
+      session.apply(createInsert(i));
+    }
+    for (SessionConfiguration.FlushMode mode : SessionConfiguration.FlushMode.values()) {
+      session.setFlushMode(mode);
+      for (int i = 0; i < 10; i++) {
+        OperationResponse resp = session.apply(createInsert(i));
+        if (mode == SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC) {
+          assertFalse(resp.hasRowError());
+        }
+      }
+      if (mode == SessionConfiguration.FlushMode.MANUAL_FLUSH) {
+        List<OperationResponse> responses = session.flush();
+        for (OperationResponse resp : responses) {
+          assertFalse(resp.hasRowError());
+        }
+      } else if (mode == SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND) {
+        while (session.hasPendingOperations()) {
+          Thread.sleep(100);
+        }
+        assertEquals(0, session.countPendingErrors());
+      }
+    }
+  }
+
+  @Test(timeout = 100000)
+  public void testBatchWithSameRow() throws Exception {
+    String tableName = name.getMethodName();
+    table = createTable(tableName, basicSchema, getBasicCreateTableOptions());
+
+    KuduSession session = syncClient.newSession();
+    session.setFlushMode(SessionConfiguration.FlushMode.MANUAL_FLUSH);
+
+    // Insert 25 rows, one per batch, along with 50 updates for each, and a delete at the end,
+    // while also clearing the cache between each batch half the time. The delete is added here
+    // so that a misplaced update would fail if it happens later than its delete.
+    for (int i = 0; i < 25; i++) {
+      session.apply(createInsert(i));
+      for (int j = 0; j < 50; j++) {
+        Update update = table.newUpdate();
+        PartialRow row = update.getRow();
+        row.addInt(basicSchema.getColumnByIndex(0).getName(), i);
+        row.addInt(basicSchema.getColumnByIndex(1).getName(), 1000);
+        session.apply(update);
+      }
+      Delete del = table.newDelete();
+      PartialRow row = del.getRow();
+      row.addInt(basicSchema.getColumnByIndex(0).getName(), i);
+      session.apply(del);
+      session.flush();
+      if (i % 2 == 0) {
+        client.emptyTabletsCacheForTable(table.getTableId());
+      }
+    }
+    assertEquals(0, countRowsInScan(client.newScannerBuilder(table).build()));
+  }
+
+  /**
+   * Regression test for KUDU-1226. Calls to session.flush() concurrent with AUTO_FLUSH_BACKGROUND
+   * can end up giving ConvertBatchToListOfResponsesCB a list with nulls if a tablet was already
+   * flushed. Only happens with multiple tablets.
+   */
+  @Test(timeout = 10000)
+  public void testConcurrentFlushes() throws Exception {
+    String tableName = name.getMethodName();
+    CreateTableOptions builder = getBasicCreateTableOptions();
+    int numTablets = 4;
+    int numRowsPerTablet = 100;
+
+    // Create a 4 tablets table split on 1000, 2000, and 3000.
+    for (int i = 1; i < numTablets; i++) {
+      PartialRow split = basicSchema.newPartialRow();
+      split.addInt(0, i * numRowsPerTablet);
+      builder.addSplitRow(split);
+    }
+    table = createTable(tableName, basicSchema, builder);
+
+    // Configure the session to background flush as often as it can (every 1ms).
+    KuduSession session = syncClient.newSession();
+    session.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND);
+    session.setFlushInterval(1);
+
+    // Fill each tablet in parallel 1 by 1 then flush. Without the fix this would quickly get an
+    // NPE.
+    for (int i = 0; i < numRowsPerTablet; i++) {
+      for (int j = 0; j < numTablets; j++) {
+        session.apply(createInsert(i + (numRowsPerTablet * j)));
+      }
+      session.flush();
+    }
+  }
+
+  @Test(timeout = 10000)
+  public void testOverWritingValues() throws Exception {
+    String tableName = name.getMethodName();
+    table = createTable(tableName, basicSchema, getBasicCreateTableOptions());
+    KuduSession session = syncClient.newSession();
+    Insert insert = createInsert(0);
+    PartialRow row = insert.getRow();
+
+    // Overwrite all the normal columns.
+    int magicNumber = 9999;
+    row.addInt(1, magicNumber);
+    row.addInt(2, magicNumber);
+    row.addBoolean(4, false);
+    // Spam the string column since it's backed by an array.
+    for (int i = 0; i <= magicNumber; i++) {
+      row.addString(3, i + "");
+    }
+    // We're supposed to keep a constant size.
+    assertEquals(5, row.getVarLengthData().size());
+    session.apply(insert);
+
+    KuduScanner scanner = syncClient.newScannerBuilder(table).build();
+    RowResult rr = scanner.nextRows().next();
+    assertEquals(magicNumber, rr.getInt(1));
+    assertEquals(magicNumber, rr.getInt(2));
+    assertEquals(magicNumber + "", rr.getString(3));
+    assertEquals(false, rr.getBoolean(4));
+
+    // Test setting a value post-apply.
+    try {
+      row.addInt(1, 0);
+      fail("Row should be frozen and throw");
+    } catch (IllegalStateException ex) {
+      // Ok.
+    }
+  }
+
+  @Test(timeout = 10000)
+  public void testUpsert() throws Exception {
+    String tableName = name.getMethodName();
+    table = createTable(tableName, basicSchema, getBasicCreateTableOptions());
+    KuduSession session = syncClient.newSession();
+
+    // Test an Upsert that acts as an Insert.
+    assertFalse(session.apply(createUpsert(1, 1, false)).hasRowError());
+
+    List<String> rowStrings = scanTableToStrings(table);
+    assertEquals(1, rowStrings.size());
+    assertEquals(
+        "INT32 key=1, INT32 column1_i=1, INT32 column2_i=3, " +
+            "STRING column3_s=a string, BOOL column4_b=true",
+        rowStrings.get(0));
+
+    // Test an Upsert that acts as an Update.
+    assertFalse(session.apply(createUpsert(1, 2, false)).hasRowError());
+    rowStrings = scanTableToStrings(table);
+    assertEquals(
+        "INT32 key=1, INT32 column1_i=2, INT32 column2_i=3, " +
+            "STRING column3_s=a string, BOOL column4_b=true",
+        rowStrings.get(0));
+  }
+
+  @Test(timeout = 10000)
+  public void testInsertManualFlushNonCoveredRange() throws Exception {
+    String tableName = name.getMethodName();
+    CreateTableOptions createOptions = getBasicTableOptionsWithNonCoveredRange();
+    createOptions.setNumReplicas(1);
+    syncClient.createTable(tableName, basicSchema, createOptions);
+    KuduTable table = syncClient.openTable(tableName);
+
+    KuduSession session = syncClient.newSession();
+    session.setFlushMode(SessionConfiguration.FlushMode.MANUAL_FLUSH);
+
+    // Insert in reverse sorted order so that more table location lookups occur
+    // (the extra results in table location lookups always occur past the inserted key).
+    List<Integer> nonCoveredKeys = ImmutableList.of(350, 300, 199, 150, 100, -1, -50);
+    for (int key : nonCoveredKeys) {
+      assertNull(session.apply(createBasicSchemaInsert(table, key)));
+    }
+    List<OperationResponse> results = session.flush();
+    assertEquals(nonCoveredKeys.size(), results.size());
+    for (OperationResponse result : results) {
+      assertTrue(result.hasRowError());
+      assertTrue(result.getRowError().getErrorStatus().isNotFound());
+    }
+
+    // Insert a batch of some valid and some invalid.
+    for (int key = 90; key < 110; key++) {
+      session.apply(createBasicSchemaInsert(table, key));
+    }
+    results = session.flush();
+
+    int failures = 0;
+    for (OperationResponse result : results) {
+      if (result.hasRowError()) {
+        failures++;
+        assertTrue(result.getRowError().getErrorStatus().isNotFound());
+      }
+    }
+    assertEquals(10, failures);
+  }
+
+  @Test(timeout = 10000)
+  public void testInsertAutoFlushSyncNonCoveredRange() throws Exception {
+    String tableName = name.getMethodName();
+    CreateTableOptions createOptions = getBasicTableOptionsWithNonCoveredRange();
+    createOptions.setNumReplicas(1);
+    syncClient.createTable(tableName, basicSchema, createOptions);
+    KuduTable table = syncClient.openTable(tableName);
+
+    KuduSession session = syncClient.newSession();
+    session.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC);
+
+    List<Integer> nonCoveredKeys = ImmutableList.of(350, 300, 199, 150, 100, -1, -50);
+    for (int key : nonCoveredKeys) {
+      try {
+        session.apply(createBasicSchemaInsert(table, key));
+        fail("apply should have thrown");
+      } catch (KuduException e) {
+        assertTrue(e.getStatus().isNotFound());
+      }
+    }
+  }
+
+  @Test(timeout = 10000)
+  public void testInsertAutoFlushBackgrounNonCoveredRange() throws Exception {
+    String tableName = name.getMethodName();
+    CreateTableOptions createOptions = getBasicTableOptionsWithNonCoveredRange();
+    createOptions.setNumReplicas(1);
+    syncClient.createTable(tableName, basicSchema, createOptions);
+    KuduTable table = syncClient.openTable(tableName);
+
+    AsyncKuduSession session = client.newSession();
+    session.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_BACKGROUND);
+
+    List<Integer> nonCoveredKeys = ImmutableList.of(350, 300, 199, 150, 100, -1, -50);
+    for (int key : nonCoveredKeys) {
+      OperationResponse result = session.apply(createBasicSchemaInsert(table, key)).join(5000);
+      assertTrue(result.hasRowError());
+      assertTrue(result.getRowError().getErrorStatus().isNotFound());
+    }
+
+    RowErrorsAndOverflowStatus errors = session.getPendingErrors();
+    assertEquals(nonCoveredKeys.size(), errors.getRowErrors().length);
+    for (RowError error : errors.getRowErrors()) {
+      assertTrue(error.getErrorStatus().isNotFound());
+    }
+
+    // Insert a batch of some valid and some invalid.
+    for (int key = 90; key < 110; key++) {
+      session.apply(createBasicSchemaInsert(table, key));
+    }
+    session.flush();
+
+    errors = session.getPendingErrors();
+    assertEquals(10, errors.getRowErrors().length);
+    for (RowError error : errors.getRowErrors()) {
+      assertTrue(error.getErrorStatus().isNotFound());
+    }
+  }
+
+  private Insert createInsert(int key) {
+    return createBasicSchemaInsert(table, key);
+  }
+
+  private Upsert createUpsert(int key, int secondVal, boolean hasNull) {
+    Upsert upsert = table.newUpsert();
+    PartialRow row = upsert.getRow();
+    row.addInt(0, key);
+    row.addInt(1, secondVal);
+    row.addInt(2, 3);
+    if (hasNull) {
+      row.setNull(3);
+    } else {
+      row.addString(3, "a string");
+    }
+    row.addBoolean(4, true);
+    return upsert;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduTable.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduTable.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduTable.java
new file mode 100644
index 0000000..4e41a29
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduTable.java
@@ -0,0 +1,301 @@
+// 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.kududb.client;
+
+import org.junit.Rule;
+import org.junit.rules.TestName;
+import org.kududb.ColumnSchema;
+import org.kududb.Schema;
+import org.kududb.Type;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class TestKuduTable extends BaseKuduTest {
+  private static final Logger LOG = LoggerFactory.getLogger(TestKuduTable.class);
+
+  @Rule
+  public TestName name = new TestName();
+
+  private static Schema schema = getBasicSchema();
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    BaseKuduTest.setUpBeforeClass();
+  }
+
+  @Test(timeout = 100000)
+  public void testAlterTable() throws Exception {
+    String tableName = name.getMethodName();
+    createTable(tableName, basicSchema, getBasicCreateTableOptions());
+    try {
+
+      // Add a col.
+      AlterTableOptions ato = new AlterTableOptions().addColumn("testaddint", Type.INT32, 4);
+      submitAlterAndCheck(ato, tableName);
+
+      // Rename that col.
+      ato = new AlterTableOptions().renameColumn("testaddint", "newtestaddint");
+      submitAlterAndCheck(ato, tableName);
+
+      // Delete it.
+      ato = new AlterTableOptions().dropColumn("newtestaddint");
+      submitAlterAndCheck(ato, tableName);
+
+      String newTableName = tableName +"new";
+
+      // Rename our table.
+      ato = new AlterTableOptions().renameTable(newTableName);
+      submitAlterAndCheck(ato, tableName, newTableName);
+
+      // Rename it back.
+      ato = new AlterTableOptions().renameTable(tableName);
+      submitAlterAndCheck(ato, newTableName, tableName);
+
+      // Try adding two columns, where one is nullable.
+      ato = new AlterTableOptions()
+          .addColumn("testaddmulticolnotnull", Type.INT32, 4)
+          .addNullableColumn("testaddmulticolnull", Type.STRING);
+      submitAlterAndCheck(ato, tableName);
+
+
+      // Try altering a table that doesn't exist.
+      String nonExistingTableName = "table_does_not_exist";
+      try {
+        syncClient.alterTable(nonExistingTableName, ato);
+        fail("Shouldn't be able to alter a table that doesn't exist");
+      } catch (KuduException ex) {
+        assertTrue(ex.getStatus().isNotFound());
+      }
+
+      try {
+        syncClient.isAlterTableDone(nonExistingTableName);
+        fail("Shouldn't be able to query if an alter table is done here");
+      } catch (KuduException ex) {
+        assertTrue(ex.getStatus().isNotFound());
+      }
+    } finally {
+      // Normally Java tests accumulate tables without issue, deleting them all
+      // when shutting down the mini cluster at the end of every test class.
+      // However, testGetLocations below expects a certain table count, so
+      // we'll delete our table to ensure there's no interaction between them.
+      syncClient.deleteTable(tableName);
+    }
+  }
+
+  /**
+   * Helper method to submit an Alter and wait for it to happen, using the default table name to
+   * check.
+   */
+  private void submitAlterAndCheck(AlterTableOptions ato, String tableToAlter)
+      throws Exception {
+    submitAlterAndCheck(ato, tableToAlter, tableToAlter);
+  }
+
+  private void submitAlterAndCheck(AlterTableOptions ato,
+                                         String tableToAlter, String tableToCheck) throws
+      Exception {
+    if (masterHostPorts.size() > 1) {
+      LOG.info("Alter table is not yet supported with multiple masters. Specify " +
+          "-DnumMasters=1 on the command line to start a single-master cluster to run this test.");
+      return;
+    }
+    AlterTableResponse alterResponse = syncClient.alterTable(tableToAlter, ato);
+    boolean done  = syncClient.isAlterTableDone(tableToCheck);
+    assertTrue(done);
+  }
+
+  /**
+   * Test creating tables of different sizes and see that we get the correct number of tablets back
+   * @throws Exception
+   */
+  @Test
+  public void testGetLocations() throws Exception {
+    String table1 = name.getMethodName() + System.currentTimeMillis();
+
+    // Test a non-existing table
+    try {
+      openTable(table1);
+      fail("Should receive an exception since the table doesn't exist");
+    } catch (Exception ex) {
+      // expected
+    }
+    // Test with defaults
+    String tableWithDefault = name.getMethodName() + "WithDefault" + System.currentTimeMillis();
+    CreateTableOptions builder = getBasicCreateTableOptions();
+    List<ColumnSchema> columns = new ArrayList<ColumnSchema>(schema.getColumnCount());
+    int defaultInt = 30;
+    String defaultString = "data";
+    for (ColumnSchema columnSchema : schema.getColumns()) {
+
+      Object defaultValue;
+
+      if (columnSchema.getType() == Type.INT32) {
+        defaultValue = defaultInt;
+      } else if (columnSchema.getType() == Type.BOOL) {
+        defaultValue = true;
+      } else {
+        defaultValue = defaultString;
+      }
+      columns.add(
+          new ColumnSchema.ColumnSchemaBuilder(columnSchema.getName(), columnSchema.getType())
+              .key(columnSchema.isKey())
+              .nullable(columnSchema.isNullable())
+              .defaultValue(defaultValue).build());
+    }
+    Schema schemaWithDefault = new Schema(columns);
+    KuduTable kuduTable = createTable(tableWithDefault, schemaWithDefault, builder);
+    assertEquals(defaultInt, kuduTable.getSchema().getColumnByIndex(0).getDefaultValue());
+    assertEquals(defaultString,
+        kuduTable.getSchema().getColumnByIndex(columns.size() - 2).getDefaultValue());
+    assertEquals(true,
+            kuduTable.getSchema().getColumnByIndex(columns.size() - 1).getDefaultValue());
+
+    // Make sure the table's schema includes column IDs.
+    assertTrue(kuduTable.getSchema().hasColumnIds());
+
+    // Test we can open a table that was already created.
+    openTable(tableWithDefault);
+
+    // Test splitting and reading those splits
+    KuduTable kuduTableWithoutDefaults = createTableWithSplitsAndTest(0);
+    // finish testing read defaults
+    assertNull(kuduTableWithoutDefaults.getSchema().getColumnByIndex(0).getDefaultValue());
+    createTableWithSplitsAndTest(3);
+    createTableWithSplitsAndTest(10);
+
+    KuduTable table = createTableWithSplitsAndTest(30);
+
+    List<LocatedTablet>tablets = table.getTabletsLocations(null, getKeyInBytes(9), DEFAULT_SLEEP);
+    assertEquals(9, tablets.size());
+    assertEquals(9, table.asyncGetTabletsLocations(null, getKeyInBytes(9), DEFAULT_SLEEP).join().size());
+
+    tablets = table.getTabletsLocations(getKeyInBytes(0), getKeyInBytes(9), DEFAULT_SLEEP);
+    assertEquals(9, tablets.size());
+    assertEquals(9, table.asyncGetTabletsLocations(getKeyInBytes(0), getKeyInBytes(9), DEFAULT_SLEEP).join().size());
+
+    tablets = table.getTabletsLocations(getKeyInBytes(5), getKeyInBytes(9), DEFAULT_SLEEP);
+    assertEquals(4, tablets.size());
+    assertEquals(4, table.asyncGetTabletsLocations(getKeyInBytes(5), getKeyInBytes(9), DEFAULT_SLEEP).join().size());
+
+    tablets = table.getTabletsLocations(getKeyInBytes(5), getKeyInBytes(14), DEFAULT_SLEEP);
+    assertEquals(9, tablets.size());
+    assertEquals(9, table.asyncGetTabletsLocations(getKeyInBytes(5), getKeyInBytes(14), DEFAULT_SLEEP).join().size());
+
+    tablets = table.getTabletsLocations(getKeyInBytes(5), getKeyInBytes(31), DEFAULT_SLEEP);
+    assertEquals(26, tablets.size());
+    assertEquals(26, table.asyncGetTabletsLocations(getKeyInBytes(5), getKeyInBytes(31), DEFAULT_SLEEP).join().size());
+
+    tablets = table.getTabletsLocations(getKeyInBytes(5), null, DEFAULT_SLEEP);
+    assertEquals(26, tablets.size());
+    assertEquals(26, table.asyncGetTabletsLocations(getKeyInBytes(5), null, DEFAULT_SLEEP).join().size());
+
+    tablets = table.getTabletsLocations(null, getKeyInBytes(10000), DEFAULT_SLEEP);
+    assertEquals(31, tablets.size());
+    assertEquals(31, table.asyncGetTabletsLocations(null, getKeyInBytes(10000), DEFAULT_SLEEP).join().size());
+
+    tablets = table.getTabletsLocations(getKeyInBytes(20), getKeyInBytes(10000), DEFAULT_SLEEP);
+    assertEquals(11, tablets.size());
+    assertEquals(11, table.asyncGetTabletsLocations(getKeyInBytes(20), getKeyInBytes(10000), DEFAULT_SLEEP).join().size());
+
+    // Test listing tables.
+    assertEquals(0, client.getTablesList(table1).join(DEFAULT_SLEEP).getTablesList().size());
+    assertEquals(1, client.getTablesList(tableWithDefault)
+                          .join(DEFAULT_SLEEP).getTablesList().size());
+    assertEquals(5, client.getTablesList().join(DEFAULT_SLEEP).getTablesList().size());
+    assertFalse(client.getTablesList(tableWithDefault).
+        join(DEFAULT_SLEEP).getTablesList().isEmpty());
+
+    assertFalse(client.tableExists(table1).join(DEFAULT_SLEEP));
+    assertTrue(client.tableExists(tableWithDefault).join(DEFAULT_SLEEP));
+  }
+
+  @Test(timeout = 100000)
+  public void testLocateTableNonCoveringRange() throws Exception {
+    String tableName = name.getMethodName();
+    syncClient.createTable(tableName, basicSchema, getBasicTableOptionsWithNonCoveredRange());
+    KuduTable table = syncClient.openTable(tableName);
+
+    List<LocatedTablet> tablets;
+
+    // all tablets
+    tablets = table.getTabletsLocations(null, null, 100000);
+    assertEquals(3, tablets.size());
+    assertArrayEquals(getKeyInBytes(0), tablets.get(0).getPartition().getPartitionKeyStart());
+    assertArrayEquals(getKeyInBytes(50), tablets.get(0).getPartition().getPartitionKeyEnd());
+    assertArrayEquals(getKeyInBytes(50), tablets.get(1).getPartition().getPartitionKeyStart());
+    assertArrayEquals(getKeyInBytes(100), tablets.get(1).getPartition().getPartitionKeyEnd());
+    assertArrayEquals(getKeyInBytes(200), tablets.get(2).getPartition().getPartitionKeyStart());
+    assertArrayEquals(getKeyInBytes(300), tablets.get(2).getPartition().getPartitionKeyEnd());
+
+    // key < 50
+    tablets = table.getTabletsLocations(null, getKeyInBytes(50), 100000);
+    assertEquals(1, tablets.size());
+    assertArrayEquals(getKeyInBytes(0), tablets.get(0).getPartition().getPartitionKeyStart());
+    assertArrayEquals(getKeyInBytes(50), tablets.get(0).getPartition().getPartitionKeyEnd());
+
+    // key >= 300
+    tablets = table.getTabletsLocations(getKeyInBytes(300), null, 100000);
+    assertEquals(0, tablets.size());
+
+    // key >= 299
+    tablets = table.getTabletsLocations(getKeyInBytes(299), null, 100000);
+    assertEquals(1, tablets.size());
+    assertArrayEquals(getKeyInBytes(200), tablets.get(0).getPartition().getPartitionKeyStart());
+    assertArrayEquals(getKeyInBytes(300), tablets.get(0).getPartition().getPartitionKeyEnd());
+
+    // key >= 150 && key < 250
+    tablets = table.getTabletsLocations(getKeyInBytes(150), getKeyInBytes(250), 100000);
+    assertEquals(1, tablets.size());
+    assertArrayEquals(getKeyInBytes(200), tablets.get(0).getPartition().getPartitionKeyStart());
+    assertArrayEquals(getKeyInBytes(300), tablets.get(0).getPartition().getPartitionKeyEnd());
+  }
+
+  public byte[] getKeyInBytes(int i) {
+    PartialRow row = schema.newPartialRow();
+    row.addInt(0, i);
+    return row.encodePrimaryKey();
+  }
+
+  public KuduTable createTableWithSplitsAndTest(int splitsCount) throws Exception {
+    String tableName = name.getMethodName() + System.currentTimeMillis();
+    CreateTableOptions builder = getBasicCreateTableOptions();
+
+    if (splitsCount != 0) {
+      for (int i = 1; i <= splitsCount; i++) {
+        PartialRow row = schema.newPartialRow();
+        row.addInt(0, i);
+        builder.addSplitRow(row);
+      }
+    }
+    KuduTable table = createTable(tableName, schema, builder);
+
+    List<LocatedTablet> tablets = table.getTabletsLocations(DEFAULT_SLEEP);
+    assertEquals(splitsCount + 1, tablets.size());
+    assertEquals(splitsCount + 1, table.asyncGetTabletsLocations(DEFAULT_SLEEP).join().size());
+    for (LocatedTablet tablet : tablets) {
+      assertEquals(3, tablet.getReplicas().size());
+    }
+    return table;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestLeaderFailover.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestLeaderFailover.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestLeaderFailover.java
new file mode 100644
index 0000000..49ac502
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestLeaderFailover.java
@@ -0,0 +1,69 @@
+// 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.kududb.client;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TestLeaderFailover extends BaseKuduTest {
+
+  private static final String TABLE_NAME =
+      TestLeaderFailover.class.getName() + "-" + System.currentTimeMillis();
+  private static KuduTable table;
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    BaseKuduTest.setUpBeforeClass();
+
+    CreateTableOptions builder = getBasicCreateTableOptions();
+    createTable(TABLE_NAME, basicSchema, builder);
+
+    table = openTable(TABLE_NAME);
+  }
+
+  /**
+   * This test writes 3 rows, kills the leader, then tries to write another 3 rows. Finally it
+   * counts to make sure we have 6 of them.
+   *
+   * This test won't run if we didn't start the cluster.
+   */
+  @Test(timeout = 100000)
+  public void testFailover() throws Exception {
+    KuduSession session = syncClient.newSession();
+    for (int i = 0; i < 3; i++) {
+      session.apply(createBasicSchemaInsert(table, i));
+    }
+
+    // Make sure the rows are in there before messing things up.
+    AsyncKuduScanner scanner = client.newScannerBuilder(table).build();
+    assertEquals(3, countRowsInScan(scanner));
+
+    killTabletLeader(table);
+
+    for (int i = 3; i < 6; i++) {
+      OperationResponse resp = session.apply(createBasicSchemaInsert(table, i));
+      if (resp.hasRowError()) {
+        fail("Encountered a row error " + resp.getRowError());
+      }
+    }
+
+    scanner = client.newScannerBuilder(table).build();
+    assertEquals(6, countRowsInScan(scanner));
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestMasterFailover.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestMasterFailover.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestMasterFailover.java
new file mode 100644
index 0000000..2f91a6e
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestMasterFailover.java
@@ -0,0 +1,72 @@
+// 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.kududb.client;
+
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertEquals;
+
+
+/**
+ * Tests {@link AsyncKuduClient} with multiple masters.
+ */
+public class TestMasterFailover extends BaseKuduTest {
+  private static final Logger LOG = LoggerFactory.getLogger(TestMasterFailover.class);
+  private static final String TABLE_NAME =
+      TestMasterFailover.class.getName() + "-" + System.currentTimeMillis();
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    BaseKuduTest.setUpBeforeClass();
+    createTable(TABLE_NAME, basicSchema, getBasicCreateTableOptions());
+  }
+
+  /**
+   * This test is disabled as we're not supporting multi-master just yet.
+   */
+  @Test(timeout = 30000)
+  @Ignore
+  public void testKillLeader() throws Exception {
+    int countMasters = masterHostPorts.size();
+    if (countMasters < 3) {
+      LOG.info("This test requires at least 3 master servers, but only " + countMasters +
+          " are specified.");
+      return;
+    }
+    killMasterLeader();
+
+    // Test that we can open a previously created table after killing the leader master.
+    KuduTable table = openTable(TABLE_NAME);
+    assertEquals(0, countRowsInScan(client.newScannerBuilder(table).build()));
+
+    // Test that we can create a new table when one of the masters is down.
+    String newTableName = TABLE_NAME + "-afterLeaderIsDead";
+    createTable(newTableName, basicSchema, new CreateTableOptions());
+    table = openTable(newTableName);
+    assertEquals(0, countRowsInScan(client.newScannerBuilder(table).build()));
+
+    // Test that we can initialize a client when one of the masters specified in the
+    // connection string is down.
+    AsyncKuduClient newClient = new AsyncKuduClient.AsyncKuduClientBuilder(masterAddresses).build();
+    table = newClient.openTable(newTableName).join(DEFAULT_SLEEP);
+    assertEquals(0, countRowsInScan(newClient.newScannerBuilder(table).build()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestMiniKuduCluster.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestMiniKuduCluster.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestMiniKuduCluster.java
new file mode 100644
index 0000000..82ffacb
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestMiniKuduCluster.java
@@ -0,0 +1,116 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.kududb.client;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TestMiniKuduCluster {
+
+  private static final int NUM_TABLET_SERVERS = 3;
+  private static final int DEFAULT_NUM_MASTERS = 1;
+
+  private MiniKuduCluster cluster;
+
+  @Before
+  public void before() throws Exception {
+    cluster = new MiniKuduCluster.MiniKuduClusterBuilder()
+        .numMasters(DEFAULT_NUM_MASTERS)
+        .numTservers(NUM_TABLET_SERVERS)
+        .build();
+    assertTrue(cluster.waitForTabletServers(NUM_TABLET_SERVERS));
+  }
+
+  @After
+  public void after() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  @Test(timeout = 50000)
+  public void test() throws Exception {
+
+    assertEquals(DEFAULT_NUM_MASTERS, cluster.getMasterProcesses().size());
+    assertEquals(NUM_TABLET_SERVERS, cluster.getTabletServerProcesses().size());
+
+    {
+      // Kill the master.
+      int masterPort = cluster.getMasterProcesses().keySet().iterator().next();
+      testPort(masterPort, true, 1000);
+      cluster.killMasterOnPort(masterPort);
+
+      testPort(masterPort, false, 2000);
+
+      // Restart the master.
+      cluster.restartDeadMasterOnPort(masterPort);
+
+      // Test we can reach it.
+      testPort(masterPort, true, 3000);
+    }
+
+
+    {
+      // Kill the first TS.
+      int tsPort = cluster.getTabletServerProcesses().keySet().iterator().next();
+      testPort(tsPort, true, 1000);
+      cluster.killTabletServerOnPort(tsPort);
+
+      testPort(tsPort, false, 2000);
+
+      // Restart it.
+      cluster.restartDeadTabletServerOnPort(tsPort);
+
+      testPort(tsPort, true, 3000);
+    }
+
+    assertEquals(DEFAULT_NUM_MASTERS, cluster.getMasterProcesses().size());
+    assertEquals(NUM_TABLET_SERVERS, cluster.getTabletServerProcesses().size());
+  }
+
+  /**
+   * Test without the specified is open or closed, waiting up to a certain time.
+   * The longer you expect it might for the socket to become open or closed.
+   * @param port the port to test
+   * @param testIsOpen true if we should want it to be open, false if we want it closed
+   * @param timeout how long we're willing to wait before it happens
+   * @throws InterruptedException
+   */
+  private void testPort(int port, boolean testIsOpen, long timeout) throws InterruptedException {
+    DeadlineTracker tracker = new DeadlineTracker();
+    while (tracker.getElapsedMillis() < timeout) {
+      try {
+        Socket socket = new Socket(TestUtils.getUniqueLocalhost(), port);
+        socket.close();
+        if (testIsOpen) {
+          return;
+        }
+      } catch (IOException e) {
+        if (!testIsOpen) {
+          return;
+        }
+      }
+      Thread.sleep(200);
+    }
+    fail("Port " + port + " is still " + (testIsOpen ? "closed " : "open"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestOperation.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestOperation.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestOperation.java
new file mode 100644
index 0000000..f305fbf
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestOperation.java
@@ -0,0 +1,166 @@
+// 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.kududb.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+import org.kududb.ColumnSchema;
+import org.kududb.Schema;
+import org.kududb.Type;
+import org.kududb.WireProtocol.RowOperationsPB;
+import org.kududb.client.Operation.ChangeType;
+import org.kududb.tserver.Tserver.WriteRequestPBOrBuilder;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Longs;
+
+/**
+ * Unit tests for Operation
+ */
+public class TestOperation {
+
+  private Schema createManyStringsSchema() {
+    ArrayList<ColumnSchema> columns = new ArrayList<ColumnSchema>(4);
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c0", Type.STRING).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c1", Type.STRING).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c2", Type.STRING).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c3", Type.STRING).nullable(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c4", Type.STRING).nullable(true).build());
+    return new Schema(columns);
+  }
+
+  @Test
+  public void testSetStrings() {
+    KuduTable table = Mockito.mock(KuduTable.class);
+    Mockito.doReturn(createManyStringsSchema()).when(table).getSchema();
+    Insert insert = new Insert(table);
+    PartialRow row = insert.getRow();
+    row.addString("c0", "c0_val");
+    row.addString("c2", "c2_val");
+    row.addString("c1", "c1_val");
+    row.addString("c3", "c3_val");
+    row.addString("c4", "c4_val");
+
+    {
+      WriteRequestPBOrBuilder pb =
+          Operation.createAndFillWriteRequestPB(ImmutableList.<Operation>of(insert));
+      RowOperationsPB rowOps = pb.getRowOperations();
+      assertEquals(6 * 5, rowOps.getIndirectData().size());
+      assertEquals("c0_valc1_valc2_valc3_valc4_val", rowOps.getIndirectData().toStringUtf8());
+      byte[] rows = rowOps.getRows().toByteArray();
+      assertEquals(ChangeType.INSERT.toEncodedByte(), rows[0]);
+      // The "isset" bitset should have 5 bits set
+      assertEquals(0x1f, rows[1]);
+      // The "null" bitset should have no bits set
+      assertEquals(0, rows[2]);
+
+      // Check the strings.
+      int offset = 3;
+      for (int i = 0; i <= 4; i++) {
+        // The offset into the indirect buffer
+        assertEquals(6 * i, Bytes.getLong(rows, offset));
+        offset += Longs.BYTES;
+        // The length of the pointed-to string.
+        assertEquals(6, Bytes.getLong(rows, offset));
+        offset += Longs.BYTES;
+      }
+
+      // Should have used up whole buffer.
+      assertEquals(rows.length, offset);
+    }
+
+    // Setting a field to NULL should add to the null bitmap and remove
+    // the old value from the indirect buffer.
+    row.setNull("c3");
+    {
+      WriteRequestPBOrBuilder pb =
+          Operation.createAndFillWriteRequestPB(ImmutableList.<Operation>of(insert));
+      RowOperationsPB rowOps = pb.getRowOperations();
+      assertEquals(6 * 4, rowOps.getIndirectData().size());
+      assertEquals("c0_valc1_valc2_valc4_val", rowOps.getIndirectData().toStringUtf8());
+      byte[] rows = rowOps.getRows().toByteArray();
+      assertEquals(ChangeType.INSERT.toEncodedByte(), rows[0]);
+      // The "isset" bitset should have 5 bits set
+      assertEquals(0x1f, rows[1]);
+      // The "null" bitset should have 1 bit set for the null column
+      assertEquals(1 << 3, rows[2]);
+
+      // Check the strings.
+      int offset = 3;
+      int indirOffset = 0;
+      for (int i = 0; i <= 4; i++) {
+        if (i == 3) continue;
+        // The offset into the indirect buffer
+        assertEquals(indirOffset, Bytes.getLong(rows, offset));
+        indirOffset += 6;
+        offset += Longs.BYTES;
+        // The length of the pointed-to string.
+        assertEquals(6, Bytes.getLong(rows, offset));
+        offset += Longs.BYTES;
+      }
+      // Should have used up whole buffer.
+      assertEquals(rows.length, offset);
+    }
+  }
+
+  private Schema createAllTypesKeySchema() {
+    ArrayList<ColumnSchema> columns = new ArrayList<ColumnSchema>(7);
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c0", Type.INT8).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c1", Type.INT16).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c2", Type.INT32).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c3", Type.INT64).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c4", Type.TIMESTAMP).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c5", Type.STRING).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c6", Type.BINARY).key(true).build());
+    return new Schema(columns);
+  }
+
+  @Test
+  public void testRowKeyStringify() {
+    KuduTable table = Mockito.mock(KuduTable.class);
+    Mockito.doReturn(createAllTypesKeySchema()).when(table).getSchema();
+    Insert insert = new Insert(table);
+    PartialRow row = insert.getRow();
+    row.addByte("c0", (byte) 1);
+    row.addShort("c1", (short) 2);
+    row.addInt("c2", 3);
+    row.addLong("c3", 4);
+    row.addLong("c4", 5);
+    row.addString("c5", "c5_val");
+    row.addBinary("c6", Bytes.fromString("c6_val"));
+
+    assertEquals("(int8 c0=1, int16 c1=2, int32 c2=3, int64 c3=4, timestamp c4=5, string" +
+            " c5=c5_val, binary c6=\"c6_val\")",
+        insert.getRow().stringifyRowKey());
+
+    // Test an incomplete row key.
+    insert = new Insert(table);
+    row = insert.getRow();
+    row.addByte("c0", (byte) 1);
+    try {
+      row.stringifyRowKey();
+      fail("Should not be able to stringifyRowKey when not all keys are specified");
+    } catch (IllegalStateException ise) {
+      // Expected.
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestRequestTracker.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRequestTracker.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRequestTracker.java
new file mode 100644
index 0000000..7528de6
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRequestTracker.java
@@ -0,0 +1,74 @@
+// 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.kududb.client;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class TestRequestTracker {
+
+  @Test(timeout = 10000)
+  public void test() {
+    RequestTracker tracker = new RequestTracker("test");
+
+    // A new tracker should have no incomplete RPCs.
+    assertEquals(RequestTracker.NO_SEQ_NO, tracker.firstIncomplete());
+
+    int max = 10;
+
+    for (int i = 0; i < max; i++) {
+      tracker.newSeqNo();
+    }
+
+    // The first RPC is the incomplete one.
+    assertEquals(1, tracker.firstIncomplete());
+
+    // Mark the first as complete, incomplete should advance by 1.
+    tracker.rpcCompleted(1);
+    assertEquals(2, tracker.firstIncomplete());
+
+    // Mark the RPC in the middle as complete, first incomplete doesn't change.
+    tracker.rpcCompleted(5);
+    assertEquals(2, tracker.firstIncomplete());
+
+    // Mark the first half as complete.
+    // Note that we're also testing that rpcCompleted is idempotent.
+    for (int i = 1; i < max / 2; i++) {
+      tracker.rpcCompleted(i);
+    }
+
+    assertEquals(6, tracker.firstIncomplete());
+
+    // Get a few more sequence numbers.
+    long lastSeqNo = 0;
+    for (int i = max / 2; i <= max; i++) {
+      lastSeqNo = tracker.newSeqNo();
+    }
+
+    // Mark them all as complete except the last one.
+    while (tracker.firstIncomplete() != lastSeqNo) {
+      tracker.rpcCompleted(tracker.firstIncomplete());
+    }
+
+    assertEquals(lastSeqNo, tracker.firstIncomplete());
+    tracker.rpcCompleted(lastSeqNo);
+
+    // Test that we get back to NO_SEQ_NO after marking them all.
+    assertEquals(RequestTracker.NO_SEQ_NO, tracker.firstIncomplete());
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowErrors.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowErrors.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowErrors.java
new file mode 100644
index 0000000..90d11aa
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowErrors.java
@@ -0,0 +1,98 @@
+// 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.kududb.client;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestRowErrors extends BaseKuduTest {
+
+  private static KuduTable table;
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    BaseKuduTest.setUpBeforeClass();
+
+  }
+
+  @Test(timeout = 100000)
+  public void singleTabletTest() throws Exception {
+    String tableName = TestRowErrors.class.getName() + "-" + System.currentTimeMillis();
+    createTable(tableName, basicSchema, getBasicCreateTableOptions());
+    table = openTable(tableName);
+    AsyncKuduSession session = client.newSession();
+
+    // Insert 3 rows to play with.
+    for (int i = 0; i < 3; i++) {
+      session.apply(createInsert(i)).join(DEFAULT_SLEEP);
+    }
+
+    // Try a single dupe row insert with AUTO_FLUSH_SYNC.
+    Insert dupeForZero = createInsert(0);
+    OperationResponse resp = session.apply(dupeForZero).join(DEFAULT_SLEEP);
+    assertTrue(resp.hasRowError());
+    assertTrue(resp.getRowError().getOperation() == dupeForZero);
+
+    // Now try inserting two dupes and one good row, make sure we get only two errors back.
+    dupeForZero = createInsert(0);
+    Insert dupeForTwo = createInsert(2);
+    session.setFlushMode(AsyncKuduSession.FlushMode.MANUAL_FLUSH);
+    session.apply(dupeForZero);
+    session.apply(dupeForTwo);
+    session.apply(createInsert(4));
+
+    List<OperationResponse> responses = session.flush().join(DEFAULT_SLEEP);
+    List<RowError> errors = OperationResponse.collectErrors(responses);
+    assertEquals(2, errors.size());
+    assertTrue(errors.get(0).getOperation() == dupeForZero);
+    assertTrue(errors.get(1).getOperation() == dupeForTwo);
+  }
+
+  /**
+   * Test collecting errors from multiple tablets.
+   * @throws Exception
+   */
+  @Test(timeout = 100000)
+  public void multiTabletTest() throws Exception {
+    String tableName = TestRowErrors.class.getName() + "-" + System.currentTimeMillis();
+    createFourTabletsTableWithNineRows(tableName);
+    table = openTable(tableName);
+    KuduSession session = syncClient.newSession();
+    session.setFlushMode(KuduSession.FlushMode.AUTO_FLUSH_BACKGROUND);
+
+    int dupRows = 3;
+    session.apply(createInsert(12));
+    session.apply(createInsert(22));
+    session.apply(createInsert(32));
+
+    session.flush();
+
+    RowErrorsAndOverflowStatus reos = session.getPendingErrors();
+    assertEquals(dupRows, reos.getRowErrors().length);
+    assertEquals(0, session.countPendingErrors());
+  }
+
+  private Insert createInsert(int key) {
+    return createBasicSchemaInsert(table, key);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
new file mode 100644
index 0000000..1b302c1
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRowResult.java
@@ -0,0 +1,129 @@
+// 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.kududb.client;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.kududb.Type;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TestRowResult extends BaseKuduTest {
+
+  // Generate a unique table name
+  private static final String TABLE_NAME =
+      TestRowResult.class.getName() + "-" + System.currentTimeMillis();
+
+  private static KuduTable table;
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    BaseKuduTest.setUpBeforeClass();
+    createTable(TABLE_NAME, allTypesSchema, getAllTypesCreateTableOptions());
+    table = openTable(TABLE_NAME);
+  }
+
+  @Test(timeout = 10000)
+  public void test() throws Exception {
+    Insert insert = table.newInsert();
+    PartialRow row = insert.getRow();
+
+    row.addByte(0, (byte) 1);
+    row.addShort(1, (short) 2);
+    row.addInt(2, 3);
+    row.addLong(3, 4l);
+    row.addBoolean(4, true);
+    row.addFloat(5, 5.6f);
+    row.addDouble(6, 7.8);
+    row.addString(7, "string-value");
+    row.addBinary(8, "binary-array".getBytes());
+    ByteBuffer bb = ByteBuffer.wrap("binary-bytebuffer".getBytes());
+    bb.position(7); // We're only inserting the bytebuffer part of the original array.
+    row.addBinary(9, bb);
+    row.setNull(10);
+    row.addLong(11, 11l);
+
+    KuduSession session = syncClient.newSession();
+    session.apply(insert);
+
+    KuduScanner scanner = syncClient.newScannerBuilder(table).build();
+    while (scanner.hasMoreRows()) {
+      RowResultIterator it = scanner.nextRows();
+      assertTrue(it.hasNext());
+      RowResult rr = it.next();
+
+      assertEquals((byte) 1, rr.getByte(0));
+      assertEquals((byte) 1, rr.getByte(allTypesSchema.getColumnByIndex(0).getName()));
+
+      assertEquals((short) 2, rr.getShort(1));
+      assertEquals((short) 2, rr.getShort(allTypesSchema.getColumnByIndex(1).getName()));
+
+      assertEquals(3, rr.getInt(2));
+      assertEquals(3, rr.getInt(allTypesSchema.getColumnByIndex(2).getName()));
+
+      assertEquals(4, rr.getLong(3));
+      assertEquals(4, rr.getLong(allTypesSchema.getColumnByIndex(3).getName()));
+
+      assertEquals(true, rr.getBoolean(4));
+      assertEquals(true, rr.getBoolean(allTypesSchema.getColumnByIndex(4).getName()));
+
+      assertEquals(5.6f, rr.getFloat(5), .001f);
+      assertEquals(5.6f, rr.getFloat(allTypesSchema.getColumnByIndex(5).getName()), .001f);
+
+      assertEquals(7.8, rr.getDouble(6), .001);
+      assertEquals(7.8, rr.getDouble(allTypesSchema.getColumnByIndex(6).getName()), .001f);
+
+      assertEquals("string-value", rr.getString(7));
+      assertEquals("string-value", rr.getString(allTypesSchema.getColumnByIndex(7).getName()));
+
+      assertArrayEquals("binary-array".getBytes(), rr.getBinaryCopy(8));
+      assertArrayEquals("binary-array".getBytes(),
+          rr.getBinaryCopy(allTypesSchema.getColumnByIndex(8).getName()));
+
+      ByteBuffer buffer = rr.getBinary(8);
+      assertEquals(buffer, rr.getBinary(allTypesSchema.getColumnByIndex(8).getName()));
+      byte[] binaryValue = new byte[buffer.remaining()];
+      buffer.get(binaryValue);
+      assertArrayEquals("binary-array".getBytes(), binaryValue);
+
+      assertArrayEquals("bytebuffer".getBytes(), rr.getBinaryCopy(9));
+
+      assertEquals(true, rr.isNull(10));
+      assertEquals(true, rr.isNull(allTypesSchema.getColumnByIndex(10).getName()));
+
+      assertEquals(11, rr.getLong(11));
+      assertEquals(11, rr.getLong(allTypesSchema.getColumnByIndex(11).getName()));
+
+      // We test with the column name once since it's the same method for all types, unlike above.
+      assertEquals(Type.INT8, rr.getColumnType(allTypesSchema.getColumnByIndex(0).getName()));
+      assertEquals(Type.INT8, rr.getColumnType(0));
+      assertEquals(Type.INT16, rr.getColumnType(1));
+      assertEquals(Type.INT32, rr.getColumnType(2));
+      assertEquals(Type.INT64, rr.getColumnType(3));
+      assertEquals(Type.BOOL, rr.getColumnType(4));
+      assertEquals(Type.FLOAT, rr.getColumnType(5));
+      assertEquals(Type.DOUBLE, rr.getColumnType(6));
+      assertEquals(Type.STRING, rr.getColumnType(7));
+      assertEquals(Type.BINARY, rr.getColumnType(8));
+      assertEquals(Type.TIMESTAMP, rr.getColumnType(11));
+    }
+  }
+}