You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by me...@apache.org on 2015/07/31 05:16:45 UTC

[3/3] drill git commit: DRILL-3151: Fix many ResultSetMetaData method return values.

DRILL-3151: Fix many ResultSetMetaData method return values.

Added ~unit test for ResultSetMetaData implementation.

Made getObject return classes available to implementation of getColumnClassName:
- Added SqlAccessor.getObjectClass() (to put that metadata right next to code
  to which it corresponds rather than in far-away parallel code).
- Added similar AvaticaDrillSqlAccessor.getObjectClass().
- Changed DrillAccessorList.accessors from Accessor[] to
  AvaticaDrillSqlAccessor[] for better access to JDBC getObject return class.
- Extracted return classes from accessors to pass to updateColumnMetaData.

Reworked some data type mapping and utilities:
- Added Added Types.getSqlTypeName(...).
- Renamed Types.getJdbcType(...) to getJdbcTypeCode(...)
- Replaced Types.isUnSigned with isJdbcSignedType.
- Fixed various bogus RPC-type XXX -> java.sql.Types.SMALLINT mappings.
- Removed DrillColumnMetaDataList.getJdbcTypeName.
- Moved getAvaticaType up (for bottom-up order).
- Revised DrillColumnMetaDataList.getAvaticaType(...).

MAIN:
- Updated updateColumnMetaData(...) to change many calculations of metadata
  input to ColumnMetaData construction.  [DrillColumnMetaDataList]

Updated other metadata tests per changes.


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

Branch: refs/heads/master
Commit: 80835082414d274fda8bf0c66fd01386180fdde5
Parents: 9932246
Author: dbarclay <db...@maprtech.com>
Authored: Fri Jun 19 19:05:39 2015 -0700
Committer: Mehant Baid <me...@gmail.com>
Committed: Thu Jul 30 18:46:02 2015 -0700

----------------------------------------------------------------------
 .../org/apache/drill/common/types/Types.java    |  163 ++-
 .../main/codegen/templates/SqlAccessors.java    |   29 +
 .../vector/accessor/BoundCheckingAccessor.java  |    5 +
 .../exec/vector/accessor/GenericAccessor.java   |    6 +
 .../drill/exec/vector/accessor/SqlAccessor.java |    8 +
 .../jdbc/impl/AvaticaDrillSqlAccessor.java      |    7 +
 .../drill/jdbc/impl/DrillAccessorList.java      |    8 +-
 .../jdbc/impl/DrillColumnMetaDataList.java      |  185 ++-
 .../org/apache/drill/jdbc/impl/DrillCursor.java |   38 +-
 .../jdbc/impl/TypeConvertingSqlAccessor.java    |    5 +
 .../jdbc/DatabaseMetaDataGetColumnsTest.java    |  323 +++--
 .../drill/jdbc/DrillColumnMetaDataListTest.java |   20 +-
 .../jdbc/ResultSetGetMethodConversionsTest.java |   71 +-
 .../drill/jdbc/ResultSetMetaDataTest.java       | 1107 ++++++++++++++++++
 .../impl/TypeConvertingSqlAccessorTest.java     |    5 +
 .../jdbc/test/TestInformationSchemaColumns.java |  203 ++--
 16 files changed, 1745 insertions(+), 438 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/common/src/main/java/org/apache/drill/common/types/Types.java
