@@ -25,6 +25,8 @@ import (
25
25
"google.golang.org/protobuf/reflect/protodesc"
26
26
"google.golang.org/protobuf/reflect/protoreflect"
27
27
"google.golang.org/protobuf/types/descriptorpb"
28
+ "google.golang.org/protobuf/types/known/durationpb"
29
+ "google.golang.org/protobuf/types/known/timestamppb"
28
30
"google.golang.org/protobuf/types/known/wrapperspb"
29
31
)
30
32
@@ -114,6 +116,21 @@ var bqTypeToWrapperMap = map[storagepb.TableFieldSchema_Type]string{
114
116
// filename used by well known types proto
115
117
var wellKnownTypesWrapperName = "google/protobuf/wrappers.proto"
116
118
119
+ // filename used by timestamp and duration proto
120
+ var extraWellKnownTypesPerTypeName = map [string ]struct {
121
+ name string
122
+ fileDescriptor * descriptorpb.FileDescriptorProto
123
+ }{
124
+ ".google.protobuf.Timestamp" : {
125
+ name : "google/protobuf/timestamp.proto" ,
126
+ fileDescriptor : protodesc .ToFileDescriptorProto (timestamppb .File_google_protobuf_timestamp_proto ),
127
+ },
128
+ ".google.protobuf.Duration" : {
129
+ name : "google/protobuf/duration.proto" ,
130
+ fileDescriptor : protodesc .ToFileDescriptorProto (durationpb .File_google_protobuf_duration_proto ),
131
+ },
132
+ }
133
+
117
134
var rangeTypesPrefix = "rangemessage_range_"
118
135
119
136
// dependencyCache is used to reduce the number of unique messages we generate by caching based on the tableschema.
@@ -180,7 +197,7 @@ func (dm *dependencyCache) add(schema *storagepb.TableSchema, descriptor protore
180
197
return nil
181
198
}
182
199
183
- func (dm * dependencyCache ) addRangeByElementType (typ storagepb.TableFieldSchema_Type , useProto3 bool ) (protoreflect.MessageDescriptor , error ) {
200
+ func (dm * dependencyCache ) addRangeByElementType (typ storagepb.TableFieldSchema_Type , cfg * customConfig ) (protoreflect.MessageDescriptor , error ) {
184
201
if md , present := dm .rangeTypes [typ ]; present {
185
202
// already added, do nothing.
186
203
return md , nil
@@ -212,7 +229,7 @@ func (dm *dependencyCache) addRangeByElementType(typ storagepb.TableFieldSchema_
212
229
// we put the range types outside the hierarchical namespace as they're effectively BQ-specific well-known types.
213
230
msgTypeName := fmt .Sprintf ("%s%s" , rangeTypesPrefix , strings .ToLower (typ .String ()))
214
231
// use a new dependency cache, as we don't want to taint the main one due to matching schema
215
- md , err := storageSchemaToDescriptorInternal (ts , msgTypeName , newDependencyCache (), useProto3 )
232
+ md , err := storageSchemaToDescriptorInternal (ts , msgTypeName , newDependencyCache (), cfg )
216
233
if err != nil {
217
234
return nil , fmt .Errorf ("failed to generate range descriptor %q: %v" , msgTypeName , err )
218
235
}
@@ -230,22 +247,34 @@ func (dm *dependencyCache) getRange(typ storagepb.TableFieldSchema_Type) protore
230
247
231
248
// StorageSchemaToProto2Descriptor builds a protoreflect.Descriptor for a given table schema using proto2 syntax.
232
249
func StorageSchemaToProto2Descriptor (inSchema * storagepb.TableSchema , scope string ) (protoreflect.Descriptor , error ) {
233
- dc := newDependencyCache ()
234
- // TODO: b/193064992 tracks support for wrapper types. In the interim, disable wrapper usage.
235
- return storageSchemaToDescriptorInternal (inSchema , scope , dc , false )
250
+ return StorageSchemaToProtoDescriptorWithOptions (inSchema , scope , withProto2 ())
236
251
}
237
252
238
253
// StorageSchemaToProto3Descriptor builds a protoreflect.Descriptor for a given table schema using proto3 syntax.
239
254
//
240
255
// NOTE: Currently the write API doesn't yet support proto3 behaviors (default value, wrapper types, etc), but this is provided for
241
256
// completeness.
242
257
func StorageSchemaToProto3Descriptor (inSchema * storagepb.TableSchema , scope string ) (protoreflect.Descriptor , error ) {
258
+ return StorageSchemaToProtoDescriptorWithOptions (inSchema , scope , withProto3 ())
259
+ }
260
+
261
+ // StorageSchemaToProtoDescriptorWithOptions builds a protoreflect.Descriptor for a given table schema with
262
+ // extra configuration options. Uses proto2 syntax by default.
263
+ func StorageSchemaToProtoDescriptorWithOptions (inSchema * storagepb.TableSchema , scope string , opts ... ProtoConversionOption ) (protoreflect.Descriptor , error ) {
243
264
dc := newDependencyCache ()
244
- return storageSchemaToDescriptorInternal (inSchema , scope , dc , true )
265
+ cfg := & customConfig {
266
+ useProto3 : false ,
267
+ protoMappingOverrides : protoMappingOverrides {},
268
+ }
269
+ for _ , opt := range opts {
270
+ opt .applyCustomClientOpt (cfg )
271
+ }
272
+ // TODO: b/193064992 tracks support for wrapper types. In the interim, disable wrapper usage.
273
+ return storageSchemaToDescriptorInternal (inSchema , scope , dc , cfg )
245
274
}
246
275
247
276
// Internal implementation of the conversion code.
248
- func storageSchemaToDescriptorInternal (inSchema * storagepb.TableSchema , scope string , cache * dependencyCache , useProto3 bool ) (protoreflect.MessageDescriptor , error ) {
277
+ func storageSchemaToDescriptorInternal (inSchema * storagepb.TableSchema , scope string , cache * dependencyCache , cfg * customConfig ) (protoreflect.MessageDescriptor , error ) {
249
278
if inSchema == nil {
250
279
return nil , newConversionError (scope , fmt .Errorf ("no input schema was provided" ))
251
280
}
@@ -277,14 +306,14 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
277
306
deps = append (deps , foundDesc .ParentFile ())
278
307
}
279
308
// Construct field descriptor for the message.
280
- fdp := tableFieldSchemaToFieldDescriptorProto (f , fNumber , string (foundDesc .FullName ()), useProto3 )
309
+ fdp := tableFieldSchemaToFieldDescriptorProto (f , fNumber , string (foundDesc .FullName ()), cfg )
281
310
fields = append (fields , fdp )
282
311
} else {
283
312
// Wrap the current struct's fields in a TableSchema outer message, and then build the submessage.
284
313
ts := & storagepb.TableSchema {
285
314
Fields : f .GetFields (),
286
315
}
287
- desc , err := storageSchemaToDescriptorInternal (ts , currentScope , cache , useProto3 )
316
+ desc , err := storageSchemaToDescriptorInternal (ts , currentScope , cache , cfg )
288
317
if err != nil {
289
318
return nil , newConversionError (currentScope , fmt .Errorf ("couldn't convert message: %w" , err ))
290
319
}
@@ -295,7 +324,7 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
295
324
if err != nil {
296
325
return nil , newConversionError (currentScope , fmt .Errorf ("failed to add descriptor to dependency cache: %w" , err ))
297
326
}
298
- fdp := tableFieldSchemaToFieldDescriptorProto (f , fNumber , currentScope , useProto3 )
327
+ fdp := tableFieldSchemaToFieldDescriptorProto (f , fNumber , currentScope , cfg )
299
328
fields = append (fields , fdp )
300
329
}
301
330
} else {
@@ -305,7 +334,7 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
305
334
if ret == nil {
306
335
return nil , fmt .Errorf ("field %q is a RANGE, but doesn't include RangeElementType info" , f .GetName ())
307
336
}
308
- foundDesc , err := cache .addRangeByElementType (ret .GetType (), useProto3 )
337
+ foundDesc , err := cache .addRangeByElementType (ret .GetType (), cfg )
309
338
if err != nil {
310
339
return nil , err
311
340
}
@@ -323,7 +352,7 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
323
352
}
324
353
}
325
354
}
326
- fd := tableFieldSchemaToFieldDescriptorProto (f , fNumber , currentScope , useProto3 )
355
+ fd := tableFieldSchemaToFieldDescriptorProto (f , fNumber , currentScope , cfg )
327
356
fields = append (fields , fd )
328
357
}
329
358
}
@@ -335,6 +364,11 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
335
364
336
365
// Use the local dependencies to generate a list of filenames.
337
366
depNames := []string {wellKnownTypesWrapperName }
367
+ for _ , override := range cfg .protoMappingOverrides {
368
+ if dep , found := extraWellKnownTypesPerTypeName [override .TypeName ]; found {
369
+ depNames = append (depNames , dep .name )
370
+ }
371
+ }
338
372
for _ , d := range deps {
339
373
depNames = append (depNames , d .ParentFile ().Path ())
340
374
}
@@ -346,7 +380,7 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
346
380
Syntax : proto .String ("proto3" ),
347
381
Dependency : depNames ,
348
382
}
349
- if ! useProto3 {
383
+ if ! cfg . useProto3 {
350
384
fdp .Syntax = proto .String ("proto2" )
351
385
}
352
386
@@ -357,6 +391,11 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
357
391
fdp ,
358
392
protodesc .ToFileDescriptorProto (wrapperspb .File_google_protobuf_wrappers_proto ),
359
393
}
394
+ for _ , override := range cfg .protoMappingOverrides {
395
+ if dep , found := extraWellKnownTypesPerTypeName [override .TypeName ]; found {
396
+ fdpList = append (fdpList , dep .fileDescriptor )
397
+ }
398
+ }
360
399
fdpList = append (fdpList , cache .getFileDescriptorProtos ()... )
361
400
362
401
fds := & descriptorpb.FileDescriptorSet {
@@ -402,7 +441,7 @@ func messageDependsOnFile(msg protoreflect.MessageDescriptor, file protoreflect.
402
441
// For proto2, we propagate the mode->label annotation as expected.
403
442
//
404
443
// Messages are always nullable, and repeated fields are as well.
405
- func tableFieldSchemaToFieldDescriptorProto (field * storagepb.TableFieldSchema , idx int32 , scope string , useProto3 bool ) * descriptorpb.FieldDescriptorProto {
444
+ func tableFieldSchemaToFieldDescriptorProto (field * storagepb.TableFieldSchema , idx int32 , scope string , cfg * customConfig ) * descriptorpb.FieldDescriptorProto {
406
445
name := field .GetName ()
407
446
var fdp * descriptorpb.FieldDescriptorProto
408
447
@@ -411,46 +450,35 @@ func tableFieldSchemaToFieldDescriptorProto(field *storagepb.TableFieldSchema, i
411
450
Name : proto .String (name ),
412
451
Number : proto .Int32 (idx ),
413
452
TypeName : proto .String (scope ),
414
- Label : convertModeToLabel (field .GetMode (), useProto3 ),
453
+ Label : convertModeToLabel (field .GetMode (), cfg . useProto3 ),
415
454
}
416
455
} else if field .GetType () == storagepb .TableFieldSchema_RANGE {
417
456
fdp = & descriptorpb.FieldDescriptorProto {
418
457
Name : proto .String (name ),
419
458
Number : proto .Int32 (idx ),
420
459
TypeName : proto .String (fmt .Sprintf ("%s%s" , rangeTypesPrefix , strings .ToLower (field .GetRangeElementType ().GetType ().String ()))),
421
- Label : convertModeToLabel (field .GetMode (), useProto3 ),
460
+ Label : convertModeToLabel (field .GetMode (), cfg . useProto3 ),
422
461
}
423
462
} else {
424
- // For (REQUIRED||REPEATED) fields for proto3, or all cases for proto2, we can use the expected scalar types.
425
- if field .GetMode () != storagepb .TableFieldSchema_NULLABLE || ! useProto3 {
426
- outType := bqTypeToFieldTypeMap [field .GetType ()]
427
- fdp = & descriptorpb.FieldDescriptorProto {
428
- Name : proto .String (name ),
429
- Number : proto .Int32 (idx ),
430
- Type : outType .Enum (),
431
- Label : convertModeToLabel (field .GetMode (), useProto3 ),
432
- }
463
+ typeName , outType , label := resolveType (scope , field , cfg )
464
+ fdp = & descriptorpb.FieldDescriptorProto {
465
+ Name : proto .String (name ),
466
+ Number : proto .Int32 (idx ),
467
+ Type : outType .Enum (),
468
+ TypeName : typeName ,
469
+ Label : label ,
470
+ }
433
471
434
- // Special case: proto2 repeated fields may benefit from using packed annotation.
435
- if field .GetMode () == storagepb .TableFieldSchema_REPEATED && ! useProto3 {
436
- for _ , v := range packedTypes {
437
- if outType == v {
438
- fdp .Options = & descriptorpb.FieldOptions {
439
- Packed : proto .Bool (true ),
440
- }
441
- break
472
+ // Special case: proto2 repeated fields may benefit from using packed annotation.
473
+ if field .GetMode () == storagepb .TableFieldSchema_REPEATED && ! cfg .useProto3 {
474
+ for _ , v := range packedTypes {
475
+ if outType == v {
476
+ fdp .Options = & descriptorpb.FieldOptions {
477
+ Packed : proto .Bool (true ),
442
478
}
479
+ break
443
480
}
444
481
}
445
- } else {
446
- // For NULLABLE proto3 fields, use a wrapper type.
447
- fdp = & descriptorpb.FieldDescriptorProto {
448
- Name : proto .String (name ),
449
- Number : proto .Int32 (idx ),
450
- Type : descriptorpb .FieldDescriptorProto_TYPE_MESSAGE .Enum (),
451
- TypeName : proto .String (bqTypeToWrapperMap [field .GetType ()]),
452
- Label : descriptorpb .FieldDescriptorProto_LABEL_OPTIONAL .Enum (),
453
- }
454
482
}
455
483
}
456
484
if nameRequiresAnnotation (name ) {
@@ -468,6 +496,23 @@ func tableFieldSchemaToFieldDescriptorProto(field *storagepb.TableFieldSchema, i
468
496
return fdp
469
497
}
470
498
499
+ func resolveType (scope string , field * storagepb.TableFieldSchema , cfg * customConfig ) (* string , descriptorpb.FieldDescriptorProto_Type , * descriptorpb.FieldDescriptorProto_Label ) {
500
+ path := strings .TrimPrefix (strings .ReplaceAll (scope , "__" , "." ), "root." )
501
+ if override := cfg .protoMappingOverrides .getByField (field , path ); override != nil {
502
+ var typeName * string
503
+ if override .TypeName != "" {
504
+ typeName = proto .String (override .TypeName )
505
+ }
506
+ return typeName , override .Type , convertModeToLabel (field .GetMode (), cfg .useProto3 )
507
+ }
508
+ // For (REQUIRED||REPEATED) fields for proto3, or all cases for proto2, we can use the expected scalar types.
509
+ if field .GetMode () != storagepb .TableFieldSchema_NULLABLE || ! cfg .useProto3 {
510
+ return nil , bqTypeToFieldTypeMap [field .GetType ()], convertModeToLabel (field .GetMode (), cfg .useProto3 )
511
+ }
512
+ // For NULLABLE proto3 fields, use a wrapper type.
513
+ return proto .String (bqTypeToWrapperMap [field .GetType ()]), descriptorpb .FieldDescriptorProto_TYPE_MESSAGE , descriptorpb .FieldDescriptorProto_LABEL_OPTIONAL .Enum ()
514
+ }
515
+
471
516
// nameRequiresAnnotation determines whether a field name requires unicode-annotation.
472
517
func nameRequiresAnnotation (in string ) bool {
473
518
return ! protoreflect .Name (in ).IsValid ()
0 commit comments