Skip to content

Commit a5e62be

Browse files
feat: add ability to write Range values with JSONStreamWriter (#2498)
* feat: add ability to write Range values with JSONStreamWriter * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update tests to include mixed case and string values * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Add Arrow processor to validate reading range values * Add test scope to Arrow dependencies * Fix unit test * Fix maven dependencies check failure for arrow * Add arrow-memory-netty to fix sample exception issue with no DefaultAllocationManager * Temp remove arrow test dependencies to test sample failure * Further testing * Test by hard coding test arrow version * Revert changes for testing * Pass dep. check * Updated test for case sensitivity * Update integration test for case sensitivity for all cases * Update JsonToProtoMessageTest to include mixed case fields --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 53bb216 commit a5e62be

File tree

9 files changed

+975
-50
lines changed

9 files changed

+975
-50
lines changed

google-cloud-bigquerystorage/pom.xml

+13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
</parent>
1616
<properties>
1717
<site.installationModule>google-cloud-bigquerystorage</site.installationModule>
18+
<arrow.version>15.0.2</arrow.version>
1819
</properties>
1920
<build>
2021
<extensions>
@@ -197,6 +198,18 @@
197198
<version>1.11.3</version>
198199
<scope>test</scope>
199200
</dependency>
201+
<dependency>
202+
<groupId>org.apache.arrow</groupId>
203+
<artifactId>arrow-vector</artifactId>
204+
<version>${arrow.version}</version>
205+
<scope>test</scope>
206+
</dependency>
207+
<dependency>
208+
<groupId>org.apache.arrow</groupId>
209+
<artifactId>arrow-memory-core</artifactId>
210+
<version>${arrow.version}</version>
211+
<scope>test</scope>
212+
</dependency>
200213

201214
<dependency>
202215
<groupId>io.grpc</groupId>

google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1/BQTableSchemaToProtoDescriptor.java

+81-24
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.google.cloud.bigquery.storage.v1;
1717

18+
import com.google.cloud.bigquery.storage.v1.TableFieldSchema.Mode;
1819
import com.google.common.base.Preconditions;
1920
import com.google.common.collect.ImmutableList;
2021
import com.google.common.collect.ImmutableMap;
@@ -61,6 +62,7 @@ public class BQTableSchemaToProtoDescriptor {
6162
.put(TableFieldSchema.Type.TIMESTAMP, FieldDescriptorProto.Type.TYPE_INT64)
6263
.put(TableFieldSchema.Type.JSON, FieldDescriptorProto.Type.TYPE_STRING)
6364
.put(TableFieldSchema.Type.INTERVAL, FieldDescriptorProto.Type.TYPE_STRING)
65+
.put(TableFieldSchema.Type.RANGE, FieldDescriptorProto.Type.TYPE_MESSAGE)
6466
.build();
6567

6668
/**
@@ -89,7 +91,7 @@ private static Descriptor convertBQTableSchemaToProtoDescriptorImpl(
8991
TableSchema BQTableSchema,
9092
String scope,
9193
HashMap<ImmutableList<TableFieldSchema>, Descriptor> dependencyMap)
92-
throws Descriptors.DescriptorValidationException {
94+
throws Descriptors.DescriptorValidationException, IllegalArgumentException {
9395
List<FileDescriptor> dependenciesList = new ArrayList<FileDescriptor>();
9496
List<FieldDescriptorProto> fields = new ArrayList<FieldDescriptorProto>();
9597
int index = 1;
@@ -99,25 +101,72 @@ private static Descriptor convertBQTableSchemaToProtoDescriptorImpl(
99101
? BQTableField.getName()
100102
: BigQuerySchemaUtil.generatePlaceholderFieldName(BQTableField.getName());
101103
String currentScope = scope + "__" + scopeName;
102-
if (BQTableField.getType() == TableFieldSchema.Type.STRUCT) {
103-
ImmutableList<TableFieldSchema> fieldList =
104-
ImmutableList.copyOf(BQTableField.getFieldsList());
105-
if (dependencyMap.containsKey(fieldList)) {
106-
Descriptor descriptor = dependencyMap.get(fieldList);
107-
dependenciesList.add(descriptor.getFile());
108-
fields.add(convertBQTableFieldToProtoField(BQTableField, index++, descriptor.getName()));
109-
} else {
110-
Descriptor descriptor =
111-
convertBQTableSchemaToProtoDescriptorImpl(
112-
TableSchema.newBuilder().addAllFields(fieldList).build(),
113-
currentScope,
114-
dependencyMap);
115-
dependenciesList.add(descriptor.getFile());
116-
dependencyMap.put(fieldList, descriptor);
104+
switch (BQTableField.getType()) {
105+
case STRUCT:
106+
ImmutableList<TableFieldSchema> fieldList =
107+
ImmutableList.copyOf(BQTableField.getFieldsList());
108+
if (dependencyMap.containsKey(fieldList)) {
109+
Descriptor descriptor = dependencyMap.get(fieldList);
110+
dependenciesList.add(descriptor.getFile());
111+
fields.add(
112+
convertBQTableFieldToProtoField(BQTableField, index++, descriptor.getName()));
113+
} else {
114+
Descriptor descriptor =
115+
convertBQTableSchemaToProtoDescriptorImpl(
116+
TableSchema.newBuilder().addAllFields(fieldList).build(),
117+
currentScope,
118+
dependencyMap);
119+
dependenciesList.add(descriptor.getFile());
120+
dependencyMap.put(fieldList, descriptor);
121+
fields.add(convertBQTableFieldToProtoField(BQTableField, index++, currentScope));
122+
}
123+
break;
124+
case RANGE:
125+
switch (BQTableField.getRangeElementType().getType()) {
126+
case DATE:
127+
case DATETIME:
128+
case TIMESTAMP:
129+
break;
130+
default:
131+
throw new IllegalArgumentException(
132+
String.format(
133+
"Error: %s of type RANGE requires range element type (DATE, DATETIME, TIMESTAMP)",
134+
currentScope));
135+
}
136+
// For RANGE type, expliclitly add the fields start and end of the same FieldElementType
137+
// as it is not expliclity defined in the TableSchema.
138+
ImmutableList<TableFieldSchema> rangeFields =
139+
ImmutableList.of(
140+
TableFieldSchema.newBuilder()
141+
.setType(BQTableField.getRangeElementType().getType())
142+
.setName("start")
143+
.setMode(Mode.NULLABLE)
144+
.build(),
145+
TableFieldSchema.newBuilder()
146+
.setType(BQTableField.getRangeElementType().getType())
147+
.setName("end")
148+
.setMode(Mode.NULLABLE)
149+
.build());
150+
151+
if (dependencyMap.containsKey(rangeFields)) {
152+
Descriptor descriptor = dependencyMap.get(rangeFields);
153+
dependenciesList.add(descriptor.getFile());
154+
fields.add(
155+
convertBQTableFieldToProtoField(BQTableField, index++, descriptor.getName()));
156+
} else {
157+
Descriptor descriptor =
158+
convertBQTableSchemaToProtoDescriptorImpl(
159+
TableSchema.newBuilder().addAllFields(rangeFields).build(),
160+
currentScope,
161+
dependencyMap);
162+
dependenciesList.add(descriptor.getFile());
163+
dependencyMap.put(rangeFields, descriptor);
164+
fields.add(convertBQTableFieldToProtoField(BQTableField, index++, currentScope));
165+
}
166+
break;
167+
default:
117168
fields.add(convertBQTableFieldToProtoField(BQTableField, index++, currentScope));
118-
}
119-
} else {
120-
fields.add(convertBQTableFieldToProtoField(BQTableField, index++, currentScope));
169+
break;
121170
}
122171
}
123172
FileDescriptor[] dependenciesArray = new FileDescriptor[dependenciesList.size()];
@@ -150,11 +199,19 @@ private static FieldDescriptorProto convertBQTableFieldToProtoField(
150199
.setNumber(index)
151200
.setLabel((FieldDescriptorProto.Label) BQTableSchemaModeMap.get(mode));
152201

153-
if (BQTableField.getType() == TableFieldSchema.Type.STRUCT) {
154-
fieldDescriptor.setTypeName(scope);
155-
} else {
156-
fieldDescriptor.setType(
157-
(FieldDescriptorProto.Type) BQTableSchemaTypeMap.get(BQTableField.getType()));
202+
switch (BQTableField.getType()) {
203+
case STRUCT:
204+
fieldDescriptor.setTypeName(scope);
205+
break;
206+
case RANGE:
207+
fieldDescriptor.setType(
208+
(FieldDescriptorProto.Type) BQTableSchemaTypeMap.get(BQTableField.getType()));
209+
fieldDescriptor.setTypeName(scope);
210+
break;
211+
default:
212+
fieldDescriptor.setType(
213+
(FieldDescriptorProto.Type) BQTableSchemaTypeMap.get(BQTableField.getType()));
214+
break;
158215
}
159216

160217
// Sets columnName annotation when field name is not proto comptaible.

google-cloud-bigquerystorage/src/main/java/com/google/cloud/bigquery/storage/v1/JsonToProtoMessage.java

+34
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,40 @@ private FieldDescriptorAndFieldTableSchema computeDescriptorAndSchema(
433433
if (tableFieldSchemaList != null) {
434434
// protoSchema is generated from tableSchema so their field ordering should match.
435435
fieldSchema = tableFieldSchemaList.get(field.getIndex());
436+
// For RANGE type, expliclitly add the fields start and end of the same FieldElementType as it
437+
// is not expliclity defined in the TableFieldSchema.
438+
if (fieldSchema.getType() == TableFieldSchema.Type.RANGE) {
439+
switch (fieldSchema.getRangeElementType().getType()) {
440+
case DATE:
441+
case DATETIME:
442+
case TIMESTAMP:
443+
fieldSchema =
444+
fieldSchema
445+
.toBuilder()
446+
.addFields(
447+
TableFieldSchema.newBuilder()
448+
.setName("start")
449+
.setType(fieldSchema.getRangeElementType().getType())
450+
.build())
451+
.addFields(
452+
TableFieldSchema.newBuilder()
453+
.setName("end")
454+
.setType(fieldSchema.getRangeElementType().getType())
455+
.build())
456+
.build();
457+
break;
458+
default:
459+
throw new ValidationException(
460+
"Field at index "
461+
+ field.getIndex()
462+
+ " with name ("
463+
+ fieldSchema.getName()
464+
+ ") with type (RANGE) has an unsupported range element type ("
465+
+ fieldSchema.getRangeElementType()
466+
+ ")");
467+
}
468+
}
469+
436470
if (!fieldSchema.getName().toLowerCase().equals(BigQuerySchemaUtil.getFieldName(field))) {
437471
throw new ValidationException(
438472
"Field at index "

google-cloud-bigquerystorage/src/test/java/com/google/cloud/bigquery/storage/v1/BQTableSchemaToProtoDescriptorTest.java

+70
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,76 @@ public void testSimpleTypes() throws Exception {
105105
}
106106
}
107107

108+
@Test
109+
public void testRange() throws Exception {
110+
final TableSchema tableSchema =
111+
TableSchema.newBuilder()
112+
.addFields(
113+
TableFieldSchema.newBuilder()
114+
.setName("range_date")
115+
.setType(TableFieldSchema.Type.RANGE)
116+
.setMode(TableFieldSchema.Mode.NULLABLE)
117+
.setRangeElementType(
118+
TableFieldSchema.FieldElementType.newBuilder()
119+
.setType(TableFieldSchema.Type.DATE)
120+
.build())
121+
.build())
122+
.addFields(
123+
TableFieldSchema.newBuilder()
124+
.setName("range_datetime")
125+
.setType(TableFieldSchema.Type.RANGE)
126+
.setMode(TableFieldSchema.Mode.NULLABLE)
127+
.setRangeElementType(
128+
TableFieldSchema.FieldElementType.newBuilder()
129+
.setType(TableFieldSchema.Type.DATETIME)
130+
.build())
131+
.build())
132+
.addFields(
133+
TableFieldSchema.newBuilder()
134+
.setName("range_timestamp")
135+
.setType(TableFieldSchema.Type.RANGE)
136+
.setMode(TableFieldSchema.Mode.NULLABLE)
137+
.setRangeElementType(
138+
TableFieldSchema.FieldElementType.newBuilder()
139+
.setType(TableFieldSchema.Type.TIMESTAMP)
140+
.build())
141+
.build())
142+
.addFields(
143+
TableFieldSchema.newBuilder()
144+
.setName("range_date_miXEd_caSE")
145+
.setType(TableFieldSchema.Type.RANGE)
146+
.setMode(TableFieldSchema.Mode.NULLABLE)
147+
.setRangeElementType(
148+
TableFieldSchema.FieldElementType.newBuilder()
149+
.setType(TableFieldSchema.Type.DATE)
150+
.build())
151+
.build())
152+
.addFields(
153+
TableFieldSchema.newBuilder()
154+
.setName("range_datetime_miXEd_caSE")
155+
.setType(TableFieldSchema.Type.RANGE)
156+
.setMode(TableFieldSchema.Mode.NULLABLE)
157+
.setRangeElementType(
158+
TableFieldSchema.FieldElementType.newBuilder()
159+
.setType(TableFieldSchema.Type.DATETIME)
160+
.build())
161+
.build())
162+
.addFields(
163+
TableFieldSchema.newBuilder()
164+
.setName("range_timestamp_miXEd_caSE")
165+
.setType(TableFieldSchema.Type.RANGE)
166+
.setMode(TableFieldSchema.Mode.NULLABLE)
167+
.setRangeElementType(
168+
TableFieldSchema.FieldElementType.newBuilder()
169+
.setType(TableFieldSchema.Type.TIMESTAMP)
170+
.build())
171+
.build())
172+
.build();
173+
final Descriptor descriptor =
174+
BQTableSchemaToProtoDescriptor.convertBQTableSchemaToProtoDescriptor(tableSchema);
175+
isDescriptorEqual(descriptor, TestRange.getDescriptor());
176+
}
177+
108178
@Test
109179
public void testStructSimple() throws Exception {
110180
final TableFieldSchema stringType =

0 commit comments

Comments
 (0)