You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by pa...@apache.org on 2015/03/27 18:49:19 UTC

[1/6] drill git commit: DRILL-2463: Implement JDBC mapping of SQL NULL for ResultSet.getXxx() methods.

Repository: drill
Updated Branches:
  refs/heads/master ee94a37e5 -> 20efb2fbc


DRILL-2463: Implement JDBC mapping of SQL NULL for ResultSet.getXxx() methods.

- Added test Drill2463GetNullsFailedWithAssertionsBugTest.
- Implemented JDBC mapping of NULL for ResultSet.getXxx() methods:
  - Fixed was-always-false isNull(...) for Nullable... SqlAccessor
    implementations in SqlAccessors template.
  - Handled NULL mapping for primitive types in AvaticaDrillSqlAccessor.
  - Handled NULL mapping for object types in Nullable... SqlAccessor
    implementations in SqlAccessors template.
- Related miscellaneous changes:
  - Added "rename this" TODO.  [config.fmpp]
  - Added documentation.  [SqlAccessor]
  - Edited comment.  [BoundCheckingAccessor]


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

Branch: refs/heads/master
Commit: 83ebf4f3e12ae42ba771e7c1cdd833a27ecf9b35
Parents: ee94a37
Author: dbarclay <db...@maprtech.com>
Authored: Tue Mar 17 20:45:31 2015 -0700
Committer: Parth Chandra <pc...@maprtech.com>
Committed: Fri Mar 27 10:19:14 2015 -0700

