Repository: googleapis/java-spanner-jdbc
Pull request #143 (2020-05-31 13:37:36)

fix: ResultSet#get(...) methods should auto convert values
Methods like `ResultSet#getString(int)` and `ResultSet#getInt(int)` would only allow data to be fetched if the underlying data type matched the method that was invoked, i.e. calling `ResultSet#getString(int)` for a column containing an `INT64` value would always fail. According to the JDBC specification it should however be allowed to call certain `get...` methods that automatically convert the underlying value to the requested type. The supported conversions will in some cases always be supported (e.g. `INT64` to `STRING`), and in some cases the method may throw an exception if the actual value cannot be converted to the requested type (e.g. `STRING` to `INT64`).

This PR adds support for all implicit conversions for `ResultSet#get...` methods that according to the JDBC specification should be allowed. This also enables the use of the JDBC driver with frameworks that rely on these automatic conversions, such as [MyBatis](https://github.com/mybatis).

codecov[bot]: # [Codecov](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143?src=pr&el=h1) Report
> Merging [#143](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143?src=pr&el=desc) into [master](https://codecov.io/gh/googleapis/java-spanner-jdbc/commit/2a7c53e5d503eefc42e7927e58430f8d24fe5b48&el=desc) will **decrease** coverage by `2.30%`.
> The diff coverage is `45.58%`.

[![Impacted file tree graph](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143/graphs/tree.svg?width=650&height=150&src=pr&token=ODlczvbGrj)](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143?src=pr&el=tree)

```diff
@@             Coverage Diff              @@
##             master     #143      +/-   ##
============================================
- Coverage     71.95%   69.65%   -2.31%     
+ Complexity     1027     1007      -20     
============================================
  Files            24       24              
  Lines          3156     3282     +126     
  Branches        463      469       +6     
============================================
+ Hits           2271     2286      +15     
- Misses          706      803      +97     
- Partials        179      193      +14     
```


| [Impacted Files](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143?src=pr&el=tree) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [...google/cloud/spanner/jdbc/AbstractJdbcWrapper.java](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143/diff?src=pr&el=tree#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvamRiYy9BYnN0cmFjdEpkYmNXcmFwcGVyLmphdmE=) | `43.63% <0.00%> (-20.37%)` | `43.00 <0.00> (ø)` | |
| [...m/google/cloud/spanner/jdbc/JdbcTypeConverter.java](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143/diff?src=pr&el=tree#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvamRiYy9KZGJjVHlwZUNvbnZlcnRlci5qYXZh) | `71.65% <0.00%> (-3.21%)` | `88.00 <0.00> (ø)` | |
| [...a/com/google/cloud/spanner/jdbc/JdbcResultSet.java](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143/diff?src=pr&el=tree#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvamRiYy9KZGJjUmVzdWx0U2V0LmphdmE=) | `66.85% <57.76%> (-15.00%)` | `109.00 <5.00> (-20.00)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143?src=pr&el=footer). Last update [2a7c53e...a724728](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/143?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..6137bef
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,8 @@
+# Code owners file.
+# This file controls who is tagged for review for any given pull request.
+
+# For syntax help see:
+# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
+
+# The java-samples-reviewers team is the default owner for samples changes
+samples/**/*.java       @googleapis/java-samples-reviewers
diff --git a/.kokoro/build.sh b/.kokoro/build.sh
index 560e019..d09a94e 100755
--- a/.kokoro/build.sh
+++ b/.kokoro/build.sh
@@ -51,9 +51,7 @@ test)
     RETURN_CODE=$?
     ;;
 lint)
-    mvn \
-      -Penable-samples \
-      com.coveo:fmt-maven-plugin:check
+    mvn com.coveo:fmt-maven-plugin:check
     RETURN_CODE=$?
     ;;
 javadoc)
diff --git a/pom.xml b/pom.xml
index d833ecd..88330df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -254,7 +254,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-shade-plugin</artifactId>
-        <version>3.2.1</version>
+        <version>3.2.4</version>
         <executions>
           <execution>
             <goals>
@@ -368,7 +368,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-project-info-reports-plugin</artifactId>
-        <version>3.0.0</version>
+        <version>3.1.0</version>
         <reportSets>
           <reportSet>
             <reports>
diff --git a/renovate.json b/renovate.json
index 2b2b265..0fd3e3f 100644
--- a/renovate.json
+++ b/renovate.json
@@ -76,5 +76,6 @@
       "groupName": "jackson dependencies"
     }
   ],
-  "semanticCommits": true
+  "semanticCommits": true,
+  "masterIssue": true
 }
\ No newline at end of file
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java
index abfa0f5..aef5638 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java
@@ -22,9 +22,11 @@ import com.google.common.base.Preconditions;
 import java.sql.Date;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Time;
 import java.sql.Timestamp;
 import java.sql.Types;
 import java.sql.Wrapper;
+import java.util.Calendar;
 
 /** Base class for all Cloud Spanner JDBC classes that implement the {@link Wrapper} interface. */
 abstract class AbstractJdbcWrapper implements Wrapper {
@@ -147,6 +149,126 @@ abstract class AbstractJdbcWrapper implements Wrapper {
     return (float) val;
   }
 
+  /**
+   * Parses the given string value as a long. Throws {@link SQLException} if the string is not a
+   * valid long value.
+   */
+  static long parseLong(String val) throws SQLException {
+    Preconditions.checkNotNull(val);
+    try {
+      return Long.valueOf(val);
+    } catch (NumberFormatException e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
+    }
+  }
+
+  /**
+   * Parses the given string value as a double. Throws {@link SQLException} if the string is not a
+   * valid double value.
+   */
+  static double parseDouble(String val) throws SQLException {
+    Preconditions.checkNotNull(val);
+    try {
+      return Double.valueOf(val);
+    } catch (NumberFormatException e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
+    }
+  }
+
+  /**
+   * Parses the given string value as a {@link Date} value. Throws {@link SQLException} if the
+   * string is not a valid {@link Date} value.
+   */
+  static Date parseDate(String val) throws SQLException {
+    Preconditions.checkNotNull(val);
+    try {
+      return JdbcTypeConverter.toSqlDate(com.google.cloud.Date.parseDate(val));
+    } catch (IllegalArgumentException e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid date", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
+    }
+  }
+
+  /**
+   * Parses the given string value as a {@link Date} value in the timezone of the given {@link
+   * Calendar}. Throws {@link SQLException} if the string is not a valid {@link Date} value.
+   */
+  static Date parseDate(String val, Calendar cal) throws SQLException {
+    Preconditions.checkNotNull(val);
+    Preconditions.checkNotNull(cal);
+    try {
+      return JdbcTypeConverter.toSqlDate(com.google.cloud.Date.parseDate(val), cal);
+    } catch (IllegalArgumentException e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid date", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
+    }
+  }
+
+  /**
+   * Parses the given string value as a {@link Time} value. Throws {@link SQLException} if the
+   * string is not a valid {@link Time} value.
+   */
+  static Time parseTime(String val) throws SQLException {
+    Preconditions.checkNotNull(val);
+    try {
+      return Time.valueOf(val);
+    } catch (IllegalArgumentException e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid time", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
+    }
+  }
+
+  /**
+   * Parses the given string value as a {@link Time} value in the timezone of the given {@link
+   * Calendar}. Throws {@link SQLException} if the string is not a valid {@link Time} value.
+   */
+  static Time parseTime(String val, Calendar cal) throws SQLException {
+    Preconditions.checkNotNull(val);
+    Preconditions.checkNotNull(cal);
+    try {
+      return JdbcTypeConverter.parseSqlTime(val, cal);
+    } catch (IllegalArgumentException e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid time", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
+    }
+  }
+
+  /**
+   * Parses the given string value as a {@link Timestamp} value. Throws {@link SQLException} if the
+   * string is not a valid {@link Timestamp} value.
+   */
+  static Timestamp parseTimestamp(String val) throws SQLException {
+    Preconditions.checkNotNull(val);
+    try {
+      return JdbcTypeConverter.toSqlTimestamp(com.google.cloud.Timestamp.parseTimestamp(val));
+    } catch (Exception e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid timestamp", val),
+          com.google.rpc.Code.INVALID_ARGUMENT,
+          e);
+    }
+  }
+
+  /**
+   * Parses the given string value as a {@link Timestamp} value in the timezone of the given {@link
+   * Calendar}. Throws {@link SQLException} if the string is not a valid {@link Timestamp} value.
+   */
+  static Timestamp parseTimestamp(String val, Calendar cal) throws SQLException {
+    Preconditions.checkNotNull(val);
+    Preconditions.checkNotNull(cal);
+    try {
+      return JdbcTypeConverter.setTimestampInCalendar(
+          com.google.cloud.Timestamp.parseTimestamp(val).toSqlTimestamp(), cal);
+    } catch (Exception e) {
+      throw JdbcSqlExceptionFactory.of(
+          String.format("%s is not a valid timestamp", val),
+          com.google.rpc.Code.INVALID_ARGUMENT,
+          e);
+    }
+  }
+
   /** Should return true if this object has been closed */
   public abstract boolean isClosed() throws SQLException;
 
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java
index e953255..2e4014b 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java
@@ -115,61 +115,219 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     return wasNull;
   }
 
