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:35 UTC

[30/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-tools/src/test/java/org/kududb/mapreduce/tools/ITImportCsv.java
----------------------------------------------------------------------
diff --git a/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITImportCsv.java b/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITImportCsv.java
deleted file mode 100644
index d7b8352..0000000
--- a/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITImportCsv.java
+++ /dev/null
@@ -1,123 +0,0 @@
-// 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.mapreduce.tools;
-
-import org.kududb.ColumnSchema;
-import org.kududb.Schema;
-import org.kududb.Type;
-import org.kududb.mapreduce.CommandLineParser;
-import org.kududb.mapreduce.HadoopTestingUtility;
-import org.kududb.client.BaseKuduTest;
-import org.kududb.client.CreateTableOptions;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.mapreduce.Job;
-import org.apache.hadoop.util.GenericOptionsParser;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.collect.ImmutableList;
-
-public class ITImportCsv extends BaseKuduTest {
-
-  private static final String TABLE_NAME =
-      ITImportCsv.class.getName() + "-" + System.currentTimeMillis();
-
-  private static final HadoopTestingUtility HADOOP_UTIL = new HadoopTestingUtility();
-
-  private static Schema schema;
-
-  @BeforeClass
-  public static void setUpBeforeClass() throws Exception {
-    BaseKuduTest.setUpBeforeClass();
-
-    ArrayList<ColumnSchema> columns = new ArrayList<ColumnSchema>(4);
-    columns.add(new ColumnSchema.ColumnSchemaBuilder("key", Type.INT32)
-        .key(true)
-        .build());
-    columns.add(new ColumnSchema.ColumnSchemaBuilder("column1_i", Type.INT32)
-        .build());
-    columns.add(new ColumnSchema.ColumnSchemaBuilder("column2_d", Type.DOUBLE)
-        .build());
-    columns.add(new ColumnSchema.ColumnSchemaBuilder("column3_s", Type.STRING)
-        .nullable(true)
-        .build());
-    columns.add(new ColumnSchema.ColumnSchemaBuilder("column4_b", Type.BOOL)
-        .build());
-    schema = new Schema(columns);
-
-    createTable(TABLE_NAME, schema,
-                new CreateTableOptions().setRangePartitionColumns(ImmutableList.of("key")));
-  }
-
-  @AfterClass
-  public static void tearDownAfterClass() throws Exception {
-    try {
-      BaseKuduTest.tearDownAfterClass();
-    } finally {
-      HADOOP_UTIL.cleanup();
-    }
-  }
-
-  @Test
-  public void test() throws Exception {
-    Configuration conf = new Configuration();
-    String testHome =
-        HADOOP_UTIL.setupAndGetTestDir(ITImportCsv.class.getName(), conf).getAbsolutePath();
-
-    // Create a 2 lines input file
-    File data = new File(testHome, "data.csv");
-    writeCsvFile(data);
-
-    StringBuilder sb = new StringBuilder();
-    for (ColumnSchema col : schema.getColumns()) {
-      sb.append(col.getName());
-      sb.append(",");
-    }
-    sb.deleteCharAt(sb.length() - 1);
-    String[] args = new String[] {
-        "-D" + CommandLineParser.MASTER_ADDRESSES_KEY + "=" + getMasterAddresses(),
-        sb.toString(), TABLE_NAME, data.toString()};
-
-    GenericOptionsParser parser = new GenericOptionsParser(conf, args);
-    Job job = ImportCsv.createSubmittableJob(parser.getConfiguration(), parser.getRemainingArgs());
-    assertTrue("Test job did not end properly", job.waitForCompletion(true));
-
-    assertEquals(1, job.getCounters().findCounter(ImportCsv.Counters.BAD_LINES).getValue());
-
-    assertEquals(3, countRowsInScan(
-        client.newScannerBuilder(openTable(TABLE_NAME)).build()));
-    // TODO: should verify the actual returned rows, not just the count!
-  }
-
-  private void writeCsvFile(File data) throws IOException {
-    FileOutputStream fos = new FileOutputStream(data);
-    fos.write("1\t3\t2.3\tsome string\ttrue\n".getBytes());
-    fos.write("2\t5\t4.5\tsome more\tfalse\n".getBytes());
-    fos.write("3\t7\twait this is not a double\tbad row\ttrue\n".getBytes());
-    fos.write("4\t9\t10\ttrailing separator isn't bad mkay?\ttrue\t\n".getBytes());
-    fos.close();
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITIntegrationTestBigLinkedList.java
----------------------------------------------------------------------
diff --git a/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITIntegrationTestBigLinkedList.java b/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITIntegrationTestBigLinkedList.java
deleted file mode 100644
index 311c5ee..0000000
--- a/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITIntegrationTestBigLinkedList.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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.mapreduce.tools;
-
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.util.ToolRunner;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Test;
-import org.kududb.client.BaseKuduTest;
-import org.kududb.mapreduce.CommandLineParser;
-import org.kududb.mapreduce.HadoopTestingUtility;
-
-public class ITIntegrationTestBigLinkedList extends BaseKuduTest {
-
-  private static final HadoopTestingUtility HADOOP_UTIL = new HadoopTestingUtility();
-
-  @AfterClass
-  public static void tearDownAfterClass() throws Exception {
-    try {
-      BaseKuduTest.tearDownAfterClass();
-    } finally {
-      HADOOP_UTIL.cleanup();
-    }
-  }
-
-  @Test
-  public void test() throws Exception {
-    Configuration conf = new Configuration();
-    HADOOP_UTIL.setupAndGetTestDir(
-        ITIntegrationTestBigLinkedList.class.getName(),conf).getAbsolutePath();
-
-    String[] args = new String[] {
-        "-D" + CommandLineParser.MASTER_ADDRESSES_KEY + "=" + getMasterAddresses(),
-        "Loop",
-        "2", // Two iterations
-        "1", // 1 mapper
-        "2500", // 2.5k rows to insert
-        "1", // 1 tablet
-        "/tmp/itbll", // output dir
-        "1", // 1 reduce
-        "100", // create 100 columns
-        "25", // wrap them together after 25 rows
-        "0"
-    };
-    int ret = ToolRunner.run(new IntegrationTestBigLinkedList(), args);
-    Assert.assertEquals(0, ret);
-
-    args[2] = "1"; // Just one iteration this time
-    args[10] = "5000"; // 2 * 2500 from previous run
-    ret = ToolRunner.run(new IntegrationTestBigLinkedList(), args);
-    Assert.assertEquals(0, ret);
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITRowCounter.java
----------------------------------------------------------------------
diff --git a/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITRowCounter.java b/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITRowCounter.java
deleted file mode 100644
index 52984bb..0000000
--- a/java/kudu-client-tools/src/test/java/org/kududb/mapreduce/tools/ITRowCounter.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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.mapreduce.tools;
-
-import org.kududb.mapreduce.CommandLineParser;
-import org.kududb.mapreduce.HadoopTestingUtility;
-import org.kududb.client.BaseKuduTest;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.mapreduce.Job;
-import org.apache.hadoop.util.GenericOptionsParser;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class ITRowCounter extends BaseKuduTest {
-
-  private static final String TABLE_NAME =
-      ITRowCounter.class.getName() + "-" + System.currentTimeMillis();
-
-  private static final HadoopTestingUtility HADOOP_UTIL = new HadoopTestingUtility();
-
-  @BeforeClass
-  public static void setUpBeforeClass() throws Exception {
-    BaseKuduTest.setUpBeforeClass();
-  }
-
-  @AfterClass
-  public static void tearDownAfterClass() throws Exception {
-    try {
-      BaseKuduTest.tearDownAfterClass();
-    } finally {
-      HADOOP_UTIL.cleanup();
-    }
-  }
-
-  @Test
-  public void test() throws Exception {
-    Configuration conf = new Configuration();
-    HADOOP_UTIL.setupAndGetTestDir(ITRowCounter.class.getName(), conf).getAbsolutePath();
-
-    createFourTabletsTableWithNineRows(TABLE_NAME);
-
-    String[] args = new String[] {
-        "-D" + CommandLineParser.MASTER_ADDRESSES_KEY + "=" + getMasterAddresses(), TABLE_NAME};
-    GenericOptionsParser parser = new GenericOptionsParser(conf, args);
-    Job job = RowCounter.createSubmittableJob(parser.getConfiguration(), parser.getRemainingArgs());
-    assertTrue("Job did not end properly", job.waitForCompletion(true));
-
-    assertEquals(9, job.getCounters().findCounter(RowCounter.Counters.ROWS).getValue());
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.java b/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.java
new file mode 100644
index 0000000..c3c617d
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/ColumnSchema.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;
+
+import org.kududb.Common.CompressionType;
+import org.kududb.Common.EncodingType;
+import org.kududb.annotations.InterfaceAudience;
+import org.kududb.annotations.InterfaceStability;
+
+/**
+ * Represents a Kudu Table column. Use {@link ColumnSchema.ColumnSchemaBuilder} in order to
+ * create columns.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public class ColumnSchema {
+
+  private final String name;
+  private final Type type;
+  private final boolean key;
+  private final boolean nullable;
+  private final Object defaultValue;
+  private final int desiredBlockSize;
+  private final Encoding encoding;
+  private final CompressionAlgorithm compressionAlgorithm;
+
+  /**
+   * Specifies the encoding of data for a column on disk.
+   * Not all encodings are available for all data types.
+   * Refer to the Kudu documentation for more information on each encoding.
+   */
+  public enum Encoding {
+    UNKNOWN(EncodingType.UNKNOWN_ENCODING),
+    AUTO_ENCODING(EncodingType.AUTO_ENCODING),
+    PLAIN_ENCODING(EncodingType.PLAIN_ENCODING),
+    PREFIX_ENCODING(EncodingType.PREFIX_ENCODING),
+    GROUP_VARINT(EncodingType.GROUP_VARINT),
+    RLE(EncodingType.RLE),
+    DICT_ENCODING(EncodingType.DICT_ENCODING),
+    BIT_SHUFFLE(EncodingType.BIT_SHUFFLE);
+
+    final EncodingType internalPbType;
+
+    Encoding(EncodingType internalPbType) {
+      this.internalPbType = internalPbType;
+    }
+
+    @InterfaceAudience.Private
+    public EncodingType getInternalPbType() {
+      return internalPbType;
+    }
+  };
+
+  /**
+   * Specifies the compression algorithm of data for a column on disk.
+   */
+  public enum CompressionAlgorithm {
+    UNKNOWN(CompressionType.UNKNOWN_COMPRESSION),
+    DEFAULT_COMPRESSION(CompressionType.DEFAULT_COMPRESSION),
+    NO_COMPRESSION(CompressionType.NO_COMPRESSION),
+    SNAPPY(CompressionType.SNAPPY),
+    LZ4(CompressionType.LZ4),
+    ZLIB(CompressionType.ZLIB);
+
+    final CompressionType internalPbType;
+
+    CompressionAlgorithm(CompressionType internalPbType) {
+      this.internalPbType = internalPbType;
+    }
+
+    @InterfaceAudience.Private
+    public CompressionType getInternalPbType() {
+      return internalPbType;
+    }
+  };
+
+  private ColumnSchema(String name, Type type, boolean key, boolean nullable,
+                       Object defaultValue, int desiredBlockSize, Encoding encoding,
+                       CompressionAlgorithm compressionAlgorithm) {
+    this.name = name;
+    this.type = type;
+    this.key = key;
+    this.nullable = nullable;
+    this.defaultValue = defaultValue;
+    this.desiredBlockSize = desiredBlockSize;
+    this.encoding = encoding;
+    this.compressionAlgorithm = compressionAlgorithm;
+  }
+
+  /**
+   * Get the column's Type
+   * @return the type
+   */
+  public Type getType() {
+    return type;
+  }
+
+  /**
+   * Get the column's name
+   * @return A string representation of the name
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Answers if the column part of the key
+   * @return true if the column is part of the key, else false
+   */
+  public boolean isKey() {
+    return key;
+  }
+
+  /**
+   * Answers if the column can be set to null
+   * @return true if it can be set to null, else false
+   */
+  public boolean isNullable() {
+    return nullable;
+  }
+
+  /**
+   * The Java object representation of the default value that's read
+   * @return the default read value
+   */
+  public Object getDefaultValue() {
+    return defaultValue;
+  }
+
+  /**
+   * Gets the desired block size for this column.
+   * If no block size has been explicitly specified for this column,
+   * returns 0 to indicate that the server-side default will be used.
+   *
+   * @return the block size, in bytes, or 0 if none has been configured.
+   */
+  public int getDesiredBlockSize() {
+    return desiredBlockSize;
+  }
+
+  /**
+   * Return the encoding of this column, or null if it is not known.
+   */
+  public Encoding getEncoding() {
+    return encoding;
+  }
+
+  /**
+   * Return the compression algorithm of this column, or null if it is not known.
+   */
+  public CompressionAlgorithm getCompressionAlgorithm() {
+    return compressionAlgorithm;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ColumnSchema that = (ColumnSchema) o;
+
+    if (key != that.key) return false;
+    if (!name.equals(that.name)) return false;
+    if (!type.equals(that.type)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = name.hashCode();
+    result = 31 * result + type.hashCode();
+    result = 31 * result + (key ? 1 : 0);
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "Column name: " + name + ", type: " + type.getName();
+  }
+
+  /**
+   * Builder for ColumnSchema.
+   */
+  public static class ColumnSchemaBuilder {
+    private final String name;
+    private final Type type;
+    private boolean key = false;
+    private boolean nullable = false;
+    private Object defaultValue = null;
+    private int blockSize = 0;
+    private Encoding encoding = null;
+    private CompressionAlgorithm compressionAlgorithm = null;
+
+    /**
+     * Constructor for the required parameters.
+     * @param name column's name
+     * @param type column's type
+     */
+    public ColumnSchemaBuilder(String name, Type type) {
+      this.name = name;
+      this.type = type;
+    }
+
+    /**
+     * Sets if the column is part of the row key. False by default.
+     * @param key a boolean that indicates if the column is part of the key
+     * @return this instance
+     */
+    public ColumnSchemaBuilder key(boolean key) {
+      this.key = key;
+      return this;
+    }
+
+    /**
+     * Marks the column as allowing null values. False by default.
+     * @param nullable a boolean that indicates if the column allows null values
+     * @return this instance
+     */
+    public ColumnSchemaBuilder nullable(boolean nullable) {
+      this.nullable = nullable;
+      return this;
+    }
+
+    /**
+     * Sets the default value that will be read from the column. Null by default.
+     * @param defaultValue a Java object representation of the default value that's read
+     * @return this instance
+     */
+    public ColumnSchemaBuilder defaultValue(Object defaultValue) {
+      this.defaultValue = defaultValue;
+      return this;
+    }
+
+    /**
+     * Set the desired block size for this column.
+     *
+     * This is the number of bytes of user data packed per block on disk, and
+     * represents the unit of IO when reading this column. Larger values
+     * may improve scan performance, particularly on spinning media. Smaller
+     * values may improve random access performance, particularly for workloads
+     * that have high cache hit rates or operate on fast storage such as SSD.
+     *
+     * Note that the block size specified here corresponds to uncompressed data.
+     * The actual size of the unit read from disk may be smaller if
+     * compression is enabled.
+     *
+     * It's recommended that this not be set any lower than 4096 (4KB) or higher
+     * than 1048576 (1MB).
+     * @param blockSize the desired block size, in bytes
+     * @return this instance
+     * <!-- TODO(KUDU-1107): move the above info to docs -->
+     */
+    public ColumnSchemaBuilder desiredBlockSize(int blockSize) {
+      this.blockSize = blockSize;
+      return this;
+    }
+
+    /**
+     * Set the block encoding for this column. See the documentation for the list
+     * of valid options.
+     */
+    public ColumnSchemaBuilder encoding(Encoding encoding) {
+      this.encoding = encoding;
+      return this;
+    }
+
+    /**
+     * Set the compression algorithm for this column. See the documentation for the list
+     * of valid options.
+     */
+    public ColumnSchemaBuilder compressionAlgorithm(CompressionAlgorithm compressionAlgorithm) {
+      this.compressionAlgorithm = compressionAlgorithm;
+      return this;
+    }
+
+    /**
+     * Builds a {@link ColumnSchema} using the passed parameters.
+     * @return a new {@link ColumnSchema}
+     */
+    public ColumnSchema build() {
+      return new ColumnSchema(name, type,
+                              key, nullable, defaultValue,
+                              blockSize, encoding, compressionAlgorithm);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/main/java/org/apache/kudu/Schema.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/Schema.java b/java/kudu-client/src/main/java/org/apache/kudu/Schema.java
new file mode 100644
index 0000000..17e7798
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/Schema.java
@@ -0,0 +1,291 @@
+// 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;
+
+import com.google.common.collect.ImmutableList;
+import org.kududb.annotations.InterfaceAudience;
+import org.kududb.annotations.InterfaceStability;
+import org.kududb.client.Bytes;
+import org.kududb.client.PartialRow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents table's schema which is essentially a list of columns.
+ * This class offers a few utility methods for querying it.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public class Schema {
+
+  /**
+   * Mapping of column index to column.
+   */
+  private final List<ColumnSchema> columnsByIndex;
+
+  /**
+   * The primary key columns.
+   */
+  private final List<ColumnSchema> primaryKeyColumns = new ArrayList<>();
+
+  /**
+   * Mapping of column name to index.
+   */
+  private final Map<String, Integer> columnsByName;
+
+  /**
+   * Mapping of column ID to index, or null if the schema does not have assigned column IDs.
+   */
+  private final Map<Integer, Integer> columnsById;
+
+  /**
+   * Mapping of column index to backing byte array offset.
+   */
+  private final int[] columnOffsets;
+
+  private final int varLengthColumnCount;
+  private final int rowSize;
+  private final boolean hasNullableColumns;
+
+  /**
+   * Constructs a schema using the specified columns and does some internal accounting
+   * @param columns the columns in index order
+   * @throws IllegalArgumentException If the key columns aren't specified first
+   *
+   * See {@code ColumnPBsToSchema()} in {@code src/kudu/common/wire_protocol.cc}
+   */
+  public Schema(List<ColumnSchema> columns) {
+    this(columns, null);
+  }
+
+  /**
+   * Constructs a schema using the specified columns and IDs.
+   *
+   * This is not a stable API, prefer using {@link Schema#Schema(List)} to create a new schema.
+   *
+   * @param columns the columns in index order
+   * @param columnIds the column ids of the provided columns, or null
+   * @throws IllegalArgumentException If the primary key columns aren't specified first
+   * @throws IllegalArgumentException If the column ids length does not match the columns length
+   *
+   * See {@code ColumnPBsToSchema()} in {@code src/kudu/common/wire_protocol.cc}
+   */
+  public Schema(List<ColumnSchema> columns, List<Integer> columnIds) {
+    boolean hasColumnIds = columnIds != null;
+    if (hasColumnIds && columns.size() != columnIds.size()) {
+      throw new IllegalArgumentException(
+          "Schema must be constructed with all column IDs, or none.");
+    }
+
+    this.columnsByIndex = ImmutableList.copyOf(columns);
+    int varLenCnt = 0;
+    this.columnOffsets = new int[columns.size()];
+    this.columnsByName = new HashMap<>(columns.size());
+    this.columnsById = hasColumnIds ? new HashMap<Integer, Integer>(columnIds.size()) : null;
+    int offset = 0;
+    boolean hasNulls = false;
+    // pre-compute a few counts and offsets
+    for (int index = 0; index < columns.size(); index++) {
+      final ColumnSchema column = columns.get(index);
+      if (column.isKey()) {
+        primaryKeyColumns.add(column);
+      }
+
+      hasNulls |= column.isNullable();
+      columnOffsets[index] = offset;
+      offset += column.getType().getSize();
+      if (this.columnsByName.put(column.getName(), index) != null) {
+        throw new IllegalArgumentException(
+            String.format("Column names must be unique: %s", columns));
+      }
+      if (column.getType() == Type.STRING || column.getType() == Type.BINARY) {
+        varLenCnt++;
+      }
+
+      if (hasColumnIds) {
+        if (this.columnsById.put(columnIds.get(index), index) != null) {
+          throw new IllegalArgumentException(
+              String.format("Column IDs must be unique: %s", columnIds));
+        }
+      }
+    }
+
+    this.hasNullableColumns = hasNulls;
+    this.varLengthColumnCount = varLenCnt;
+    this.rowSize = getRowSize(this.columnsByIndex);
+  }
+
+  /**
+   * Get the list of columns used to create this schema
+   * @return list of columns
+   */
+  public List<ColumnSchema> getColumns() {
+    return this.columnsByIndex;
+  }
+
+  /**
+   * Get the count of columns with variable length (BINARY/STRING) in
+   * this schema.
+   * @return strings count
+   */
+  public int getVarLengthColumnCount() {
+    return this.varLengthColumnCount;
+  }
+
+  /**
+   * Get the size a row built using this schema would be
+   * @return size in bytes
+   */
+  public int getRowSize() {
+    return this.rowSize;
+  }
+
+  /**
+   * Get the index at which this column can be found in the backing byte array
+   * @param idx column's index
+   * @return column's offset
+   */
+  public int getColumnOffset(int idx) {
+    return this.columnOffsets[idx];
+  }
+
+  /**
+   * Get the index for the provided column name.
+   * @param columnName column to search for
+   * @return an index in the schema
+   */
+  public int getColumnIndex(String columnName) {
+    Integer index = this.columnsByName.get(columnName);
+    if (index == null) {
+      throw new IllegalArgumentException(
+          String.format("Unknown column: %s", columnName));
+    }
+    return index;
+  }
+
+  /**
+   * Get the column at the specified index in the original list
+   * @param idx column's index
+   * @return the column
+   */
+  public ColumnSchema getColumnByIndex(int idx) {
+    return this.columnsByIndex.get(idx);
+  }
+
+  /**
+   * Get the column index of the column with the provided ID.
+   * This method is not part of the stable API.
+   * @param columnId the column id of the column
+   * @return the column index of the column.
+   */
+  public int getColumnIndex(int columnId) {
+    if (!hasColumnIds()) throw new IllegalStateException("Schema does not have Column IDs");
+    Integer index = this.columnsById.get(columnId);
+    if (index == null) throw new IllegalArgumentException(
+        String.format("Unknown column id: %s", columnId));
+    return index;
+  }
+
+  /**
+   * Get the column associated with the specified name
+   * @param columnName column's name
+   * @return the column
+   */
+  public ColumnSchema getColumn(String columnName) {
+    return columnsByIndex.get(getColumnIndex(columnName));
+  }
+
+  /**
+   * Get the count of columns in this schema
+   * @return count of columns
+   */
+  public int getColumnCount() {
+    return this.columnsByIndex.size();
+  }
+
+  /**
+   * Get the count of columns that are part of the primary key.
+   * @return count of primary key columns.
+   */
+  public int getPrimaryKeyColumnCount() {
+    return this.primaryKeyColumns.size();
+  }
+
+  /**
+   * Get the primary key columns.
+   * @return the primary key columns.
+   */
+  public List<ColumnSchema> getPrimaryKeyColumns() {
+    return primaryKeyColumns;
+  }
+
+  /**
+   * Get a schema that only contains the columns which are part of the key
+   * @return new schema with only the keys
+   */
+  public Schema getRowKeyProjection() {
+    return new Schema(primaryKeyColumns);
+  }
+
+  /**
+   * Tells if there's at least one nullable column
+   * @return true if at least one column is nullable, else false.
+   */
+  public boolean hasNullableColumns() {
+    return this.hasNullableColumns;
+  }
+
+  /**
+   * Tells whether this schema includes IDs for columns. A schema created by a client as part of
+   * table creation will not include IDs, but schemas for open tables will include IDs.
+   * This method is not part of the stable API.
+   *
+   * @return whether this schema includes column IDs.
+   */
+  public boolean hasColumnIds() {
+    return columnsById != null;
+  }
+
+  /**
+   * Gives the size in bytes for a single row given the specified schema
+   * @param columns the row's columns
+   * @return row size in bytes
+   */
+  private static int getRowSize(List<ColumnSchema> columns) {
+    int totalSize = 0;
+    boolean hasNullables = false;
+    for (ColumnSchema column : columns) {
+      totalSize += column.getType().getSize();
+      hasNullables |= column.isNullable();
+    }
+    if (hasNullables) {
+      totalSize += Bytes.getBitSetSize(columns.size());
+    }
+    return totalSize;
+  }
+
+  /**
+   * Creates a new partial row for the schema.
+   * @return a new partial row
+   */
+  public PartialRow newPartialRow() {
+    return new PartialRow(this);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/main/java/org/apache/kudu/Type.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/Type.java b/java/kudu-client/src/main/java/org/apache/kudu/Type.java
new file mode 100644
index 0000000..fd23835
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/Type.java
@@ -0,0 +1,136 @@
+// 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;
+
+import com.google.common.primitives.Ints;
+import com.google.common.primitives.Longs;
+import com.google.common.primitives.Shorts;
+import org.kududb.annotations.InterfaceAudience;
+import org.kududb.annotations.InterfaceStability;
+
+import static org.kududb.Common.DataType;
+
+/**
+ * Describes all the types available to build table schemas.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public enum Type {
+
+  INT8 (DataType.INT8, "int8"),
+  INT16 (DataType.INT16, "int16"),
+  INT32 (DataType.INT32, "int32"),
+  INT64 (DataType.INT64, "int64"),
+  BINARY (DataType.BINARY, "binary"),
+  STRING (DataType.STRING, "string"),
+  BOOL (DataType.BOOL, "bool"),
+  FLOAT (DataType.FLOAT, "float"),
+  DOUBLE (DataType.DOUBLE, "double"),
+  TIMESTAMP (DataType.TIMESTAMP, "timestamp");
+
+
+  private final DataType dataType;
+  private final String name;
+  private final int size;
+
+  /**
+   * Private constructor used to pre-create the types
+   * @param dataType DataType from the common's pb
+   * @param name string representation of the type
+   */
+  private Type(DataType dataType, String name) {
+    this.dataType = dataType;
+    this.name = name;
+    this.size = getTypeSize(this.dataType);
+  }
+
+  /**
+   * Get the data type from the common's pb
+   * @return A DataType
+   */
+  public DataType getDataType() {
+    return this.dataType;
+  }
+
+  /**
+   * Get the string representation of this type
+   * @return The type's name
+   */
+  public String getName() {
+    return this.name;
+  }
+
+  /**
+   * The size of this type on the wire
+   * @return A size
+   */
+  public int getSize() {
+    return this.size;
+  }
+
+  @Override
+  public String toString() {
+    return "Type: " + this.name + ", size: " + this.size;
+  }
+
+  /**
+   * Gives the size in bytes for a given DataType, as per the pb specification
+   * @param type pb type
+   * @return size in bytes
+   */
+  static int getTypeSize(DataType type) {
+    switch (type) {
+      case STRING:
+      case BINARY: return 8 + 8; // offset then string length
+      case BOOL:
+      case INT8: return 1;
+      case INT16: return Shorts.BYTES;
+      case INT32:
+      case FLOAT: return Ints.BYTES;
+      case INT64:
+      case DOUBLE:
+      case TIMESTAMP: return Longs.BYTES;
+      default: throw new IllegalArgumentException("The provided data type doesn't map" +
+          " to know any known one.");
+    }
+  }
+
+  /**
+   * Convert the pb DataType to a Type
+   * @param type DataType to convert
+   * @return a matching Type
+   */
+  public static Type getTypeForDataType(DataType type) {
+    switch (type) {
+      case STRING: return STRING;
+      case BINARY: return BINARY;
+      case BOOL: return BOOL;
+      case INT8: return INT8;
+      case INT16: return INT16;
+      case INT32: return INT32;
+      case INT64: return INT64;
+      case TIMESTAMP: return TIMESTAMP;
+      case FLOAT: return FLOAT;
+      case DOUBLE: return DOUBLE;
+      default:
+        throw new IllegalArgumentException("The provided data type doesn't map" +
+            " to know any known one: " + type.getDescriptorForType().getFullName());
+
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/main/java/org/apache/kudu/client/AbstractKuduScannerBuilder.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AbstractKuduScannerBuilder.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AbstractKuduScannerBuilder.java
new file mode 100644
index 0000000..b1b0712
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AbstractKuduScannerBuilder.java
@@ -0,0 +1,338 @@
+// 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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableList;
+import org.kududb.Common;
+import org.kududb.annotations.InterfaceAudience;
+import org.kududb.annotations.InterfaceStability;
+import org.kududb.tserver.Tserver;
+import org.kududb.util.HybridTimeUtil;
+
+/**
+ * Abstract class to extend in order to create builders for scanners.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public abstract class AbstractKuduScannerBuilder
+    <S extends AbstractKuduScannerBuilder<? super S, T>, T> {
+  final AsyncKuduClient client;
+  final KuduTable table;
+
+  /** Map of column name to predicate */
+  final Map<String, KuduPredicate> predicates = new HashMap<>();
+
+  AsyncKuduScanner.ReadMode readMode = AsyncKuduScanner.ReadMode.READ_LATEST;
+  Common.OrderMode orderMode = Common.OrderMode.UNORDERED;
+  int batchSizeBytes = 1024*1024;
+  long limit = Long.MAX_VALUE;
+  boolean prefetching = false;
+  boolean cacheBlocks = true;
+  long htTimestamp = AsyncKuduClient.NO_TIMESTAMP;
+  byte[] lowerBoundPrimaryKey = AsyncKuduClient.EMPTY_ARRAY;
+  byte[] upperBoundPrimaryKey = AsyncKuduClient.EMPTY_ARRAY;
+  byte[] lowerBoundPartitionKey = AsyncKuduClient.EMPTY_ARRAY;
+  byte[] upperBoundPartitionKey = AsyncKuduClient.EMPTY_ARRAY;
+  List<String> projectedColumnNames = null;
+  List<Integer> projectedColumnIndexes = null;
+  long scanRequestTimeout;
+
+  AbstractKuduScannerBuilder(AsyncKuduClient client, KuduTable table) {
+    this.client = client;
+    this.table = table;
+    this.scanRequestTimeout = client.getDefaultOperationTimeoutMs();
+  }
+
+  /**
+   * Sets the read mode, the default is to read the latest values.
+   * @param readMode a read mode for the scanner
+   * @return this instance
+   */
+  public S readMode(AsyncKuduScanner.ReadMode readMode) {
+    this.readMode = readMode;
+    return (S) this;
+  }
+
+  /**
+   * Return scan results in primary key sorted order.
+   *
+   * If the table is hash partitioned, the scan must have an equality predicate
+   * on all hashed columns.
+   *
+   * Package private until proper hash partitioning equality predicate checks
+   * are in place.
+   *
+   * Disabled by default.
+   * @return this instance
+   */
+  @InterfaceAudience.Private
+  @InterfaceStability.Unstable
+  S sortResultsByPrimaryKey() {
+    orderMode = Common.OrderMode.ORDERED;
+    readMode = AsyncKuduScanner.ReadMode.READ_AT_SNAPSHOT;
+    return (S) this;
+  }
+
+  /**
+   * Adds a predicate for a column.
+   * @param predicate predicate for a column to add
+   * @return this instance
+   * @deprecated use {@link #addPredicate(KuduPredicate)}
+   */
+  @Deprecated
+  public S addColumnRangePredicate(ColumnRangePredicate predicate) {
+    return addPredicate(predicate.toKuduPredicate());
+  }
+
+  /**
+   * Adds a list of predicates in their raw format,
+   * as given by {@link ColumnRangePredicate#toByteArray(List)}.
+   * @param predicateBytes predicates to add
+   * @return this instance
+   * @throws IllegalArgumentException thrown when the passed bytes aren't valid
+   * @deprecated use {@link #addPredicate}
+   */
+  @Deprecated
+  public S addColumnRangePredicatesRaw(byte[] predicateBytes) {
+    for (Tserver.ColumnRangePredicatePB pb : ColumnRangePredicate.fromByteArray(predicateBytes)) {
+      addPredicate(ColumnRangePredicate.fromPb(pb).toKuduPredicate());
+    }
+    return (S) this;
+  }
+
+  /**
+   * Adds a predicate to the scan.
+   * @param predicate predicate to add
+   * @return this instance
+   */
+  public S addPredicate(KuduPredicate predicate) {
+    String columnName = predicate.getColumn().getName();
+    KuduPredicate existing = predicates.get(columnName);
+    if (existing == null) {
+      predicates.put(columnName, predicate);
+    } else {
+      predicates.put(columnName, existing.merge(predicate));
+    }
+    return (S) this;
+  }
+
+  /**
+   * Set which columns will be read by the Scanner.
+   * Calling this method after {@link #setProjectedColumnIndexes(List)} will reset the projected
+   * columns to those specified in {@code columnNames}.
+   * @param columnNames the names of columns to read, or 'null' to read all columns
+   * (the default)
+   */
+  public S setProjectedColumnNames(List<String> columnNames) {
+    projectedColumnIndexes = null;
+    if (columnNames != null) {
+      projectedColumnNames = ImmutableList.copyOf(columnNames);
+    } else {
+      projectedColumnNames = null;
+    }
+    return (S) this;
+  }
+
+  /**
+   * Set which columns will be read by the Scanner.
+   * Calling this method after {@link #setProjectedColumnNames(List)} will reset the projected
+   * columns to those specified in {@code columnIndexes}.
+   * @param columnIndexes the indexes of columns to read, or 'null' to read all columns
+   * (the default)
+   */
+  public S setProjectedColumnIndexes(List<Integer> columnIndexes) {
+    projectedColumnNames = null;
+    if (columnIndexes != null) {
+      projectedColumnIndexes = ImmutableList.copyOf(columnIndexes);
+    } else {
+      projectedColumnIndexes = null;
+    }
+    return (S) this;
+  }
+
+  /**
+   * Sets the maximum number of bytes returned by the scanner, on each batch. The default is 1MB.
+   * <p>
+   * Kudu may actually return more than this many bytes because it will not
+   * truncate a rowResult in the middle.
+   * @param batchSizeBytes a strictly positive number of bytes
+   * @return this instance
+   */
+  public S batchSizeBytes(int batchSizeBytes) {
+    this.batchSizeBytes = batchSizeBytes;
+    return (S) this;
+  }
+
+  /**
+   * Sets a limit on the number of rows that will be returned by the scanner. There's no limit
+   * by default.
+   * @param limit a positive long
+   * @return this instance
+   */
+  public S limit(long limit) {
+    this.limit = limit;
+    return (S) this;
+  }
+
+  /**
+   * Enables prefetching of rows for the scanner, i.e. whether to send a request for more data
+   * to the server immediately after we receive a response (instead of waiting for the user
+   * to call {@code  nextRows()}). Disabled by default.
+   * NOTE: This is risky until KUDU-1260 is resolved.
+   * @param prefetching a boolean that indicates if the scanner should prefetch rows
+   * @return this instance
+   */
+  public S prefetching(boolean prefetching) {
+    this.prefetching = prefetching;
+    return (S) this;
+  }
+
+  /**
+   * Sets the block caching policy for the scanner. If true, scanned data blocks will be cached
+   * in memory and made available for future scans. Enabled by default.
+   * @param cacheBlocks a boolean that indicates if data blocks should be cached or not
+   * @return this instance
+   */
+  public S cacheBlocks(boolean cacheBlocks) {
+    this.cacheBlocks = cacheBlocks;
+    return (S) this;
+  }
+
+  /**
+   * Sets a previously encoded HT timestamp as a snapshot timestamp, for tests. None is used by
+   * default.
+   * Requires that the ReadMode is READ_AT_SNAPSHOT.
+   * @param htTimestamp a long representing a HybridClock-encoded timestamp
+   * @return this instance
+   * @throws IllegalArgumentException on build(), if the timestamp is less than 0 or if the
+   *                                  read mode was not set to READ_AT_SNAPSHOT
+   */
+  @InterfaceAudience.Private
+  public S snapshotTimestampRaw(long htTimestamp) {
+    this.htTimestamp = htTimestamp;
+    return (S) this;
+  }
+
+  /**
+   * Sets the timestamp the scan must be executed at, in microseconds since the Unix epoch. None is
+   * used by default.
+   * Requires that the ReadMode is READ_AT_SNAPSHOT.
+   * @param timestamp a long representing an instant in microseconds since the unix epoch.
+   * @return this instance
+   * @throws IllegalArgumentException on build(), if the timestamp is less than 0 or if the
+   *                                  read mode was not set to READ_AT_SNAPSHOT
+   */
+  public S snapshotTimestampMicros(long timestamp) {
+    this.htTimestamp = HybridTimeUtil.physicalAndLogicalToHTTimestamp(timestamp, 0);
+    return (S) this;
+  }
+
+  /**
+   * Sets how long each scan request to a server can last.
+   * Defaults to {@link KuduClient#getDefaultOperationTimeoutMs()}.
+   * @param scanRequestTimeout a long representing time in milliseconds
+   * @return this instance
+   */
+  public S scanRequestTimeout(long scanRequestTimeout) {
+    this.scanRequestTimeout = scanRequestTimeout;
+    return (S) this;
+  }
+
+  /**
+   * Add a lower bound (inclusive) primary key for the scan.
+   * If any bound is already added, this bound is intersected with that one.
+   * @param partialRow a partial row with specified key columns
+   * @return this instance
+   */
+  public S lowerBound(PartialRow partialRow) {
+    return lowerBoundRaw(partialRow.encodePrimaryKey());
+  }
+
+  /**
+   * Like lowerBoundPrimaryKey() but the encoded primary key is an opaque byte array obtained elsewhere.
+   * @param startPrimaryKey bytes containing an encoded start key
+   * @return this instance
+   * @deprecated use {@link #lowerBound(PartialRow)}
+   */
+  @Deprecated
+  public S lowerBoundRaw(byte[] startPrimaryKey) {
+    if (lowerBoundPrimaryKey == AsyncKuduClient.EMPTY_ARRAY ||
+        Bytes.memcmp(startPrimaryKey, lowerBoundPrimaryKey) > 0) {
+      this.lowerBoundPrimaryKey = startPrimaryKey;
+    }
+    return (S) this;
+  }
+
+  /**
+   * Add an upper bound (exclusive) primary key for the scan.
+   * If any bound is already added, this bound is intersected with that one.
+   * @param partialRow a partial row with specified key columns
+   * @return this instance
+   */
+  public S exclusiveUpperBound(PartialRow partialRow) {
+    return exclusiveUpperBoundRaw(partialRow.encodePrimaryKey());
+  }
+
+  /**
+   * Like exclusiveUpperBound() but the encoded primary key is an opaque byte array obtained elsewhere.
+   * @param endPrimaryKey bytes containing an encoded end key
+   * @return this instance
+   * @deprecated use {@link #exclusiveUpperBound(PartialRow)}
+   */
+  @Deprecated
+  public S exclusiveUpperBoundRaw(byte[] endPrimaryKey) {
+    if (upperBoundPrimaryKey == AsyncKuduClient.EMPTY_ARRAY ||
+        Bytes.memcmp(endPrimaryKey, upperBoundPrimaryKey) < 0) {
+      this.upperBoundPrimaryKey = endPrimaryKey;
+    }
+    return (S) this;
+  }
+
+  /**
+   * Set an encoded (inclusive) start partition key for the scan.
+   *
+   * @param partitionKey the encoded partition key
+   * @return this instance
+   */
+  @InterfaceAudience.LimitedPrivate("Impala")
+  public S lowerBoundPartitionKeyRaw(byte[] partitionKey) {
+    if (Bytes.memcmp(partitionKey, lowerBoundPartitionKey) > 0) {
+      this.lowerBoundPartitionKey = partitionKey;
+    }
+    return (S) this;
+  }
+
+  /**
+   * Set an encoded (exclusive) end partition key for the scan.
+   *
+   * @param partitionKey the encoded partition key
+   * @return this instance
+   */
+  @InterfaceAudience.LimitedPrivate("Impala")
+  public S exclusiveUpperBoundPartitionKeyRaw(byte[] partitionKey) {
+    if (upperBoundPartitionKey.length == 0 || Bytes.memcmp(partitionKey, upperBoundPartitionKey) < 0) {
+      this.upperBoundPartitionKey = partitionKey;
+    }
+    return (S) this;
+  }
+
+  public abstract T build();
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableOptions.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableOptions.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableOptions.java
new file mode 100644
index 0000000..ecffb5e
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableOptions.java
@@ -0,0 +1,107 @@
+// 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.kududb.ColumnSchema;
+import org.kududb.Type;
+import org.kududb.annotations.InterfaceAudience;
+import org.kududb.annotations.InterfaceStability;
+
+import static org.kududb.master.Master.AlterTableRequestPB;
+
+/**
+ * This builder must be used to alter a table. At least one change must be specified.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Unstable
+public class AlterTableOptions {
+
+  AlterTableRequestPB.Builder pb = AlterTableRequestPB.newBuilder();
+
+  /**
+   * Change a table's name.
+   * @param newName new table's name, must be used to check progress
+   * @return this instance
+   */
+  public AlterTableOptions renameTable(String newName) {
+    pb.setNewTableName(newName);
+    return this;
+  }
+
+  /**
+   * Add a new column that's not nullable.
+   * @param name name of the new column
+   * @param type type of the new column
+   * @param defaultVal default value used for the currently existing rows
+   * @return this instance
+   */
+  public AlterTableOptions addColumn(String name, Type type, Object defaultVal) {
+    if (defaultVal == null) {
+      throw new IllegalArgumentException("A new column must have a default value, " +
+          "use addNullableColumn() to add a NULLABLE column");
+    }
+    AlterTableRequestPB.Step.Builder step = pb.addAlterSchemaStepsBuilder();
+    step.setType(AlterTableRequestPB.StepType.ADD_COLUMN);
+    step.setAddColumn(AlterTableRequestPB.AddColumn.newBuilder().setSchema(ProtobufHelper
+        .columnToPb(new ColumnSchema.ColumnSchemaBuilder(name, type)
+            .defaultValue(defaultVal)
+            .build())));
+    return this;
+  }
+
+  /**
+   * Add a new column that's nullable, thus has no default value.
+   * @param name name of the new column
+   * @param type type of the new column
+   * @return this instance
+   */
+  public AlterTableOptions addNullableColumn(String name, Type type) {
+    AlterTableRequestPB.Step.Builder step = pb.addAlterSchemaStepsBuilder();
+    step.setType(AlterTableRequestPB.StepType.ADD_COLUMN);
+    step.setAddColumn(AlterTableRequestPB.AddColumn.newBuilder().setSchema(ProtobufHelper
+        .columnToPb(new ColumnSchema.ColumnSchemaBuilder(name, type)
+            .nullable(true)
+            .build())));
+    return this;
+  }
+
+  /**
+   * Drop a column.
+   * @param name name of the column
+   * @return this instance
+   */
+  public AlterTableOptions dropColumn(String name) {
+    AlterTableRequestPB.Step.Builder step = pb.addAlterSchemaStepsBuilder();
+    step.setType(AlterTableRequestPB.StepType.DROP_COLUMN);
+    step.setDropColumn(AlterTableRequestPB.DropColumn.newBuilder().setName(name));
+    return this;
+  }
+
+  /**
+   * Change the name of a column.
+   * @param oldName old column's name, must exist
+   * @param newName new name to use
+   * @return this instance
+   */
+  public AlterTableOptions renameColumn(String oldName, String newName) {
+    AlterTableRequestPB.Step.Builder step = pb.addAlterSchemaStepsBuilder();
+    step.setType(AlterTableRequestPB.StepType.RENAME_COLUMN);
+    step.setRenameColumn(AlterTableRequestPB.RenameColumn.newBuilder().setOldName(oldName)
+        .setNewName(newName));
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableRequest.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableRequest.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableRequest.java
new file mode 100644
index 0000000..751290c
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableRequest.java
@@ -0,0 +1,70 @@
+// 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.protobuf.Message;
+import org.kududb.annotations.InterfaceAudience;
+import org.kududb.util.Pair;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+import static org.kududb.master.Master.*;
+
+/**
+ * RPC used to alter a table. When it returns it doesn't mean that the table is altered,
+ * a success just means that the master accepted it.
+ */
+@InterfaceAudience.Private
+class AlterTableRequest extends KuduRpc<AlterTableResponse> {
+
+  static final String ALTER_TABLE = "AlterTable";
+  private final String name;
+  private final AlterTableRequestPB.Builder builder;
+
+  AlterTableRequest(KuduTable masterTable, String name, AlterTableOptions ato) {
+    super(masterTable);
+    this.name = name;
+    this.builder = ato.pb;
+  }
+
+  @Override
+  ChannelBuffer serialize(Message header) {
+    assert header.isInitialized();
+    TableIdentifierPB tableID =
+        TableIdentifierPB.newBuilder().setTableName(name).build();
+    this.builder.setTable(tableID);
+    return toChannelBuffer(header, this.builder.build());
+  }
+
+  @Override
+  String serviceName() { return MASTER_SERVICE_NAME; }
+
+  @Override
+  String method() {
+    return ALTER_TABLE;
+  }
+
+  @Override
+  Pair<AlterTableResponse, Object> deserialize(final CallResponse callResponse,
+                                                String tsUUID) throws Exception {
+    final AlterTableResponsePB.Builder respBuilder = AlterTableResponsePB.newBuilder();
+    readProtobuf(callResponse.getPBMessage(), respBuilder);
+    AlterTableResponse response = new AlterTableResponse(deadlineTracker.getElapsedMillis(),
+        tsUUID);
+    return new Pair<AlterTableResponse, Object>(
+        response, respBuilder.hasError() ? respBuilder.getError() : null);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kudu/blob/5c305689/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableResponse.java
----------------------------------------------------------------------
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableResponse.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableResponse.java
new file mode 100644
index 0000000..7d1d581
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AlterTableResponse.java
@@ -0,0 +1,32 @@
+// 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.kududb.annotations.InterfaceAudience;
+import org.kududb.annotations.InterfaceStability;
+
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public class AlterTableResponse extends KuduRpcResponse {
+
+  /**
+   * @param ellapsedMillis Time in milliseconds since RPC creation to now.
+   */
+  AlterTableResponse(long ellapsedMillis, String tsUUID) {
+    super(ellapsedMillis, tsUUID);
+  }
+}
\ No newline at end of file