Repository: googleapis/java-spanner-jdbc (java)

Issue:
[General] Datanucleus is incompatible with Cloud Spanner JDBC Driver >= 2.8.0 + GoogleSQL
#### Environment details

1. Specify the API at the beginning of the title. For example, "BigQuery: ...").
   General, Core, and Other are also allowed as types
2. OS type and version: Linux
3. Java version: 1.8
4.  version(s): `2.20.1`

#### Steps to reproduce

  1. I'm not at liberty to share the code, however, when attempting to use Datanucleus with CloudSpanner JDBC driver newer than 2.8.0 Datanucleus will be unable to get the type info from the JDBC connection string.

#### Code example

When Datanucleus attempts to load a JDBC connection, it will get the Type info from the connection, however, this type info is then immediately cast to a `short` in Datanucleus (1). However, with Google SQL and support for JSON it will crash upon trying to convert `100011` to a short, which is coming from the JSON vendor type number (2).

1. https://github.com/datanucleus/datanucleus-rdbms/blob/master/src/main/java/org/datanucleus/store/rdbms/schema/SQLTypeInfo.java#L137
2. https://github.com/googleapis/java-spanner-jdbc/blob/v2.15.5/src/main/java/com/google/cloud/spanner/jdbc/JsonType.java#L33

#### Stack trace
```
Caused by: com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory$JdbcSqlExceptionImpl: OUT_OF_RANGE: Value out of range for short: 100011
	at com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.of(JdbcSqlExceptionFactory.java:247) ~[google-cloud-spanner-jdbc-2.20.1-single-jar-with-dependencies.jar:2.20.1]
	at com.google.cloud.spanner.jdbc.AbstractJdbcWrapper.checkedCastToShort(AbstractJdbcWrapper.java:237) ~[google-cloud-spanner-jdbc-2.20.1-single-jar-with-dependencies.jar:2.20.1]
	at com.google.cloud.spanner.jdbc.JdbcResultSet.getShort(JdbcResultSet.java:330) ~[google-cloud-spanner-jdbc-2.20.1-single-jar-with-dependencies.jar:2.20.1]
	at org.datanucleus.store.rdbms.schema.SQLTypeInfo.<init>(SQLTypeInfo.java:133) ~[datanucleus-rdbms-4.1.1.jar:?]
	... 59 more
```

The question is, can we either bring down the 100,000 addition to 32,000 ? Would that be a breaking change? This would ensure compatibility to those jdbc loaders who assume JDBC types are very small always.

Pull request:
fix: support getShort for DATA_TYPE in TypeInfo
The ResultSet that is returned by DatabaseMetadata#getTypeInfo has a column at index 2 with the name DATA_TYPE. This field should contain one of the java.sql.Types constants, or a vendor-specific type code. The JDBC specification states that this column should be a `short` (although the constants in java.sql.Types are of type `int`).

Cloud Spanner (at the time of writing) does not support any int16 fields. The type code is therefore returned as an int64. The codes that are used for vendor-specific types by Spanner exceed the max value of a `short`, and therefore resulted in an OUT_OF_RANGE exception if you tried to call `ResultSet#getShort(int)` on this column for any of the Spanner-specific types (e.g. JSON).

This change fixes that by adding an additional vendor type code for these types that does fit in a `short`. This value is returned when `getShort(int)` is called on the ResultSet.

Fixes #1688


Patch:
diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
index 8eb9dfb..09f9543 100644
--- a/.github/workflows/unmanaged_dependency_check.yaml
+++ b/.github/workflows/unmanaged_dependency_check.yaml
@@ -14,6 +14,6 @@ jobs:
       shell: bash
       run: .kokoro/build.sh
     - name: Unmanaged dependency check
-      uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.32.0
+      uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.33.0
       with:
         bom-path: pom.xml
diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg
index 7d5ab3a..7008a72 100644
--- a/.kokoro/presubmit/graalvm-native-17.cfg
+++ b/.kokoro/presubmit/graalvm-native-17.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.32.0"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.33.0"
 }
 
 env_vars: {
diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg
index 519c2e3..931f9bb 100644
--- a/.kokoro/presubmit/graalvm-native.cfg
+++ b/.kokoro/presubmit/graalvm-native.cfg
@@ -3,7 +3,7 @@
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
   key: "TRAMPOLINE_IMAGE"
-  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.32.0"
+  value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.33.0"
 }
 
 env_vars: {
diff --git a/pom.xml b/pom.xml
index 08f73f7..d510d81 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>sdk-platform-java-config</artifactId>
-    <version>3.32.0</version>
+    <version>3.33.0</version>
   </parent>
   <developers>
     <developer>
@@ -201,6 +201,13 @@
     </dependency>
 
     <!-- Add OpenTelemetry implementation and exporters for use in integration tests -->
+    <!-- TODO: Remove this alpha dependency when the bug in OpenTelemetry has been fixed that makes SdkTracer implement ExtendedTracer -->
+    <dependency>
+      <groupId>io.opentelemetry</groupId>
+      <artifactId>opentelemetry-api-incubator</artifactId>
+      <version>1.40.0-alpha</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>io.opentelemetry</groupId>
       <artifactId>opentelemetry-sdk</artifactId>
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 0c3b12d..ee6deda 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -9,7 +9,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>sdk-platform-java-config</artifactId>
-    <version>3.32.0</version>
+    <version>3.33.0</version>
     <relativePath/>
   </parent>
 
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
index 025b265..7bafb2f 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
@@ -26,6 +26,7 @@ import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.Type.StructField;
 import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -1284,7 +1285,9 @@ class JdbcDatabaseMetaData extends AbstractJdbcWrapper implements DatabaseMetaDa
                     .set("NUM_PREC_RADIX")
                     .to(10)
                     .build(),
-                getJsonType(connection.getDialect()))));
+                getJsonType(connection.getDialect()))),
+        // Allow column 2 to be cast to short without any range checks.
+        ImmutableSet.of(2));
   }
 
   private Struct getJsonType(Dialect dialect) {
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java
index accb632..47d0962 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java
@@ -284,9 +284,13 @@ class JdbcParameterStore {
       case Types.NUMERIC:
       case Types.DECIMAL:
       case JsonType.VENDOR_TYPE_NUMBER:
+      case JsonType.SHORT_VENDOR_TYPE_NUMBER:
       case PgJsonbType.VENDOR_TYPE_NUMBER:
+      case PgJsonbType.SHORT_VENDOR_TYPE_NUMBER:
       case ProtoMessageType.VENDOR_TYPE_NUMBER:
+      case ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER:
       case ProtoEnumType.VENDOR_TYPE_NUMBER:
+      case ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER:
         return true;
     }
     return false;
@@ -348,19 +352,23 @@ class JdbcParameterStore {
       case Types.NCLOB:
         return value instanceof NClob || value instanceof Reader;
       case JsonType.VENDOR_TYPE_NUMBER:
+      case JsonType.SHORT_VENDOR_TYPE_NUMBER:
         return value instanceof String
             || value instanceof InputStream
             || value instanceof Reader
             || (value instanceof Value && ((Value) value).getType().getCode() == Type.Code.JSON);
       case PgJsonbType.VENDOR_TYPE_NUMBER:
+      case PgJsonbType.SHORT_VENDOR_TYPE_NUMBER:
         return value instanceof String
             || value instanceof InputStream
             || value instanceof Reader
             || (value instanceof Value
                 && ((Value) value).getType().getCode() == Type.Code.PG_JSONB);
       case ProtoMessageType.VENDOR_TYPE_NUMBER:
+      case ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER:
         return value instanceof AbstractMessage || value instanceof byte[];
       case ProtoEnumType.VENDOR_TYPE_NUMBER:
+      case ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER:
         return value instanceof ProtocolMessageEnum || value instanceof Number;
     }
     return false;
@@ -449,7 +457,12 @@ class JdbcParameterStore {
   /** Set a JDBC parameter value on a Spanner {@link Statement} with a known SQL type. */
   private Builder setParamWithKnownType(ValueBinder<Builder> binder, Object value, Integer sqlType)
       throws SQLException {
-    switch (sqlType) {
+    if (sqlType == null) {
+      return null;
+    }
+    int type = sqlType;
+
+    switch (type) {
       case Types.BIT:
       case Types.BOOLEAN:
         if (value instanceof Boolean) {
@@ -522,7 +535,9 @@ class JdbcParameterStore {
         }
         return binder.to(stringValue);
       case JsonType.VENDOR_TYPE_NUMBER:
+      case JsonType.SHORT_VENDOR_TYPE_NUMBER:
       case PgJsonbType.VENDOR_TYPE_NUMBER:
+      case PgJsonbType.SHORT_VENDOR_TYPE_NUMBER:
         String jsonValue;
         if (value instanceof String) {
           jsonValue = (String) value;
@@ -534,7 +549,8 @@ class JdbcParameterStore {
           throw JdbcSqlExceptionFactory.of(
               value + " is not a valid JSON value", Code.INVALID_ARGUMENT);
         }
-        if (sqlType == PgJsonbType.VENDOR_TYPE_NUMBER) {
+        if (type == PgJsonbType.VENDOR_TYPE_NUMBER
+            || type == PgJsonbType.SHORT_VENDOR_TYPE_NUMBER) {
           return binder.to(Value.pgJsonb(jsonValue));
         }
         return binder.to(Value.json(jsonValue));
@@ -631,6 +647,7 @@ class JdbcParameterStore {
         }
         throw JdbcSqlExceptionFactory.of(value + " is not a valid clob", Code.INVALID_ARGUMENT);
       case ProtoMessageType.VENDOR_TYPE_NUMBER:
+      case ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER:
         if (value instanceof AbstractMessage) {
           return binder.to((AbstractMessage) value);
         } else if (value instanceof byte[]) {
@@ -640,6 +657,7 @@ class JdbcParameterStore {
               value + " is not a valid PROTO value", Code.INVALID_ARGUMENT);
         }
       case ProtoEnumType.VENDOR_TYPE_NUMBER:
+      case ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER:
         if (value instanceof ProtocolMessageEnum) {
           return binder.to((ProtocolMessageEnum) value);
         } else if (value instanceof Number) {
@@ -809,8 +827,10 @@ class JdbcParameterStore {
         case Types.NCLOB:
           return binder.toStringArray(null);
         case JsonType.VENDOR_TYPE_NUMBER:
+        case JsonType.SHORT_VENDOR_TYPE_NUMBER:
           return binder.toJsonArray(null);
         case PgJsonbType.VENDOR_TYPE_NUMBER:
+        case PgJsonbType.SHORT_VENDOR_TYPE_NUMBER:
           return binder.toPgJsonbArray(null);
         case Types.DATE:
           return binder.toDateArray(null);
@@ -825,7 +845,9 @@ class JdbcParameterStore {
         case Types.BLOB:
           return binder.toBytesArray(null);
         case ProtoMessageType.VENDOR_TYPE_NUMBER:
+        case ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER:
         case ProtoEnumType.VENDOR_TYPE_NUMBER:
+        case ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER:
           return binder.to(
               Value.untyped(
                   com.google.protobuf.Value.newBuilder()
@@ -886,9 +908,10 @@ class JdbcParameterStore {
     } else if (Timestamp[].class.isAssignableFrom(value.getClass())) {
       return binder.toTimestampArray(JdbcTypeConverter.toGoogleTimestamps((Timestamp[]) value));
     } else if (String[].class.isAssignableFrom(value.getClass())) {
-      if (type == JsonType.VENDOR_TYPE_NUMBER) {
+      if (type == JsonType.VENDOR_TYPE_NUMBER || type == JsonType.SHORT_VENDOR_TYPE_NUMBER) {
         return binder.toJsonArray(Arrays.asList((String[]) value));
-      } else if (type == PgJsonbType.VENDOR_TYPE_NUMBER) {
+      } else if (type == PgJsonbType.VENDOR_TYPE_NUMBER
+          || type == PgJsonbType.SHORT_VENDOR_TYPE_NUMBER) {
         return binder.toPgJsonbArray(Arrays.asList((String[]) value));
       } else {
         return binder.toStringArray(Arrays.asList((String[]) value));
@@ -992,11 +1015,13 @@ class JdbcParameterStore {
           Value.untyped(
               com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()));
     }
-    switch (sqlType) {
+    int type = sqlType;
+    switch (type) {
       case Types.BIGINT:
         return binder.to((Long) null);
       case Types.BINARY:
       case ProtoMessageType.VENDOR_TYPE_NUMBER:
+      case ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER:
         return binder.to((ByteArray) null);
       case Types.BLOB:
         return binder.to((ByteArray) null);
@@ -1021,6 +1046,7 @@ class JdbcParameterStore {
         return binder.to((Double) null);
       case Types.INTEGER:
       case ProtoEnumType.VENDOR_TYPE_NUMBER:
+      case ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER:
         return binder.to((Long) null);
       case Types.LONGNVARCHAR:
         return binder.to((String) null);
@@ -1052,8 +1078,10 @@ class JdbcParameterStore {
       case Types.VARCHAR:
         return binder.to((String) null);
       case JsonType.VENDOR_TYPE_NUMBER:
+      case JsonType.SHORT_VENDOR_TYPE_NUMBER:
         return binder.to(Value.json(null));
       case PgJsonbType.VENDOR_TYPE_NUMBER:
+      case PgJsonbType.SHORT_VENDOR_TYPE_NUMBER:
         return binder.to(Value.pgJsonb(null));
       default:
         return binder.to(
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 054c1ed..00df251 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java
@@ -26,6 +26,7 @@ import com.google.cloud.spanner.Value;
 import com.google.cloud.spanner.connection.PartitionedQueryResultSet;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.Reader;
@@ -57,8 +58,16 @@ import javax.annotation.Nonnull;
 class JdbcResultSet extends AbstractJdbcResultSet {
 
   static JdbcResultSet of(com.google.cloud.spanner.ResultSet resultSet) {
-    Preconditions.checkNotNull(resultSet);
-    return new JdbcResultSet(null, resultSet);
+    return of(resultSet, ImmutableSet.of());
+  }
+
+  static JdbcResultSet of(
+      com.google.cloud.spanner.ResultSet resultSet,
+      ImmutableSet<Integer> columnsAllowedUncheckedLongCastToShort) {
+    return new JdbcResultSet(
+        null,
+        Preconditions.checkNotNull(resultSet),
+        Preconditions.checkNotNull(columnsAllowedUncheckedLongCastToShort));
   }
 
   static JdbcResultSet of(Statement statement, com.google.cloud.spanner.ResultSet resultSet) {
@@ -129,10 +138,19 @@ class JdbcResultSet extends AbstractJdbcResultSet {
   private boolean nextCalledForMetaData = false;
   private boolean nextCalledForMetaDataResult = false;
   private long currentRow = 0L;
+  private final ImmutableSet<Integer> columnsAllowedUncheckedLongCastToShort;
 
   JdbcResultSet(Statement statement, com.google.cloud.spanner.ResultSet spanner) {
+    this(statement, spanner, ImmutableSet.of());
+  }
+
+  JdbcResultSet(
+      Statement statement,
+      com.google.cloud.spanner.ResultSet spanner,
+      ImmutableSet<Integer> columnsAllowedUncheckedLongCastToShort) {
     super(spanner);
     this.statement = statement;
+    this.columnsAllowedUncheckedLongCastToShort = columnsAllowedUncheckedLongCastToShort;
   }
 
   void checkClosedAndValidRow() throws SQLException {
@@ -327,6 +345,12 @@ class JdbcResultSet extends AbstractJdbcResultSet {
             : checkedCastToShort(Double.valueOf(spanner.getDouble(spannerIndex)).longValue());
       case INT64:
       case ENUM:
+        if (this.columnsAllowedUncheckedLongCastToShort.contains(columnIndex)) {
+          // This is used to allow frameworks that call getShort(int) on the ResultSet that is
+          // returned by DatabaseMetadata#getTypeInfo() to get the type code as a short, even when
+          // the value is out of range for a short.
+          return isNull ? 0 : (short) spanner.getLong(spannerIndex);
+        }
         return isNull ? 0 : checkedCastToShort(spanner.getLong(spannerIndex));
       case NUMERIC:
         return isNull ? 0 : checkedCastToShort(spanner.getBigDecimal(spannerIndex));
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JsonType.java b/src/main/java/com/google/cloud/spanner/jdbc/JsonType.java
index 44af26b..3e69ade 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JsonType.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JsonType.java
@@ -17,6 +17,7 @@
 package com.google.cloud.spanner.jdbc;
 
 import com.google.spanner.v1.TypeCode;
+import java.sql.DatabaseMetaData;
 import java.sql.PreparedStatement;
 import java.sql.SQLType;
 
@@ -31,6 +32,11 @@ public class JsonType implements SQLType {
    * conflicts with the type numbers in java.sql.Types.
    */
   public static final int VENDOR_TYPE_NUMBER = 100_000 + TypeCode.JSON_VALUE;
+  /**
+   * Define a short type number as well, as this is what is expected to be returned in {@link
+   * DatabaseMetaData#getTypeInfo()}.
+   */
+  public static final short SHORT_VENDOR_TYPE_NUMBER = (short) VENDOR_TYPE_NUMBER;
 
   private JsonType() {}
 
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/PgJsonbType.java b/src/main/java/com/google/cloud/spanner/jdbc/PgJsonbType.java
index 8627d34..b0a819b 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/PgJsonbType.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/PgJsonbType.java
@@ -17,6 +17,7 @@
 package com.google.cloud.spanner.jdbc;
 
 import com.google.spanner.v1.TypeCode;
+import java.sql.DatabaseMetaData;
 import java.sql.SQLType;
 
 public class PgJsonbType implements SQLType {
@@ -27,6 +28,11 @@ public class PgJsonbType implements SQLType {
    * the range starting at 100,000 (see {@link JsonType}).
    */
   public static final int VENDOR_TYPE_NUMBER = 200_000 + TypeCode.JSON_VALUE;
+  /**
+   * Define a short type number as well, as this is what is expected to be returned in {@link
+   * DatabaseMetaData#getTypeInfo()}.
+   */
+  public static final short SHORT_VENDOR_TYPE_NUMBER = (short) VENDOR_TYPE_NUMBER;
 
   private PgJsonbType() {}
 
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ProtoEnumType.java b/src/main/java/com/google/cloud/spanner/jdbc/ProtoEnumType.java
index 478d082..953fb73 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/ProtoEnumType.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/ProtoEnumType.java
@@ -17,6 +17,7 @@
 package com.google.cloud.spanner.jdbc;
 
 import com.google.spanner.v1.TypeCode;
+import java.sql.DatabaseMetaData;
 import java.sql.PreparedStatement;
 import java.sql.SQLType;
 
@@ -31,6 +32,11 @@ public class ProtoEnumType implements SQLType {
    * conflicts with the type numbers in java.sql.Types.
    */
   public static final int VENDOR_TYPE_NUMBER = 100_000 + TypeCode.ENUM_VALUE;
+  /**
+   * Define a short type number as well, as this is what is expected to be returned in {@link
+   * DatabaseMetaData#getTypeInfo()}.
+   */
+  public static final short SHORT_VENDOR_TYPE_NUMBER = (short) VENDOR_TYPE_NUMBER;
 
   private ProtoEnumType() {}
 
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ProtoMessageType.java b/src/main/java/com/google/cloud/spanner/jdbc/ProtoMessageType.java
index 5a813d6..2a7f6f7 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/ProtoMessageType.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/ProtoMessageType.java
@@ -17,6 +17,7 @@
 package com.google.cloud.spanner.jdbc;
 
 import com.google.spanner.v1.TypeCode;
+import java.sql.DatabaseMetaData;
 import java.sql.PreparedStatement;
 import java.sql.SQLType;
 
@@ -31,6 +32,11 @@ public class ProtoMessageType implements SQLType {
    * conflicts with the type numbers in java.sql.Types.
    */
   public static final int VENDOR_TYPE_NUMBER = 100_000 + TypeCode.PROTO_VALUE;
+  /**
+   * Define a short type number as well, as this is what is expected to be returned in {@link
+   * DatabaseMetaData#getTypeInfo()}.
+   */
+  public static final short SHORT_VENDOR_TYPE_NUMBER = (short) VENDOR_TYPE_NUMBER;
 
   private ProtoMessageType() {}
 


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java
index 3e94a4c..64499f4 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java
@@ -475,24 +475,51 @@ public class JdbcDatabaseMetaDataTest {
       try (ResultSet rs = meta.getTypeInfo()) {
         assertTrue(rs.next());
         assertEquals("STRING", rs.getString("TYPE_NAME"));
+        assertEquals(Types.NVARCHAR, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.NVARCHAR, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("INT64", rs.getString("TYPE_NAME"));
+        assertEquals(Types.BIGINT, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.BIGINT, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("BYTES", rs.getString("TYPE_NAME"));
+        assertEquals(Types.BINARY, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.BINARY, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("FLOAT32", rs.getString("TYPE_NAME"));
+        assertEquals(Types.REAL, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.REAL, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("FLOAT64", rs.getString("TYPE_NAME"));
+        assertEquals(Types.DOUBLE, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.DOUBLE, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("BOOL", rs.getString("TYPE_NAME"));
+        assertEquals(Types.BOOLEAN, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.BOOLEAN, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("DATE", rs.getString("TYPE_NAME"));
+        assertEquals(Types.DATE, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.DATE, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("TIMESTAMP", rs.getString("TYPE_NAME"));
+        assertEquals(Types.TIMESTAMP, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.TIMESTAMP, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
         assertEquals("NUMERIC", rs.getString("TYPE_NAME"));
+        assertEquals(Types.NUMERIC, rs.getInt("DATA_TYPE"));
+        assertEquals(Types.NUMERIC, rs.getShort("DATA_TYPE"));
         assertTrue(rs.next());
-        assertEquals(dialect == Dialect.POSTGRESQL ? "JSONB" : "JSON", rs.getString("TYPE_NAME"));
+        if (dialect == Dialect.POSTGRESQL) {
+          assertEquals("JSONB", rs.getString("TYPE_NAME"));
+          assertEquals(PgJsonbType.VENDOR_TYPE_NUMBER, rs.getInt("DATA_TYPE"));
+          assertEquals(PgJsonbType.SHORT_VENDOR_TYPE_NUMBER, rs.getShort("DATA_TYPE"));
+        } else {
+          assertEquals("JSON", rs.getString("TYPE_NAME"));
+          assertEquals(JsonType.VENDOR_TYPE_NUMBER, rs.getInt("DATA_TYPE"));
+          assertEquals(JsonType.SHORT_VENDOR_TYPE_NUMBER, rs.getShort("DATA_TYPE"));
+        }
+
         assertFalse(rs.next());
         ResultSetMetaData rsmd = rs.getMetaData();
         assertEquals(18, rsmd.getColumnCount());
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java
index eb8237c..d77c932 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java
@@ -225,6 +225,10 @@ public class JdbcParameterStoreTest {
     assertEquals(jsonString, params.getParameter(1));
     verifyParameter(params, Value.json(jsonString));
 
+    params.setParameter(1, jsonString, (int) JsonType.SHORT_VENDOR_TYPE_NUMBER);
+    assertEquals(jsonString, params.getParameter(1));
+    verifyParameter(params, Value.json(jsonString));
+
     params.setParameter(1, jsonString, JsonType.INSTANCE);
     assertEquals(jsonString, params.getParameter(1));
     verifyParameter(params, Value.json(jsonString));
@@ -233,6 +237,10 @@ public class JdbcParameterStoreTest {
     assertEquals(jsonString, params.getParameter(1));
     verifyParameter(params, Value.pgJsonb(jsonString));
 
+    params.setParameter(1, jsonString, (int) PgJsonbType.SHORT_VENDOR_TYPE_NUMBER);
+    assertEquals(jsonString, params.getParameter(1));
+    verifyParameter(params, Value.pgJsonb(jsonString));
+
     params.setParameter(1, jsonString, PgJsonbType.INSTANCE);
     assertEquals(jsonString, params.getParameter(1));
     verifyParameter(params, Value.pgJsonb(jsonString));
@@ -250,6 +258,10 @@ public class JdbcParameterStoreTest {
     assertEquals(singerInfo, params.getParameter(1));
     verifyParameter(params, Value.protoMessage(singerInfo));
 
+    params.setParameter(1, singerInfo, (int) ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER);
+    assertEquals(singerInfo, params.getParameter(1));
+    verifyParameter(params, Value.protoMessage(singerInfo));
+
     params.setParameter(1, singerInfo, ProtoMessageType.INSTANCE);
     assertEquals(singerInfo, params.getParameter(1));
     verifyParameter(params, Value.protoMessage(singerInfo));
@@ -259,6 +271,11 @@ public class JdbcParameterStoreTest {
     assertArrayEquals(singerInfo.toByteArray(), (byte[]) params.getParameter(1));
     verifyParameter(params, Value.bytes(ByteArray.copyFrom(singerInfo.toByteArray())));
 
+    params.setParameter(
+        1, singerInfo.toByteArray(), (int) ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER);
+    assertArrayEquals(singerInfo.toByteArray(), (byte[]) params.getParameter(1));
+    verifyParameter(params, Value.bytes(ByteArray.copyFrom(singerInfo.toByteArray())));
+
     params.setParameter(1, singerInfo, Types.BINARY);
     assertEquals(singerInfo, params.getParameter(1));
     verifyParameter(params, Value.protoMessage(singerInfo));
@@ -267,6 +284,10 @@ public class JdbcParameterStoreTest {
     assertEquals(Genre.ROCK, params.getParameter(1));
     verifyParameter(params, Value.protoEnum(Genre.ROCK));
 
+    params.setParameter(1, Genre.ROCK, (int) ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER);
+    assertEquals(Genre.ROCK, params.getParameter(1));
+    verifyParameter(params, Value.protoEnum(Genre.ROCK));
+
     params.setParameter(1, Genre.ROCK, ProtoEnumType.INSTANCE);
     assertEquals(Genre.ROCK, params.getParameter(1));
     verifyParameter(params, Value.protoEnum(Genre.ROCK));
@@ -276,6 +297,10 @@ public class JdbcParameterStoreTest {
     assertEquals(Genre.ROCK.getNumber(), params.getParameter(1));
     verifyParameter(params, Value.int64(Genre.ROCK.getNumber()));
 
+    params.setParameter(1, Genre.ROCK.getNumber(), (int) ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER);
+    assertEquals(Genre.ROCK.getNumber(), params.getParameter(1));
+    verifyParameter(params, Value.int64(Genre.ROCK.getNumber()));
+
     params.setParameter(1, Genre.ROCK, Types.INTEGER);
     assertEquals(Genre.ROCK, params.getParameter(1));
     verifyParameter(params, Value.protoEnum(Genre.ROCK));
@@ -287,7 +312,8 @@ public class JdbcParameterStoreTest {
           Types.SMALLINT,
           Types.INTEGER,
           Types.BIGINT,
-          ProtoEnumType.VENDOR_TYPE_NUMBER
+          ProtoEnumType.VENDOR_TYPE_NUMBER,
+          ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER
         }) {
       params.setParameter(1, (byte) 1, type);
       assertEquals(1, ((Byte) params.getParameter(1)).byteValue());
@@ -397,7 +423,11 @@ public class JdbcParameterStoreTest {
     // types that should lead to bytes (except BLOB which is handled separately)
     for (int type :
         new int[] {
-          Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY, ProtoMessageType.VENDOR_TYPE_NUMBER
+          Types.BINARY,
+          Types.VARBINARY,
+          Types.LONGVARBINARY,
+          ProtoMessageType.VENDOR_TYPE_NUMBER,
+          ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER
         }) {
       params.setParameter(1, new byte[] {1, 2, 3}, type);
       assertArrayEquals(new byte[] {1, 2, 3}, (byte[]) params.getParameter(1));
@@ -512,7 +542,8 @@ public class JdbcParameterStoreTest {
           Types.SMALLINT,
           Types.INTEGER,
           Types.BIGINT,
-          ProtoEnumType.VENDOR_TYPE_NUMBER
+          ProtoEnumType.VENDOR_TYPE_NUMBER,
+          ProtoEnumType.SHORT_VENDOR_TYPE_NUMBER
         }) {
       assertInvalidParameter(params, "1", type);
       assertInvalidParameter(params, new Object(), type);
@@ -557,7 +588,11 @@ public class JdbcParameterStoreTest {
     // types that should lead to bytes (except BLOB which is handled separately)
     for (int type :
         new int[] {
-          Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY, ProtoMessageType.VENDOR_TYPE_NUMBER
+          Types.BINARY,
+          Types.VARBINARY,
+          Types.LONGVARBINARY,
+          ProtoMessageType.VENDOR_TYPE_NUMBER,
+          ProtoMessageType.SHORT_VENDOR_TYPE_NUMBER
         }) {
       assertInvalidParameter(params, "1", type);
       assertInvalidParameter(params, new Object(), type);
@@ -576,7 +611,9 @@ public class JdbcParameterStoreTest {
           Types.NVARCHAR,
           Types.LONGNVARCHAR,
           JsonType.VENDOR_TYPE_NUMBER,
-          PgJsonbType.VENDOR_TYPE_NUMBER
+          JsonType.SHORT_VENDOR_TYPE_NUMBER,
+          PgJsonbType.VENDOR_TYPE_NUMBER,
+          PgJsonbType.SHORT_VENDOR_TYPE_NUMBER
         }) {
       assertInvalidParameter(params, new Object(), type);
       assertInvalidParameter(params, Boolean.TRUE, type);
@@ -603,7 +640,9 @@ public class JdbcParameterStoreTest {
           Types.NVARCHAR,
           Types.LONGNVARCHAR,
           JsonType.VENDOR_TYPE_NUMBER,
-          PgJsonbType.VENDOR_TYPE_NUMBER
+          JsonType.SHORT_VENDOR_TYPE_NUMBER,
+          PgJsonbType.VENDOR_TYPE_NUMBER,
+          PgJsonbType.SHORT_VENDOR_TYPE_NUMBER
         }) {
       Reader reader = new StringReader("test");
       reader.close();
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITOpenTelemetryTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITOpenTelemetryTest.java
index 19fb114..bbbbaf3 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITOpenTelemetryTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITOpenTelemetryTest.java
@@ -38,6 +38,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.devtools.cloudtrace.v1.ListTracesRequest;
 import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.incubator.trace.ExtendedTracer;
 import io.opentelemetry.sdk.OpenTelemetrySdk;
 import io.opentelemetry.sdk.resources.Resource;
 import io.opentelemetry.sdk.trace.SdkTracerProvider;
@@ -79,7 +80,7 @@ public class ITOpenTelemetryTest extends ITAbstractJdbcTest {
   private Database database;
 
   @BeforeClass
-  public static void setupOpenTelemetry() throws IOException {
+  public static void setupOpenTelemetry() {
     assumeFalse("This test requires credentials", EmulatorSpannerHelper.isUsingEmulator());
 
     SpannerOptions options = env.getTestHelper().getOptions();
@@ -103,6 +104,9 @@ public class ITOpenTelemetryTest extends ITAbstractJdbcTest {
                     .addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
                     .build())
             .build();
+    // TODO: Remove when the bug in OpenTelemetry that has SdkTracer implement ExtendedTracer,
+    //       which is only available in the incubator project.
+    ExtendedTracer ignore = (ExtendedTracer) openTelemetry.getTracer("foo");
   }
 
   @AfterClass
@@ -245,8 +249,6 @@ public class ITOpenTelemetryTest extends ITAbstractJdbcTest {
       Thread.sleep(5000L);
       boolean foundTrace = false;
       for (int attempts = 0; attempts < 20; attempts++) {
-        // Ignore deprecation for now, as there is no alternative offered (yet?).
-        //noinspection deprecation
         ListTracesPagedResponse response =
             client.listTraces(
                 ListTracesRequest.newBuilder()