-  private boolean isNull(String columnName) {
-    wasNull = spanner.isNull(columnName);
-    return wasNull;
+  SQLException createInvalidToGetAs(String sqlType, Code type) {
+    return JdbcSqlExceptionFactory.of(
+        String.format("Invalid column type to get as %s: %s", sqlType, type.name()),
+        com.google.rpc.Code.INVALID_ARGUMENT);
   }
 
   @Override
   public String getString(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex) ? null : spanner.getString(columnIndex - 1);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? null : String.valueOf(spanner.getBoolean(spannerIndex));
+      case BYTES:
+        return isNull ? null : spanner.getBytes(spannerIndex).toBase64();
+      case DATE:
+        return isNull ? null : spanner.getDate(spannerIndex).toString();
+      case FLOAT64:
+        return isNull ? null : Double.toString(spanner.getDouble(spannerIndex));
+      case INT64:
+        return isNull ? null : Long.toString(spanner.getLong(spannerIndex));
+      case STRING:
+        return isNull ? null : spanner.getString(spannerIndex);
+      case TIMESTAMP:
+        return isNull ? null : spanner.getTimestamp(spannerIndex).toString();
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("string", type);
+    }
   }
 
   @Override
   public boolean getBoolean(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex) ? false : spanner.getBoolean(columnIndex - 1);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? false : spanner.getBoolean(spannerIndex);
+      case FLOAT64:
+        return isNull ? false : spanner.getDouble(spannerIndex) != 0D;
+      case INT64:
+        return isNull ? false : spanner.getLong(spannerIndex) != 0L;
+      case STRING:
+        return isNull ? false : Boolean.valueOf(spanner.getString(spannerIndex));
+      case BYTES:
+      case DATE:
+      case STRUCT:
+      case TIMESTAMP:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("boolean", type);
+    }
   }
 
   @Override
   public byte getByte(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    long val = isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1);
-    return checkedCastToByte(val);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? (byte) 0 : (spanner.getBoolean(spannerIndex) ? (byte) 1 : 0);
+      case FLOAT64:
+        return isNull
+            ? (byte) 0
+            : checkedCastToByte(Double.valueOf(spanner.getDouble(spannerIndex)).longValue());
+      case INT64:
+        return isNull ? (byte) 0 : checkedCastToByte(spanner.getLong(spannerIndex));
+      case STRING:
+        return isNull ? (byte) 0 : checkedCastToByte(parseLong(spanner.getString(spannerIndex)));
+      case BYTES:
+      case DATE:
+      case STRUCT:
+      case TIMESTAMP:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("byte", type);
+    }
   }
 
   @Override
   public short getShort(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    long val = isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1);
-    return checkedCastToShort(val);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (short) 1 : 0);
+      case FLOAT64:
+        return isNull
+            ? 0
+            : checkedCastToShort(Double.valueOf(spanner.getDouble(spannerIndex)).longValue());
+      case INT64:
+        return isNull ? 0 : checkedCastToShort(spanner.getLong(spannerIndex));
+      case STRING:
+        return isNull ? 0 : checkedCastToShort(parseLong(spanner.getString(spannerIndex)));
+      case BYTES:
+      case DATE:
+      case STRUCT:
+      case TIMESTAMP:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("short", type);
+    }
   }
 
   @Override
   public int getInt(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    long val = isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1);
-    return checkedCastToInt(val);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? 1 : 0);
+      case FLOAT64:
+        return isNull
+            ? 0
+            : checkedCastToInt(Double.valueOf(spanner.getDouble(spannerIndex)).longValue());
+      case INT64:
+        return isNull ? 0 : checkedCastToInt(spanner.getLong(spannerIndex));
+      case STRING:
+        return isNull ? 0 : checkedCastToInt(parseLong(spanner.getString(spannerIndex)));
+      case BYTES:
+      case DATE:
+      case STRUCT:
+      case TIMESTAMP:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("int", type);
+    }
   }
 
   @Override
   public long getLong(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex) ? 0L : spanner.getLong(columnIndex - 1);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? 0L : (spanner.getBoolean(spannerIndex) ? 1L : 0L);
+      case FLOAT64:
+        return isNull ? 0L : Double.valueOf(spanner.getDouble(spannerIndex)).longValue();
+      case INT64:
+        return isNull ? 0L : spanner.getLong(spannerIndex);
+      case STRING:
+        return isNull ? 0L : parseLong(spanner.getString(spannerIndex));
+      case BYTES:
+      case DATE:
+      case STRUCT:
+      case TIMESTAMP:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("long", type);
+    }
   }
 
   @Override
   public float getFloat(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    double val = isNull(columnIndex) ? 0D : spanner.getDouble(columnIndex - 1);
-    return checkedCastToFloat(val);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (float) 1 : 0);
+      case FLOAT64:
+        return isNull ? 0 : checkedCastToFloat(spanner.getDouble(spannerIndex));
+      case INT64:
+        return isNull ? 0 : checkedCastToFloat(spanner.getLong(spannerIndex));
+      case STRING:
+        return isNull ? 0 : checkedCastToFloat(parseDouble(spanner.getString(spannerIndex)));
+      case BYTES:
+      case DATE:
+      case STRUCT:
+      case TIMESTAMP:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("float", type);
+    }
   }
 
   @Override
   public double getDouble(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex) ? 0D : spanner.getDouble(columnIndex - 1);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case BOOL:
+        return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (double) 1 : 0);
+      case FLOAT64:
+        return isNull ? 0 : spanner.getDouble(spannerIndex);
+      case INT64:
+        return isNull ? 0 : spanner.getLong(spannerIndex);
+      case STRING:
+        return isNull ? 0 : parseDouble(spanner.getString(spannerIndex));
+      case BYTES:
+      case DATE:
+      case STRUCT:
+      case TIMESTAMP:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("double", type);
+    }
   }
 
   @Override
@@ -181,25 +339,74 @@ class JdbcResultSet extends AbstractJdbcResultSet {
   @Override
   public Date getDate(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex)
-        ? null
-        : JdbcTypeConverter.toSqlDate(spanner.getDate(columnIndex - 1));
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case DATE:
+        return isNull ? null : JdbcTypeConverter.toSqlDate(spanner.getDate(spannerIndex));
+      case STRING:
+        return isNull ? null : parseDate(spanner.getString(spannerIndex));
+      case TIMESTAMP:
+        return isNull
+            ? null
+            : new Date(spanner.getTimestamp(spannerIndex).toSqlTimestamp().getTime());
+      case BOOL:
+      case FLOAT64:
+      case INT64:
+      case BYTES:
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("date", type);
+    }
   }
 
   @Override
   public Time getTime(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex)
