Repository: ariga/atlas
Pull request #308 (2021-12-14 07:11:34)

schema/schemaspec/schemahcl: support optional type attrs
for types with non required attributes, support both:
```hcl
type = int
// AND
type = int(10)
```


Patch:
diff --git a/schema/schemaspec/schemahcl/hcl.go b/schema/schemaspec/schemahcl/hcl.go
index 577d39e8..1532420b 100644
--- a/schema/schemaspec/schemahcl/hcl.go
+++ b/schema/schemaspec/schemahcl/hcl.go
@@ -314,6 +314,10 @@ func hclType(spec *schemaspec.TypeSpec, typ *schemaspec.Type) (string, error) {
 			}
 		}
 	}
+	// If no args were chosen and the type can be described without a function.
+	if len(args) == 0 && len(typeFuncReqArgs(spec)) == 0 {
+		return spec.Name, nil
+	}
 	return fmt.Sprintf("%s(%s)", spec.Name, strings.Join(args, ",")), nil
 }
 
diff --git a/schema/schemaspec/schemahcl/opts.go b/schema/schemaspec/schemahcl/opts.go
index c96357f1..59a5f2a6 100644
--- a/schema/schemaspec/schemahcl/opts.go
+++ b/schema/schemaspec/schemahcl/opts.go
@@ -47,62 +47,75 @@ func WithTypes(typeSpecs []*schemaspec.TypeSpec) Option {
 		for _, ts := range typeSpecs {
 			typeSpec := ts
 			config.types = append(config.types, typeSpec)
-			if len(typeFuncArgs(typeSpec)) == 0 {
+			// If no required args exist, register the type as a variable in the HCL context.
+			if len(typeFuncReqArgs(typeSpec)) == 0 {
 				typ := &schemaspec.Type{T: typeSpec.T}
 				config.ctx.Variables[typeSpec.Name] = cty.CapsuleVal(ctyTypeSpec, typ)
-				continue
 			}
-			spec := &function.Spec{
-				Type: function.StaticReturnType(ctyTypeSpec),
+			// If func args exist, register the type as a function in HCL.
+			if len(typeFuncArgs(typeSpec)) > 0 {
+				config.ctx.Functions[typeSpec.Name] = typeFuncSpec(typeSpec)
 			}
-			for _, arg := range typeFuncArgs(typeSpec) {
-				p := function.Parameter{
-					Name:      arg.Name,
-					AllowNull: !arg.Required,
-				}
-				switch arg.Kind {
-				case reflect.Slice:
-					p.Type = cty.DynamicPseudoType
-					spec.VarParam = &p
-				case reflect.String:
-					p.Type = cty.String
-					spec.Params = append(spec.Params, p)
-				case reflect.Int, reflect.Float32:
-					p.Type = cty.Number
-					spec.Params = append(spec.Params, p)
-				case reflect.Bool:
-					p.Type = cty.Bool
-					spec.Params = append(spec.Params, p)
+		}
+	}
+}
+
+// typeFuncSpec returns the HCL function for defining the type in the spec.
+func typeFuncSpec(typeSpec *schemaspec.TypeSpec) function.Function {
+	spec := &function.Spec{
+		Type: function.StaticReturnType(ctyTypeSpec),
+	}
+	for _, arg := range typeFuncArgs(typeSpec) {
+		p := function.Parameter{
+			Name:      arg.Name,
+			AllowNull: !arg.Required,
+		}
+		switch arg.Kind {
+		case reflect.Slice:
+			p.Type = cty.DynamicPseudoType
+			spec.VarParam = &p
+		case reflect.String:
+			p.Type = cty.String
+			spec.Params = append(spec.Params, p)
+		case reflect.Int, reflect.Float32:
+			p.Type = cty.Number
+			spec.Params = append(spec.Params, p)
+		case reflect.Bool:
+			p.Type = cty.Bool
+			spec.Params = append(spec.Params, p)
+		}
+		spec.Impl = typeFuncSpecImpl(spec, typeSpec)
+	}
+	return function.New(spec)
+}
+
+// typeFuncSpecImpl returns the function implementation for the HCL function spec.
+func typeFuncSpecImpl(spec *function.Spec, typeSpec *schemaspec.TypeSpec) function.ImplFunc {
+	return func(args []cty.Value, retType cty.Type) (cty.Value, error) {
+		t := &schemaspec.Type{
+			T: typeSpec.T,
+		}
+		if spec.VarParam != nil {
+			lst := &schemaspec.ListValue{}
+			for _, arg := range args {
+				v, err := extractLiteralValue(arg)
+				if err != nil {
+					return cty.NilVal, err
 				}
-				spec.Impl = func(args []cty.Value, retType cty.Type) (cty.Value, error) {
-					t := &schemaspec.Type{
-						T: typeSpec.T,
-					}
-					if spec.VarParam != nil {
-						lst := &schemaspec.ListValue{}
-						for _, arg := range args {
-							v, err := extractLiteralValue(arg)
-							if err != nil {
-								return cty.NilVal, err
-							}
-							lst.V = append(lst.V, v)
-						}
-						t.Attrs = append(t.Attrs, &schemaspec.Attr{K: spec.VarParam.Name, V: lst})
-					} else {
-						for i, arg := range args {
-							v, err := extractLiteralValue(arg)
-							if err != nil {
-								return cty.NilVal, err
-							}
-							attrName := typeSpec.Attributes[i].Name
-							t.Attrs = append(t.Attrs, &schemaspec.Attr{K: attrName, V: v})
-						}
-					}
-					return cty.CapsuleVal(ctyTypeSpec, t), nil
+				lst.V = append(lst.V, v)
+			}
+			t.Attrs = append(t.Attrs, &schemaspec.Attr{K: spec.VarParam.Name, V: lst})
+		} else {
+			for i, arg := range args {
+				v, err := extractLiteralValue(arg)
+				if err != nil {
+					return cty.NilVal, err
 				}
+				attrName := typeSpec.Attributes[i].Name
+				t.Attrs = append(t.Attrs, &schemaspec.Attr{K: attrName, V: v})
 			}
-			config.ctx.Functions[typeSpec.Name] = function.New(spec)
 		}
+		return cty.CapsuleVal(ctyTypeSpec, t), nil
 	}
 }
 
@@ -119,3 +132,16 @@ func typeFuncArgs(spec *schemaspec.TypeSpec) []*schemaspec.TypeAttr {
 	}
 	return args
 }
+
+// typeFuncReqArgs returns the required type attributes that are configured via arguments.
+// for instance, in MySQL a field may be defined as both `int` and `int(10)`, in this case
+// it is not a required parameter.
+func typeFuncReqArgs(spec *schemaspec.TypeSpec) []*schemaspec.TypeAttr {
+	var args []*schemaspec.TypeAttr
+	for _, arg := range typeFuncArgs(spec) {
+		if arg.Required {
+			args = append(args, arg)
+		}
+	}
+	return args
+}


Test patch:
diff --git a/schema/schemaspec/schemahcl/context_test.go b/schema/schemaspec/schemahcl/context_test.go
index ca439069..d9d6a1b0 100644
--- a/schema/schemaspec/schemahcl/context_test.go
+++ b/schema/schemaspec/schemahcl/context_test.go
@@ -354,6 +354,7 @@ x = from_ctx
 func TestWithTypes(t *testing.T) {
 	f := `first    = int
 second   = bool
+third    = int(10)
 sized    = varchar(255)
 variadic = enum("a","b","c")
 `
@@ -365,6 +366,7 @@ variadic = enum("a","b","c")
 					Name: "int",
 					T:    "int",
 					Attributes: []*schemaspec.TypeAttr{
+						{Name: "size", Kind: reflect.Int, Required: false},
 						{Name: "unsigned", Kind: reflect.Bool, Required: false},
 					},
 				},
@@ -388,6 +390,7 @@ variadic = enum("a","b","c")
 	var test struct {
 		First    *schemaspec.Type `spec:"first"`
 		Second   *schemaspec.Type `spec:"second"`
+		Third    *schemaspec.Type `spec:"third"`
 		Varchar  *schemaspec.Type `spec:"sized"`
 		Variadic *schemaspec.Type `spec:"variadic"`
 	}
@@ -416,6 +419,12 @@ variadic = enum("a","b","c")
 			},
 		},
 	}, test.Variadic)
+	require.EqualValues(t, &schemaspec.Type{
+		T: "int",
+		Attrs: []*schemaspec.Attr{
+			{K: "size", V: &schemaspec.LiteralValue{V: "10"}},
+		},
+	}, test.Third)
 	after, err := s.MarshalSpec(&test)
 	require.NoError(t, err)
 	require.EqualValues(t, f, string(after))