diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index fbbc0153f89..eaf7637b0b9 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -506,6 +506,48 @@
com.google.cloud.spanner.connection.StatementResult execute(com.google.cloud.spanner.Statement, java.util.Set)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ float getFloat(int)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ float getFloat(java.lang.String)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ float[] getFloatArray(int)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ float[] getFloatArray(java.lang.String)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ java.util.List getFloatList(int)
+
+
+ 7012
+ com/google/cloud/spanner/StructReader
+ java.util.List getFloatList(java.lang.String)
+
+
+ 7013
+ com/google/cloud/spanner/Value
+ float getFloat32()
+
+
+ 7013
+ com/google/cloud/spanner/Value
+ java.util.List getFloat32Array()
+
+
7012
@@ -569,7 +611,7 @@
void setSpan(io.opencensus.trace.Span)void setSpan(com.google.cloud.spanner.ISpan)
-
+
7012
@@ -580,5 +622,5 @@
7012com/google/cloud/spanner/connection/Connectionvoid setDirectedRead(com.google.spanner.v1.DirectedReadOptions)
-
+
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
index 6cce03e72cb..2cf93fb92ec 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
@@ -173,16 +173,44 @@ static double valueProtoToFloat64(com.google.protobuf.Value proto) {
return proto.getNumberValue();
}
+ static float valueProtoToFloat32(com.google.protobuf.Value proto) {
+ if (proto.getKindCase() == KindCase.STRING_VALUE) {
+ switch (proto.getStringValue()) {
+ case "-Infinity":
+ return Float.NEGATIVE_INFINITY;
+ case "Infinity":
+ return Float.POSITIVE_INFINITY;
+ case "NaN":
+ return Float.NaN;
+ default:
+ // Fall-through to handling below to produce an error.
+ }
+ }
+ if (proto.getKindCase() != KindCase.NUMBER_VALUE) {
+ throw newSpannerException(
+ ErrorCode.INTERNAL,
+ "Invalid value for column type "
+ + Type.float32()
+ + " expected NUMBER_VALUE or STRING_VALUE with value one of"
+ + " \"Infinity\", \"-Infinity\", or \"NaN\" but was "
+ + proto.getKindCase()
+ + (proto.getKindCase() == KindCase.STRING_VALUE
+ ? " with value \"" + proto.getStringValue() + "\""
+ : ""));
+ }
+ return (float) proto.getNumberValue();
+ }
+
static NullPointerException throwNotNull(int columnIndex) {
throw new NullPointerException(
"Cannot call array getter for column " + columnIndex + " with null elements");
}
/**
- * Memory-optimized base class for {@code ARRAY} and {@code ARRAY} types. Both of
- * these involve conversions from the type yielded by JSON parsing, which are {@code String} and
- * {@code BigDecimal} respectively. Rather than construct new wrapper objects for each array
- * element, we use primitive arrays and a {@code BitSet} to track nulls.
+ * Memory-optimized base class for {@code ARRAY}, {@code ARRAY} and {@code
+ * ARRAY} types. All of these involve conversions from the type yielded by JSON parsing,
+ * which are {@code String} and {@code BigDecimal} respectively. Rather than construct new wrapper
+ * objects for each array element, we use primitive arrays and a {@code BitSet} to track nulls.
*/
abstract static class PrimitiveArray extends AbstractList {
private final A data;
@@ -264,6 +292,31 @@ Long get(long[] array, int i) {
}
}
+ static class Float32Array extends PrimitiveArray {
+ Float32Array(ListValue protoList) {
+ super(protoList);
+ }
+
+ Float32Array(float[] data, BitSet nulls) {
+ super(data, nulls, data.length);
+ }
+
+ @Override
+ float[] newArray(int size) {
+ return new float[size];
+ }
+
+ @Override
+ void setProto(float[] array, int i, com.google.protobuf.Value protoValue) {
+ array[i] = valueProtoToFloat32(protoValue);
+ }
+
+ @Override
+ Float get(float[] array, int i) {
+ return array[i];
+ }
+ }
+
static class Float64Array extends PrimitiveArray {
Float64Array(ListValue protoList) {
super(protoList);
@@ -306,6 +359,11 @@ protected long getLongInternal(int columnIndex) {
return currRow().getLongInternal(columnIndex);
}
+ @Override
+ protected float getFloatInternal(int columnIndex) {
+ return currRow().getFloatInternal(columnIndex);
+ }
+
@Override
protected double getDoubleInternal(int columnIndex) {
return currRow().getDoubleInternal(columnIndex);
@@ -382,6 +440,16 @@ protected List getLongListInternal(int columnIndex) {
return currRow().getLongListInternal(columnIndex);
}
+ @Override
+ protected float[] getFloatArrayInternal(int columnIndex) {
+ return currRow().getFloatArrayInternal(columnIndex);
+ }
+
+ @Override
+ protected List getFloatListInternal(int columnIndex) {
+ return currRow().getFloatListInternal(columnIndex);
+ }
+
@Override
protected double[] getDoubleArrayInternal(int columnIndex) {
return currRow().getDoubleArrayInternal(columnIndex);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
index ef6f63d52ea..a11c573233b 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
@@ -43,6 +43,10 @@ public abstract class AbstractStructReader implements StructReader {
protected abstract long getLongInternal(int columnIndex);
+ protected float getFloatInternal(int columnIndex) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
protected abstract double getDoubleInternal(int columnIndex);
protected abstract BigDecimal getBigDecimalInternal(int columnIndex);
@@ -94,6 +98,14 @@ protected Value getValueInternal(int columnIndex) {
protected abstract List getLongListInternal(int columnIndex);
+ protected float[] getFloatArrayInternal(int columnIndex) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ protected List getFloatListInternal(int columnIndex) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
protected abstract double[] getDoubleArrayInternal(int columnIndex);
protected abstract List getDoubleListInternal(int columnIndex);
@@ -164,6 +176,19 @@ public long getLong(String columnName) {
return getLongInternal(columnIndex);
}
+ @Override
+ public float getFloat(int columnIndex) {
+ checkNonNullOfType(columnIndex, Type.float32(), columnIndex);
+ return getFloatInternal(columnIndex);
+ }
+
+ @Override
+ public float getFloat(String columnName) {
+ int columnIndex = getColumnIndex(columnName);
+ checkNonNullOfType(columnIndex, Type.float32(), columnName);
+ return getFloatInternal(columnIndex);
+ }
+
@Override
public double getDouble(int columnIndex) {
checkNonNullOfType(columnIndex, Type.float64(), columnIndex);
@@ -368,6 +393,32 @@ public List getLongList(String columnName) {
return getLongListInternal(columnIndex);
}
+ @Override
+ public float[] getFloatArray(int columnIndex) {
+ checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnIndex);
+ return getFloatArrayInternal(columnIndex);
+ }
+
+ @Override
+ public float[] getFloatArray(String columnName) {
+ int columnIndex = getColumnIndex(columnName);
+ checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnName);
+ return getFloatArrayInternal(columnIndex);
+ }
+
+ @Override
+ public List getFloatList(int columnIndex) {
+ checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnIndex);
+ return getFloatListInternal(columnIndex);
+ }
+
+ @Override
+ public List getFloatList(String columnName) {
+ int columnIndex = getColumnIndex(columnName);
+ checkNonNullOfType(columnIndex, Type.array(Type.float32()), columnName);
+ return getFloatListInternal(columnIndex);
+ }
+
@Override
public double[] getDoubleArray(int columnIndex) {
checkNonNullOfType(columnIndex, Type.array(Type.float64()), columnIndex);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java
index 97c39c00a8d..b3e37ffcddb 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ForwardingStructReader.java
@@ -125,6 +125,18 @@ public long getLong(String columnName) {
return delegate.get().getLong(columnName);
}
+ @Override
+ public float getFloat(int columnIndex) {
+ checkValidState();
+ return delegate.get().getFloat(columnIndex);
+ }
+
+ @Override
+ public float getFloat(String columnName) {
+ checkValidState();
+ return delegate.get().getFloat(columnName);
+ }
+
@Override
public double getDouble(int columnIndex) {
checkValidState();
@@ -267,6 +279,30 @@ public List getLongList(String columnName) {
return delegate.get().getLongList(columnName);
}
+ @Override
+ public float[] getFloatArray(int columnIndex) {
+ checkValidState();
+ return delegate.get().getFloatArray(columnIndex);
+ }
+
+ @Override
+ public float[] getFloatArray(String columnName) {
+ checkValidState();
+ return delegate.get().getFloatArray(columnName);
+ }
+
+ @Override
+ public List getFloatList(int columnIndex) {
+ checkValidState();
+ return delegate.get().getFloatList(columnIndex);
+ }
+
+ @Override
+ public List getFloatList(String columnName) {
+ checkValidState();
+ return delegate.get().getFloatList(columnName);
+ }
+
@Override
public double[] getDoubleArray(int columnIndex) {
checkValidState();
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java
index 6be649ae7c9..a6769acfadf 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java
@@ -17,6 +17,7 @@
package com.google.cloud.spanner;
import static com.google.cloud.spanner.AbstractResultSet.throwNotNull;
+import static com.google.cloud.spanner.AbstractResultSet.valueProtoToFloat32;
import static com.google.cloud.spanner.AbstractResultSet.valueProtoToFloat64;
import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException;
import static com.google.common.base.Preconditions.checkArgument;
@@ -24,6 +25,7 @@
import com.google.cloud.ByteArray;
import com.google.cloud.Date;
import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.AbstractResultSet.Float32Array;
import com.google.cloud.spanner.AbstractResultSet.Float64Array;
import com.google.cloud.spanner.AbstractResultSet.Int64Array;
import com.google.cloud.spanner.AbstractResultSet.LazyByteArray;
@@ -83,6 +85,9 @@ private Object writeReplace() {
case FLOAT64:
builder.set(fieldName).to((Double) value);
break;
+ case FLOAT32:
+ builder.set(fieldName).to((Float) value);
+ break;
case NUMERIC:
builder.set(fieldName).to((BigDecimal) value);
break;
@@ -135,6 +140,9 @@ private Object writeReplace() {
case FLOAT64:
builder.set(fieldName).toFloat64Array((Iterable) value);
break;
+ case FLOAT32:
+ builder.set(fieldName).toFloat32Array((Iterable) value);
+ break;
case NUMERIC:
builder.set(fieldName).toNumericArray((Iterable) value);
break;
@@ -259,6 +267,8 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot
return Long.parseLong(proto.getStringValue());
case FLOAT64:
return valueProtoToFloat64(proto);
+ case FLOAT32:
+ return valueProtoToFloat32(proto);
case NUMERIC:
checkType(fieldType, proto, KindCase.STRING_VALUE);
return new BigDecimal(proto.getStringValue());
@@ -310,11 +320,13 @@ static Object decodeArrayValue(Type elementType, ListValue listValue) {
switch (elementType.getCode()) {
case INT64:
case ENUM:
- // For int64/float64/enum types, use custom containers. These avoid wrapper object
- // creation for non-null arrays.
+ // For int64/float64/float32/enum types, use custom containers.
+ // These avoid wrapper object creation for non-null arrays.
return new Int64Array(listValue);
case FLOAT64:
return new Float64Array(listValue);
+ case FLOAT32:
+ return new Float32Array(listValue);
case BOOL:
case NUMERIC:
case PG_NUMERIC:
@@ -418,6 +430,12 @@ protected double getDoubleInternal(int columnIndex) {
return (Double) rowData.get(columnIndex);
}
+ @Override
+ protected float getFloatInternal(int columnIndex) {
+ ensureDecoded(columnIndex);
+ return (Float) rowData.get(columnIndex);
+ }
+
@Override
protected BigDecimal getBigDecimalInternal(int columnIndex) {
ensureDecoded(columnIndex);
@@ -537,6 +555,8 @@ protected Value getValueInternal(int columnIndex) {
return Value.pgNumeric(isNull ? null : getStringInternal(columnIndex));
case FLOAT64:
return Value.float64(isNull ? null : getDoubleInternal(columnIndex));
+ case FLOAT32:
+ return Value.float32(isNull ? null : getFloatInternal(columnIndex));
case STRING:
return Value.string(isNull ? null : getStringInternal(columnIndex));
case JSON:
@@ -570,6 +590,8 @@ protected Value getValueInternal(int columnIndex) {
return Value.pgNumericArray(isNull ? null : getStringListInternal(columnIndex));
case FLOAT64:
return Value.float64Array(isNull ? null : getDoubleListInternal(columnIndex));
+ case FLOAT32:
+ return Value.float32Array(isNull ? null : getFloatListInternal(columnIndex));
case STRING:
return Value.stringArray(isNull ? null : getStringListInternal(columnIndex));
case JSON:
@@ -652,6 +674,18 @@ protected Float64Array getDoubleListInternal(int columnIndex) {
return (Float64Array) rowData.get(columnIndex);
}
+ @Override
+ protected float[] getFloatArrayInternal(int columnIndex) {
+ ensureDecoded(columnIndex);
+ return getFloatListInternal(columnIndex).toPrimitiveArray(columnIndex);
+ }
+
+ @Override
+ protected Float32Array getFloatListInternal(int columnIndex) {
+ ensureDecoded(columnIndex);
+ return (Float32Array) rowData.get(columnIndex);
+ }
+
@Override
@SuppressWarnings("unchecked") // We know ARRAY produces a List.
protected List getBigDecimalListInternal(int columnIndex) {
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java
index 73995a20df8..6c869c549fc 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java
@@ -364,6 +364,8 @@ public int hashCode() {
* mutation equality to check for modifications before committing. We noticed that when NaNs where
* used the template would always indicate a modification was present, when it turned out not to
* be the case. For more information see b/206339664.
+ *
+ *
Similar change is being done while calculating `Value.hashCode()`.
*/
private boolean areValuesEqual(List values, List otherValues) {
if (values == null && otherValues == null) {
@@ -385,9 +387,19 @@ private boolean areValuesEqual(List values, List otherValues) {
}
private boolean isNaN(Value value) {
- return !value.isNull()
- && value.getType().equals(Type.float64())
- && Double.isNaN(value.getFloat64());
+ return !value.isNull() && (isFloat64NaN(value) || isFloat32NaN(value));
+ }
+
+ // Checks if the Float64 value is either a "Double" or a "Float" NaN.
+ // Refer the comment above `areValuesEqual` for more details.
+ private boolean isFloat64NaN(Value value) {
+ return value.getType().equals(Type.float64()) && Double.isNaN(value.getFloat64());
+ }
+
+ // Checks if the Float32 value is either a "Double" or a "Float" NaN.
+ // Refer the comment above `areValuesEqual` for more details.
+ private boolean isFloat32NaN(Value value) {
+ return value.getType().equals(Type.float32()) && Float.isNaN(value.getFloat32());
}
static void toProto(Iterable mutations, List out) {
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java
index a6cc7c729e5..3d12cf5ad2c 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ResultSets.java
@@ -236,6 +236,16 @@ public long getLong(String columnName) {
return getCurrentRowAsStruct().getLong(columnName);
}
+ @Override
+ public float getFloat(int columnIndex) {
+ return getCurrentRowAsStruct().getFloat(columnIndex);
+ }
+
+ @Override
+ public float getFloat(String columnName) {
+ return getCurrentRowAsStruct().getFloat(columnName);
+ }
+
@Override
public double getDouble(int columnIndex) {
return getCurrentRowAsStruct().getDouble(columnIndex);
@@ -388,6 +398,26 @@ public List getLongList(String columnName) {
return getCurrentRowAsStruct().getLongList(columnName);
}
+ @Override
+ public float[] getFloatArray(int columnIndex) {
+ return getCurrentRowAsStruct().getFloatArray(columnIndex);
+ }
+
+ @Override
+ public float[] getFloatArray(String columnName) {
+ return getCurrentRowAsStruct().getFloatArray(columnName);
+ }
+
+ @Override
+ public List getFloatList(int columnIndex) {
+ return getCurrentRowAsStruct().getFloatList(columnIndex);
+ }
+
+ @Override
+ public List getFloatList(String columnName) {
+ return getCurrentRowAsStruct().getFloatList(columnName);
+ }
+
@Override
public double[] getDoubleArray(int columnIndex) {
return getCurrentRowAsStruct().getDoubleArray(columnIndex);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java
index 40c30148d0e..0e65fa7f1ba 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java
@@ -27,6 +27,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Booleans;
import com.google.common.primitives.Doubles;
+import com.google.common.primitives.Floats;
import com.google.common.primitives.Longs;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ProtocolMessageEnum;
@@ -180,6 +181,11 @@ protected long getLongInternal(int columnIndex) {
return values.get(columnIndex).getInt64();
}
+ @Override
+ protected float getFloatInternal(int columnIndex) {
+ return values.get(columnIndex).getFloat32();
+ }
+
@Override
protected double getDoubleInternal(int columnIndex) {
return values.get(columnIndex).getFloat64();
@@ -261,6 +267,16 @@ protected List getLongListInternal(int columnIndex) {
return values.get(columnIndex).getInt64Array();
}
+ @Override
+ protected float[] getFloatArrayInternal(int columnIndex) {
+ return Floats.toArray(getFloatListInternal(columnIndex));
+ }
+
+ @Override
+ protected List getFloatListInternal(int columnIndex) {
+ return values.get(columnIndex).getFloat32Array();
+ }
+
@Override
protected double[] getDoubleArrayInternal(int columnIndex) {
return Doubles.toArray(getDoubleListInternal(columnIndex));
@@ -382,6 +398,8 @@ private Object getAsObject(int columnIndex) {
case INT64:
case ENUM:
return getLongInternal(columnIndex);
+ case FLOAT32:
+ return getFloatInternal(columnIndex);
case FLOAT64:
return getDoubleInternal(columnIndex);
case NUMERIC:
@@ -410,6 +428,8 @@ private Object getAsObject(int columnIndex) {
case INT64:
case ENUM:
return getLongListInternal(columnIndex);
+ case FLOAT32:
+ return getFloatListInternal(columnIndex);
case FLOAT64:
return getDoubleListInternal(columnIndex);
case NUMERIC:
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java
index fd8cb77f397..f9967db0451 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/StructReader.java
@@ -123,6 +123,22 @@ public interface StructReader {
*/
long getLong(String columnName);
+ /**
+ * @param columnIndex index of the column
+ * @return the value of a non-{@code NULL} column with type {@link Type#float32()}.
+ */
+ default float getFloat(int columnIndex) {
+ throw new UnsupportedOperationException("method should be overwritten");
+ }
+
+ /**
+ * @param columnName name of the column
+ * @return the value of a non-{@code NULL} column with type {@link Type#float32()}.
+ */
+ default float getFloat(String columnName) {
+ throw new UnsupportedOperationException("method should be overwritten");
+ }
+
/**
* @param columnIndex index of the column
* @return the value of a non-{@code NULL} column with type {@link Type#float64()}.
@@ -361,6 +377,44 @@ default Value getValue(String columnName) {
*/
List getLongList(String columnName);
+ /**
+ * @param columnIndex index of the column
+ * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())}.
+ * @throws NullPointerException if any element of the array value is {@code NULL}. If the array
+ * may contain {@code NULL} values, use {@link #getFloatList(int)} instead.
+ */
+ default float[] getFloatArray(int columnIndex) {
+ throw new UnsupportedOperationException("method should be overwritten");
+ }
+
+ /**
+ * @param columnName name of the column
+ * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())}.
+ * @throws NullPointerException if any element of the array value is {@code NULL}. If the array
+ * may contain {@code NULL} values, use {@link #getFloatList(String)} instead.
+ */
+ default float[] getFloatArray(String columnName) {
+ throw new UnsupportedOperationException("method should be overwritten");
+ }
+
+ /**
+ * @param columnIndex index of the column
+ * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())} The
+ * list returned by this method is lazily constructed. Create a copy of it if you intend to
+ * access each element in the list multiple times.
+ */
+ default List getFloatList(int columnIndex) {
+ throw new UnsupportedOperationException("method should be overwritten");
+ }
+
+ /**
+ * @param columnName name of the column
+ * @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float32())} The
+ * list returned by this method is lazily constructed. Create a copy of it if you intend to
+ * access each element in the list multiple times.
+ */
+ List getFloatList(String columnName);
+
/**
* @param columnIndex index of the column
* @return the value of a non-{@code NULL} column with type {@code Type.array(Type.float64())}.
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
index 348db5d04ae..5d871227f54 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java
@@ -48,6 +48,7 @@
public final class Type implements Serializable {
private static final Type TYPE_BOOL = new Type(Code.BOOL, null, null);
private static final Type TYPE_INT64 = new Type(Code.INT64, null, null);
+ private static final Type TYPE_FLOAT32 = new Type(Code.FLOAT32, null, null);
private static final Type TYPE_FLOAT64 = new Type(Code.FLOAT64, null, null);
private static final Type TYPE_NUMERIC = new Type(Code.NUMERIC, null, null);
private static final Type TYPE_PG_NUMERIC = new Type(Code.PG_NUMERIC, null, null);
@@ -59,6 +60,7 @@ public final class Type implements Serializable {
private static final Type TYPE_DATE = new Type(Code.DATE, null, null);
private static final Type TYPE_ARRAY_BOOL = new Type(Code.ARRAY, TYPE_BOOL, null);
private static final Type TYPE_ARRAY_INT64 = new Type(Code.ARRAY, TYPE_INT64, null);
+ private static final Type TYPE_ARRAY_FLOAT32 = new Type(Code.ARRAY, TYPE_FLOAT32, null);
private static final Type TYPE_ARRAY_FLOAT64 = new Type(Code.ARRAY, TYPE_FLOAT64, null);
private static final Type TYPE_ARRAY_NUMERIC = new Type(Code.ARRAY, TYPE_NUMERIC, null);
private static final Type TYPE_ARRAY_PG_NUMERIC = new Type(Code.ARRAY, TYPE_PG_NUMERIC, null);
@@ -89,9 +91,17 @@ public static Type int64() {
return TYPE_INT64;
}
+ /**
+ * Returns the descriptor for the {@code FLOAT32} type: a floating point type with the same value
+ * domain as a Java {@code float}.
+ */
+ public static Type float32() {
+ return TYPE_FLOAT32;
+ }
+
/**
* Returns the descriptor for the {@code FLOAT64} type: a floating point type with the same value
- * domain as a Java {code double}.
+ * domain as a Java {@code double}.
*/
public static Type float64() {
return TYPE_FLOAT64;
@@ -174,6 +184,8 @@ public static Type array(Type elementType) {
return TYPE_ARRAY_BOOL;
case INT64:
return TYPE_ARRAY_INT64;
+ case FLOAT32:
+ return TYPE_ARRAY_FLOAT32;
case FLOAT64:
return TYPE_ARRAY_FLOAT64;
case NUMERIC:
@@ -264,6 +276,7 @@ public enum Code {
NUMERIC(TypeCode.NUMERIC, "unknown"),
PG_NUMERIC(TypeCode.NUMERIC, "numeric", TypeAnnotationCode.PG_NUMERIC),
FLOAT64(TypeCode.FLOAT64, "double precision"),
+ FLOAT32(TypeCode.FLOAT32, "real"),
STRING(TypeCode.STRING, "character varying"),
JSON(TypeCode.JSON, "unknown"),
PG_JSONB(TypeCode.JSON, "jsonb", TypeAnnotationCode.PG_JSONB),
@@ -565,6 +578,8 @@ static Type fromProto(com.google.spanner.v1.Type proto) {
return bool();
case INT64:
return int64();
+ case FLOAT32:
+ return float32();
case FLOAT64:
return float64();
case NUMERIC:
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
index 3f0155e4a5e..e4db5ff1469 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
@@ -149,6 +149,20 @@ public static Value int64(long v) {
return new Int64Impl(false, v);
}
+ /**
+ * Returns a {@code FLOAT32} value.
+ *
+ * @param v the value, which may be null
+ */
+ public static Value float32(@Nullable Float v) {
+ return new Float32Impl(v == null, v == null ? 0 : v);
+ }
+
+ /** Returns a {@code FLOAT32} value. */
+ public static Value float32(float v) {
+ return new Float32Impl(false, v);
+ }
+
/**
* Returns a {@code FLOAT64} value.
*
@@ -454,6 +468,40 @@ public static Value int64Array(@Nullable Iterable v) {
return int64ArrayFactory.create(v);
}
+ /**
+ * Returns an {@code ARRAY} value.
+ *
+ * @param v the source of element values, which may be null to produce a value for which {@code
+ * isNull()} is {@code true}
+ */
+ public static Value float32Array(@Nullable float[] v) {
+ return float32Array(v, 0, v == null ? 0 : v.length);
+ }
+
+ /**
+ * Returns an {@code ARRAY} value that takes its elements from a region of an array.
+ *
+ * @param v the source of element values, which may be null to produce a value for which {@code
+ * isNull()} is {@code true}
+ * @param pos the start position of {@code v} to copy values from. Ignored if {@code v} is {@code
+ * null}.
+ * @param length the number of values to copy from {@code v}. Ignored if {@code v} is {@code
+ * null}.
+ */
+ public static Value float32Array(@Nullable float[] v, int pos, int length) {
+ return float32ArrayFactory.create(v, pos, length);
+ }
+
+ /**
+ * Returns an {@code ARRAY} value.
+ *
+ * @param v the source of element values. This may be {@code null} to produce a value for which
+ * {@code isNull()} is {@code true}. Individual elements may also be {@code null}.
+ */
+ public static Value float32Array(@Nullable Iterable v) {
+ return float32ArrayFactory.create(v);
+ }
+
/**
* Returns an {@code ARRAY} value.
*
@@ -729,6 +777,13 @@ private Value() {}
*/
public abstract long getInt64();
+ /**
+ * Returns the value of a {@code FLOAT32}-typed instance.
+ *
+ * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type
+ */
+ public abstract float getFloat32();
+
/**
* Returns the value of a {@code FLOAT64}-typed instance.
*
@@ -835,6 +890,14 @@ public T getProtoEnum(
*/
public abstract List getInt64Array();
+ /**
+ * Returns the value of an {@code ARRAY}-typed instance. While the returned list itself
+ * will never be {@code null}, elements of that list may be null.
+ *
+ * @throws IllegalStateException if {@code isNull()} or the value is not of the expected type
+ */
+ public abstract List getFloat32Array();
+
/**
* Returns the value of an {@code ARRAY}-typed instance. While the returned list itself
* will never be {@code null}, elements of that list may be null.
@@ -1052,6 +1115,23 @@ Value newValue(boolean isNull, BitSet nulls, long[] values) {
return new Int64ArrayImpl(isNull, nulls, values);
}
};
+ private static final PrimitiveArrayValueFactory float32ArrayFactory =
+ new PrimitiveArrayValueFactory() {
+ @Override
+ float[] newArray(int size) {
+ return new float[size];
+ }
+
+ @Override
+ void set(float[] arr, int i, Float value) {
+ arr[i] = value;
+ }
+
+ @Override
+ Value newValue(boolean isNull, BitSet nulls, float[] values) {
+ return new Float32ArrayImpl(isNull, nulls, values);
+ }
+ };
private static final PrimitiveArrayValueFactory float64ArrayFactory =
new PrimitiveArrayValueFactory() {
@Override
@@ -1122,6 +1202,11 @@ public long getInt64() {
throw defaultGetter(Type.int64());
}
+ @Override
+ public float getFloat32() {
+ throw defaultGetter(Type.float32());
+ }
+
@Override
public double getFloat64() {
throw defaultGetter(Type.float64());
@@ -1181,6 +1266,11 @@ public List getInt64Array() {
throw defaultGetter(Type.array(Type.int64()));
}
+ @Override
+ public List getFloat32Array() {
+ throw defaultGetter(Type.array(Type.float32()));
+ }
+
@Override
public List getFloat64Array() {
throw defaultGetter(Type.array(Type.float64()));
@@ -1285,9 +1375,29 @@ public final boolean equals(Object o) {
@Override
public final int hashCode() {
- int result = Objects.hash(getType(), isNull);
+ Type typeToHash = getType();
+ int valueHash = isNull ? 0 : valueHash();
+
+ /**
+ * We are relaxing equality values here, making sure that Double.NaNs and Float.NaNs are equal
+ * to each other. This is because our Cloud Spanner Import / Export template in Apache Beam
+ * uses the mutation equality to check for modifications before committing. We noticed that
+ * when NaNs where used the template would always indicate a modification was present, when it
+ * turned out not to be the case.
+ *
+ *
With FLOAT32 being introduced, we want to ensure the backward compatibility of the NaN
+ * equality checks that existed for FLOAT64. We're promoting the type to FLOAT64 while
+ * calculating the type hash when the value is a NaN. We're doing a similar type promotion
+ * while calculating valueHash of Float32 type. Note that this is not applicable for composite
+ * types containing FLOAT32.
+ */
+ if (type.getCode() == Type.Code.FLOAT32 && !isNull && Float.isNaN(getFloat32())) {
+ typeToHash = Type.float64();
+ }
+
+ int result = Objects.hash(typeToHash, isNull);
if (!isNull) {
- result = 31 * result + valueHash();
+ result = 31 * result + valueHash;
}
return result;
}
@@ -1492,6 +1602,46 @@ int valueHash() {
}
}
+ private static class Float32Impl extends AbstractValue {
+ private final float value;
+
+ private Float32Impl(boolean isNull, float value) {
+ super(isNull, Type.float32());
+ this.value = value;
+ }
+
+ @Override
+ public float getFloat32() {
+ checkNotNull();
+ return value;
+ }
+
+ @Override
+ com.google.protobuf.Value valueToProto() {
+ return com.google.protobuf.Value.newBuilder().setNumberValue(value).build();
+ }
+
+ @Override
+ void valueToString(StringBuilder b) {
+ b.append(value);
+ }
+
+ @Override
+ boolean valueEquals(Value v) {
+ return ((Float32Impl) v).value == value;
+ }
+
+ @Override
+ int valueHash() {
+ // For backward compatibility of NaN equality checks with Float64 NaNs.
+ // Refer the comment in `Value.hashCode()` for more details.
+ if (!isNull() && Float.isNaN(value)) {
+ return Double.valueOf(Double.NaN).hashCode();
+ }
+ return Float.valueOf(value).hashCode();
+ }
+ }
+
private static class Float64Impl extends AbstractValue {
private final double value;
@@ -2106,6 +2256,46 @@ int arrayHash() {
}
}
+ private static class Float32ArrayImpl extends PrimitiveArrayImpl {
+ private final float[] values;
+
+ private Float32ArrayImpl(boolean isNull, BitSet nulls, float[] values) {
+ super(isNull, Type.float32(), nulls);
+ this.values = values;
+ }
+
+ @Override
+ public List getFloat32Array() {
+ return getArray();
+ }
+
+ @Override
+ boolean valueEquals(Value v) {
+ Float32ArrayImpl that = (Float32ArrayImpl) v;
+ return Arrays.equals(values, that.values);
+ }
+
+ @Override
+ int size() {
+ return values.length;
+ }
+
+ @Override
+ Float getValue(int i) {
+ return values[i];
+ }
+
+ @Override
+ com.google.protobuf.Value getValueAsProto(int i) {
+ return com.google.protobuf.Value.newBuilder().setNumberValue(values[i]).build();
+ }
+
+ @Override
+ int arrayHash() {
+ return Arrays.hashCode(values);
+ }
+ }
+
private static class Float64ArrayImpl extends PrimitiveArrayImpl {
private final double[] values;
@@ -2588,6 +2778,8 @@ private Value getValue(int fieldIndex) {
return Value.pgJsonb(value.getPgJsonb(fieldIndex));
case BYTES:
return Value.bytes(value.getBytes(fieldIndex));
+ case FLOAT32:
+ return Value.float32(value.getFloat(fieldIndex));
case FLOAT64:
return Value.float64(value.getDouble(fieldIndex));
case NUMERIC:
@@ -2622,6 +2814,8 @@ private Value getValue(int fieldIndex) {
case BYTES:
case PROTO:
return Value.bytesArray(value.getBytesList(fieldIndex));
+ case FLOAT32:
+ return Value.float32Array(value.getFloatList(fieldIndex));
case FLOAT64:
return Value.float64Array(value.getDoubleList(fieldIndex));
case NUMERIC:
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java
index 9915e12175a..d675686ffeb 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java
@@ -81,6 +81,16 @@ public R to(@Nullable Long value) {
return handle(Value.int64(value));
}
+ /** Binds to {@code Value.float32(value)} */
+ public R to(float value) {
+ return handle(Value.float32(value));
+ }
+
+ /** Binds to {@code Value.float32(value)} */
+ public R to(@Nullable Float value) {
+ return handle(Value.float32(value));
+ }
+
/** Binds to {@code Value.float64(value)} */
public R to(double value) {
return handle(Value.float64(value));
@@ -198,6 +208,21 @@ public R toInt64Array(@Nullable Iterable values) {
return handle(Value.int64Array(values));
}
+ /** Binds to {@code Value.float32Array(values)} */
+ public R toFloat32Array(@Nullable float[] values) {
+ return handle(Value.float32Array(values));
+ }
+
+ /** Binds to {@code Value.float32Array(values, pos, length)} */
+ public R toFloat32Array(@Nullable float[] values, int pos, int length) {
+ return handle(Value.float32Array(values, pos, length));
+ }
+
+ /** Binds to {@code Value.float32Array(values)} */
+ public R toFloat32Array(@Nullable Iterable values) {
+ return handle(Value.float32Array(values));
+ }
+
/** Binds to {@code Value.float64Array(values)} */
public R toFloat64Array(@Nullable double[] values) {
return handle(Value.float64Array(values));
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java
index 1b15ec50822..b5e4060ddd8 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectExecuteResultSet.java
@@ -180,6 +180,12 @@ public long getLong(String columnName) {
return delegate.getLong(columnName);
}
+ @Override
+ public float getFloat(int columnIndex) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getFloat(columnIndex);
+ }
+
@Override
public double getDouble(int columnIndex) {
Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
@@ -198,6 +204,12 @@ public BigDecimal getBigDecimal(int columnIndex) {
return delegate.getBigDecimal(columnIndex);
}
+ @Override
+ public float getFloat(String columnName) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getFloat(columnName);
+ }
+
@Override
public double getDouble(String columnName) {
Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
@@ -336,6 +348,30 @@ public List getLongList(String columnName) {
return delegate.getLongList(columnName);
}
+ @Override
+ public float[] getFloatArray(int columnIndex) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getFloatArray(columnIndex);
+ }
+
+ @Override
+ public float[] getFloatArray(String columnName) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getFloatArray(columnName);
+ }
+
+ @Override
+ public List getFloatList(int columnIndex) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getFloatList(columnIndex);
+ }
+
+ @Override
+ public List getFloatList(String columnName) {
+ Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
+ return delegate.getFloatList(columnName);
+ }
+
@Override
public double[] getDoubleArray(int columnIndex) {
Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java
index a8de14e5121..bd7c794a0fa 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReplaceableForwardingResultSet.java
@@ -189,6 +189,18 @@ public long getLong(String columnName) {
return delegate.getLong(columnName);
}
+ @Override
+ public float getFloat(int columnIndex) {
+ checkClosed();
+ return delegate.getFloat(columnIndex);
+ }
+
+ @Override
+ public float getFloat(String columnName) {
+ checkClosed();
+ return delegate.getFloat(columnName);
+ }
+
@Override
public double getDouble(int columnIndex) {
checkClosed();
@@ -345,6 +357,30 @@ public List getLongList(String columnName) {
return delegate.getLongList(columnName);
}
+ @Override
+ public float[] getFloatArray(int columnIndex) {
+ checkClosed();
+ return delegate.getFloatArray(columnIndex);
+ }
+
+ @Override
+ public float[] getFloatArray(String columnName) {
+ checkClosed();
+ return delegate.getFloatArray(columnName);
+ }
+
+ @Override
+ public List getFloatList(int columnIndex) {
+ checkClosed();
+ return delegate.getFloatList(columnIndex);
+ }
+
+ @Override
+ public List getFloatList(String columnName) {
+ checkClosed();
+ return delegate.getFloatList(columnName);
+ }
+
@Override
public double[] getDoubleArray(int columnIndex) {
checkClosed();
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java
index 4fc3c67ceba..16dd51a36a3 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java
@@ -58,6 +58,11 @@ protected long getLongInternal(int columnIndex) {
return 0;
}
+ @Override
+ protected float getFloatInternal(int columnIndex) {
+ return 0f;
+ }
+
@Override
protected double getDoubleInternal(int columnIndex) {
return 0;
@@ -134,6 +139,16 @@ protected List getLongListInternal(int columnIndex) {
return null;
}
+ @Override
+ protected float[] getFloatArrayInternal(int columnIndex) {
+ return null;
+ }
+
+ @Override
+ protected List getFloatListInternal(int columnIndex) {
+ return null;
+ }
+
@Override
protected double[] getDoubleArrayInternal(int columnIndex) {
return null;
@@ -222,6 +237,13 @@ public static Collection