From 513d1df339d7e97a42a7b1c493ed8613f90bed78 Mon Sep 17 00:00:00 2001 From: Jack Dingilian Date: Tue, 16 Jul 2024 15:36:37 -0400 Subject: [PATCH 1/2] feat: Support float32, float64, and array type query params Also fixes a bug with float32 lists where it was incorrectly converted to List where we expect List Change-Id: I4d4b32dcddef74711eeea8997b020c46eee8be3c --- .../internal/AbstractProtoStructReader.java | 4 +- .../data/v2/models/sql/Statement.java | 165 ++++++- .../AbstractProtoStructReaderTest.java | 42 ++ .../data/v2/models/sql/StatementTest.java | 416 ++++++++++++++++++ 4 files changed, 615 insertions(+), 12 deletions(-) diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReader.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReader.java index 7035a8285d..dff4d4e0b2 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReader.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReader.java @@ -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, etc + return (float) value.getFloatValue(); case BOOL: return value.getBoolValue(); case TIMESTAMP: diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/sql/Statement.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/sql/Statement.java index c68a9feec5..58c16e2c5c 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/sql/Statement.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/sql/Statement.java @@ -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; @@ -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; @@ -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 = @@ -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} */ @@ -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 Builder setListParam( + String paramName, @Nullable List value, SqlType.Array 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) { @@ -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) { @@ -191,11 +242,7 @@ 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(); } @@ -203,16 +250,112 @@ private static Value timestampParamOf(@Nullable Instant value) { 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 Value arrayParamOf(@Nullable List value, SqlType.Array 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) { + 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()); + 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); } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReaderTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReaderTest.java index 68c88f775e..95955bab94 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReaderTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/AbstractProtoStructReaderTest.java @@ -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 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) @@ -378,6 +394,32 @@ public static List 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>) + (row, field) -> row.getList(field, SqlType.arrayOf(SqlType.float32())), + (BiFunction>) + (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>) + (row, field) -> row.getList(field, SqlType.arrayOf(SqlType.float64())), + (BiFunction>) + (row, index) -> row.getList(index, SqlType.arrayOf(SqlType.float64())), + Arrays.asList(1.11d, 1.22d, 1.33d) + }, // Simple Map { Collections.singletonList( diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/sql/StatementTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/sql/StatementTest.java index ef72384852..cb19a7fde9 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/sql/StatementTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/sql/StatementTest.java @@ -15,21 +15,34 @@ */ package com.google.cloud.bigtable.data.v2.models.sql; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.arrayType; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.arrayValue; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.boolType; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.boolValue; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.bytesType; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.bytesValue; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.dateType; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.dateValue; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.float32Type; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.float64Type; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.floatValue; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.int64Type; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.int64Value; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.nullValue; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.stringType; +import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.stringValue; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.timestampType; import static com.google.cloud.bigtable.data.v2.stub.sql.SqlProtoFactory.timestampValue; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.bigtable.v2.ExecuteQueryRequest; import com.google.bigtable.v2.Value; import com.google.cloud.Date; import com.google.cloud.bigtable.data.v2.internal.RequestContext; import com.google.protobuf.ByteString; +import java.util.Arrays; +import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -286,6 +299,409 @@ public void statementWithNullDateParam() { .build()); } + @Test + public void statementWithBytesListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam( + "listParam", + Arrays.asList(ByteString.copyFromUtf8("foo"), ByteString.copyFromUtf8("bar")), + SqlType.arrayOf(SqlType.bytes())) + .setListParam( + "listWithNullElem", + Arrays.asList(ByteString.copyFromUtf8("foo"), null, ByteString.copyFromUtf8("bar")), + SqlType.arrayOf(SqlType.bytes())) + .setListParam("emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.bytes())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.bytes())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(bytesType())) + .setArrayValue( + arrayValue(bytesValue("foo"), bytesValue("bar")).getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(bytesType())) + .setArrayValue( + arrayValue(bytesValue("foo"), nullValue(), bytesValue("bar")) + .getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(bytesType())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams("nullList", Value.newBuilder().setType(arrayType(bytesType())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void statementWithStringListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam( + "listParam", Arrays.asList("foo", "bar"), SqlType.arrayOf(SqlType.string())) + .setListParam( + "listWithNullElem", + Arrays.asList("foo", "bar", null), + SqlType.arrayOf(SqlType.string())) + .setListParam("emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.string())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.string())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(stringType())) + .setArrayValue( + arrayValue(stringValue("foo"), stringValue("bar")).getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(stringType())) + .setArrayValue( + arrayValue(stringValue("foo"), stringValue("bar"), nullValue()) + .getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(stringType())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams("nullList", Value.newBuilder().setType(arrayType(stringType())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void statementWithInt64ListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam("listParam", Arrays.asList(1L, 2L), SqlType.arrayOf(SqlType.int64())) + .setListParam( + "listWithNullElem", Arrays.asList(null, 3L, 4L), SqlType.arrayOf(SqlType.int64())) + .setListParam("emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.int64())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.int64())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(int64Type())) + .setArrayValue(arrayValue(int64Value(1), int64Value(2)).getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(int64Type())) + .setArrayValue( + arrayValue(nullValue(), int64Value(3), int64Value(4)).getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(int64Type())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams("nullList", Value.newBuilder().setType(arrayType(int64Type())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void statementWithFloat32ListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam( + "listParam", Arrays.asList(1.1f, 1.2f), SqlType.arrayOf(SqlType.float32())) + .setListParam( + "listWithNullElem", + Arrays.asList(1.3f, 1.4f, null), + SqlType.arrayOf(SqlType.float32())) + .setListParam("emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.float32())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.float32())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(float32Type())) + .setArrayValue( + arrayValue(floatValue(1.1f), floatValue(1.2f)).getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(float32Type())) + .setArrayValue( + arrayValue(floatValue(1.3f), floatValue(1.4f), nullValue()) + .getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(float32Type())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams("nullList", Value.newBuilder().setType(arrayType(float32Type())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void statementWithFloat64ListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam( + "listParam", Arrays.asList(1.1d, 1.2d), SqlType.arrayOf(SqlType.float64())) + .setListParam( + "listWithNullElem", + Arrays.asList(1.3d, 1.4d, null), + SqlType.arrayOf(SqlType.float64())) + .setListParam("emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.float64())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.float64())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(float64Type())) + .setArrayValue(arrayValue(floatValue(1.1), floatValue(1.2)).getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(float64Type())) + .setArrayValue( + arrayValue(floatValue(1.3), floatValue(1.4), nullValue()) + .getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(float64Type())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams("nullList", Value.newBuilder().setType(arrayType(float64Type())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void statementWithBooleanListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam("listParam", Arrays.asList(true, false), SqlType.arrayOf(SqlType.bool())) + .setListParam( + "listWithNullElem", + Arrays.asList(true, false, null), + SqlType.arrayOf(SqlType.bool())) + .setListParam("emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.bool())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.bool())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(boolType())) + .setArrayValue( + arrayValue(boolValue(true), boolValue(false)).getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(boolType())) + .setArrayValue( + arrayValue(boolValue(true), boolValue(false), nullValue()) + .getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(boolType())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams("nullList", Value.newBuilder().setType(arrayType(boolType())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void statementWithTimestampListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam( + "listParam", + Arrays.asList(Instant.ofEpochSecond(3000, 100), Instant.ofEpochSecond(4000, 100)), + SqlType.arrayOf(SqlType.timestamp())) + .setListParam( + "listWithNullElem", + Arrays.asList( + Instant.ofEpochSecond(1000, 100), Instant.ofEpochSecond(2000, 100), null), + SqlType.arrayOf(SqlType.timestamp())) + .setListParam( + "emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.timestamp())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.timestamp())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(timestampType())) + .setArrayValue( + arrayValue(timestampValue(3000, 100), timestampValue(4000, 100)) + .getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(timestampType())) + .setArrayValue( + arrayValue( + timestampValue(1000, 100), + timestampValue(2000, 100), + nullValue()) + .getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(timestampType())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams( + "nullList", Value.newBuilder().setType(arrayType(timestampType())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void statementWithDateListParam() { + Statement s = + Statement.newBuilder( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .setListParam( + "listParam", + Arrays.asList(Date.fromYearMonthDay(2024, 6, 1), Date.fromYearMonthDay(2024, 7, 1)), + SqlType.arrayOf(SqlType.date())) + .setListParam( + "listWithNullElem", + Arrays.asList( + Date.fromYearMonthDay(2024, 8, 1), Date.fromYearMonthDay(2024, 8, 2), null), + SqlType.arrayOf(SqlType.date())) + .setListParam("emptyList", Collections.emptyList(), SqlType.arrayOf(SqlType.date())) + .setListParam("nullList", null, SqlType.arrayOf(SqlType.date())) + .build(); + + assertThat(s.toProto(REQUEST_CONTEXT)) + .isEqualTo( + ExecuteQueryRequest.newBuilder() + .setQuery( + "SELECT cf, @listParam, @listWithNullElem, @emptyList, @nullList FROM table") + .putParams( + "listParam", + Value.newBuilder() + .setType(arrayType(dateType())) + .setArrayValue( + arrayValue(dateValue(2024, 6, 1), dateValue(2024, 7, 1)) + .getArrayValue()) + .build()) + .putParams( + "listWithNullElem", + Value.newBuilder() + .setType(arrayType(dateType())) + .setArrayValue( + arrayValue(dateValue(2024, 8, 1), dateValue(2024, 8, 2), nullValue()) + .getArrayValue()) + .build()) + .putParams( + "emptyList", + Value.newBuilder() + .setType(arrayType(dateType())) + .setArrayValue(arrayValue().getArrayValue()) + .build()) + .putParams("nullList", Value.newBuilder().setType(arrayType(dateType())).build()) + .setInstanceName(EXPECTED_INSTANCE_NAME) + .setAppProfileId(EXPECTED_APP_PROFILE) + .build()); + } + + @Test + public void setListParamRejectsUnsupportedElementTypes() { + Statement.Builder statement = Statement.newBuilder("SELECT @param"); + + assertThrows( + IllegalArgumentException.class, + () -> statement.setListParam("param", null, SqlType.arrayOf(SqlType.struct()))); + assertThrows( + IllegalArgumentException.class, + () -> + statement.setListParam( + "param", null, SqlType.arrayOf(SqlType.arrayOf(SqlType.string())))); + assertThrows( + IllegalArgumentException.class, + () -> + statement.setListParam( + "param", null, SqlType.arrayOf(SqlType.mapOf(SqlType.bytes(), SqlType.bytes())))); + } + @Test public void statementBuilderAllowsParamsToBeOverridden() { Statement s = From 3930c2cd59d2c339c7d5b6aa855b5780b0b96bfa Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Fri, 26 Jul 2024 17:10:21 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7c33e633fc..640ea976e7 100644 --- a/README.md +++ b/README.md @@ -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" ``` @@ -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