-        ? null
-        : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnIndex - 1));
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case STRING:
+        return isNull ? null : parseTime(spanner.getString(spannerIndex));
+      case TIMESTAMP:
+        return isNull ? null : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(spannerIndex));
+      case BOOL:
+      case DATE:
+      case FLOAT64:
+      case INT64:
+      case BYTES:
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("time", type);
+    }
   }
 
   @Override
   public Timestamp getTimestamp(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex)
-        ? null
-        : JdbcTypeConverter.toSqlTimestamp(spanner.getTimestamp(columnIndex - 1));
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case DATE:
+        return isNull ? null : JdbcTypeConverter.toSqlTimestamp(spanner.getDate(spannerIndex));
+      case STRING:
+        return isNull ? null : parseTimestamp(spanner.getString(spannerIndex));
+      case TIMESTAMP:
+        return isNull ? null : JdbcTypeConverter.toSqlTimestamp(spanner.getTimestamp(spannerIndex));
+      case BOOL:
+      case FLOAT64:
+      case INT64:
+      case BYTES:
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("timestamp", type);
+    }
   }
 
   private InputStream getInputStream(String val, Charset charset) {
@@ -230,102 +437,92 @@ class JdbcResultSet extends AbstractJdbcResultSet {
 
   @Override
   public String getString(String columnLabel) throws SQLException {
-    checkClosedAndValidRow();
-    return isNull(columnLabel) ? null : spanner.getString(columnLabel);
+    return getString(findColumn(columnLabel));
   }
 
   @Override
   public boolean getBoolean(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnLabel) ? false : spanner.getBoolean(columnLabel);
+    return getBoolean(findColumn(columnLabel));
   }
 
   @Override
   public byte getByte(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    long val = isNull(columnLabel) ? 0L : spanner.getLong(columnLabel);
-    return checkedCastToByte(val);
+    return getByte(findColumn(columnLabel));
   }
 
   @Override
   public short getShort(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    long val = isNull(columnLabel) ? 0L : spanner.getLong(columnLabel);
-    return checkedCastToShort(val);
+    return getShort(findColumn(columnLabel));
   }
 
   @Override
   public int getInt(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    long val = isNull(columnLabel) ? 0L : spanner.getLong(columnLabel);
-    return checkedCastToInt(val);
+    return getInt(findColumn(columnLabel));
   }
 
   @Override
   public long getLong(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnLabel) ? 0L : spanner.getLong(columnLabel);
+    return getLong(findColumn(columnLabel));
   }
 
   @Override
   public float getFloat(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    double val = isNull(columnLabel) ? 0D : spanner.getDouble(columnLabel);
-    return checkedCastToFloat(val);
+    return getFloat(findColumn(columnLabel));
   }
 
   @Override
   public double getDouble(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnLabel) ? 0D : spanner.getDouble(columnLabel);
+    return getDouble(findColumn(columnLabel));
   }
 
   @Override
   public byte[] getBytes(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnLabel) ? null : spanner.getBytes(columnLabel).toByteArray();
+    return getBytes(findColumn(columnLabel));
   }
 
   @Override
   public Date getDate(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnLabel) ? null : JdbcTypeConverter.toSqlDate(spanner.getDate(columnLabel));
+    return getDate(findColumn(columnLabel));
   }
 
   @Override
   public Time getTime(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnLabel)
-        ? null
-        : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnLabel));
+    return getTime(findColumn(columnLabel));
   }
 
   @Override
   public Timestamp getTimestamp(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnLabel)
-        ? null
-        : JdbcTypeConverter.toSqlTimestamp(spanner.getTimestamp(columnLabel));
+    return getTimestamp(findColumn(columnLabel));
   }
 
   @Override
   public InputStream getAsciiStream(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return getInputStream(getString(columnLabel), StandardCharsets.US_ASCII);
+    return getAsciiStream(findColumn(columnLabel));
   }
 
   @Override
   @Deprecated
   public InputStream getUnicodeStream(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return getInputStream(getString(columnLabel), StandardCharsets.UTF_16LE);
+    return getUnicodeStream(findColumn(columnLabel));
   }
 
   @Override
   public InputStream getBinaryStream(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    byte[] val = getBytes(columnLabel);
-    return val == null ? null : new ByteArrayInputStream(val);
+    return getBinaryStream(findColumn(columnLabel));
   }
 
   @Override
@@ -342,8 +539,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
   @Override
   public Object getObject(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    Type type = spanner.getColumnType(columnLabel);
-    return isNull(columnLabel) ? null : getObject(type, columnLabel);
+    return getObject(findColumn(columnLabel));
   }
 
   @Override
@@ -353,10 +549,6 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     return isNull(columnIndex) ? null : getObject(type, columnIndex);
   }
 
-  private Object getObject(Type type, String columnLabel) throws SQLException {
-    return getObject(type, spanner.getColumnIndex(columnLabel) + 1);
-  }
-
   private Object getObject(Type type, int columnIndex) throws SQLException {
     if (type == Type.bool()) return getBoolean(columnIndex);
     if (type == Type.bytes()) return getBytes(columnIndex);
@@ -390,9 +582,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
 
   @Override
   public Reader getCharacterStream(String columnLabel) throws SQLException {
-    checkClosedAndValidRow();
-    String val = getString(columnLabel);
-    return val == null ? null : new StringReader(val);
+    return getCharacterStream(findColumn(columnLabel));
   }
 
   @Override
@@ -404,7 +594,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
   @Override
   public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
     checkClosedAndValidRow();
-    return getBigDecimal(spanner.getColumnIndex(columnLabel) + 1, false, 0);
+    return getBigDecimal(findColumn(columnLabel), false, 0);
   }
 
   @Override
