Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support float32, float64, and array type query params #2297

Merged
merged 2 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-bigtable'
If you are using Gradle without BOM, add this to your dependencies:

```Groovy
implementation 'com.google.cloud:google-cloud-bigtable:2.40.0'
implementation 'com.google.cloud:google-cloud-bigtable:2.41.0'
```

If you are using SBT, add this to your dependencies:

```Scala
libraryDependencies += "com.google.cloud" % "google-cloud-bigtable" % "2.40.0"
libraryDependencies += "com.google.cloud" % "google-cloud-bigtable" % "2.41.0"
```
<!-- {x-version-update-end} -->

Expand Down Expand Up @@ -542,7 +542,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
[kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-bigtable/java11.html
[stability-image]: https://img.shields.io/badge/stability-stable-green
[maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-bigtable.svg
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-bigtable/2.40.0
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-bigtable/2.41.0
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,10 @@ Object decodeValue(Value value, SqlType<?> type) {
case INT64:
return value.getIntValue();
case FLOAT64:
case FLOAT32:
return value.getFloatValue();
case FLOAT32:
// cast to float so we produce List<Float>, etc
return (float) value.getFloatValue();
case BOOL:
return value.getBoolValue();
case TIMESTAMP:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.bigtable.v2.ArrayValue;
import com.google.bigtable.v2.ExecuteQueryRequest;
import com.google.bigtable.v2.Type;
import com.google.bigtable.v2.Value;
Expand All @@ -27,6 +28,7 @@
import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.threeten.bp.Instant;
Expand Down Expand Up @@ -65,6 +67,10 @@ public class Statement {
Type.newBuilder().setBytesType(Type.Bytes.getDefaultInstance()).build();
private static final Type INT64_TYPE =
Type.newBuilder().setInt64Type(Type.Int64.getDefaultInstance()).build();
private static final Type FLOAT32_TYPE =
Type.newBuilder().setFloat32Type(Type.Float32.getDefaultInstance()).build();
private static final Type FLOAT64_TYPE =
Type.newBuilder().setFloat64Type(Type.Float64.getDefaultInstance()).build();
private static final Type BOOL_TYPE =
Type.newBuilder().setBoolType(Type.Bool.getDefaultInstance()).build();
private static final Type TIMESTAMP_TYPE =
Expand Down Expand Up @@ -131,6 +137,24 @@ public Builder setLongParam(String paramName, @Nullable Long value) {
return this;
}

/**
* Sets a query parameter with the name {@code paramName} and the FLOAT32 typed value {@code
* value}
*/
public Builder setFloatParam(String paramName, @Nullable Float value) {
params.put(paramName, float32ParamOf(value));
return this;
}

/**
* Sets a query parameter with the name {@code paramName} and the FLOAT64 typed value {@code
* value}
*/
public Builder setDoubleParam(String paramName, @Nullable Double value) {
params.put(paramName, float64ParamOf(value));
return this;
}

/**
* Sets a query parameter with the name {@code paramName} and the BOOL typed value {@code value}
*/
Expand All @@ -156,6 +180,17 @@ public Builder setDateParam(String paramName, @Nullable Date value) {
return this;
}

/**
* Sets a query parameter with the name {@code paramName} and the ARRAY typed value {@code
* value}. The array element type is specified by {@code arrayType} and the List elements must
* be of the corresponding Java type. Null array elements are valid.
*/
public <T> Builder setListParam(
String paramName, @Nullable List<T> value, SqlType.Array<T> arrayType) {
params.put(paramName, arrayParamOf(value, arrayType));
return this;
}

private static Value stringParamOf(@Nullable String value) {
Value.Builder builder = nullValueWithType(STRING_TYPE);
if (value != null) {
Expand All @@ -180,6 +215,22 @@ private static Value int64ParamOf(@Nullable Long value) {
return builder.build();
}

private static Value float32ParamOf(@Nullable Float value) {
Value.Builder builder = nullValueWithType(FLOAT32_TYPE);
if (value != null) {
builder.setFloatValue(value);
}
return builder.build();
}

private static Value float64ParamOf(@Nullable Double value) {
Value.Builder builder = nullValueWithType(FLOAT64_TYPE);
if (value != null) {
builder.setFloatValue(value);
}
return builder.build();
}

private static Value booleanParamOf(@Nullable Boolean value) {
Value.Builder builder = nullValueWithType(BOOL_TYPE);
if (value != null) {
Expand All @@ -191,28 +242,120 @@ private static Value booleanParamOf(@Nullable Boolean value) {
private static Value timestampParamOf(@Nullable Instant value) {
Value.Builder builder = nullValueWithType(TIMESTAMP_TYPE);
if (value != null) {
builder.setTimestampValue(
Timestamp.newBuilder()
.setSeconds(value.getEpochSecond())
.setNanos(value.getNano())
.build());
builder.setTimestampValue(toTimestamp(value));
}
return builder.build();
}

private static Value dateParamOf(@Nullable Date value) {
Value.Builder builder = nullValueWithType(DATE_TYPE);
if (value != null) {
builder.setDateValue(
com.google.type.Date.newBuilder()
.setYear(value.getYear())
.setMonth(value.getMonth())
.setDay(value.getDayOfMonth())
.build());
builder.setDateValue(toProtoDate(value));
}
return builder.build();
}

private static <T> Value arrayParamOf(@Nullable List<T> value, SqlType.Array<T> arrayType) {
Type type =
Type.newBuilder()
.setArrayType(
Type.Array.newBuilder().setElementType(getElementType(arrayType)).build())
.build();
Value.Builder builder = nullValueWithType(type);
if (value != null) {
builder.setArrayValue(arrayValueOf(value, arrayType));
}
return builder.build();
}

private static Type getElementType(SqlType.Array<?> arrayType) {
switch (arrayType.getElementType().getCode()) {
case BYTES:
return BYTES_TYPE;
case STRING:
return STRING_TYPE;
case INT64:
return INT64_TYPE;
case FLOAT32:
return FLOAT32_TYPE;
case FLOAT64:
return FLOAT64_TYPE;
case BOOL:
return BOOL_TYPE;
case TIMESTAMP:
return TIMESTAMP_TYPE;
case DATE:
return DATE_TYPE;
default:
throw new IllegalArgumentException(
"Unsupported query parameter Array element type: " + arrayType.getElementType());
}
}

private static ArrayValue arrayValueOf(List<?> value, SqlType.Array<?> arrayType) {
ArrayValue.Builder valueBuilder = ArrayValue.newBuilder();
for (Object element : value) {
if (element == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be of a nullValueWithType?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the API only wants the type set on the outermost value, not nested elements

valueBuilder.addValues(Value.getDefaultInstance());
continue;
}
switch (arrayType.getElementType().getCode()) {
case BYTES:
ByteString bytesElem = (ByteString) element;
valueBuilder.addValues(Value.newBuilder().setBytesValue(bytesElem).build());
break;
case STRING:
String stringElem = (String) element;
valueBuilder.addValues(Value.newBuilder().setStringValue(stringElem).build());
break;
case INT64:
Long longElem = (Long) element;
valueBuilder.addValues(Value.newBuilder().setIntValue(longElem).build());
break;
case FLOAT32:
Float floatElem = (Float) element;
valueBuilder.addValues(Value.newBuilder().setFloatValue(floatElem).build());
break;
case FLOAT64:
Double doubleElem = (Double) element;
valueBuilder.addValues(Value.newBuilder().setFloatValue(doubleElem).build());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we float64ParamOf(doubleElm)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because we don't want to set type on the nested elements

break;
case BOOL:
Boolean boolElem = (Boolean) element;
valueBuilder.addValues(Value.newBuilder().setBoolValue(boolElem).build());
break;
case TIMESTAMP:
Instant timestampElem = (Instant) element;
valueBuilder.addValues(
Value.newBuilder().setTimestampValue(toTimestamp(timestampElem)).build());
break;
case DATE:
Date dateElem = (Date) element;
valueBuilder.addValues(Value.newBuilder().setDateValue(toProtoDate(dateElem)).build());
break;
default:
throw new IllegalArgumentException(
"Unsupported query parameter Array element type: " + arrayType.getElementType());
}
}
return valueBuilder.build();
}

private static Timestamp toTimestamp(Instant instant) {
return Timestamp.newBuilder()
.setSeconds(instant.getEpochSecond())
.setNanos(instant.getNano())
.build();
}

private static com.google.type.Date toProtoDate(Date date) {
return com.google.type.Date.newBuilder()
.setYear(date.getYear())
.setMonth(date.getMonth())
.setDay(date.getDayOfMonth())
.build();
}

private static Value.Builder nullValueWithType(Type type) {
return Value.newBuilder().setType(type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,22 @@ public void arrayField_validatesType() {
IllegalStateException.class,
() -> structWithList.getList(0, SqlType.arrayOf(SqlType.bytes())));
}

// Test this independently since it won't throw an exception until accessing an element if
// float is converted to double incorrectly
@Test
public void arrayField_accessingFloat() {
TestProtoStruct structWithList =
TestProtoStruct.create(
ProtoResultSetMetadata.fromProto(
metadata(columnMetadata("testField", arrayType(float32Type()))).getMetadata()),
Collections.singletonList(arrayValue(floatValue(1.1f), floatValue(1.2f))));

List<Float> floatList =
structWithList.getList("testField", SqlType.arrayOf(SqlType.float32()));
assertThat(floatList.get(0)).isEqualTo(1.1f);
assertThat(floatList.get(1)).isEqualTo(1.2f);
}
}

@RunWith(Parameterized.class)
Expand Down Expand Up @@ -378,6 +394,32 @@ public static List<Object[]> parameters() {
(row, index) -> row.getList(index, SqlType.arrayOf(SqlType.string())),
Arrays.asList("foo", null, "baz")
},
// Float List
{
Collections.singletonList(columnMetadata("testField", arrayType(float32Type()))),
Collections.singletonList(
arrayValue(floatValue(1.1f), floatValue(1.2f), floatValue(1.3f))),
0,
"testField",
(BiFunction<TestProtoStruct, String, List<Float>>)
(row, field) -> row.getList(field, SqlType.arrayOf(SqlType.float32())),
(BiFunction<TestProtoStruct, Integer, List<Float>>)
(row, index) -> row.getList(index, SqlType.arrayOf(SqlType.float32())),
Arrays.asList(1.1f, 1.2f, 1.3f)
},
// Double List
{
Collections.singletonList(columnMetadata("testField", arrayType(float64Type()))),
Collections.singletonList(
arrayValue(floatValue(1.11d), floatValue(1.22d), floatValue(1.33d))),
0,
"testField",
(BiFunction<TestProtoStruct, String, List<Double>>)
(row, field) -> row.getList(field, SqlType.arrayOf(SqlType.float64())),
(BiFunction<TestProtoStruct, Integer, List<Double>>)
(row, index) -> row.getList(index, SqlType.arrayOf(SqlType.float64())),
Arrays.asList(1.11d, 1.22d, 1.33d)
},
// Simple Map
{
Collections.singletonList(
Expand Down
Loading
Loading