----------------------------------------------------------------------
diff --git a/common/src/main/java/org/apache/drill/common/types/Types.java b/common/src/main/java/org/apache/drill/common/types/Types.java
index df484b7..69b1b4c 100644
--- a/common/src/main/java/org/apache/drill/common/types/Types.java
+++ b/common/src/main/java/org/apache/drill/common/types/Types.java
@@ -82,9 +82,85 @@ public class Types {
   }
 
   /***
+   * Gets SQL data type name for given Drill RPC-/protobuf-level data type.
+   * @return
+   *   canonical keyword sequence for SQL data type (leading keywords in
+   *   corresponding {@code <data type>}; what
+   *   {@code INFORMATION_SCHEMA.COLUMNS.TYPE_NAME} would list)
+   */
+  public static String getSqlTypeName(final MajorType type) {
+    if (type.getMode() == DataMode.REPEATED) {
+      return "ARRAY";
+    }
+
+    switch (type.getMinorType()) {
+
+      // Standard SQL atomic data types:
+
+      case BIT:             return "BOOLEAN";
+
+      case SMALLINT:        return "SMALLINT";
+      case INT:             return "INTEGER";
+      case BIGINT:          return "BIGINT";
+
+      case FLOAT4:          return "FLOAT";
+      case FLOAT8:          return "DOUBLE";
+
+      case DECIMAL9:
+      case DECIMAL18:
+      case DECIMAL28DENSE:
+      case DECIMAL28SPARSE:
+      case DECIMAL38DENSE:
+      case DECIMAL38SPARSE: return "DECIMAL";
+
+      case VARCHAR:         return "CHARACTER VARYING";
+      case FIXEDCHAR:       return "CHARACTER";
+
+      case VAR16CHAR:       return "NATIONAL CHARACTER VARYING";
+      case FIXED16CHAR:     return "NATIONAL CHARACTER";
+
+      case VARBINARY:       return "BINARY VARYING";
+      case FIXEDBINARY:     return "BINARY";
+
+      case DATE:            return "DATE";
+      case TIME:            return "TIME";
+      case TIMETZ:          return "TIME WITH TIME ZONE";
+      case TIMESTAMP:       return "TIMESTAMP";
+      case TIMESTAMPTZ:     return "TIMESTAMP WITH TIME ZONE";
+
+      case INTERVALYEAR:
+      case INTERVALDAY:     return "INTERVAL";
+
+      // Non-standard SQL atomic data types:
+
+      case INTERVAL:        return "INTERVAL";
+      case MONEY:           return "DECIMAL";
+      case TINYINT:         return "TINYINT";
+
+      // Composite types and other types that are not atomic types (SQL standard
+      // or not) except ARRAY types (handled above):
+
+      case MAP:             return "MAP";
+      case LATE:            return "ANY";
+      case NULL:            return "NULL";
+
+      // Internal types not actually used at level of SQL types(?):
+
+      case UINT1:          return "TINYINT";
+      case UINT2:          return "SMALLINT";
+      case UINT4:          return "INTEGER";
+      case UINT8:          return "BIGINT";
+
+      default:
+        throw new AssertionError(
+            "Unexpected/unhandled MinorType value " + type.getMinorType() );
+    }
+  }
+
+  /***
    * Gets JDBC type code for given Drill RPC-/protobuf-level type.
    */
