Skip to content

Commit ea1bcc5

Browse files
feat: Add picosecond timestamp support for Json to Proto converter (#3131)
* feat: Add picosecond timestamp support for Json to Proto converter * chore: Add edge cases for user input * chore: Fix lint issues * chore: Disable check for v1beta2 * chore: generate libraries at Wed Dec 10 15:59:07 UTC 2025 * chore: Address PR feedback * chore: generate libraries at Wed Dec 10 19:01:14 UTC 2025 * chore: Fix timestamp precision check condition --------- Co-authored-by: cloud-java-bot <[email protected]>
1 parent 1610117 commit ea1bcc5

File tree

6 files changed

+605
-91
lines changed

6 files changed

+605
-91
lines changed

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

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,27 @@
3030
import java.util.ArrayList;
3131
import java.util.HashMap;
3232
import java.util.List;
33+
import java.util.Map;
34+
import java.util.logging.Logger;
3335

3436
/**
3537
* Converts a BQ table schema to protobuf descriptor. All field names will be converted to lowercase
3638
* when constructing the protobuf descriptor. The mapping between field types and field modes are
3739
* shown in the ImmutableMaps below.
3840
*/
3941
public class BQTableSchemaToProtoDescriptor {
40-
private static ImmutableMap<TableFieldSchema.Mode, FieldDescriptorProto.Label>
41-
BQTableSchemaModeMap =
42-
ImmutableMap.of(
43-
TableFieldSchema.Mode.NULLABLE, FieldDescriptorProto.Label.LABEL_OPTIONAL,
44-
TableFieldSchema.Mode.REPEATED, FieldDescriptorProto.Label.LABEL_REPEATED,
45-
TableFieldSchema.Mode.REQUIRED, FieldDescriptorProto.Label.LABEL_REQUIRED);
4642

47-
private static ImmutableMap<TableFieldSchema.Type, FieldDescriptorProto.Type>
48-
BQTableSchemaTypeMap =
43+
private static final Logger LOG =
44+
Logger.getLogger(BQTableSchemaToProtoDescriptor.class.getName());
45+
46+
private static Map<Mode, FieldDescriptorProto.Label> DEFAULT_BQ_TABLE_SCHEMA_MODE_MAP =
47+
ImmutableMap.of(
48+
TableFieldSchema.Mode.NULLABLE, FieldDescriptorProto.Label.LABEL_OPTIONAL,
49+
TableFieldSchema.Mode.REPEATED, FieldDescriptorProto.Label.LABEL_REPEATED,
50+
TableFieldSchema.Mode.REQUIRED, FieldDescriptorProto.Label.LABEL_REQUIRED);
51+
52+
private static Map<TableFieldSchema.Type, FieldDescriptorProto.Type>
53+
DEFAULT_BQ_TABLE_SCHEMA_TYPE_MAP =
4954
new ImmutableMap.Builder<TableFieldSchema.Type, FieldDescriptorProto.Type>()
5055
.put(TableFieldSchema.Type.BOOL, FieldDescriptorProto.Type.TYPE_BOOL)
5156
.put(TableFieldSchema.Type.BYTES, FieldDescriptorProto.Type.TYPE_BYTES)
@@ -142,11 +147,13 @@ private static Descriptor convertBQTableSchemaToProtoDescriptorImpl(
142147
.setType(BQTableField.getRangeElementType().getType())
143148
.setName("start")
144149
.setMode(Mode.NULLABLE)
150+
.setTimestampPrecision(BQTableField.getTimestampPrecision())
145151
.build(),
146152
TableFieldSchema.newBuilder()
147153
.setType(BQTableField.getRangeElementType().getType())
148154
.setName("end")
149155
.setMode(Mode.NULLABLE)
156+
.setTimestampPrecision(BQTableField.getTimestampPrecision())
150157
.build());
151158

152159
if (dependencyMap.containsKey(rangeFields)) {
@@ -189,7 +196,7 @@ private static Descriptor convertBQTableSchemaToProtoDescriptorImpl(
189196
* @param index Index for protobuf fields.
190197
* @param scope used to name descriptors
191198
*/
192-
private static FieldDescriptorProto convertBQTableFieldToProtoField(
199+
static FieldDescriptorProto convertBQTableFieldToProtoField(
193200
TableFieldSchema BQTableField, int index, String scope) {
194201
TableFieldSchema.Mode mode = BQTableField.getMode();
195202
String fieldName = BQTableField.getName().toLowerCase();
@@ -198,20 +205,45 @@ private static FieldDescriptorProto convertBQTableFieldToProtoField(
198205
FieldDescriptorProto.newBuilder()
199206
.setName(fieldName)
200207
.setNumber(index)
201-
.setLabel((FieldDescriptorProto.Label) BQTableSchemaModeMap.get(mode));
208+
.setLabel((FieldDescriptorProto.Label) DEFAULT_BQ_TABLE_SCHEMA_MODE_MAP.get(mode));
202209

203210
switch (BQTableField.getType()) {
204211
case STRUCT:
205212
fieldDescriptor.setTypeName(scope);
206213
break;
207214
case RANGE:
208215
fieldDescriptor.setType(
209-
(FieldDescriptorProto.Type) BQTableSchemaTypeMap.get(BQTableField.getType()));
216+
(FieldDescriptorProto.Type)
217+
DEFAULT_BQ_TABLE_SCHEMA_TYPE_MAP.get(BQTableField.getType()));
210218
fieldDescriptor.setTypeName(scope);
211219
break;
220+
case TIMESTAMP:
221+
// Can map to either int64 or string based on the BQ Field's timestamp precision
222+
// Default: microsecond (6) maps to int64 and picosecond (12) maps to string.
223+
long timestampPrecision = BQTableField.getTimestampPrecision().getValue();
224+
if (timestampPrecision == 12L) {
225+
fieldDescriptor.setType(
226+
(FieldDescriptorProto.Type) FieldDescriptorProto.Type.TYPE_STRING);
227+
break;
228+
}
229+
// This should never happen as this is a server response issue. If this is the case,
230+
// warn the user and use INT64 as the default is microsecond precision.
231+
if (timestampPrecision != 6L && timestampPrecision != 0L) {
232+
LOG.warning(
233+
"BigQuery Timestamp field "
234+
+ BQTableField.getName()
235+
+ " has timestamp precision that is not 6 or 12. Defaulting to microsecond"
236+
+ " precision and mapping to INT64 protobuf type.");
237+
}
238+
// If the timestampPrecision value comes back as a null result from the server,
239+
// timestampPrecision has a value of 0L. Use the INT64 to map to the type used
240+
// for the default precision (microsecond).
241+
fieldDescriptor.setType((FieldDescriptorProto.Type) FieldDescriptorProto.Type.TYPE_INT64);
242+
break;
212243
default:
213244
fieldDescriptor.setType(
214-
(FieldDescriptorProto.Type) BQTableSchemaTypeMap.get(BQTableField.getType()));
245+
(FieldDescriptorProto.Type)
246+
DEFAULT_BQ_TABLE_SCHEMA_TYPE_MAP.get(BQTableField.getType()));
215247
break;
216248
}
217249

0 commit comments

Comments
 (0)