Repository: googleapis/java-spanner-jdbc
Pull request #1518 (2024-02-29 16:54:46)

feat: support float32
Adds support for the FLOAT32 / real data type.



Patch:
diff --git a/samples/spring-data-jdbc/pom.xml b/samples/spring-data-jdbc/pom.xml
index 6bcfd49..79af944 100644
--- a/samples/spring-data-jdbc/pom.xml
+++ b/samples/spring-data-jdbc/pom.xml
@@ -87,7 +87,7 @@
       <plugin>
         <groupId>com.spotify.fmt</groupId>
         <artifactId>fmt-maven-plugin</artifactId>
-        <version>2.22.1</version>
+        <version>2.23</version>
         <executions>
           <execution>
             <goals>
diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml
index a32e7a8..ef02f01 100644
--- a/samples/spring-data-mybatis/pom.xml
+++ b/samples/spring-data-mybatis/pom.xml
@@ -97,7 +97,7 @@
       <plugin>
         <groupId>com.spotify.fmt</groupId>
         <artifactId>fmt-maven-plugin</artifactId>
-        <version>2.22.1</version>
+        <version>2.23</version>
         <executions>
           <execution>
             <goals>
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java
index cd1c50a..2a9ba06 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java
@@ -118,7 +118,7 @@ abstract class AbstractJdbcPreparedStatement extends JdbcStatement implements Pr
   @Override
   public void setFloat(int parameterIndex, float value) throws SQLException {
     checkClosed();
-    parameters.setParameter(parameterIndex, value, Types.FLOAT);
+    parameters.setParameter(parameterIndex, value, Types.REAL);
   }
 
   @Override
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 86281af..b06e599 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java
@@ -49,6 +49,8 @@ abstract class AbstractJdbcWrapper implements Wrapper {
         return Types.BINARY;
       case DATE:
         return Types.DATE;
+      case FLOAT32:
+        return Types.REAL;
       case FLOAT64:
         return Types.DOUBLE;
       case INT64:
@@ -81,9 +83,13 @@ abstract class AbstractJdbcWrapper implements Wrapper {
    */
   @Deprecated
   static String getSpannerTypeName(int sqlType) {
+    // TODO: Re-write to be dialect-aware (or remove all-together).
     if (sqlType == Types.BOOLEAN) return Type.bool().getCode().name();
     if (sqlType == Types.BINARY) return Type.bytes().getCode().name();
     if (sqlType == Types.DATE) return Type.date().getCode().name();
+    if (sqlType == Types.REAL) {
+      return Type.float32().getCode().name();
+    }
     if (sqlType == Types.DOUBLE || sqlType == Types.FLOAT) return Type.float64().getCode().name();
     if (sqlType == Types.BIGINT
         || sqlType == Types.INTEGER
@@ -109,6 +115,9 @@ abstract class AbstractJdbcWrapper implements Wrapper {
     if (sqlType == Types.BOOLEAN) return Boolean.class.getName();
     if (sqlType == Types.BINARY) return Byte[].class.getName();
     if (sqlType == Types.DATE) return Date.class.getName();
+    if (sqlType == Types.REAL) {
+      return Float.class.getName();
+    }
     if (sqlType == Types.DOUBLE || sqlType == Types.FLOAT) return Double.class.getName();
     if (sqlType == Types.BIGINT
         || sqlType == Types.INTEGER
@@ -137,6 +146,8 @@ abstract class AbstractJdbcWrapper implements Wrapper {
         return byte[].class.getName();
       case DATE:
         return Date.class.getName();
+      case FLOAT32:
+        return Float.class.getName();
       case FLOAT64:
         return Double.class.getName();
       case INT64:
@@ -158,6 +169,8 @@ abstract class AbstractJdbcWrapper implements Wrapper {
             return byte[][].class.getName();
           case DATE:
             return Date[].class.getName();
+          case FLOAT32:
+            return Float[].class.getName();
           case FLOAT64:
             return Double[].class.getName();
           case INT64:
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java
index 650c1eb..4ef2ac2 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java
@@ -188,6 +188,9 @@ class JdbcArray implements Array {
           case DATE:
             builder = binder.to(JdbcTypeConverter.toGoogleDate((Date) value));
             break;
+          case FLOAT32:
+            builder = binder.to((Float) value);
+            break;
           case FLOAT64:
             builder = binder.to((Double) value);
             break;
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
index 5dd082e..b920d3d 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
@@ -110,6 +110,47 @@ enum JdbcDataType {
       return Type.date();
     }
   },
+  FLOAT32 {
+    @Override
+    public int getSqlType() {
+      return Types.REAL;
+    }
+
+    @Override
+    public int getScale() {
+      return 7;
+    }
+
+    @Override
+    public int getPrecision() {
+      return 7;
+    }
+
+    @Override
+    public int getDefaultColumnDisplaySize() {
+      return 7;
+    }
+
+    @Override
+    public Class<Float> getJavaClass() {
+      return Float.class;
+    }
+
+    @Override
+    public Code getCode() {
+      return Code.FLOAT32;
+    }
+
+    @Override
+    public List<Float> getArrayElements(ResultSet rs, int columnIndex) {
+      return rs.getFloatList(columnIndex);
+    }
+
+    @Override
+    public Type getSpannerType() {
+      return Type.float32();
+    }
+  },
   FLOAT64 {
     private final Set<Class<?>> classes = new HashSet<>(Arrays.asList(Float.class, Double.class));
 
@@ -371,6 +412,21 @@ enum JdbcDataType {
 
   public abstract Type getSpannerType();
 
+  // TODO: Implement and use this method for all types.
+  public int getPrecision() {
+    throw new UnsupportedOperationException();
+  }
+
+  // TODO: Implement and use this method for all types.
+  public int getScale() {
+    throw new UnsupportedOperationException();
+  }
+
+  // TODO: Implement and use this method for all types.
+  public int getDefaultColumnDisplaySize() {
+    throw new UnsupportedOperationException();
+  }
+
   /**
    * @param rs the result set to look up the elements
    * @param columnIndex zero based column index
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 4463395..025b265 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
@@ -1056,6 +1056,44 @@ class JdbcDatabaseMetaData extends AbstractJdbcWrapper implements DatabaseMetaDa
                     .set("NUM_PREC_RADIX")
                     .to((Long) null)
                     .build(),
+                Struct.newBuilder()
+                    .set("TYPE_NAME")
+                    .to("FLOAT32")
+                    .set("DATA_TYPE")
+                    .to(Types.REAL) // 8
+                    .set("PRECISION")
+                    .to(7L)
+                    .set("LITERAL_PREFIX")
+                    .to((String) null)
+                    .set("LITERAL_SUFFIX")
+                    .to((String) null)
+                    .set("CREATE_PARAMS")
+                    .to((String) null)
+                    .set("NULLABLE")
+                    .to(DatabaseMetaData.typeNullable)
+                    .set("CASE_SENSITIVE")
+                    .to(false)
+                    .set("SEARCHABLE")
+                    .to(DatabaseMetaData.typePredBasic)
+                    .set("UNSIGNED_ATTRIBUTE")
+                    .to(false)
+                    .set("FIXED_PREC_SCALE")
+                    .to(false)
+                    .set("AUTO_INCREMENT")
+                    .to(false)
+                    .set("LOCAL_TYPE_NAME")
+                    .to("FLOAT32")
+                    .set("MINIMUM_SCALE")
+                    .to(0)
+                    .set("MAXIMUM_SCALE")
+                    .to(0)
+                    .set("SQL_DATA_TYPE")
+                    .to((Long) null)
+                    .set("SQL_DATETIME_SUB")
+                    .to((Long) null)
+                    .set("NUM_PREC_RADIX")
+                    .to(2)
+                    .build(),
                 Struct.newBuilder()
                     .set("TYPE_NAME")
                     .to("FLOAT64")
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java
index 82a4b91..784a267 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java
@@ -76,6 +76,7 @@ class JdbcParameterMetaData extends AbstractJdbcWrapper implements ParameterMeta
     int type = getParameterType(param);
     return type == Types.DOUBLE
         || type == Types.FLOAT
+        || type == Types.REAL
         || type == Types.BIGINT
         || type == Types.INTEGER
         || type == Types.SMALLINT
@@ -139,7 +140,7 @@ class JdbcParameterMetaData extends AbstractJdbcWrapper implements ParameterMeta
     } else if (Long.class.isAssignableFrom(value.getClass())) {
       return Types.BIGINT;
     } else if (Float.class.isAssignableFrom(value.getClass())) {
-      return Types.FLOAT;
+      return Types.REAL;
     } else if (Double.class.isAssignableFrom(value.getClass())) {
       return Types.DOUBLE;
     } else if (BigDecimal.class.isAssignableFrom(value.getClass())) {
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 5fb4177..dabc9bf 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java
@@ -452,8 +452,12 @@ class JdbcParameterStore {
           return binder.to(((Number) value).longValue());
         }
         throw JdbcSqlExceptionFactory.of(value + " is not a valid long", Code.INVALID_ARGUMENT);
-      case Types.FLOAT:
       case Types.REAL:
+        if (value instanceof Number) {
+          return binder.to(((Number) value).floatValue());
+        }
+        throw JdbcSqlExceptionFactory.of(value + " is not a valid float", Code.INVALID_ARGUMENT);
+      case Types.FLOAT:
       case Types.DOUBLE:
         if (value instanceof Number) {
           return binder.to(((Number) value).doubleValue());
@@ -744,8 +748,9 @@ class JdbcParameterStore {
         case Types.INTEGER:
         case Types.BIGINT:
           return binder.toInt64Array((long[]) null);
-        case Types.FLOAT:
         case Types.REAL:
+          return binder.toFloat32Array((float[]) null);
+        case Types.FLOAT:
         case Types.DOUBLE:
           return binder.toFloat64Array((double[]) null);
         case Types.NUMERIC:
@@ -814,13 +819,9 @@ class JdbcParameterStore {
     } else if (Long[].class.isAssignableFrom(value.getClass())) {
       return binder.toInt64Array(toLongList((Long[]) value));
     } else if (float[].class.isAssignableFrom(value.getClass())) {
-      double[] l = new double[((float[]) value).length];
-      for (int i = 0; i < l.length; i++) {
-        l[i] = ((float[]) value)[i];
-      }
-      return binder.toFloat64Array(l);
+      return binder.toFloat32Array((float[]) value);
     } else if (Float[].class.isAssignableFrom(value.getClass())) {
-      return binder.toFloat64Array(toDoubleList((Float[]) value));
+      return binder.toFloat32Array(toFloatList((Float[]) value));
     } else if (double[].class.isAssignableFrom(value.getClass())) {
       return binder.toFloat64Array((double[]) value);
     } else if (Double[].class.isAssignableFrom(value.getClass())) {
@@ -860,6 +861,14 @@ class JdbcParameterStore {
     return res;
   }
 
+  private List<Float> toFloatList(Number[] input) {
+    List<Float> res = new ArrayList<>(input.length);
+    for (Number number : input) {
+      res.add(number == null ? null : number.floatValue());
+    }
+    return res;
+  }
+
   private List<Double> toDoubleList(Number[] input) {
     List<Double> res = new ArrayList<>(input.length);
     for (Number number : input) {
@@ -901,9 +910,8 @@ class JdbcParameterStore {
         } else {
           return binder.to((BigDecimal) null);
         }
-      case Types.DOUBLE:
-        return binder.to((Double) null);
       case Types.FLOAT:
+      case Types.DOUBLE:
         return binder.to((Double) null);
       case Types.INTEGER:
         return binder.to((Long) null);
@@ -920,7 +928,7 @@ class JdbcParameterStore {
       case Types.NVARCHAR:
         return binder.to((String) null);
       case Types.REAL:
-        return binder.to((Double) null);
+        return binder.to((Float) null);
       case Types.SMALLINT:
         return binder.to((Long) null);
       case Types.SQLXML:
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 4152fa5..91ec976 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java
@@ -203,6 +203,8 @@ class JdbcResultSet extends AbstractJdbcResultSet {
         return isNull ? null : spanner.getBytes(spannerIndex).toBase64();
       case DATE:
         return isNull ? null : spanner.getDate(spannerIndex).toString();
+      case FLOAT32:
+        return isNull ? null : Float.toString(spanner.getFloat(spannerIndex));
       case FLOAT64:
         return isNull ? null : Double.toString(spanner.getDouble(spannerIndex));
       case INT64:
@@ -235,6 +237,8 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     switch (type) {
       case BOOL:
         return !isNull && spanner.getBoolean(spannerIndex);
+      case FLOAT32:
+        return !isNull && spanner.getFloat(spannerIndex) != 0f;
       case FLOAT64:
         return !isNull && spanner.getDouble(spannerIndex) != 0D;
       case INT64:
@@ -266,6 +270,10 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     switch (type) {
       case BOOL:
         return isNull ? (byte) 0 : (spanner.getBoolean(spannerIndex) ? (byte) 1 : 0);
+      case FLOAT32:
+        return isNull
+            ? (byte) 0
+            : checkedCastToByte(Float.valueOf(spanner.getFloat(spannerIndex)).longValue());
       case FLOAT64:
         return isNull
             ? (byte) 0
@@ -301,6 +309,10 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     switch (type) {
       case BOOL:
         return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (short) 1 : 0);
+      case FLOAT32:
+        return isNull
+            ? 0
+            : checkedCastToShort(Float.valueOf(spanner.getFloat(spannerIndex)).longValue());
       case FLOAT64:
         return isNull
             ? 0
@@ -336,6 +348,10 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     switch (type) {
       case BOOL:
         return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? 1 : 0);
+      case FLOAT32:
+        return isNull
+            ? 0
+            : checkedCastToInt(Float.valueOf(spanner.getFloat(spannerIndex)).longValue());
       case FLOAT64:
         return isNull
             ? 0
@@ -371,6 +387,8 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     switch (type) {
       case BOOL:
         return isNull ? 0L : (spanner.getBoolean(spannerIndex) ? 1L : 0L);
+      case FLOAT32:
+        return isNull ? 0L : Float.valueOf(spanner.getFloat(spannerIndex)).longValue();
       case FLOAT64:
         return isNull ? 0L : Double.valueOf(spanner.getDouble(spannerIndex)).longValue();
       case INT64:
@@ -404,6 +422,8 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     switch (type) {
       case BOOL:
         return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (float) 1 : 0);
+      case FLOAT32:
+        return isNull ? 0 : spanner.getFloat(spannerIndex);
       case FLOAT64:
         return isNull ? 0 : checkedCastToFloat(spanner.getDouble(spannerIndex));
       case INT64:
@@ -435,6 +455,8 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     switch (type) {
       case BOOL:
         return isNull ? 0 : (spanner.getBoolean(spannerIndex) ? (double) 1 : 0);
+      case FLOAT32:
+        return isNull ? 0 : spanner.getFloat(spannerIndex);
       case FLOAT64:
         return isNull ? 0 : spanner.getDouble(spannerIndex);
       case INT64:
@@ -481,6 +503,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
             ? null
             : new Date(spanner.getTimestamp(spannerIndex).toSqlTimestamp().getTime());
       case BOOL:
+      case FLOAT32:
       case FLOAT64:
       case INT64:
       case NUMERIC:
@@ -508,6 +531,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
         return isNull ? null : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(spannerIndex));
       case BOOL:
       case DATE:
+      case FLOAT32:
       case FLOAT64:
       case INT64:
       case NUMERIC:
@@ -536,6 +560,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
       case TIMESTAMP:
         return isNull ? null : JdbcTypeConverter.toSqlTimestamp(spanner.getTimestamp(spannerIndex));
       case BOOL:
+      case FLOAT32:
       case FLOAT64:
       case INT64:
       case NUMERIC:
@@ -694,6 +719,9 @@ class JdbcResultSet extends AbstractJdbcResultSet {
     if (type == Type.bool()) return getBoolean(columnIndex);
     if (type == Type.bytes()) return getBytes(columnIndex);
     if (type == Type.date()) return getDate(columnIndex);
+    if (type == Type.float32()) {
+      return getFloat(columnIndex);
+    }
     if (type == Type.float64()) return getDouble(columnIndex);
     if (type == Type.int64()) return getLong(columnIndex);
     if (type == Type.numeric()) return getBigDecimal(columnIndex);
@@ -774,6 +802,9 @@ class JdbcResultSet extends AbstractJdbcResultSet {
                 ? null
                 : (spanner.getBoolean(columnIndex - 1) ? BigDecimal.ONE : BigDecimal.ZERO);
         break;
+      case FLOAT32:
+        res = isNull ? null : BigDecimal.valueOf(spanner.getFloat(spannerIndex));
+        break;
       case FLOAT64:
         res = isNull ? null : BigDecimal.valueOf(spanner.getDouble(spannerIndex));
         break;
@@ -886,6 +917,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
         return new Date(
             JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(spannerIndex), cal).getTime());
       case BOOL:
+      case FLOAT32:
       case FLOAT64:
       case INT64:
       case NUMERIC:
@@ -917,6 +949,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
         return isNull ? null : JdbcTypeConverter.toSqlTime(spanner.getTimestamp(spannerIndex), cal);
       case BOOL:
       case DATE:
+      case FLOAT32:
       case FLOAT64:
       case INT64:
       case NUMERIC:
@@ -951,6 +984,7 @@ class JdbcResultSet extends AbstractJdbcResultSet {
       case TIMESTAMP:
         return JdbcTypeConverter.getAsSqlTimestamp(spanner.getTimestamp(spannerIndex), cal);
       case BOOL:
+      case FLOAT32:
       case FLOAT64:
       case INT64:
       case NUMERIC:
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java
index 26226bc..ff1ec2e 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java
@@ -95,6 +95,9 @@ class JdbcResultSetMetaData extends AbstractJdbcWrapper implements ResultSetMeta
         return binaryLength == 0 ? DEFAULT_COL_DISPLAY_SIZE_FOR_VARIABLE_LENGTH_COLS : binaryLength;
       case Types.DATE:
         return 10;
+      case Types.REAL:
+        return JdbcDataType.FLOAT32.getDefaultColumnDisplaySize();
+      case Types.FLOAT:
       case Types.DOUBLE:
         return 14;
       case Types.BIGINT:
@@ -134,6 +137,9 @@ class JdbcResultSetMetaData extends AbstractJdbcWrapper implements ResultSetMeta
         return 1;
       case Types.DATE:
         return 10;
+      case Types.REAL:
+        return JdbcDataType.FLOAT32.getPrecision();
+      case Types.FLOAT:
       case Types.DOUBLE:
         return 14;
       case Types.BIGINT:
@@ -155,6 +161,9 @@ class JdbcResultSetMetaData extends AbstractJdbcWrapper implements ResultSetMeta
   @Override
   public int getScale(int column) {
     int colType = getColumnType(column);
+    if (colType == Types.REAL) {
+      return JdbcDataType.FLOAT32.getScale();
+    }
     if (colType == Types.DOUBLE || colType == Types.NUMERIC) {
       return 15;
     }
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 a506998..c6be0a8 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java
@@ -91,6 +91,9 @@ class JdbcTypeConverter {
       if (targetType.equals(Boolean.class)) {
         if (type.getCode() == Code.BOOL) return value;
         if (type.getCode() == Code.INT64) return (Long) value != 0;
+        if (type.getCode() == Code.FLOAT32) {
+          return (Float) value != 0f;
+        }
         if (type.getCode() == Code.FLOAT64) return (Double) value != 0d;
         if (type.getCode() == Code.NUMERIC) return !value.equals(BigDecimal.ZERO);
       }
@@ -134,6 +137,9 @@ class JdbcTypeConverter {
       if (targetType.equals(Float.class)) {
         if (type.getCode() == Code.BOOL)
           return (Boolean) value ? Float.valueOf(1f) : Float.valueOf(0f);
+        if (type.getCode() == Code.FLOAT32) {
+          return value;
+        }
         if (type.getCode() == Code.FLOAT64)
           return AbstractJdbcWrapper.checkedCastToFloat((Double) value);
         if (type.getCode() == Code.NUMERIC) return ((BigDecimal) value).floatValue();
@@ -141,7 +147,9 @@ class JdbcTypeConverter {
       if (targetType.equals(Double.class)) {
         if (type.getCode() == Code.BOOL)
           return (Boolean) value ? Double.valueOf(1d) : Double.valueOf(0d);
-        if (type.getCode() == Code.FLOAT64) return value;
+        if (type.getCode() == Code.FLOAT64 || type.getCode() == Code.FLOAT32) {
+          return value;
+        }
         if (type.getCode() == Code.NUMERIC) return ((BigDecimal) value).doubleValue();
       }
       if (targetType.equals(java.sql.Date.class)) {
@@ -190,6 +198,8 @@ class JdbcTypeConverter {
           case DATE:
             return Value.dateArray(
                 toGoogleDates((java.sql.Date[]) ((java.sql.Array) value).getArray()));
+          case FLOAT32:
+            return Value.float32Array(Arrays.asList((Float[]) ((java.sql.Array) value).getArray()));
           case FLOAT64:
             return Value.float64Array(
                 Arrays.asList((Double[]) ((java.sql.Array) value).getArray()));
@@ -222,6 +232,8 @@ class JdbcTypeConverter {
         return Value.bytes(ByteArray.copyFrom((byte[]) value));
       case DATE:
         return Value.date(toGoogleDate((java.sql.Date) value));
+      case FLOAT32:
+        return Value.float32((Float) value);
       case FLOAT64:
         return Value.float64((Double) value);
       case INT64:
@@ -260,6 +272,9 @@ class JdbcTypeConverter {
     JdbcPreconditions.checkArgument(
         type.getCode() != Code.DATE || value.getClass().equals(java.sql.Date.class),
         "input type is date, but input value is not an instance of java.sql.Date");
+    JdbcPreconditions.checkArgument(
+        type.getCode() != Code.FLOAT32 || value.getClass().equals(Float.class),
+        "input type is float32, but input value is not an instance of Float");
     JdbcPreconditions.checkArgument(
         type.getCode() != Code.FLOAT64 || value.getClass().equals(Double.class),
         "input type is float64, but input value is not an instance of Double");


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AllTypesMockServerTest.java b/src/test/java/com/google/cloud/spanner/jdbc/AllTypesMockServerTest.java
new file mode 100644
index 0000000..4a1eb6a
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/jdbc/AllTypesMockServerTest.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.jdbc;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.ByteArray;
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
+import com.google.cloud.spanner.Statement;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.Value;
+import com.google.spanner.v1.ExecuteSqlRequest;
+import com.google.spanner.v1.Type;
+import com.google.spanner.v1.TypeCode;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AllTypesMockServerTest
+    extends com.google.cloud.spanner.connection.AllTypesMockServerTest {
+
+  @Override
+  @Test
+  public void testSelectAllTypes() {
+    try (Connection connection = createJdbcConnection()) {
+      try (ResultSet resultSet =
+          connection.createStatement().executeQuery(SELECT_STATEMENT.getSql())) {
+        assertTrue(resultSet.next());
+
+        int col = 0;
+        assertEquals(BOOL_VALUE, resultSet.getBoolean(++col));
+        assertEquals(INT64_VALUE, resultSet.getLong(++col));
+        assertEquals(FLOAT32_VALUE, resultSet.getFloat(++col), 0.0f);
+        assertEquals(FLOAT64_VALUE, resultSet.getDouble(++col), 0.0d);
+        if (dialect == Dialect.POSTGRESQL) {
+          assertEquals(PG_NUMERIC_VALUE, resultSet.getString(++col));
+        } else {
+          assertEquals(NUMERIC_VALUE, resultSet.getBigDecimal(++col));
+        }
+        assertEquals(STRING_VALUE, resultSet.getString(++col));
+        assertEquals(JSON_VALUE, resultSet.getString(++col));
+        assertArrayEquals(BYTES_VALUE, resultSet.getBytes(++col));
+        assertEquals(
+            new java.sql.Date(
+                DATE_VALUE.getYear() - 1900, DATE_VALUE.getMonth() - 1, DATE_VALUE.getDayOfMonth()),
+            resultSet.getDate(++col));
+        assertEquals(TIMESTAMP_VALUE.toSqlTimestamp(), resultSet.getTimestamp(++col));
+
+        assertEquals(
+            BOOL_ARRAY_VALUE, Arrays.asList((Boolean[]) resultSet.getArray(++col).getArray()));
+        assertEquals(
+            INT64_ARRAY_VALUE, Arrays.asList((Long[]) resultSet.getArray(++col).getArray()));
+        assertEquals(
+            FLOAT32_ARRAY_VALUE, Arrays.asList((Float[]) resultSet.getArray(++col).getArray()));
+        assertEquals(
+            FLOAT64_ARRAY_VALUE, Arrays.asList((Double[]) resultSet.getArray(++col).getArray()));
+        if (dialect == Dialect.POSTGRESQL) {
+          // TODO: Fix getting an ARRAY<PG_NUMERIC>. This currently tries to cast it to a
+          // BigDecimal[], which is not supported for NaN values.
+          // assertEquals(PG_NUMERIC_ARRAY_VALUE, Arrays.asList((String[])
+          // resultSet.getArray(++col).getArray()));
+          ++col;
+        } else {
+          assertEquals(
+              NUMERIC_ARRAY_VALUE,
+              Arrays.asList((BigDecimal[]) resultSet.getArray(++col).getArray()));
+        }
+        assertEquals(
+            STRING_ARRAY_VALUE, Arrays.asList((String[]) resultSet.getArray(++col).getArray()));
+        assertEquals(
+            JSON_ARRAY_VALUE, Arrays.asList((String[]) resultSet.getArray(++col).getArray()));
+        // Convert bytes to base64 strings, as the equals(..) method for byte[] uses ==.
+        assertEquals(
+            BYTES_ARRAY_VALUE.stream()
+                .map(bytes -> bytes == null ? null : bytes.toBase64())
+                .collect(Collectors.toList()),
+            Arrays.stream((byte[][]) resultSet.getArray(++col).getArray())
+                .map(bytes -> bytes == null ? null : Base64.getEncoder().encodeToString(bytes))
+                .collect(Collectors.toList()));
+        assertEquals(
+            DATE_ARRAY_VALUE.stream()
+                .map(
+                    date ->
+                        date == null
+                            ? null
+                            : new Date(
+                                date.getYear() - 1900, date.getMonth() - 1, date.getDayOfMonth()))
+                .collect(Collectors.toList()),
+            Arrays.asList((Date[]) resultSet.getArray(++col).getArray()));
+        assertEquals(
+            TIMESTAMP_ARRAY_VALUE.stream()
+                .map(timestamp -> timestamp == null ? null : timestamp.toSqlTimestamp())
+                .collect(Collectors.toList()),
+            Arrays.asList((Timestamp[]) resultSet.getArray(++col).getArray()));
+
+        assertFalse(resultSet.next());
+      }
+    } catch (SQLException sqlException) {
+      throw new RuntimeException(sqlException);
+    }
+  }
+
+  @Override
+  @Test
+  public void testInsertAllTypes() {
+    // TODO: Remove when PG_NUMERIC NaN is supported.
+    if (dialect == Dialect.POSTGRESQL) {
+      Statement insertStatement = createInsertStatement(dialect);
+      insertStatement =
+          insertStatement
+              .toBuilder()
+              .replace(insertStatement.getSql().replaceAll("@p", "\\$"))
+              .bind("p15")
+              .to(
+                  com.google.cloud.spanner.Value.pgNumericArray(
+                      NUMERIC_ARRAY_VALUE.stream()
+                          .map(
+                              bigDecimal ->
+                                  bigDecimal == null ? null : bigDecimal.toEngineeringString())
+                          .collect(Collectors.toList())))
+              .build();
+      mockSpanner.putStatementResult(StatementResult.update(insertStatement, 1L));
+    }
+    try (Connection connection = createJdbcConnection()) {
+      try (PreparedStatement statement =
+          connection.prepareStatement(
+              createInsertStatement(dialect).getSql().replaceAll("@p\\d+", "?"))) {
+        int param = 0;
+        statement.setBoolean(++param, BOOL_VALUE);
+        statement.setLong(++param, INT64_VALUE);
+        statement.setFloat(++param, FLOAT32_VALUE);
+        statement.setDouble(++param, FLOAT64_VALUE);
+        if (dialect == Dialect.POSTGRESQL) {
+          statement.setBigDecimal(++param, new BigDecimal(PG_NUMERIC_VALUE));
+        } else {
+          statement.setBigDecimal(++param, NUMERIC_VALUE);
+        }
+        statement.setString(++param, STRING_VALUE);
+        if (dialect == Dialect.POSTGRESQL) {
+          statement.setObject(++param, JSON_VALUE, JdbcDataType.PG_JSONB.getSqlType());
+        } else {
+          statement.setObject(++param, JSON_VALUE, JdbcDataType.JSON.getSqlType());
+        }
+        statement.setBytes(++param, BYTES_VALUE);
+        statement.setDate(
+            ++param,
+            new Date(
+                DATE_VALUE.getYear() - 1900,
+                DATE_VALUE.getMonth() - 1,
+                DATE_VALUE.getDayOfMonth()));
+        statement.setTimestamp(++param, TIMESTAMP_VALUE.toSqlTimestamp());
+
+        // TODO: Support PostgreSQL type names for creating arrays.
+        statement.setArray(
+            ++param, connection.createArrayOf("BOOL", BOOL_ARRAY_VALUE.toArray(new Boolean[0])));
+        statement.setArray(
+            ++param, connection.createArrayOf("INT64", INT64_ARRAY_VALUE.toArray(new Long[0])));
+        statement.setArray(
+            ++param,
+            connection.createArrayOf("FLOAT32", FLOAT32_ARRAY_VALUE.toArray(new Float[0])));
+        statement.setArray(
+            ++param,
+            connection.createArrayOf("FLOAT64", FLOAT64_ARRAY_VALUE.toArray(new Double[0])));
+
+        // TODO: Make dialect-specific when NaN in arrays for PG_NUMERIC is supported.
+        //        statement.setArray(
+        //            ++param,
+        //            connection.createArrayOf(
+        //                "NUMERIC",
+        //                dialect == Dialect.POSTGRESQL
+        //                    ? PG_NUMERIC_ARRAY_VALUE.toArray(new String[0])
+        //                    : NUMERIC_ARRAY_VALUE.toArray(new BigDecimal[0])));
+        statement.setArray(
+            ++param,
+            connection.createArrayOf(
+                dialect == Dialect.POSTGRESQL ? "PG_NUMERIC" : "NUMERIC",
+                NUMERIC_ARRAY_VALUE.toArray(new BigDecimal[0])));
+
+        statement.setArray(
+            ++param, connection.createArrayOf("STRING", STRING_ARRAY_VALUE.toArray(new String[0])));
+        statement.setArray(
+            ++param,
+            connection.createArrayOf(
+                dialect == Dialect.POSTGRESQL ? "JSONB" : "JSON",
+                JSON_ARRAY_VALUE.toArray(new String[0])));
+        statement.setArray(
+            ++param,
+            connection.createArrayOf(
+                "BYTES",
+                BYTES_ARRAY_VALUE.stream()
+                    .map(bytes -> bytes == null ? null : bytes.toByteArray())
+                    .toArray(byte[][]::new)));
+        statement.setArray(
+            ++param,
+            connection.createArrayOf(
+                "DATE",
+                DATE_ARRAY_VALUE.stream()
+                    .map(
+                        date ->
+                            date == null
+                                ? null
+                                : new Date(
+                                    date.getYear() - 1900,
+                                    date.getMonth() - 1,
+                                    date.getDayOfMonth()))
+                    .toArray(Date[]::new)));
+        statement.setArray(
+            ++param,
+            connection.createArrayOf(
+                "TIMESTAMP",
+                TIMESTAMP_ARRAY_VALUE.stream()
+                    .map(timestamp -> timestamp == null ? null : timestamp.toSqlTimestamp())
+                    .toArray(Timestamp[]::new)));
+
+        assertEquals(1, statement.executeUpdate());
+      }
+
+      assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
+      ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
+      Map<String, Type> paramTypes = request.getParamTypesMap();
+      Map<String, Value> params = request.getParams().getFieldsMap();
+      assertEquals(20, paramTypes.size());
+      assertEquals(20, params.size());
+
+      // Verify param types.
+      ImmutableList<TypeCode> expectedTypes =
+          ImmutableList.of(
+              TypeCode.BOOL,
+              TypeCode.INT64,
+              TypeCode.FLOAT32,
+              TypeCode.FLOAT64,
+              TypeCode.NUMERIC,
+              TypeCode.STRING,
+              TypeCode.JSON,
+              TypeCode.BYTES,
+              TypeCode.DATE,
+              TypeCode.TIMESTAMP);
+      for (int col = 0; col < expectedTypes.size(); col++) {
+        assertEquals(expectedTypes.get(col), paramTypes.get("p" + (col + 1)).getCode());
+        int arrayCol = col + expectedTypes.size();
+        assertEquals(TypeCode.ARRAY, paramTypes.get("p" + (arrayCol + 1)).getCode());
+        assertEquals(
+            expectedTypes.get(col),
+            paramTypes.get("p" + (arrayCol + 1)).getArrayElementType().getCode());
+      }
+
+      // Verify param values.
+      int col = 0;
+      assertEquals(BOOL_VALUE, params.get("p" + ++col).getBoolValue());
+      assertEquals(String.valueOf(INT64_VALUE), params.get("p" + ++col).getStringValue());
+      assertEquals(FLOAT32_VALUE, params.get("p" + ++col).getNumberValue(), 0.0d);
+      assertEquals(FLOAT64_VALUE, params.get("p" + ++col).getNumberValue(), 0.0d);
+      assertEquals(
+          dialect == Dialect.POSTGRESQL ? PG_NUMERIC_VALUE : NUMERIC_VALUE.toEngineeringString(),
+          params.get("p" + ++col).getStringValue());
+      assertEquals(STRING_VALUE, params.get("p" + ++col).getStringValue());
+      assertEquals(JSON_VALUE, params.get("p" + ++col).getStringValue());
+      assertEquals(
+          Base64.getEncoder().encodeToString(BYTES_VALUE),
+          params.get("p" + ++col).getStringValue());
+      assertEquals(DATE_VALUE.toString(), params.get("p" + ++col).getStringValue());
+      assertEquals(TIMESTAMP_VALUE.toString(), params.get("p" + ++col).getStringValue());
+
+      assertEquals(
+          BOOL_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(value -> value.hasNullValue() ? null : value.getBoolValue())
+              .collect(Collectors.toList()));
+      assertEquals(
+          INT64_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(value -> value.hasNullValue() ? null : Long.valueOf(value.getStringValue()))
+              .collect(Collectors.toList()));
+      assertEquals(
+          FLOAT32_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(value -> value.hasNullValue() ? null : (float) value.getNumberValue())
+              .collect(Collectors.toList()));
+      assertEquals(
+          FLOAT64_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(value -> value.hasNullValue() ? null : value.getNumberValue())
+              .collect(Collectors.toList()));
+      if (dialect == Dialect.POSTGRESQL) {
+        // TODO: Replace with PG specific value when NaN is supported.
+        //          assertEquals(
+        //              PG_NUMERIC_ARRAY_VALUE,
+        //              params.get("p" + ++col).getListValue().getValuesList().stream()
+        //                  .map(value -> value.hasNullValue() ? null : value.getStringValue())
+        //                  .collect(Collectors.toList()));
+        assertEquals(
+            NUMERIC_ARRAY_VALUE,
+            params.get("p" + ++col).getListValue().getValuesList().stream()
+                .map(value -> value.hasNullValue() ? null : new BigDecimal(value.getStringValue()))
+                .collect(Collectors.toList()));
+      } else {
+        assertEquals(
+            NUMERIC_ARRAY_VALUE,
+            params.get("p" + ++col).getListValue().getValuesList().stream()
+                .map(value -> value.hasNullValue() ? null : new BigDecimal(value.getStringValue()))
+                .collect(Collectors.toList()));
+      }
+      assertEquals(
+          STRING_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(value -> value.hasNullValue() ? null : value.getStringValue())
+              .collect(Collectors.toList()));
+      assertEquals(
+          JSON_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(value -> value.hasNullValue() ? null : value.getStringValue())
+              .collect(Collectors.toList()));
+      assertEquals(
+          BYTES_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(
+                  value ->
+                      value.hasNullValue() ? null : ByteArray.fromBase64(value.getStringValue()))
+              .collect(Collectors.toList()));
+      assertEquals(
+          DATE_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(
+                  value ->
+                      value.hasNullValue()
+                          ? null
+                          : com.google.cloud.Date.parseDate(value.getStringValue()))
+              .collect(Collectors.toList()));
+      assertEquals(
+          TIMESTAMP_ARRAY_VALUE,
+          params.get("p" + ++col).getListValue().getValuesList().stream()
+              .map(
+                  value ->
+                      value.hasNullValue()
+                          ? null
+                          : com.google.cloud.Timestamp.parseTimestamp(value.getStringValue()))
+              .collect(Collectors.toList()));
+    } catch (SQLException sqlException) {
+      throw new RuntimeException(sqlException);
+    }
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java
index 16e16ad..e39e28a 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcArrayTest.java
@@ -95,6 +95,24 @@ public class JdbcArrayTest {
       assertThat(rs.next()).isFalse();
     }
 
+    array =
+        JdbcArray.createArray(
+            "FLOAT32", new Float[] {1.1f, 2.2f, Double.valueOf(Math.PI).floatValue()});
+    assertEquals(Types.REAL, array.getBaseType());
+    assertThat(((Float[]) array.getArray(1, 3))[2]).isEqualTo(Double.valueOf(Math.PI).floatValue());
+    assertEquals(Double.valueOf(Math.PI).floatValue(), ((Float[]) array.getArray(1, 3))[2], 0.0f);
+    try (ResultSet resultSet = array.getResultSet()) {
+      assertTrue(resultSet.next());
+      // Column index 2 of a JDBC array is the value.
+      // Column index 1 of a JDBC array is the index.
+      assertEquals(1.1f, resultSet.getFloat(2), 0.0f);
+      assertTrue(resultSet.next());
+      assertEquals(2.2f, resultSet.getFloat(2), 0.0f);
+      assertTrue(resultSet.next());
+      assertEquals(Double.valueOf(Math.PI).floatValue(), resultSet.getFloat(2), 0.0f);
+      assertFalse(resultSet.next());
+    }
+
     array = JdbcArray.createArray("FLOAT64", new Double[] {1.1D, 2.2D, Math.PI});
     assertThat(array.getBaseType()).isEqualTo(Types.DOUBLE);
     assertThat(((Double[]) array.getArray(1, 3))[2]).isEqualTo(Math.PI);
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 9782e96..1dc9565 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java
@@ -487,6 +487,8 @@ public class JdbcDatabaseMetaDataTest {
         assertTrue(rs.next());
         assertEquals("BYTES", rs.getString("TYPE_NAME"));
         assertTrue(rs.next());
+        assertEquals("FLOAT32", rs.getString("TYPE_NAME"));
+        assertTrue(rs.next());
         assertEquals("FLOAT64", rs.getString("TYPE_NAME"));
         assertTrue(rs.next());
         assertEquals("BOOL", rs.getString("TYPE_NAME"));
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 869655b..ba674b2 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java
@@ -147,9 +147,9 @@ public class JdbcParameterStoreTest {
     params.setParameter(1, 1L, Types.BIGINT);
     assertEquals(1, ((Long) params.getParameter(1)).longValue());
     verifyParameter(params, Value.int64(1));
-    params.setParameter(1, (float) 1, Types.FLOAT);
+    params.setParameter(1, (float) 1, Types.REAL);
     assertEquals(1.0f, (Float) params.getParameter(1), 0.0f);
-    verifyParameter(params, Value.float64(1));
+    verifyParameter(params, Value.float32(1));
     params.setParameter(1, (double) 1, Types.DOUBLE);
     assertEquals(1.0d, (Double) params.getParameter(1), 0.0d);
     verifyParameter(params, Value.float64(1));
@@ -266,8 +266,33 @@ public class JdbcParameterStoreTest {
       verifyParameter(params, Value.int64(1));
     }
 
+    // types that should lead to float32
+    for (int type : new int[] {Types.REAL}) {
+      params.setParameter(1, (byte) 1, type);
+      assertEquals(1, ((Byte) params.getParameter(1)).byteValue());
+      verifyParameter(params, Value.float32(1));
+      params.setParameter(1, (short) 1, type);
+      assertEquals(1, ((Short) params.getParameter(1)).shortValue());
+      verifyParameter(params, Value.float32(1));
+      params.setParameter(1, 1, type);
+      assertEquals(1, ((Integer) params.getParameter(1)).intValue());
+      verifyParameter(params, Value.float32(1));
+      params.setParameter(1, 1L, type);
+      assertEquals(1, ((Long) params.getParameter(1)).longValue());
+      verifyParameter(params, Value.float32(1));
+      params.setParameter(1, (float) 1, type);
+      assertEquals(1.0f, (Float) params.getParameter(1), 0.0f);
+      verifyParameter(params, Value.float32(1));
+      params.setParameter(1, (double) 1, type);
+      assertEquals(1.0d, (Double) params.getParameter(1), 0.0d);
+      verifyParameter(params, Value.float32(1));
+      params.setParameter(1, BigDecimal.ONE, type);
+      assertEquals(BigDecimal.ONE, params.getParameter(1));
+      verifyParameter(params, Value.float32(1));
+    }
+
     // types that should lead to float64
-    for (int type : new int[] {Types.FLOAT, Types.REAL, Types.DOUBLE}) {
+    for (int type : new int[] {Types.FLOAT, Types.DOUBLE}) {
       params.setParameter(1, (byte) 1, type);
       assertEquals(1, ((Byte) params.getParameter(1)).byteValue());
       verifyParameter(params, Value.float64(1));
@@ -438,8 +463,15 @@ public class JdbcParameterStoreTest {
       assertInvalidParameter(params, Boolean.TRUE, type);
     }
 
+    // types that should not be valid float32 parameters.
+    for (int type : new int[] {Types.REAL}) {
+      assertInvalidParameter(params, "1", type);
+      assertInvalidParameter(params, new Object(), type);
+      assertInvalidParameter(params, Boolean.TRUE, type);
+    }
+
     // types that should lead to float64
-    for (int type : new int[] {Types.FLOAT, Types.REAL, Types.DOUBLE}) {
+    for (int type : new int[] {Types.FLOAT, Types.DOUBLE}) {
       assertInvalidParameter(params, "1", type);
       assertInvalidParameter(params, new Object(), type);
       assertInvalidParameter(params, Boolean.TRUE, type);
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaDataTest.java
index a46a1c3..53b58dc 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaDataTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaDataTest.java
@@ -73,6 +73,8 @@ public class JdbcResultSetMetaDataTest {
           return 1;
         case DATE:
           return 10;
+        case FLOAT32:
+          return 7;
         case FLOAT64:
           return 14;
         case INT64:
@@ -189,6 +191,7 @@ public class JdbcResultSetMetaDataTest {
     types.add(Type.bool());
     types.add(Type.bytes());
     types.add(Type.date());
+    types.add(Type.float32());
     types.add(Type.float64());
     types.add(Type.int64());
     types.add(Type.string());
@@ -237,6 +240,8 @@ public class JdbcResultSetMetaDataTest {
         return Value.numeric(new BigDecimal("3.14"));
       case PG_NUMERIC:
         return Value.pgNumeric("3.14");
+      case FLOAT32:
+        return Value.float32(6.626f);
       case FLOAT64:
         return Value.float64(123.45D);
       case STRING:
@@ -261,6 +266,8 @@ public class JdbcResultSetMetaDataTest {
             return Value.numericArray(Arrays.asList(BigDecimal.ONE, BigDecimal.TEN));
           case PG_NUMERIC:
             return Value.pgNumericArray(Arrays.asList("3.14", null, "NaN", "6.626"));
+          case FLOAT32:
+            return Value.float32Array(Arrays.asList(-3498.31490f, 82.353f));
           case FLOAT64:
             return Value.float64Array(Arrays.asList(123.45D, 543.21D));
           case STRING:
@@ -371,6 +378,8 @@ public class JdbcResultSetMetaDataTest {
         return 5;
       case INT64:
         return 10;
+      case FLOAT32:
+        return 7;
       case NUMERIC:
       case PG_NUMERIC:
         return 14;
@@ -432,6 +441,8 @@ public class JdbcResultSetMetaDataTest {
         return 1;
       case DATE:
         return 10;
+      case FLOAT32:
+        return 7;
       case FLOAT64:
         return 14;
       case INT64:
@@ -460,6 +471,9 @@ public class JdbcResultSetMetaDataTest {
   }
 
   private int getScale(TestColumn col) {
+    if (col.type == Type.float32()) {
+      return 7;
+    }
     if (col.type == Type.float64() || col.type == Type.numeric() || col.type == Type.pgNumeric()) {
       return 15;
     }
@@ -495,6 +509,8 @@ public class JdbcResultSetMetaDataTest {
       case NUMERIC:
       case PG_NUMERIC:
         return Types.NUMERIC;
+      case FLOAT32:
+        return Types.REAL;
       case FLOAT64:
         return Types.DOUBLE;
       case STRING:
@@ -562,6 +578,8 @@ public class JdbcResultSetMetaDataTest {
       case NUMERIC:
       case PG_NUMERIC:
         return BigDecimal.class.getName();
+      case FLOAT32:
+        return Float.class.getName();
       case FLOAT64:
         return Double.class.getName();
       case STRING:
@@ -583,6 +601,8 @@ public class JdbcResultSetMetaDataTest {
           case NUMERIC:
           case PG_NUMERIC:
             return BigDecimal[].class.getName();
+          case FLOAT32:
+            return Float[].class.getName();
           case FLOAT64:
             return Double[].class.getName();
           case STRING:
@@ -610,15 +630,15 @@ public class JdbcResultSetMetaDataTest {
       "Col 1: COL1 BOOL\n"
           + "Col 2: COL2 BYTES\n"
           + "Col 3: COL3 DATE\n"
-          + "Col 4: COL4 FLOAT64\n"
-          + "Col 5: COL5 INT64\n"
-          + "Col 6: COL6 STRING\n"
-          + "Col 7: COL7 JSON\n"
-          + "Col 8: COL8 PG_JSONB\n"
-          + "Col 9: COL9 TIMESTAMP\n"
-          + "Col 10: COL10 NUMERIC\n"
-          + "Col 11: COL11 PG_NUMERIC\n"
-          + "Col 12: COL12 ARRAY\n"
+          + "Col 4: COL4 FLOAT32\n"
+          + "Col 5: COL5 FLOAT64\n"
+          + "Col 6: COL6 INT64\n"
+          + "Col 7: COL7 STRING\n"
+          + "Col 8: COL8 JSON\n"
+          + "Col 9: COL9 PG_JSONB\n"
+          + "Col 10: COL10 TIMESTAMP\n"
+          + "Col 11: COL11 NUMERIC\n"
+          + "Col 12: COL12 PG_NUMERIC\n"
           + "Col 13: COL13 ARRAY\n"
           + "Col 14: COL14 ARRAY\n"
           + "Col 15: COL15 ARRAY\n"
@@ -629,7 +649,9 @@ public class JdbcResultSetMetaDataTest {
           + "Col 20: COL20 ARRAY\n"
           + "Col 21: COL21 ARRAY\n"
           + "Col 22: COL22 ARRAY\n"
-          + "Col 23: CALCULATED INT64\n";
+          + "Col 23: COL23 ARRAY\n"
+          + "Col 24: COL24 ARRAY\n"
+          + "Col 25: CALCULATED INT64\n";
 
   @Test
   public void testToString() {
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/PartitionedQueryMockServerTest.java b/src/test/java/com/google/cloud/spanner/jdbc/PartitionedQueryMockServerTest.java
index be5087f..3184240 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/PartitionedQueryMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/PartitionedQueryMockServerTest.java
@@ -50,6 +50,8 @@ import org.junit.runners.Parameterized.Parameters;
 public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
   @Parameter public Dialect dialect;
 
+  private Dialect currentDialect;
+
   @Parameters(name = "dialect = {0}")
   public static Object[] data() {
     return Dialect.values();
@@ -57,13 +59,21 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
 
   @Before
   public void setupDialect() {
-    mockSpanner.putStatementResult(StatementResult.detectDialectResult(this.dialect));
+    if (currentDialect != dialect) {
+      mockSpanner.putStatementResult(StatementResult.detectDialectResult(this.dialect));
+      currentDialect = dialect;
+      SpannerPool.closeSpannerPool();
+    }
   }
 
   @After
   public void clearRequests() {
     mockSpanner.clearRequests();
-    SpannerPool.closeSpannerPool();
+  }
+
+  private int getExpectedColumnCount(Dialect dialect) {
+    // GoogleSQL also adds 4 PROTO columns.
+    return dialect == Dialect.GOOGLE_STANDARD_SQL ? 24 : 20;
   }
 
   private String createUrl() {
@@ -80,7 +90,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
   public void testPartitionedQueryUsingSql() throws SQLException {
     int numRows = 5;
     int maxPartitions = 4;
-    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows);
+    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows, dialect);
     Statement statement =
         Statement.newBuilder(
                 String.format(
@@ -158,7 +168,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
         partitionStatement.setBoolean(1, true);
         try (ResultSet results = partitionStatement.executeQuery()) {
           assertNotNull(results.getMetaData());
-          assertEquals(24, results.getMetaData().getColumnCount());
+          assertEquals(getExpectedColumnCount(dialect), results.getMetaData().getColumnCount());
           int rowCount = 0;
           while (results.next()) {
             rowCount++;
@@ -184,7 +194,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
   public void testPartitionedQueryStatement() throws SQLException {
     int numRows = 5;
     int maxPartitions = 4;
-    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows);
+    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows, dialect);
     Statement statement = Statement.of("select * from my_table where active=true");
     mockSpanner.putStatementResult(StatementResult.query(statement, generator.generate()));
 
@@ -266,7 +276,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
   public void testPartitionedQueryPreparedStatement() throws SQLException {
     int numRows = 5;
     int maxPartitions = 4;
-    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows);
+    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows, dialect);
     Statement statement =
         Statement.newBuilder(
                 String.format(
@@ -357,7 +367,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
   public void testAutoPartitionMode() throws SQLException {
     int numRows = 5;
     int maxPartitions = 4;
-    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows);
+    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows, dialect);
     Statement statement = Statement.of("select * from my_table where active=true");
     mockSpanner.putStatementResult(StatementResult.query(statement, generator.generate()));
 
@@ -376,7 +386,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
       try (ResultSet results =
           connection.createStatement().executeQuery("select * from my_table where active=true")) {
         assertNotNull(results.getMetaData());
-        assertEquals(24, results.getMetaData().getColumnCount());
+        assertEquals(getExpectedColumnCount(dialect), results.getMetaData().getColumnCount());
         int rowCount = 0;
         while (results.next()) {
           rowCount++;
@@ -463,7 +473,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
   public void testAutoPartitionModeEmptyResult() throws SQLException {
     int numRows = 0;
     int maxPartitions = 1;
-    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows);
+    RandomResultSetGenerator generator = new RandomResultSetGenerator(numRows, dialect);
     Statement statement = Statement.of("select * from my_table where active=true");
     mockSpanner.putStatementResult(StatementResult.query(statement, generator.generate()));
 
@@ -482,7 +492,7 @@ public class PartitionedQueryMockServerTest extends AbstractMockServerTest {
       try (ResultSet results =
           connection.createStatement().executeQuery("select * from my_table where active=true")) {
         assertNotNull(results.getMetaData());
-        assertEquals(24, results.getMetaData().getColumnCount());
+        assertEquals(getExpectedColumnCount(dialect), results.getMetaData().getColumnCount());
         int rowCount = 0;
         while (results.next()) {
           rowCount++;
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/PreparedStatementParameterMetadataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/PreparedStatementParameterMetadataTest.java
index 8b7130e..a6d7c35 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/PreparedStatementParameterMetadataTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/PreparedStatementParameterMetadataTest.java
@@ -60,18 +60,18 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
         MockSpannerServiceImpl.StatementResult.detectDialectResult(Dialect.GOOGLE_STANDARD_SQL));
 
     String baseSql =
-        "insert into all_types (col_bool, col_bytes, col_date, col_float64, col_int64, "
+        "insert into all_types (col_bool, col_bytes, col_date, col_float32, col_float64, col_int64, "
             + "col_json, col_numeric, col_string, col_timestamp, col_bool_array, col_bytes_array, "
-            + "col_date_array, col_float64_array, col_int64_array, col_json_array, col_numeric_array, "
-            + "col_string_array, col_timestamp_array) values (%s)";
+            + "col_date_array, col_float32_array, col_float64_array, col_int64_array, col_json_array,"
+            + "col_numeric_array, col_string_array, col_timestamp_array) values (%s)";
     String jdbcSql =
         String.format(
             baseSql,
-            IntStream.range(0, 18).mapToObj(ignored -> "?").collect(Collectors.joining(", ")));
+            IntStream.range(0, 20).mapToObj(ignored -> "?").collect(Collectors.joining(", ")));
     String googleSql =
         String.format(
             baseSql,
-            IntStream.range(1, 19)
+            IntStream.range(1, 21)
                 .mapToObj(index -> "@p" + index)
                 .collect(Collectors.joining(", ")));
     mockSpanner.putStatementResult(
@@ -89,7 +89,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
     try (Connection connection = createJdbcConnection()) {
       try (PreparedStatement statement = connection.prepareStatement(jdbcSql)) {
         ParameterMetaData metadata = statement.getParameterMetaData();
-        assertEquals(18, metadata.getParameterCount());
+        assertEquals(20, metadata.getParameterCount());
         int index = 0;
         assertEquals(Types.BOOLEAN, metadata.getParameterType(++index));
         assertEquals("BOOL", metadata.getParameterTypeName(index));
@@ -97,6 +97,8 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
         assertEquals("BYTES", metadata.getParameterTypeName(index));
         assertEquals(Types.DATE, metadata.getParameterType(++index));
         assertEquals("DATE", metadata.getParameterTypeName(index));
+        assertEquals(Types.REAL, metadata.getParameterType(++index));
+        assertEquals("FLOAT32", metadata.getParameterTypeName(index));
         assertEquals(Types.DOUBLE, metadata.getParameterType(++index));
         assertEquals("FLOAT64", metadata.getParameterTypeName(index));
         assertEquals(Types.BIGINT, metadata.getParameterType(++index));
@@ -117,6 +119,8 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
         assertEquals(Types.ARRAY, metadata.getParameterType(++index));
         assertEquals("ARRAY<DATE>", metadata.getParameterTypeName(index));
         assertEquals(Types.ARRAY, metadata.getParameterType(++index));
+        assertEquals("ARRAY<FLOAT32>", metadata.getParameterTypeName(index));
+        assertEquals(Types.ARRAY, metadata.getParameterType(++index));
         assertEquals("ARRAY<FLOAT64>", metadata.getParameterTypeName(index));
         assertEquals(Types.ARRAY, metadata.getParameterType(++index));
         assertEquals("ARRAY<INT64>", metadata.getParameterTypeName(index));
@@ -166,7 +170,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
     try (Connection connection = createJdbcConnection()) {
       try (PreparedStatement statement = connection.prepareStatement(jdbcSql)) {
         ParameterMetaData metadata = statement.getParameterMetaData();
-        assertEquals(18, metadata.getParameterCount());
+        assertEquals(20, metadata.getParameterCount());
         int index = 0;
         assertEquals(Types.BOOLEAN, metadata.getParameterType(++index));
         assertEquals("boolean", metadata.getParameterTypeName(index));
@@ -174,6 +178,8 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
         assertEquals("bytea", metadata.getParameterTypeName(index));
         assertEquals(Types.DATE, metadata.getParameterType(++index));
         assertEquals("date", metadata.getParameterTypeName(index));
+        assertEquals(Types.REAL, metadata.getParameterType(++index));
+        assertEquals("real", metadata.getParameterTypeName(index));
         assertEquals(Types.DOUBLE, metadata.getParameterType(++index));
         assertEquals("double precision", metadata.getParameterTypeName(index));
         assertEquals(Types.BIGINT, metadata.getParameterType(++index));
@@ -194,6 +200,8 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
         assertEquals(Types.ARRAY, metadata.getParameterType(++index));
         assertEquals("date[]", metadata.getParameterTypeName(index));
         assertEquals(Types.ARRAY, metadata.getParameterType(++index));
+        assertEquals("real[]", metadata.getParameterTypeName(index));
+        assertEquals(Types.ARRAY, metadata.getParameterType(++index));
         assertEquals("double precision[]", metadata.getParameterTypeName(index));
         assertEquals(Types.ARRAY, metadata.getParameterType(++index));
         assertEquals("bigint[]", metadata.getParameterTypeName(index));
@@ -229,16 +237,21 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
         .addFields(
             Field.newBuilder()
                 .setName("p4")
-                .setType(Type.newBuilder().setCode(TypeCode.FLOAT64).build())
+                .setType(Type.newBuilder().setCode(TypeCode.FLOAT32).build())
                 .build())
         .addFields(
             Field.newBuilder()
                 .setName("p5")
-                .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+                .setType(Type.newBuilder().setCode(TypeCode.FLOAT64).build())
                 .build())
         .addFields(
             Field.newBuilder()
                 .setName("p6")
+                .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+                .build())
+        .addFields(
+            Field.newBuilder()
+                .setName("p7")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.JSON)
@@ -250,7 +263,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p7")
+                .setName("p8")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.NUMERIC)
@@ -262,17 +275,17 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p8")
+                .setName("p9")
                 .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p9")
+                .setName("p10")
                 .setType(Type.newBuilder().setCode(TypeCode.TIMESTAMP).build())
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p10")
+                .setName("p11")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -280,7 +293,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p11")
+                .setName("p12")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -288,7 +301,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p12")
+                .setName("p13")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -296,7 +309,15 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p13")
+                .setName("p14")
+                .setType(
+                    Type.newBuilder()
+                        .setCode(TypeCode.ARRAY)
+                        .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT32).build()))
+                .build())
+        .addFields(
+            Field.newBuilder()
+                .setName("p15")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -304,7 +325,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p14")
+                .setName("p16")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -312,7 +333,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p15")
+                .setName("p17")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -327,7 +348,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p16")
+                .setName("p18")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -342,7 +363,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p17")
+                .setName("p19")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
@@ -350,7 +371,7 @@ public class PreparedStatementParameterMetadataTest extends AbstractMockServerTe
                 .build())
         .addFields(
             Field.newBuilder()
-                .setName("p18")
+                .setName("p20")
                 .setType(
                     Type.newBuilder()
                         .setCode(TypeCode.ARRAY)
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/RandomResultSetTest.java b/src/test/java/com/google/cloud/spanner/jdbc/RandomResultSetTest.java
new file mode 100644
index 0000000..4d24068
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/jdbc/RandomResultSetTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.spanner.jdbc;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
+import com.google.cloud.spanner.connection.AbstractMockServerTest;
+import com.google.cloud.spanner.connection.RandomResultSetGenerator;
+import com.google.cloud.spanner.connection.SpannerPool;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RandomResultSetTest extends AbstractMockServerTest {
+  @Parameter public Dialect dialect;
+
+  @Parameters(name = "dialect = {0}")
+  public static Object[] data() {
+    return Dialect.values();
+  }
+
+  @Before
+  public void setupDialect() {
+    mockSpanner.putStatementResult(StatementResult.detectDialectResult(dialect));
+    RandomResultSetGenerator generator =
+        new RandomResultSetGenerator(RANDOM_RESULT_SET_ROW_COUNT, dialect);
+    mockSpanner.putStatementResult(
+        StatementResult.query(SELECT_RANDOM_STATEMENT, generator.generate()));
+  }
+
+  @After
+  public void reset() {
+    // This ensures that each test gets a fresh Spanner instance. This is necessary to get a new
+    // dialect result for each connection.
+    SpannerPool.closeSpannerPool();
+  }
+
+  @Test
+  public void testSelectRandomResults() throws SQLException {
+    try (Connection connection = createJdbcConnection()) {
+      try (ResultSet resultSet =
+          connection.createStatement().executeQuery(SELECT_RANDOM_STATEMENT.getSql())) {
+        ResultSetMetaData metadata = resultSet.getMetaData();
+        int col = 0;
+        assertEquals(Types.BOOLEAN, metadata.getColumnType(++col));
+        assertEquals(Types.BIGINT, metadata.getColumnType(++col));
+        assertEquals(Types.REAL, metadata.getColumnType(++col));
+        assertEquals(Types.DOUBLE, metadata.getColumnType(++col));
+        assertEquals(Types.NUMERIC, metadata.getColumnType(++col));
+        assertEquals(Types.NVARCHAR, metadata.getColumnType(++col));
+        assertEquals(Types.NVARCHAR, metadata.getColumnType(++col));
+        assertEquals(Types.BINARY, metadata.getColumnType(++col));
+        assertEquals(Types.DATE, metadata.getColumnType(++col));
+        assertEquals(Types.TIMESTAMP, metadata.getColumnType(++col));
+
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // boolean
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // bigint
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // real
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // double
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // numeric
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // nvarchar
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // nvarchar
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // binary
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // date
+        assertEquals(Types.ARRAY, metadata.getColumnType(++col)); // timestamp
+
+        // GoogleSQL also includes proto columns.
+        if (dialect == Dialect.GOOGLE_STANDARD_SQL) {
+          // Skip for now, as support for this has not yet been added.
+          // assertEquals(Types.OTHER, metadata.getColumnType(++col));
+          // assertEquals(Types.OTHER, metadata.getColumnType(++col));
+          // assertEquals(Types.ARRAY, metadata.getColumnType(++col));
+          // assertEquals(Types.ARRAY, metadata.getColumnType(++col));
+          col += 4;
+        }
+
+        assertEquals(col, metadata.getColumnCount());
+
+        int rowCount = 0;
+        while (resultSet.next()) {
+          // Verify that we can get all columns as an object.
+          for (col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) {
+            if (dialect == Dialect.GOOGLE_STANDARD_SQL && col > 20) {
+              // Proto columns are not yet supported, so skipping.
+            } else if (dialect == Dialect.POSTGRESQL && col == 7) {
+              // PG_JSONB is not yet recognized by the JDBC driver, so skipping.
+            } else if (dialect == Dialect.POSTGRESQL && col == 15) {
+              // getObject for ARRAY<PG_NUMERIC> tries to get the array as a List<BigDecimal>.
+              // That fails if the array contains a NaN, so skipping.
+            } else {
+              resultSet.getObject(col);
+            }
+          }
+
+          // Verify that we can get the results as the actual type.
+          col = 0;
+          resultSet.getBoolean(++col);
+          resultSet.getLong(++col);
+          resultSet.getFloat(++col);
+          resultSet.getDouble(++col);
+          resultSet.getFloat(++col);
+          resultSet.getString(++col);
+          resultSet.getString(++col); // JSON
+          resultSet.getBytes(++col);
+          resultSet.getDate(++col);
+          resultSet.getTimestamp(++col);
+
+          resultSet.getArray(++col);
+          resultSet.getArray(++col);
+          resultSet.getArray(++col);
+          resultSet.getArray(++col);
+          if (dialect == Dialect.POSTGRESQL) {
+            // Skipping, as getArray(..) returns an array of BigDecimal for PG_NUMERIC, and that is
+            // not supported if the array contains a NaN.
+            ++col;
+          } else {
+            resultSet.getArray(++col);
+          }
+          resultSet.getArray(++col);
+          resultSet.getArray(++col);
+          resultSet.getArray(++col);
+          resultSet.getArray(++col);
+          resultSet.getArray(++col);
+
+          // GoogleSQL also includes proto columns.
+          if (dialect == Dialect.GOOGLE_STANDARD_SQL) {
+            // Skip for now, as support for this has not yet been added.
+            col += 4;
+          }
+          assertEquals(col, resultSet.getMetaData().getColumnCount());
+
+          rowCount++;
+        }
+        assertEquals(RANDOM_RESULT_SET_ROW_COUNT, rowCount);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgNumericTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgNumericTest.java
index 5527c8b..2198c94 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgNumericTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgNumericTest.java
@@ -227,7 +227,9 @@ public class ITJdbcPgNumericTest {
       preparedStatement.setShort(3, (short) 1);
       preparedStatement.setInt(4, 1);
       preparedStatement.setLong(5, 1L);
-      preparedStatement.setFloat(6, 1F);
+      // TODO: Change to setFloat(..) when float32 is supported.
+      // preparedStatement.setFloat(6, 1f);
+      preparedStatement.setDouble(6, 1d);
       preparedStatement.setDouble(7, 1D);
       preparedStatement.setBigDecimal(8, new BigDecimal("1"));
       preparedStatement.setObject(9, (byte) 1);
@@ -239,7 +241,9 @@ public class ITJdbcPgNumericTest {
       preparedStatement.setObject(15, new BigDecimal("1"));
       preparedStatement.setObject(16, Value.pgNumeric("1"));
 
-      preparedStatement.setFloat(17, Float.NaN);
+      // TODO: Change to setFloat(..) when float32 is supported.
+      // preparedStatement.setFloat(17, Float.NaN);
+      preparedStatement.setDouble(17, Double.NaN);
       preparedStatement.setDouble(18, Double.NaN);
       preparedStatement.setObject(19, Value.pgNumeric("NaN"));