This is an automated email from the ASF dual-hosted git repository.

granthenke pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git


The following commit(s) were added to refs/heads/master by this push:
     new 62f6a30  [KUDU-2632] Add a DATE type backed by INT32 (Part 2, Java 
client)
62f6a30 is described below

commit 62f6a300a5c092a6c61c91fe1546014eb8787b6e
Author: Volodymyr Verovkin <[email protected]>
AuthorDate: Sun Oct 20 23:51:50 2019 -0700

    [KUDU-2632] Add a DATE type backed by INT32 (Part 2, Java client)
    
    This adds a new DATE type, represented by an INT32 and that should
    store the number of days from the Unix epoch, January 1, 1970.
    
    Range:
    from
    LocalDate.parse("0001-01-01").toEpochDay()
    to
    LocalDate.parse("9999-12-31").toEpochDay()
    
    Range validation is done in DateUtil::checkDateWithinRange() function.
    
    Change-Id: I9a09f4f9c804c1b2965e78da78528225a9c769e2
    Reviewed-on: http://gerrit.cloudera.org:8080/14517
    Tested-by: Kudu Jenkins
    Reviewed-by: Grant Henke <[email protected]>
    Tested-by: Alexey Serbin <[email protected]>
---
 .../org/apache/kudu/backup/TableMetadata.scala     | 10 +++
 .../kudu/mapreduce/tools/ExportCsvMapper.java      |  3 +
 .../src/main/java/org/apache/kudu/Type.java        |  8 +-
 .../apache/kudu/client/ColumnRangePredicate.java   |  1 +
 .../java/org/apache/kudu/client/KeyEncoder.java    |  6 ++
 .../java/org/apache/kudu/client/KuduPredicate.java | 26 ++++++-
 .../java/org/apache/kudu/client/PartialRow.java    | 87 ++++++++++++++++++++++
 .../org/apache/kudu/client/ProtobufHelper.java     |  6 ++
 .../java/org/apache/kudu/client/RowResult.java     | 38 +++++++++-
 .../java/org/apache/kudu/util/DataGenerator.java   | 14 ++++
 .../main/java/org/apache/kudu/util/DateUtil.java   | 77 +++++++++++++++++++
 .../java/org/apache/kudu/util/SchemaGenerator.java |  5 ++
 .../org/apache/kudu/client/TestKeyEncoding.java    | 16 ++--
 .../org/apache/kudu/client/TestKuduClient.java     | 47 ++++++++++++
 .../java/org/apache/kudu/client/TestOperation.java |  5 +-
 .../org/apache/kudu/client/TestPartialRow.java     | 39 +++++++++-
 .../java/org/apache/kudu/client/TestRowResult.java |  7 ++
 .../java/org/apache/kudu/util/TestDateUtil.java    | 78 +++++++++++++++++++
 .../spark/tools/DistributedDataGenerator.scala     |  3 +
 .../org/apache/kudu/spark/kudu/RowConverter.scala  |  2 +
 .../org/apache/kudu/spark/kudu/SparkUtil.scala     |  2 +
 .../apache/kudu/spark/kudu/DefaultSourceTest.scala |  2 +-
 .../apache/kudu/spark/kudu/KuduContextTest.scala   |  6 +-
 .../org/apache/kudu/spark/kudu/KuduTestSuite.scala |  6 +-
 .../java/org/apache/kudu/test/ClientTestUtil.java  | 14 +++-
 25 files changed, 490 insertions(+), 18 deletions(-)

diff --git 
a/java/kudu-backup-common/src/main/scala/org/apache/kudu/backup/TableMetadata.scala
 
b/java/kudu-backup-common/src/main/scala/org/apache/kudu/backup/TableMetadata.scala
index 1d1f6d7..a859d92 100644
--- 
a/java/kudu-backup-common/src/main/scala/org/apache/kudu/backup/TableMetadata.scala
+++ 
b/java/kudu-backup-common/src/main/scala/org/apache/kudu/backup/TableMetadata.scala
@@ -18,6 +18,7 @@
 package org.apache.kudu.backup
 
 import java.math.BigDecimal
+import java.sql.Date
 import java.util
 
 import com.google.protobuf.ByteString
@@ -32,6 +33,7 @@ import org.apache.kudu.client.CreateTableOptions
 import org.apache.kudu.client.KuduTable
 import org.apache.kudu.client.PartialRow
 import org.apache.kudu.client.PartitionSchema
+import org.apache.kudu.util.DateUtil
 import org.apache.kudu.ColumnSchema
 import org.apache.kudu.Schema
 import org.apache.kudu.Type