@@ -418,36 +608,48 @@ class JdbcResultSet extends AbstractJdbcResultSet {
   @Deprecated
   public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
     checkClosedAndValidRow();
-    return getBigDecimal(spanner.getColumnIndex(columnLabel) + 1, true, scale);
+    return getBigDecimal(findColumn(columnLabel), true, scale);
   }
 
   private BigDecimal getBigDecimal(int columnIndex, boolean fixedScale, int scale)
       throws SQLException {
-    Type type = spanner.getColumnType(columnIndex - 1);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    boolean isNull = isNull(columnIndex);
     BigDecimal res;
-    if (isNull(columnIndex)) {
-      res = null;
-    } else {
-      if (type.getCode() == Code.STRING) {
+    switch (type) {
+      case BOOL:
+        res =
+            isNull
+                ? null
+                : (spanner.getBoolean(columnIndex - 1) ? BigDecimal.ONE : BigDecimal.ZERO);
+        break;
+      case FLOAT64:
+        res = isNull ? null : BigDecimal.valueOf(spanner.getDouble(spannerIndex));
+        break;
+      case INT64:
+        res = isNull ? null : BigDecimal.valueOf(spanner.getLong(spannerIndex));
+        break;
+      case STRING:
         try {
-          res = new BigDecimal(spanner.getString(columnIndex - 1));
+          res = isNull ? null : new BigDecimal(spanner.getString(spannerIndex));
+          break;
         } catch (NumberFormatException e) {
           throw JdbcSqlExceptionFactory.of(
               "The column does not contain a valid BigDecimal",
               com.google.rpc.Code.INVALID_ARGUMENT,
               e);
         }
-      } else if (type.getCode() == Code.INT64) {
-        res = BigDecimal.valueOf(spanner.getLong(columnIndex - 1));
-      } else if (type.getCode() == Code.FLOAT64) {
-        res = BigDecimal.valueOf(spanner.getDouble(columnIndex - 1));
-      } else {
-        throw JdbcSqlExceptionFactory.of(
-            "The column does not contain a valid BigDecimal", com.google.rpc.Code.INVALID_ARGUMENT);
-      }
-      if (fixedScale) {
-        res = res.setScale(scale, RoundingMode.HALF_UP);
-      }
+      case BYTES:
+      case DATE:
+      case TIMESTAMP:
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("BigDecimal", type);
+    }
+    if (res != null && fixedScale) {
+      res = res.setScale(scale, RoundingMode.HALF_UP);
     }
     return res;
   }
@@ -506,56 +708,99 @@ class JdbcResultSet extends AbstractJdbcResultSet {
   @Override
   public Date getDate(int columnIndex, Calendar cal) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex)
-        ? null
-        : JdbcTypeConverter.toSqlDate(spanner.getDate(columnIndex - 1), cal);
+    if (isNull(columnIndex)) {
+      return null;
+    }
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case DATE:
+        return JdbcTypeConverter.toSqlDate(spanner.getDate(spannerIndex), cal);
+      case STRING:
+        return parseDate(spanner.getString(spannerIndex), cal);
+      case TIMESTAMP:
+        return new Date(
+            JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(spannerIndex), cal).getTime());
+      case BOOL:
+      case FLOAT64:
+      case INT64:
+      case BYTES:
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("date", type);
+    }
   }
 
   @Override
   public Date getDate(String columnLabel, Calendar cal) throws SQLException {
-    checkClosedAndValidRow();
-    return isNull(columnLabel)
-        ? null
-        : JdbcTypeConverter.toSqlDate(spanner.getDate(columnLabel), cal);
+    return getDate(findColumn(columnLabel), cal);
   }
 
   @Override
   public Time getTime(int columnIndex, Calendar cal) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex)
-        ? null
-        : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnIndex - 1), cal);
+    boolean isNull = isNull(columnIndex);
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case STRING:
+        return isNull ? null : parseTime(spanner.getString(spannerIndex), cal);
+      case TIMESTAMP:
+        return isNull ? null : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(spannerIndex), cal);
+      case BOOL:
+      case DATE:
+      case FLOAT64:
+      case INT64:
+      case BYTES:
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("time", type);
+    }
   }
 
   @Override
   public Time getTime(String columnLabel, Calendar cal) throws SQLException {
-    checkClosedAndValidRow();
-    return isNull(columnLabel)
-        ? null
-        : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(columnLabel), cal);
+    return getTime(findColumn(columnLabel), cal);
   }
 
   @Override
   public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
     checkClosedAndValidRow();
-    return isNull(columnIndex)
-        ? null
-        : JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(columnIndex - 1), cal);
+    if (isNull(columnIndex)) {
+      return null;
+    }
+    int spannerIndex = columnIndex - 1;
+    Code type = spanner.getColumnType(spannerIndex).getCode();
+    switch (type) {
+      case DATE:
+        return JdbcTypeConverter.toSqlTimestamp(spanner.getDate(spannerIndex), cal);
+      case STRING:
+        return parseTimestamp(spanner.getString(spannerIndex), cal);
+      case TIMESTAMP:
+        return JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(spannerIndex), cal);
+      case BOOL:
+      case FLOAT64:
+      case INT64:
+      case BYTES:
+      case STRUCT:
+      case ARRAY:
+      default:
+        throw createInvalidToGetAs("timestamp", type);
+    }
   }
 
   @Override
   public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
