Repository: ariga/atlas
Pull request #343 (2021-12-26 13:35:27)

sql/sqlspec: better support default values
sqlspec.Column Default attribute is now a `schemaspec.Value` that can contain either `LiteralValue` or `RawExpr`. 

This is currently integration-tested on MySQL and SQLite. 


Patch:
diff --git a/go.mod b/go.mod
index bed4d890..6a9916d1 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,6 @@ require (
 require (
 	github.com/go-openapi/inflect v0.19.0
 	github.com/mitchellh/go-homedir v1.0.0
-	github.com/pelletier/go-toml/v2 v2.0.0-beta.4
 )
 
 require (
diff --git a/go.sum b/go.sum
index 89014137..5dfd7d83 100644
--- a/go.sum
+++ b/go.sum
@@ -223,10 +223,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pelletier/go-toml/v2 v2.0.0-beta.4 h1:GCs8ebsDtEH3RiO78+BvhHqj65d/I6tjESitJZc07Rc=
-github.com/pelletier/go-toml/v2 v2.0.0-beta.4/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
diff --git a/internal/integration/go.sum b/internal/integration/go.sum
index 0f4effb5..a4f64326 100644
--- a/internal/integration/go.sum
+++ b/internal/integration/go.sum
@@ -66,7 +66,6 @@ github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -242,7 +241,6 @@ github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
 github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -252,7 +250,6 @@ github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -276,7 +273,6 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pelletier/go-toml/v2 v2.0.0-beta.4 h1:GCs8ebsDtEH3RiO78+BvhHqj65d/I6tjESitJZc07Rc=
 github.com/pelletier/go-toml/v2 v2.0.0-beta.4/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -313,13 +309,11 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
 github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
diff --git a/schema/schemaspec/extension.go b/schema/schemaspec/extension.go
index aee6b06e..61333839 100644
--- a/schema/schemaspec/extension.go
+++ b/schema/schemaspec/extension.go
@@ -257,6 +257,10 @@ func setField(field reflect.Value, attr *Attr) error {
 		}
 		field.SetBool(b)
 	case reflect.Ptr:
+		if err := setPtr(field, attr.V); err != nil {
+			return fmt.Errorf("schemaspec: failed setting pointer field %q: %w", attr.K, err)
+		}
+	case reflect.Interface:
 		field.Set(reflect.ValueOf(attr.V))
 	default:
 		return fmt.Errorf("schemaspec: unsupported field kind %q", field.Kind())
@@ -264,6 +268,22 @@ func setField(field reflect.Value, attr *Attr) error {
 	return nil
 }
 
+func setPtr(field reflect.Value, val Value) error {
+	rt := reflect.TypeOf(val)
+	if field.Type() == rt {
+		field.Set(reflect.ValueOf(val))
+		return nil
+	}
+	if x, ok := val.(*RawExpr); ok {
+		i := field.Interface()
+		if _, ok := i.(*Type); ok {
+			field.Set(reflect.ValueOf(&Type{T: x.X}))
+			return nil
+		}
+	}
+	return fmt.Errorf("unhandled pointer type %T", val)
+}
+
 // setSliceAttr sets the value of attr to the slice field. This function expects both the target field
 // and the source attr to be slices.
 func setSliceAttr(field reflect.Value, attr *Attr) error {
@@ -394,6 +414,20 @@ func scanAttr(key string, r *Resource, field reflect.Value) error {
 		lit = fmt.Sprintf("%d", field.Int())
 	case reflect.Bool:
 		lit = strconv.FormatBool(field.Bool())
+	case reflect.Interface:
+		if field.IsNil() {
+			return nil
+		}
+		i := field.Interface()
+		v, ok := i.(Value)
+		if !ok {
+			return fmt.Errorf("schemaspec: unsupported interface type %T for field %q", i, key)
+		}
+		r.SetAttr(&Attr{
+			K: key,
+			V: v,
+		})
+		return nil
 	default:
 		return fmt.Errorf("schemaspec: unsupported field kind %q", field.Kind())
 	}
diff --git a/schema/schemaspec/schemahcl/context.go b/schema/schemaspec/schemahcl/context.go
index 7f326ee4..4cb6a813 100644
--- a/schema/schemaspec/schemahcl/context.go
+++ b/schema/schemaspec/schemahcl/context.go
@@ -153,6 +153,7 @@ var (
 		},
 	})
 	ctyTypeSpec = cty.Capsule("type", reflect.TypeOf(schemaspec.Type{}))
+	ctyRawExpr  = cty.Capsule("raw_expr", reflect.TypeOf(schemaspec.RawExpr{}))
 )
 
 // defRegistry returns a tree of blockDef structs representing the schema of the
