Skip to content

Commit c1997dd

Browse files
author
Praful Makani
authored
feat: add struct query parameters (#223)
* feat: struct query parameters * feat: unit and integration test case * feat: adjust code * feat: remove clirr and refactor code * feat: additional test case * feat: add asserts for values
1 parent ec6328e commit c1997dd

File tree

3 files changed

+231
-2
lines changed

3 files changed

+231
-2
lines changed

google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
import com.google.cloud.Timestamp;
2727
import com.google.common.base.Function;
2828
import com.google.common.collect.ImmutableList;
29+
import com.google.common.collect.ImmutableMap;
2930
import com.google.common.collect.Lists;
3031
import com.google.common.io.BaseEncoding;
3132
import java.io.Serializable;
3233
import java.math.BigDecimal;
3334
import java.util.ArrayList;
3435
import java.util.Date;
36+
import java.util.HashMap;
3537
import java.util.List;
38+
import java.util.Map;
3639
import javax.annotation.Nullable;
3740
import org.threeten.bp.Instant;
3841
import org.threeten.bp.ZoneOffset;
@@ -127,12 +130,27 @@ public Builder setArrayValues(List<QueryParameterValue> arrayValues) {
127130

128131
abstract Builder setArrayValuesInner(ImmutableList<QueryParameterValue> arrayValues);
129132

133+
/** Sets struct values. The type must set to STRUCT. */
134+
public Builder setStructValues(Map<String, QueryParameterValue> structValues) {
135+
setStructTypes(ImmutableMap.copyOf(structValues));
136+
return setStructValuesInner(ImmutableMap.copyOf(structValues));
137+
}
138+
139+
abstract Builder setStructValuesInner(Map<String, QueryParameterValue> structValues);
140+
130141
/** Sets the parameter data type. */
131142
public abstract Builder setType(StandardSQLTypeName type);
132143

133144
/** Sets the data type of the array elements. The type must set to ARRAY. */
134145
public abstract Builder setArrayType(StandardSQLTypeName arrayType);
135146

147+
/** Sets the data type of the struct elements. The type must set to STRUCT. */
148+
public Builder setStructTypes(Map<String, QueryParameterValue> structTypes) {
149+
return setStructTypesInner(structTypes);
150+
}
151+
152+
abstract Builder setStructTypesInner(Map<String, QueryParameterValue> structTypes);
153+
136154
/** Creates a {@code QueryParameterValue} object. */
137155
public abstract QueryParameterValue build();
138156
}
@@ -154,13 +172,31 @@ public List<QueryParameterValue> getArrayValues() {
154172
@Nullable
155173
abstract ImmutableList<QueryParameterValue> getArrayValuesInner();
156174

175+
/** Returns the struct values of this parameter. The returned map, if not null, is immutable. */
176+
@Nullable
177+
public Map<String, QueryParameterValue> getStructValues() {
178+
return getStructValuesInner();
179+
}
180+
181+
@Nullable
182+
abstract Map<String, QueryParameterValue> getStructValuesInner();
183+
157184
/** Returns the data type of this parameter. */
158185
public abstract StandardSQLTypeName getType();
159186

160187
/** Returns the data type of the array elements. */
161188
@Nullable
162189
public abstract StandardSQLTypeName getArrayType();
163190

191+
/** Returns the data type of the struct elements. */
192+
@Nullable
193+
public Map<String, QueryParameterValue> getStructTypes() {
194+
return getStructTypesInner();
195+
}
196+
197+
@Nullable
198+
abstract Map<String, QueryParameterValue> getStructTypesInner();
199+
164200
/** Creates a {@code QueryParameterValue} object with the given value and type. */
165201
public static <T> QueryParameterValue of(T value, Class<T> type) {
166202
return of(value, classToType(type));
@@ -274,6 +310,17 @@ public static <T> QueryParameterValue array(T[] array, StandardSQLTypeName type)
274310
.build();
275311
}
276312

313+
/**
314+
* Creates a map with {@code QueryParameterValue} object and a type of STRUCT the given struct
315+
* element type.
316+
*/
317+
public static QueryParameterValue struct(Map<String, QueryParameterValue> struct) {
318+
return QueryParameterValue.newBuilder()
319+
.setStructValues(struct)
320+
.setType(StandardSQLTypeName.STRUCT)
321+
.build();
322+
}
323+
277324
private static <T> StandardSQLTypeName classToType(Class<T> type) {
278325
if (Boolean.class.isAssignableFrom(type)) {
279326
return StandardSQLTypeName.BOOL;
@@ -398,6 +445,14 @@ com.google.api.services.bigquery.model.QueryParameterValue toValuePb() {
398445
valuePb.setArrayValues(
399446
Lists.transform(getArrayValues(), QueryParameterValue.TO_VALUE_PB_FUNCTION));
400447
}
448+
if (getStructValues() != null) {
449+
Map<String, com.google.api.services.bigquery.model.QueryParameterValue> structValues =
450+
new HashMap<>();
451+
for (Map.Entry<String, QueryParameterValue> structValue : getStructValues().entrySet()) {
452+
structValues.put(structValue.getKey(), structValue.getValue().toValuePb());
453+
}
454+
valuePb.setStructValues(structValues);
455+
}
401456
return valuePb;
402457
}
403458

@@ -409,14 +464,24 @@ QueryParameterType toTypePb() {
409464
arrayTypePb.setType(getArrayType().toString());
410465
typePb.setArrayType(arrayTypePb);
411466
}
467+
if (getStructTypes() != null) {
468+
List<QueryParameterType.StructTypes> structTypes = new ArrayList<>();
469+
for (Map.Entry<String, QueryParameterValue> entry : getStructTypes().entrySet()) {
470+
QueryParameterType.StructTypes structType = new QueryParameterType.StructTypes();
471+
structType.setName(entry.getKey());
472+
structType.setType(entry.getValue().toTypePb());
473+
structTypes.add(structType);
474+
}
475+
typePb.setStructTypes(structTypes);
476+
}
412477
return typePb;
413478
}
414479

415480
static QueryParameterValue fromPb(
416481
com.google.api.services.bigquery.model.QueryParameterValue valuePb,
417482
QueryParameterType typePb) {
418483
Builder valueBuilder = newBuilder();
419-
484+
Map<String, QueryParameterType> parameterTypes = new HashMap<>();
420485
StandardSQLTypeName type = StandardSQLTypeName.valueOf(typePb.getType());
421486
valueBuilder.setType(type);
422487
if (type == StandardSQLTypeName.ARRAY) {
@@ -431,10 +496,35 @@ static QueryParameterValue fromPb(
431496
}
432497
valueBuilder.setArrayValues(arrayValues.build());
433498
}
499+
} else if (type == StandardSQLTypeName.STRUCT) {
500+
Map<String, QueryParameterValue> structTypes = new HashMap<>();
501+
for (QueryParameterType.StructTypes types : typePb.getStructTypes()) {
502+
structTypes.put(
503+
types.getName(),
504+
QueryParameterValue.newBuilder()
505+
.setType(StandardSQLTypeName.valueOf(types.getType().getType()))
506+
.build());
507+
}
508+
valueBuilder.setStructTypes(structTypes);
509+
if (valuePb == null || valuePb.getStructValues() == null) {
510+
valueBuilder.setStructValues(ImmutableMap.<String, QueryParameterValue>of());
511+
} else {
512+
Map<String, QueryParameterValue> structValues = new HashMap<>();
513+
for (QueryParameterType.StructTypes structType : typePb.getStructTypes()) {
514+
parameterTypes.put(structType.getName(), structType.getType());
515+
}
516+
for (Map.Entry<String, com.google.api.services.bigquery.model.QueryParameterValue>
517+
structValue : valuePb.getStructValues().entrySet()) {
518+
structValues.put(
519+
structValue.getKey(),
520+
QueryParameterValue.fromPb(
521+
structValue.getValue(), parameterTypes.get(structValue.getKey())));
522+
}
523+
valueBuilder.setStructValues(structValues);
524+
}
434525
} else {
435526
valueBuilder.setValue(valuePb == null ? "" : valuePb.getValue());
436527
}
437-
438528
return valueBuilder.build();
439529
}
440530
}