@@ -241,6 +243,7 @@ object TableMetadata {
       case Type.STRING => row.getString(columnName)
       case Type.BINARY => row.getBinary(columnName)
       case Type.DECIMAL => row.getDecimal(columnName)
+      case Type.DATE => row.getDate(columnName)
       case _ =>
         throw new IllegalArgumentException(s"Unsupported column type: 
$colType")
     }
@@ -262,11 +265,15 @@ object TableMetadata {
         row.addBinary(columnName, value.asInstanceOf[Array[Byte]])
       case Type.DECIMAL =>
         row.addDecimal(columnName, value.asInstanceOf[BigDecimal])
+      case Type.DATE => row.addDate(columnName, value.asInstanceOf[Date])
       case _ =>
         throw new IllegalArgumentException(s"Unsupported column type: 
$colType")
     }
   }
 
+  /**
+   * Returns the string value of serialized value according to the type of 
column.
+   */
   private def valueToString(value: Any, colType: Type): String = {
     colType match {
       case Type.BOOL =>
@@ -293,6 +300,8 @@ object TableMetadata {
         value
           .asInstanceOf[BigDecimal]
           .toString // TODO: Explicitly control print format
+      case Type.DATE =>
+        value.asInstanceOf[Date].toString()
       case _ =>
         throw new IllegalArgumentException(s"Unsupported column type: 
$colType")
     }
@@ -311,6 +320,7 @@ object TableMetadata {
       case Type.STRING => value
       case Type.BINARY => Base64.decodeBase64(value)
       case Type.DECIMAL => new BigDecimal(value) // TODO: Explicitly pass scale
+      case Type.DATE => Date.valueOf(value)
       case _ =>
         throw new IllegalArgumentException(s"Unsupported column type: 
$colType")
     }
diff --git 
a/java/kudu-client-tools/src/main/java/org/apache/kudu/mapreduce/tools/ExportCsvMapper.java
 
b/java/kudu-client-tools/src/main/java/org/apache/kudu/mapreduce/tools/ExportCsvMapper.java
index d7b4800..9692fc6 100644
--- 
a/java/kudu-client-tools/src/main/java/org/apache/kudu/mapreduce/tools/ExportCsvMapper.java
+++ 
b/java/kudu-client-tools/src/main/java/org/apache/kudu/mapreduce/tools/ExportCsvMapper.java
@@ -104,6 +104,9 @@ public class ExportCsvMapper extends Mapper<NullWritable, 
RowResult, NullWritabl
         case BOOL:
           buf.append(value.getBoolean(i));
           break;
+        case DATE:
+          buf.append(value.getInt(i));
+          break;
         default:
           buf.append("<unknown type!>");
           break;
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
index b1cb785..5a346c3 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/Type.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/Type.java
@@ -49,7 +49,8 @@ public enum Type {
   DOUBLE(DataType.DOUBLE, "double"),
   UNIXTIME_MICROS(DataType.UNIXTIME_MICROS, "unixtime_micros"),
   DECIMAL(Arrays.asList(DataType.DECIMAL32, DataType.DECIMAL64, 
DataType.DECIMAL128), "decimal"),
-  VARCHAR(DataType.VARCHAR, "varchar");
+  VARCHAR(DataType.VARCHAR, "varchar"),
+  DATE(DataType.DATE, "date");
 
   private final ImmutableList<DataType> dataTypes;
   private final String name;
@@ -77,6 +78,7 @@ public enum Type {
    * @return A DataType
    * @deprecated use {@link #getDataType(ColumnTypeAttributes)}
    */
+  @Deprecated
   public DataType getDataType() {
     if (this == DECIMAL) {
       throw new IllegalStateException("Please use the newer 
getDataType(ColumnTypeAttributes) " +
@@ -110,6 +112,7 @@ public enum Type {
    * @return A size
    * @deprecated use {@link #getSize(ColumnTypeAttributes)}
    */
+  @Deprecated
   public int getSize() {
     if (this == DECIMAL) {
       throw new IllegalStateException("Please use the newer 
getSize(ColumnTypeAttributes) " +
@@ -153,6 +156,7 @@ public enum Type {
       case INT16:
         return Shorts.BYTES;
       case INT32:
+      case DATE:
       case FLOAT:
         return Ints.BYTES;
       case INT64:
@@ -194,6 +198,8 @@ public enum Type {
         return FLOAT;
       case DOUBLE:
         return DOUBLE;
+      case DATE:
+        return DATE;
       case DECIMAL32:
       case DECIMAL64:
       case DECIMAL128:
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/ColumnRangePredicate.java
 
b/java/kudu-client/src/main/java/org/apache/kudu/client/ColumnRangePredicate.java
index 8a8a26e..ec7e0b8 100644
--- 
a/java/kudu-client/src/main/java/org/apache/kudu/client/ColumnRangePredicate.java
+++ 
b/java/kudu-client/src/main/java/org/apache/kudu/client/ColumnRangePredicate.java
@@ -89,6 +89,7 @@ public class ColumnRangePredicate {
       case INT16:
         return KuduPredicate.newComparisonPredicate(column, op, 
Bytes.getShort(bound));
       case INT32:
+      case DATE:
         return KuduPredicate.newComparisonPredicate(column, op, 
Bytes.getInt(bound));
       case INT64:
       case UNIXTIME_MICROS:
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/KeyEncoder.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/KeyEncoder.java
index de268b6..71a71ec 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/KeyEncoder.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/KeyEncoder.java
@@ -37,6 +37,7 @@ import org.apache.kudu.Schema;
 import org.apache.kudu.Type;
 import org.apache.kudu.client.PartitionSchema.HashBucketSchema;
 import org.apache.kudu.util.ByteVec;
+import org.apache.kudu.util.DateUtil;
 import org.apache.kudu.util.DecimalUtil;
 import org.apache.kudu.util.Pair;
 
@@ -327,6 +328,11 @@ class KeyEncoder {
       case INT16:
         row.addShort(idx, (short) (buf.getShort() ^ Short.MIN_VALUE));
         break;
+      case DATE: {
+        int days = buf.getInt() ^ Integer.MIN_VALUE;
+        row.addDate(idx, DateUtil.epochDaysToSqlDate(days));
+        break;
+      }
       case INT32:
         row.addInt(idx, buf.getInt() ^ Integer.MIN_VALUE);
         break;
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java
index 5c9f706..49abfe8 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduPredicate.java
@@ -22,7 +22,9 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.sql.Date;
 import java.sql.Timestamp;
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -44,6 +46,7 @@ import org.apache.kudu.ColumnTypeAttributes;
 import org.apache.kudu.Common;
 import org.apache.kudu.Schema;
 import org.apache.kudu.Type;
+import org.apache.kudu.util.DateUtil;
 import org.apache.kudu.util.DecimalUtil;
 import org.apache.kudu.util.TimestampUtil;
 
@@ -166,7 +169,8 @@ public class KuduPredicate {
   public static KuduPredicate newComparisonPredicate(ColumnSchema column,
                                                      ComparisonOp op,
                                                      long value) {
-    checkColumn(column, Type.INT8, Type.INT16, Type.INT32, Type.INT64, 
Type.UNIXTIME_MICROS);
+    checkColumn(column, Type.INT8, Type.INT16, Type.INT32, Type.INT64, 
Type.UNIXTIME_MICROS,
+        Type.DATE);
     long minValue = minIntValue(column.getType());
     long maxValue = maxIntValue(column.getType());
     Preconditions.checkArgument(value <= maxValue && value >= minValue,
@@ -202,6 +206,7 @@ public class KuduPredicate {
         bytes = Bytes.fromShort((short) value);
         break;
       }
+      case DATE:
       case INT32: {
         bytes = Bytes.fromInt((int) value);
         break;
@@ -315,6 +320,20 @@ public class KuduPredicate {
   }
 
   /**
+   * Creates a new comparison predicate on a date column.
+   * @param column the column schema
+   * @param op the comparison operation
+   * @param value the value to compare against
+   */
+  public static KuduPredicate newComparisonPredicate(ColumnSchema column,
+                                                     ComparisonOp op,
+                                                     Date value) {
+    checkColumn(column, Type.DATE);
+    int days = DateUtil.sqlDateToEpochDays(value);
+    return newComparisonPredicate(column, op, days);
+  }
+
+  /**
    * Creates a new comparison predicate on a float column.
    * @param column the column schema
    * @param op the comparison operation
@@ -573,7 +592,7 @@ public class KuduPredicate {
         vals.add(Bytes.fromShort((Short) value));
       }
     } else if (t instanceof Integer) {
-      checkColumn(column, Type.INT32);
+      checkColumn(column, Type.INT32, Type.DATE);
       for (T value : values) {
         vals.add(Bytes.fromInt((Integer) value));
       }
@@ -974,6 +993,7 @@ public class KuduPredicate {
       case INT16:
         return Short.compare(Bytes.getShort(a), Bytes.getShort(b));
       case INT32:
+      case DATE:
       case DECIMAL32:
         return Integer.compare(Bytes.getInt(a), Bytes.getInt(b));
       case INT64:
@@ -1015,6 +1035,7 @@ public class KuduPredicate {
         return m < n && m + 1 == n;
       }
       case INT32:
+      case DATE:
       case DECIMAL32: {
         int m = Bytes.getInt(a);
         int n = Bytes.getInt(b);
@@ -1153,6 +1174,7 @@ public class KuduPredicate {
       case INT16: return Short.toString(Bytes.getShort(value));
       case INT32: return Integer.toString(Bytes.getInt(value));
       case INT64: return Long.toString(Bytes.getLong(value));
+      case DATE: return DateUtil.epochDaysToDateString(Bytes.getInt(value));
       case UNIXTIME_MICROS: return 
TimestampUtil.timestampToString(Bytes.getLong(value));
       case FLOAT: return Float.toString(Bytes.getFloat(value));
       case DOUBLE: return Double.toString(Bytes.getDouble(value));
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/PartialRow.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/PartialRow.java
index c118ed4..37de74f 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/PartialRow.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/PartialRow.java
@@ -20,6 +20,7 @@ package org.apache.kudu.client;
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.sql.Date;
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,6 +37,7 @@ import org.apache.kudu.ColumnSchema;
 import org.apache.kudu.ColumnTypeAttributes;
 import org.apache.kudu.Schema;
 import org.apache.kudu.Type;
+import org.apache.kudu.util.DateUtil;
 import org.apache.kudu.util.DecimalUtil;
 import org.apache.kudu.util.StringUtil;
 import org.apache.kudu.util.TimestampUtil;
@@ -610,6 +612,64 @@ public class PartialRow {
   }
 
   /**
+   * Add a sql.Date for the specified column.
+   *
+   * @param columnIndex the column's index in the schema
+   * @param val value to add
+   * @throws IllegalArgumentException if the value doesn't match the column's 
type
+   * @throws IllegalStateException if the row was already applied
+   * @throws IndexOutOfBoundsException if the column doesn't exist
+   */
+  public void addDate(int columnIndex, Date val) {
+    checkNotFrozen();
+    checkColumn(schema.getColumnByIndex(columnIndex), Type.DATE);
+    int days = DateUtil.sqlDateToEpochDays(val);
+    Bytes.setInt(rowAlloc, days, 
getPositionInRowAllocAndSetBitSet(columnIndex));
+  }
+
+  /**
+   * Add a Date for the specified column.
+   *
+   * @param columnName Name of the column
+   * @param val value to add
+   * @throws IllegalArgumentException if the column doesn't exist
+   * or if the value doesn't match the column's type
+   * @throws IllegalStateException if the row was already applied
+   */
+  public void addDate(String columnName, Date val) {
+    addDate(schema.getColumnIndex(columnName), val);
+  }
+
+  /**
+   * Get the specified column's Date.
+   *
+   * @param columnName name of the column to get data for
+   * @return a Date
+   * @throws IllegalArgumentException if the column doesn't exist,
+   * is null, is unset, or the type doesn't match the column's type
+   */
+  public Date getDate(String columnName) {
+    return getDate(this.schema.getColumnIndex(columnName));
+  }
+
+  /**
+   * Get the specified column's Date.
+   *
+   * @param columnIndex Column index in the schema
+   * @return a Date
+   * @throws IllegalArgumentException if the column is null, is unset,
+   * or if the type doesn't match the column's type
+   * @throws IndexOutOfBoundsException if the column doesn't exist
+   */
+  public Date getDate(int columnIndex) {
+    checkColumnExists(schema.getColumnByIndex(columnIndex));
+    checkColumn(schema.getColumnByIndex(columnIndex), Type.DATE);
+    checkValue(columnIndex);
+    int days = Bytes.getInt(rowAlloc, schema.getColumnOffset(columnIndex));
+    return DateUtil.epochDaysToSqlDate(days);
+  }
+
+  /**
    * Add a String for the specified column.
    * @param columnIndex the column's index in the schema
    * @param val value to add
@@ -1001,6 +1061,7 @@ public class PartialRow {
    *  Type.VARCHAR -> java.lang.String
    *  Type.BINARY -> byte[] or java.lang.ByteBuffer
    *  Type.DECIMAL -> java.math.BigDecimal
+   *  Type.DATE -> java.sql.Date
    *
    * @param columnIndex column index in the schema
    * @param val the value to add as an Object
@@ -1051,6 +1112,9 @@ public class PartialRow {
         case VARCHAR:
           addVarchar(columnIndex, (String) val);
           break;
+        case DATE:
+          addDate(columnIndex, (Date) val);
+          break;
         case BINARY:
           if (val instanceof byte[]) {
             addBinary(columnIndex, (byte[]) val);
@@ -1090,6 +1154,7 @@ public class PartialRow {
    *  Type.VARCHAR -> java.lang.String
    *  Type.BINARY -> byte[]
    *  Type.DECIMAL -> java.math.BigDecimal
+   *  Type.DATE -> java.sql.Date
    *
    * @param columnName name of the column in the schema
    * @return the column's value as an Object, null if the column value is null 
or unset
@@ -1135,6 +1200,7 @@ public class PartialRow {
       case INT16: return getShort(columnIndex);
       case INT32: return getInt(columnIndex);
       case INT64: return getLong(columnIndex);
+      case DATE: return getDate(columnIndex);
       case UNIXTIME_MICROS: return getTimestamp(columnIndex);
       case FLOAT: return getFloat(columnIndex);
       case DOUBLE: return getDouble(columnIndex);
@@ -1374,6 +1440,10 @@ public class PartialRow {
       case INT64:
         sb.append(Bytes.getLong(rowAlloc, schema.getColumnOffset(idx)));
         return;
+      case DATE:
+        sb.append(DateUtil.epochDaysToDateString(
+            Bytes.getInt(rowAlloc, schema.getColumnOffset(idx))));
+        return;
       case UNIXTIME_MICROS:
         sb.append(TimestampUtil.timestampToString(
             Bytes.getLong(rowAlloc, schema.getColumnOffset(idx))));
@@ -1429,6 +1499,9 @@ public class PartialRow {
       case INT32:
         addInt(index, Integer.MIN_VALUE);
         break;
+      case DATE:
+        addDate(index, DateUtil.epochDaysToSqlDate(DateUtil.MIN_DATE_VALUE));
+        break;
       case INT64:
       case UNIXTIME_MICROS:
         addLong(index, Long.MIN_VALUE);
@@ -1472,6 +1545,7 @@ public class PartialRow {
       case INT16:
       case INT32:
       case INT64:
+      case DATE:
       case UNIXTIME_MICROS:
       case FLOAT:
       case DOUBLE:
@@ -1536,6 +1610,14 @@ public class PartialRow {
         Bytes.setInt(rowAlloc, existing + 1, offset);
         return true;
       }
+      case DATE: {
+        int existing = Bytes.getInt(rowAlloc, offset);
+        if (existing == DateUtil.MAX_DATE_VALUE) {
+          return false;
+        }
+        Bytes.setInt(rowAlloc, existing + 1, offset);
+        return true;
+      }
       case INT64:
       case UNIXTIME_MICROS: {
         long existing = Bytes.getLong(rowAlloc, offset);
@@ -1648,6 +1730,7 @@ public class PartialRow {
         return a.rowAlloc[offset] == b.rowAlloc[offset];
       case INT16:
         return Bytes.getShort(a.rowAlloc, offset) == 
Bytes.getShort(b.rowAlloc, offset);
+      case DATE:
       case INT32:
         return Bytes.getInt(a.rowAlloc, offset) == Bytes.getInt(b.rowAlloc, 
offset);
       case INT64:
@@ -1725,6 +1808,10 @@ public class PartialRow {
         int val = Bytes.getInt(lower.rowAlloc, offset);
         return val != Integer.MAX_VALUE && val + 1 == 
Bytes.getInt(upper.rowAlloc, offset);
       }
+      case DATE: {
+        int val = Bytes.getInt(lower.rowAlloc, offset);
+        return val != DateUtil.MAX_DATE_VALUE && val + 1 == 
Bytes.getInt(upper.rowAlloc, offset);
+      }
       case INT64:
       case UNIXTIME_MICROS: {
         long val = Bytes.getLong(lower.rowAlloc, offset);
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java
index e4dc71b..0d58bbc 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/ProtobufHelper.java
@@ -22,6 +22,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.sql.Date;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
@@ -37,6 +38,7 @@ import org.apache.kudu.ColumnTypeAttributes;
 import org.apache.kudu.Common;
 import org.apache.kudu.Schema;
 import org.apache.kudu.Type;
+import org.apache.kudu.util.DateUtil;
 import org.apache.kudu.util.DecimalUtil;
 
 @InterfaceAudience.Private
@@ -271,6 +273,8 @@ public class ProtobufHelper {
         return new byte[] {(Byte) value};
       case INT16:
         return Bytes.fromShort((Short) value);
+      case DATE:
+        return Bytes.fromInt(DateUtil.sqlDateToEpochDays((Date) value));
       case INT32:
         return Bytes.fromInt((Integer) value);
       case INT64:
@@ -304,6 +308,8 @@ public class ProtobufHelper {
         return buf.get();
       case INT16:
         return buf.getShort();
+      case DATE:
+        return DateUtil.epochDaysToSqlDate(buf.getInt());
       case INT32:
         return buf.getInt();
       case INT64:
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java 
b/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
index e793092..6470e9d 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/RowResult.java
@@ -19,6 +19,7 @@ package org.apache.kudu.client;
 
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.sql.Date;
 import java.sql.Timestamp;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -30,6 +31,7 @@ import org.apache.kudu.ColumnSchema;
 import org.apache.kudu.ColumnTypeAttributes;
 import org.apache.kudu.Schema;
 import org.apache.kudu.Type;
+import org.apache.kudu.util.DateUtil;
 import org.apache.kudu.util.Slice;
 import org.apache.kudu.util.TimestampUtil;
 
@@ -132,7 +134,7 @@ public class RowResult {
   public int getInt(int columnIndex) {
     checkValidColumn(columnIndex);
     checkNull(columnIndex);
-    checkType(columnIndex, Type.INT32);
+    checkType(columnIndex, Type.INT32, Type.DATE);
     return Bytes.getInt(this.rowData.getRawArray(),
         this.rowData.getRawOffset() + 
getCurrentRowDataOffsetForColumn(columnIndex));
   }
@@ -370,6 +372,35 @@ public class RowResult {
   }
 
   /**
+   * Get the specified column's Date.
+   *
+   * @param columnName name of the column to get data for
+   * @return a Date
+   * @throws IllegalArgumentException if the column doesn't exist,
+   * is null, is unset, or the type doesn't match the column's type
+   */
+  public Date getDate(String columnName) {
+    return getDate(this.schema.getColumnIndex(columnName));
+  }
+
+  /**
+   * Get the specified column's Date.
+   *
+   * @param columnIndex Column index in the schema
+   * @return a Date
+   * @throws IllegalArgumentException if the column is null, is unset,
+   * or if the type doesn't match the column's type
+   * @throws IndexOutOfBoundsException if the column doesn't exist
+   */
+  public Date getDate(int columnIndex) {
+    checkValidColumn(columnIndex);
+    checkNull(columnIndex);
+    checkType(columnIndex, Type.DATE);
+    int days = getInt(columnIndex);
+    return DateUtil.epochDaysToSqlDate(days);
+  }
+
+  /**
    * Get the schema used for this scanner's column projection.
    * @return a column projection as a schema.
    */
@@ -602,6 +633,7 @@ public class RowResult {
    *  Type.STRING -> java.lang.String
    *  Type.BINARY -> byte[]
    *  Type.DECIMAL -> java.math.BigDecimal
+   *  Type.Date -> java.sql.Date
    *
    * @param columnIndex Column index in the schema
    * @return the column's value as an Object, null if the value is null
@@ -619,6 +651,7 @@ public class RowResult {
       case INT16: return getShort(columnIndex);
       case INT32: return getInt(columnIndex);
       case INT64: return getLong(columnIndex);
+      case DATE: return getDate(columnIndex);
       case UNIXTIME_MICROS: return getTimestamp(columnIndex);
       case FLOAT: return getFloat(columnIndex);
       case DOUBLE: return getDouble(columnIndex);
@@ -752,6 +785,9 @@ public class RowResult {
           case INT64:
             buf.append(getLong(i));
             break;
+          case DATE:
+            buf.append(DateUtil.epochDaysToDateString(getInt(i)));
+            break;
           case UNIXTIME_MICROS: {
             buf.append(TimestampUtil.timestampToString(getTimestamp(i)));
           } break;
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/util/DataGenerator.java 
b/java/kudu-client/src/main/java/org/apache/kudu/util/DataGenerator.java
index 0e281bc..07a11a2 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/util/DataGenerator.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/util/DataGenerator.java
@@ -19,6 +19,7 @@ package org.apache.kudu.util;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.sql.Date;
 import java.util.Base64;
 import java.util.List;
 import java.util.Random;
@@ -103,6 +104,9 @@ public class DataGenerator {
         case INT32:
           row.addInt(i, random.nextInt());
           break;
+        case DATE:
+          row.addDate(i, randomDate(random));
+          break;
         case INT64:
         case UNIXTIME_MICROS:
           row.addLong(i, random.nextLong());
@@ -133,6 +137,16 @@ public class DataGenerator {
   }
 
   /**
+   * Utility method to return a random integer value which can be converted 
into
+   * correct Kudu Date value
+   */
+  public static Date randomDate(Random random) {
+    final int bound = DateUtil.MAX_DATE_VALUE - DateUtil.MIN_DATE_VALUE + 1;
+    int days = random.nextInt(bound) + DateUtil.MIN_DATE_VALUE;
+    return DateUtil.epochDaysToSqlDate(days);
+  }
+
+  /**
    * Utility method to return a random decimal value.
    */
   public static BigDecimal randomDecimal(ColumnTypeAttributes attributes, 
Random random) {
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/util/DateUtil.java 
b/java/kudu-client/src/main/java/org/apache/kudu/util/DateUtil.java
new file mode 100644
index 0000000..10bb20d
--- /dev/null
+++ b/java/kudu-client/src/main/java/org/apache/kudu/util/DateUtil.java
@@ -0,0 +1,77 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.kudu.util;
+
+import java.sql.Date;
+import java.time.LocalDate;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
[email protected]
+public class DateUtil {
+  public static final int MIN_DATE_VALUE =
+      (int)LocalDate.parse("0001-01-01").toEpochDay(); // -719162
+  public static final int MAX_DATE_VALUE =
+      (int)LocalDate.parse("9999-12-31").toEpochDay(); // 2932896
+
+  /**
+   * Check whether the date is within the range '0001-01-01':'9999-12-31'
+   *
+   * @param the number days since the Unix epoch
+   */
+  public static void checkDateWithinRange(long days) {
+    if (days < MIN_DATE_VALUE || days > MAX_DATE_VALUE) {
+      throw new IllegalArgumentException(
+          "Date value <" + days + ">} is out of range 
'0001-01-01':'9999-12-31'");
+    }
+  }
+
+  /**
+   * Converts a {@link java.sql.Date} to the number of days since the Unix 
epoch
+   * (1970-01-01T00:00:00Z).
+   *
+   * @param date the date to convert to days
+   * @return the number days since the Unix epoch
+   */
+  public static int sqlDateToEpochDays(Date date) {
+    long days = date.toLocalDate().toEpochDay();
+    checkDateWithinRange(days);
+    return (int)days;
+  }
+
+  /**
+   * Converts a number of days since the Unix epoch to a {@link java.sql.Date}.
+   *
+   * @param the number of days since the Unix epoch
+   * @return the corresponding Date
+   */
+  public static Date epochDaysToSqlDate(int days) {
+    checkDateWithinRange(days);
+    return Date.valueOf(LocalDate.ofEpochDay(days));
+  }
+
+  /**
+   * Transforms a number of days since the Unix epoch into a string according 
the ISO-8601 format.
+   *
+   * @param the number of days since the Unix epoch
+   * @return a string, in the format: YYYY-MM-DD
+   */
+  public static String epochDaysToDateString(int days) {
+    return LocalDate.ofEpochDay(days).toString();
+  }
+}
diff --git 
a/java/kudu-client/src/main/java/org/apache/kudu/util/SchemaGenerator.java 
b/java/kudu-client/src/main/java/org/apache/kudu/util/SchemaGenerator.java
index 78b7734..2da7bd2 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/util/SchemaGenerator.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/util/SchemaGenerator.java
@@ -18,6 +18,7 @@
 package org.apache.kudu.util;
 
 import static org.apache.kudu.util.DataGenerator.randomBinary;
+import static org.apache.kudu.util.DataGenerator.randomDate;
 import static org.apache.kudu.util.DataGenerator.randomDecimal;
 import static org.apache.kudu.util.DataGenerator.randomString;
 
@@ -149,6 +150,9 @@ public class SchemaGenerator {
         case INT32:
           builder.defaultValue(random.nextInt());
           break;
+        case DATE:
+          builder.defaultValue(randomDate(random));
+          break;
         case INT64:
         case UNIXTIME_MICROS:
           builder.defaultValue(random.nextLong());
@@ -204,6 +208,7 @@ public class SchemaGenerator {
       case INT16:
       case INT32:
       case INT64:
+      case DATE:
       case UNIXTIME_MICROS:
         validEncodings.retainAll(Arrays.asList(
             Encoding.AUTO_ENCODING,
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKeyEncoding.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKeyEncoding.java
index d7708d8..ade63ed 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKeyEncoding.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKeyEncoding.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.math.BigDecimal;
+import java.sql.Date;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -396,6 +397,7 @@ public class TestKeyEncoding {
           
.typeAttributes(DecimalUtil.typeAttributes(DecimalUtil.MAX_DECIMAL128_PRECISION,
 0)),
         new ColumnSchemaBuilder("varchar", Type.VARCHAR).key(true)
           .typeAttributes(CharUtil.typeAttributes(10)),
+        new ColumnSchemaBuilder("date", Type.DATE).key(true),
         new ColumnSchemaBuilder("bool", Type.BOOL),       // not primary key 
type
         new ColumnSchemaBuilder("float", Type.FLOAT),     // not primary key 
type
         new ColumnSchemaBuilder("double", Type.DOUBLE));  // not primary key 
type
@@ -417,9 +419,10 @@ public class TestKeyEncoding {
     row.addDecimal(8, BigDecimal.valueOf(DecimalUtil.MAX_UNSCALED_DECIMAL64));
     row.addDecimal(9, new BigDecimal(DecimalUtil.MAX_UNSCALED_DECIMAL128));
     row.addVarchar(10, "varchar bar");
-    row.addBoolean(11, true);
-    row.addFloat(12, 7.8f);
-    row.addDouble(13, 9.9);
+    row.addDate(11, new Date(0));
+    row.addBoolean(12, true);
+    row.addFloat(13, 7.8f);
+    row.addDouble(14, 9.9);
     session.apply(insert);
     session.close();
 
@@ -442,9 +445,10 @@ public class TestKeyEncoding {
       assertTrue(new BigDecimal(DecimalUtil.MAX_UNSCALED_DECIMAL128)
           .compareTo(rr.getDecimal(9)) == 0);
       assertEquals("varchar ba", rr.getVarchar(10));
-      assertTrue(rr.getBoolean(11));
-      assertEquals(7.8f, rr.getFloat(12), .001f);
-      assertEquals(9.9, rr.getDouble(13), .001);
+      assertEquals(0, rr.getInt(11));
+      assertTrue(rr.getBoolean(12));
+      assertEquals(7.8f, rr.getFloat(13), .001f);
+      assertEquals(9.9, rr.getDouble(14), .001);
     }
   }
 }
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
index 1792642..1fc7d99 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestKuduClient.java
@@ -27,6 +27,7 @@ import static 
org.apache.kudu.test.ClientTestUtil.createBasicSchemaInsert;
 import static org.apache.kudu.test.ClientTestUtil.createManyStringsSchema;
 import static org.apache.kudu.test.ClientTestUtil.createManyVarcharsSchema;
 import static 
org.apache.kudu.test.ClientTestUtil.createSchemaWithBinaryColumns;
+import static org.apache.kudu.test.ClientTestUtil.createSchemaWithDateColumns;
 import static 
org.apache.kudu.test.ClientTestUtil.createSchemaWithDecimalColumns;
 import static 
org.apache.kudu.test.ClientTestUtil.createSchemaWithTimestampColumns;
 import static org.apache.kudu.test.ClientTestUtil.getBasicCreateTableOptions;
@@ -46,6 +47,8 @@ import java.io.Closeable;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
+import java.sql.Date;
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -74,6 +77,7 @@ import org.apache.kudu.test.KuduTestHarness;
 import org.apache.kudu.test.KuduTestHarness.LocationConfig;
 import org.apache.kudu.test.KuduTestHarness.TabletServerConfig;
 import org.apache.kudu.test.RandomUtils;
+import org.apache.kudu.util.DateUtil;
 import org.apache.kudu.util.DecimalUtil;
 import org.apache.kudu.util.TimestampUtil;
 
@@ -649,6 +653,49 @@ public class TestKuduClient {
   }
 
   /**
+   * Test inserting and retrieving date columns.
+   */
+  @Test(timeout = 100000)
+  public void testDateColumns() throws Exception {
+    Schema schema = createSchemaWithDateColumns();
+    client.createTable(TABLE_NAME, schema, getBasicCreateTableOptions());
+
+    List<Integer> dates = new ArrayList<>();
+
+    KuduSession session = client.newSession();
+    KuduTable table = client.openTable(TABLE_NAME);
+    for (int i = 0; i < 100; i++) {
+      Insert insert = table.newInsert();
+      PartialRow row = insert.getRow();
+      dates.add(i);
+      Date date = DateUtil.epochDaysToSqlDate(i);
+      row.addDate("key", date);
+      if (i % 2 == 1) {
+        row.addDate("c1", date);
+      }
+      session.apply(insert);
+      if (i % 50 == 0) {
+        session.flush();
+      }
+    }
+    session.flush();
+
+    List<String> rowStrings = scanTableToStrings(table);
+    assertEquals(100, rowStrings.size());
+    for (int i = 0; i < rowStrings.size(); i++) {
+      String sdate = DateUtil.epochDaysToDateString(dates.get(i));
+      StringBuilder expectedRow = new StringBuilder();
+      expectedRow.append(String.format("DATE key=%s, DATE c1=", sdate));
+      if (i % 2 == 1) {
+        expectedRow.append(sdate);
+      } else {
+        expectedRow.append("NULL");
+      }
+      assertEquals(expectedRow.toString(), rowStrings.get(i));
+    }
+  }
+
+  /**
    * Test inserting and retrieving decimal columns.
    */
   @Test(timeout = 100000)
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
index 7280c30..20e7640 100644
--- 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
@@ -35,6 +35,7 @@ import org.apache.kudu.WireProtocol.RowOperationsPB;
 import org.apache.kudu.client.Operation.ChangeType;
 import org.apache.kudu.test.junit.RetryRule;
 import org.apache.kudu.tserver.Tserver.WriteRequestPBOrBuilder;
+import org.apache.kudu.util.DateUtil;
 
 /**
  * Unit tests for Operation
@@ -139,6 +140,7 @@ public class TestOperation {
     columns.add(new ColumnSchema.ColumnSchemaBuilder("c4", 
Type.UNIXTIME_MICROS).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());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c7", 
Type.DATE).key(true).build());
     return new Schema(columns);
   }
 
@@ -155,10 +157,11 @@ public class TestOperation {
     row.addLong("c4", 5);
     row.addString("c5", "c5_val");
     row.addBinary("c6", Bytes.fromString("c6_val"));
+    row.addDate("c7", DateUtil.epochDaysToSqlDate(0));
 
     assertEquals("(int8 c0=1, int16 c1=2, int32 c2=3, int64 c3=4, " +
                  "unixtime_micros c4=1970-01-01T00:00:00.000005Z, string 
c5=\"c5_val\", " +
-                 "binary c6=\"c6_val\")",
+                 "binary c6=\"c6_val\", date c7=1970-01-01)",
                  insert.getRow().stringifyRowKey());
 
     // Test an incomplete row key.
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java 
b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
index a7c5d53..417f096 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestPartialRow.java
@@ -28,7 +28,9 @@ import static org.junit.Assert.fail;
 
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.sql.Date;
 import java.sql.Timestamp;
+import java.time.LocalDate;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -37,6 +39,7 @@ import org.apache.kudu.ColumnSchema;
 import org.apache.kudu.Schema;
 import org.apache.kudu.Type;
 import org.apache.kudu.test.junit.RetryRule;
+import org.apache.kudu.util.DateUtil;
 
 public class TestPartialRow {
 
@@ -52,6 +55,7 @@ public class TestPartialRow {
     assertEquals(44, partialRow.getInt("int32"));
     assertEquals(45, partialRow.getLong("int64"));
     assertEquals(new Timestamp(1234567890), 
partialRow.getTimestamp("timestamp"));
+    assertEquals(Date.valueOf(LocalDate.ofEpochDay(0)), 
partialRow.getDate("date"));
     assertEquals(52.35F, partialRow.getFloat("float"), 0.0f);
     assertEquals(53.35, partialRow.getDouble("double"), 0.0);
     assertEquals("fun with ütf\0", partialRow.getString("string"));
@@ -84,6 +88,8 @@ public class TestPartialRow {
     assertEquals((long) 45, partialRow.getObject("int64"));
     assertTrue(partialRow.getObject("timestamp") instanceof Timestamp);
     assertEquals(new Timestamp(1234567890), partialRow.getObject("timestamp"));
+    assertTrue(partialRow.getObject("date") instanceof Date);
+    assertEquals(Date.valueOf(LocalDate.ofEpochDay(0)), 
partialRow.getObject("date"));
     assertTrue(partialRow.getObject("float") instanceof Float);
     assertEquals(52.35F, (float) partialRow.getObject("float"), 0.0f);
     assertTrue(partialRow.getObject("double") instanceof Double);
@@ -108,7 +114,7 @@ public class TestPartialRow {
   public void testAddObject() {
     Schema schema = getSchemaWithAllTypes();
     // Ensure we aren't missing any types
-    assertEquals(14, schema.getColumnCount());
+    assertEquals(15, schema.getColumnCount());
 
     PartialRow row = schema.newPartialRow();
     row.addObject("int8", (byte) 42);
@@ -116,6 +122,7 @@ public class TestPartialRow {
     row.addObject("int32", 44);
     row.addObject("int64", 45L);
     row.addObject("timestamp", new Timestamp(1234567890));
+    row.addObject("date", Date.valueOf(LocalDate.ofEpochDay(0)));
     row.addObject("bool", true);
     row.addObject("float", 52.35F);
     row.addObject("double", 53.35);
@@ -310,6 +317,13 @@ public class TestPartialRow {
     assertEquals("22.200", decimal.toString());
   }
 
+  @Test(expected = IllegalArgumentException.class)
+  public void testAddDateOutOfRange() {
+    PartialRow partialRow = getPartialRowWithAllTypes();
+    Date d = Date.valueOf(LocalDate.of(10000, 1, 1));
+    partialRow.addDate("date", d);
+  }
+
   @Test
   public void testToString() {
     Schema schema = getSchemaWithAllTypes();
@@ -350,8 +364,12 @@ public class TestPartialRow {
     assertEquals("(int8 int8=42, int32 int32=42, double double=52.35, " +
         "string string=\"fun with ütf\\0\", binary binary-bytebuffer=[2, 3, 
4], " +
         "decimal(5, 3) decimal=12.345, varchar(10) varchar=\"árvíztűrő \")",
-
         row.toString());
+
+    PartialRow row2 = schema.newPartialRow();
+    assertEquals("()", row2.toString());
+    row2.addDate("date", Date.valueOf(LocalDate.ofEpochDay(0)));
+    assertEquals("(date date=1970-01-01)", row2.toString());
   }
 
   @Test
@@ -432,6 +450,14 @@ public class TestPartialRow {
     partialRow.addVarchar(varcharIndex, "hello");
     assertTrue(partialRow.incrementColumn(varcharIndex));
     assertEquals("hello\0", partialRow.getVarchar(varcharIndex));
+
+    // Date
+    int dateIndex = getColumnIndex(partialRow, "date");
+    partialRow.addDate(dateIndex, 
DateUtil.epochDaysToSqlDate(DateUtil.MAX_DATE_VALUE - 1));
+    assertTrue(partialRow.incrementColumn(dateIndex));
+    Date maxDate = DateUtil.epochDaysToSqlDate(DateUtil.MAX_DATE_VALUE);
+    assertEquals(maxDate, partialRow.getDate(dateIndex));
+    assertFalse(partialRow.incrementColumn(dateIndex));
   }
 
   @Test
@@ -446,6 +472,7 @@ public class TestPartialRow {
     assertEquals(Integer.MIN_VALUE, partialRow.getInt("int32"));
     assertEquals(Long.MIN_VALUE, partialRow.getLong("int64"));
     assertEquals(Long.MIN_VALUE, partialRow.getLong("timestamp"));
+    assertEquals(DateUtil.epochDaysToSqlDate(DateUtil.MIN_DATE_VALUE), 
partialRow.getDate("date"));
     assertEquals(-Float.MAX_VALUE, partialRow.getFloat("float"), 0.0f);
     assertEquals(-Double.MAX_VALUE, partialRow.getDouble("double"), 0.0);
     assertEquals("", partialRow.getString("string"));
@@ -474,6 +501,7 @@ public class TestPartialRow {
       case INT16: return partialRow.getShort(columnName);
       case INT32: return partialRow.getInt(columnName);
       case INT64: return partialRow.getLong(columnName);
+      case DATE: return partialRow.getDate(columnName);
       case UNIXTIME_MICROS: return partialRow.getTimestamp(columnName);
       case VARCHAR: return partialRow.getVarchar(columnName);
       case STRING: return partialRow.getString(columnName);
@@ -496,6 +524,7 @@ public class TestPartialRow {
       case INT16: return partialRow.getShort(columnIndex);
       case INT32: return partialRow.getInt(columnIndex);
       case INT64: return partialRow.getLong(columnIndex);
+      case DATE: return partialRow.getDate(columnIndex);
       case UNIXTIME_MICROS: return partialRow.getTimestamp(columnIndex);
       case VARCHAR: return partialRow.getVarchar(columnIndex);
       case STRING: return partialRow.getString(columnIndex);
@@ -547,6 +576,9 @@ public class TestPartialRow {
       case DECIMAL:
         partialRow.addDecimal(columnName, BigDecimal.valueOf(12345, 3));
         break;
+      case DATE:
+        partialRow.addDate(columnName, new Date(0));
+        break;
       default:
         throw new UnsupportedOperationException();
     }
@@ -590,6 +622,9 @@ public class TestPartialRow {
       case DECIMAL:
         partialRow.addDecimal(columnIndex, BigDecimal.valueOf(12345, 3));
         break;
+      case DATE:
+        partialRow.addDate(columnIndex, new Date(0));
+        break;
       default:
         throw new UnsupportedOperationException();
     }
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
index 05a5106..6aa8910 100644
--- 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
@@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue;
 
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.sql.Date;
 import java.sql.Timestamp;
 
 import org.junit.Before;
@@ -77,6 +78,7 @@ public class TestRowResult {
     row.addTimestamp(11, new Timestamp(11));
     row.addDecimal(12, BigDecimal.valueOf(12345, 3));
     row.addVarchar(13, "varcharval");
+    row.addDate(14, new Date(0));
 
     KuduClient client = harness.getClient();
     KuduSession session = client.newSession();
@@ -155,6 +157,10 @@ public class TestRowResult {
       assertEquals("varcharval",
           rr.getVarchar(allTypesSchema.getColumnByIndex(13).getName()));
 
+      assertEquals(new Date(0), rr.getDate(14));
+      assertEquals(new Date(0), rr.getObject(14));
+      assertEquals(new Date(0), 
rr.getDate(allTypesSchema.getColumnByIndex(14).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));
@@ -169,6 +175,7 @@ public class TestRowResult {
       assertEquals(Type.UNIXTIME_MICROS, rr.getColumnType(11));
       assertEquals(Type.DECIMAL, rr.getColumnType(12));
       assertEquals(Type.VARCHAR, rr.getColumnType(13));
+      assertEquals(Type.DATE, rr.getColumnType(14));
     }
   }
 }
diff --git 
a/java/kudu-client/src/test/java/org/apache/kudu/util/TestDateUtil.java 
b/java/kudu-client/src/test/java/org/apache/kudu/util/TestDateUtil.java
new file mode 100644
index 0000000..f0587d8
--- /dev/null
+++ b/java/kudu-client/src/test/java/org/apache/kudu/util/TestDateUtil.java
@@ -0,0 +1,78 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.kudu.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Date;
+import java.time.LocalDate;
+
+import org.junit.Test;
+
+public class TestDateUtil {
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testDateOutOfRange() {
+    DateUtil.checkDateWithinRange(LocalDate.of(10000, 1, 1).toEpochDay());
+  }
+
+  @Test
+  public void testSqlDateToEpochDays() {
+    Date b = Date.valueOf(LocalDate.of(1, 1, 1));
+    Date e = Date.valueOf(LocalDate.of(9999, 12, 31));
+    assertEquals(DateUtil.sqlDateToEpochDays(b), DateUtil.MIN_DATE_VALUE);
+    assertEquals(DateUtil.sqlDateToEpochDays(e), DateUtil.MAX_DATE_VALUE);
+  }
+
+  @Test
+  public void testEpochDaysToSqlDate() {
+    Date b = Date.valueOf(LocalDate.of(1, 1, 1));
+    Date e = Date.valueOf(LocalDate.of(9999, 12, 31));
+    assertEquals(DateUtil.epochDaysToSqlDate(DateUtil.MIN_DATE_VALUE), b);
+    assertEquals(DateUtil.epochDaysToSqlDate(DateUtil.MAX_DATE_VALUE), e);
+  }
+
+  @Test
+  public void testEpochDaysToDateString() {
+    assertEquals(DateUtil.epochDaysToDateString(DateUtil.MIN_DATE_VALUE), 
"0001-01-01");
+    assertEquals(DateUtil.epochDaysToDateString(DateUtil.MAX_DATE_VALUE), 
"9999-12-31");
+    assertEquals(DateUtil.epochDaysToDateString(0), "1970-01-01");
+    assertEquals(DateUtil.epochDaysToDateString(-10000), "1942-08-16");
+    assertEquals(DateUtil.epochDaysToDateString(10000), "1997-05-19");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMinInt() {
+    DateUtil.checkDateWithinRange(Integer.MIN_VALUE);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMaxInt() {
+    DateUtil.checkDateWithinRange(Integer.MAX_VALUE);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testBeforeMinDate() {
+    DateUtil.checkDateWithinRange(DateUtil.MIN_DATE_VALUE - 1);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testAfterMaxDate() {
+    DateUtil.checkDateWithinRange(DateUtil.MAX_DATE_VALUE + 1);
+  }
+}
diff --git 
a/java/kudu-spark-tools/src/main/scala/org/apache/kudu/spark/tools/DistributedDataGenerator.scala
 
b/java/kudu-spark-tools/src/main/scala/org/apache/kudu/spark/tools/DistributedDataGenerator.scala
index 6f8a664..294a6a7 100644
--- 
a/java/kudu-spark-tools/src/main/scala/org/apache/kudu/spark/tools/DistributedDataGenerator.scala
+++ 
b/java/kudu-spark-tools/src/main/scala/org/apache/kudu/spark/tools/DistributedDataGenerator.scala
@@ -31,6 +31,7 @@ import org.apache.kudu.spark.kudu.RowConverter
 import org.apache.kudu.spark.kudu.SparkUtil
 import org.apache.kudu.spark.tools.DistributedDataGeneratorOptions._
 import org.apache.kudu.util.DataGenerator
+import org.apache.kudu.util.DateUtil
 import org.apache.spark.sql.Row
 import org.apache.spark.sql.SparkSession
 import org.apache.spark.util.LongAccumulator
@@ -213,6 +214,8 @@ private class GeneratedRowIterator(
           row.addLong(i, value)
         case Type.UNIXTIME_MICROS =>
           row.addLong(i, value)
+        case Type.DATE =>
+          row.addDate(i, DateUtil.epochDaysToSqlDate(value.toInt))
         case Type.FLOAT =>
           row.addFloat(i, value.toFloat)
         case Type.DOUBLE =>
diff --git 
a/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/RowConverter.scala 
b/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/RowConverter.scala
index 03a5f74..f4c52c8 100644
--- 
a/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/RowConverter.scala
+++ 
b/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/RowConverter.scala
@@ -93,6 +93,8 @@ class RowConverter(kuduSchema: Schema, schema: StructType, 
ignoreNull: Boolean)
             partialRow.addDouble(kuduIdx, row.getDouble(sparkIdx))
           case DataTypes.TimestampType =>
             partialRow.addTimestamp(kuduIdx, row.getTimestamp(sparkIdx))
+          case DataTypes.DateType =>
+            partialRow.addDate(kuduIdx, row.getDate(sparkIdx))
           case DecimalType() =>
             partialRow.addDecimal(kuduIdx, row.getDecimal(sparkIdx))
           case t =>
diff --git 
a/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/SparkUtil.scala 
b/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/SparkUtil.scala
index bac9193..bf34c90 100644
--- a/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/SparkUtil.scala
+++ b/java/kudu-spark/src/main/scala/org/apache/kudu/spark/kudu/SparkUtil.scala
@@ -50,6 +50,7 @@ object SparkUtil {
       case Type.INT32 => IntegerType
       case Type.INT64 => LongType
       case Type.UNIXTIME_MICROS => TimestampType
+      case Type.DATE => DateType
       case Type.FLOAT => FloatType
       case Type.DOUBLE => DoubleType
       case Type.VARCHAR => StringType
@@ -71,6 +72,7 @@ object SparkUtil {
     case DataTypes.BooleanType => Type.BOOL
     case DataTypes.StringType => Type.STRING
     case DataTypes.TimestampType => Type.UNIXTIME_MICROS
+    case DataTypes.DateType => Type.DATE
     case DataTypes.ByteType => Type.INT8
     case DataTypes.ShortType => Type.INT16
     case DataTypes.IntegerType => Type.INT32
diff --git 
a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala
 
b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala
index 43cb10b..ebd11b8 100644
--- 
a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala
+++ 
b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/DefaultSourceTest.scala
@@ -567,7 +567,7 @@ class DefaultSourceTest extends KuduTestSuite with Matchers 
{
       ))
 
     val dfDefaultSchema = 
sqlContext.read.options(kuduOptions).format("kudu").load
-    assertEquals(15, dfDefaultSchema.schema.fields.length)
+    assertEquals(16, dfDefaultSchema.schema.fields.length)
 
     val dfWithUserSchema =
       
sqlContext.read.options(kuduOptions).schema(userSchema).format("kudu").load
diff --git 
a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduContextTest.scala
 
b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduContextTest.scala
index aa5c5e8..61eb3c0 100644
--- 
a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduContextTest.scala
+++ 
b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduContextTest.scala
@@ -23,8 +23,10 @@ import java.io.ObjectInputStream
 import java.io.ObjectOutputStream
 import java.math.BigDecimal
 import java.nio.charset.StandardCharsets.UTF_8
+import java.sql.Date
 import java.sql.Timestamp
 
+import org.apache.kudu.util.DateUtil
 import org.apache.kudu.util.TimestampUtil
 import org.apache.spark.sql.functions.decode
 import org.junit.Test
@@ -85,7 +87,8 @@ class KuduContextTest extends KuduTestSuite with Matchers {
           "c11_decimal32",
           "c12_decimal64",
           "c13_decimal128",
-          "c14_varchar"
+          "c14_varchar",
+          "c15_date"
         )
       )
       .map(r => r.toSeq)
@@ -110,6 +113,7 @@ class KuduContextTest extends KuduTestSuite with Matchers {
       assert(r.apply(12).asInstanceOf[BigDecimal] == 
BigDecimal.valueOf(rows.apply(index)._2))
       assert(r.apply(13).asInstanceOf[BigDecimal] == 
BigDecimal.valueOf(rows.apply(index)._2))
       assert(r.apply(14).asInstanceOf[String] == rows.apply(index)._3)
+      assert(r.apply(15).asInstanceOf[Date] == 
DateUtil.epochDaysToSqlDate(rows.apply(index)._2))
     })
   }
 
diff --git 
a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduTestSuite.scala 
b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduTestSuite.scala
index b45ef0b..d775977 100644
--- 
a/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduTestSuite.scala
+++ 
b/java/kudu-spark/src/test/scala/org/apache/kudu/spark/kudu/KuduTestSuite.scala
@@ -33,6 +33,7 @@ import org.apache.kudu.Schema
 import org.apache.kudu.Type
 import org.apache.kudu.test.KuduTestHarness
 import org.apache.kudu.util.CharUtil
+import org.apache.kudu.util.DateUtil
 import org.apache.kudu.util.DecimalUtil
 import org.apache.spark.sql.execution.datasources.LogicalRelation
 import org.apache.spark.sql.DataFrame
@@ -91,7 +92,8 @@ trait KuduTestSuite extends JUnitSuite {
       new ColumnSchemaBuilder("c14_varchar", Type.VARCHAR)
         .typeAttributes(CharUtil.typeAttributes(CharUtil.MAX_VARCHAR_LENGTH))
         .nullable(true)
-        .build()
+        .build(),
+      new ColumnSchemaBuilder("c15_date", Type.DATE).build()
     )
     new Schema(columns.asJava)
   }
@@ -184,6 +186,7 @@ trait KuduTestSuite extends JUnitSuite {
       row.addDecimal(11, BigDecimal.valueOf(i))
       row.addDecimal(12, BigDecimal.valueOf(i))
       row.addDecimal(13, BigDecimal.valueOf(i))
+      row.addDate(15, DateUtil.epochDaysToSqlDate(i))
 
       // Sprinkling some nulls so that queries see them.
       val s = if (i % 2 == 0) {
@@ -226,6 +229,7 @@ trait KuduTestSuite extends JUnitSuite {
       row.addDecimal(12, BigDecimal.valueOf(i))
       row.addDecimal(13, BigDecimal.valueOf(i))
       row.addVarchar(14, i.toString)
+      row.addDate(15, DateUtil.epochDaysToSqlDate(i))
 
       // Sprinkling some nulls so that queries see them.
       val s = if (i % 2 == 0) {
diff --git 
a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java 
b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
index e57dbf8..1f21dcc 100644
--- 
a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
+++ 
b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ClientTestUtil.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.sql.Date;
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -220,7 +221,8 @@ public abstract class ClientTestUtil {
             new ColumnSchema.ColumnSchemaBuilder("decimal", Type.DECIMAL)
               .typeAttributes(DecimalUtil.typeAttributes(5, 3)).build(),
             new ColumnSchema.ColumnSchemaBuilder("varchar", Type.VARCHAR)
-              .typeAttributes(CharUtil.typeAttributes(10)).build());
+              .typeAttributes(CharUtil.typeAttributes(10)).build(),
+            new ColumnSchema.ColumnSchemaBuilder("date", Type.DATE).build());
 
     return new Schema(columns);
   }
@@ -228,7 +230,7 @@ public abstract class ClientTestUtil {
   public static PartialRow getPartialRowWithAllTypes() {
     Schema schema = getSchemaWithAllTypes();
     // Ensure we aren't missing any types
-    assertEquals(14, schema.getColumnCount());
+    assertEquals(15, schema.getColumnCount());
 
     PartialRow row = schema.newPartialRow();
     row.addByte("int8", (byte) 42);
@@ -236,6 +238,7 @@ public abstract class ClientTestUtil {
     row.addInt("int32", 44);
     row.addLong("int64", 45);
     row.addTimestamp("timestamp", new Timestamp(1234567890));
+    row.addDate("date", new Date(0));
     row.addBoolean("bool", true);
     row.addFloat("float", 52.35F);
     row.addDouble("double", 53.35);
@@ -465,6 +468,13 @@ public abstract class ClientTestUtil {
     return new Schema(columns);
   }
 
+  public static Schema createSchemaWithDateColumns() {
+    ArrayList<ColumnSchema> columns = new ArrayList<ColumnSchema>();
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("key", 
Type.DATE).key(true).build());
+    columns.add(new ColumnSchema.ColumnSchemaBuilder("c1", 
Type.DATE).nullable(true).build());
+    return new Schema(columns);
+  }
+
   public static Schema createSchemaWithDecimalColumns() {
     ArrayList<ColumnSchema> columns = new ArrayList<>();
     columns.add(new ColumnSchema.ColumnSchemaBuilder("key", 
Type.DECIMAL).key(true)

Reply via email to