diff --git a/schema/schemaspec/schemahcl/hcl.go b/schema/schemaspec/schemahcl/hcl.go
index 7a2a5cf2..ffc0b428 100644
--- a/schema/schemaspec/schemahcl/hcl.go
+++ b/schema/schemaspec/schemahcl/hcl.go
@@ -117,6 +117,8 @@ func toAttrs(ctx *hcl.EvalContext, hclAttrs hclsyntax.Attributes) ([]*schemaspec
 		switch {
 		case isRef(value):
 			at.V = &schemaspec.Ref{V: value.GetAttr("__ref").AsString()}
+		case value.Type() == ctyRawExpr:
+			at.V = value.EncapsulatedValue().(*schemaspec.RawExpr)
 		case value.Type() == ctyTypeSpec:
 			at.V = value.EncapsulatedValue().(*schemaspec.Type)
 		case value.Type().IsTupleType():
@@ -247,6 +249,7 @@ func labels(r *schemaspec.Resource) []string {
 }
 
 func (s *state) writeAttr(attr *schemaspec.Attr, body *hclwrite.Body) error {
+	attr = normalizeLiterals(attr)
 	switch v := attr.V.(type) {
 	case *schemaspec.Ref:
 		expr := strings.ReplaceAll(v.V, "$", "")
@@ -265,6 +268,10 @@ func (s *state) writeAttr(attr *schemaspec.Attr, body *hclwrite.Body) error {
 		body.SetAttributeRaw(attr.K, hclRawTokens(st))
 	case *schemaspec.LiteralValue:
 		body.SetAttributeRaw(attr.K, hclRawTokens(v.V))
+	case *schemaspec.RawExpr:
+		// TODO(rotemtam): the func name should be decided on contextual basis.
+		fnc := fmt.Sprintf("sql(%q)", v.X)
+		body.SetAttributeRaw(attr.K, hclRawTokens(fnc))
 	case *schemaspec.ListValue:
 		lst := make([]string, 0, len(v.V))
 		for _, item := range v.V {
@@ -285,6 +292,21 @@ func (s *state) writeAttr(attr *schemaspec.Attr, body *hclwrite.Body) error {
 	return nil
 }
 
+// normalizeLiterals transforms attriburtes with LiteralValue that cannot be
+// written as correct HCL into RawExpr.
+func normalizeLiterals(attr *schemaspec.Attr) *schemaspec.Attr {
+	lv, ok := attr.V.(*schemaspec.LiteralValue)
+	if !ok {
+		return attr
+	}
+	exp := "x = " + lv.V
+	p := hclparse.NewParser()
+	if _, diag := p.ParseHCL([]byte(exp), ""); diag != nil {
+		return &schemaspec.Attr{K: attr.K, V: &schemaspec.RawExpr{X: lv.V}}
+	}
+	return attr
+}
+
 func (s *state) findTypeSpec(t string) (*schemaspec.TypeSpec, bool) {
 	for _, v := range s.config.types {
 		if v.T == t {
diff --git a/schema/schemaspec/schemahcl/opts.go b/schema/schemaspec/schemahcl/opts.go
index 63481c1d..b0e116fa 100644
--- a/schema/schemaspec/schemahcl/opts.go
+++ b/schema/schemaspec/schemahcl/opts.go
@@ -56,7 +56,7 @@ func WithTypes(typeSpecs []*schemaspec.TypeSpec) Option {
 				ctx.Functions[typeSpec.Name] = typeFuncSpec(typeSpec)
 			}
 		}
-		ctx.Functions["sql"] = rawTypeImpl()
+		ctx.Functions["sql"] = rawExprImpl()
 		return ctx
 	}
 	return func(config *Config) {
@@ -65,15 +65,15 @@ func WithTypes(typeSpecs []*schemaspec.TypeSpec) Option {
 	}
 }
 
-func rawTypeImpl() function.Function {
+func rawExprImpl() function.Function {
 	return function.New(&function.Spec{
 		Params: []function.Parameter{
 			{Name: "def", Type: cty.String, AllowNull: false},
 		},
-		Type: function.StaticReturnType(ctyTypeSpec),
+		Type: function.StaticReturnType(ctyRawExpr),
 		Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
-			t := &schemaspec.Type{T: args[0].AsString()}
-			return cty.CapsuleVal(ctyTypeSpec, t), nil
+			t := &schemaspec.RawExpr{X: args[0].AsString()}
+			return cty.CapsuleVal(ctyRawExpr, t), nil
 		},
 	})
 }
diff --git a/schema/schemaspec/spec.go b/schema/schemaspec/spec.go
index 58accd2e..4f67be4c 100644
--- a/schema/schemaspec/spec.go
+++ b/schema/schemaspec/spec.go
@@ -31,6 +31,11 @@ type (
 		V string
 	}
 
+	// RawExpr implements Value and represents any raw expression.
+	RawExpr struct {
+		X string
+	}
+
 	// ListValue implements Value and represents a list of Values.
 	ListValue struct {
 		V []Value
@@ -251,6 +256,7 @@ func (s *TypeSpec) Attr(name string) (*TypeAttr, bool) {
 }
 
 func (*LiteralValue) val() {}
+func (*RawExpr) val()      {}
 func (*ListValue) val()    {}
 func (*Ref) val()          {}
 func (*Type) val()         {}
diff --git a/sql/internal/specutil/convert.go b/sql/internal/specutil/convert.go
index 26ce9bda..26e73fa3 100644
--- a/sql/internal/specutil/convert.go
+++ b/sql/internal/specutil/convert.go
@@ -7,6 +7,7 @@ package specutil
 import (
 	"errors"
 	"fmt"
+	"strconv"
 	"strings"
 
 	"ariga.io/atlas/schema/schemaspec"
@@ -94,7 +95,14 @@ func Column(spec *sqlspec.Column, conv ConvertTypeFunc) (*schema.Column, error)
 		},
 	}
 	if spec.Default != nil {
-		out.Default = &schema.Literal{V: spec.Default.V}
+		switch d := spec.Default.(type) {
+		case *schemaspec.LiteralValue:
+			out.Default = &schema.Literal{V: d.V}
+		case *schemaspec.RawExpr:
+			out.Default = &schema.RawExpr{X: d.X}
+		default:
+			return nil, fmt.Errorf("unsupported value type for default: %T", d)
+		}
 	}
 	ct, err := conv(spec)
 	if err != nil {
@@ -286,14 +294,53 @@ func FromColumn(col *schema.Column, columnTypeSpec ColumnTypeSpecFunc) (*sqlspec
 	if err != nil {
 		return nil, err
 	}
-	return &sqlspec.Column{
+	spec := &sqlspec.Column{
 		Name: col.Name,
 		Type: ct.Type,
 		Null: col.Type.Null,
 		DefaultExtension: schemaspec.DefaultExtension{
 			Extra: schemaspec.Resource{Attrs: ct.DefaultExtension.Extra.Attrs},
 		},
-	}, nil
+	}
+	if col.Default != nil {
+		lv, err := toValue(col.Default)
+		if err != nil {
+			return nil, err
+		}
+		spec.Default = lv
+	}
+	return spec, nil
+}
+
+func toValue(expr schema.Expr) (schemaspec.Value, error) {
+	var (
+		v   string
+		err error
+	)
+	switch expr := expr.(type) {
+	case *schema.RawExpr:
+		return &schemaspec.RawExpr{X: expr.X}, nil
+	case *schema.Literal:
+		v, err = normalizeQuotes(expr.V)
+		if err != nil {
+			return nil, err
+		}
+		return &schemaspec.LiteralValue{V: v}, nil
+	default:
+		return nil, fmt.Errorf("converting expr %T to literal value", expr)
+	}
+}
+
+func normalizeQuotes(s string) (string, error) {
+	if len(s) < 2 {
+		return s, nil
+	}
+	// If string is quoted with single quotes:
+	if strings.HasPrefix(s, `'`) && strings.HasSuffix(s, `'`) {
+		uq := strings.ReplaceAll(s[1:len(s)-1], "''", "'")
+		return strconv.Quote(uq), nil
+	}
+	return s, nil
 }
 
 // FromIndex converts schema.Index to sqlspec.Index
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 2ed98a9f..676d9de6 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -223,6 +223,20 @@ func (b *Builder) MapComma(x interface{}, f func(i int, b *Builder)) *Builder {
 	return b
 }
 
+// MapCommaErr is like MapComma, but returns an error if f returns an error.
+func (b *Builder) MapCommaErr(x interface{}, f func(i int, b *Builder) error) error {
+	s := reflect.ValueOf(x)
+	for i := 0; i < s.Len(); i++ {
+		if i > 0 {
+			b.Comma()
+		}
+		if err := f(i, b); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // Wrap wraps the written string with parentheses.
 func (b *Builder) Wrap(f func(b *Builder)) *Builder {
 	b.WriteByte('(')
diff --git a/sql/mysql/inspect.go b/sql/mysql/inspect.go
index 593bf43d..aca2bc32 100644
--- a/sql/mysql/inspect.go
+++ b/sql/mysql/inspect.go
@@ -224,7 +224,7 @@ func (i *inspect) addColumn(t *schema.Table, rows *sql.Rows) error {
 		return err
 	}
 	c.Type.Type = ct
-	if err := extraAttr(c, extra.String); err != nil {
+	if err := extraAttr(t, c, extra.String); err != nil {
 		return err
 	}
 	if sqlx.ValidString(defaults) {
@@ -564,14 +564,18 @@ func parseColumn(typ string) (parts []string, size int64, unsigned bool, err err
 
 // extraAttr parses the EXTRA column from the INFORMATION_SCHEMA.COLUMNS table
 // and appends its parsed representation to the column.
-func extraAttr(c *schema.Column, extra string) error {
+func extraAttr(t *schema.Table, c *schema.Column, extra string) error {
 	switch extra := strings.ToLower(extra); extra {
 	case "", "null": // ignore.
 	case defaultGen:
 		// The column has an expression default value
 		// and it is handled in Driver.addColumn.
 	case autoIncrement:
-		c.Attrs = append(c.Attrs, &AutoIncrement{A: extra})
+		a := &AutoIncrement{}
+		// Reference to the table attribute if exists, as
+		// there can be only one auto_increment in a table.
+		sqlx.Has(t.Attrs, a)
+		c.Attrs = append(c.Attrs, a)
 	case "default_generated on update current_timestamp", "on update current_timestamp",
 		"on update current_timestamp()" /* MariaDB format. */ :
 		c.Attrs = append(c.Attrs, &OnUpdate{A: extra})
@@ -742,9 +746,9 @@ ORDER BY
 
 type (
 	// AutoIncrement attribute for columns with "AUTO_INCREMENT" as a default.
+	// V represent an optional start value for the counter.
 	AutoIncrement struct {
 		schema.Attr
-		A string
 		V int64
 	}
 
diff --git a/sql/mysql/migrate.go b/sql/mysql/migrate.go
index f41cc357..ad044575 100644
--- a/sql/mysql/migrate.go
+++ b/sql/mysql/migrate.go
@@ -69,7 +69,9 @@ func (s *state) plan(changes []schema.Change) error {
 	for _, c := range planned {
 		switch c := c.(type) {
 		case *schema.AddTable:
-			s.addTable(c)
+			if err := s.addTable(c); err != nil {
+				return err
+			}
 		case *schema.DropTable:
 			s.dropTable(c)
 		case *schema.ModifyTable:
@@ -124,7 +126,7 @@ func (s *state) topLevel(changes []schema.Change) []schema.Change {
 
 // addTable builds and appends the migrate.Change
 // for creating a table in a schema.
-func (s *state) addTable(add *schema.AddTable) {
+func (s *state) addTable(add *schema.AddTable) error {
 	b := Build("CREATE TABLE").Table(add.T)
 	if sqlx.Has(add.Extra, &schema.IfNotExists{}) {
 		b.P("IF NOT EXISTS")
@@ -155,13 +157,16 @@ func (s *state) addTable(add *schema.AddTable) {
 			s.fks(b, add.T.ForeignKeys...)
 		}
 	})
-	s.tableAttr(b, add.T.Attrs...)
+	if err := s.tableAttr(b, add.T.Attrs...); err != nil {
+		return err
+	}
 	s.append(&migrate.Change{
 		Cmd:     b.String(),
 		Source:  add,
 		Reverse: Build("DROP TABLE").Table(add.T).String(),
 		Comment: fmt.Sprintf("create %q table", add.T.Name),
 	})
+	return nil
 }
 
 // dropTable builds and appends the migrate.Change
@@ -223,7 +228,9 @@ func (s *state) modifyTable(modify *schema.ModifyTable) error {
 	}
 	for i := range changes {
 		if len(changes[i]) > 0 {
-			s.alterTable(modify.T, changes[i])
+			if err := s.alterTable(modify.T, changes[i]); err != nil {
+				return err
+			}
 		}
 	}
 	return nil
@@ -231,8 +238,9 @@ func (s *state) modifyTable(modify *schema.ModifyTable) error {
 
 // alterTable modifies the given table by executing on it a list of
 // changes in one SQL statement.
-func (s *state) alterTable(t *schema.Table, changes []schema.Change) {
+func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 	var (
+		errors     []string
 		b          = Build("ALTER TABLE").Table(t)
 		reversible = true
 		reverse    = b.Clone()
@@ -271,14 +279,23 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) {
 			b.P("DROP FOREIGN KEY").Ident(change.F.Symbol)
 			reversible = false
 		case *schema.AddAttr:
-			s.tableAttr(b, change.A)
+			if err := s.tableAttr(b, change.A); err != nil {
+				errors = append(errors, fmt.Sprintf("add attribute: %s", err.Error()))
+			}
 			// Unsupported reverse operation.
 			reversible = false
 		case *schema.ModifyAttr:
-			s.tableAttr(b, change.To)
-			s.tableAttr(reverse, change.From)
+			if err := s.tableAttr(b, change.To); err != nil {
+				errors = append(errors, fmt.Sprintf("modify attribute: %s", err.Error()))
+			}
+			if err := s.tableAttr(reverse, change.From); err != nil {
+				errors = append(errors, fmt.Sprintf("reverse modify attribute: %s", err.Error()))
+			}
 		}
 	})
+	if len(errors) > 0 {
+		return fmt.Errorf("alter table: %s", strings.Join(errors, ", "))
+	}
 	change := &migrate.Change{
 		Cmd: b.String(),
 		Source: &schema.ModifyTable{
@@ -291,6 +308,7 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) {
 		change.Reverse = reverse.String()
 	}
 	s.append(change)
+	return nil
 }
 
 func (s *state) column(b *sqlx.Builder, t *schema.Table, c *schema.Column) {
@@ -319,8 +337,9 @@ func (s *state) column(b *sqlx.Builder, t *schema.Table, c *schema.Column) {
 			b.P("ON UPDATE", a.A)
 		case *AutoIncrement:
 			b.P("AUTO_INCREMENT")
-			if a.V != 0 {
-				b.P(strconv.FormatInt(a.V, 10))
+			// Auto increment with value should be configured on table options.
+			if a.V != 0 && !sqlx.Has(t.Attrs, &AutoIncrement{}) {
+				t.Attrs = append(t.Attrs, a)
 			}
 		default:
 			s.attr(b, a)
@@ -375,20 +394,23 @@ func (s *state) fks(b *sqlx.Builder, fks ...*schema.ForeignKey) {
 
 // tableAttr writes the given table attribute to the SQL
 // statement builder when a table is created or altered.
-func (s *state) tableAttr(b *sqlx.Builder, attrs ...schema.Attr) {
+func (s *state) tableAttr(b *sqlx.Builder, attrs ...schema.Attr) error {
 	for _, a := range attrs {
 		switch a := a.(type) {
 		case *AutoIncrement:
-			b.P("AUTO_INCREMENT")
-			if a.V != 0 {
-				b.P(strconv.FormatInt(a.V, 10))
+			if a.V == 0 {
+				return fmt.Errorf("missing value for table option AUTO_INCREMENT")
 			}
+			b.P("AUTO_INCREMENT", strconv.FormatInt(a.V, 10))
 		case *schema.Charset:
 			b.P("CHARACTER SET", a.V)
-		default:
-			s.attr(b, a)
+		case *schema.Collation:
+			b.P("COLLATE", a.V)
+		case *schema.Comment:
+			b.P("COMMENT", quote(a.Text))
 		}
 	}
+	return nil
 }
 
 // collation returns the table collation from its attributes
diff --git a/sql/postgres/convert.go b/sql/postgres/convert.go
index 78e63b4d..38bcde2f 100644
--- a/sql/postgres/convert.go
+++ b/sql/postgres/convert.go
@@ -171,11 +171,8 @@ func parseColumn(s string) (*columnDesc, error) {
 	)
 	switch c.parts[0] {
 	case tVarChar, tCharVar, tChar, tCharacter:
-		if len(c.parts) > 1 {
-			c.size, err = strconv.ParseInt(c.parts[1], 10, 64)
-			if err != nil {
-				return nil, fmt.Errorf("postgres: parse size %q: %w", parts[1], err)
-			}
+		if err := parseCharParts(c.parts, c); err != nil {
+			return nil, err
 		}
 	case tDecimal, tNumeric:
 		if len(parts) > 1 {
@@ -204,6 +201,29 @@ func parseColumn(s string) (*columnDesc, error) {
 	return c, nil
 }
 
+func parseCharParts(parts []string, c *columnDesc) error {
+	j := strings.Join(parts, " ")
+	switch {
+	case strings.HasPrefix(j, tVarChar):
+		c.typ = tVarChar
+		parts = parts[1:]
+	case strings.HasPrefix(j, tCharVar):
+		c.typ = tCharVar
+		parts = parts[2:]
+	default:
+		parts = parts[1:]
+	}
+	if len(parts) == 0 {
+		return nil
+	}
+	size, err := strconv.ParseInt(parts[0], 10, 64)
+	if err != nil {
+		return fmt.Errorf("postgres: parse size %q: %w", parts[1], err)
+	}
+	c.size = size
+	return nil
+}
+
 func parseBitParts(parts []string, c *columnDesc) error {
 	if len(parts) == 1 {
 		c.size = 1
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index a9197f41..4faf7e08 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -3,11 +3,13 @@ package postgres
 import (
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 	"unicode"
 
 	"ariga.io/atlas/schema/schemaspec"
 	"ariga.io/atlas/sql/internal/specutil"
+	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
 )
@@ -76,9 +78,29 @@ func convertIndex(spec *sqlspec.Index, parent *schema.Table) (*schema.Index, err
 
 // convertColumn converts a sqlspec.Column into a schema.Column.
 func convertColumn(spec *sqlspec.Column, _ *schema.Table) (*schema.Column, error) {
+	if err := fixDefaultQuotes(spec.Default); err != nil {
+		return nil, err
+	}
 	return specutil.Column(spec, convertColumnType)
 }
 
+// fixDefaultQuotes fixes the quotes on the Default field to be single quotes
+// instead of double quotes.
+func fixDefaultQuotes(value schemaspec.Value) error {
+	lv, ok := value.(*schemaspec.LiteralValue)
+	if !ok {
+		return nil
+	}
+	if sqlx.IsQuoted(lv.V, '"') {
+		uq, err := strconv.Unquote(lv.V)
+		if err != nil {
+			return err
+		}
+		lv.V = "'" + uq + "'"
+	}
+	return nil
+}
+
 // convertColumnType converts a sqlspec.Column into a concrete Postgres schema.Type.
 func convertColumnType(spec *sqlspec.Column) (schema.Type, error) {
 	return TypeRegistry.Type(spec.Type, spec.Extra.Attrs)
@@ -151,6 +173,7 @@ var TypeRegistry = specutil.NewRegistry(
 		specutil.TypeSpec(tBit, &schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64}),
 		specutil.AliasTypeSpec("bit_varying", tBitVar, &schemaspec.TypeAttr{Name: "len", Kind: reflect.Int64}),
 		specutil.TypeSpec(tVarChar, specutil.SizeTypeAttr(true)),
+		specutil.AliasTypeSpec("character_varying", tCharVar, &schemaspec.TypeAttr{Name: "size", Kind: reflect.Int}),
 		specutil.TypeSpec(tChar, specutil.SizeTypeAttr(true)),
 		specutil.TypeSpec(tCharacter, specutil.SizeTypeAttr(true)),
 		specutil.TypeSpec(tInt2),
diff --git a/sql/sqlspec/sqlspec.go b/sql/sqlspec/sqlspec.go
index ef49ac0e..6e01abda 100644
--- a/sql/sqlspec/sqlspec.go
+++ b/sql/sqlspec/sqlspec.go
@@ -26,10 +26,10 @@ type (
 
 	// Column holds a specification for a column in an SQL table.
 	Column struct {
-		Name    string                   `spec:",name"`
-		Null    bool                     `spec:"null"`
-		Type    *schemaspec.Type         `spec:"type"`
-		Default *schemaspec.LiteralValue `spec:"default"`
+		Name    string           `spec:",name"`
+		Null    bool             `spec:"null"`
+		Type    *schemaspec.Type `spec:"type"`
+		Default schemaspec.Value `spec:"default"`
 		schemaspec.DefaultExtension
 	}
 


Test patch:
diff --git a/internal/integration/mysql_test.go b/internal/integration/mysql_test.go
index 958b57b6..0d8fb158 100644
--- a/internal/integration/mysql_test.go
+++ b/internal/integration/mysql_test.go
@@ -530,6 +530,35 @@ func TestMySQL_CLI(t *testing.T) {
 	})
 }
 
+func TestMySQL_DefaultsHCL(t *testing.T) {
+	n := "atlas_defaults"
+	myRun(t, func(t *myTest) {
+		ddl := `
+create table atlas_defaults
+(
+	string varchar(255) default "hello_world",
+	quoted varchar(100) default 'never say "never"',
+	tBit bit(10) default b'10101',
+	ts timestamp default CURRENT_TIMESTAMP,
+	number int default 42
+)
+`
+		t.dropTables(n)
+		_, err := t.db.Exec(ddl)
+		require.NoError(t, err)
+		realm := t.loadRealm()
+		hcl := schemahcl.New(schemahcl.WithTypes(mysql.TypeRegistry.Specs()))
+		spec, err := mysql.MarshalSpec(realm.Schemas[0], hcl)
+		require.NoError(t, err)
+		var s schema.Schema
+		err = mysql.UnmarshalSpec(spec, hcl, &s)
+		require.NoError(t, err)
+		t.dropTables(n)
+		t.applyHcl(string(spec))
+		ensureNoChange(t, realm.Schemas[0].Tables[0])
+	})
+}
+
 func TestMySQL_Sanity(t *testing.T) {
 	n := "atlas_types_sanity"
 	t.Run("Common", func(t *testing.T) {
diff --git a/internal/integration/postgres_test.go b/internal/integration/postgres_test.go
index d34400c3..c94d17a7 100644
--- a/internal/integration/postgres_test.go
+++ b/internal/integration/postgres_test.go
@@ -439,6 +439,36 @@ func TestPostgres_CLI(t *testing.T) {
 	})
 }
 
+func TestPostgres_DefaultsHCL(t *testing.T) {
+	n := "atlas_defaults"
+	pgRun(t, func(t *pgTest) {
+		ddl := `
+create table atlas_defaults
+(
+	string varchar(255) default 'hello_world',
+	quoted varchar(100) default 'never say "never"',
+	tBit bit(10) default b'10101',
+	ts timestamp default CURRENT_TIMESTAMP,
+	tstz timestamp with time zone default CURRENT_TIMESTAMP,
+	number int default 42
+)
+`
+		t.dropTables(n)
+		_, err := t.db.Exec(ddl)
+		require.NoError(t, err)
+		realm := t.loadRealm()
+		hcl := schemahcl.New(schemahcl.WithTypes(postgres.TypeRegistry.Specs()))
+		spec, err := postgres.MarshalSpec(realm.Schemas[0], hcl)
+		require.NoError(t, err)
+		var s schema.Schema
+		err = postgres.UnmarshalSpec(spec, hcl, &s)
+		require.NoError(t, err)
+		t.dropTables(n)
+		t.applyHcl(string(spec))
+		ensureNoChange(t, realm.Schemas[0].Tables[0])
+	})
+}
+
 func TestPostgres_Sanity(t *testing.T) {
 	n := "atlas_types_sanity"
 	ddl := `
diff --git a/internal/integration/sqlite_test.go b/internal/integration/sqlite_test.go
index c939a75b..e979f347 100644
--- a/internal/integration/sqlite_test.go
+++ b/internal/integration/sqlite_test.go
@@ -396,6 +396,34 @@ schema "public" {
 	})
 }
 
+func TestSQLite_DefaultsHCL(t *testing.T) {
+	n := "atlas_defaults"
+	liteRun(t, func(t *liteTest) {
+		ddl := `
+create table atlas_defaults
+(
+	string varchar(255) default "hello_world",
+	quoted varchar(100) default 'never say "never"',
+	d date default current_timestamp,
+	n integer default 0x100 
+)
+`
+		t.dropTables(n)
+		_, err := t.db.Exec(ddl)
+		require.NoError(t, err)
+		realm := t.loadRealm()
+		hcl := schemahcl.New(schemahcl.WithTypes(sqlite.TypeRegistry.Specs()))
+		spec, err := sqlite.MarshalSpec(realm.Schemas[0], hcl)
+		require.NoError(t, err)
+		var s schema.Schema
+		err = sqlite.UnmarshalSpec(spec, hcl, &s)
+		require.NoError(t, err)
+		t.dropTables(n)
+		t.applyHcl(string(spec))
+		ensureNoChange(t, realm.Schemas[0].Tables[0])
+	})
+}
+
 func TestSQLite_Sanity(t *testing.T) {
 	n := "atlas_types_sanity"
 	ddl := `
diff --git a/sql/mysql/inspect_test.go b/sql/mysql/inspect_test.go
index e0389660..adb5b323 100644
--- a/sql/mysql/inspect_test.go
+++ b/sql/mysql/inspect_test.go
@@ -86,7 +86,7 @@ func TestDriver_InspectTable(t *testing.T) {
 				require.Len(t.PrimaryKey.Parts, 1)
 				require.True(t.PrimaryKey.Parts[0].C == t.Columns[0])
 				require.EqualValues([]*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "bigint(20)", Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}},
+					{Name: "id", Type: &schema.ColumnType{Raw: "bigint(20)", Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 				}, t.Columns)
 			},
 		},
@@ -126,7 +126,7 @@ func TestDriver_InspectTable(t *testing.T) {
 				require.Len(t.PrimaryKey.Parts, 1)
 				require.True(t.PrimaryKey.Parts[0].C == t.Columns[0])
 				require.EqualValues([]*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "bigint(20)", Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}},
+					{Name: "id", Type: &schema.ColumnType{Raw: "bigint(20)", Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 					{Name: "v57_tiny", Type: &schema.ColumnType{Raw: "tinyint(1)", Type: &schema.BoolType{T: "tinyint"}}},
 					{Name: "v57_tiny_unsigned", Type: &schema.ColumnType{Raw: "tinyint(4) unsigned", Type: &schema.IntegerType{T: "tinyint", Unsigned: true}}},
 					{Name: "v57_small", Type: &schema.ColumnType{Raw: "smallint(6)", Type: &schema.IntegerType{T: "smallint"}}},
@@ -179,7 +179,7 @@ func TestDriver_InspectTable(t *testing.T) {
 				require.Len(t.PrimaryKey.Parts, 1)
 				require.True(t.PrimaryKey.Parts[0].C == t.Columns[0])
 				require.EqualValues([]*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "bigint(20)", Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}},
+					{Name: "id", Type: &schema.ColumnType{Raw: "bigint(20)", Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 					{Name: "tiny_int", Type: &schema.ColumnType{Raw: "tinyint(1)", Type: &schema.BoolType{T: "tinyint"}}},
 					{Name: "longtext", Type: &schema.ColumnType{Raw: "longtext", Type: &schema.StringType{T: "longtext"}}},
 					{Name: "jsonc", Type: &schema.ColumnType{Raw: "json", Type: &schema.JSONType{T: "json"}}},
@@ -482,7 +482,7 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "unique_index", Unique: true, Table: t, Attrs: []schema.Attr{&IndexType{T: "BTREE"}}},
 				}
 				columns := []*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}},
+					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 					{Name: "nickname", Type: &schema.ColumnType{Raw: "varchar(255)", Type: &schema.StringType{T: "varchar", Size: 255}}, Indexes: indexes[0:1], Attrs: []schema.Attr{&schema.Charset{V: "utf8mb4"}, &schema.Collation{V: "utf8mb4_0900_ai_ci"}}},
 					{Name: "oid", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Indexes: indexes[2:]},
 					{Name: "uid", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Indexes: indexes[2:]},
@@ -547,7 +547,7 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Symbol: "self_reference", Table: t, OnUpdate: schema.NoAction, OnDelete: schema.Cascade, RefTable: t},
 				}
 				columns := []*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}, ForeignKeys: fks[0:1]},
+					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{}}, ForeignKeys: fks[0:1]},
 					{Name: "oid", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, ForeignKeys: fks[0:1]},
 					{Name: "uid", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, ForeignKeys: fks[1:2]},
 				}
@@ -590,7 +590,7 @@ func TestDriver_InspectTable(t *testing.T) {
 				require.Equal("users", t.Name)
 				require.Equal("public", t.Schema.Name)
 				columns := []*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}},
+					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 					{Name: "c1", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}},
 				}
 				require.EqualValues(columns, t.Columns)
@@ -727,7 +727,7 @@ func TestDriver_InspectSchema(t *testing.T) {
 					{Symbol: "spouse_id", Table: users, OnUpdate: schema.NoAction, OnDelete: schema.Cascade, RefTable: users},
 				}
 				userColumns := []*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}},
+					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 					{Name: "spouse_id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}, Null: true}, ForeignKeys: userFKs},
 				}
 				userFKs[0].Columns = userColumns[1:]
@@ -740,7 +740,7 @@ func TestDriver_InspectSchema(t *testing.T) {
 					{Symbol: "owner_id", Table: pets, OnUpdate: schema.NoAction, OnDelete: schema.Cascade, RefTable: users, RefColumns: userColumns[:1]},
 				}
 				petsColumns := []*schema.Column{
-					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{A: "auto_increment"}}},
+					{Name: "id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 					{Name: "owner_id", Type: &schema.ColumnType{Raw: "int", Type: &schema.IntegerType{T: "int"}, Null: true}, ForeignKeys: petsFKs},
 				}
 				petsFKs[0].Columns = petsColumns[1:]
diff --git a/sql/mysql/migrate_test.go b/sql/mysql/migrate_test.go
index 712fa5b6..600b0ac7 100644
--- a/sql/mysql/migrate_test.go
+++ b/sql/mysql/migrate_test.go
@@ -193,19 +193,65 @@ func TestPlanChanges(t *testing.T) {
 		},
 		{
 			changes: []schema.Change{
-				&schema.AddTable{
-					T: &schema.Table{
+				func() *schema.AddTable {
+					t := &schema.Table{
 						Name: "posts",
 						Columns: []*schema.Column{
-							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}}},
+							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
 							{Name: "text", Type: &schema.ColumnType{Type: &schema.StringType{T: "text"}, Null: true}},
 						},
-					},
-				},
+					}
+					t.PrimaryKey = &schema.Index{Parts: []*schema.IndexPart{{C: t.Columns[0]}}}
+					return &schema.AddTable{T: t}
+				}(),
+			},
+			plan: &migrate.Plan{
+				Reversible: true,
+				Changes:    []*migrate.Change{{Cmd: "CREATE TABLE `posts` (`id` bigint NOT NULL AUTO_INCREMENT, `text` text NULL, PRIMARY KEY (`id`))", Reverse: "DROP TABLE `posts`"}},
+			},
+		},
+		{
+			changes: []schema.Change{
+				func() *schema.AddTable {
+					t := &schema.Table{
+						Name: "posts",
+						Columns: []*schema.Column{
+							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{V: 100}}},
+							{Name: "text", Type: &schema.ColumnType{Type: &schema.StringType{T: "text"}, Null: true}},
+						},
+						Attrs: []schema.Attr{
+							&schema.Charset{V: "utf8mb4"},
+							&schema.Collation{V: "utf8mb4_bin"},
+							&schema.Comment{Text: "posts comment"},
+						},
+					}
+					t.PrimaryKey = &schema.Index{Parts: []*schema.IndexPart{{C: t.Columns[0]}}}
+					return &schema.AddTable{T: t}
+				}(),
+			},
+			plan: &migrate.Plan{
+				Reversible: true,
+				Changes:    []*migrate.Change{{Cmd: "CREATE TABLE `posts` (`id` bigint NOT NULL AUTO_INCREMENT, `text` text NULL, PRIMARY KEY (`id`)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT \"posts comment\" AUTO_INCREMENT 100", Reverse: "DROP TABLE `posts`"}},
+			},
+		},
+		{
+			changes: []schema.Change{
+				func() *schema.AddTable {
+					t := &schema.Table{
+						Name: "posts",
+						Columns: []*schema.Column{
+							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}}, Attrs: []schema.Attr{&AutoIncrement{}}},
+							{Name: "text", Type: &schema.ColumnType{Type: &schema.StringType{T: "text"}, Null: true}},
+						},
+						Attrs: []schema.Attr{&AutoIncrement{V: 10}},
+					}
+					t.PrimaryKey = &schema.Index{Parts: []*schema.IndexPart{{C: t.Columns[0]}}}
+					return &schema.AddTable{T: t}
+				}(),
 			},
 			plan: &migrate.Plan{
 				Reversible: true,
-				Changes:    []*migrate.Change{{Cmd: "CREATE TABLE `posts` (`id` bigint NOT NULL, `text` text NULL)", Reverse: "DROP TABLE `posts`"}},
+				Changes:    []*migrate.Change{{Cmd: "CREATE TABLE `posts` (`id` bigint NOT NULL AUTO_INCREMENT, `text` text NULL, PRIMARY KEY (`id`)) AUTO_INCREMENT 10", Reverse: "DROP TABLE `posts`"}},
 			},
 		},
 		{