-    checkClosedAndValidRow();
-    return isNull(columnLabel)
-        ? null
-        : JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(columnLabel), cal);
+    return getTimestamp(findColumn(columnLabel), cal);
   }
 
   @Override
   public URL getURL(int columnIndex) throws SQLException {
     checkClosedAndValidRow();
     try {
-      return isNull(columnIndex) ? null : new URL(spanner.getString(columnIndex - 1));
+      return isNull(columnIndex) ? null : new URL(getString(columnIndex));
     } catch (MalformedURLException e) {
       throw JdbcSqlExceptionFactory.of(
           "Invalid URL: " + spanner.getString(columnIndex - 1),
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java
index a11cb5e..e922e58 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java
@@ -224,6 +224,14 @@ class JdbcTypeConverter {
     return ts == null ? null : ts.toSqlTimestamp();
   }
 
+  static java.sql.Timestamp toSqlTimestamp(Date date) {
+    return date == null ? null : new java.sql.Timestamp(toSqlDate(date).getTime());
+  }
+
+  static java.sql.Timestamp toSqlTimestamp(Date date, Calendar cal) {
+    return date == null ? null : new java.sql.Timestamp(toSqlDate(date, cal).getTime());
+  }
+
   static java.sql.Timestamp getAsSqlTimestamp(Timestamp ts, Calendar cal) {
     return ts == null ? null : getTimestampInCalendar(ts.toSqlTimestamp(), cal);
   }
@@ -334,6 +342,17 @@ class JdbcTypeConverter {
     return null;
   }
 
+  @SuppressWarnings("deprecation")
+  static java.sql.Time parseSqlTime(String val, Calendar cal) {
+    if (val != null) {
+      Time time = Time.valueOf(val);
+      cal.set(1970, 0, 1, time.getHours(), time.getMinutes(), time.getSeconds());
+      cal.clear(Calendar.MILLISECOND);
+      return new java.sql.Time(cal.getTimeInMillis());
+    }
+    return null;
+  }
+
   static List<ByteArray> toGoogleBytes(byte[][] bytes) {
     List<ByteArray> res = new ArrayList<>(bytes.length);
     for (int index = 0; index < bytes.length; index++) {
diff --git a/synth.metadata b/synth.metadata
index 0faa79c..367337e 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,14 +4,14 @@
       "git": {
         "name": ".",
         "remote": "https://github.com/googleapis/java-spanner-jdbc.git",
-        "sha": "839e4dcc0ab9b1044cd0534195a44c37c2e37f14"
+        "sha": "bc476ac3505b1793f62d64026c2537ecbf227e2e"
       }
     },
     {
       "git": {
         "name": "synthtool",
         "remote": "https://github.com/googleapis/synthtool.git",
-        "sha": "5b48b0716a36ca069db3038da7e205c87a22ed19"
+        "sha": "987270824bd26f6a8c716d5e2022057b8ae7b26e"
       }
     }
   ]


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java b/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java
index 1a391df..593466f 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapperTest.java
@@ -17,8 +17,15 @@
 package com.google.cloud.spanner.jdbc;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
 
+import com.google.cloud.Timestamp;
+import com.google.rpc.Code;
+import java.sql.Date;
 import java.sql.SQLException;
+import java.sql.Time;
+import java.util.Calendar;
+import java.util.TimeZone;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -176,4 +183,131 @@ public class AbstractJdbcWrapperTest {
       return false;
     }
   }
+
+  @Test
+  public void testParseDouble() throws SQLException {
+    assertThat(AbstractJdbcWrapper.parseDouble("3.14")).isEqualTo(3.14D);
+    try {
+      AbstractJdbcWrapper.parseDouble("not a number");
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertThat((Exception) e).isInstanceOf(JdbcSqlException.class);
+      assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @SuppressWarnings("deprecation")
+  @Test
+  public void testParseDate() throws SQLException {
+    assertThat(AbstractJdbcWrapper.parseDate("2020-06-01")).isEqualTo(new Date(2020 - 1900, 5, 1));
+    try {
+      AbstractJdbcWrapper.parseDate("01-06-2020");
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertThat((Exception) e).isInstanceOf(JdbcSqlException.class);
+      assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @Test
+  public void testParseDateWithCalendar() throws SQLException {
+    assertThat(
+            AbstractJdbcWrapper.parseDate(
+                "2020-06-01", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"))))
+        .isEqualTo(
+            new Date(
+                Timestamp.parseTimestamp("2020-06-01T00:00:00-07:00").toSqlTimestamp().getTime()));
+    assertThat(
+            AbstractJdbcWrapper.parseDate(
+                "2020-06-01", Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam"))))
+        .isEqualTo(
+            new Date(
+                Timestamp.parseTimestamp("2020-06-01T00:00:00+02:00").toSqlTimestamp().getTime()));
+    try {
+      AbstractJdbcWrapper.parseDate("01-06-2020", Calendar.getInstance());
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertThat((Exception) e).isInstanceOf(JdbcSqlException.class);
+      assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @SuppressWarnings("deprecation")
+  @Test
+  public void testParseTime() throws SQLException {
+    assertThat(AbstractJdbcWrapper.parseTime("10:31:05")).isEqualTo(new Time(10, 31, 5));
+    try {
+      AbstractJdbcWrapper.parseTime("10.31.05");
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertThat((Exception) e).isInstanceOf(JdbcSqlException.class);
+      assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @Test
+  public void testParseTimeWithCalendar() throws SQLException {
+    assertThat(
+            AbstractJdbcWrapper.parseTime(
+                "10:31:05", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"))))
+        .isEqualTo(
+            new Time(
+                Timestamp.parseTimestamp("1970-01-01T10:31:05-08:00").toSqlTimestamp().getTime()));
+    assertThat(
+            AbstractJdbcWrapper.parseTime(
+                "10:31:05", Calendar.getInstance(TimeZone.getTimeZone("Pacific/Auckland"))))
+        .isEqualTo(
+            new Time(
+                Timestamp.parseTimestamp("1970-01-01T10:31:05+12:00").toSqlTimestamp().getTime()));
+    try {
+      AbstractJdbcWrapper.parseTime("10.31.05", Calendar.getInstance());
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertThat((Exception) e).isInstanceOf(JdbcSqlException.class);
+      assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @Test
+  public void testParseTimestamp() throws SQLException {
+    assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31:05Z"))
+        .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:05Z").toSqlTimestamp());
+    assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31:05.123Z"))
+        .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:05.123Z").toSqlTimestamp());
+    assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31Z"))
+        .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:00Z").toSqlTimestamp());
+    assertThat(AbstractJdbcWrapper.parseTimestamp("2020-06-01T10:31"))
+        .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:00Z").toSqlTimestamp());
+    assertThat(AbstractJdbcWrapper.parseTimestamp("1970-01-01T00:00:00Z"))
+        .isEqualTo(Timestamp.ofTimeMicroseconds(0L).toSqlTimestamp());
+    try {
+      AbstractJdbcWrapper.parseTimestamp("2020-06-01T10");
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertThat((Exception) e).isInstanceOf(JdbcSqlException.class);
+      assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @Test
+  public void testParseTimestampWithCalendar() throws SQLException {
+    assertThat(
+            AbstractJdbcWrapper.parseTimestamp(
+                "2020-02-01T10:31:05Z",
+                Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"))))
+        .isEqualTo(Timestamp.parseTimestamp("2020-02-01T10:31:05-08:00").toSqlTimestamp());
+    assertThat(
+            AbstractJdbcWrapper.parseTimestamp(
+                "2020-06-01T10:31:05Z",
+                Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam"))))
+        .isEqualTo(Timestamp.parseTimestamp("2020-06-01T10:31:05+02:00").toSqlTimestamp());
+    try {
+      AbstractJdbcWrapper.parseTimestamp(
+          "2020-06-01T10", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")));
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertThat((Exception) e).isInstanceOf(JdbcSqlException.class);
+      assertThat(((JdbcSqlException) e).getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+    }
+  }
 }
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java
index 4167213..1f88675 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java
@@ -29,6 +29,7 @@ import com.google.cloud.spanner.connection.SpannerPool;
 import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl;
 import com.google.protobuf.ListValue;
 import com.google.protobuf.Value;
+import com.google.rpc.Code;
 import com.google.spanner.v1.ResultSetMetadata;
 import com.google.spanner.v1.StructType;
 import com.google.spanner.v1.StructType.Field;
@@ -36,7 +37,6 @@ import com.google.spanner.v1.Type;
 import com.google.spanner.v1.TypeCode;
 import io.grpc.Server;
 import io.grpc.Status;
-import io.grpc.Status.Code;
 import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
 import java.io.IOException;
 import java.net.InetSocketAddress;
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java
index 44eefa9..9429288 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java
@@ -35,7 +35,7 @@ import com.google.cloud.spanner.Struct;
 import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.Type.StructField;
 import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlExceptionImpl;
-import io.grpc.Status.Code;
+import com.google.rpc.Code;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
@@ -112,6 +112,19 @@ public class JdbcResultSetTest {
   private static final int URL_COLINDEX_NULL = 19;
   private static final int URL_COLINDEX_NOTNULL = 20;
 
+  private static final String STRING_COL_NUMBER = "STRING_COL_NUMBER";
+  private static final int STRING_COLINDEX_NUMBER = 21;
+  private static final String STRING_NUMBER_VALUE = "123";
+  private static final String STRING_COL_DATE = "STRING_COL_DATE";
+  private static final int STRING_COLINDEX_DATE = 22;
+  private static final String STRING_DATE_VALUE = "2020-06-01";
+  private static final String STRING_COL_TIMESTAMP = "STRING_COL_TIMESTAMP";
+  private static final int STRING_COLINDEX_TIMESTAMP = 23;
+  private static final String STRING_TIMESTAMP_VALUE = "2020-06-01T10:31:15.123Z";
+  private static final String STRING_COL_TIME = "STRING_COL_TIME";
+  private static final int STRING_COLINDEX_TIME = 24;
+  private static final String STRING_TIME_VALUE = "10:31:15";
+
   private JdbcResultSet subject;
 
   static ResultSet getMockResultSet() {
@@ -136,7 +149,11 @@ public class JdbcResultSetTest {
             StructField.of(ARRAY_COL_NULL, Type.array(Type.int64())),
             StructField.of(ARRAY_COL_NOT_NULL, Type.array(Type.int64())),
             StructField.of(URL_COL_NULL, Type.string()),
-            StructField.of(URL_COL_NOT_NULL, Type.string())),
+            StructField.of(URL_COL_NOT_NULL, Type.string()),
+            StructField.of(STRING_COL_NUMBER, Type.string()),
+            StructField.of(STRING_COL_DATE, Type.string()),
+            StructField.of(STRING_COL_TIMESTAMP, Type.string()),
+            StructField.of(STRING_COL_TIME, Type.string())),
         Arrays.asList(
             Struct.newBuilder()
                 .set(STRING_COL_NULL)
@@ -179,6 +196,14 @@ public class JdbcResultSetTest {
                 .to((String) null)
                 .set(URL_COL_NOT_NULL)
                 .to(URL_VALUE)
+                .set(STRING_COL_NUMBER)
+                .to(STRING_NUMBER_VALUE)
+                .set(STRING_COL_DATE)
+                .to(STRING_DATE_VALUE)
+                .set(STRING_COL_TIMESTAMP)
+                .to(STRING_TIMESTAMP_VALUE)
+                .set(STRING_COL_TIME)
+                .to(STRING_TIME_VALUE)
                 .build()));
   }
 
@@ -242,6 +267,84 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetStringIndexForBool() throws SQLException {
+    assertNotNull(subject.getString(BOOLEAN_COLINDEX_NOTNULL));
+    assertEquals(String.valueOf(BOOLEAN_VALUE), subject.getString(BOOLEAN_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getString(BOOLEAN_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetStringIndexForInt64() throws SQLException {
+    assertNotNull(subject.getString(LONG_COLINDEX_NOTNULL));
+    assertEquals(String.valueOf(LONG_VALUE), subject.getString(LONG_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getString(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetStringIndexForFloat64() throws SQLException {
+    assertNotNull(subject.getString(DOUBLE_COLINDEX_NOTNULL));
+    assertEquals(String.valueOf(DOUBLE_VALUE), subject.getString(DOUBLE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getString(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetStringIndexForBytes() throws SQLException {
+    assertNotNull(subject.getString(BYTES_COLINDEX_NOTNULL));
+    assertEquals(BYTES_VALUE.toBase64(), subject.getString(BYTES_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getString(BYTES_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetStringIndexForDate() throws SQLException {
+    assertNotNull(subject.getString(DATE_COLINDEX_NOTNULL));
+    assertEquals(String.valueOf(DATE_VALUE), subject.getString(DATE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getString(DATE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetStringIndexForTimestamp() throws SQLException {
+    assertNotNull(subject.getString(TIMESTAMP_COLINDEX_NOTNULL));
+    assertEquals(String.valueOf(TIMESTAMP_VALUE), subject.getString(TIMESTAMP_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getString(TIMESTAMP_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetStringIndexForArray() throws SQLException {
+    try {
+      subject.getString(ARRAY_COLINDEX_NOTNULL);
+      fail("missing SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      JdbcSqlException jse = (JdbcSqlException) e;
+      assertEquals(jse.getCode(), Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @Test
+  public void testGetStringIndexForNullArray() throws SQLException {
+    try {
+      subject.getString(ARRAY_COLINDEX_NULL);
+      fail("missing SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      JdbcSqlException jse = (JdbcSqlException) e;
+      assertEquals(jse.getCode(), Code.INVALID_ARGUMENT);
+    }
+  }
+
   @Test
   public void testGetNStringIndex() throws SQLException {
     assertNotNull(subject.getNString(STRING_COLINDEX_NOTNULL));
@@ -284,15 +387,120 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetBooleanIndexForLong() throws SQLException {
+    assertNotNull(subject.getBoolean(LONG_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertFalse(subject.getBoolean(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetBooleanIndexForDouble() throws SQLException {
+    assertNotNull(subject.getBoolean(DOUBLE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertFalse(subject.getBoolean(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetBooleanIndexForString() throws SQLException {
+    assertNotNull(subject.getBoolean(STRING_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertFalse(subject.getBoolean(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetBooleanIndexForDate() throws SQLException {
+    try {
+      subject.getBoolean(DATE_COLINDEX_NOTNULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+    try {
+      subject.getBoolean(DATE_COLINDEX_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+  }
+
+  @Test
+  public void testGetNullBooleanIndex() throws SQLException {
+    assertFalse(subject.getBoolean(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertFalse(subject.getBoolean(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertFalse(subject.getBoolean(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
   @Test
   public void testGetLongIndex() throws SQLException {
     assertNotNull(subject.getLong(LONG_COLINDEX_NOTNULL));
     assertEquals(LONG_VALUE, subject.getLong(LONG_COLINDEX_NOTNULL));
     assertFalse(subject.wasNull());
-    assertEquals(0l, subject.getLong(LONG_COLINDEX_NULL));
+    assertEquals(0L, subject.getLong(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetLongIndexForBool() throws SQLException {
+    assertNotNull(subject.getLong(BOOLEAN_COLINDEX_NOTNULL));
+    assertEquals(BOOLEAN_VALUE ? 1L : 0L, subject.getLong(BOOLEAN_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertEquals(0L, subject.getLong(BOOLEAN_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetLongIndexForFloat64() throws SQLException {
+    assertNotNull(subject.getLong(DOUBLE_COLINDEX_NOTNULL));
+    assertEquals((long) DOUBLE_VALUE, subject.getLong(DOUBLE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertEquals(0L, subject.getLong(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetLongIndexForString() throws SQLException {
+    try {
+      subject.getLong(STRING_COLINDEX_NOTNULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
+    }
+  }
+
+  @Test
+  public void testGetLongIndexForNumberString() throws SQLException {
+    assertEquals(
+        Long.valueOf(STRING_NUMBER_VALUE).longValue(), subject.getLong(STRING_COLINDEX_NUMBER));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetLongIndexForNullString() throws SQLException {
+    assertEquals(0L, subject.getLong(STRING_COLINDEX_NULL));
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetLongIndexForTimestamp() throws SQLException {
+    try {
+      subject.getLong(TIMESTAMP_COLINDEX_NOTNULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
+    }
+  }
+
   @Test
   public void testGetDoubleIndex() throws SQLException {
     assertNotNull(subject.getDouble(DOUBLE_COLINDEX_NOTNULL));
@@ -302,6 +510,47 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetDoubleIndexFromString() throws SQLException {
+    assertNotNull(subject.getDouble(STRING_COLINDEX_NUMBER));
+    assertEquals(
+        Double.valueOf(STRING_NUMBER_VALUE).doubleValue(),
+        subject.getDouble(STRING_COLINDEX_NUMBER),
+        0d);
+    assertFalse(subject.wasNull());
+    assertEquals(0d, subject.getDouble(STRING_COLINDEX_NULL), 0d);
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetDoubleIndexFromBool() throws SQLException {
+    assertNotNull(subject.getDouble(BOOLEAN_COLINDEX_NOTNULL));
+    assertEquals(BOOLEAN_VALUE ? 1d : 0d, subject.getDouble(BOOLEAN_COLINDEX_NOTNULL), 0d);
+    assertFalse(subject.wasNull());
+    assertEquals(0d, subject.getDouble(BOOLEAN_COLINDEX_NULL), 0d);
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetDoubleIndexFromInt64() throws SQLException {
+    assertNotNull(subject.getDouble(LONG_COLINDEX_NOTNULL));
+    assertEquals(LONG_VALUE, subject.getDouble(LONG_COLINDEX_NOTNULL), 0d);
+    assertFalse(subject.wasNull());
+    assertEquals(0d, subject.getDouble(LONG_COLINDEX_NULL), 0d);
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetDoubleIndexFromTimestamp() throws SQLException {
+    try {
+      subject.getDouble(TIMESTAMP_COLINDEX_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
+    }
+  }
+
   @SuppressWarnings("deprecation")
   @Test
   public void testGetBigDecimalIndexAndScale() throws SQLException {
@@ -336,6 +585,37 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetDateIndexFromString() throws SQLException {
+    assertNotNull(subject.getDate(STRING_COLINDEX_DATE));
+    assertEquals(java.sql.Date.valueOf(STRING_DATE_VALUE), subject.getDate(STRING_COLINDEX_DATE));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getDate(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetDateIndexFromTimestamp() throws SQLException {
+    assertNotNull(subject.getDate(TIMESTAMP_COLINDEX_NOTNULL));
+    assertEquals(
+        new java.sql.Date(TIMESTAMP_VALUE.toSqlTimestamp().getTime()),
+        subject.getDate(TIMESTAMP_COL_NOT_NULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getDate(TIMESTAMP_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetDateIndexFromInt64() {
+    try {
+      subject.getDate(LONG_COLINDEX_NOTNULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+  }
+
   @Test
   public void testGetTimeIndex() throws SQLException {
     assertNotNull(subject.getTime(TIME_COLINDEX_NOTNULL));
@@ -346,6 +626,15 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetTimeIndexFromString() throws SQLException {
+    assertNotNull(subject.getTime(STRING_COLINDEX_TIME));
+    assertEquals(Time.valueOf(STRING_TIME_VALUE), subject.getTime(STRING_COLINDEX_TIME));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getTime(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
   @Test
   public void testGetTimestampIndex() throws SQLException {
     assertNotNull(subject.getTimestamp(TIMESTAMP_COLINDEX_NOTNULL));
@@ -356,6 +645,36 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetTimestampIndexFromString() throws SQLException {
+    assertNotNull(subject.getTimestamp(STRING_COLINDEX_TIMESTAMP));
+    assertEquals(
+        Timestamp.parseTimestamp(STRING_TIMESTAMP_VALUE).toSqlTimestamp(),
+        subject.getTimestamp(STRING_COLINDEX_TIMESTAMP));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getTimestamp(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @SuppressWarnings("deprecation")
+  @Test
+  public void testGetTimestampIndexFromDate() throws SQLException {
+    assertNotNull(subject.getTimestamp(DATE_COLINDEX_NOTNULL));
+    assertEquals(
+        new java.sql.Timestamp(
+            DATE_VALUE.getYear() - 1900,
+            DATE_VALUE.getMonth() - 1,
+            DATE_VALUE.getDayOfMonth(),
+            0,
+            0,
+            0,
+            0),
+        subject.getTimestamp(DATE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getTimestamp(DATE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
   @Test
   public void testGetStringLabel() throws SQLException {
     assertNotNull(subject.getString(STRING_COL_NOT_NULL));
@@ -591,6 +910,33 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetTimestampIndexCalendarFromString() throws SQLException {
+    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"));
+
+    assertNotNull(subject.getTimestamp(STRING_COLINDEX_TIMESTAMP, cal));
+    assertEquals(
+        Timestamp.parseTimestamp(STRING_TIMESTAMP_VALUE.replace("Z", "-07:00")).toSqlTimestamp(),
+        subject.getTimestamp(STRING_COLINDEX_TIMESTAMP, cal));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getTimestamp(STRING_COLINDEX_NULL, cal));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetTimestampIndexCalendarFromDate() throws SQLException {
+    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"));
+
+    assertNotNull(subject.getTimestamp(DATE_COLINDEX_NOTNULL, cal));
+    assertEquals(
+        Timestamp.parseTimestamp(String.format("%sT00:00:00-08:00", DATE_VALUE.toString()))
+            .toSqlTimestamp(),
+        subject.getTimestamp(DATE_COLINDEX_NOTNULL, cal));
+    assertFalse(subject.wasNull());
+    assertNull(subject.getTimestamp(DATE_COLINDEX_NULL, cal));
+    assertTrue(subject.wasNull());
+  }
+
   @Test
   public void testGetTimestampLabelCalendar() throws SQLException {
     Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
@@ -621,6 +967,47 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetByteIndexFromString() throws SQLException {
+    assertNotNull(subject.getByte(STRING_COLINDEX_NUMBER));
+    assertEquals(
+        Byte.valueOf(STRING_NUMBER_VALUE).byteValue(), subject.getByte(STRING_COLINDEX_NUMBER));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetByteIndexFromDouble() throws SQLException {
+    assertNotNull(subject.getByte(DOUBLE_COLINDEX_NOTNULL));
+    assertEquals((byte) DOUBLE_VALUE, subject.getByte(DOUBLE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetByteIndexFromBoolean() throws SQLException {
+    assertNotNull(subject.getByte(BOOLEAN_COLINDEX_NOTNULL));
+    assertEquals(BOOLEAN_VALUE ? 1 : 0, subject.getByte(BOOLEAN_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetNullByteIndex() throws SQLException {
+    assertEquals(0, subject.getByte(BOOLEAN_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getByte(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getByte(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getByte(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    try {
+      subject.getByte(TIMESTAMP_COL_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+  }
+
   @Test
   public void testGetShortIndex() throws SQLException {
     assertNotNull(subject.getShort(LONG_COLINDEX_NOTNULL));
@@ -630,6 +1017,58 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetShortIndexFromString() throws SQLException {
+    assertNotNull(subject.getShort(STRING_COLINDEX_NUMBER));
+    assertEquals(
+        Short.valueOf(STRING_NUMBER_VALUE).shortValue(), subject.getShort(STRING_COLINDEX_NUMBER));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetShortIndexFromDouble() throws SQLException {
+    assertNotNull(subject.getShort(DOUBLE_COLINDEX_NOTNULL));
+    assertEquals((short) DOUBLE_VALUE, subject.getShort(DOUBLE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetShortIndexFromBoolean() throws SQLException {
+    assertNotNull(subject.getShort(BOOLEAN_COLINDEX_NOTNULL));
+    assertEquals(BOOLEAN_VALUE ? 1 : 0, subject.getShort(BOOLEAN_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetShortIndexFromBytes() throws SQLException {
+    try {
+      subject.getShort(BYTES_COL_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+  }
+
+  @Test
+  public void testGetNullShortIndex() throws SQLException {
+    assertEquals(0, subject.getShort(BOOLEAN_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getShort(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getShort(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getShort(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    try {
+      subject.getShort(TIMESTAMP_COL_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+  }
+
   @Test
   public void testGetIntIndex() throws SQLException {
     assertNotNull(subject.getInt(LONG_COLINDEX_NOTNULL));
@@ -640,6 +1079,58 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetIntIndexFromString() throws SQLException {
+    assertNotNull(subject.getInt(STRING_COLINDEX_NUMBER));
+    assertEquals(
+        Integer.valueOf(STRING_NUMBER_VALUE).intValue(), subject.getInt(STRING_COLINDEX_NUMBER));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetIntIndexFromDouble() throws SQLException {
+    assertNotNull(subject.getInt(DOUBLE_COLINDEX_NOTNULL));
+    assertEquals((int) DOUBLE_VALUE, subject.getInt(DOUBLE_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetIntIndexFromBoolean() throws SQLException {
+    assertNotNull(subject.getInt(BOOLEAN_COLINDEX_NOTNULL));
+    assertEquals(BOOLEAN_VALUE ? 1 : 0, subject.getInt(BOOLEAN_COLINDEX_NOTNULL));
+    assertFalse(subject.wasNull());
+  }
+
+  @Test
+  public void testGetIntIndexFromTimestamp() throws SQLException {
+    try {
+      subject.getInt(TIMESTAMP_COL_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+  }
+
+  @Test
+  public void testGetNullIntIndex() throws SQLException {
+    assertEquals(0, subject.getInt(BOOLEAN_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getInt(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getInt(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    assertEquals(0, subject.getInt(STRING_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+    try {
+      subject.getInt(TIMESTAMP_COL_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
+    }
+  }
+
   @Test
   public void testGetFloatIndex() throws SQLException {
     assertNotNull(subject.getFloat(DOUBLE_COLINDEX_NOTNULL));
@@ -650,6 +1141,47 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetFloatIndexFromString() throws SQLException {
+    assertNotNull(subject.getFloat(STRING_COLINDEX_NUMBER));
+    assertEquals(
+        Float.valueOf(STRING_NUMBER_VALUE).floatValue(),
+        subject.getFloat(STRING_COLINDEX_NUMBER),
+        0f);
+    assertFalse(subject.wasNull());
+    assertEquals(0f, subject.getFloat(STRING_COLINDEX_NULL), 0f);
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetFloatIndexFromBool() throws SQLException {
+    assertNotNull(subject.getFloat(BOOLEAN_COLINDEX_NOTNULL));
+    assertEquals(BOOLEAN_VALUE ? 1f : 0f, subject.getFloat(BOOLEAN_COLINDEX_NOTNULL), 0f);
+    assertFalse(subject.wasNull());
+    assertEquals(0f, subject.getFloat(BOOLEAN_COLINDEX_NULL), 0f);
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetFloatIndexFromInt64() throws SQLException {
+    assertNotNull(subject.getFloat(LONG_COLINDEX_NOTNULL));
+    assertEquals(LONG_VALUE, subject.getFloat(LONG_COLINDEX_NOTNULL), 0f);
+    assertFalse(subject.wasNull());
+    assertEquals(0f, subject.getFloat(LONG_COLINDEX_NULL), 0f);
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetFloatIndexFromTimestamp() throws SQLException {
+    try {
+      subject.getFloat(TIMESTAMP_COLINDEX_NULL);
+      fail("missing expected SQLException");
+    } catch (SQLException e) {
+      assertTrue(e instanceof JdbcSqlException);
+      assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
+    }
+  }
+
   @Test
   public void testGetByteLabel() throws SQLException {
     assertNotNull(subject.getByte(LONG_COL_NOT_NULL));
@@ -858,6 +1390,73 @@ public class JdbcResultSetTest {
     assertTrue(subject.wasNull());
   }
 
+  @Test
+  public void testGetAsciiStreamIndexForBool() throws SQLException, IOException {
+    assertNotNull(subject.getAsciiStream(BOOLEAN_COLINDEX_NOTNULL));
+    InputStream actual = subject.getAsciiStream(BOOLEAN_COLINDEX_NOTNULL);
+    byte[] cbuf = new byte[10];
+    int len = actual.read(cbuf, 0, cbuf.length);
+    assertEquals(
+        String.valueOf(BOOLEAN_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII));
+    assertEquals(4, len);
+    assertFalse(subject.wasNull());
+    assertNull(subject.getAsciiStream(BOOLEAN_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetAsciiStreamIndexForInt64() throws SQLException, IOException {
+    assertNotNull(subject.getAsciiStream(LONG_COLINDEX_NOTNULL));
+    InputStream actual = subject.getAsciiStream(LONG_COLINDEX_NOTNULL);
+    byte[] cbuf = new byte[10];
+    int len = actual.read(cbuf, 0, cbuf.length);
+    assertEquals(String.valueOf(LONG_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII));
+    assertEquals(1, len);
+    assertFalse(subject.wasNull());
+    assertNull(subject.getAsciiStream(LONG_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetAsciiStreamIndexForFloat64() throws SQLException, IOException {
+    assertNotNull(subject.getAsciiStream(DOUBLE_COLINDEX_NOTNULL));
+    InputStream actual = subject.getAsciiStream(DOUBLE_COLINDEX_NOTNULL);
+    byte[] cbuf = new byte[20];
+    int len = actual.read(cbuf, 0, cbuf.length);
+    assertEquals(String.valueOf(DOUBLE_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII));
+    assertEquals(13, len);
+    assertFalse(subject.wasNull());
+    assertNull(subject.getAsciiStream(DOUBLE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetAsciiStreamIndexForDate() throws SQLException, IOException {
+    assertNotNull(subject.getAsciiStream(DATE_COLINDEX_NOTNULL));
+    InputStream actual = subject.getAsciiStream(DATE_COLINDEX_NOTNULL);
+    byte[] cbuf = new byte[10];
+    int len = actual.read(cbuf, 0, cbuf.length);
+    assertEquals(String.valueOf(DATE_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII));
+    assertEquals(10, len);
+    assertFalse(subject.wasNull());
+    assertNull(subject.getAsciiStream(DATE_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
+  @Test
+  public void testGetAsciiStreamIndexForTimestamp() throws SQLException, IOException {
+    assertNotNull(subject.getAsciiStream(TIMESTAMP_COLINDEX_NOTNULL));
+    InputStream actual = subject.getAsciiStream(TIMESTAMP_COLINDEX_NOTNULL);
+    byte[] cbuf = new byte[100];
+    int len = actual.read(cbuf, 0, cbuf.length);
+    assertEquals(
+        String.valueOf(TIMESTAMP_VALUE), new String(cbuf, 0, len, StandardCharsets.US_ASCII));
+    assertEquals(30, len);
+    assertFalse(subject.wasNull());
+    assertNull(subject.getAsciiStream(TIMESTAMP_COLINDEX_NULL));
+    assertTrue(subject.wasNull());
+  }
+
   @SuppressWarnings("deprecation")
   @Test
   public void testGetUnicodeStreamIndex() throws SQLException, IOException {
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java
index c1b1e99..7349bfc 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.when;
 
 import com.google.cloud.spanner.ErrorCode;
 import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.connection.Connection;
 import com.google.cloud.spanner.connection.StatementParser;
 import com.google.cloud.spanner.connection.StatementResult;
@@ -57,6 +58,7 @@ public class JdbcStatementTest {
 
     com.google.cloud.spanner.ResultSet resultSet = mock(com.google.cloud.spanner.ResultSet.class);
     when(resultSet.next()).thenReturn(true, false);
+    when(resultSet.getColumnType(0)).thenReturn(Type.int64());
     when(resultSet.getLong(0)).thenReturn(1L);
 
     StatementResult selectResult = mock(StatementResult.class);
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java
index 870711d..58ef03a 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java
@@ -760,4 +760,49 @@ public class JdbcTypeConverterTest {
     List<byte[]> output = toJavaByteArrays(input);
     assertThat(Arrays.deepEquals(expected.toArray(), output.toArray())).isTrue();
   }
+
+  @SuppressWarnings("deprecation")
+  @Test
+  public void testDateToSqlTimestamp() {
+    assertThat(JdbcTypeConverter.toSqlTimestamp(com.google.cloud.Date.fromYearMonthDay(2020, 6, 1)))
+        .isEqualTo(new Timestamp(2020 - 1900, 5, 1, 0, 0, 0, 0));
+  }
+
+  @Test
+  public void testDateToSqlTimestampWithCalendar() {
+    assertThat(
+            JdbcTypeConverter.toSqlTimestamp(
+                com.google.cloud.Date.fromYearMonthDay(2020, 6, 1),
+                Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"))))
+        .isEqualTo(
+            com.google.cloud.Timestamp.parseTimestamp("2020-06-01T00:00:00-07:00")
+                .toSqlTimestamp());
+    assertThat(
+            JdbcTypeConverter.toSqlTimestamp(
+                com.google.cloud.Date.fromYearMonthDay(2020, 6, 1),
+                Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam"))))
+        .isEqualTo(
+            com.google.cloud.Timestamp.parseTimestamp("2020-06-01T00:00:00+02:00")
+                .toSqlTimestamp());
+  }
+
+  @Test
+  public void testParseSqlTimeWithCalendar() {
+    assertThat(
+            JdbcTypeConverter.parseSqlTime(
+                "10:31:15", Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"))))
+        .isEqualTo(
+            new Time(
+                com.google.cloud.Timestamp.parseTimestamp("1970-01-01T10:31:15-08:00")
+                    .toSqlTimestamp()
+                    .getTime()));
+    assertThat(
+            JdbcTypeConverter.parseSqlTime(
+                "10:31:15", Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam"))))
+        .isEqualTo(
+            new Time(
+                com.google.cloud.Timestamp.parseTimestamp("1970-01-01T10:31:15+01:00")
+                    .toSqlTimestamp()
+                    .getTime()));
+  }
 }
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java b/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java
index 5a1663c..4cd5e9b 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/SpannerJdbcExceptionMatcher.java
@@ -17,7 +17,7 @@
 package com.google.cloud.spanner.jdbc;
 
 import com.google.common.base.Preconditions;
-import io.grpc.Status.Code;
+import com.google.rpc.Code;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 
@@ -52,9 +52,9 @@ public final class SpannerJdbcExceptionMatcher<T extends JdbcSqlException> exten
     if (exceptionClass.isAssignableFrom(item.getClass())) {
       JdbcSqlException exception = (JdbcSqlException) item;
       if (message == null) {
-        return exception.getErrorCode() == errorCode.value();
+        return exception.getErrorCode() == errorCode.getNumber();
       }
-      return exception.getErrorCode() == errorCode.value()
+      return exception.getErrorCode() == errorCode.getNumber()
           && exception.getMessage().endsWith(": " + message);
     }
     return false;