----------------------------------------------------------------------
 exec/java-exec/src/main/codegen/config.fmpp     |   1 +
 .../main/codegen/templates/SqlAccessors.java    | 199 ++++++++++++++-----
 .../vector/accessor/BoundCheckingAccessor.java  |   4 +-
 .../drill/exec/vector/accessor/SqlAccessor.java |  83 +++++---
 .../drill/jdbc/AvaticaDrillSqlAccessor.java     |  37 +++-
 ...2463GetNullsFailedWithAssertionsBugTest.java | 122 ++++++++++++
 6 files changed, 360 insertions(+), 86 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/83ebf4f3/exec/java-exec/src/main/codegen/config.fmpp
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/codegen/config.fmpp b/exec/java-exec/src/main/codegen/config.fmpp
index 5dc7360..8db120d 100644
--- a/exec/java-exec/src/main/codegen/config.fmpp
+++ b/exec/java-exec/src/main/codegen/config.fmpp
@@ -15,6 +15,7 @@
 # limitations under the License.
 
 data: {
+    # TODO:  Rename to ~valueVectorModesAndTypes for clarity.
     vv:                       tdd(../data/ValueVectorTypes.tdd),
 
     # Most types for comparison functions (for templates/ComparisonFunctions.java).

http://git-wip-us.apache.org/repos/asf/drill/blob/83ebf4f3/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 3039e5c..a838e11 100644
--- a/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
+++ b/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
@@ -32,150 +32,245 @@ package org.apache.drill.exec.vector.accessor;
 <#include "/@includes/vv_imports.ftl" />
 
 @SuppressWarnings("unused")
-public class ${name}Accessor extends AbstractSqlAccessor{
-  <#if mode == "Nullable">
+public class ${name}Accessor extends AbstractSqlAccessor {
+ <#if mode == "Nullable">
   private static final MajorType TYPE = Types.optional(MinorType.${minor.class?upper_case});
-  <#else>
+ <#else>
   private static final MajorType TYPE = Types.required(MinorType.${minor.class?upper_case});
-  </#if>
-  
+ </#if>
+
   private final ${name}Vector.Accessor ac;
-  
-  public ${name}Accessor(${name}Vector vector){
+
+  public ${name}Accessor(${name}Vector vector) {
     this.ac = vector.getAccessor();
   }
 
-  <#if minor.class != "TimeStamp" && minor.class != "Time" && minor.class != "Date">
-  public Object getObject(int index){
+  @Override
+  MajorType getType() {
+    return TYPE;
+  };
+
+  @Override
+  public boolean isNull(int index) {
+   <#if mode == "Nullable">
+    return ac.isNull(index);
+   <#else>
+    return false;
+   </#if>
+  }
+
+ <#if minor.class != "TimeStamp" && minor.class != "Time" && minor.class != "Date">
+  public Object getObject(int index) {
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
     return ac.getObject(index);
   }
-  </#if>
-  
-  <#if type.major == "VarLen">
+ </#if>
+
+ <#if type.major == "VarLen">
 
-  @Override 
-  public InputStream getStream(int index){
+  @Override
+  public InputStream getStream(int index) {
+    <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
     ${name}Holder h = new ${name}Holder();
     ac.get(index, h);
     return new ByteBufInputStream(h.buffer.slice(h.start, h.end));
   }
-  
-  @Override 
-  public byte[] getBytes(int index){
+
+  @Override
+  public byte[] getBytes(int index) {
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
     return ac.get(index);
   }
-  
+
   <#switch minor.class>
+
     <#case "VarBinary">
     public String getString(int index) {
+     <#if mode == "Nullable">
+      if (ac.isNull(index)) {
+        return null;
+      }
+     </#if>
       byte [] b = ac.get(index);
       return DrillStringUtils.toBinaryString(b);
     }
       <#break>
+
     <#case "VarChar">
-    @Override 
-    public InputStreamReader getReader(int index){
+    @Override
+    public InputStreamReader getReader(int index) {
+     <#if mode == "Nullable">
+      if (ac.isNull(index)) {
+        return null;
+      }
+     </#if>
       return new InputStreamReader(getStream(index), Charsets.UTF_8);
     }
-    
-    @Override 
-    public String getString(int index){
+
+    @Override
+    public String getString(int index) {
+     <#if mode == "Nullable">
+      if (ac.isNull(index)) {
+        return null;
+      }
+     </#if>
       return new String(getBytes(index), Charsets.UTF_8);
     }
-    
-    
       <#break>
+
     <#case "Var16Char">
-    @Override 
-    public InputStreamReader getReader(int index){
+    @Override
+    public InputStreamReader getReader(int index) {
+     <#if mode == "Nullable">
+      if (ac.isNull(index)) {
+        return null;
+      }
+     </#if>
       return new InputStreamReader(getStream(index), Charsets.UTF_16);
     }
-    
-    @Override 
-    public String getString(int index){
+
+    @Override
+    public String getString(int index) {
+     <#if mode == "Nullable">
+      if (ac.isNull(index)) {
+        return null;
+      }
+     </#if>
       return new String(getBytes(index), Charsets.UTF_16);
     }
-        
-    
       <#break>
-    <#default> 
+
+    <#default>
     This is uncompilable code
+
   </#switch>
 
-  <#else>
-  <#if minor.class == "Interval" || minor.class == "IntervalDay" || minor.class == "IntervalYear">
+ <#else> <#-- VarLen -->
+
+  <#if minor.class == "TimeStampTZ">
+
+  public Object getObject(int index) {
+    return getTimestamp(index);
+  }
+
+  @Override
+  public Timestamp getTimestamp(int index) {
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
+    return new Timestamp(ac.getObject(index).getMillis());
+  }
+
+  <#elseif minor.class == "Interval" || minor.class == "IntervalDay" || minor.class == "IntervalYear">
+
   @Override
   public String getString(int index) {
-      return String.valueOf(ac.getAsStringBuilder(index));
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
+    return String.valueOf(ac.getAsStringBuilder(index));
   }
+
   <#elseif minor.class.startsWith("Decimal")>
+
   @Override
   public BigDecimal getBigDecimal(int index) {
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
       return ac.getObject(index);
   }
   <#elseif minor.class == "Date">
+
   public Object getObject(int index) {
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
     return getDate(index);
   }
 
   @Override
   public Date getDate(int index) {
-    <#if mode == "Nullable">
+   <#if mode == "Nullable">
     if (ac.isNull(index)) {
       return null;
     }
-    </#if>
+   </#if>
     org.joda.time.DateTime date = new org.joda.time.DateTime(ac.get(index), org.joda.time.DateTimeZone.UTC);
     date = date.withZoneRetainFields(org.joda.time.DateTimeZone.getDefault());
     return new Date(date.getMillis());
   }
+
   <#elseif minor.class == "TimeStamp">
+
   public Object getObject(int index) {
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return null;
+    }
+   </#if>
     return getTimestamp(index);
   }
 
   @Override
   public Timestamp getTimestamp(int index) {
-    <#if mode == "Nullable">
+   <#if mode == "Nullable">
     if (ac.isNull(index)) {
       return null;
     }
-    </#if>
+   </#if>
     org.joda.time.DateTime date = new org.joda.time.DateTime(ac.get(index), org.joda.time.DateTimeZone.UTC);
     date = date.withZoneRetainFields(org.joda.time.DateTimeZone.getDefault());
     return new Timestamp(date.getMillis());
   }
+
   <#elseif minor.class == "Time">
+
   public Object getObject(int index) {
     return getTime(index);
   }
 
   @Override
   public Time getTime(int index) {
-    <#if mode == "Nullable">
+   <#if mode == "Nullable">
     if (ac.isNull(index)) {
       return null;
     }
-    </#if>
+   </#if>
     org.joda.time.DateTime time = new org.joda.time.DateTime(ac.get(index), org.joda.time.DateTimeZone.UTC);
     time = time.withZoneRetainFields(org.joda.time.DateTimeZone.getDefault());
     return new TimePrintMillis(time.getMillis());
   }
+
   <#else>
+
   @Override
-  public ${javaType} get${javaType?cap_first}(int index){
+  public ${javaType} get${javaType?cap_first}(int index) {
     return ac.get(index);
   }
   </#if>
-  </#if>
-  
-  @Override
-  public boolean isNull(int index){
-    return false;
-  }
-  
-  @Override
-  MajorType getType(){return TYPE;};
+
+ </#if> <#-- not VarLen -->
 
 }
 

http://git-wip-us.apache.org/repos/asf/drill/blob/83ebf4f3/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 7e8da2c..c8d6cc7 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
@@ -130,8 +130,8 @@ public class BoundCheckingAccessor implements SqlAccessor {
    */
   @Override
   public Object getObject(int index) throws AbstractSqlAccessor.InvalidAccessException {
-    // in case some vectors have less values than others, callee invokes this method with index >= #getValueCount
-    // this should still yield null.
+    // In case some vectors have fewer values than others, and callee invokes
+    // this method with index >= getValueCount(), this should still yield null.
     final ValueVector.Accessor accessor = vector.getAccessor();
     if (index < accessor.getValueCount()) {
       return delegate.getObject(index);

http://git-wip-us.apache.org/repos/asf/drill/blob/83ebf4f3/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 b69ae54..6007bf4 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
@@ -26,53 +26,90 @@ import java.sql.Timestamp;
 
 import org.apache.drill.exec.vector.accessor.AbstractSqlAccessor.InvalidAccessException;
 
-// TODO:  Doc.
-public interface SqlAccessor {
 
-  // TODO:  Document (renamed) index.
-  // TODO:  Rename ambiguous "index" (JDBC (1-based) column index? other index?)
-  // TODO:  Doc./Spec.:  What happens if index is invalid?
+// TODO:  Doc.:  Document more of basics of pattern of contracts for getXxx(...):
+// - What index is (especially since is not 1-based JDBC/SQL column index).
+// - What constitutes invalid access (that throws InvalidAccessException):
+//   - Does it include out-of-bound index values?  (The lack of "throws
+//     InvalidAccessException" on isNull(...) suggests no, but ...)
 
-  public abstract boolean isNull(int index);
+/**
+ * Column-data accessor that implements JDBC's Java-null--when--SQL-NULL mapping.
+ * <p>
+ *   When the requested value is logically a SQL NULL:
+ * </p>
+ * <li>
+ *   a get method that return primitive type throws an exception (callers are
+ *   responsible for calling {@link isNull} to check for null before calling
+ *   such methods)
+ * </li>
+ * <li>
+ *   a get method that returns a non-primitive type returns Java {@code null}
+ *   (the caller does not need to call {@link isNull} to check for nulls)
+ * </li>
+ */
+public interface SqlAccessor {
 
   // TODO:  Clean:  This interface refers to type InvalidAccessException
   // defined in class implementing this interface.
 
-  public abstract BigDecimal getBigDecimal(int index) throws InvalidAccessException;
+  /**
+   * Reports whether the logical value is a SQL NULL.
+   */
+  boolean isNull(int index);
+
+  /** (See {@link SqlAccessor class description}.) */
+  BigDecimal getBigDecimal(int index) throws InvalidAccessException;
 
-  public abstract boolean getBoolean(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  boolean getBoolean(int index) throws InvalidAccessException;
 
-  public abstract byte getByte(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  byte getByte(int index) throws InvalidAccessException;
 
-  public abstract byte[] getBytes(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  byte[] getBytes(int index) throws InvalidAccessException;
 
-  public abstract Date getDate(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  Date getDate(int index) throws InvalidAccessException;
 
-  public abstract double getDouble(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  double getDouble(int index) throws InvalidAccessException;
 
-  public abstract float getFloat(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  float getFloat(int index) throws InvalidAccessException;
 
-  public abstract char getChar(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  char getChar(int index) throws InvalidAccessException;
 
-  public abstract int getInt(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  int getInt(int index) throws InvalidAccessException;
 
-  public abstract long getLong(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  long getLong(int index) throws InvalidAccessException;
 
-  public abstract short getShort(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  short getShort(int index) throws InvalidAccessException;
 
-  public abstract InputStream getStream(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  InputStream getStream(int index) throws InvalidAccessException;
 
-  public abstract Reader getReader(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  Reader getReader(int index) throws InvalidAccessException;
 
   // TODO: Doc./Spec.:  What should happen if called on non-string type?  (Most
   // are convertible to string.  Does that result in error or conversion?)
   // Similar question for many other methods.
-  public abstract String getString(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  String getString(int index) throws InvalidAccessException;
 
-  public abstract Time getTime(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  Time getTime(int index) throws InvalidAccessException;
 
-  public abstract Timestamp getTimestamp(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  Timestamp getTimestamp(int index) throws InvalidAccessException;
 
-  public abstract Object getObject(int index) throws InvalidAccessException;
+  /** (See {@link SqlAccessor class description}.) */
+  Object getObject(int index) throws InvalidAccessException;
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/drill/blob/83ebf4f3/exec/jdbc/src/main/java/org/apache/drill/jdbc/AvaticaDrillSqlAccessor.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/main/java/org/apache/drill/jdbc/AvaticaDrillSqlAccessor.java b/exec/jdbc/src/main/java/org/apache/drill/jdbc/AvaticaDrillSqlAccessor.java
index 3702257..2b20f23 100644
--- a/exec/jdbc/src/main/java/org/apache/drill/jdbc/AvaticaDrillSqlAccessor.java
+++ b/exec/jdbc/src/main/java/org/apache/drill/jdbc/AvaticaDrillSqlAccessor.java
@@ -39,9 +39,14 @@ import net.hydromatic.avatica.Cursor.Accessor;
 
 import org.apache.drill.exec.vector.accessor.SqlAccessor;
 
+// TODO:  Revisit adding null check for non-primitive types to SqlAccessor's
+// contract and classes generated by SqlAccessor template (DRILL-xxxx).
 
-public class AvaticaDrillSqlAccessor implements Accessor{
-  static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(AvaticaDrillSqlAccessor.class);
+public class AvaticaDrillSqlAccessor implements Accessor {
+  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(AvaticaDrillSqlAccessor.class);
+
+  private final static byte PRIMITIVE_NUM_NULL_VALUE = 0;
+  private final static boolean BOOLEAN_NULL_VALUE = false;
 
   private SqlAccessor underlyingAccessor;
   private DrillCursor cursor;
@@ -78,37 +83,51 @@ public class AvaticaDrillSqlAccessor implements Accessor{
 
   @Override
   public boolean getBoolean() throws SQLException {
-    return underlyingAccessor.getBoolean(getCurrentRecordNumber());
+    return underlyingAccessor.isNull(getCurrentRecordNumber())
+        ? BOOLEAN_NULL_VALUE
+        : underlyingAccessor.getBoolean(getCurrentRecordNumber());
   }
 
   @Override
   public byte getByte() throws SQLException {
-    return underlyingAccessor.getByte(getCurrentRecordNumber());
+    return underlyingAccessor.isNull(getCurrentRecordNumber())
+        ? PRIMITIVE_NUM_NULL_VALUE
+        : underlyingAccessor.getByte(getCurrentRecordNumber());
   }
 
   @Override
   public short getShort() throws SQLException {
-    return underlyingAccessor.getShort(getCurrentRecordNumber());
+    return underlyingAccessor.isNull(getCurrentRecordNumber())
+        ? PRIMITIVE_NUM_NULL_VALUE
+        : underlyingAccessor.getShort(getCurrentRecordNumber());
   }
 
   @Override
   public int getInt() throws SQLException {
-    return underlyingAccessor.getInt(getCurrentRecordNumber());
+    return underlyingAccessor.isNull(getCurrentRecordNumber())
+        ? PRIMITIVE_NUM_NULL_VALUE
+        : underlyingAccessor.getInt(getCurrentRecordNumber());
   }
 
   @Override
   public long getLong() throws SQLException {
-    return underlyingAccessor.getLong(getCurrentRecordNumber());
+    return underlyingAccessor.isNull(getCurrentRecordNumber())
+        ? PRIMITIVE_NUM_NULL_VALUE
+        : underlyingAccessor.getLong(getCurrentRecordNumber());
   }
 
   @Override
   public float getFloat() throws SQLException {
-    return underlyingAccessor.getFloat(getCurrentRecordNumber());
+    return underlyingAccessor.isNull(getCurrentRecordNumber())
+        ? PRIMITIVE_NUM_NULL_VALUE
+        : underlyingAccessor.getFloat(getCurrentRecordNumber());
   }
 
   @Override
   public double getDouble() throws SQLException {
-    return underlyingAccessor.getDouble(getCurrentRecordNumber());
+    return underlyingAccessor.isNull(getCurrentRecordNumber())
+        ? PRIMITIVE_NUM_NULL_VALUE
+        : underlyingAccessor.getDouble(getCurrentRecordNumber());
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/drill/blob/83ebf4f3/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2463GetNullsFailedWithAssertionsBugTest.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2463GetNullsFailedWithAssertionsBugTest.java b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2463GetNullsFailedWithAssertionsBugTest.java
new file mode 100644
index 0000000..c8346b1
--- /dev/null
+++ b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2463GetNullsFailedWithAssertionsBugTest.java
@@ -0,0 +1,122 @@
+/**
+ * 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.drill.jdbc.test;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.*;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.apache.drill.jdbc.Driver;
+import org.apache.drill.jdbc.JdbcTest;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+
+public class Drill2463GetNullsFailedWithAssertionsBugTest extends JdbcTest {
+
+  private static Connection connection;
+  private static Statement statement;
+
+  @BeforeClass
+  public static void setUpConnection() throws SQLException {
+    connection = new Driver().connect( "jdbc:drill:zk=local", null );
+    statement = connection.createStatement();
+  }
+
+  @AfterClass
+  public static void tearDownConnection() throws SQLException {
+    connection.close();
+  }
+
+  // Test primitive types vs. non-primitive types:
+
+  @Test
+  public void testGetPrimitiveTypeNullAsOwnType() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS INTEGER ) FROM INFORMATION_SCHEMA.CATALOGS" );
+    assert rs.next();
+    assertThat( "getInt(...) for NULL", rs.getInt( 1 ), equalTo( 0 ) );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+  @Test
+  public void testGetPrimitiveTypeNullAsObject() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS INTEGER ) FROM INFORMATION_SCHEMA.CATALOGS" );
+    assert rs.next();
+    assertThat( "getObject(...) for NULL", rs.getObject( 1 ), nullValue() );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+  @Test
+  public void testGetNonprimitiveTypeNullAsOwnType() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS VARCHAR) FROM INFORMATION_SCHEMA.CATALOGS" );
+    assert rs.next();
+    assertThat( "getString(...) for NULL", rs.getString( 1 ), nullValue() );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+
+  // Test a few specifics
+
+  @Test
+  public void testGetBooleanNullAsOwnType() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS BOOLEAN ) FROM INFORMATION_SCHEMA.CATALOGS" );
+    assert rs.next();
+    assertThat( "getBoolean(...) for NULL", rs.getBoolean( 1 ), equalTo( false ) );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+  @Test
+  public void testGetBooleanNullAsObject() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS BOOLEAN ) FROM INFORMATION_SCHEMA.CATALOGS" );
+    assert rs.next();
+    assertThat( "getObject(...) for NULL", rs.getObject( 1 ), nullValue() );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+  @Test
+  public void testGetIntegerNullAsOwnType() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS INTEGER ) FROM INFORMATION_SCHEMA.CATALOGS" );
+    assert rs.next();
+    assertThat( "getInt(...) for NULL", rs.getInt( 1 ), equalTo( 0 ) );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+  @Test
+  public void testGetIntegerNullAsObject() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS INTEGER ) FROM INFORMATION_SCHEMA.CATALOGS" );
+    assert rs.next();
+    assertThat( "getObject(...) for NULL", rs.getObject( 1 ), nullValue() );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+
+}


[3/6] drill git commit: DRILL-2461: Fix: INTERVAL in view makes INFORMATION_SCHEMA.COLUMN fail.

Posted by pa...@apache.org.
DRILL-2461: Fix: INTERVAL in view makes INFORMATION_SCHEMA.COLUMN fail.

- Created test.
- Handled INTERVAL data types in View:
  - Added <interval qualifier> data to FieldType.
  - Switch calling of createSqlType to createSqlIntervalType for interval types


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

Branch: refs/heads/master
Commit: 8796fd1e13f07ac84df1c466dce83456dfa22950
Parents: 9a5b50e
Author: dbarclay <db...@maprtech.com>
Authored: Sun Mar 22 13:05:38 2015 -0700
Committer: Parth Chandra <pc...@maprtech.com>
Committed: Fri Mar 27 10:19:32 2015 -0700

----------------------------------------------------------------------
 .../org/apache/drill/exec/dotdrill/View.java    | 160 +++++++++++++++----
 ...rill2461IntervalsBreakInfoSchemaBugTest.java |  91 +++++++++++
 2 files changed, 222 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/8796fd1e/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java b/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java
index a7b496b..c5c62d8 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java
@@ -22,9 +22,13 @@ import java.util.List;
 import org.apache.drill.exec.planner.StarColumnHelper;
 import org.apache.drill.exec.planner.types.RelDataTypeDrillImpl;
 import org.apache.drill.exec.planner.types.RelDataTypeHolder;
+
 import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.reltype.RelDataTypeFactory;
 import org.eigenbase.reltype.RelDataTypeField;
+import org.eigenbase.sql.SqlIntervalQualifier;
+import org.eigenbase.sql.parser.SqlParserPos;
+import org.eigenbase.sql.type.SqlTypeFamily;
 import org.eigenbase.sql.type.SqlTypeName;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
@@ -48,36 +52,50 @@ public class View {
 
   @JsonInclude(Include.NON_NULL)
   public static class FieldType {
-    public final String name;
-    public final SqlTypeName type;
-    public final Integer precision;
-    public final Integer scale;
-    public final Boolean isNullable;
+
+    private final String name;
+    private final SqlTypeName type;
+    private final Integer precision;
+    private final Integer scale;
+    private SqlIntervalQualifier intervalQualifier;
+    private final Boolean isNullable;
+
 
     @JsonCreator
     public FieldType(
-        @JsonProperty("name") String name,
-        @JsonProperty("type") SqlTypeName type,
-        @JsonProperty("precision") Integer precision,
-        @JsonProperty("scale") Integer scale,
-        @JsonProperty("isNullable") Boolean isNullable){
+        @JsonProperty("name")                       String name,
+        @JsonProperty("type")                       SqlTypeName type,
+        @JsonProperty("precision")                  Integer precision,
+        @JsonProperty("scale")                      Integer scale,
+        @JsonProperty("startUnit")                  SqlIntervalQualifier.TimeUnit startUnit,
+        @JsonProperty("endUnit")                    SqlIntervalQualifier.TimeUnit endUnit,
+        @JsonProperty("fractionalSecondPrecision")  Integer fractionalSecondPrecision,
+        @JsonProperty("isNullable")                 Boolean isNullable) {
       this.name = name;
       this.type = type;
       this.precision = precision;
       this.scale = scale;
+      this.intervalQualifier =
+          null == startUnit
+          ? null
+          : new SqlIntervalQualifier(
+              startUnit, precision, endUnit, fractionalSecondPrecision, SqlParserPos.ZERO );
 
-      // Property "isNullable" is not part of the initial view definition and added in DRILL-2342. If the
-      // default value is null, consider it as "true". It is safe to default to "nullable" than "required" type.
-      this.isNullable = (isNullable == null) ? true : isNullable;
+      // Property "isNullable" is not part of the initial view definition and
+      // was added in DRILL-2342.  If the default value is null, consider it as
+      // "true".  It is safe to default to "nullable" than "required" type.
+      this.isNullable = isNullable == null ? true : isNullable;
     }
 
-    public FieldType(String name, RelDataType dataType){
+    public FieldType(String name, RelDataType dataType) {
       this.name = name;
       this.type = dataType.getSqlTypeName();
+
       Integer p = null;
       Integer s = null;
+      Integer fractionalSecondPrecision = null;
 
-      switch(dataType.getSqlTypeName()){
+      switch (dataType.getSqlTypeName()) {
       case CHAR:
       case BINARY:
       case VARBINARY:
@@ -88,19 +106,100 @@ public class View {
         p = dataType.getPrecision();
         s = dataType.getScale();
         break;
+      case INTERVAL_YEAR_MONTH:
+      case INTERVAL_DAY_TIME:
+        p = dataType.getIntervalQualifier().getStartPrecision();
+      default:
+        break;
       }
 
       this.precision = p;
       this.scale = s;
+      this.intervalQualifier = dataType.getIntervalQualifier();
       this.isNullable = dataType.isNullable();
     }
+
+    /**
+     * Gets the name of this field.
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * Gets the data type of this field.
+     * (Data type only; not full datatype descriptor.)
+     */
+    public SqlTypeName getType() {
+      return type;
+    }
+
+    /**
+     * Gets the precision of the data type descriptor of this field.
+     * The precision is the precision for a numeric type, the length for a
+     * string type, or the start unit precision for an interval type.
+     * */
+    public Integer getPrecision() {
+      return precision;
+    }
+
+    /**
+     * Gets the numeric scale of the data type descriptor of this field,
+     * for numeric types.
+     */
+    public Integer getScale() {
+      return scale;
+    }
+
+    /**
+     * Gets the interval type qualifier of the interval data type descriptor of
+     * this field (<i>iff</i> interval type). */
+    @JsonIgnore
+    public SqlIntervalQualifier getIntervalQualifier() {
+      return intervalQualifier;
+    }
+
+    /**
+     * Gets the time range start unit of the type qualifier of the interval data
+     * type descriptor of this field (<i>iff</i> interval type).
+     */
+    public SqlIntervalQualifier.TimeUnit getStartUnit() {
+      return null == intervalQualifier ? null : intervalQualifier.getStartUnit();
+    }
+
+    /**
+     * Gets the time range end unit of the type qualifier of the interval data
+     * type descriptor of this field (<i>iff</i> interval type).
+     */
+    public SqlIntervalQualifier.TimeUnit getEndUnit() {
+      return null == intervalQualifier ? null : intervalQualifier.getEndUnit();
+    }
+
+    /**
+     * Gets the fractional second precision of the type qualifier of the interval
+     * data type descriptor of this field (<i>iff</i> interval type).
+     * Gets the interval type descriptor's fractional second precision
+     * (<i>iff</i> interval type).
+     */
+    public Integer getFractionalSecondPrecision() {
+      return null == intervalQualifier ? null : intervalQualifier.getFractionalSecondPrecision();
+    }
+
+    /**
+     * Gets the nullability of the data type desription of this field.
+     */
+    public Boolean getIsNullable() {
+      return isNullable;
+    }
+
   }
 
-  public View(String name, String sql, RelDataType rowType, List<String> workspaceSchemaPath){
+
+  public View(String name, String sql, RelDataType rowType, List<String> workspaceSchemaPath) {
     this.name = name;
     this.sql = sql;
     fields = Lists.newArrayList();
-    for(RelDataTypeField f : rowType.getFieldList()){
+    for (RelDataTypeField f : rowType.getFieldList()) {
       fields.add(new FieldType(f.getName(), f.getType()));
     }
     this.workspaceSchemaPath =
@@ -119,28 +218,31 @@ public class View {
         workspaceSchemaPath == null ? ImmutableList.<String>of() : ImmutableList.copyOf(workspaceSchemaPath);
   }
 
-  public RelDataType getRowType(RelDataTypeFactory factory){
+  public RelDataType getRowType(RelDataTypeFactory factory) {
 
     // if there are no fields defined, this is a dynamic view.
-    if(isDynamic()){
+    if (isDynamic()) {
       return new RelDataTypeDrillImpl(new RelDataTypeHolder(), factory);
     }
 
     List<RelDataType> types = Lists.newArrayList();
     List<String> names = Lists.newArrayList();
 
-    for(FieldType field : fields){
-      names.add(field.name);
+    for (FieldType field : fields) {
+      names.add(field.getName());
       RelDataType type;
-      if(field.precision == null && field.scale == null){
-        type = factory.createSqlType(field.type);
-      }else if(field.precision != null && field.scale == null){
-        type = factory.createSqlType(field.type, field.precision);
-      }else{
-        type = factory.createSqlType(field.type, field.precision, field.scale);
+      if (   SqlTypeFamily.INTERVAL_YEAR_MONTH == field.getType().getFamily()
+          || SqlTypeFamily.INTERVAL_DAY_TIME   == field.getType().getFamily() ) {
+       type = factory.createSqlIntervalType( field.getIntervalQualifier() );
+      } else if (field.getPrecision() == null && field.getScale() == null) {
+        type = factory.createSqlType(field.getType());
+      } else if (field.getPrecision() != null && field.getScale() == null) {
+        type = factory.createSqlType(field.getType(), field.getPrecision());
+      } else {
+        type = factory.createSqlType(field.getType(), field.getPrecision(), field.getScale());
       }
 
-      if (field.isNullable) {
+      if (field.getIsNullable()) {
         types.add(factory.createTypeWithNullability(type, true));
       } else {
         types.add(type);
@@ -157,7 +259,7 @@ public class View {
   @JsonIgnore
   public boolean hasStar() {
     for (FieldType field : fields) {
-      if (StarColumnHelper.isNonPrefixedStarColumn(field.name)) {
+      if (StarColumnHelper.isNonPrefixedStarColumn(field.getName())) {
         return true;
       }
     }

http://git-wip-us.apache.org/repos/asf/drill/blob/8796fd1e/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java
new file mode 100644
index 0000000..fce7923
--- /dev/null
+++ b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java
@@ -0,0 +1,91 @@
+/**
+ * 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.drill.jdbc.test;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.*;
+
+import org.apache.drill.common.util.TestTools;
+import org.apache.drill.jdbc.Driver;
+import org.apache.drill.jdbc.JdbcTest;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.sql.SQLException;
+
+
+public class Drill2461IntervalsBreakInfoSchemaBugTest extends JdbcTest {
+
+  private static final String VIEW_NAME =
+      Drill2461IntervalsBreakInfoSchemaBugTest.class.getSimpleName() + "_View";
+
+  private static Connection connection;
+
+
+  @BeforeClass
+  public static void setUpConnection() throws Exception {
+    connection = connect( "jdbc:drill:zk=local" );
+  }
+
+  @AfterClass
+  public static void tearDownConnection() throws Exception {
+    connection.close();
+  }
+
+
+  @Test
+  public void testIntervalInViewDoesntCrashInfoSchema() throws Exception {
+    final Statement stmt = connection.createStatement();
+    ResultSet util;
+
+    // Create a view using an INTERVAL type:
+    util = stmt.executeQuery( "USE dfs.tmp" );
+    assert util.next();
+    assert util.getBoolean( 1 )
+        : "Error setting schema to dfs.tmp: " + util.getString( 2 );
+    util = stmt.executeQuery(
+        "CREATE OR REPLACE VIEW " + VIEW_NAME + " AS "
+      + "\n  SELECT CAST( NULL AS INTERVAL HOUR(4) TO MINUTE ) AS optINTERVAL_HM "
+      + "\n  FROM INFORMATION_SCHEMA.CATALOGS "
+      + "\n  LIMIT 1 " );
+    assert util.next();
+    assert util.getBoolean( 1 )
+        : "Error creating temporary test-columns view " + VIEW_NAME + ": "
+          + util.getString( 2 );
+
+    // Test whether query INFORMATION_SCHEMA.COLUMNS works (doesn't crash):
+    util = stmt.executeQuery( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS" );
+    assert util.next();
+
+    // Clean up the test view:
+    util = connection.createStatement().executeQuery( "DROP VIEW " + VIEW_NAME );
+    assert util.next();
+    assert util.getBoolean( 1 )
+       : "Error dropping temporary test-columns view " + VIEW_NAME + ": "
+         + util.getString( 2 );
+  }
+
+}


[4/6] drill git commit: DRILL-2465: Fix multiple DatabaseMetaData.getColumns() bugs.

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/drill/blob/20efb2fb/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsBugsTest.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsBugsTest.java b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsBugsTest.java
deleted file mode 100644
index 0a9104b..0000000
--- a/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsBugsTest.java
+++ /dev/null
@@ -1,169 +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.apache.drill.jdbc.test;
-
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-import static org.hamcrest.CoreMatchers.*;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
-
-import org.apache.drill.jdbc.JdbcTest;
-
-
-/**
- * Basic (spot-check/incomplete) tests for DRILL-2128 bugs (many
- * DatabaseMetaData.getColumns(...) result table problems).
- */
-public class Drill2128GetColumnsBugsTest extends JdbcTest {
-
-  private static Connection connection;
-  private static DatabaseMetaData dbMetadata;
-
-  @BeforeClass
-  public static void setUpConnection() throws Exception {
-    connection = connect( "jdbc:drill:zk=local" );
-    dbMetadata = connection.getMetaData();
-  }
-
-  @AfterClass
-  public static void tearDownConnection() throws SQLException {
-    connection.close();
-  }
-
-
-  /**
-   * Basic test that column DATA_TYPE is integer type codes (not strings such
-   * as "VARCHAR" or "INTEGER").
-   */
-  @SuppressWarnings("unchecked")
-  @Test
-  public void testColumn_DATA_TYPE_isInteger() throws Exception {
-    // Get metadata for some column(s).
-    final ResultSet columns = dbMetadata.getColumns( null, null, null, null );
-    final boolean hasRow = columns.next();
-    assert hasRow : "DatabaseMetaData.getColumns(...) returned no rows";
-
-    do {
-      // DATA_TYPE should be INTEGER, so getInt( "DATA_TYPE" ) should succeed:
-      final int typeCode1 = columns.getInt( "DATA_TYPE" );
-
-      // DATA_TYPE should be at ordinal position 5 (seemingly):
-      assertThat( "Column 5's label",
-                  columns.getMetaData().getColumnLabel( 5 ), equalTo( "DATA_TYPE" ) );
-
-      // Also, getInt( 5 ) should succeed and return the same type code as above:
-      final int typeCode2 = columns.getInt( 5 );
-      assertThat( "getInt( 5 ) (expected to be same as getInt( \"DATA_TYPE\" ))",
-                  typeCode2, equalTo( typeCode1 ) );
-
-      // Type code should be one of java.sql.Types.*:
-      assertThat(
-          typeCode1,
-          anyOf( // List is from java.sql.Types
-                 equalTo( Types.ARRAY ),
-                 equalTo( Types.BIGINT ),
-                 equalTo( Types.BINARY ),
-                 equalTo( Types.BIT ),
-                 equalTo( Types.BLOB ),
-                 equalTo( Types.BOOLEAN ),
-                 equalTo( Types.CHAR ),
-                 equalTo( Types.CLOB ),
-                 equalTo( Types.DATALINK ),
-                 equalTo( Types.DATE ),
-                 equalTo( Types.DECIMAL ),
-                 equalTo( Types.DISTINCT ),
-                 equalTo( Types.DOUBLE ),
-                 equalTo( Types.FLOAT ),
-                 equalTo( Types.INTEGER ),
-                 equalTo( Types.JAVA_OBJECT ),
-                 equalTo( Types.LONGNVARCHAR ),
-                 equalTo( Types.LONGVARBINARY ),
-                 equalTo( Types.LONGVARCHAR ),
-                 equalTo( Types.NCHAR ),
-                 equalTo( Types.NCLOB ),
-                 // TODO:  Resolve:  Is it not clear whether Types.NULL can re-
-                 // present a type (e.g., the type of NULL), or whether a column
-                 // can ever have that type, and therefore whether Types.NULL
-                 // can appear.  Currently, exclude NULL so we'll notice if it
-                 // does appear:
-                 // No equalTo( Types.NULL ).
-                 equalTo( Types.NUMERIC ),
-                 equalTo( Types.NVARCHAR ),
-                 equalTo( Types.OTHER ),
-                 equalTo( Types.REAL ),
-                 equalTo( Types.REF ),
-                 equalTo( Types.ROWID ),
-                 equalTo( Types.SMALLINT ),
-                 equalTo( Types.SQLXML ),
-                 equalTo( Types.STRUCT ),
-                 equalTo( Types.TIME ),
-                 equalTo( Types.TIMESTAMP ),
-                 equalTo( Types.TINYINT ),
-                 equalTo( Types.VARBINARY ),
-                 equalTo( Types.VARCHAR )
-              ) );
-    } while ( columns.next() );
-  }
-
-  /**
-   * Basic test that column TYPE_NAME exists and is strings (such "INTEGER").
-   */
-  @Test
-  public void testColumn_TYPE_NAME_isString() throws Exception {
-    // Get metadata for some INTEGER column.
-    final ResultSet columns =
-        dbMetadata.getColumns( null, "INFORMATION_SCHEMA", "COLUMNS",
-                               "ORDINAL_POSITION" );
-    final boolean hasRow = columns.next();
-    assert hasRow : "DatabaseMetaData.getColumns(...) returned no rows";
-
-    // TYPE_NAME should be character string for type name "INTEGER", so
-    // getString( "TYPE_NAME" ) should succeed and getInt( "TYPE_NAME" ) should
-    // fail:
-    final String typeName1 = columns.getString( "TYPE_NAME" );
-    assertThat( "getString( \"TYPE_NAME\" )", typeName1, equalTo( "INTEGER" ) );
-
-    try {
-      final int unexpected = columns.getInt( "TYPE_NAME"  );
-      fail( "getInt( \"TYPE_NAME\" ) didn't throw exception (and returned "
-            + unexpected + ")" );
-    }
-    catch ( SQLException e ) {
-      // Expected.
-    }
-
-    // TYPE_NAME should be at ordinal position 6 (seemingly):
-    assertThat( "Column 6's label",
-                columns.getMetaData().getColumnLabel( 6 ), equalTo( "TYPE_NAME" ) );
-
-    // Also, getString( 6 ) should succeed and return the same type name as above:
-    final String typeName2 = columns.getString( 6 );
-    assertThat( "getString( 6 ) (expected to be same as getString( \"TYPE_NAME\" ))",
-                  typeName2, equalTo( typeName1 ) );
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/drill/blob/20efb2fb/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsDataTypeNotTypeCodeIntBugsTest.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsDataTypeNotTypeCodeIntBugsTest.java b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsDataTypeNotTypeCodeIntBugsTest.java
new file mode 100644
index 0000000..faca543
--- /dev/null
+++ b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2128GetColumnsDataTypeNotTypeCodeIntBugsTest.java
@@ -0,0 +1,169 @@
+/**
+ * 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.drill.jdbc.test;
+
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.hamcrest.CoreMatchers.*;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import org.apache.drill.jdbc.JdbcTest;
+
+
+/**
+ * Basic (spot-check/incomplete) tests for DRILL-2128 bugs (many
+ * DatabaseMetaData.getColumns(...) result table problems).
+ */
+public class Drill2128GetColumnsDataTypeNotTypeCodeIntBugsTest extends JdbcTest {
+
+  private static Connection connection;
+  private static DatabaseMetaData dbMetadata;
+
+  @BeforeClass
+  public static void setUpConnection() throws Exception {
+    connection = connect( "jdbc:drill:zk=local" );
+    dbMetadata = connection.getMetaData();
+  }
+
+  @AfterClass
+  public static void tearDownConnection() throws SQLException {
+    connection.close();
+  }
+
+
+  /**
+   * Basic test that column DATA_TYPE is integer type codes (not strings such
+   * as "VARCHAR" or "INTEGER").
+   */
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testColumn_DATA_TYPE_isInteger() throws Exception {
+    // Get metadata for some column(s).
+    final ResultSet columns = dbMetadata.getColumns( null, null, null, null );
+    final boolean hasRow = columns.next();
+    assert hasRow : "DatabaseMetaData.getColumns(...) returned no rows";
+
+    do {
+      // DATA_TYPE should be INTEGER, so getInt( "DATA_TYPE" ) should succeed:
+      final int typeCode1 = columns.getInt( "DATA_TYPE" );
+
+      // DATA_TYPE should be at ordinal position 5 (seemingly):
+      assertThat( "Column 5's label",
+                  columns.getMetaData().getColumnLabel( 5 ), equalTo( "DATA_TYPE" ) );
+
+      // Also, getInt( 5 ) should succeed and return the same type code as above:
+      final int typeCode2 = columns.getInt( 5 );
+      assertThat( "getInt( 5 ) (expected to be same as getInt( \"DATA_TYPE\" ))",
+                  typeCode2, equalTo( typeCode1 ) );
+
+      // Type code should be one of java.sql.Types.*:
+      assertThat(
+          typeCode1,
+          anyOf( // List is from java.sql.Types
+                 equalTo( Types.ARRAY ),
+                 equalTo( Types.BIGINT ),
+                 equalTo( Types.BINARY ),
+                 equalTo( Types.BIT ),
+                 equalTo( Types.BLOB ),
+                 equalTo( Types.BOOLEAN ),
+                 equalTo( Types.CHAR ),
+                 equalTo( Types.CLOB ),
+                 equalTo( Types.DATALINK ),
+                 equalTo( Types.DATE ),
+                 equalTo( Types.DECIMAL ),
+                 equalTo( Types.DISTINCT ),
+                 equalTo( Types.DOUBLE ),
+                 equalTo( Types.FLOAT ),
+                 equalTo( Types.INTEGER ),
+                 equalTo( Types.JAVA_OBJECT ),
+                 equalTo( Types.LONGNVARCHAR ),
+                 equalTo( Types.LONGVARBINARY ),
+                 equalTo( Types.LONGVARCHAR ),
+                 equalTo( Types.NCHAR ),
+                 equalTo( Types.NCLOB ),
+                 // TODO:  Resolve:  Is it not clear whether Types.NULL can re-
+                 // present a type (e.g., the type of NULL), or whether a column
+                 // can ever have that type, and therefore whether Types.NULL
+                 // can appear.  Currently, exclude NULL so we'll notice if it
+                 // does appear:
+                 // No equalTo( Types.NULL ).
+                 equalTo( Types.NUMERIC ),
+                 equalTo( Types.NVARCHAR ),
+                 equalTo( Types.OTHER ),
+                 equalTo( Types.REAL ),
+                 equalTo( Types.REF ),
+                 equalTo( Types.ROWID ),
+                 equalTo( Types.SMALLINT ),
+                 equalTo( Types.SQLXML ),
+                 equalTo( Types.STRUCT ),
+                 equalTo( Types.TIME ),
+                 equalTo( Types.TIMESTAMP ),
+                 equalTo( Types.TINYINT ),
+                 equalTo( Types.VARBINARY ),
+                 equalTo( Types.VARCHAR )
+              ) );
+    } while ( columns.next() );
+  }
+
+  /**
+   * Basic test that column TYPE_NAME exists and is strings (such "INTEGER").
+   */
+  @Test
+  public void testColumn_TYPE_NAME_isString() throws Exception {
+    // Get metadata for some INTEGER column.
+    final ResultSet columns =
+        dbMetadata.getColumns( null, "INFORMATION_SCHEMA", "COLUMNS",
+                               "ORDINAL_POSITION" );
+    final boolean hasRow = columns.next();
+    assert hasRow : "DatabaseMetaData.getColumns(...) returned no rows";
+
+    // TYPE_NAME should be character string for type name "INTEGER", so
+    // getString( "TYPE_NAME" ) should succeed and getInt( "TYPE_NAME" ) should
+    // fail:
+    final String typeName1 = columns.getString( "TYPE_NAME" );
+    assertThat( "getString( \"TYPE_NAME\" )", typeName1, equalTo( "INTEGER" ) );
+
+    try {
+      final int unexpected = columns.getInt( "TYPE_NAME"  );
+      fail( "getInt( \"TYPE_NAME\" ) didn't throw exception (and returned "
+            + unexpected + ")" );
+    }
+    catch ( SQLException e ) {
+      // Expected.
+    }
+
+    // TYPE_NAME should be at ordinal position 6 (seemingly):
+    assertThat( "Column 6's label",
+                columns.getMetaData().getColumnLabel( 6 ), equalTo( "TYPE_NAME" ) );
+
+    // Also, getString( 6 ) should succeed and return the same type name as above:
+    final String typeName2 = columns.getString( 6 );
+    assertThat( "getString( 6 ) (expected to be same as getString( \"TYPE_NAME\" ))",
+                  typeName2, equalTo( typeName1 ) );
+  }
+
+}


[6/6] drill git commit: DRILL-2465: Fix multiple DatabaseMetaData.getColumns() bugs.

Posted by pa...@apache.org.
DRILL-2465: Fix multiple DatabaseMetaData.getColumns() bugs.

- Added test DatabaseMetaDataGetColumnsTest.
- (Renamed Drill2128GetColumnsBugsTest to
  Drill2128GetColumnsDataTypeNotTypeCodeIntBugsTest.)
- Fixed/implemented various columns:
  - Added COLUMN_SIZE (big CASE expression handling lots of cases).
  - Fixed DECIMAL_DIGITS.
  - Fixed NUM_PREC_RADIX.
  - ~Fixed REMARKS ('' -> NULL).
  - ~Fixed COLUMN_DEF ('' -> NULL).
  - Fixed CHARACTER_OCTET_LENGTH.
  - Fixed ORDINAL_POSITION.
  - Fixed SCOPE_CATALOG, SCOPE_SCHEMA, SCOPE_TABLE,
  - Fixed SOURCE_DATA_TYPE.
- Note:  INTERVAL types have only *interim* implementations.
  (INFORMATION_SCHEMA.COLUMNS fixes are needed for completion.)
- Added canonical data type name strings in DATA_TYPE -> TYPE_NAME CASE
  expression (for robustness for expected upcoming INFORMATION_SCHEMA.COLUMNS
  standard-compliance bug fixing).


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

Branch: refs/heads/master
Commit: 20efb2fbc8176242c94505bcaab662586d66ba4d
Parents: 8796fd1
Author: dbarclay <db...@maprtech.com>
Authored: Sat Mar 21 00:45:41 2015 -0700
Committer: Parth Chandra <pc...@maprtech.com>
Committed: Fri Mar 27 10:19:43 2015 -0700

----------------------------------------------------------------------
 .../java/org/apache/drill/jdbc/MetaImpl.java    |  409 ++-
 .../jdbc/DatabaseMetaDataGetColumnsTest.java    | 2779 ++++++++++++++++++
 .../jdbc/test/Drill2128GetColumnsBugsTest.java  |  169 --
 ...etColumnsDataTypeNotTypeCodeIntBugsTest.java |  169 ++
 4 files changed, 3261 insertions(+), 265 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/20efb2fb/exec/jdbc/src/main/java/org/apache/drill/jdbc/MetaImpl.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/main/java/org/apache/drill/jdbc/MetaImpl.java b/exec/jdbc/src/main/java/org/apache/drill/jdbc/MetaImpl.java
index 99e0d22..4ff626e 100644
--- a/exec/jdbc/src/main/java/org/apache/drill/jdbc/MetaImpl.java
+++ b/exec/jdbc/src/main/java/org/apache/drill/jdbc/MetaImpl.java
@@ -34,10 +34,51 @@ import org.apache.drill.common.util.DrillStringUtils;
 
 
 public class MetaImpl implements Meta {
+  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MetaImpl.class);
+
+  // TODO:  Use more central version of these constants if availabe.
+
+  /** Radix used to report precision and scale of integral exact numeric types. */
+  private static final int RADIX_INTEGRAL = 10;
+  /** Radix used to report precision and scale of non-integral exact numeric
+      types (DECIMAL). */
+  private static final int RADIX_DECIMAL = 10;
+  /** Radix used to report precision and scale of approximate numeric types
+      (FLOAT, etc.). */
+  private static final int RADIX_APPROXIMATE = 10;
+  /** Radix used to report precisions of interval types. */
+  private static final int RADIX_INTERVAL = 10;
+
+  /** (Maximum) precision of TINYINT. */
+  private static final int PREC_TINYINT  = 3;
+  /** (Maximum) precision of SMALLINT. */
+  private static final int PREC_SMALLINT = 5;
+  /** (Maximum) precision of INTEGER. */
+  private static final int PREC_INTEGER  = 10;
+  /** (Maximum) precision of BIGINT. */
+  private static final int PREC_BIGINT   = 19;
+
+  /** Precision of FLOAT. */
+  private static final int PREC_FLOAT  =  7;
+  /** Precision of DOUBLE. */
+  private static final int PREC_DOUBLE = 15;
+  /** Precision of REAL. */
+  private static final int PREC_REAL   = PREC_DOUBLE;
+
+  /** Scale of INTEGER types. */
+  private static final int SCALE_INTEGRAL = 0;
+  /** JDBC conventional(?) scale value for FLOAT. */
+  private static final int SCALE_FLOAT = 7;
+  /** JDBC conventional(?) scale value for DOUBLE. */
+  private static final int SCALE_DOUBLE = 15;
+  /** JDBC conventional(?) scale value for REAL. */
+  private static final int SCALE_REAL = SCALE_DOUBLE;
+
+  /** (Apparent) maximum precision for starting unit of INTERVAL type. */
+  private static final int PREC_INTERVAL_LEAD_MAX = 10;
+  /** (Apparent) maximum fractional seconds precision for INTERVAL type. */
+  private static final int PREC_INTERVAL_TRAIL_MAX = 9;
 
-  static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MetaImpl.class);
-
-  static final Driver DRIVER = new Driver();
 
   final DrillConnectionImpl connection;
 
@@ -126,145 +167,323 @@ public class MetaImpl implements Meta {
     return s(sb.toString());
   }
 
-  public ResultSet getColumns(String catalog, Pat schemaPattern, Pat tableNamePattern, Pat columnNamePattern) {
+  /**
+   * Implements @link DatabaseMetaData#getColumns()}.
+   */
+  public ResultSet getColumns(String catalog, Pat schemaPattern,
+                              Pat tableNamePattern, Pat columnNamePattern) {
     StringBuilder sb = new StringBuilder();
-    // TODO:  Fix the various remaining bugs and resolve the various questions
-    // noted below.
+    // TODO:  Resolve the various questions noted below.
     sb.append(
-        "SELECT \n"
-        // getColumns INFORMATION_SCHEMA.COLUMNS   getColumns()
-        // column     source column or             column name
-        // number     expression
-        // -------    ------------------------     -------------
-        + /*  1 */ "  TABLE_CATALOG            as  TABLE_CAT, \n"
-        + /*  2 */ "  TABLE_SCHEMA             as  TABLE_SCHEM, \n"
-        + /*  3 */ "  TABLE_NAME               as  TABLE_NAME, \n"
-        + /*  4 */ "  COLUMN_NAME              as  COLUMN_NAME, \n"
-
+        "SELECT "
+        // getColumns   INFORMATION_SCHEMA.COLUMNS        getColumns()
+        // column       source column or                  column name
+        // number       expression
+        // -------      ------------------------          -------------
+        + /*  1 */ "\n  TABLE_CATALOG                 as  TABLE_CAT, "
+        + /*  2 */ "\n  TABLE_SCHEMA                  as  TABLE_SCHEM, "
+        + /*  3 */ "\n  TABLE_NAME                    as  TABLE_NAME, "
+        + /*  4 */ "\n  COLUMN_NAME                   as  COLUMN_NAME, "
+
+        /*    5                                           DATA_TYPE */
         // TODO:  Resolve the various questions noted below for DATA_TYPE.
-        /*    5  (DATA_TYPE) */
-        + "  CASE \n"
+        + "\n  CASE DATA_TYPE "
         // (All values in JDBC 4.0/Java 7 java.sql.Types except for types.NULL:)
 
-        // TODO:  RESOLVE:  How does ARRAY appear in COLUMNS.DATA_TYPE?
-        // - Only at end (with no maximum size, as "VARCHAR(65535) ARRAY")?
-        // - Possibly with maximum size (as "... ARRAY[10]")?
-        // (SQL source syntax:
-        //   <array type> ::=
-        //     <data type> ARRAY
-        //       [ <left bracket or trigraph> <maximum cardinality> <right bracket or trigraph> ]
-        + "    WHEN DATA_TYPE LIKE '% ARRAY'    THEN " + Types.ARRAY
-
-        + "    WHEN DATA_TYPE = 'BIGINT'        THEN " + Types.BIGINT
-        + "    WHEN DATA_TYPE = 'BINARY'        THEN " + Types.BINARY
+        // Exact-match cases:
+        + "\n    WHEN 'BIGINT'                      THEN " + Types.BIGINT
+        + "\n    WHEN 'BINARY'                      THEN " + Types.BINARY
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'BIT'           THEN " + Types.BIT
+        + "\n    WHEN 'BIT'                         THEN " + Types.BIT
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'BLOB'          THEN " + Types.BLOB
-        + "    WHEN DATA_TYPE = 'BOOLEAN'       THEN " + Types.BOOLEAN
+        + "\n    WHEN 'BLOB', 'BINARY LARGE OBJECT' THEN " + Types.BLOB
+        + "\n    WHEN 'BOOLEAN'                     THEN " + Types.BOOLEAN
 
-        + "    WHEN DATA_TYPE = 'CHAR'          THEN " + Types.CHAR
+        + "\n    WHEN 'CHAR', 'CHARACTER'           THEN " + Types.CHAR
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'CLOB'          THEN " + Types.CLOB
+        + "\n    WHEN 'CLOB', 'CHARACTER LARGE OBJECT' "
+        + "\n                                       THEN " + Types.CLOB
 
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'DATALINK'      THEN " + Types.DATALINK
-        + "    WHEN DATA_TYPE = 'DATE'          THEN " + Types.DATE
-        + "    WHEN DATA_TYPE = 'DECIMAL'       THEN " + Types.DECIMAL
+        + "\n    WHEN 'DATALINK'                    THEN " + Types.DATALINK
+        + "\n    WHEN 'DATE'                        THEN " + Types.DATE
+        + "\n    WHEN 'DECIMAL'                     THEN " + Types.DECIMAL
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'DISTINCT'      THEN " + Types.DISTINCT
-        + "    WHEN DATA_TYPE = 'DOUBLE'        THEN " + Types.DOUBLE
+        + "\n    WHEN 'DISTINCT'                    THEN " + Types.DISTINCT
+        + "\n    WHEN 'DOUBLE', 'DOUBLE PRECISION'  THEN " + Types.DOUBLE
+
+        + "\n    WHEN 'FLOAT'                       THEN " + Types.FLOAT
 
-        + "    WHEN DATA_TYPE = 'FLOAT'         THEN " + Types.FLOAT
+        + "\n    WHEN 'INTEGER'                     THEN " + Types.INTEGER
 
-        + "    WHEN DATA_TYPE = 'INTEGER'       THEN " + Types.INTEGER
+        // Drill's INFORMATION_SCHEMA's COLUMNS currently has
+        // "INTERVAL_YEAR_MONTH" and "INTERVAL_DAY_TIME" instead of SQL standard
+        // 'INTERVAL'.
+        + "\n    WHEN 'INTERVAL', "
+        + "\n         'INTERVAL_YEAR_MONTH', "
+        + "\n         'INTERVAL_DAY_TIME'           THEN " + Types.OTHER
 
         // Resolve:  Not seen in Drill yet.  Can it ever appear?:
-        + "    WHEN DATA_TYPE = 'JAVA_OBJECT'   THEN " + Types.JAVA_OBJECT
+        + "\n    WHEN 'JAVA_OBJECT'                 THEN " + Types.JAVA_OBJECT
 
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'LONGNVARCHAR'  THEN " + Types.LONGNVARCHAR
+        + "\n    WHEN 'LONGNVARCHAR'                THEN " + Types.LONGNVARCHAR
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'LONGVARBINARY' THEN " + Types.LONGVARBINARY
+        + "\n    WHEN 'LONGVARBINARY'               THEN " + Types.LONGVARBINARY
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'LONGVARCHAR'   THEN " + Types.LONGVARCHAR
+        + "\n    WHEN 'LONGVARCHAR'                 THEN " + Types.LONGVARCHAR
 
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'NCHAR'         THEN " + Types.NCHAR
+        + "\n    WHEN 'NCHAR', 'NATIONAL CHARACTER' THEN " + Types.NCHAR
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'NCLOB'         THEN " + Types.NCLOB
+        + "\n    WHEN 'NCLOB', 'NATIONAL CHARACTER LARGE OBJECT' "
+        + "\n                                       THEN " + Types.NCLOB
         // TODO:  Resolve following about NULL (and then update comment and code):
         // It is not clear whether Types.NULL can represent a type (perhaps the
         // type of the literal NULL when no further type information is known?) or
         // whether 'NULL' can appear in INFORMATION_SCHEMA.COLUMNS.DATA_TYPE.
         // For now, since it shouldn't hurt, include 'NULL'/Types.NULL in mapping.
-        + "    WHEN DATA_TYPE = 'NULL'          THEN " + Types.NULL
+        + "\n    WHEN 'NULL'                        THEN " + Types.NULL
         // (No NUMERIC--Drill seems to map any to DECIMAL currently.)
-        + "    WHEN DATA_TYPE = 'NUMERIC'       THEN " + Types.NUMERIC
+        + "\n    WHEN 'NUMERIC'                     THEN " + Types.NUMERIC
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'NVARCHAR'      THEN " + Types.NVARCHAR
+        + "\n    WHEN 'NVARCHAR', 'NATIONAL CHARACTER VARYING' "
+        + "\n                                       THEN " + Types.NVARCHAR
 
         // Resolve:  Unexpectedly, has appeared in Drill.  Should it?
-        + "    WHEN DATA_TYPE = 'OTHER'         THEN " + Types.OTHER
+        + "\n    WHEN 'OTHER'                       THEN " + Types.OTHER
 
-        + "    WHEN DATA_TYPE = 'REAL'          THEN " + Types.REAL
+        + "\n    WHEN 'REAL'                        THEN " + Types.REAL
         // SQL source syntax:
         //   <reference type> ::=
         //     REF <left paren> <referenced type> <right paren> [ <scope clause> ]
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'REF'           THEN " + Types.REF
+        + "\n    WHEN 'REF'                         THEN " + Types.REF
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'ROWID'         THEN " + Types.ROWID
+        + "\n    WHEN 'ROWID'                       THEN " + Types.ROWID
 
-        + "    WHEN DATA_TYPE = 'SMALLINT'      THEN " + Types.SMALLINT
+        + "\n    WHEN 'SMALLINT'                    THEN " + Types.SMALLINT
         // Resolve:  Not seen in Drill yet.  Can it appear?:
-        + "    WHEN DATA_TYPE = 'SQLXML'        THEN " + Types.SQLXML
+        + "\n    WHEN 'SQLXML'                      THEN " + Types.SQLXML
 
-        // TODO:  RESOLVE:  How does "STRUCT" appear?
-        // - Only at beginning (as "STRUCT(INTEGER sint, BOOLEAN sboolean")?
-        // - Otherwise too?
-        + "    WHEN DATA_TYPE LIKE 'STRUCT(%'   THEN " + Types.STRUCT
+        + "\n    WHEN 'TIME'                        THEN " + Types.TIME
+        + "\n    WHEN 'TIMESTAMP'                   THEN " + Types.TIMESTAMP
+        + "\n    WHEN 'TINYINT'                     THEN " + Types.TINYINT
 
-        + "    WHEN DATA_TYPE = 'TIME'          THEN " + Types.TIME
-        + "    WHEN DATA_TYPE = 'TIMESTAMP'     THEN " + Types.TIMESTAMP
-        + "    WHEN DATA_TYPE = 'TINYINT'       THEN " + Types.TINYINT
+        + "\n    WHEN 'VARBINARY', 'BINARY VARYING' THEN " + Types.VARBINARY
+        + "\n    WHEN 'VARCHAR', 'CHARACTER VARYING' "
+        + "\n                                       THEN " + Types.VARCHAR
 
-        + "    WHEN DATA_TYPE = 'VARBINARY'     THEN " + Types.VARBINARY
-        + "    WHEN DATA_TYPE = 'VARCHAR'       THEN " + Types.VARCHAR
+        + "\n    ELSE"
+        // Pattern-match cases:
+        + "\n      CASE "
+
+        // TODO:  RESOLVE:  How does ARRAY appear in COLUMNS.DATA_TYPE?
+        // - Only at end (with no maximum size, as "VARCHAR(65535) ARRAY")?
+        // - Possibly with maximum size (as "... ARRAY[10]")?
+        // - Then, how should it appear in JDBC ("ARRAY"? "... ARRAY"?)
+        // (SQL source syntax:
+        //   <array type> ::=
+        //     <data type> ARRAY
+        //       [ <left bracket or trigraph> <maximum cardinality>
+        //         <right bracket or trigraph> ]
+        + "\n        WHEN DATA_TYPE LIKE '% ARRAY'  THEN " + Types.ARRAY
 
         // TODO:  RESOLVE:  How does MAP appear in COLUMNS.DATA_TYPE?
         // - Only at end?
         // - Otherwise?
         // TODO:  RESOLVE:  Should it map to Types.OTHER or something else?
         // Has appeared in Drill.  Should it?
-        + "    WHEN DATA_TYPE LIKE '% MAP'      THEN " + Types.OTHER
-
-        + "    ELSE                                  " + Types.OTHER
-        + "  END                               as  DATA_TYPE, \n"
-
-        + /*  6 */ "  DATA_TYPE                as  TYPE_NAME, \n"
-        ///*  7 */  FIXME:  BUG:  There should be: COLUMN_SIZE
-        + /*  8 */ "  CHARACTER_MAXIMUM_LENGTH as  BUFFER_LENGTH, \n"
-        //  FIXME:  BUG:  Many of the following are wrong.
-        + /*  9 */ "  NUMERIC_PRECISION        as  DECIMAL_PRECISION, \n" // FIXME:  BUG:  Should be "DECIMAL_DIGITS"
-        + /* 10 */ "  NUMERIC_PRECISION_RADIX  as  NUM_PREC_RADIX, \n"
-        + /* 11 */ "  " + DatabaseMetaData.columnNullableUnknown
-        +             "                        as  NULLABLE, \n"
-        + /* 12 */ "  ''                       as  REMARKS, \n"
-        + /* 13 */ "  ''                       as  COLUMN_DEF, \n"
-        + /* 14 */ "  0                        as  SQL_DATA_TYPE, \n"
-        + /* 15 */ "  0                        as  SQL_DATETIME_SUB, \n"
-        + /* 16 */ "  4                        as  CHAR_OCTET_LENGTH, \n"
-        + /* 17 */ "  1                        as  ORDINAL_POSITION, \n"
-        + /* 18 */ "  'YES'                    as  IS_NULLABLE, \n"
-        + /* 19 */ "  ''                       as  SCOPE_CATALOG,"
-        + /* 20 */ "  ''                       as  SCOPE_SCHEMA, \n"
-        + /* 21 */ "  ''                       as  SCOPE_TABLE, \n"
-        + /* 22 */ "  ''                       as  SOURCE_DATA_TYPE, \n"
-        + /* 23 */ "  ''                       as  IS_AUTOINCREMENT, \n"
-        + /* 24 */ "  ''                       as  IS_GENERATEDCOLUMN \n"
-        + "FROM INFORMATION_SCHEMA.COLUMNS \n"
-        + "WHERE 1=1 ");
+        + "\n        WHEN DATA_TYPE LIKE '% MAP'    THEN " + Types.OTHER
+
+        // TODO:  RESOLVE:  How does "STRUCT" appear?
+        // - Only at beginning (as "STRUCT(INTEGER sint, BOOLEAN sboolean")?
+        // - Otherwise too?
+        // - Then, how should it appear in JDBC ("STRUCT"? "STRUCT(...)"?)
+        + "\n        WHEN DATA_TYPE LIKE 'STRUCT(%' THEN " + Types.STRUCT
+
+        + "\n        ELSE                                " + Types.OTHER
+        + "\n      END "
+        + "\n  END                                    as  DATA_TYPE, "
+
+        /*    6                                           TYPE_NAME */
+        // Map Drill's current info. schema values to what SQL standard
+        // specifies (for DATA_TYPE)--and assume that that's what JDBC wants.
+        + "\n  CASE DATA_TYPE "
+        + "\n    WHEN 'INTERVAL_YEAR_MONTH', "
+        + "\n         'INTERVAL_DAY_TIME'     THEN 'INTERVAL'"
+        // TODO:  Resolve how non-scalar types should appear in
+        // INFORMATION_SCHEMA.COLUMNS and here in JDBC:
+        // - "ARRAY" or "... ARRAY"?
+        // - "MAP" or "... MAP"?
+        // - "STRUCT" or "STRUCT(...)"?
+        + "\n    ELSE                               DATA_TYPE "
+        + "\n  END                                    as TYPE_NAME, "
+
+        /*    7                                           COLUMN_SIZE */
+        /* "... COLUMN_SIZE ....
+         * For numeric data, this is the maximum precision.
+         * For character data, this is the length in characters.
+         * For datetime datatypes, this is the length in characters of the String
+         *   representation (assuming the maximum allowed precision of the
+         *   fractional seconds component).
+         * For binary data, this is the length in bytes.
+         * For the ROWID datatype, this is the length in bytes.
+         * Null is returned for data types where the column size is not applicable."
+         *
+         * Note:  "Maximum precision" seems to mean the maximum number of
+         * significant decimal digits that can appear (not the number of digits
+         * that can be counted on, and not the maximum number of characters
+         * needed to display a value).
+         */
+        + "\n  CASE DATA_TYPE "
+
+        // "For numeric data, ... the maximum precision":
+        //   TODO:  Change literals to references to declared constant fields:
+        // - exact numeric types:
+        //   (in decimal digits, coordinated with NUM_PREC_RADIX = 10)
+        + "\n    WHEN 'TINYINT'                      THEN " + PREC_TINYINT
+        + "\n    WHEN 'SMALLINT'                     THEN " + PREC_SMALLINT
+        + "\n    WHEN 'INTEGER'                      THEN " + PREC_INTEGER
+        + "\n    WHEN 'BIGINT'                       THEN " + PREC_BIGINT
+        + "\n    WHEN 'DECIMAL', 'NUMERIC'           THEN NUMERIC_PRECISION "
+        // - approximate numeric types:
+        //   (in decimal digits, coordinated with NUM_PREC_RADIX = 10)
+        // TODO:  REVISIT:  Should these be in bits or decimal digits (with
+        //   NUM_PREC_RADIX coordinated)?  INFORMATION_SCHEMA.COLUMNS's value
+        //   are supposed to be in bits (per the SQL spec.).  What does JDBC
+        //   require and allow?
+        + "\n    WHEN 'FLOAT'                        THEN " + PREC_FLOAT
+        + "\n    WHEN 'DOUBLE'                       THEN " + PREC_DOUBLE
+        + "\n    WHEN 'REAL'                         THEN " + PREC_REAL
+
+        // "For character data, ... the length in characters":
+        // TODO:  BUG:  DRILL-2459:  For CHARACTER / CHAR, length is not in
+        // CHARACTER_MAXIMUM_LENGTH but in NUMERIC_PRECISION.
+        // Workaround:
+        + "\n    WHEN 'VARCHAR', 'CHARACTER VARYING' "
+        + "\n                                    THEN CHARACTER_MAXIMUM_LENGTH "
+        + "\n    WHEN 'CHAR', 'CHARACTER', "
+        + "\n         'NCHAR', 'NATIONAL CHAR', 'NATIONAL CHARACTER' "
+        + "\n                                        THEN NUMERIC_PRECISION "
+
+        // "For datetime datatypes ... length ... String representation
+        // (assuming the maximum ... precision of ... fractional seconds ...)":
+        + "\n    WHEN 'DATE'            THEN 10 "              // YYYY-MM-DD
+        + "\n    WHEN 'TIME'            THEN "
+        + "\n      CASE "
+        + "\n        WHEN NUMERIC_PRECISION > 0 "              // HH:MM:SS.sss
+        + "\n                           THEN          8 + 1 + NUMERIC_PRECISION"
+        + "\n        ELSE                             8"       // HH:MM:SS
+        + "\n      END "
+        + "\n    WHEN 'TIMESTAMP'       THEN "
+        + "\n      CASE "                          // date + "T" + time (above)
+        + "\n        WHEN NUMERIC_PRECISION > 0 "
+        + "                             THEN 10 + 1 + 8 + 1 + NUMERIC_PRECISION"
+        + "\n        ELSE                    10 + 1 + 8"
+        + "\n      END "
+
+        // TODO:  DRILL-2531:  When DRILL-2519 is fixed, use start and end unit
+        // and start-unit precision to implement maximum width more precisely
+        // (narrowly) than this workaround:
+        // For INTERVAL_YEAR_MONTH, maximum width is from "P1234567890Y12M"
+        // (5 + apparent maximum start unit precision of 10)
+        // unit precision):
+        + "\n    WHEN 'INTERVAL_YEAR_MONTH' "
+        + "\n                                        THEN 5 + "
+                                                          + PREC_INTERVAL_LEAD_MAX
+        // For INTERVAL_DAY_TIME, maximum width is from
+        // "P1234567890D12H12M12.123456789S" (12 + apparent maximum start unit
+        // precision of 10 + apparent maximum seconds fractional precision of 9):
+        + "\n    WHEN 'INTERVAL_DAY_TIME' "
+        + "\n                                        THEN 12 + "
+                                                          + ( PREC_INTERVAL_LEAD_MAX
+                                                             + PREC_INTERVAL_TRAIL_MAX )
+
+        // "For binary data, ... the length in bytes":
+        // BUG:  DRILL-2459:  BINARY and BINARY VARYING / VARBINARY length is
+        // not in CHARACTER_MAXIMUM_LENGTH but in NUMERIC_PRECISION.
+        // Workaround:
+        + "\n    WHEN 'VARBINARY', 'BINARY VARYING', "
+        + "\n         'BINARY'                       THEN NUMERIC_PRECISION "
+
+        // "For ... ROWID datatype...": Not in Drill?
+
+        // "Null ... for data types [for which] ... not applicable.":
+        + "\n    ELSE                                     NULL "
+        + "\n  END                                    as  COLUMN_SIZE, "
+
+        + /*  8 */ "\n  CHARACTER_MAXIMUM_LENGTH      as  BUFFER_LENGTH, "
+
+        /*    9                                           DECIMAL_DIGITS */
+        + "\n  CASE  DATA_TYPE"
+        + "\n    WHEN 'TINYINT', "
+        + "\n         'SMALLINT', "
+        + "\n         'INTEGER', "
+        + "\n         'BIGINT'                       THEN " + SCALE_INTEGRAL
+        + "\n    WHEN 'DECIMAL', "
+        + "\n         'NUMERIC'                      THEN NUMERIC_SCALE "
+        + "\n    WHEN 'FLOAT'                        THEN " + SCALE_FLOAT
+        + "\n    WHEN 'DOUBLE'                       THEN " + SCALE_DOUBLE
+        + "\n    WHEN 'REAL'                         THEN " + SCALE_REAL
+        + "\n    WHEN 'INTERVAL'                     THEN NUMERIC_SCALE "
+        + "\n    WHEN 'INTERVAL_YEAR_MONTH'          THEN 0 "
+        + "\n    WHEN 'INTERVAL_DAY_TIME'            THEN NUMERIC_SCALE "
+        + "\n  END                                    as  DECIMAL_DIGITS, "
+
+        /*   10                                           NUM_PREC_RADIX */
+        + "\n  CASE DATA_TYPE "
+        + "\n    WHEN 'TINYINT', "
+        + "\n         'SMALLINT', "
+        + "\n         'INTEGER', "
+        + "\n         'BIGINT'                       THEN " + RADIX_INTEGRAL
+        + "\n    WHEN 'DECIMAL', "
+        + "\n         'NUMERIC'                      THEN " + RADIX_DECIMAL
+        + "\n    WHEN 'FLOAT', "
+        + "\n         'DOUBLE', "
+        + "\n         'REAL'                         THEN " + RADIX_APPROXIMATE
+        + "\n    WHEN 'INTERVAL_YEAR_MONTH', "
+        + "\n         'INTERVAL_DAY_TIME'            THEN " + RADIX_INTERVAL
+        + "\n    ELSE                                     NULL"
+        + "\n  END                                    as  NUM_PREC_RADIX, "
+
+        /*   11                                           NULLABLE */
+        + "\n  CASE IS_NULLABLE "
+        + "\n    WHEN 'YES'      THEN " + DatabaseMetaData.columnNullable
+        + "\n    WHEN 'NO'       THEN " + DatabaseMetaData.columnNoNulls
+        + "\n    WHEN ''         THEN " + DatabaseMetaData.columnNullableUnknown
+        + "\n    ELSE                 -1"
+        + "\n  END                                    as  NULLABLE, "
+
+        + /* 12 */ "\n  CAST( NULL as VARCHAR )       as  REMARKS, "
+        + /* 13 */ "\n  CAST( NULL as VARCHAR )       as  COLUMN_DEF, "
+        + /* 14 */ "\n  0                             as  SQL_DATA_TYPE, "
+        + /* 15 */ "\n  0                             as  SQL_DATETIME_SUB, "
+
+        /*   16                                           CHAR_OCTET_LENGTH */
+        + "\n  CASE DATA_TYPE"
+        + "\n    WHEN 'VARCHAR', 'CHARACTER VARYING' "
+        + "\n                                 THEN 4 * CHARACTER_MAXIMUM_LENGTH "
+        + "\n    WHEN 'CHAR', 'CHARACTER', "
+        + "\n         'NCHAR', 'NATIONAL CHAR', 'NATIONAL CHARACTER' "
+        // TODO:  BUG:  DRILL-2459:  For CHARACTER / CHAR, length is not in
+        // CHARACTER_MAXIMUM_LENGTH but in NUMERIC_PRECISION.  Workaround:
+        + "\n                                 THEN 4 * NUMERIC_PRECISION "
+        + "\n    ELSE                              NULL "
+        + "\n  END                                    as  CHAR_OCTET_LENGTH, "
+
+        + /* 17 */ "\n  1 + ORDINAL_POSITION          as  ORDINAL_POSITION, "
+        + /* 18 */ "\n  IS_NULLABLE                   as  IS_NULLABLE, "
+        + /* 19 */ "\n  CAST( NULL as VARCHAR )       as  SCOPE_CATALOG, "
+        + /* 20 */ "\n  CAST( NULL as VARCHAR )       as  SCOPE_SCHEMA, "
+        + /* 21 */ "\n  CAST( NULL as VARCHAR )       as  SCOPE_TABLE, "
+        // TODO:  Change to SMALLINT when it's implemented (DRILL-2470):
+        + /* 22 */ "\n  CAST( NULL as INTEGER )       as  SOURCE_DATA_TYPE, "
+        + /* 23 */ "\n  ''                            as  IS_AUTOINCREMENT, "
+        + /* 24 */ "\n  ''                            as  IS_GENERATEDCOLUMN "
+
+        + "\n  FROM INFORMATION_SCHEMA.COLUMNS "
+        + "\n  WHERE 1=1 ");
 
     if (catalog != null) {
       sb.append("\n  AND TABLE_CATALOG = '" + DrillStringUtils.escapeSql(catalog) + "'");
@@ -272,16 +491,14 @@ public class MetaImpl implements Meta {
     if (schemaPattern.s != null) {
       sb.append("\n  AND TABLE_SCHEMA like '" + DrillStringUtils.escapeSql(schemaPattern.s) + "'");
     }
-
     if (tableNamePattern.s != null) {
       sb.append("\n  AND TABLE_NAME like '" + DrillStringUtils.escapeSql(tableNamePattern.s) + "'");
     }
-
     if (columnNamePattern.s != null) {
       sb.append("\n  AND COLUMN_NAME like '" + DrillStringUtils.escapeSql(columnNamePattern.s) + "'");
     }
 
-    sb.append(" ORDER BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME");
+    sb.append("\n ORDER BY TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME");
 
     return s(sb.toString());
   }


[2/6] drill git commit: DRILL-2439: Fix: getBoolean() doesn't work (for non-null values).

Posted by pa...@apache.org.
DRILL-2439: Fix: getBoolean() doesn't work (for non-null values).

- Created regression test Drill2439GetBooleanSaysWrongTypeBugTest.
- Added getBoolean(...) for SqlAccessor implementations for type Bit in
  SqlAccessors template.


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

Branch: refs/heads/master
Commit: 9a5b50ea52ee60a355926ac0d52ab87fafb592c0
Parents: 83ebf4f
Author: dbarclay <db...@maprtech.com>
Authored: Fri Mar 20 22:14:56 2015 -0700
Committer: Parth Chandra <pc...@maprtech.com>
Committed: Fri Mar 27 10:19:23 2015 -0700

----------------------------------------------------------------------
 .../main/codegen/templates/SqlAccessors.java    | 13 ++++
 ...39GetBooleanFailsSayingWrongTypeBugTest.java | 80 ++++++++++++++++++++
 2 files changed, 93 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/drill/blob/9a5b50ea/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 a838e11..c0ece9b 100644
--- a/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
+++ b/exec/java-exec/src/main/codegen/templates/SqlAccessors.java
@@ -270,6 +270,19 @@ public class ${name}Accessor extends AbstractSqlAccessor {
   }
   </#if>
 
+
+  <#if minor.class == "Bit" >
+  public boolean getBoolean(int index) {
+   <#if mode == "Nullable">
+    if (ac.isNull(index)) {
+      return false;
+    }
+   </#if>
+   return 1 == ac.get(index);
+  }
+ </#if>
+
+
  </#if> <#-- not VarLen -->
 
 }

http://git-wip-us.apache.org/repos/asf/drill/blob/9a5b50ea/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2439GetBooleanFailsSayingWrongTypeBugTest.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2439GetBooleanFailsSayingWrongTypeBugTest.java b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2439GetBooleanFailsSayingWrongTypeBugTest.java
new file mode 100644
index 0000000..69f194a
--- /dev/null
+++ b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2439GetBooleanFailsSayingWrongTypeBugTest.java
@@ -0,0 +1,80 @@
+/**
+ * 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.drill.jdbc.test;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.*;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.apache.drill.jdbc.Driver;
+import org.apache.drill.jdbc.JdbcTest;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+
+public class Drill2439GetBooleanFailsSayingWrongTypeBugTest extends JdbcTest {
+
+  private static Connection connection;
+  private static Statement statement;
+
+  @BeforeClass
+  public static void setUpConnection() throws SQLException {
+    connection = new Driver().connect( "jdbc:drill:zk=local", null );
+    statement = connection.createStatement();
+  }
+
+  @AfterClass
+  public static void tearDownConnection() throws SQLException {
+    connection.close();
+  }
+
+  @Test
+  public void testGetBooleanGetsTrue() throws Exception {
+    ResultSet rs =
+        statement.executeQuery( "SELECT TRUE FROM INFORMATION_SCHEMA.CATALOGS" );
+    rs.next();
+    assertThat( "getBoolean(...) for TRUE", rs.getBoolean( 1 ), equalTo( true ) );
+    assertThat( "wasNull", rs.wasNull(), equalTo( false ) );
+  }
+
+  @Test
+  public void testGetBooleanGetsFalse() throws Exception {
+    ResultSet rs =
+        statement.executeQuery( "SELECT FALSE FROM INFORMATION_SCHEMA.CATALOGS" );
+    rs.next();
+    assertThat( "getBoolean(...) for FALSE", rs.getBoolean( 1 ), equalTo( false ) );
+    assertThat( "wasNull", rs.wasNull(), equalTo( false ) );
+  }
+
+  @Test
+  public void testGetBooleanGetsNull() throws Exception {
+    ResultSet rs = statement.executeQuery(
+        "SELECT CAST( NULL AS BOOLEAN ) FROM INFORMATION_SCHEMA.CATALOGS" );
+    rs.next();
+    assertThat( "getBoolean(...) for BOOLEAN NULL", rs.getBoolean( 1 ), equalTo( false ) );
+    assertThat( "wasNull", rs.wasNull(), equalTo( true ) );
+  }
+
+}


[5/6] drill git commit: DRILL-2465: Fix multiple DatabaseMetaData.getColumns() bugs.

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/drill/blob/20efb2fb/exec/jdbc/src/test/java/org/apache/drill/jdbc/DatabaseMetaDataGetColumnsTest.java
----------------------------------------------------------------------
diff --git a/exec/jdbc/src/test/java/org/apache/drill/jdbc/DatabaseMetaDataGetColumnsTest.java b/exec/jdbc/src/test/java/org/apache/drill/jdbc/DatabaseMetaDataGetColumnsTest.java
new file mode 100644
index 0000000..1a7b63d
--- /dev/null
+++ b/exec/jdbc/src/test/java/org/apache/drill/jdbc/DatabaseMetaDataGetColumnsTest.java
@@ -0,0 +1,2779 @@
+/**
+ * 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.drill.jdbc;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.*;
+
+import org.apache.drill.exec.store.hive.HiveTestDataGenerator;
+import org.apache.drill.jdbc.Driver;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.Statement;
+
+import static java.sql.ResultSetMetaData.columnNoNulls;
+import static java.sql.ResultSetMetaData.columnNullable;
+import static java.sql.ResultSetMetaData.columnNullableUnknown;
+
+import java.sql.SQLException;
+import java.sql.Types;
+
+// TODO:  MOVE notes to implementation (have this not (just) in test).
+
+// TODO:  Determine for each numeric type whether its precision is reported in
+//   decimal or binary (and NUM_PREC_RADIX is 10 or 2, respectively).
+//   The SQL specification for INFORMATION_SCHEMA.COLUMNS seems to specify the
+//   radix for each numeric type:
+//   - 2 or 10 for SMALLINT, INTEGER, and BIGINT;
+//   - only 10 for NUMERIC and DECIMAL; and
+//   - only 2  for REAL, DOUBLE PRECISION, and FLOAT.
+//   However, it is not clear what the JDBC API intends:
+//   - It has NUM_PREC_RADIX, specifying a radix or 10 or 2, but doesn't specify
+//     exactly what is applies to.  Apparently, it applies to COLUMN_SIZE abd
+//     ResultMetaData.getPrecision() (which are defined in terms of maximum
+//     precision for numeric types).
+//   - Is has DECIMAL_DIGITS, which is <em>not</em> the number of decimal digits
+//     of precision, but which it defines as the number of fractional digits--
+//     without actually specifying that it's in decimal.
+
+// TODO:  Review nullability (NULLABLE and IS_NULLABLE columns):
+// - It's not clear what JDBC's requirements are.
+//   - It does seem obvious that metadata should not contradictorily say that a
+//   - column cannot contain nulls when the column currently does contain nulls.
+//   - It's not clear whether metadata must say that a column cannot contains
+//     nulls if JDBC specifies that the column always has a non-null value.
+// - It's not clear why Drill reports that columns that will never contain nulls
+//   can contain nulls.
+// - It's not clear why Drill sets INFORMATION_SCHEMA.COLUMNS.IS_NULLABLE to
+//   'NO' for some columns that contain only null (e.g., for
+//   "CREATE VIEW x AS SELECT CAST(NULL AS ...) ..."
+
+
+/**
+ * Test class for Drill's java.sql.DatabaseMetaData.getColumns() implementation.
+ * <p>
+ *   Based on JDBC 4.1 (Java 7).
+ * </p>
+ */
+public class DatabaseMetaDataGetColumnsTest extends JdbcTest {
+
+  private static final String VIEW_NAME =
+      DatabaseMetaDataGetColumnsTest.class.getSimpleName() + "_View";
+
+
+  /** The one shared JDBC connection to Drill. */
+  private static Connection connection;
+
+  /** Overall (connection-level) metadata. */
+  private static DatabaseMetaData dbMetadata;
+
+
+  /** getColumns result metadata.  For checking columns themselves (not cell
+   *  values or row order). */
+  private static ResultSetMetaData rowsMetadata;
+
+
+  ////////////////////
+  // Results from getColumns for test columns of various types.
+  // Each ResultSet is positioned at first row for, and must not be modified by,
+  // test methods.
+
+  //////////
+  // For columns in temporary test view (types accessible via casting):
+  // TODO:  Determine whether any other data types are accessible via CAST
+  // and add them.
+
+  private static ResultSet mdrOptBOOLEAN;
+
+  private static ResultSet mdrReqTINYINT;
+  private static ResultSet mdrOptSMALLINT;
+  private static ResultSet mdrReqINTEGER;
+  private static ResultSet mdrOptBIGINT;
+
+  private static ResultSet mdrOptFLOAT;
+  private static ResultSet mdrReqDOUBLE;
+  private static ResultSet mdrOptREAL;
+
+  private static ResultSet mdrReqDECIMAL_5_3;
+  // No NUMERIC while Drill just maps it to DECIMAL.
+
+  private static ResultSet mdrReqVARCHAR_10;
+  private static ResultSet mdrOptVARCHAR;
+  private static ResultSet mdrReqCHAR_5;
+  // No NCHAR, etc., in Drill (?).
+  private static ResultSet mdrOptVARBINARY_16;
+  private static ResultSet mdrOptBINARY_1048576;
+
+  private static ResultSet mdrReqDATE;
+  private static ResultSet mdrOptTIME;
+  private static ResultSet mdrOptTIME_7;
+  private static ResultSet mdrOptTIMESTAMP;
+  // No "... WITH TIME ZONE" in Drill.
+  private static ResultSet mdrOptINTERVAL_H_S3;
+  private static ResultSet mdrOptINTERVAL_Y4;
+
+  //////////
+  // For columns in schema hive_test.default's infoschematest table:
+
+  // listtype column:      VARCHAR(65535) ARRAY, non-null(?):
+  private static ResultSet mdrReqARRAY;
+  // maptype column:       (VARCHAR(65535), INTEGER) MAP, non-null(?):
+  private static ResultSet mdrReqMAP;
+  // structtype column:    STRUCT(INTEGER sint, BOOLEAN sboolean,
+  //                              VARCHAR(65535) sstring), non-null(?):
+  private static ResultSet testRowSTRUCT;
+  // uniontypetype column: OTHER (?), non=nullable(?):
+  private static ResultSet testRowUnion;
+
+
+  private static ResultSet setUpRow( final String schemaName,
+                                     final String tableOrViewName,
+                                     final String columnName ) throws SQLException
+  {
+    System.out.println( "(Setting up row for " + tableOrViewName + "." + columnName + ".)");
+    assert null != dbMetadata
+        : "dbMetadata is null; must be set before calling setUpRow(...)";
+    final ResultSet testRow =
+        dbMetadata.getColumns( "DRILL", schemaName, tableOrViewName, columnName );
+    if ( ! testRow.next() ) {
+      assert false
+          : "Test setup error:  No row for column DRILL . `" + schemaName + "` . `"
+            + tableOrViewName + "` . `" + columnName + "`";
+    }
+    return testRow;
+  }
+
+  @BeforeClass
+  public static void setUpConnectionAndMetadataToCheck() throws Exception {
+
+    // Get JDBC connection to Drill:
+    connection = new Driver().connect( "jdbc:drill:zk=local", null );
+    dbMetadata = connection.getMetaData();
+    Statement stmt = connection.createStatement();
+
+    ResultSet util;
+
+    // Create Hive test data, only if not created already (speed optimization):
+    util = stmt.executeQuery( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS "
+                              + "WHERE TABLE_SCHEMA = 'hive_test.default' "
+                              + "  AND TABLE_NAME = 'infoschematest'" );
+
+    System.out.println( "(Hive infoschematest columns: " );
+    int hiveTestColumnRowCount = 0;
+    while ( util.next() ) {
+      hiveTestColumnRowCount++;
+      System.out.println(
+          " Hive test column: "
+          + util.getString( 1 ) + " - " + util.getString( 2 ) + " - "
+          + util.getString( 3 ) + " - " + util.getString( 4 ) );
+    }
+    System.out.println( " Hive test column count: " + hiveTestColumnRowCount + ")" );
+    if ( 0 == hiveTestColumnRowCount ) {
+      // No Hive test data--create it.
+      new HiveTestDataGenerator().generateTestData();
+    } else if ( 17 == hiveTestColumnRowCount ) {
+      // Hive data seems to exist already--skip recreating it.
+    } else {
+      assert false
+          : "Expected 17 Hive test columns see " + hiveTestColumnRowCount + "."
+            + "  Test code is out of date or Hive data is corrupted.";
+    }
+
+    // Note: Assertions must be enabled (as they have been so far in tests).
+
+    // Create temporary test-columns view:
+    util = stmt.executeQuery( "USE dfs.tmp" );
+    assert util.next();
+    assert util.getBoolean( 1 )
+        : "Error setting schema for test: " + util.getString( 2 );
+    util = stmt.executeQuery(
+        ""
+        +   "CREATE OR REPLACE VIEW " + VIEW_NAME + " AS SELECT  "
+        + "\n  CAST( NULL         AS BOOLEAN             ) AS optBOOLEAN,      "
+        + "\n"
+        + "\n  CAST(    1         AS TINYINT             ) AS reqTINYINT,      "
+        + "\n  CAST( NULL         AS SMALLINT            ) AS optSMALLINT,     "
+        + "\n  CAST(    2         AS INTEGER             ) AS reqINTEGER,      "
+        + "\n  CAST( NULL         AS BIGINT              ) AS optBIGINT,       "
+        + "\n"
+        + "\n  CAST( NULL         AS FLOAT               ) AS optFLOAT,        "
+        + "\n  CAST(  3.3         AS DOUBLE              ) AS reqDOUBLE,       "
+        + "\n  CAST( NULL         AS REAL                ) AS optREAL,         "
+        + "\n"
+        + "\n  CAST(  4.4         AS DECIMAL(5,3)        ) AS reqDECIMAL_5_3,  "
+        + "\n"
+        + "\n  CAST( 'Hi'         AS VARCHAR(10)         ) AS reqVARCHAR_10,   "
+        + "\n  CAST( NULL         AS VARCHAR             ) AS optVARCHAR,      "
+        + "\n  CAST( '55'         AS CHAR(5)             ) AS reqCHAR_5,       "
+        + "\n  CAST( NULL         AS VARBINARY(16)       ) AS optVARBINARY_16, "
+        + "\n  CAST( NULL         AS VARBINARY(1048576)  ) AS optBINARY_1048576, "
+        + "\n  CAST( NULL         AS BINARY(8)           ) AS optBINARY_8,     "
+        + "\n"
+        + "\n  CAST( '2015-01-01' AS DATE                ) AS reqDATE,         "
+        + "\n  CAST( NULL         AS TIME                ) AS optTIME,         "
+        + "\n  CAST( NULL         AS TIME(7)             ) AS optTIME_7,       "
+        + "\n  CAST( NULL         AS TIMESTAMP           ) AS optTIMESTAMP,    "
+        + "\n  CAST( NULL  AS INTERVAL HOUR TO SECOND(3) ) AS optINTERVAL_H_S3, "
+        + "\n  CAST( NULL  AS INTERVAL YEAR(4)           ) AS optINTERVAL_Y4,  "
+        + "\n  '' "
+        + "\nFROM INFORMATION_SCHEMA.COLUMNS "
+        + "\nLIMIT 1 " );
+    assert util.next();
+    assert util.getBoolean( 1 )
+        : "Error creating temporary test-columns view " + VIEW_NAME + ": "
+          + util.getString( 2 );
+
+    // Set up result rows for temporary test view and Hivetest columns:
+
+    mdrOptBOOLEAN        = setUpRow( "dfs.tmp", VIEW_NAME, "optBOOLEAN" );
+
+    mdrReqTINYINT        = setUpRow( "dfs.tmp", VIEW_NAME, "reqTINYINT" );
+    mdrOptSMALLINT       = setUpRow( "dfs.tmp", VIEW_NAME, "optSMALLINT" );
+    mdrReqINTEGER        = setUpRow( "dfs.tmp", VIEW_NAME, "reqINTEGER" );
+    mdrOptBIGINT         = setUpRow( "dfs.tmp", VIEW_NAME, "optBIGINT" );
+
+    mdrOptFLOAT          = setUpRow( "dfs.tmp", VIEW_NAME, "optFLOAT" );
+    mdrReqDOUBLE         = setUpRow( "dfs.tmp", VIEW_NAME, "reqDOUBLE" );
+    mdrOptREAL           = setUpRow( "dfs.tmp", VIEW_NAME, "optREAL" );
+
+    mdrReqDECIMAL_5_3    = setUpRow( "dfs.tmp", VIEW_NAME, "reqDECIMAL_5_3" );
+
+    mdrReqVARCHAR_10     = setUpRow( "dfs.tmp", VIEW_NAME, "reqVARCHAR_10" );
+    mdrOptVARCHAR        = setUpRow( "dfs.tmp", VIEW_NAME, "optVARCHAR" );
+    mdrReqCHAR_5         = setUpRow( "dfs.tmp", VIEW_NAME, "reqCHAR_5" );
+    mdrOptVARBINARY_16   = setUpRow( "dfs.tmp", VIEW_NAME, "optVARBINARY_16" );
+    mdrOptBINARY_1048576 = setUpRow( "dfs.tmp", VIEW_NAME, "optBINARY_1048576" );
+
+    mdrReqDATE           = setUpRow( "dfs.tmp", VIEW_NAME, "reqDATE" );
+    mdrOptTIME           = setUpRow( "dfs.tmp", VIEW_NAME, "optTIME" );
+    mdrOptTIME_7         = setUpRow( "dfs.tmp", VIEW_NAME, "optTIME_7" );
+    mdrOptTIMESTAMP      = setUpRow( "dfs.tmp", VIEW_NAME, "optTIMESTAMP" );
+    mdrOptINTERVAL_H_S3  = setUpRow( "dfs.tmp", VIEW_NAME, "optINTERVAL_H_S3" );
+    mdrOptINTERVAL_Y4    = setUpRow( "dfs.tmp", VIEW_NAME, "optINTERVAL_Y4" );
+
+    mdrReqARRAY   = setUpRow( "hive_test.default", "infoschematest", "listtype" );
+    mdrReqMAP     = setUpRow( "hive_test.default", "infoschematest", "maptype" );
+    testRowSTRUCT = setUpRow( "hive_test.default", "infoschematest", "structtype" );
+    testRowUnion  = setUpRow( "hive_test.default", "infoschematest", "uniontypetype" );
+
+    // Set up getColumns(...)) result set' metadata:
+
+    // Get all columns for more diversity of values (e.g., nulls and non-nulls),
+    // in case that affects metadata (e.g., nullability) (in future).
+    final ResultSet allColumns = dbMetadata.getColumns( null /* any catalog */,
+                                                        null /* any schema */,
+                                                        "%"  /* any table */,
+                                                        "%"  /* any column */ );
+    rowsMetadata = allColumns.getMetaData();
+  }
+
+  @AfterClass
+  public static void tearDownConnection() throws SQLException {
+
+    ResultSet util =
+        connection.createStatement().executeQuery( "DROP VIEW " + VIEW_NAME + "" );
+    assert util.next();
+    // DRILL-2439:  assert util.getBoolean( 1 ) : ...;
+    assert util.getBoolean( 1 )
+       : "Error dropping temporary test-columns view " + VIEW_NAME + ": "
+         + util.getString( 2 );
+    connection.close();
+  }
+
+
+  //////////////////////////////////////////////////////////////////////
+  // Tests:
+
+  ////////////////////////////////////////////////////////////
+  // Number of columns.
+
+  @Test
+  public void testMetadataHasRightNumberOfColumns() throws SQLException {
+    // TODO:  Review:  Is this check valid?  (Are extra columns allowed?)
+    assertThat( "column count", rowsMetadata.getColumnCount(), equalTo( 24 ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #1: TABLE_CAT:
+  // - JDBC:   "1. ... String => table catalog (may be null)"
+  // - Drill:  Apparently chooses always "DRILL".
+  // - (Meta): VARCHAR (NVARCHAR?); Non-nullable(?);
+
+  @Test
+  public void test_TABLE_CAT_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 1 ), equalTo( "TABLE_CAT" ) );
+  }
+
+  @Test
+  public void test_TABLE_CAT_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getString( "TABLE_CAT" ), equalTo( "DRILL" ) );
+  }
+
+  // Not bothering with other test columns for TABLE_CAT.
+
+  @Test
+  public void test_TABLE_CAT_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 1 ), equalTo( "TABLE_CAT" ) );
+  }
+
+  @Test
+  public void test_TABLE_CAT_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 1 ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_TABLE_CAT_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 1 ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_TABLE_CAT_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.String" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 1 ), equalTo( String.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_TABLE_CAT_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 1 ), equalTo( columnNullable ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #2: TABLE_SCHEM:
+  // - JDBC:   "2. ... String => table schema (may be null)"
+  // - Drill:  Always reports a schema name.
+  // - (Meta): VARCHAR (NVARCHAR?); Nullable?;
+
+  @Test
+  public void test_TABLE_SCHEM_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 2 ), equalTo( "TABLE_SCHEM" ) );
+  }
+
+  @Test
+  public void test_TABLE_SCHEM_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getString( "TABLE_SCHEM" ), equalTo( "dfs.tmp" ) );
+  }
+
+  // Not bothering with other _local_view_ test columns for TABLE_SCHEM.
+
+  @Test
+  public void test_TABLE_SCHEM_hasRightValue_tdbARRAY() throws SQLException {
+    assertThat( mdrReqARRAY.getString( "TABLE_SCHEM" ), equalTo( "hive_test.default" ) );
+  }
+
+  // Not bothering with other Hive test columns for TABLE_SCHEM.
+
+  @Test
+  public void test_TABLE_SCHEM_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 2 ), equalTo( "TABLE_SCHEM" ) );
+  }
+
+  @Test
+  public void test_TABLE_SCHEM_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 2 ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_TABLE_SCHEM_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 2 ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_TABLE_SCHEM_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.String" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 2 ), equalTo( String.class.getName() ) );
+  }
+
+  @Ignore( "until resolved:  any requirement on nullability (DRILL-2420?)" )
+  @Test
+  public void test_TABLE_SCHEM_hasRightNullability() throws SQLException {
+    // To-do:  CHECK:  Why columnNullable, when seemingly known nullable?
+    // (Why not like TABLE_CAT, which does have columnNoNulls?)
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 2 ), equalTo( columnNoNulls ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #3: TABLE_NAME:
+  // - JDBC:  "3. ... String => table name"
+  // - Drill:
+  // - (Meta): VARCHAR (NVARCHAR?); Non-nullable?;
+
+  @Test
+  public void test_TABLE_NAME_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 3 ), equalTo( "TABLE_NAME" ) );
+  }
+
+  @Test
+  public void test_TABLE_NAME_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getString( "TABLE_NAME" ), equalTo( VIEW_NAME ) );
+  }
+
+  // Not bothering with other _local_view_ test columns for TABLE_NAME.
+
+  @Test
+  public void test_TABLE_NAME_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 3 ), equalTo( "TABLE_NAME" ) );
+  }
+
+  @Test
+  public void test_TABLE_NAME_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 3 ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_TABLE_NAME_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 3 ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_TABLE_NAME_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.String" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 3 ), equalTo( String.class.getName() ) );
+  }
+
+  @Ignore( "until resolved:  any requirement on nullability (DRILL-2420?)" )
+  @Test
+  public void test_TABLE_NAME_hasRightNullability() throws SQLException {
+    // To-do:  CHECK:  Why columnNullable, when seemingly known nullable?
+    // (Why not like TABLE_CAT, which does have columnNoNulls?)
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 3 ), equalTo( columnNoNulls ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #4: COLUMN_NAME:
+  // - JDBC:  "4. ... String => column name"
+  // - Drill:
+  // - (Meta): VARCHAR (NVARCHAR?); Non-nullable(?);
+
+  @Test
+  public void test_COLUMN_NAME_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 4 ), equalTo( "COLUMN_NAME" ) );
+  }
+
+  @Test
+  public void test_COLUMN_NAME_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getString( "COLUMN_NAME" ), equalTo( "optBOOLEAN" ) );
+  }
+
+  // Not bothering with other _local_view_ test columns for TABLE_SCHEM.
+
+  @Test
+  public void test_COLUMN_NAME_hasRightValue_tdbARRAY() throws SQLException {
+    assertThat( mdrReqARRAY.getString( "COLUMN_NAME" ), equalTo( "listtype" ) );
+  }
+
+  // Not bothering with other Hive test columns for TABLE_SCHEM.
+
+  @Test
+  public void test_COLUMN_NAME_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 4 ), equalTo( "COLUMN_NAME" ) );
+  }
+
+  @Test
+  public void test_COLUMN_NAME_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 4 ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_COLUMN_NAME_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 4 ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_COLUMN_NAME_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.String" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 4 ), equalTo( String.class.getName() ) );
+  }
+
+  @Ignore( "until resolved:  any requirement on nullability (DRILL-2420?)" )
+  @Test
+  public void test_COLUMN_NAME_hasRightNullability() throws SQLException {
+    // To-do:  CHECK:  Why columnNullable, when seemingly known nullable?
+    // (Why not like TABLE_CAT, which does have columnNoNulls?)
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 4 ), equalTo( columnNoNulls ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #5: DATA_TYPE:
+  // - JDBC:  "5. ... int => SQL type from java.sql.Types"
+  // - Drill:
+  // - (Meta): INTEGER(?);  Non-nullable(?);
+
+  @Test
+  public void test_DATA_TYPE_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 5 ), equalTo( "DATA_TYPE" ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getInt( "DATA_TYPE" ), equalTo( Types.BOOLEAN ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_reqTINYINT() throws SQLException {
+    assertThat( mdrReqTINYINT.getInt( "DATA_TYPE" ), equalTo( Types.TINYINT ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optSMALLINT() throws SQLException {
+    assertThat( mdrOptSMALLINT.getInt( "DATA_TYPE" ), equalTo( Types.SMALLINT ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_reqINTEGER() throws SQLException {
+    assertThat( mdrReqINTEGER.getInt( "DATA_TYPE" ), equalTo( Types.INTEGER ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optBIGINT() throws SQLException {
+    assertThat( mdrOptBIGINT.getInt( "DATA_TYPE" ), equalTo( Types.BIGINT ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optFLOAT() throws SQLException {
+    assertThat( mdrOptFLOAT.getInt( "DATA_TYPE" ), equalTo( Types.FLOAT ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_reqDOUBLE() throws SQLException {
+    assertThat( mdrReqDOUBLE.getInt( "DATA_TYPE" ), equalTo( Types.DOUBLE ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optREAL() throws SQLException {
+    assertThat( mdrOptREAL.getInt( "DATA_TYPE" ), equalTo( Types.REAL ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_reqDECIMAL_5_3() throws SQLException {
+    assertThat( mdrReqDECIMAL_5_3.getInt( "DATA_TYPE" ), equalTo( Types.DECIMAL ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_reqVARCHAR_10() throws SQLException {
+    assertThat( mdrReqVARCHAR_10.getInt( "DATA_TYPE" ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optVARCHAR() throws SQLException {
+    assertThat( mdrOptVARCHAR.getInt( "DATA_TYPE" ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_reqCHAR_5() throws SQLException {
+    assertThat( mdrReqCHAR_5.getInt( "DATA_TYPE" ), equalTo( Types.CHAR ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optVARBINARY_16() throws SQLException {
+    assertThat( mdrOptVARBINARY_16.getInt( "DATA_TYPE" ), equalTo( Types.VARBINARY ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optBINARY_1048576CHECK() throws SQLException {
+    assertThat( mdrOptBINARY_1048576.getInt( "DATA_TYPE" ), equalTo( Types.VARBINARY ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_reqDATE() throws SQLException {
+    assertThat( mdrReqDATE.getInt( "DATA_TYPE" ), equalTo( Types.DATE ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optTIME() throws SQLException {
+    assertThat( mdrOptTIME.getInt( "DATA_TYPE" ), equalTo( Types.TIME ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optTIME_7() throws SQLException {
+    assertThat( mdrOptTIME_7.getInt( "DATA_TYPE" ), equalTo( Types.TIME ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optTIMESTAMP() throws SQLException {
+    assertThat( mdrOptTIMESTAMP.getInt( "DATA_TYPE" ), equalTo( Types.TIMESTAMP ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optINTERVAL_HM() throws SQLException {
+    assertThat( mdrOptINTERVAL_H_S3.getInt( "DATA_TYPE" ), equalTo( Types.OTHER ) );
+    // To-do:  Determine which.
+    assertThat( mdrOptINTERVAL_H_S3.getInt( "DATA_TYPE" ), equalTo( Types.JAVA_OBJECT ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_DATA_TYPE_hasRightValue_optINTERVAL_Y3() throws SQLException {
+    assertThat( mdrOptINTERVAL_Y4.getInt( "DATA_TYPE" ), equalTo( Types.OTHER ) );
+    // To-do:  Determine which.
+    assertThat( mdrOptINTERVAL_Y4.getInt( "DATA_TYPE" ), equalTo( Types.JAVA_OBJECT ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_tdbARRAY() throws SQLException {
+    assertThat( mdrReqARRAY.getInt( "DATA_TYPE" ), equalTo( Types.ARRAY ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_DATA_TYPE_hasRightValue_tbdMAP() throws SQLException {
+    assertThat( "java.sql.Types.* type code",
+                mdrReqMAP.getInt( "DATA_TYPE" ), equalTo( Types.OTHER ) );
+    // To-do:  Determine which.
+    assertThat( "java.sql.Types.* type code",
+                mdrReqMAP.getInt( "DATA_TYPE" ), equalTo( Types.JAVA_OBJECT ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightValue_tbdSTRUCT() throws SQLException {
+    assertThat( testRowSTRUCT.getInt( "DATA_TYPE" ), equalTo( Types.STRUCT ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_DATA_TYPE_hasRightValue_tbdUnion() throws SQLException {
+    assertThat( "java.sql.Types.* type code",
+                testRowUnion.getInt( "DATA_TYPE" ), equalTo( Types.OTHER ) );
+    // To-do:  Determine which.
+    assertThat( "java.sql.Types.* type code",
+                testRowUnion.getInt( "DATA_TYPE" ), equalTo( Types.JAVA_OBJECT ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 5 ), equalTo( "DATA_TYPE" ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 5 ), equalTo( "INTEGER" ) );
+  }
+
+  @Test
+  public void test_DATA_TYPE_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 5 ), equalTo( Types.INTEGER ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_DATA_TYPE_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 5 ), equalTo( Integer.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_DATA_TYPE_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 5 ), equalTo( columnNoNulls ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #6: TYPE_NAME:
+  // - JDBC:  "6. ... String => Data source dependent type name, for a UDT the
+  //     type name is fully qualified"
+  // - Drill:
+  // - (Meta): VARCHAR (NVARCHAR?); Non-nullable?;
+
+  @Test
+  public void test_TYPE_NAME_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 6 ), equalTo( "TYPE_NAME" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getString( "TYPE_NAME" ), equalTo( "BOOLEAN" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_reqTINYINT() throws SQLException {
+    assertThat( mdrReqTINYINT.getString( "TYPE_NAME" ), equalTo( "TINYINT" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optSMALLINT() throws SQLException {
+    assertThat( mdrOptSMALLINT.getString( "TYPE_NAME" ), equalTo( "SMALLINT" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_reqINTEGER() throws SQLException {
+    assertThat( mdrReqINTEGER.getString( "TYPE_NAME" ), equalTo( "INTEGER" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optBIGINT() throws SQLException {
+    assertThat( mdrOptBIGINT.getString( "TYPE_NAME" ), equalTo( "BIGINT" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optFLOAT() throws SQLException {
+    assertThat( mdrOptFLOAT.getString( "TYPE_NAME" ), equalTo( "FLOAT" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_reqDOUBLE() throws SQLException {
+    assertThat( mdrReqDOUBLE.getString( "TYPE_NAME" ), equalTo( "DOUBLE" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optREAL() throws SQLException {
+    assertThat( mdrOptREAL.getString( "TYPE_NAME" ), equalTo( "REAL" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_reqDECIMAL_5_3() throws SQLException {
+    assertThat( mdrReqDECIMAL_5_3.getString( "TYPE_NAME" ), equalTo( "DECIMAL" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_reqVARCHAR_10() throws SQLException {
+    assertThat( mdrReqVARCHAR_10.getString( "TYPE_NAME" ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optVARCHAR() throws SQLException {
+    assertThat( mdrOptVARCHAR.getString( "TYPE_NAME" ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_reqCHAR_5() throws SQLException {
+    assertThat( mdrReqCHAR_5.getString( "TYPE_NAME" ), equalTo( "CHAR" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optVARBINARY_16() throws SQLException {
+    assertThat( mdrOptVARBINARY_16.getString( "TYPE_NAME" ), equalTo( "VARBINARY" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optBINARY_1048576CHECK() throws SQLException {
+    assertThat( mdrOptBINARY_1048576.getString( "TYPE_NAME" ), equalTo( "VARBINARY" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_reqDATE() throws SQLException {
+    assertThat( mdrReqDATE.getString( "TYPE_NAME" ), equalTo( "DATE" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optTIME() throws SQLException {
+    assertThat( mdrOptTIME.getString( "TYPE_NAME" ), equalTo( "TIME" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optTIME_7() throws SQLException {
+    assertThat( mdrOptTIME_7.getString( "TYPE_NAME" ), equalTo( "TIME" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optTIMESTAMP() throws SQLException {
+    assertThat( mdrOptTIMESTAMP.getString( "TYPE_NAME" ), equalTo( "TIMESTAMP" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optINTERVAL_HM() throws SQLException {
+    // (What SQL standard specifies for DATA_TYPE in INFORMATION_SCHEMA.COLUMNS:)
+    assertThat( mdrOptINTERVAL_H_S3.getString( "TYPE_NAME" ), equalTo( "INTERVAL" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightValue_optINTERVAL_Y3() throws SQLException {
+    // (What SQL standard specifies for DATA_TYPE in INFORMATION_SCHEMA.COLUMNS:)
+    assertThat( mdrOptINTERVAL_Y4.getString( "TYPE_NAME" ), equalTo( "INTERVAL" ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_TYPE_NAME_hasRightValue_tdbARRAY() throws SQLException {
+    assertThat( mdrReqARRAY.getString( "TYPE_NAME" ), equalTo( "VARCHAR(65535) ARRAY" ) );
+    // TODO:  Determine which.
+    assertThat( mdrReqARRAY.getString( "TYPE_NAME" ), equalTo( "ARRAY" ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_TYPE_NAME_hasRightValue_tbdMAP() throws SQLException {
+    assertThat( mdrReqMAP.getString( "TYPE_NAME" ), equalTo( "(VARCHAR(65535), INTEGER) MAP" ) );
+    // TODO:  Determine which.
+    assertThat( mdrReqMAP.getString( "TYPE_NAME" ), equalTo( "MAP" ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_TYPE_NAME_hasRightValue_tbdSTRUCT() throws SQLException {
+    assertThat( testRowSTRUCT.getString( "TYPE_NAME" ),
+                equalTo( "STRUCT(INTEGER sint, BOOLEAN sboolean, VARCHAR(65535) sstring)" ) ); // TODO:  Confirm.
+    // TODO:  Determine which.
+    assertThat( testRowSTRUCT.getString( "TYPE_NAME" ), equalTo( "STRUCT" ) );
+  }
+
+  @Ignore( "until resolved:  expected value (DRILL-2420?)" )
+  @Test
+  public void test_TYPE_NAME_hasRightValue_tbdUnion() throws SQLException {
+    assertThat( testRowUnion.getString( "TYPE_NAME" ), equalTo( "OTHER" ) );
+    fail( "Expected value is not resolved yet." );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 6 ), equalTo( "TYPE_NAME" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 6 ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_TYPE_NAME_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 6 ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_TYPE_NAME_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.String" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 6 ), equalTo( String.class.getName() ) );
+  }
+
+  @Ignore( "until resolved:  any requirement on nullability (DRILL-2420?)" )
+  @Test
+  public void test_TYPE_NAME_hasRightNullability() throws SQLException {
+    // To-do:  CHECK:  Why columnNullable, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 6 ), equalTo( columnNoNulls ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #7: COLUMN_SIZE:
+  // - JDBC:  "7. ... int => column size."
+  //     "The COLUMN_SIZE column specifies the column size for the given column.
+  //      For numeric data, this is the maximum precision.
+  //      For character data, this is the length in characters.
+  //      For datetime datatypes, this is the length in characters of the String
+  //        representation (assuming the maximum allowed precision of the
+  //        fractional seconds component).
+  //      For binary data, this is the length in bytes.
+  //      For the ROWID datatype, this is the length in bytes.
+  //      Null is returned for data types where the column size is not applicable."
+  //   - "Maximum precision" seem to mean maximum number of digits that can
+  //     appear.
+  // - (Meta): INTEGER(?); Nullable;
+
+  @Test
+  public void test_COLUMN_SIZE_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 7 ), equalTo( "COLUMN_SIZE" ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optBOOLEAN() throws SQLException {
+    final int value = mdrOptBOOLEAN.getInt( "COLUMN_SIZE" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBOOLEAN.wasNull(), equalTo( true )  ); // TODO:  CONFIRM.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_reqTINYINT() throws SQLException {
+    assertThat( mdrReqTINYINT.getInt( "COLUMN_SIZE" ), equalTo( 3 ) ); // TODO:  CONFIRM.
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optSMALLINT() throws SQLException {
+    assertThat( mdrOptSMALLINT.getInt( "COLUMN_SIZE" ), equalTo( 5 ) );  // TODO:  CONFIRM
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_reqINTEGER() throws SQLException {
+    assertThat( mdrReqINTEGER.getInt( "COLUMN_SIZE" ), equalTo( 10 ) ); // TODO:  CONFIRM.
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optBIGINT() throws SQLException {
+    assertThat( mdrOptBIGINT.getInt( "COLUMN_SIZE" ), equalTo( 19 ) );  // To-do:  CONFIRM.
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optFLOAT() throws SQLException {
+    assertThat( mdrOptFLOAT.getInt( "COLUMN_SIZE" ), equalTo( 7 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_reqDOUBLE() throws SQLException {
+    assertThat( mdrReqDOUBLE.getInt( "COLUMN_SIZE" ), equalTo( 15 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optREAL() throws SQLException {
+    assertThat( mdrOptREAL.getInt( "COLUMN_SIZE" ), equalTo( 15 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_reqDECIMAL_5_3() throws SQLException {
+    assertThat( mdrReqDECIMAL_5_3.getInt( "COLUMN_SIZE" ), equalTo( 5 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_reqVARCHAR_10() throws SQLException {
+    assertThat( mdrReqVARCHAR_10.getInt( "COLUMN_SIZE" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optVARCHAR() throws SQLException {
+    assertThat( mdrOptVARCHAR.getInt( "COLUMN_SIZE" ), equalTo( 1 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_reqCHAR_5() throws SQLException {
+    assertThat( mdrReqCHAR_5.getInt( "COLUMN_SIZE" ), equalTo( 5 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optVARBINARY_16() throws SQLException {
+    assertThat( mdrOptVARBINARY_16.getInt( "COLUMN_SIZE" ), equalTo( 16 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optBINARY_1048576() throws SQLException {
+    assertThat( mdrOptBINARY_1048576.getInt( "COLUMN_SIZE" ), equalTo( 1048576 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_reqDATE() throws SQLException {
+    assertThat( mdrReqDATE.getInt( "COLUMN_SIZE" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optTIME() throws SQLException {
+    assertThat( mdrOptTIME.getInt( "COLUMN_SIZE" ),
+                equalTo( 8  /* HH:MM:SS */  ) );
+  }
+
+  @Ignore( "until resolved:  whether to implement TIME precision or drop test" )
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optTIME_7() throws SQLException {
+    assertThat( mdrOptTIME_7.getInt( "COLUMN_SIZE" ),
+                equalTo( 8  /* HH:MM:SS */ + 1 /* '.' */ + 7 /* sssssss */ ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optTIMESTAMP() throws SQLException {
+    assertThat( mdrOptTIMESTAMP.getInt( "COLUMN_SIZE" ),
+                equalTo( 19 /* YYYY-MM-DDTHH:MM:SS */  ) );
+  }
+
+  @Ignore( "until fixed:  INTERVAL metadata in INFORMATION_SCHEMA (DRILL-2531)" )
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optINTERVAL_HM() throws SQLException {
+    assertThat( mdrOptINTERVAL_H_S3.getInt( "COLUMN_SIZE" ),
+                equalTo( 14 ) );  // "P12H12M12.1234S"
+  }
+
+  // TODO:  When DRILL-2531 is fixed, remove this:
+  @Test
+  public void test_COLUMN_SIZE_hasRightINTERIMValue_optINTERVAL_HM() throws SQLException {
+    assertThat( mdrOptINTERVAL_H_S3.getInt( "COLUMN_SIZE" ),
+                equalTo( 31 ) );  // from max. form "P12..90D12H12M12.12..89S"
+  }
+
+  @Ignore( "until fixed:  INTERVAL metadata in INFORMATION_SCHEMA (DRILL-2531)" )
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_optINTERVAL_Y3() throws SQLException {
+    assertThat( mdrOptINTERVAL_Y4.getInt( "COLUMN_SIZE" ),
+                equalTo( 6 ) );  // "P1234Y"
+  }
+
+  // TODO:  When DRILL-2531 is fixed, remove this:
+  @Test
+  public void test_COLUMN_SIZE_hasRightINTERIMValue_optINTERVAL_Y3() throws SQLException {
+    assertThat( mdrOptINTERVAL_Y4.getInt( "COLUMN_SIZE" ),
+                equalTo( 15 ) );  // from max. form "P12..90Y"
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_tdbARRAY() throws SQLException {
+    final int value = mdrReqARRAY.getInt( "COLUMN_SIZE" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqARRAY.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_tbdMAP() throws SQLException {
+    final int value = mdrReqMAP.getInt( "COLUMN_SIZE" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqMAP.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_tbdSTRUCT() throws SQLException {
+    final int value = testRowSTRUCT.getInt( "COLUMN_SIZE" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowSTRUCT.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightValue_tbdUnion() throws SQLException {
+    final int value = testRowUnion.getInt( "COLUMN_SIZE" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowUnion.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 7 ), equalTo( "COLUMN_SIZE" ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 7 ), equalTo( "INTEGER" ) );
+  }
+
+  @Test
+  public void test_COLUMN_SIZE_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 7 ), equalTo( Types.INTEGER ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_COLUMN_SIZE_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 7 ), equalTo( Integer.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_COLUMN_SIZE_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 7 ), equalTo( columnNullable ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #8: BUFFER_LENGTH:
+  // - JDBC:   "8. ... is not used"
+  // - Drill:
+  // - (Meta):
+
+  // Since "unused," check only certain meta-metadata.
+
+  @Test
+  public void test_BUFFER_LENGTH_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 8 ), equalTo( "BUFFER_LENGTH" ) );
+  }
+
+  // No specific value or even type to check for.
+
+  @Test
+  public void test_BUFFER_LENGTH_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 8 ), equalTo( "BUFFER_LENGTH" ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #9: DECIMAL_DIGITS:
+  // - JDBC:  "9. ... int => the number of fractional digits. Null is
+  //     returned for data types where DECIMAL_DIGITS is not applicable."
+  //   - Resolve:  When exactly null?
+  // - Drill:
+  // - (Meta):  INTEGER(?); Nullable;
+
+  @Test
+  public void test_DECIMAL_DIGITS_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 9 ), equalTo( "DECIMAL_DIGITS" ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optBOOLEAN() throws SQLException {
+    final int value = mdrOptBOOLEAN.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBOOLEAN.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_reqTINYINT() throws SQLException {
+    final int value = mdrReqTINYINT.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqTINYINT.wasNull(), equalTo( false ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optSMALLINT() throws SQLException {
+    final int value = mdrOptSMALLINT.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptSMALLINT.wasNull(), equalTo( false ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_reqINTEGER() throws SQLException {
+    final int value = mdrReqINTEGER.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqINTEGER.wasNull(), equalTo( false ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optBIGINT() throws SQLException {
+    final int value = mdrOptBIGINT.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBIGINT.wasNull(), equalTo( false ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optFLOAT() throws SQLException {
+    assertThat( mdrOptFLOAT.getInt( "DECIMAL_DIGITS" ), equalTo( 7 ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_reqDOUBLE() throws SQLException {
+    assertThat( mdrReqDOUBLE.getInt( "DECIMAL_DIGITS" ), equalTo( 15 ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optREAL() throws SQLException {
+    assertThat( mdrOptREAL.getInt( "DECIMAL_DIGITS" ), equalTo( 15 ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_reqDECIMAL_5_3() throws SQLException {
+    assertThat( mdrReqDECIMAL_5_3.getInt( "DECIMAL_DIGITS" ), equalTo( 3 ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_reqVARCHAR_10() throws SQLException {
+    final int value = mdrReqVARCHAR_10.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqVARCHAR_10.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optVARCHAR() throws SQLException {
+    final int value = mdrOptVARCHAR.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptVARCHAR.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_reqCHAR_5() throws SQLException {
+    final int value = mdrReqCHAR_5.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqCHAR_5.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optVARBINARY_16() throws SQLException {
+    final int value = mdrOptVARBINARY_16.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptVARBINARY_16.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optBINARY_1048576CHECK() throws SQLException {
+    final int value = mdrOptBINARY_1048576.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBINARY_1048576.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_reqDATE() throws SQLException {
+    final int value = mdrReqDATE.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqDATE.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optTIME() throws SQLException {
+    assertThat( mdrOptTIME.getInt( "DECIMAL_DIGITS" ), equalTo( 0 ) );
+  }
+
+  @Ignore( "until resolved:  whether to implement TIME precision or drop test" )
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optTIME_7() throws SQLException {
+    assertThat( mdrOptTIME_7.getInt( "DECIMAL_DIGITS" ), equalTo( 7 ) );
+  }
+
+  @Ignore( "until resolved:  whether to implement TIME precision or drop test" )
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optTIMESTAMP() throws SQLException {
+    assertThat( mdrOptTIMESTAMP.getInt( "DECIMAL_DIGITS" ), equalTo( 0 ) );
+  }
+
+  @Ignore( "until fixed:  INTERVAL metadata in INFORMATION_SCHEMA (DRILL-2531)" )
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optINTERVAL_HM() throws SQLException {
+    assertThat( mdrOptINTERVAL_H_S3.getInt( "DECIMAL_DIGITS" ), equalTo( 3 ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_optINTERVAL_Y3() throws SQLException {
+    assertThat( mdrOptINTERVAL_Y4.getInt( "DECIMAL_DIGITS" ), equalTo( 0 ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_tdbARRAY() throws SQLException {
+    final int value = mdrReqARRAY.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqARRAY.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_tbdMAP() throws SQLException {
+    final int value = mdrReqMAP.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqMAP.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_tbdSTRUCT() throws SQLException {
+    final int value = testRowSTRUCT.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowSTRUCT.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightValue_tbdUnion() throws SQLException {
+    final int value = testRowUnion.getInt( "DECIMAL_DIGITS" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowUnion.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 9 ), equalTo( "DECIMAL_DIGITS" ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 9 ), equalTo( "INTEGER" ) );
+  }
+
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 9 ), equalTo( Types.INTEGER ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 9 ), equalTo( Integer.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_DECIMAL_DIGITS_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 9 ), equalTo( columnNullable ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #10: NUM_PREC_RADIX:
+  // - JDBC:  "10. ... int => Radix (typically either 10 or 2)"
+  //   - Seems should be null for non-numeric, but unclear.
+  // - Drill:  ?
+  // - (Meta): INTEGER?; Nullable?;
+  //
+  // Note:  Some MS page says NUM_PREC_RADIX specifies the units (decimal digits
+  // or binary bits COLUMN_SIZE, and is NULL for non-numeric columns.
+
+  @Test
+  public void test_NUM_PREC_RADIX_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 10 ), equalTo( "NUM_PREC_RADIX" ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optBOOLEAN() throws SQLException {
+    final int value = mdrOptBOOLEAN.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBOOLEAN.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_reqTINYINT() throws SQLException {
+    assertThat( mdrReqTINYINT.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optSMALLINT() throws SQLException {
+    assertThat( mdrOptSMALLINT.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_reqINTEGER() throws SQLException {
+    assertThat( mdrReqINTEGER.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optBIGINT() throws SQLException {
+    assertThat( mdrOptBIGINT.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optFLOAT() throws SQLException {
+    assertThat( mdrOptFLOAT.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_reqDOUBLE() throws SQLException {
+    assertThat( mdrReqDOUBLE.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optREAL() throws SQLException {
+    assertThat( mdrOptREAL.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_reqDECIMAL_5_3() throws SQLException {
+    assertThat( mdrReqDECIMAL_5_3.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_reqVARCHAR_10() throws SQLException {
+    final int value = mdrReqVARCHAR_10.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqVARCHAR_10.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optVARCHAR() throws SQLException {
+    final int value = mdrOptVARCHAR.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptVARCHAR.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_reqCHAR_5() throws SQLException {
+    final int value = mdrReqCHAR_5.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqCHAR_5.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optVARBINARY_16() throws SQLException {
+    final int value = mdrOptVARBINARY_16.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptVARBINARY_16.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optBINARY_1048576CHECK() throws SQLException {
+    final int value = mdrOptBINARY_1048576.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBINARY_1048576.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_reqDATE() throws SQLException {
+    final int value = mdrReqDATE.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqDATE.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Ignore( "until resolved:  expected value" )
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optTIME() throws SQLException {
+    assertThat( mdrOptTIME.getInt( "NUM_PREC_RADIX" ), equalTo( 10 /* NULL */ ) );
+    // To-do:  Determine which.
+    final int value = mdrOptTIME.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptTIME.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Ignore( "until resolved:  expected value" )
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optTIME_7() throws SQLException {
+    assertThat( mdrOptTIME_7.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+    // To-do:  Determine which.
+    final int value = mdrOptTIME_7.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptTIME_7.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Ignore( "until resolved:  expected value" )
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optTIMESTAMP() throws SQLException {
+    assertThat( mdrOptTIMESTAMP.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+    // To-do:  Determine which.
+    final int value = mdrOptTIMESTAMP.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                 mdrOptTIMESTAMP.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optINTERVAL_HM() throws SQLException {
+    assertThat( mdrOptINTERVAL_H_S3.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_optINTERVAL_Y3() throws SQLException {
+    assertThat( mdrOptINTERVAL_Y4.getInt( "NUM_PREC_RADIX" ), equalTo( 10 ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_tdbARRAY() throws SQLException {
+    final int value = mdrReqARRAY.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqARRAY.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_tbdMAP() throws SQLException {
+    final int value = mdrReqMAP.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqMAP.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_tbdSTRUCT() throws SQLException {
+    final int value = testRowSTRUCT.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowSTRUCT.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightValue_tbdUnion() throws SQLException {
+    final int value = testRowUnion.getInt( "NUM_PREC_RADIX" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowUnion.wasNull(), equalTo( true ) ); // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 10 ), equalTo( "NUM_PREC_RADIX" ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 10 ), equalTo( "INTEGER" ) );
+  }
+
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 10 ), equalTo( Types.INTEGER ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 10 ), equalTo( Integer.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NUM_PREC_RADIX_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 10 ), equalTo( columnNullable ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #11: NULLABLE:
+  // - JDBC:  "11. ... int => is NULL allowed.
+  //     columnNoNulls - might not allow NULL values
+  //     columnNullable - definitely allows NULL values
+  //     columnNullableUnknown - nullability unknown"
+  // - Drill:
+  // - (Meta): INTEGER(?); Non-nullable(?).
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 11 ), equalTo( "NULLABLE" ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optBOOLEAN() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptBOOLEAN.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_reqTINYINT() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known non-nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrReqTINYINT.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optSMALLINT() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known  nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptSMALLINT.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optBIGINT() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known  nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptBIGINT.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optFLOAT() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptFLOAT.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_reqDOUBLE() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known non-nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrReqDOUBLE.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optREAL() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptREAL.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_reqINTEGER() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known non-nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrReqINTEGER.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_reqDECIMAL_5_3() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known non-nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrReqDECIMAL_5_3.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_reqVARCHAR_10() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known non-nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrReqVARCHAR_10.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optVARCHAR() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptVARCHAR.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_reqCHAR_5() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known non-nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrReqCHAR_5.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optVARBINARY_16() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptVARBINARY_16.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optBINARY_1048576CHECK() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptBINARY_1048576.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_reqDATE() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known non-nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrReqDATE.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optTIME() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptTIME.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optTIME_7() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptTIME_7.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optTIMESTAMP() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptTIMESTAMP.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optINTERVAL_HM() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptINTERVAL_H_S3.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_optINTERVAL_Y3() throws SQLException {
+    // To-do:  CHECK:  Why columnNullableUnknown, when seemingly known nullable?
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                mdrOptINTERVAL_Y4.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_tdbARRAY() throws SQLException {
+    assertThat( mdrReqARRAY.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightValue_tbdMAP() throws SQLException {
+    assertThat( mdrReqMAP.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+  }
+
+  @Ignore( "until resolved:  any requirement on nullability (DRILL-2420?)" )
+  @Test
+  public void test_NULLABLE_hasRightValue_tbdSTRUCT() throws SQLException {
+    assertThat( testRowSTRUCT.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+    // To-do:  Determine which.
+    assertThat( testRowSTRUCT.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+    // To-do:  Determine which.
+    assertThat( testRowSTRUCT.getInt( "NULLABLE" ), equalTo( columnNullableUnknown ) );
+  }
+
+  @Ignore( "until resolved:  any requirement on nullability (DRILL-2420?)" )
+  @Test
+  public void test_NULLABLE_hasRightValue_tbdUnion() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                testRowUnion.getInt( "NULLABLE" ), equalTo( columnNullable ) );
+    // To-do:  Determine which.
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                testRowUnion.getInt( "NULLABLE" ), equalTo( columnNoNulls ) );
+    // To-do:  Determine which.
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                testRowUnion.getInt( "NULLABLE" ), equalTo( columnNullableUnknown ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 11 ), equalTo( "NULLABLE" ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 11 ), equalTo( "INTEGER" ) );  // TODO:  Confirm.
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 11 ), equalTo( Types.INTEGER ) );  // TODO:  Confirm.
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_NULLABLE_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 11 ), equalTo( Integer.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_NULLABLE_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 11 ), equalTo( columnNoNulls ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #12: REMARKS:
+  // - JDBC:  "12. ... String => comment describing column (may be null)"
+  // - Drill: none, so always null
+  // - (Meta): VARCHAR (NVARCHAR?); Nullable;
+
+  @Test
+  public void test_REMARKS_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 12 ), equalTo( "REMARKS" ) );
+  }
+
+  @Test
+  public void test_REMARKS_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getString( "REMARKS" ), nullValue() );
+  }
+
+  @Test
+  public void test_REMARKS_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 12 ), equalTo( "REMARKS" ) );
+  }
+
+  @Test
+  public void test_REMARKS_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 12 ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_REMARKS_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 12 ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_REMARKS_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.String" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 12 ), equalTo( String.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_REMARKS_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 12 ), equalTo( columnNullable ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #13: COLUMN_DEF:
+  // - JDBC:  "13. ... String => default value for the column, which should be
+  //     interpreted as a string when the value is enclosed in single quotes
+  //     (may be null)"
+  // - Drill:  no real default values, right?
+  // - (Meta): VARCHAR (NVARCHAR?);  Nullable;
+
+  @Test
+  public void test_COLUMN_DEF_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 13 ), equalTo( "COLUMN_DEF" ) );
+  }
+
+  @Test
+  public void test_COLUMN_DEF_hasRightValue_optBOOLEAN() throws SQLException {
+    assertThat( mdrOptBOOLEAN.getString( "COLUMN_DEF" ), nullValue() );
+  }
+
+  @Test
+  public void test_COLUMN_DEF_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 13 ), equalTo( "COLUMN_DEF" ) );
+  }
+
+  @Test
+  public void test_COLUMN_DEF_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 13 ), equalTo( "VARCHAR" ) );
+  }
+
+  @Test
+  public void test_COLUMN_DEF_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 13 ), equalTo( Types.VARCHAR ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_COLUMN_DEF_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.String" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 13 ), equalTo( String.class.getName() ) );
+  }
+
+  // (See to-do note near top of file about reviewing nullability.)
+  @Test
+  public void test_COLUMN_DEF_hasRightNullability() throws SQLException {
+    assertThat( "ResultSetMetaData.column...Null... nullability code",
+                rowsMetadata.isNullable( 13 ), equalTo( columnNullable ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #14: SQL_DATA_??TYPE:
+  // - JDBC:  "14. ... int => unused"
+  // - Drill:
+  // - (Meta): INTEGER(?);
+
+  // Since "unused," check only certain meta-metadata.
+
+  @Test
+  public void test_SQL_DATA_TYPE_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 14 ), equalTo( "SQL_DATA_TYPE" ) );
+  }
+
+  // No specific value to check for.
+
+  @Test
+  public void test_SQL_DATA_TYPE_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 14 ), equalTo( "SQL_DATA_TYPE" ) );
+  }
+
+  @Test
+  public void test_SQL_DATA_TYPE_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 14 ), equalTo( "INTEGER" ) );
+  }
+
+  @Test
+  public void test_SQL_DATA_TYPE_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 14 ), equalTo( Types.INTEGER ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_SQL_DATA_TYPE_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 14 ), equalTo( Integer.class.getName() ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #15: SQL_DATETIME_SUB:
+  // - JDBC:  "15. ... int => unused"
+  // - Drill:
+  // - (Meta):  INTEGER(?);
+
+  // Since "unused," check only certain meta-metadata.
+
+  @Test
+  public void test_SQL_DATETIME_SUB_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 15 ), equalTo( "SQL_DATETIME_SUB" ) );
+  }
+
+  // No specific value to check for.
+
+  @Test
+  public void test_SQL_DATETIME_SUB_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 15 ), equalTo( "SQL_DATETIME_SUB" ) );
+  }
+
+  @Test
+  public void test_SQL_DATETIME_SUB_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 15 ), equalTo( "INTEGER" ) );  // TODO:  Confirm.
+  }
+
+  @Test
+  public void test_SQL_DATETIME_SUB_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 15 ), equalTo( Types.INTEGER ) );  // TODO:  Confirm.
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_SQL_DATETIME_SUB_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassName( 15 ), equalTo( Integer.class.getName() ) );
+  }
+
+
+  ////////////////////////////////////////////////////////////
+  // #16: CHAR_OCTET_LENGTH:
+  // - JDBC:  "16. ... int => for char types the maximum number of bytes
+  //     in the column"
+  //   - apparently should be null for non-character types
+  // - Drill:
+  // - (Meta): INTEGER(?); Nullable(?);
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_isAtRightPosition() throws SQLException {
+    assertThat( rowsMetadata.getColumnLabel( 16 ), equalTo( "CHAR_OCTET_LENGTH" ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optBOOLEAN() throws SQLException {
+    final int value = mdrOptBOOLEAN.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBOOLEAN.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_reqTINYINT() throws SQLException {
+    final int value = mdrReqTINYINT.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqTINYINT.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optSMALLINT() throws SQLException {
+    final int value = mdrOptSMALLINT.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptSMALLINT.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_reqINTEGER() throws SQLException {
+    final int value = mdrReqINTEGER.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqINTEGER.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optBIGINT() throws SQLException {
+    final int value = mdrOptREAL.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptREAL.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optFLOAT() throws SQLException {
+    final int value = mdrOptFLOAT.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptFLOAT.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_reqDOUBLE() throws SQLException {
+    final int value = mdrReqDOUBLE.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqDOUBLE.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optREAL() throws SQLException {
+    final int value = mdrOptREAL.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptREAL.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_reqDECIMAL_5_3() throws SQLException {
+    final int value = mdrReqDECIMAL_5_3.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqDECIMAL_5_3.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_reqVARCHAR_10() throws SQLException {
+    assertThat( mdrReqVARCHAR_10.getInt( "CHAR_OCTET_LENGTH" ),
+                equalTo( 10   /* chars. */
+                         * 4  /* max. UTF-8 bytes per char. */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optVARCHAR() throws SQLException {
+    assertThat( mdrOptVARCHAR.getInt( "CHAR_OCTET_LENGTH" ),
+                equalTo( 1    /* chars. (default of 1) */
+                         * 4  /* max. UTF-8 bytes per char. */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_reqCHAR_5() throws SQLException {
+    assertThat( mdrReqCHAR_5.getInt( "CHAR_OCTET_LENGTH" ),
+                equalTo( 5    /* chars. */
+                         * 4  /* max. UTF-8 bytes per char. */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optVARBINARY_16() throws SQLException {
+    final int value = mdrOptVARBINARY_16.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptVARBINARY_16.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optBINARY_1048576CHECK() throws SQLException {
+    final int value = mdrOptBINARY_1048576.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptBINARY_1048576.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_reqDATE() throws SQLException {
+    final int value = mdrReqDATE.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqDATE.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optTIME() throws SQLException {
+    final int value = mdrOptTIME.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptTIME.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optTIME_7() throws SQLException {
+    final int value = mdrOptTIME_7.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptTIME_7.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optTIMESTAMP() throws SQLException {
+    final int value = mdrOptTIMESTAMP.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptTIMESTAMP.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optINTERVAL_HM() throws SQLException {
+    final int value = mdrOptINTERVAL_H_S3.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptINTERVAL_H_S3.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_optINTERVAL_Y3() throws SQLException {
+    final int value = mdrOptINTERVAL_Y4.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrOptINTERVAL_Y4.wasNull(), equalTo( true ) );
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_tdbARRAY() throws SQLException {
+    final int value = mdrReqARRAY.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqARRAY.wasNull(), equalTo( true ) );  // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_tbdMAP() throws SQLException {
+    final int value = mdrReqMAP.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                mdrReqMAP.wasNull(), equalTo( true ) );  // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_tbdSTRUCT() throws SQLException {
+    final int value = testRowSTRUCT.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowSTRUCT.wasNull(), equalTo( true ) );  // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightValue_tbdUnion() throws SQLException {
+    final int value = testRowUnion.getInt( "CHAR_OCTET_LENGTH" );
+    assertThat( "wasNull() [after " + value + "]",
+                testRowUnion.wasNull(), equalTo( true ) );  // TODO:  Confirm.
+    assertThat( value, equalTo( 0 /* NULL */ ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasSameNameAndLabel() throws SQLException {
+    assertThat( rowsMetadata.getColumnName( 16 ), equalTo( "CHAR_OCTET_LENGTH" ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightTypeString() throws SQLException {
+    assertThat( rowsMetadata.getColumnTypeName( 16 ), equalTo( "INTEGER" ) );
+  }
+
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightTypeCode() throws SQLException {
+    assertThat( rowsMetadata.getColumnType( 16 ), equalTo( Types.INTEGER ) );
+  }
+
+  @Ignore( "until fixed (\"none\" -> right class name) (DRILL-2137)" )
+  @Test
+  public void test_CHAR_OCTET_LENGTH_hasRightClass() throws SQLException {
+    // TODO:  Confirm that this "java.lang.Integer" is correct:
+    assertThat( rowsMetadata.getColumnClassN

<TRUNCATED>