google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323
import static org.threeten.bp.temporal.ChronoField.SECOND_OF_MINUTE;
2424

2525
import com.google.api.services.bigquery.model.QueryParameterType;
26+
import com.google.common.collect.ImmutableMap;
2627
import java.math.BigDecimal;
2728
import java.text.ParseException;
2829
import java.util.Date;
30+
import java.util.HashMap;
2931
import java.util.List;
32+
import java.util.Map;
3033
import org.junit.Test;
3134
import org.threeten.bp.Instant;
3235
import org.threeten.bp.ZoneOffset;
@@ -378,6 +381,68 @@ public void testFromEmptyArray() {
378381
assertThat(value.getArrayValues()).isEmpty();
379382
}
380383

384+
@Test
385+
public void testStruct() {
386+
QueryParameterValue booleanField = QueryParameterValue.bool(true);
387+
QueryParameterValue integerField = QueryParameterValue.int64(15);
388+
QueryParameterValue stringField = QueryParameterValue.string("test-string");
389+
QueryParameterValue recordField =
390+
QueryParameterValue.struct(
391+
ImmutableMap.of(
392+
"booleanField",
393+
booleanField,
394+
"integerField",
395+
integerField,
396+
"stringField",
397+
stringField));
398+
com.google.api.services.bigquery.model.QueryParameterValue parameterValue =
399+
recordField.toValuePb();
400+
QueryParameterType parameterType = recordField.toTypePb();
401+
QueryParameterValue queryParameterValue =
402+
QueryParameterValue.fromPb(parameterValue, parameterType);
403+
assertThat(queryParameterValue).isEqualTo(recordField);
404+
assertThat(recordField.getValue()).isNull();
405+
assertThat(recordField.getType()).isEqualTo(StandardSQLTypeName.STRUCT);
406+
assertThat(recordField.getStructTypes()).isNotNull();
407+
assertThat(recordField.getStructValues()).isNotNull();
408+
}
409+
410+
@Test
411+
public void testNestedStruct() {
412+
QueryParameterValue booleanField = QueryParameterValue.bool(true);
413+
QueryParameterValue integerField = QueryParameterValue.int64(15);
414+
QueryParameterValue stringField = QueryParameterValue.string("test-string");
415+
QueryParameterValue recordField =
416+
QueryParameterValue.struct(
417+
ImmutableMap.of(
418+
"booleanField",
419+
booleanField,
420+
"integerField",
421+
integerField,
422+
"stringField",
423+
stringField));
424+
Map<String, QueryParameterValue> structValue = new HashMap<>();
425+
structValue.put("bool", booleanField);
426+
structValue.put("int", integerField);
427+
structValue.put("string", stringField);
428+
structValue.put("struct", recordField);
429+
QueryParameterValue nestedRecordField = QueryParameterValue.struct(structValue);
430+
com.google.api.services.bigquery.model.QueryParameterValue parameterValue =
431+
nestedRecordField.toValuePb();
432+
QueryParameterType parameterType = nestedRecordField.toTypePb();
433+
QueryParameterValue queryParameterValue =
434+
QueryParameterValue.fromPb(parameterValue, parameterType);
435+
assertThat(queryParameterValue).isEqualTo(nestedRecordField);
436+
assertThat(nestedRecordField.getValue()).isNull();
437+
assertThat(nestedRecordField.getType()).isEqualTo(StandardSQLTypeName.STRUCT);
438+
assertThat(nestedRecordField.getStructTypes().get("struct").getType())
439+
.isEqualTo(StandardSQLTypeName.STRUCT);
440+
assertThat(nestedRecordField.getStructValues().get("struct").getStructValues())
441+
.containsAtLeastEntriesIn(recordField.getStructValues());
442+
assertThat(nestedRecordField.getStructTypes().size()).isEqualTo(structValue.size());
443+
assertThat(nestedRecordField.getStructValues().size()).isEqualTo(structValue.size());
444+
}
445+
381446
private static void assertArrayDataEquals(
382447
String[] expectedValues,
383448
StandardSQLTypeName expectedType,

google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,80 @@ public void testNamedQueryParameters() throws InterruptedException {
14551455
assertEquals(2, Iterables.size(result.getValues()));
14561456
}
14571457

1458+
@Test
1459+
public void testStructNamedQueryParameters() throws InterruptedException {
1460+
QueryParameterValue booleanValue = QueryParameterValue.bool(true);
1461+
QueryParameterValue stringValue = QueryParameterValue.string("test-stringField");
1462+
QueryParameterValue integerValue = QueryParameterValue.int64(10);
1463+
Map<String, QueryParameterValue> struct = new HashMap<>();
1464+
struct.put("booleanField", booleanValue);
1465+
struct.put("integerField", integerValue);
1466+
struct.put("stringField", stringValue);
1467+
QueryParameterValue recordValue = QueryParameterValue.struct(struct);
1468+
String query = "SELECT STRUCT(@recordField) AS record";
1469+
QueryJobConfiguration config =
1470+
QueryJobConfiguration.newBuilder(query)
1471+
.setDefaultDataset(DATASET)
1472+
.setUseLegacySql(false)
1473+
.addNamedParameter("recordField", recordValue)
1474+
.build();
1475+
TableResult result = bigquery.query(config);
1476+
assertEquals(1, Iterables.size(result.getValues()));
1477+
for (FieldValueList values : result.iterateAll()) {
1478+
for (FieldValue value : values) {
1479+
for (FieldValue record : value.getRecordValue()) {
1480+
assertEquals(FieldValue.Attribute.RECORD, record.getAttribute());
1481+
assertEquals(true, record.getRecordValue().get(0).getBooleanValue());
1482+
assertEquals(10, record.getRecordValue().get(1).getLongValue());
1483+
assertEquals("test-stringField", record.getRecordValue().get(2).getStringValue());
1484+
}
1485+
}
1486+
}
1487+
}
1488+
1489+
@Test
1490+
public void testNestedStructNamedQueryParameters() throws InterruptedException {
1491+
QueryParameterValue booleanValue = QueryParameterValue.bool(true);
1492+
QueryParameterValue stringValue = QueryParameterValue.string("test-stringField");
1493+
QueryParameterValue integerValue = QueryParameterValue.int64(10);
1494+
Map<String, QueryParameterValue> struct = new HashMap<>();
1495+
struct.put("booleanField", booleanValue);
1496+
struct.put("integerField", integerValue);
1497+
struct.put("stringField", stringValue);
1498+
QueryParameterValue recordValue = QueryParameterValue.struct(struct);
1499+
Map<String, QueryParameterValue> structValue = new HashMap<>();
1500+
structValue.put("bool", booleanValue);
1501+
structValue.put("int", integerValue);
1502+
structValue.put("string", stringValue);
1503+
structValue.put("struct", recordValue);
1504+
QueryParameterValue nestedRecordField = QueryParameterValue.struct(structValue);
1505+
String query = "SELECT STRUCT(@nestedRecordField) AS record";
1506+
QueryJobConfiguration config =
1507+
QueryJobConfiguration.newBuilder(query)
1508+
.setDefaultDataset(DATASET)
1509+
.setUseLegacySql(false)
1510+
.addNamedParameter("nestedRecordField", nestedRecordField)
1511+
.build();
1512+
TableResult result = bigquery.query(config);
1513+
assertEquals(1, Iterables.size(result.getValues()));
1514+
for (FieldValueList values : result.iterateAll()) {
1515+
for (FieldValue value : values) {
1516+
assertEquals(FieldValue.Attribute.RECORD, value.getAttribute());
1517+
for (FieldValue record : value.getRecordValue()) {
1518+
assertEquals(
1519+
true, record.getRecordValue().get(0).getRecordValue().get(0).getBooleanValue());
1520+
assertEquals(10, record.getRecordValue().get(0).getRecordValue().get(1).getLongValue());
1521+
assertEquals(
1522+
"test-stringField",
1523+
record.getRecordValue().get(0).getRecordValue().get(2).getStringValue());
1524+
assertEquals(true, record.getRecordValue().get(1).getBooleanValue());
1525+
assertEquals("test-stringField", record.getRecordValue().get(2).getStringValue());
1526+
assertEquals(10, record.getRecordValue().get(3).getLongValue());
1527+
}
1528+
}
1529+
}
1530+
}
1531+
14581532
@Test
14591533
public void testBytesParameter() throws Exception {
14601534
String query = "SELECT BYTE_LENGTH(@p) AS length";

0 commit comments

Comments
 (0)