Skip to content

Commit 182906f

Browse files
chore: support untyped values for statement parameters (#1854)
* feat: support untyped values * chore: support untyped values Adds support for untyped values that can be used as statement parameters. This is required for Spangres, as many native PG drivers send (some) parameters without an explicit type, and expect the backend to infer the type from the context. If we were to include a (placeholder) type for such a parameter, the type inference on the backend fails. * chore: support untyped values Adds support for untyped values that can be used as statement parameters. This is required for Spangres, as many native PG drivers send (some) parameters without an explicit type, and expect the backend to infer the type from the context. If we were to include a (placeholder) type for such a parameter, the type inference on the backend fails. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent ca85276 commit 182906f

File tree

6 files changed

+103
-7
lines changed

6 files changed

+103
-7
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ ExecuteSqlRequest.Builder getExecuteSqlRequestBuilder(
584584
com.google.protobuf.Struct.Builder paramsBuilder = builder.getParamsBuilder();
585585
for (Map.Entry<String, Value> param : stmtParameters.entrySet()) {
586586
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
587-
if (param.getValue() != null) {
587+
if (param.getValue() != null && param.getValue().getType() != null) {
588588
builder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
589589
}
590590
}
@@ -615,7 +615,7 @@ ExecuteBatchDmlRequest.Builder getExecuteBatchDmlRequestBuilder(
615615
builder.getStatementsBuilder(idx).getParamsBuilder();
616616
for (Map.Entry<String, Value> param : stmtParameters.entrySet()) {
617617
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
618-
if (param.getValue() != null) {
618+
if (param.getValue() != null && param.getValue().getType() != null) {
619619
builder
620620
.getStatementsBuilder(idx)
621621
.putParamTypes(param.getKey(), param.getValue().getType().toProto());

google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public List<Partition> partitionQuery(
170170
Struct.Builder paramsBuilder = builder.getParamsBuilder();
171171
for (Map.Entry<String, Value> param : stmtParameters.entrySet()) {
172172
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
173-
if (param.getValue() != null) {
173+
if (param.getValue() != null && param.getValue().getType() != null) {
174174
builder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
175175
}
176176
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ private void setParameters(
218218
com.google.protobuf.Struct.Builder paramsBuilder = requestBuilder.getParamsBuilder();
219219
for (Map.Entry<String, Value> param : statementParameters.entrySet()) {
220220
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
221-
if (param.getValue() != null) {
221+
if (param.getValue() != null && param.getValue().getType() != null) {
222222
requestBuilder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
223223
}
224224
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ public abstract class Value implements Serializable {
8787
private static final char LIST_CLOSE = ']';
8888
private static final long serialVersionUID = -5289864325087675338L;
8989

90+
/**
91+
* Returns a {@link Value} that wraps the given proto value. This can be used to construct a value
92+
* without a specific type, and let the backend infer the type based on the statement where it is
93+
* used.
94+
*
95+
* @param value the non-null proto value (a {@link NullValue} is allowed)
96+
*/
97+
public static Value untyped(com.google.protobuf.Value value) {
98+
return new UntypedValueImpl(Preconditions.checkNotNull(value));
99+
}
100+
90101
/**
91102
* Returns a {@code BOOL} value.
92103
*
@@ -914,7 +925,7 @@ public final boolean equals(Object o) {
914925
}
915926

916927
AbstractValue that = (AbstractValue) o;
917-
if (!getType().equals(that.getType()) || isNull != that.isNull) {
928+
if (!Objects.equals(getType(), that.getType()) || isNull != that.isNull) {
918929
return false;
919930
}
920931

@@ -963,6 +974,58 @@ final void checkNotNull() {
963974
}
964975
}
965976

977+
private static class UntypedValueImpl extends AbstractValue {
978+
private final com.google.protobuf.Value value;
979+
980+
private UntypedValueImpl(com.google.protobuf.Value value) {
981+
super(value.hasNullValue(), null);
982+
this.value = value;
983+
}
984+
985+
@Override
986+
public boolean getBool() {
987+
checkNotNull();
988+
Preconditions.checkState(value.hasBoolValue(), "This value does not contain a bool value");
989+
return value.getBoolValue();
990+
}
991+
992+
@Override
993+
public String getString() {
994+
checkNotNull();
995+
Preconditions.checkState(
996+
value.hasStringValue(), "This value does not contain a string value");
997+
return value.getStringValue();
998+
}
999+
1000+
@Override
1001+
public double getFloat64() {
1002+
checkNotNull();
1003+
Preconditions.checkState(
1004+
value.hasNumberValue(), "This value does not contain a number value");
1005+
return value.getNumberValue();
1006+
}
1007+
1008+
@Override
1009+
void valueToString(StringBuilder b) {
1010+
b.append(value);
1011+
}
1012+
1013+
@Override
1014+
com.google.protobuf.Value valueToProto() {
1015+
return value;
1016+
}
1017+
1018+
@Override
1019+
boolean valueEquals(Value v) {
1020+
return ((UntypedValueImpl) v).value.equals(value);
1021+
}
1022+
1023+
@Override
1024+
int valueHash() {
1025+
return value.hashCode();
1026+
}
1027+
}
1028+
9661029
private static class BoolImpl extends AbstractValue {
9671030
private final boolean value;
9681031

google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,13 @@ Integer handle(Value value) {
5656
public void reflection()
5757
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
5858
// Test that every Value factory method has a counterpart in ValueBinder, and that invoking it
59-
// produces the expected Value.
59+
// produces the expected Value. The only exception is for untyped values, which must be
60+
// constructed manually as an untyped value and then assigned as a parameter.
6061
for (Method method : Value.class.getMethods()) {
6162
if (!Modifier.isStatic(method.getModifiers())
62-
|| !method.getReturnType().equals(Value.class)) {
63+
|| !method.getReturnType().equals(Value.class)
64+
|| (method.getParameterTypes().length > 0
65+
&& method.getParameterTypes()[0].equals(com.google.protobuf.Value.class))) {
6366
continue;
6467
}
6568
Method binderMethod = findBinderMethod(method);

google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import static org.junit.Assert.assertArrayEquals;
2323
import static org.junit.Assert.assertEquals;
2424
import static org.junit.Assert.assertFalse;
25+
import static org.junit.Assert.assertNotEquals;
2526
import static org.junit.Assert.assertNull;
27+
import static org.junit.Assert.assertSame;
2628
import static org.junit.Assert.assertThrows;
2729
import static org.junit.Assert.assertTrue;
2830
import static org.junit.Assert.fail;
@@ -63,6 +65,34 @@ private static <T> Iterable<T> plainIterable(T... values) {
6365
return Lists.newArrayList(values);
6466
}
6567

68+
@Test
69+
public void untyped() {
70+
com.google.protobuf.Value proto =
71+
com.google.protobuf.Value.newBuilder().setStringValue("test").build();
72+
Value v = Value.untyped(proto);
73+
assertNull(v.getType());
74+
assertFalse(v.isNull());
75+
assertSame(proto, v.toProto());
76+
77+
assertEquals(
78+
v, Value.untyped(com.google.protobuf.Value.newBuilder().setStringValue("test").build()));
79+
assertEquals(
80+
Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()),
81+
Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()));
82+
assertEquals(
83+
Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()),
84+
Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()));
85+
86+
assertNotEquals(
87+
v, Value.untyped(com.google.protobuf.Value.newBuilder().setStringValue("foo").build()));
88+
assertNotEquals(
89+
Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()),
90+
Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(0.14d).build()));
91+
assertNotEquals(
92+
Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(false).build()),
93+
Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()));
94+
}
95+
6696
@Test
6797
public void bool() {
6898
Value v = Value.bool(true);

0 commit comments

Comments
 (0)