@@ -62,6 +62,13 @@ var bqTypeToFieldTypeMap = map[storagepb.TableFieldSchema_Type]descriptorpb.Fiel
62
62
storagepb .TableFieldSchema_STRUCT : descriptorpb .FieldDescriptorProto_TYPE_MESSAGE ,
63
63
storagepb .TableFieldSchema_TIME : descriptorpb .FieldDescriptorProto_TYPE_INT64 ,
64
64
storagepb .TableFieldSchema_TIMESTAMP : descriptorpb .FieldDescriptorProto_TYPE_INT64 ,
65
+ storagepb .TableFieldSchema_RANGE : descriptorpb .FieldDescriptorProto_TYPE_MESSAGE ,
66
+ }
67
+
68
+ var allowedRangeTypes = []storagepb.TableFieldSchema_Type {
69
+ storagepb .TableFieldSchema_DATE ,
70
+ storagepb .TableFieldSchema_DATETIME ,
71
+ storagepb .TableFieldSchema_TIMESTAMP ,
65
72
}
66
73
67
74
// Primitive types which can leverage packed encoding when repeated/arrays.
@@ -105,12 +112,26 @@ var bqTypeToWrapperMap = map[storagepb.TableFieldSchema_Type]string{
105
112
// filename used by well known types proto
106
113
var wellKnownTypesWrapperName = "google/protobuf/wrappers.proto"
107
114
115
+ var rangeTypesPrefix = "rangemessage_range_"
116
+
108
117
// dependencyCache is used to reduce the number of unique messages we generate by caching based on the tableschema.
109
118
//
110
119
// Keys are based on the base64-encoded serialized tableschema value.
111
- type dependencyCache map [string ]protoreflect.MessageDescriptor
120
+ type dependencyCache struct {
121
+ // keyed by element type
122
+ rangeTypes map [storagepb.TableFieldSchema_Type ]protoreflect.MessageDescriptor
123
+ // general cache
124
+ msgs map [string ]protoreflect.MessageDescriptor
125
+ }
126
+
127
+ func newDependencyCache () * dependencyCache {
128
+ return & dependencyCache {
129
+ rangeTypes : make (map [storagepb.TableFieldSchema_Type ]protoreflect.MessageDescriptor ),
130
+ msgs : make (map [string ]protoreflect.MessageDescriptor ),
131
+ }
132
+ }
112
133
113
- func (dm dependencyCache ) get (schema * storagepb.TableSchema ) protoreflect.MessageDescriptor {
134
+ func (dm * dependencyCache ) get (schema * storagepb.TableSchema ) protoreflect.MessageDescriptor {
114
135
if dm == nil {
115
136
return nil
116
137
}
@@ -119,15 +140,23 @@ func (dm dependencyCache) get(schema *storagepb.TableSchema) protoreflect.Messag
119
140
return nil
120
141
}
121
142
encoded := base64 .StdEncoding .EncodeToString (b )
122
- if desc , ok := ( dm ) [encoded ]; ok {
143
+ if desc , ok := dm . msgs [encoded ]; ok {
123
144
return desc
124
145
}
125
146
return nil
126
147
}
127
148
128
- func (dm dependencyCache ) getFileDescriptorProtos () []* descriptorpb.FileDescriptorProto {
149
+ func (dm * dependencyCache ) getFileDescriptorProtos () []* descriptorpb.FileDescriptorProto {
129
150
var fdpList []* descriptorpb.FileDescriptorProto
130
- for _ , d := range dm {
151
+ // emit encountered messages.
152
+ for _ , d := range dm .msgs {
153
+ if fd := d .ParentFile (); fd != nil {
154
+ fdp := protodesc .ToFileDescriptorProto (fd )
155
+ fdpList = append (fdpList , fdp )
156
+ }
157
+ }
158
+ // emit any range value types used.
159
+ for _ , d := range dm .rangeTypes {
131
160
if fd := d .ParentFile (); fd != nil {
132
161
fdp := protodesc .ToFileDescriptorProto (fd )
133
162
fdpList = append (fdpList , fdp )
@@ -136,7 +165,7 @@ func (dm dependencyCache) getFileDescriptorProtos() []*descriptorpb.FileDescript
136
165
return fdpList
137
166
}
138
167
139
- func (dm dependencyCache ) add (schema * storagepb.TableSchema , descriptor protoreflect.MessageDescriptor ) error {
168
+ func (dm * dependencyCache ) add (schema * storagepb.TableSchema , descriptor protoreflect.MessageDescriptor ) error {
140
169
if dm == nil {
141
170
return fmt .Errorf ("cache is nil" )
142
171
}
@@ -145,24 +174,72 @@ func (dm dependencyCache) add(schema *storagepb.TableSchema, descriptor protoref
145
174
return fmt .Errorf ("failed to serialize tableschema: %w" , err )
146
175
}
147
176
encoded := base64 .StdEncoding .EncodeToString (b )
148
- ( dm ) [encoded ] = descriptor
177
+ dm . msgs [encoded ] = descriptor
149
178
return nil
150
179
}
151
180
181
+ func (dm * dependencyCache ) addRangeByElementType (typ storagepb.TableFieldSchema_Type , useProto3 bool ) (protoreflect.MessageDescriptor , error ) {
182
+ if md , present := dm .rangeTypes [typ ]; present {
183
+ // already added, do nothing.
184
+ return md , nil
185
+ }
186
+ // Not yet present. Build the message.
187
+ allowed := false
188
+ for _ , a := range allowedRangeTypes {
189
+ if typ == a {
190
+ allowed = true
191
+ }
192
+ }
193
+ if ! allowed {
194
+ return nil , fmt .Errorf ("range does not support %q as a valid element type" , typ .String ())
195
+ }
196
+ ts := & storagepb.TableSchema {
197
+ Fields : []* storagepb.TableFieldSchema {
198
+ {
199
+ Name : "start" ,
200
+ Type : typ ,
201
+ Mode : storagepb .TableFieldSchema_NULLABLE ,
202
+ },
203
+ {
204
+ Name : "end" ,
205
+ Type : typ ,
206
+ Mode : storagepb .TableFieldSchema_NULLABLE ,
207
+ },
208
+ },
209
+ }
210
+ // we put the range types outside the hierarchical namespace as they're effectively BQ-specific well-known types.
211
+ msgTypeName := fmt .Sprintf ("%s%s" , rangeTypesPrefix , strings .ToLower (typ .String ()))
212
+ // use a new dependency cache, as we don't want to taint the main one due to matching schema
213
+ md , err := storageSchemaToDescriptorInternal (ts , msgTypeName , newDependencyCache (), useProto3 )
214
+ if err != nil {
215
+ return nil , fmt .Errorf ("failed to generate range descriptor %q: %v" , msgTypeName , err )
216
+ }
217
+ dm .rangeTypes [typ ] = md
218
+ return md , nil
219
+ }
220
+
221
+ func (dm * dependencyCache ) getRange (typ storagepb.TableFieldSchema_Type ) protoreflect.MessageDescriptor {
222
+ md , ok := dm .rangeTypes [typ ]
223
+ if ! ok {
224
+ return nil
225
+ }
226
+ return md
227
+ }
228
+
152
229
// StorageSchemaToProto2Descriptor builds a protoreflect.Descriptor for a given table schema using proto2 syntax.
153
230
func StorageSchemaToProto2Descriptor (inSchema * storagepb.TableSchema , scope string ) (protoreflect.Descriptor , error ) {
154
- dc := make ( dependencyCache )
231
+ dc := newDependencyCache ( )
155
232
// TODO: b/193064992 tracks support for wrapper types. In the interim, disable wrapper usage.
156
- return storageSchemaToDescriptorInternal (inSchema , scope , & dc , false )
233
+ return storageSchemaToDescriptorInternal (inSchema , scope , dc , false )
157
234
}
158
235
159
236
// StorageSchemaToProto3Descriptor builds a protoreflect.Descriptor for a given table schema using proto3 syntax.
160
237
//
161
238
// NOTE: Currently the write API doesn't yet support proto3 behaviors (default value, wrapper types, etc), but this is provided for
162
239
// completeness.
163
240
func StorageSchemaToProto3Descriptor (inSchema * storagepb.TableSchema , scope string ) (protoreflect.Descriptor , error ) {
164
- dc := make ( dependencyCache )
165
- return storageSchemaToDescriptorInternal (inSchema , scope , & dc , true )
241
+ dc := newDependencyCache ( )
242
+ return storageSchemaToDescriptorInternal (inSchema , scope , dc , true )
166
243
}
167
244
168
245
// Internal implementation of the conversion code.
@@ -178,10 +255,11 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
178
255
for _ , f := range inSchema .GetFields () {
179
256
fNumber = fNumber + 1
180
257
currentScope := fmt .Sprintf ("%s__%s" , scope , f .GetName ())
181
- // If we're dealing with a STRUCT type, we must deal with sub messages.
182
- // As multiple submessages may share the same type definition, we use a dependency cache
183
- // and interrogate it / populate it as we're going.
258
+
184
259
if f .Type == storagepb .TableFieldSchema_STRUCT {
260
+ // If we're dealing with a STRUCT type, we must deal with sub messages.
261
+ // As multiple submessages may share the same type definition, we use a dependency cache
262
+ // and interrogate it / populate it as we're going.
185
263
foundDesc := cache .get (& storagepb.TableSchema {Fields : f .GetFields ()})
186
264
if foundDesc != nil {
187
265
// check to see if we already have this in current dependency list
@@ -225,6 +303,30 @@ func storageSchemaToDescriptorInternal(inSchema *storagepb.TableSchema, scope st
225
303
fields = append (fields , fdp )
226
304
}
227
305
} else {
306
+ if f .Type == storagepb .TableFieldSchema_RANGE {
307
+ // Range handling is a special case of general struct handling.
308
+ ret := f .GetRangeElementType ()
309
+ if ret == nil {
310
+ return nil , fmt .Errorf ("field %q is a RANGE, but doesn't include RangeElementType info" , f .GetName ())
311
+ }
312
+ foundDesc , err := cache .addRangeByElementType (ret .GetType (), useProto3 )
313
+ if err != nil {
314
+ return nil , err
315
+ }
316
+ if foundDesc != nil {
317
+ haveDep := false
318
+ for _ , dep := range deps {
319
+ if messageDependsOnFile (foundDesc , dep ) {
320
+ haveDep = true
321
+ break
322
+ }
323
+ }
324
+ // If dep is missing, add to current dependencies.
325
+ if ! haveDep {
326
+ deps = append (deps , foundDesc .ParentFile ())
327
+ }
328
+ }
329
+ }
228
330
fd , err := tableFieldSchemaToFieldDescriptorProto (f , fNumber , currentScope , useProto3 )
229
331
if err != nil {
230
332
return nil , newConversionError (currentScope , err )
@@ -318,6 +420,13 @@ func tableFieldSchemaToFieldDescriptorProto(field *storagepb.TableFieldSchema, i
318
420
TypeName : proto .String (scope ),
319
421
Label : convertModeToLabel (field .GetMode (), useProto3 ),
320
422
}
423
+ } else if field .GetType () == storagepb .TableFieldSchema_RANGE {
424
+ fdp = & descriptorpb.FieldDescriptorProto {
425
+ Name : proto .String (name ),
426
+ Number : proto .Int32 (idx ),
427
+ TypeName : proto .String (fmt .Sprintf ("%s%s" , rangeTypesPrefix , strings .ToLower (field .GetRangeElementType ().GetType ().String ()))),
428
+ Label : convertModeToLabel (field .GetMode (), useProto3 ),
429
+ }
321
430
} else {
322
431
// For (REQUIRED||REPEATED) fields for proto3, or all cases for proto2, we can use the expected scalar types.
323
432
if field .GetMode () != storagepb .TableFieldSchema_NULLABLE || ! useProto3 {
0 commit comments