Repository: googleapis/google-cloud-go
Pull request #2516 (2020-06-25 17:33:48)

bigquery: support type aliases in SchemaFromJSON
This PR allows the SchemaFromJSON() function to accept JSON that uses aliases for type names, and resolve them to the types that the API will actually return.  This is a convenience feature for users who want to process schema from JSON representations that are less strict.  We continue to use only the bidirectional FieldType identifiers through the rest of the library.

TL;DR if you invoke SchemaFromJSON() and you specify schema using the strings INT64, FLOAT64, BOOL, or STRUCT in the type names, we'll accept them and coerce them to the expected INTEGER, FLOAT, BOOLEAN, and RECORD representations.


Patch:
diff --git a/bigquery/schema.go b/bigquery/schema.go
index c1c021872a..1fbb92571e 100644
--- a/bigquery/schema.go
+++ b/bigquery/schema.go
@@ -200,6 +200,13 @@ var (
 		NumericFieldType:   true,
 		GeographyFieldType: true,
 	}
+	// The API will accept alias names for the types based on the Standard SQL type names.
+	fieldAliases = map[FieldType]FieldType{
+		"BOOL":    BooleanFieldType,
+		"FLOAT64": FloatFieldType,
+		"INT64":   IntegerFieldType,
+		"STRUCT":  RecordFieldType,
+	}
 )
 
 var typeOfByteSlice = reflect.TypeOf([]byte{})
@@ -491,6 +498,18 @@ type bigQueryJSONField struct {
 	Type        string              `json:"type"`
 }
 
+// validateKnownType ensures a type is known (or alias of a known type).
+func validateKnownType(in FieldType) (FieldType, error) {
+	if _, ok := fieldTypes[in]; !ok {
+		// not a defined type, check aliases.
+		if resolved, ok := fieldAliases[in]; ok {
+			return resolved, nil
+		}
+		return "", fmt.Errorf("unknown field type (%v)", in)
+	}
+	return in, nil
+}
+
 // convertSchemaFromJSON generates a Schema:
 func convertSchemaFromJSON(fs []bigQueryJSONField) (Schema, error) {
 	convertedSchema := Schema{}
@@ -510,11 +529,11 @@ func convertSchemaFromJSON(fs []bigQueryJSONField) (Schema, error) {
 		}
 
 		// Check that the field-type (string) maps to a known FieldType:
-		if _, ok := fieldTypes[FieldType(f.Type)]; !ok {
-			return nil, fmt.Errorf("unknown field type (%v)", f.Type)
+		validType, err := validateKnownType(FieldType(f.Type))
+		if err != nil {
+			return nil, err
 		}
-		convertedFieldSchema.Type = FieldType(f.Type)
-
+		convertedFieldSchema.Type = validType
 		convertedSchema = append(convertedSchema, convertedFieldSchema)
 	}
 	return convertedSchema, nil


Test patch:
diff --git a/bigquery/schema_test.go b/bigquery/schema_test.go
index 1df20fd9db..5bd10849ac 100644
--- a/bigquery/schema_test.go
+++ b/bigquery/schema_test.go
@@ -1042,7 +1042,11 @@ func TestSchemaFromJSON(t *testing.T) {
 	{"name":"flat_time","type":"TIME","mode":"REQUIRED","description":"Flat nullable TIME"},
 	{"name":"flat_datetime","type":"DATETIME","mode":"NULLABLE","description":"Flat required DATETIME"},
 	{"name":"flat_numeric","type":"NUMERIC","mode":"REQUIRED","description":"Flat nullable NUMERIC"},
-	{"name":"flat_geography","type":"GEOGRAPHY","mode":"REQUIRED","description":"Flat required GEOGRAPHY"}
+	{"name":"flat_geography","type":"GEOGRAPHY","mode":"REQUIRED","description":"Flat required GEOGRAPHY"},
+	{"name":"aliased_integer","type":"INT64","mode":"REQUIRED","description":"Aliased required integer"},
+	{"name":"aliased_boolean","type":"BOOL","mode":"NULLABLE","description":"Aliased nullable boolean"},
+	{"name":"aliased_float","type":"FLOAT64","mode":"REQUIRED","description":"Aliased required float"},
+	{"name":"aliased_record","type":"STRUCT","mode":"NULLABLE","description":"Aliased nullable record"}
 ]`),
 			expectedSchema: Schema{
 				fieldSchema("Flat nullable string", "flat_string", "STRING", false, false, nil),
@@ -1056,6 +1060,10 @@ func TestSchemaFromJSON(t *testing.T) {
 				fieldSchema("Flat required DATETIME", "flat_datetime", "DATETIME", false, false, nil),
 				fieldSchema("Flat nullable NUMERIC", "flat_numeric", "NUMERIC", false, true, nil),
 				fieldSchema("Flat required GEOGRAPHY", "flat_geography", "GEOGRAPHY", false, true, nil),
+				fieldSchema("Aliased required integer", "aliased_integer", "INTEGER", false, true, nil),
+				fieldSchema("Aliased nullable boolean", "aliased_boolean", "BOOLEAN", false, false, nil),
+				fieldSchema("Aliased required float", "aliased_float", "FLOAT", false, true, nil),
+				fieldSchema("Aliased nullable record", "aliased_record", "RECORD", false, false, nil),
 			},
 		},
 		{