-  public static int getJdbcType(final MajorType type) {
+  public static int getJdbcTypeCode(final MajorType type) {
     if (type.getMode() == DataMode.REPEATED) {
       return java.sql.Types.ARRAY;
     }
@@ -120,10 +196,13 @@ public class Types {
     case MONEY:
       return java.sql.Types.DECIMAL;
     case NULL:
+      return java.sql.Types.NULL;
     case INTERVAL:
     case INTERVALYEAR:
     case INTERVALDAY:
+      return java.sql.Types.OTHER;  // JDBC (4.1) has nothing for INTERVAL
     case LATE:
+      return java.sql.Types.OTHER;
     case SMALLINT:
       return java.sql.Types.SMALLINT;
     case TIME:
@@ -132,7 +211,7 @@ public class Types {
     case TIMESTAMP:
       return java.sql.Types.TIMESTAMP;
     case TIMETZ:
-      return java.sql.Types.DATE;
+      return java.sql.Types.TIME;
     case TINYINT:
       return java.sql.Types.TINYINT;
     case UINT1:
@@ -154,22 +233,80 @@ public class Types {
       //   is an unexpected, code-out-of-sync-with-itself case, so use an
       //   exception intended for that.
       throw new UnsupportedOperationException(
-          "Unexpected/unhandled " + type.getMinorType() + " value " + type.getMinorType() );
+          "Unexpected/unhandled MinorType value " + type.getMinorType() );
     }
   }
 
-  public static boolean isUnSigned(final MajorType type) {
-    switch(type.getMinorType()) {
-    case UINT1:
-    case UINT2:
-    case UINT4:
-    case UINT8:
-      return true;
-    default:
-      return false;
+  /**
+   * Reports whether given RPC-level type is a signed type (per semantics of
+   * {@link ResultSetMetaData#isSigned(int)}).
+   */
+  public static boolean isJdbcSignedType( final MajorType type ) {
+    final boolean isSigned;
+    switch ( type.getMode() ) {
+      case REPEATED:
+        isSigned = false;   // SQL ARRAY
+        break;
+      case REQUIRED:
+      case OPTIONAL:
+        switch ( type.getMinorType() ) {
+          // Verified signed types:
+          case SMALLINT:
+          case INT:             // SQL INTEGER
+          case BIGINT:
+          case FLOAT4:          // SQL REAL / FLOAT(N)
+          case FLOAT8:          // SQL DOUBLE PRECISION / FLOAT(N)
+          case INTERVALYEAR:    // SQL INTERVAL w/YEAR and/or MONTH
+          case INTERVALDAY:     // SQL INTERVAL w/DAY, HOUR, MINUTE and/or SECOND
+          // Not-yet seen/verified signed types:
+          case DECIMAL9:        // SQL DECIMAL (if used)
+          case DECIMAL18:       // SQL DECIMAL (if used)
+          case DECIMAL28SPARSE: // SQL DECIMAL (if used)
+          case DECIMAL38SPARSE: // SQL DECIMAL (if used)
+          case DECIMAL28DENSE:  // SQL DECIMAL (if used)
+          case DECIMAL38DENSE:  // SQL DECIMAL (if used)
+          case TINYINT:         // (not standard SQL)
+          case MONEY:           // (not standard SQL)
+          case INTERVAL:        // unknown (given INTERVALYEAR and INTERVALDAY)
+            isSigned = true;
+            break;
+          // Verified unsigned types:
+          case BIT:            // SQL BOOLEAN
+          case VARCHAR:
+          case FIXEDCHAR:      // SQL CHARACTER
+          case VARBINARY:
+          case FIXEDBINARY:    // SQL BINARY
+          case DATE:
+          case TIME:           // SQL TIME WITHOUT TIME ZONE
+          case TIMESTAMP:      // SQL TIMESTAMP WITHOUT TIME ZONE
+          // Not-yet seen/verified unsigned types:
+          case UINT1:
+          case UINT2:
+          case UINT4:
+          case UINT8:
+          case FIXED16CHAR:
+          case VAR16CHAR:
+          case GENERIC_OBJECT:
+          case LATE:
+          case LIST:
+          case MAP:
+          case NULL:
+          case TIMETZ:      // SQL TIME WITH TIME ZONE
+          case TIMESTAMPTZ: // SQL TIMESTAMP WITH TIME ZONE
+            isSigned = false;
+            break;
+          default:
+            throw new UnsupportedOperationException(
+                "Unexpected/unhandled MinorType value " + type.getMinorType() );
+        }
+        break;
+      default:
+        throw new UnsupportedOperationException(
+            "Unexpected/unhandled DataMode value " + type.getMode() );
     }
-
+    return isSigned;
   }
+
   public static boolean usesHolderForGet(final MajorType type) {
     if (type.getMode() == REPEATED) {
       return true;

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/codegen/templates/SqlAccessors.java b/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
index c50a3e2..3257499 100644
--- a/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
+++ b/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
@@ -24,6 +24,10 @@ import java.lang.Override;
 <#list ["", "Nullable"] as mode>
 <#assign name = mode + minor.class?cap_first />
 <#assign javaType = (minor.javaType!type.javaType) />
+<#assign friendlyType = (minor.friendlyType!minor.boxedType!type.boxedType) />
+<#-- Class returned by ResultSet.getObject(...): -->
+<#assign jdbcObjectClass = minor.jdbcObjectClass ! friendlyType />
+
 <@pp.changeOutputFile name="/org/apache/drill/exec/vector/accessor/${name}Accessor.java" />
 <#include "/@includes/license.ftl" />
 
@@ -60,6 +64,11 @@ public class ${name}Accessor extends AbstractSqlAccessor {
   }
 
  <#if minor.class != "TimeStamp" && minor.class != "Time" && minor.class != "Date">
+  @Override
+  public Class<?> getObjectClass() {
+    return ${jdbcObjectClass}.class;
+  }
+
   public Object getObject(int index) {
    <#if mode == "Nullable">
     if (ac.isNull(index)) {
@@ -161,6 +170,11 @@ public class ${name}Accessor extends AbstractSqlAccessor {
 
   <#if minor.class == "TimeStampTZ">
 
+  @Override
+  public Class<?> getObjectClass() {
+    return Timestamp.class;
+  }
+
   public Object getObject(int index) {
     return getTimestamp(index);
   }
@@ -200,6 +214,11 @@ public class ${name}Accessor extends AbstractSqlAccessor {
   }
   <#elseif minor.class == "Date">
 
+  @Override
+  public Class<?> getObjectClass() {
+    return Date.class;
+  }
+
   public Object getObject(int index) {
    <#if mode == "Nullable">
     if (ac.isNull(index)) {
@@ -223,6 +242,11 @@ public class ${name}Accessor extends AbstractSqlAccessor {
 
   <#elseif minor.class == "TimeStamp">
 
+  @Override
+  public Class<?> getObjectClass() {
+    return Timestamp.class;
+  }
+
   public Object getObject(int index) {
    <#if mode == "Nullable">
     if (ac.isNull(index)) {
@@ -246,6 +270,11 @@ public class ${name}Accessor extends AbstractSqlAccessor {
 
   <#elseif minor.class == "Time">
 
+  @Override
+  public Class<?> getObjectClass() {
+    return Time.class;
+  }
+
   public Object getObject(int index) {
     return getTime(index);
   }

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/BoundCheckingAccessor.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/BoundCheckingAccessor.java b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/BoundCheckingAccessor.java
index 3d3683e..bfef947 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/BoundCheckingAccessor.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/BoundCheckingAccessor.java
@@ -45,6 +45,11 @@ public class BoundCheckingAccessor implements SqlAccessor {
   }
 
   @Override
+  public Class<?> getObjectClass() {
+    return delegate.getObjectClass();
+  }
+
+  @Override
   public boolean isNull(int rowOffset) {
     return delegate.isNull(rowOffset);
   }

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/GenericAccessor.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/GenericAccessor.java b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/GenericAccessor.java
index 65c34ad..d8f2995 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/GenericAccessor.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/GenericAccessor.java
@@ -20,6 +20,7 @@ package org.apache.drill.exec.vector.accessor;
 import org.apache.drill.common.types.TypeProtos;
 import org.apache.drill.exec.vector.ValueVector;
 
+
 public class GenericAccessor extends AbstractSqlAccessor {
 
   private ValueVector v;
@@ -29,6 +30,11 @@ public class GenericAccessor extends AbstractSqlAccessor {
   }
 
   @Override
+  public Class<?> getObjectClass() {
+    return Object.class;
+  }
+
+  @Override
   public boolean isNull(int index) {
     return v.getAccessor().isNull(index);
   }

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/SqlAccessor.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/SqlAccessor.java b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/SqlAccessor.java
index 19e6fcf..636456d 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/SqlAccessor.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/vector/accessor/SqlAccessor.java
@@ -63,6 +63,14 @@ public interface SqlAccessor {
   MajorType getType();
 
   /**
+   * Reports the class returned by getObject() of this accessor.
+   * <p>
+   *  (Is for {@link ResultSetMetaData#getColumnClassName(...)}.)
+   * </p>
+   */
+  Class<?> getObjectClass();
+
+  /**
    * Reports whether the logical value is a SQL NULL.
    */
   boolean isNull(int rowOffset);

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/AvaticaDrillSqlAccessor.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/AvaticaDrillSqlAccessor.java b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/AvaticaDrillSqlAccessor.java
index 64f5b87..7e53a07 100644
--- a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/AvaticaDrillSqlAccessor.java
+++ b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/AvaticaDrillSqlAccessor.java
@@ -78,6 +78,13 @@ class AvaticaDrillSqlAccessor implements Accessor {
     }
   }
 
+  /**
+   * @see SQLAccesstor#getObjectClass()
+   */
+  public Class<?> getObjectClass() {
+    return underlyingAccessor.getObjectClass();
+  }
+
   @Override
   public boolean wasNull() throws SQLException {
     return underlyingAccessor.isNull(getCurrentRecordNumber());

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillAccessorList.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillAccessorList.java b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillAccessorList.java
index 25ca1ba..9284875 100644
--- a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillAccessorList.java
+++ b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillAccessorList.java
@@ -31,14 +31,14 @@ import org.apache.drill.exec.vector.accessor.SqlAccessor;
 class DrillAccessorList extends BasicList<Accessor>{
   static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DrillAccessorList.class);
 
-  private Accessor[] accessors = new Accessor[0];
+  private AvaticaDrillSqlAccessor[] accessors = new AvaticaDrillSqlAccessor[0];
   // TODO  Rename to lastColumnAccessed and/or document.
   // TODO  Why 1, rather than, say, -1?
   private int lastColumn = 1;
 
-  void generateAccessors(DrillCursor cursor, RecordBatchLoader currentBatch){
+  void generateAccessors(DrillCursor cursor, RecordBatchLoader currentBatch) {
     int cnt = currentBatch.getSchema().getFieldCount();
-    accessors = new Accessor[cnt];
+    accessors = new AvaticaDrillSqlAccessor[cnt];
     for(int i =0; i < cnt; i++){
       final ValueVector vector = currentBatch.getValueAccessorById(null, i).getValueVector();
       final SqlAccessor acc =
@@ -50,7 +50,7 @@ class DrillAccessorList extends BasicList<Accessor>{
   }
 
   @Override
-  public Accessor get(int index) {
+  public AvaticaDrillSqlAccessor get(int index) {
     lastColumn = index;
     return accessors[index];
   }

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillColumnMetaDataList.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillColumnMetaDataList.java b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillColumnMetaDataList.java
index d43755e..1813cc7 100644
--- a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillColumnMetaDataList.java
+++ b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillColumnMetaDataList.java
@@ -41,7 +41,7 @@ public class DrillColumnMetaDataList extends BasicList<ColumnMetaData>{
 
   @Override
   public int size() {
-    return (columns.size());
+    return columns.size();
   }
 
   @Override
@@ -49,121 +49,80 @@ public class DrillColumnMetaDataList extends BasicList<ColumnMetaData>{
     return columns.get(index);
   }
 
-  public void updateColumnMetaData(String catalogName, String schemaName, String tableName, BatchSchema schema){
-
-    columns = new ArrayList<ColumnMetaData>(schema.getFieldCount());
-    for(int i = 0; i < schema.getFieldCount(); i++){
-      MaterializedField f = schema.getColumn(i);
-      MajorType t = f.getType();
-      ColumnMetaData col = new ColumnMetaData( //
-          i, // ordinal
-          false, // autoIncrement
-          true, // caseSensitive
-          false, // searchable
-          false, // currency
-          f.getDataMode() == DataMode.OPTIONAL ? ResultSetMetaData.columnNullable : ResultSetMetaData.columnNoNulls, //nullability
-          !Types.isUnSigned(t), // signed
-          10, // display size.
-          f.getAsSchemaPath().getRootSegment().getPath(), // label
-          f.getAsSchemaPath().getRootSegment().getPath(), // columnname
-          schemaName, // schemaname
-          t.hasPrecision() ? t.getPrecision() : 0, // precision
-          t.hasScale() ? t.getScale() : 0, // scale
-          null, // tablename is null so sqlline doesn't try to retrieve primary keys.
-          catalogName, // catalogname
-          getAvaticaType(t),  // sql type
-          true, // readonly
-          false, // writable
-          false, // definitely writable
-          "none" // column class name
-         );
-      columns.add(col);
-    }
+  /**
+   * Gets AvaticaType carrying both JDBC {@code java.sql.Type.*} type code
+   * and SQL type name for given RPC-level type (from batch schema).
+   */
+  private static AvaticaType getAvaticaType( MajorType rpcDateType ) {
+    final String sqlTypeName = Types.getSqlTypeName( rpcDateType );
+    final int jdbcTypeId = Types.getJdbcTypeCode( rpcDateType );
+    return ColumnMetaData.scalar( jdbcTypeId, sqlTypeName,
+                                  Rep.BOOLEAN /* dummy value, unused */ );
   }
 
-  private static AvaticaType getAvaticaType(MajorType t){
-    final int jdbcTypeId = Types.getJdbcType(t);
-    return ColumnMetaData.scalar(jdbcTypeId, getJdbcTypeName(jdbcTypeId), Rep.BOOLEAN /* dummy value, unused */);
-  }
-
-  private static String getJdbcTypeName(int type) {
-    switch (type) {
-    case java.sql.Types.BIT:
-        return "BIT";
-    case java.sql.Types.TINYINT:
-        return "TINYINT";
-    case java.sql.Types.SMALLINT:
-        return "SMALLINT";
-    case java.sql.Types.INTEGER:
-        return "INTEGER";
-    case java.sql.Types.BIGINT:
-        return "BIGINT";
-    case java.sql.Types.FLOAT:
-        return "FLOAT";
-    case java.sql.Types.REAL:
-        return "REAL";
-    case java.sql.Types.DOUBLE:
-        return "DOUBLE";
-    case java.sql.Types.NUMERIC:
-        return "NUMERIC";
-    case java.sql.Types.DECIMAL:
-        return "DECIMAL";
-    case java.sql.Types.CHAR:
-        return "CHAR";
-    case java.sql.Types.VARCHAR:
-        return "VARCHAR";
-    case java.sql.Types.LONGVARCHAR:
-        return "LONGVARCHAR";
-    case java.sql.Types.DATE:
-        return "DATE";
-    case java.sql.Types.TIME:
-        return "TIME";
-    case java.sql.Types.TIMESTAMP:
-        return "TIMESTAMP";
-    case java.sql.Types.BINARY:
-        return "BINARY";
-    case java.sql.Types.VARBINARY:
-        return "VARBINARY";
-    case java.sql.Types.LONGVARBINARY:
-        return "LONGVARBINARY";
-    case java.sql.Types.NULL:
-        return "NULL";
-    case java.sql.Types.OTHER:
-        return "OTHER";
-    case java.sql.Types.JAVA_OBJECT:
-        return "JAVA_OBJECT";
-    case java.sql.Types.DISTINCT:
-        return "DISTINCT";
-    case java.sql.Types.STRUCT:
-        return "STRUCT";
-    case java.sql.Types.ARRAY:
-        return "ARRAY";
-    case java.sql.Types.BLOB:
-        return "BLOB";
-    case java.sql.Types.CLOB:
-        return "CLOB";
-    case java.sql.Types.REF:
-        return "REF";
-    case java.sql.Types.DATALINK:
-        return "DATALINK";
-    case java.sql.Types.BOOLEAN:
-        return "BOOLEAN";
-    case java.sql.Types.ROWID:
-        return "ROWID";
-    case java.sql.Types.NCHAR:
-        return "NCHAR";
-    case java.sql.Types.NVARCHAR:
-        return "NVARCHAR";
-    case java.sql.Types.LONGNVARCHAR:
-        return "LONGNVARCHAR";
-    case java.sql.Types.NCLOB:
-        return "NCLOB";
-    case java.sql.Types.SQLXML:
-        return "SQLXML";
-    default:
-        logger.error( "Unexpected java.sql.Types value {}", type );
-        return "unknown java.sql.Types value " + type;
+  public void updateColumnMetaData(String catalogName, String schemaName,
+                                   String tableName, BatchSchema schema,
+                                   List<Class<?>> getObjectClasses ) {
+    final List<ColumnMetaData> newColumns =
+        new ArrayList<ColumnMetaData>(schema.getFieldCount());
+    for (int colOffset = 0; colOffset < schema.getFieldCount(); colOffset++) {
+      final MaterializedField field = schema.getColumn(colOffset);
+      Class<?> objectClass = getObjectClasses.get( colOffset );
+
+      final String columnName = field.getPath().getRootSegment().getPath();
+
+      final MajorType rpcDataType = field.getType();
+      final AvaticaType bundledSqlDataType = getAvaticaType(rpcDataType);
+      final String columnClassName = objectClass.getName();
+
+      final int nullability;
+      switch ( field.getDataMode() ) {
+        case OPTIONAL: nullability = ResultSetMetaData.columnNullable; break;
+        case REQUIRED: nullability = ResultSetMetaData.columnNoNulls;  break;
+        // Should REPEATED still map to columnNoNulls? or to columnNullable?
+        case REPEATED: nullability = ResultSetMetaData.columnNoNulls;  break;
+        default:
+          throw new AssertionError( "Unexpected new DataMode value '"
+                                    + field.getDataMode().name() + "'" );
+      }
+      final boolean isSigned = Types.isJdbcSignedType( rpcDataType );
+
+      // TODO(DRILL-3355):  TODO(DRILL-3356):  When string lengths, precisions,
+      // interval kinds, etc., are available from RPC-level data, implement:
+      // - precision for ResultSetMetadata.getPrecision(...) (like
+      //   getColumns()'s COLUMN_SIZE)
+      // - scale for getScale(...), and
+      // - and displaySize for getColumnDisplaySize(...).
+      final int precision =
+          rpcDataType.hasPrecision() ? rpcDataType.getPrecision() : 0;
+      final int scale = rpcDataType.hasScale() ? rpcDataType.getScale() : 0;
+      final int displaySize = 10;
+
+      ColumnMetaData col = new ColumnMetaData(
+          colOffset,    // (zero-based ordinal (for Java arrays/lists).)
+          false,        /* autoIncrement */
+          false,        /* caseSensitive */
+          true,         /* searchable */
+          false,        /* currency */
+          nullability,
+          isSigned,
+          displaySize,
+          columnName,   /* label */
+          columnName,   /* columnName */
+          schemaName,
+          precision,
+          scale,
+          tableName,
+          catalogName,
+          bundledSqlDataType,
+          true,         /* readOnly */
+          false,        /* writable */
+          false,        /* definitelyWritable */
+          columnClassName
+         );
+      newColumns.add(col);
     }
+    columns = newColumns;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillCursor.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillCursor.java b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillCursor.java
index 5ae7509..07d7066 100644
--- a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillCursor.java
+++ b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/DrillCursor.java
@@ -18,6 +18,7 @@
 package org.apache.drill.jdbc.impl;
 
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.List;
 
@@ -31,6 +32,7 @@ import org.apache.drill.exec.exception.SchemaChangeException;
 import org.apache.drill.exec.record.BatchSchema;
 import org.apache.drill.exec.record.RecordBatchLoader;
 import org.apache.drill.exec.rpc.user.QueryDataBatch;
+import org.apache.drill.exec.store.ischema.InfoSchemaConstants;
 import org.slf4j.Logger;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -38,7 +40,8 @@ import static org.slf4j.LoggerFactory.getLogger;
 class DrillCursor implements Cursor {
   private static final Logger logger = getLogger( DrillCursor.class );
 
-  private static final String UNKNOWN = "--UNKNOWN--";
+  /** JDBC-specified string for unknown catalog, schema, and table names. */
+  private static final String UNKNOWN_NAME_STRING = "";
 
   /** The associated {@link java.sql.ResultSet} implementation. */
   private final DrillResultSetImpl resultSet;
@@ -104,6 +107,10 @@ class DrillCursor implements Cursor {
     return currentRecordNumber;
   }
 
+  // (Overly restrictive Avatica uses List<Accessor> instead of List<? extends
+  // Accessor>, so accessors/DrillAccessorList can't be of type
+  // List<AvaticaDrillSqlAccessor>, and we have to cast from Accessor to
+  // AvaticaDrillSqlAccessor in updateColumns().)
   @Override
   public List<Accessor> createAccessors(List<ColumnMetaData> types,
                                         Calendar localCalendar, Factory factory) {
@@ -111,9 +118,31 @@ class DrillCursor implements Cursor {
     return accessors;
   }
 
+  /**
+   * Updates column accessors and metadata from current record batch.
+   */
   private void updateColumns() {
+    // First update accessors and schema from batch:
     accessors.generateAccessors(this, currentBatchHolder);
-    columnMetaDataList.updateColumnMetaData(UNKNOWN, UNKNOWN, UNKNOWN, schema);
+
+    // Extract Java types from accessors for metadata's getColumnClassName:
+    final List<Class<?>> getObjectClasses = new ArrayList<>();
+    // (Can't use modern for loop because, for some incompletely clear reason,
+    // DrillAccessorList blocks iterator() (throwing exception).)
+    for ( int ax = 0; ax < accessors.size(); ax++ ) {
+      final AvaticaDrillSqlAccessor accessor =
+          (AvaticaDrillSqlAccessor) accessors.get( ax );
+      getObjectClasses.add( accessor.getObjectClass() );
+    }
+
+    // Update metadata for result set.
+    columnMetaDataList.updateColumnMetaData(
+        InfoSchemaConstants.IS_CATALOG_NAME,
+        UNKNOWN_NAME_STRING,  // schema name
+        UNKNOWN_NAME_STRING,  // table name
+        schema,
+        getObjectClasses );
+
     if (getResultSet().changeListener != null) {
       getResultSet().changeListener.schemaChanged(schema);
     }
@@ -180,8 +209,9 @@ class DrillCursor implements Cursor {
           afterLastRow = true;
           return false;
         } else {
-          // Got next (or first) batch--reset record offset to beginning,
-          // assimilate schema if changed, ... ???
+          // Got next (or first) batch--reset record offset to beginning;
+          // assimilate schema if changed; set up return value for first call
+          // to next().
 
           currentRecordNumber = 0;
 

http://git-wip-us.apache.org/repos/asf/drill/blob/80835082/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/TypeConvertingSqlAccessor.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/TypeConvertingSqlAccessor.java b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/TypeConvertingSqlAccessor.java
index b542f94..2e17e33 100644
--- a/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/TypeConvertingSqlAccessor.java
+++ b/exec/jdbc/src/main/java/org/apache/drill/jdbc/impl/TypeConvertingSqlAccessor.java
@@ -46,6 +46,11 @@ class TypeConvertingSqlAccessor implements SqlAccessor {
   }
 
   @Override
+  public Class<?> getObjectClass() {
+    return innerAccessor.getObjectClass();
+  }
+
+  @Override
   public boolean isNull( int rowOffset ) {
     return innerAccessor.isNull( rowOffset );
   }