3030import java .util .ArrayList ;
3131import java .util .HashMap ;
3232import 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 */
3941public 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