Repository: cloudspannerecosystem/harbourbridge
Pull request #81 (2020-09-08 05:48:25)

refactor: update the Spanner data type
Refactored `ddl/ast.go` and all other files dependent on it to handle Spanner datatype such that we can remove usage of interface. Spanner column type is refactored and represented by :
```sh
type Type struct {
	Name string
	Len int64
        IsArray bool
}
```

Further, we want to support `schema-only` and `data-only` modes. In order to support it, we need a feature to extract all relevant information of source and spanner database in a JSON file. To facilitate the same, refactoring of code is done.

hengfengli: A recommendation for the PR title. There is a convention to follow: https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716. 
Dharmil-searce: > A recommendation for the PR title. There is a convention to follow: https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716.

@hengfengli Sure, I will keep it in mind from next time onwards.

Patch:
diff --git a/go.mod b/go.mod
index bf4598e..8a872f2 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,8 @@ require (
 	github.com/DATA-DOG/go-sqlmock v1.4.1
 	github.com/davecgh/go-spew v1.1.1
 	github.com/go-sql-driver/mysql v1.5.0
+	github.com/gorilla/handlers v1.5.0
+	github.com/gorilla/mux v1.7.3
 	github.com/goware/modvendor v0.0.0-20190516042800-ce72b408a8fe // indirect
 	github.com/juju/errors v0.0.0-20200330140219-3fe23663418f
 	github.com/lfittl/pg_query_go v1.0.0
diff --git a/go.sum b/go.sum
index 3140635..6c3f3d0 100644
--- a/go.sum
+++ b/go.sum
@@ -125,6 +125,8 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
+github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
+github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -236,6 +238,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gorilla/handlers v1.5.0 h1:4wjo3sf9azi99c8hTmyaxp9y5S+pFszsy3pP0rAw/lw=
+github.com/gorilla/handlers v1.5.0/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
diff --git a/internal/convert.go b/internal/convert.go
index 67b2825..b62df6b 100644
--- a/internal/convert.go
+++ b/internal/convert.go
@@ -271,7 +271,7 @@ func (conv *Conv) AddPrimaryKeys() {
 		if len(ct.Pks) == 0 {
 			k := conv.buildPrimaryKey(t)
 			ct.ColNames = append(ct.ColNames, k)
-			ct.ColDefs[k] = ddl.ColumnDef{Name: k, T: ddl.Int64{}}
+			ct.ColDefs[k] = ddl.ColumnDef{Name: k, T: ddl.Type{Name: ddl.Int64}}
 			ct.Pks = []ddl.IndexKey{ddl.IndexKey{Col: k}}
 			conv.SpSchema[t] = ct
 			conv.SyntheticPKeys[t] = SyntheticPKey{k, 0}
diff --git a/internal/report.go b/internal/report.go
index 7f33e85..706ec4f 100644
--- a/internal/report.go
+++ b/internal/report.go
@@ -173,7 +173,7 @@ func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]Schem
 					conv.Unexpected(err.Error())
 				}
 				srcType := srcSchema.ColDefs[srcCol].Type.Print()
-				spType := spSchema.ColDefs[spCol].PrintColumnDefType()
+				spType := spSchema.ColDefs[spCol].T.PrintColumnDefType()
 				// A note on case: Spanner types are case insensitive, but
 				// default to upper case. In particular, the Spanner AST uses
 				// upper case, so spType is upper case. Many source DBs
diff --git a/mysql/data.go b/mysql/data.go
index 670eabb..28b4793 100644
--- a/mysql/data.go
+++ b/mysql/data.go
@@ -17,14 +17,12 @@ package mysql
 import (
 	"fmt"
 	"math/bits"
-	"reflect"
 	"strconv"
 	"strings"
 	"time"
 
 	"cloud.google.com/go/civil"
 	"cloud.google.com/go/spanner"
-
 	"github.com/cloudspannerecosystem/harbourbridge/internal"
 	"github.com/cloudspannerecosystem/harbourbridge/schema"
 	"github.com/cloudspannerecosystem/harbourbridge/spanner/ddl"
@@ -75,7 +73,7 @@ func ConvertData(conv *internal.Conv, srcTable string, srcCols []string, srcSche
 		}
 		var x interface{}
 		var err error
-		if spColDef.IsArray {
+		if spColDef.T.IsArray {
 			x, err = convArray(spColDef.T, srcColDef.Type.Name, vals[i])
 		} else {
 			x, err = convScalar(spColDef.T, srcColDef.Type.Name, conv.TimezoneOffset, vals[i])
@@ -99,13 +97,13 @@ func ConvertData(conv *internal.Conv, srcTable string, srcCols []string, srcSche
 // appropriate Spanner value. It is the caller's responsibility to
 // detect and handle NULL values: convScalar will return error if a
 // NULL value is passed.
-func convScalar(spannerType ddl.ScalarType, srcTypeName string, TimezoneOffset string, val string) (interface{}, error) {
+func convScalar(spannerType ddl.Type, srcTypeName string, TimezoneOffset string, val string) (interface{}, error) {
 	// Whitespace within the val string is considered part of the data value.
 	// Note that many of the underlying conversions functions we use (like
 	// strconv.ParseFloat and strconv.ParseInt) return "invalid syntax"
 	// errors if whitespace were to appear at the start or end of a string.
 	// We do not expect mysqldump to generate such output.
-	switch spannerType.(type) {
+	switch spannerType.Name {
 	case ddl.Bool:
 		return convBool(val)
 	case ddl.Bytes:
@@ -121,7 +119,7 @@ func convScalar(spannerType ddl.ScalarType, srcTypeName string, TimezoneOffset s
 	case ddl.Timestamp:
 		return convTimestamp(srcTypeName, TimezoneOffset, val)
 	default:
-		return val, fmt.Errorf("data conversion not implemented for type %v", reflect.TypeOf(spannerType))
+		return val, fmt.Errorf("data conversion not implemented for type %v", spannerType.Name)
 	}
 }
 
@@ -199,7 +197,7 @@ func convTimestamp(srcTypeName string, TimezoneOffset string, val string) (t tim
 // array elements are NULL. In other words, convArray handles "{1,
 // NULL, 2}", but it does not handle "NULL" (it returns error).
 // NOTE : convArray would only be called when MySQL 'SET' datatype is encountered.
-func convArray(spannerType ddl.ScalarType, srcTypeName string, v string) (interface{}, error) {
+func convArray(spannerType ddl.Type, srcTypeName string, v string) (interface{}, error) {
 	v = strings.TrimSpace(v)
 	// Handle empty array. Note that we use an empty NullString array
 	// for all Spanner array types since this will be converted to the
@@ -215,7 +213,7 @@ func convArray(spannerType ddl.ScalarType, srcTypeName string, v string) (interf
 	// Hence we have to do the following case analysis.
 	// NOTE: MySQL only supports SET of string which will be translated
 	// to spanner array<string>.
-	switch spannerType.(type) {
+	switch spannerType.Name {
 	case ddl.String:
 		var r []spanner.NullString
 		for _, s := range a {
@@ -231,7 +229,7 @@ func convArray(spannerType ddl.ScalarType, srcTypeName string, v string) (interf
 		}
 		return r, nil
 	}
-	return []interface{}{}, fmt.Errorf("array type conversion not implemented for type %v", reflect.TypeOf(spannerType))
+	return []interface{}{}, fmt.Errorf("array type conversion not implemented for type %v", spannerType.Name)
 }
 
 // processQuote returns the unquoted version of s.
diff --git a/mysql/toddl.go b/mysql/toddl.go
index 631e10e..4b15ffd 100644
--- a/mysql/toddl.go
+++ b/mysql/toddl.go
@@ -52,7 +52,7 @@ func schemaToDDL(conv *internal.Conv) error {
 			spColNames = append(spColNames, colName)
 			ty, issues := toSpannerType(conv, srcCol.Type.Name, srcCol.Type.Mods)
 			if len(srcCol.Type.ArrayBounds) > 1 {
-				ty = ddl.String{Len: ddl.MaxLength{}}
+				ty = ddl.Type{Name: ddl.String, Len: ddl.MaxLength}
 				issues = append(issues, internal.MultiDimensionalArray)
 			}
 			// TODO(hengfeng): add issues for all elements of srcCol.Ignored.
@@ -68,10 +68,10 @@ func schemaToDDL(conv *internal.Conv) error {
 			if len(issues) > 0 {
 				conv.Issues[srcTable.Name][srcCol.Name] = issues
 			}
+			ty.IsArray = len(srcCol.Type.ArrayBounds) == 1
 			spColDef[colName] = ddl.ColumnDef{
 				Name:    colName,
 				T:       ty,
-				IsArray: len(srcCol.Type.ArrayBounds) == 1,
 				NotNull: srcCol.NotNull,
 				Comment: "From: " + quoteIfNeeded(srcCol.Name) + " " + srcCol.Type.Print(),
 			}
@@ -91,7 +91,7 @@ func schemaToDDL(conv *internal.Conv) error {
 // mods) into a Spanner type. This is the core source-to-Spanner type
 // mapping.  toSpannerType returns the Spanner type and a list of type
 // conversion issues encountered.
-func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.ScalarType, []internal.SchemaIssue) {
+func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.Type, []internal.SchemaIssue) {
 	maxExpectedMods := func(n int) {
 		if len(mods) > n {
 			conv.Unexpected(fmt.Sprintf("Found %d mods while processing type id=%s", len(mods), id))
@@ -100,73 +100,73 @@ func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.ScalarType
 	switch id {
 	case "bool", "boolean":
 		maxExpectedMods(0)
-		return ddl.Bool{}, nil
+		return ddl.Type{Name: ddl.Bool}, nil
 	case "tinyint":
 		maxExpectedMods(1)
 		// tinyint(1) is a bool in MySQL
 		if len(mods) > 0 && mods[0] == 1 {
-			return ddl.Bool{}, nil
+			return ddl.Type{Name: ddl.Bool}, nil
 		}
-		return ddl.Int64{}, []internal.SchemaIssue{internal.Widened}
+		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "double":
 		maxExpectedMods(2)
-		return ddl.Float64{}, nil
+		return ddl.Type{Name: ddl.Float64}, nil
 	case "float":
 		maxExpectedMods(2)
-		return ddl.Float64{}, []internal.SchemaIssue{internal.Widened}
+		return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.Widened}
 	case "numeric", "decimal": // Map all numeric and decimal types to float64.
 		maxExpectedMods(2)
 		if len(mods) > 0 && mods[0] <= 15 {
 			// float64 can represent this numeric type faithfully.
 			// Note: int64 has 53 bits for mantissa, which is ~15.96
 			// decimal digits.
-			return ddl.Float64{}, []internal.SchemaIssue{internal.DecimalThatFits}
+			return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.DecimalThatFits}
 		}
-		return ddl.Float64{}, []internal.SchemaIssue{internal.Decimal}
+		return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.Decimal}
 	case "bigint":
 		maxExpectedMods(1)
-		return ddl.Int64{}, nil
+		return ddl.Type{Name: ddl.Int64}, nil
 	case "smallint", "mediumint", "integer", "int":
 		maxExpectedMods(1)
-		return ddl.Int64{}, []internal.SchemaIssue{internal.Widened}
+		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "bit":
 		maxExpectedMods(1)
-		return ddl.Bytes{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "varchar", "char":
 		maxExpectedMods(1)
 		if len(mods) > 0 {
-			return ddl.String{Len: ddl.Int64Length{Value: mods[0]}}, nil
+			return ddl.Type{Name: ddl.String, Len: mods[0]}, nil
 		}
-		return ddl.String{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "text", "tinytext", "mediumtext", "longtext":
 		maxExpectedMods(1)
-		return ddl.String{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "set", "enum":
 		maxExpectedMods(1)
-		return ddl.String{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "json":
 		maxExpectedMods(0)
-		return ddl.String{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "binary", "varbinary":
 		maxExpectedMods(1)
-		return ddl.Bytes{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "tinyblob", "mediumblob", "blob", "longblob":
 		maxExpectedMods(1)
-		return ddl.Bytes{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "date":
 		maxExpectedMods(1)
-		return ddl.Date{}, nil
+		return ddl.Type{Name: ddl.Date}, nil
 	case "datetime":
 		maxExpectedMods(1)
-		return ddl.Timestamp{}, []internal.SchemaIssue{internal.Datetime}
+		return ddl.Type{Name: ddl.Timestamp}, []internal.SchemaIssue{internal.Datetime}
 	case "timestamp":
 		maxExpectedMods(1)
-		return ddl.Timestamp{}, nil
+		return ddl.Type{Name: ddl.Timestamp}, nil
 	case "time", "year":
 		maxExpectedMods(1)
-		return ddl.String{Len: ddl.MaxLength{}}, []internal.SchemaIssue{internal.Time}
+		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Time}
 	}
-	return ddl.String{Len: ddl.MaxLength{}}, []internal.SchemaIssue{internal.NoGoodType}
+	return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.NoGoodType}
 }
 
 func quoteIfNeeded(s string) string {
diff --git a/postgres/data.go b/postgres/data.go
index 7a9d1b4..6a8083d 100644
--- a/postgres/data.go
+++ b/postgres/data.go
@@ -25,7 +25,6 @@ import (
 
 	"cloud.google.com/go/civil"
 	"cloud.google.com/go/spanner"
-
 	"github.com/cloudspannerecosystem/harbourbridge/internal"
 	"github.com/cloudspannerecosystem/harbourbridge/spanner/ddl"
 )
@@ -85,7 +84,7 @@ func ConvertData(conv *internal.Conv, srcTable string, srcCols []string, vals []
 		}
 		var x interface{}
 		var err error
-		if spColDef.IsArray {
+		if spColDef.T.IsArray {
 			x, err = convArray(spColDef.T, srcColDef.Type.Name, conv.Location, vals[i])
 		} else {
 			x, err = convScalar(spColDef.T, srcColDef.Type.Name, conv.Location, vals[i])
@@ -109,13 +108,13 @@ func ConvertData(conv *internal.Conv, srcTable string, srcCols []string, vals []
 // appropriate Spanner value. It is the caller's responsibility to
 // detect and handle NULL values: convScalar will return error if a
 // NULL value is passed.
-func convScalar(spannerType ddl.ScalarType, srcTypeName string, location *time.Location, val string) (interface{}, error) {
+func convScalar(spannerType ddl.Type, srcTypeName string, location *time.Location, val string) (interface{}, error) {
 	// Whitespace within the val string is considered part of the data value.
 	// Note that many of the underlying conversions functions we use (like
 	// strconv.ParseFloat and strconv.ParseInt) return "invalid syntax"
 	// errors if whitespace were to appear at the start or end of a string.
 	// We do not expect pg_dump to generate such output.
-	switch spannerType.(type) {
+	switch spannerType.Name {
 	case ddl.Bool:
 		return convBool(val)
 	case ddl.Bytes:
@@ -131,7 +130,7 @@ func convScalar(spannerType ddl.ScalarType, srcTypeName string, location *time.L
 	case ddl.Timestamp:
 		return convTimestamp(srcTypeName, location, val)
 	default:
-		return val, fmt.Errorf("data conversion not implemented for type %v", reflect.TypeOf(spannerType))
+		return val, fmt.Errorf("data conversion not implemented for type %v", spannerType.Name)
 	}
 }
 
@@ -224,7 +223,7 @@ func convTimestamp(srcTypeName string, location *time.Location, val string) (t t
 // is NULL. However, convArray does handle the case where individual
 // array elements are NULL. In other words, convArray handles "{1,
 // NULL, 2}", but it does not handle "NULL" (it returns error).
-func convArray(spannerType ddl.ScalarType, srcTypeName string, location *time.Location, v string) (interface{}, error) {
+func convArray(spannerType ddl.Type, srcTypeName string, location *time.Location, v string) (interface{}, error) {
 	v = strings.TrimSpace(v)
 	// Handle empty array. Note that we use an empty NullString array
 	// for all Spanner array types since this will be converted to the
@@ -240,7 +239,7 @@ func convArray(spannerType ddl.ScalarType, srcTypeName string, location *time.Lo
 	// The Spanner client for go does not accept []interface{} for arrays.
 	// Instead it only accepts slices of a specific type e.g. []int64, []string.
 	// Hence we have to do the following case analysis.
-	switch spannerType.(type) {
+	switch spannerType.Name {
 	case ddl.Bool:
 		var r []spanner.NullBool
 		for _, s := range a {
diff --git a/postgres/infoschema.go b/postgres/infoschema.go
index e34c7fd..b2a1459 100644
--- a/postgres/infoschema.go
+++ b/postgres/infoschema.go
@@ -144,7 +144,7 @@ func ConvertSQLRow(conv *internal.Conv, srcTable string, srcCols []string, srcSc
 		}
 		var spVal interface{}
 		var err error
-		if spCd.IsArray {
+		if spCd.T.IsArray {
 			spVal, err = cvtSQLArray(conv, srcCd, spCd, srcVals[i])
 		} else {
 			spVal, err = cvtSQLScalar(conv, srcCd, spCd, srcVals[i])
@@ -387,7 +387,7 @@ func cvtSQLArray(conv *internal.Conv, srcCd schema.Column, spCd ddl.ColumnDef, v
 //    string
 //    time.Time
 func cvtSQLScalar(conv *internal.Conv, srcCd schema.Column, spCd ddl.ColumnDef, val interface{}) (interface{}, error) {
-	switch spCd.T.(type) {
+	switch spCd.T.Name {
 	case ddl.Bool:
 		switch v := val.(type) {
 		case bool:
diff --git a/postgres/toddl.go b/postgres/toddl.go
index 1205bb0..1ed0df9 100644
--- a/postgres/toddl.go
+++ b/postgres/toddl.go
@@ -48,7 +48,7 @@ func schemaToDDL(conv *internal.Conv) error {
 			spColNames = append(spColNames, colName)
 			ty, issues := toSpannerType(conv, srcCol.Type.Name, srcCol.Type.Mods)
 			if len(srcCol.Type.ArrayBounds) > 1 {
-				ty = ddl.String{Len: ddl.MaxLength{}}
+				ty = ddl.Type{Name: ddl.String, Len: ddl.MaxLength}
 				issues = append(issues, internal.MultiDimensionalArray)
 			}
 			// TODO: add issues for all elements of srcCol.Ignored.
@@ -61,10 +61,10 @@ func schemaToDDL(conv *internal.Conv) error {
 			if len(issues) > 0 {
 				conv.Issues[srcTable.Name][srcCol.Name] = issues
 			}
+			ty.IsArray = len(srcCol.Type.ArrayBounds) == 1
 			spColDef[colName] = ddl.ColumnDef{
 				Name:    colName,
 				T:       ty,
-				IsArray: len(srcCol.Type.ArrayBounds) == 1,
 				NotNull: srcCol.NotNull,
 				Comment: "From: " + quoteIfNeeded(srcCol.Name) + " " + srcCol.Type.Print(),
 			}
@@ -84,7 +84,7 @@ func schemaToDDL(conv *internal.Conv) error {
 // mods) into a Spanner type. This is the core source-to-Spanner type
 // mapping.  toSpannerType returns the Spanner type and a list of type
 // conversion issues encountered.
-func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.ScalarType, []internal.SchemaIssue) {
+func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.Type, []internal.SchemaIssue) {
 	maxExpectedMods := func(n int) {
 		if len(mods) > n {
 			conv.Unexpected(fmt.Sprintf("Found %d mods while processing type id=%s", len(mods), id))
@@ -93,68 +93,68 @@ func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.ScalarType
 	switch id {
 	case "bool", "boolean":
 		maxExpectedMods(0)
-		return ddl.Bool{}, nil
+		return ddl.Type{Name: ddl.Bool}, nil
 	case "bigserial":
 		maxExpectedMods(0)
-		return ddl.Int64{}, []internal.SchemaIssue{internal.Serial}
+		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Serial}
 	case "bpchar", "character": // Note: Postgres internal name for char is bpchar (aka blank padded char).
 		maxExpectedMods(1)
 		if len(mods) > 0 {
-			return ddl.String{Len: ddl.Int64Length{Value: mods[0]}}, nil
+			return ddl.Type{Name: ddl.String, Len: mods[0]}, nil
 		}
 		// Note: bpchar without length specifier is equivalent to bpchar(1)
-		return ddl.String{Len: ddl.Int64Length{Value: 1}}, nil
+		return ddl.Type{Name: ddl.String, Len: 1}, nil
 	case "bytea":
 		maxExpectedMods(0)
-		return ddl.Bytes{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "date":
 		maxExpectedMods(0)
-		return ddl.Date{}, nil
+		return ddl.Type{Name: ddl.Date}, nil
 	case "float8", "double precision":
 		maxExpectedMods(0)
-		return ddl.Float64{}, nil
+		return ddl.Type{Name: ddl.Float64}, nil
 	case "float4", "real":
 		maxExpectedMods(0)
-		return ddl.Float64{}, []internal.SchemaIssue{internal.Widened}
+		return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.Widened}
 	case "int8", "bigint":
 		maxExpectedMods(0)
-		return ddl.Int64{}, nil
+		return ddl.Type{Name: ddl.Int64}, nil
 	case "int4", "integer":
 		maxExpectedMods(0)
-		return ddl.Int64{}, []internal.SchemaIssue{internal.Widened}
+		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "int2", "smallint":
 		maxExpectedMods(0)
-		return ddl.Int64{}, []internal.SchemaIssue{internal.Widened}
+		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "numeric": // Map all numeric types to float64.
 		maxExpectedMods(2)
 		if len(mods) > 0 && mods[0] <= 15 {
 			// float64 can represent this numeric type faithfully.
 			// Note: int64 has 53 bits for mantissa, which is ~15.96
 			// decimal digits.
-			return ddl.Float64{}, []internal.SchemaIssue{internal.NumericThatFits}
+			return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.NumericThatFits}
 		}
-		return ddl.Float64{}, []internal.SchemaIssue{internal.Numeric}
+		return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.Numeric}
 	case "serial":
 		maxExpectedMods(0)
-		return ddl.Int64{}, []internal.SchemaIssue{internal.Serial}
+		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Serial}
 	case "text":
 		maxExpectedMods(0)
-		return ddl.String{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "timestamptz", "timestamp with time zone":
 		maxExpectedMods(1)
-		return ddl.Timestamp{}, nil
+		return ddl.Type{Name: ddl.Timestamp}, nil
 	case "timestamp", "timestamp without time zone":
 		maxExpectedMods(1)
 		// Map timestamp without timezone to Spanner timestamp.
-		return ddl.Timestamp{}, []internal.SchemaIssue{internal.Timestamp}
+		return ddl.Type{Name: ddl.Timestamp}, []internal.SchemaIssue{internal.Timestamp}
 	case "varchar", "character varying":
 		maxExpectedMods(1)
 		if len(mods) > 0 {
-			return ddl.String{Len: ddl.Int64Length{Value: mods[0]}}, nil
+			return ddl.Type{Name: ddl.String, Len: mods[0]}, nil
 		}
-		return ddl.String{Len: ddl.MaxLength{}}, nil
+		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	}
-	return ddl.String{Len: ddl.MaxLength{}}, []internal.SchemaIssue{internal.NoGoodType}
+	return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.NoGoodType}
 }
 
 func quoteIfNeeded(s string) string {
diff --git a/spanner/ddl/ast.go b/spanner/ddl/ast.go
index 83f150f..c27382a 100644
--- a/spanner/ddl/ast.go
+++ b/spanner/ddl/ast.go
@@ -18,113 +18,72 @@
 //
 // Definitions are from
 // https://cloud.google.com/spanner/docs/data-definition-language.
-// Before defining each type, we give the snippet from this definition.
-//
-// We use go interface types to preserve the structural constraints of
-// Spanner DDL. For example, suppose ScalarType could be either
-// an INT or a STRING with a length specifier:
-//    ScalarType := INT | STRING( length )
-// then we use a go interface type to encode ScalarType:
-//    type ScalarType interface {
-//        PrintScalarType() string  // To print ScalarTypes types.
-//    }
-// and struct types for each case:
-//    type Int struct { }
-//    type String struct { length int64 }
-// and finally, we define functions:
-//    func (i Int) PrintScalarType() { ... }
-//    func (s String) PrintScalarType() { .. }
-//
-// The net result is that the only way to build a ScalarType
-// is using Int or String.
 package ddl
 
 import (
 	"fmt"
+	"math"
+	"strconv"
 	"strings"
 )
 
-// Length encodes the following Spanner DDL definition:
-//     length:
-//        { int64_value | MAX }
-type Length interface {
-	PrintLength() string
-}
-
-// Int64Length wraps an integer length specifier.
-type Int64Length struct{ Value int64 }
-
-// MaxLength represents "MAX".
-type MaxLength struct{}
-
-// Interface validation for Length.
-var _ = []Length{Int64Length{}, MaxLength{}}
-
-// PrintLength unparses Int64Length.
-func (i Int64Length) PrintLength() string { return fmt.Sprintf("%d", i.Value) }
-
-// PrintLength unparses MaxLength.
-func (m MaxLength) PrintLength() string { return fmt.Sprintf("MAX") }
+const (
+	// Bool represent BOOL type.
+	Bool string = "BOOL"
+	// Bytes represent BYTES type.
+	Bytes string = "BYTES"
+	// Date represent DATE type.
+	Date string = "DATE"
+	// Float64 represent FLOAT64 type.
+	Float64 string = "FLOAT64"
+	// Int64 represent INT64 type.
+	Int64 string = "INT64"
+	// String represent STRING type.
+	String string = "STRING"
+	// Timestamp represent TIMESTAMP type.
+	Timestamp string = "TIMESTAMP"
+	// MaxLength is a sentinel for Type's Len field, representing the MAX value.
+	MaxLength = math.MaxInt64
+)
 
-// ScalarType encodes the following DDL definition:
-//     scalar_type:
+// Type represents the type of a column.
+//     type:
 //        { BOOL | INT64 | FLOAT64 | STRING( length ) | BYTES( length ) | DATE | TIMESTAMP }
-type ScalarType interface {
-	PrintScalarType() string
+type Type struct {
+	Name string
+	// Len encodes the following Spanner DDL definition:
+	//     length:
+	//        { int64_value | MAX }
+	Len int64
+	// IsArray represents if Type is an array_type or not
+	// When false, column has type T; when true, it is an array of type T.
+	IsArray bool
 }
 
-// Bool encodes DDL BOOL.
-type Bool struct{}
-
-// Bytes encodes DDL BYTES( length ).
-type Bytes struct{ Len Length }
-
-// Date encodes DDL DATE.
-type Date struct{}
-
-// Float64 encodes DDL FLOAT64.
-type Float64 struct{}
-
-// Int64 encodes DDL INT64.
-type Int64 struct{}
-
-// String encodes DDL STRING.
-type String struct{ Len Length }
-
-// Timestamp encodes DDL TIMESTAMP.
-type Timestamp struct{}
-
-// Interface validation for ScalarTypes
-var _ = []ScalarType{Bool{}, Bytes{}, Date{}, Float64{}, Int64{}, String{}}
-
-// PrintScalarType unparses Bool.
-func (b Bool) PrintScalarType() string { return "BOOL" }
-
-// PrintScalarType unparses Bytes.
-func (b Bytes) PrintScalarType() string { return fmt.Sprintf("BYTES(%s)", b.Len.PrintLength()) }
-
-// PrintScalarType unparses Date.
-func (d Date) PrintScalarType() string { return "DATE" }
-
-// PrintScalarType unparses Float64
-func (g Float64) PrintScalarType() string { return "FLOAT64" }
-
-// PrintScalarType unparses Int64
-func (i Int64) PrintScalarType() string { return "INT64" }
-
-// PrintScalarType unparses String
-func (s String) PrintScalarType() string { return fmt.Sprintf("STRING(%s)", s.Len.PrintLength()) }
-
-// PrintScalarType unparses Timestamp
-func (t Timestamp) PrintScalarType() string { return "TIMESTAMP" }
+// PrintColumnDefType unparses the type encoded in a ColumnDef.
+func (ty Type) PrintColumnDefType() string {
+	str := ty.Name
+	if ty.Name == String || ty.Name == Bytes {
+		str += "("
+		if ty.Len == MaxLength {
+			str += "MAX"
+		} else {
+			str += strconv.FormatInt(ty.Len, 10)
+		}
+		str += ")"
+	}
+	if ty.IsArray {
+		str = "ARRAY<" + str + ">"
+	}
+	return str
+}
 
 // ColumnDef encodes the following DDL definition:
 //     column_def:
-//       column_name {scalar_type | array_type} [NOT NULL] [options_def]
+//       column_name type [NOT NULL] [options_def]
 type ColumnDef struct {
 	Name    string
-	T       ScalarType
-	IsArray bool // When false, this column has type T; when true, it is an array of type T.
+	T       Type
 	NotNull bool
 	Comment string
 }
@@ -146,22 +105,13 @@ func (c Config) quote(s string) string {
 // comment. These are returned as separate strings to support formatting
 // needs of PrintCreateTable.
 func (cd ColumnDef) PrintColumnDef(c Config) (string, string) {
-	s := fmt.Sprintf("%s %s", c.quote(cd.Name), cd.PrintColumnDefType())
+	s := fmt.Sprintf("%s %s", c.quote(cd.Name), cd.T.PrintColumnDefType())
 	if cd.NotNull {
 		s += " NOT NULL"
 	}
 	return s, cd.Comment
 }
 
-// PrintColumnDefType unparses the type encoded in a ColumnDef.
-func (cd ColumnDef) PrintColumnDefType() string {
-	t := cd.T.PrintScalarType()
-	if cd.IsArray {
-		return fmt.Sprintf("ARRAY<%s>", t)
-	}
-	return t
-}
-
 // IndexKey encodes the following DDL definition:
 //     primary_key:
 //       PRIMARY KEY ( [key_part, ...] )


Test patch:
diff --git a/internal/convert_test.go b/internal/convert_test.go
index 964b6a4..c4476c8 100644
--- a/internal/convert_test.go
+++ b/internal/convert_test.go
@@ -49,15 +49,15 @@ func TestGetDDL(t *testing.T) {
 		Name:     "table1",
 		ColNames: []string{"a", "b"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
 		},
 		Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}
 	conv.SpSchema["table2"] = ddl.CreateTable{
 		Name:     "table2",
 		ColNames: []string{"a"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
 		},
 		Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}
 	ddl := conv.GetDDL(ddl.Config{})
@@ -123,8 +123,8 @@ func TestAddPrimaryKeys(t *testing.T) {
 		Name:     "table",
 		ColNames: []string{"a", "b"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
 		},
 		Pks: []ddl.IndexKey{}}
 	conv.AddPrimaryKeys()
@@ -132,9 +132,9 @@ func TestAddPrimaryKeys(t *testing.T) {
 		Name:     "table",
 		ColNames: []string{"a", "b", "synth_id"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a":        ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b":        ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-			"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Int64{}},
+			"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+			"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
 		},
 		Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}}}
 	assert.Equal(t, e, conv.SpSchema["table"])
diff --git a/mysql/data_test.go b/mysql/data_test.go
index dd95dd5..10f52e4 100644
--- a/mysql/data_test.go
+++ b/mysql/data_test.go
@@ -45,9 +45,9 @@ func TestProcessDataRow(t *testing.T) {
 			Name:     tableName,
 			ColNames: cols,
 			ColDefs: map[string]ddl.ColumnDef{
-				"a": ddl.ColumnDef{Name: "a", T: ddl.Float64{}},
-				"b": ddl.ColumnDef{Name: "b", T: ddl.Int64{}},
-				"c": ddl.ColumnDef{Name: "c", T: ddl.String{Len: ddl.MaxLength{}}},
+				"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Float64}},
+				"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Int64}},
+				"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 			}},
 		schema.Table{
 			Name:     tableName,
@@ -68,22 +68,21 @@ func TestProcessDataRow(t *testing.T) {
 
 func TestConvertData(t *testing.T) {
 	singleColTests := []struct {
-		name    string
-		ty      ddl.ScalarType
-		isArray bool
-		srcTy   string      // Source DB type (Used by e.g. timestamp conversions).
-		in      string      // Input value for conversion.
-		e       interface{} // Expected result.
+		name  string
+		ty    ddl.Type
+		srcTy string      // Source DB type (Used by e.g. timestamp conversions).
+		in    string      // Input value for conversion.
+		e     interface{} // Expected result.
 	}{
-		{"bool", ddl.Bool{}, false, "", "1", true},
-		{"bytes", ddl.Bytes{Len: ddl.MaxLength{}}, false, "", string([]byte{137, 80}), []byte{0x89, 0x50}}, // need some other approach to testblob type
-		{"date", ddl.Date{}, false, "", "2019-10-29", getDate("2019-10-29")},
-		{"float64", ddl.Float64{}, false, "", "42.6", float64(42.6)},
-		{"int64", ddl.Int64{}, false, "", "42", int64(42)},
-		{"string", ddl.String{Len: ddl.MaxLength{}}, false, "", "eh", "eh"},
-		{"datetime", ddl.Timestamp{}, false, "datetime", "2019-10-29 05:30:00", getTimeWithoutTimezone(t, "2019-10-29 05:30:00")},
-		{"timestamp", ddl.Timestamp{}, false, "timestamp", "2019-10-29 05:30:00", getTime(t, "2019-10-29T05:30:00+05:30")},
-		{"string array(set)", ddl.String{Len: ddl.MaxLength{}}, true, "", "1,Travel,3,Dance", []spanner.NullString{
+		{"bool", ddl.Type{Name: ddl.Bool}, "", "1", true},
+		{"bytes", ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, "", string([]byte{137, 80}), []byte{0x89, 0x50}}, // need some other approach to testblob type
+		{"date", ddl.Type{Name: ddl.Date}, "", "2019-10-29", getDate("2019-10-29")},
+		{"float64", ddl.Type{Name: ddl.Float64}, "", "42.6", float64(42.6)},
+		{"int64", ddl.Type{Name: ddl.Int64}, "", "42", int64(42)},
+		{"string", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, "", "eh", "eh"},
+		{"datetime", ddl.Type{Name: ddl.Timestamp}, "datetime", "2019-10-29 05:30:00", getTimeWithoutTimezone(t, "2019-10-29 05:30:00")},
+		{"timestamp", ddl.Type{Name: ddl.Timestamp}, "timestamp", "2019-10-29 05:30:00", getTime(t, "2019-10-29T05:30:00+05:30")},
+		{"string array(set)", ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}, "", "1,Travel,3,Dance", []spanner.NullString{
 			spanner.NullString{StringVal: "1", Valid: true},
 			spanner.NullString{StringVal: "Travel", Valid: true},
 			spanner.NullString{StringVal: "3", Valid: true},
@@ -96,7 +95,7 @@ func TestConvertData(t *testing.T) {
 			ddl.CreateTable{
 				Name:     tableName,
 				ColNames: []string{col},
-				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: tc.ty, IsArray: tc.isArray, NotNull: false}},
+				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: tc.ty, NotNull: false}},
 				Pks:      []ddl.IndexKey{}},
 			schema.Table{Name: tableName, ColNames: []string{col}, ColDefs: map[string]schema.Column{col: schema.Column{Type: schema.Type{Name: tc.srcTy}}}})
 		conv.TimezoneOffset = "+05:30"
@@ -124,7 +123,7 @@ func TestConvertTimestampData(t *testing.T) {
 			ddl.CreateTable{
 				Name:     tableName,
 				ColNames: []string{col},
-				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: ddl.Timestamp{}}}},
+				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: ddl.Type{Name: ddl.Timestamp}}}},
 			schema.Table{
 				Name:     tableName,
 				ColNames: []string{col},
@@ -186,9 +185,9 @@ func TestConvertMultiColData(t *testing.T) {
 		Name:     tableName,
 		ColNames: []string{"a", "b", "c"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-			"c": ddl.ColumnDef{Name: "c", T: ddl.Bool{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+			"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.Bool}},
 		}}
 	srcTable := schema.Table{
 		Name:     tableName,
@@ -234,9 +233,9 @@ func TestConvertError(t *testing.T) {
 		Name:     tableName,
 		ColNames: []string{"a", "b", "c"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-			"c": ddl.ColumnDef{Name: "c", T: ddl.Bool{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+			"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.Bool}},
 		}}
 	srcTable := schema.Table{
 		Name:     tableName,
@@ -283,9 +282,9 @@ func TestConvertsyntheticPKey(t *testing.T) {
 		Name:     tableName,
 		ColNames: []string{"a", "b", "c"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-			"c": ddl.ColumnDef{Name: "c", T: ddl.Bool{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+			"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.Bool}},
 		}}
 	srcTable := schema.Table{
 		Name:     tableName,
diff --git a/mysql/infoschema_test.go b/mysql/infoschema_test.go
index f397e3d..7633f08 100644
--- a/mysql/infoschema_test.go
+++ b/mysql/infoschema_test.go
@@ -99,35 +99,35 @@ func TestProcessInfoSchemaMYSQL(t *testing.T) {
 			Name:     "cart",
 			ColNames: []string{"productid", "userid", "quantity"},
 			ColDefs: map[string]ddl.ColumnDef{
-				"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-				"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-				"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
+				"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+				"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+				"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
 			},
 			Pks: []ddl.IndexKey{ddl.IndexKey{Col: "productid"}, ddl.IndexKey{Col: "userid"}}},
 		"test": ddl.CreateTable{
 			Name:     "test",
 			ColNames: []string{"id", "s", "txt", "b", "bs", "bl", "c", "c8", "d", "dec", "f8", "f4", "i8", "i4", "i2", "si", "ts", "tz", "vc", "vc6"},
 			ColDefs: map[string]ddl.ColumnDef{
-				"id":  ddl.ColumnDef{Name: "id", T: ddl.Int64{}, NotNull: true},
-				"s":   ddl.ColumnDef{Name: "s", T: ddl.String{Len: ddl.MaxLength{}}, IsArray: true},
-				"txt": ddl.ColumnDef{Name: "txt", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-				"b":   ddl.ColumnDef{Name: "b", T: ddl.Bool{}},
-				"bs":  ddl.ColumnDef{Name: "bs", T: ddl.Int64{}, NotNull: true},
-				"bl":  ddl.ColumnDef{Name: "bl", T: ddl.Bytes{Len: ddl.MaxLength{}}},
-				"c":   ddl.ColumnDef{Name: "c", T: ddl.String{Len: ddl.Int64Length{Value: 1}}},
-				"c8":  ddl.ColumnDef{Name: "c8", T: ddl.String{Len: ddl.Int64Length{Value: 8}}},
-				"d":   ddl.ColumnDef{Name: "d", T: ddl.Date{}},
-				"dec": ddl.ColumnDef{Name: "dec", T: ddl.Float64{}},
-				"f8":  ddl.ColumnDef{Name: "f8", T: ddl.Float64{}},
-				"f4":  ddl.ColumnDef{Name: "f4", T: ddl.Float64{}},
-				"i8":  ddl.ColumnDef{Name: "i8", T: ddl.Int64{}},
-				"i4":  ddl.ColumnDef{Name: "i4", T: ddl.Int64{}},
-				"i2":  ddl.ColumnDef{Name: "i2", T: ddl.Int64{}},
-				"si":  ddl.ColumnDef{Name: "si", T: ddl.Int64{}, NotNull: true},
-				"ts":  ddl.ColumnDef{Name: "ts", T: ddl.Timestamp{}},
-				"tz":  ddl.ColumnDef{Name: "tz", T: ddl.Timestamp{}},
-				"vc":  ddl.ColumnDef{Name: "vc", T: ddl.String{Len: ddl.MaxLength{}}},
-				"vc6": ddl.ColumnDef{Name: "vc6", T: ddl.String{Len: ddl.Int64Length{Value: 6}}},
+				"id":  ddl.ColumnDef{Name: "id", T: ddl.Type{Name: ddl.Int64}, NotNull: true},
+				"s":   ddl.ColumnDef{Name: "s", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}},
+				"txt": ddl.ColumnDef{Name: "txt", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+				"b":   ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Bool}},
+				"bs":  ddl.ColumnDef{Name: "bs", T: ddl.Type{Name: ddl.Int64}, NotNull: true},
+				"bl":  ddl.ColumnDef{Name: "bl", T: ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}},
+				"c":   ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: int64(1)}},
+				"c8":  ddl.ColumnDef{Name: "c8", T: ddl.Type{Name: ddl.String, Len: int64(8)}},
+				"d":   ddl.ColumnDef{Name: "d", T: ddl.Type{Name: ddl.Date}},
+				"dec": ddl.ColumnDef{Name: "dec", T: ddl.Type{Name: ddl.Float64}},
+				"f8":  ddl.ColumnDef{Name: "f8", T: ddl.Type{Name: ddl.Float64}},
+				"f4":  ddl.ColumnDef{Name: "f4", T: ddl.Type{Name: ddl.Float64}},
+				"i8":  ddl.ColumnDef{Name: "i8", T: ddl.Type{Name: ddl.Int64}},
+				"i4":  ddl.ColumnDef{Name: "i4", T: ddl.Type{Name: ddl.Int64}},
+				"i2":  ddl.ColumnDef{Name: "i2", T: ddl.Type{Name: ddl.Int64}},
+				"si":  ddl.ColumnDef{Name: "si", T: ddl.Type{Name: ddl.Int64}, NotNull: true},
+				"ts":  ddl.ColumnDef{Name: "ts", T: ddl.Type{Name: ddl.Timestamp}},
+				"tz":  ddl.ColumnDef{Name: "tz", T: ddl.Type{Name: ddl.Timestamp}},
+				"vc":  ddl.ColumnDef{Name: "vc", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+				"vc6": ddl.ColumnDef{Name: "vc6", T: ddl.Type{Name: ddl.String, Len: int64(6)}},
 			},
 			Pks: []ddl.IndexKey{ddl.IndexKey{Col: "id"}}},
 	}
@@ -169,9 +169,9 @@ func TestProcessSQLData(t *testing.T) {
 			Name:     "te_st",
 			ColNames: []string{"a a", " b", " c "},
 			ColDefs: map[string]ddl.ColumnDef{
-				"a_a": ddl.ColumnDef{Name: "a_a", T: ddl.Float64{}},
-				"Ab":  ddl.ColumnDef{Name: "Ab", T: ddl.Int64{}},
-				"Ac_": ddl.ColumnDef{Name: "Ac_", T: ddl.String{Len: ddl.MaxLength{}}},
+				"a_a": ddl.ColumnDef{Name: "a_a", T: ddl.Type{Name: ddl.Float64}},
+				"Ab":  ddl.ColumnDef{Name: "Ab", T: ddl.Type{Name: ddl.Int64}},
+				"Ac_": ddl.ColumnDef{Name: "Ac_", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 			}},
 		schema.Table{
 			Name:     "te st",
@@ -253,10 +253,10 @@ func TestProcessSQLData_MultiCol(t *testing.T) {
 			Name:     "test",
 			ColNames: []string{"a", "b", "c", "synth_id"},
 			ColDefs: map[string]ddl.ColumnDef{
-				"a":        ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-				"b":        ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-				"c":        ddl.ColumnDef{Name: "c", T: ddl.Int64{}},
-				"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Int64{}},
+				"a":        ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+				"b":        ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+				"c":        ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.Int64}},
+				"synth_id": ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
 			},
 			Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}}},
 	}
diff --git a/mysql/mysqldump_test.go b/mysql/mysqldump_test.go
index 32b2f02..a083d1e 100644
--- a/mysql/mysqldump_test.go
+++ b/mysql/mysqldump_test.go
@@ -32,32 +32,32 @@ func TestProcessMySQLDump_Scalar(t *testing.T) {
 	// First, test scalar types.
 	scalarTests := []struct {
 		ty       string
-		expected ddl.ScalarType
+		expected ddl.Type
 	}{
-		{"bigint", ddl.Int64{}},
-		{"bool", ddl.Bool{}},
-		{"boolean", ddl.Bool{}},
-		{"tinyint(1)", ddl.Bool{}},
-		{"tinyint(4)", ddl.Int64{}},
-		{"blob", ddl.Bytes{Len: ddl.MaxLength{}}},
-		{"char(42)", ddl.String{Len: ddl.Int64Length{Value: 42}}},
-		{"date", ddl.Date{}},
-		{"decimal(4,10)", ddl.Float64{}},
-		{"double(4,10)", ddl.Float64{}},
-		{"float(4,10)", ddl.Float64{}},
-		{"integer", ddl.Int64{}},
-		{"mediumint", ddl.Int64{}},
-		{"int", ddl.Int64{}},
-		{"bit", ddl.Bytes{Len: ddl.MaxLength{}}},
-		{"smallint", ddl.Int64{}},
-		{"text", ddl.String{Len: ddl.MaxLength{}}},
-		{"tinytext", ddl.String{Len: ddl.MaxLength{}}},
-		{"mediumtext", ddl.String{Len: ddl.MaxLength{}}},
-		{"longtext", ddl.String{Len: ddl.MaxLength{}}},
-		{"enum('a','b')", ddl.String{Len: ddl.MaxLength{}}},
-		{"timestamp", ddl.Timestamp{}},
-		{"datetime", ddl.Timestamp{}},
-		{"varchar(42)", ddl.String{Len: ddl.Int64Length{Value: 42}}},
+		{"bigint", ddl.Type{Name: ddl.Int64}},
+		{"bool", ddl.Type{Name: ddl.Bool}},
+		{"boolean", ddl.Type{Name: ddl.Bool}},
+		{"tinyint(1)", ddl.Type{Name: ddl.Bool}},
+		{"tinyint(4)", ddl.Type{Name: ddl.Int64}},
+		{"blob", ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}},
+		{"char(42)", ddl.Type{Name: ddl.String, Len: int64(42)}},
+		{"date", ddl.Type{Name: ddl.Date}},
+		{"decimal(4,10)", ddl.Type{Name: ddl.Float64}},
+		{"double(4,10)", ddl.Type{Name: ddl.Float64}},
+		{"float(4,10)", ddl.Type{Name: ddl.Float64}},
+		{"integer", ddl.Type{Name: ddl.Int64}},
+		{"mediumint", ddl.Type{Name: ddl.Int64}},
+		{"int", ddl.Type{Name: ddl.Int64}},
+		{"bit", ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}},
+		{"smallint", ddl.Type{Name: ddl.Int64}},
+		{"text", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+		{"tinytext", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+		{"mediumtext", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+		{"longtext", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+		{"enum('a','b')", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+		{"timestamp", ddl.Type{Name: ddl.Timestamp}},
+		{"datetime", ddl.Type{Name: ddl.Timestamp}},
+		{"varchar(42)", ddl.Type{Name: ddl.String, Len: int64(42)}},
 	}
 	for _, tc := range scalarTests {
 		t.Run(tc.ty, func(t *testing.T) {
@@ -74,8 +74,8 @@ func TestProcessMySQLDump_SingleCol(t *testing.T) {
 		ty       string
 		expected ddl.ColumnDef
 	}{
-		{"set('a','b','c')", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, IsArray: true}},
-		{"text NOT NULL", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true}},
+		{"set('a','b','c')", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}}},
+		{"text NOT NULL", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true}},
 	}
 	for _, tc := range singleColTests {
 		t.Run(tc.ty, func(t *testing.T) {
@@ -106,9 +106,9 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 					Name:     "cart",
 					ColNames: []string{"productid", "userid", "quantity"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
+						"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "productid"}, ddl.IndexKey{Col: "userid"}}}},
 		},
@@ -120,10 +120,10 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 					Name:     "cart",
 					ColNames: []string{"productid", "userid", "quantity", "synth_id"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}},
-						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
-						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Int64{}},
+						"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
+						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}}}},
 		},
@@ -135,8 +135,8 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 					Name:     "test",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}},
 		},
@@ -148,9 +148,9 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 					Name:     "test",
 					ColNames: []string{"a", "b", "n"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"n": ddl.ColumnDef{Name: "n", T: ddl.Int64{}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"n": ddl.ColumnDef{Name: "n", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}, ddl.IndexKey{Col: "b"}}}},
 		},
@@ -162,8 +162,8 @@ func TestProcessMySQLDump_MultiCol(t *testing.T) {
 					Name:     "myschema_test",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}},
 		},
@@ -196,8 +196,8 @@ CREATE TABLE test (a text PRIMARY KEY, b text);`,
 					Name:     "test",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}},
 			expectIssues: true, // conv.Stats.Reparsed != 0
@@ -211,8 +211,8 @@ CREATE TABLE test (a text PRIMARY KEY, b text);`,
 					Name:     "test",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}},
 		},
@@ -226,14 +226,14 @@ CREATE TABLE test (a text PRIMARY KEY, b text);`,
 					Name:     "t1",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}}},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}},
 				"t2": ddl.CreateTable{
 					Name:     "t2",
 					ColNames: []string{"c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"c": ddl.ColumnDef{Name: "c", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true}},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "c"}}}},
 		},
 		{
@@ -346,9 +346,9 @@ CREATE TABLE test (a text PRIMARY KEY, b text);`,
 					Name:     "te_s_t",
 					ColNames: []string{"a__", "b_b", "n_n"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a__": ddl.ColumnDef{Name: "a__", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.String{Len: ddl.MaxLength{}}},
-						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Int64{}},
+						"a__": ddl.ColumnDef{Name: "a__", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a__"}}}},
 		},
@@ -364,9 +364,9 @@ CREATE TABLE test (a text PRIMARY KEY, b text);`,
 					Name:     "te_s_t",
 					ColNames: []string{"a__", "b_b", "n_n"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a__": ddl.ColumnDef{Name: "a__", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.String{Len: ddl.MaxLength{}}},
-						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Int64{}},
+						"a__": ddl.ColumnDef{Name: "a__", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a__"}}}},
 		},
@@ -525,10 +525,10 @@ func TestProcessMySQLDump_AddPrimaryKeys(t *testing.T) {
 					Name:     "cart",
 					ColNames: []string{"productid", "userid", "quantity", "synth_id"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}},
-						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}},
-						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
-						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Int64{}},
+						"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
+						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}}}},
 		},
@@ -540,10 +540,10 @@ func TestProcessMySQLDump_AddPrimaryKeys(t *testing.T) {
 					Name:     "test",
 					ColNames: []string{"synth_id", "synth_id0", "synth_id1", "synth_id2"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.String{Len: ddl.MaxLength{}}},
-						"synth_id0": ddl.ColumnDef{Name: "synth_id0", T: ddl.String{Len: ddl.MaxLength{}}},
-						"synth_id1": ddl.ColumnDef{Name: "synth_id1", T: ddl.Int64{}},
-						"synth_id2": ddl.ColumnDef{Name: "synth_id2", T: ddl.Int64{}},
+						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"synth_id0": ddl.ColumnDef{Name: "synth_id0", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"synth_id1": ddl.ColumnDef{Name: "synth_id1", T: ddl.Type{Name: ddl.Int64}},
+						"synth_id2": ddl.ColumnDef{Name: "synth_id2", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id2"}}}},
 		},
diff --git a/mysql/toddl_test.go b/mysql/toddl_test.go
index 4ec7320..53f255d 100644
--- a/mysql/toddl_test.go
+++ b/mysql/toddl_test.go
@@ -48,12 +48,12 @@ func TestToSpannerType(t *testing.T) {
 		Name:     name,
 		ColNames: []string{"a", "b", "c", "d", "e", "f"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-			"c": ddl.ColumnDef{Name: "c", T: ddl.Bool{}},
-			"d": ddl.ColumnDef{Name: "d", T: ddl.String{Len: ddl.Int64Length{Value: 6}}},
-			"e": ddl.ColumnDef{Name: "e", T: ddl.Float64{}},
-			"f": ddl.ColumnDef{Name: "f", T: ddl.Timestamp{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+			"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.Bool}},
+			"d": ddl.ColumnDef{Name: "d", T: ddl.Type{Name: ddl.String, Len: int64(6)}},
+			"e": ddl.ColumnDef{Name: "e", T: ddl.Type{Name: ddl.Float64}},
+			"f": ddl.ColumnDef{Name: "f", T: ddl.Type{Name: ddl.Timestamp}},
 		},
 		Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}},
 	}
diff --git a/postgres/data_test.go b/postgres/data_test.go
index 3648508..aa05951 100644
--- a/postgres/data_test.go
+++ b/postgres/data_test.go
@@ -38,9 +38,9 @@ func TestProcessDataRow(t *testing.T) {
 			Name:     tableName,
 			ColNames: cols,
 			ColDefs: map[string]ddl.ColumnDef{
-				"a": ddl.ColumnDef{Name: "a", T: ddl.Float64{}},
-				"b": ddl.ColumnDef{Name: "b", T: ddl.Int64{}},
-				"c": ddl.ColumnDef{Name: "c", T: ddl.String{Len: ddl.MaxLength{}}},
+				"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Float64}},
+				"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Int64}},
+				"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 			}},
 		schema.Table{
 			Name:     tableName,
@@ -62,52 +62,51 @@ func TestProcessDataRow(t *testing.T) {
 
 func TestConvertData(t *testing.T) {
 	singleColTests := []struct {
-		name    string
-		ty      ddl.ScalarType
-		isArray bool
-		srcTy   string      // Source DB type (used by e.g. timestamp conversions).
-		in      string      // Input value for conversion.
-		e       interface{} // Expected result.
+		name  string
+		ty    ddl.Type
+		srcTy string      // Source DB type (used by e.g. timestamp conversions).
+		in    string      // Input value for conversion.
+		e     interface{} // Expected result.
 	}{
-		{"bool", ddl.Bool{}, false, "", "true", true},
-		{"bytes", ddl.Bytes{Len: ddl.MaxLength{}}, false, "", `\x0001beef`, []byte{0x0, 0x1, 0xbe, 0xef}},
-		{"date", ddl.Date{}, false, "", "2019-10-29", getDate("2019-10-29")},
-		{"float64", ddl.Float64{}, false, "", "42.6", float64(42.6)},
-		{"int64", ddl.Int64{}, false, "", "42", int64(42)},
-		{"string", ddl.String{Len: ddl.MaxLength{}}, false, "", "eh", "eh"},
-		{"timestamptz", ddl.Timestamp{}, false, "timestamptz", "2019-10-29 05:30:00+10", getTime(t, "2019-10-29T05:30:00+10:00")},
-		{"timestamp", ddl.Timestamp{}, false, "timestamp", "2019-10-29 05:30:00", getTime(t, "2019-10-29T05:30:00Z")},
+		{"bool", ddl.Type{Name: ddl.Bool}, "", "true", true},
+		{"bytes", ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, "", `\x0001beef`, []byte{0x0, 0x1, 0xbe, 0xef}},
+		{"date", ddl.Type{Name: ddl.Date}, "", "2019-10-29", getDate("2019-10-29")},
+		{"float64", ddl.Type{Name: ddl.Float64}, "", "42.6", float64(42.6)},
+		{"int64", ddl.Type{Name: ddl.Int64}, "", "42", int64(42)},
+		{"string", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, "", "eh", "eh"},
+		{"timestamptz", ddl.Type{Name: ddl.Timestamp}, "timestamptz", "2019-10-29 05:30:00+10", getTime(t, "2019-10-29T05:30:00+10:00")},
+		{"timestamp", ddl.Type{Name: ddl.Timestamp}, "timestamp", "2019-10-29 05:30:00", getTime(t, "2019-10-29T05:30:00Z")},
 
 		// Add cases for each array type, since each is a separate code path.
 		// Note: the PostgreSQL array output routine puts double quotes around
 		// elements if they have white space e.g. timestamps, bytea arrays.
-		{"bool array", ddl.Bool{}, true, "", "{true,false,NULL}", []spanner.NullBool{
+		{"bool array", ddl.Type{Name: ddl.Bool, IsArray: true}, "", "{true,false,NULL}", []spanner.NullBool{
 			spanner.NullBool{Bool: true, Valid: true}, spanner.NullBool{Bool: false, Valid: true},
 			spanner.NullBool{Valid: false}}},
-		{"bytes array", ddl.Bytes{Len: ddl.MaxLength{}}, true, "", `{"\\x0001beef",NULL}`, [][]byte{{0x0, 0x1, 0xbe, 0xef}, nil}},
-		{"date array", ddl.Date{}, true, "", "{2019-10-29,NULL,2019-10-28}", []spanner.NullDate{
+		{"bytes array", ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength, IsArray: true}, "", `{"\\x0001beef",NULL}`, [][]byte{{0x0, 0x1, 0xbe, 0xef}, nil}},
+		{"date array", ddl.Type{Name: ddl.Date, IsArray: true}, "", "{2019-10-29,NULL,2019-10-28}", []spanner.NullDate{
 			spanner.NullDate{Date: getDate("2019-10-29"), Valid: true},
 			spanner.NullDate{Valid: false},
 			spanner.NullDate{Date: getDate("2019-10-28"), Valid: true}}},
-		{"float64 array", ddl.Float64{}, true, "", "{1.1,NULL,2.2,3.3}", []spanner.NullFloat64{
+		{"float64 array", ddl.Type{Name: ddl.Float64, IsArray: true}, "", "{1.1,NULL,2.2,3.3}", []spanner.NullFloat64{
 			spanner.NullFloat64{Float64: 1.1, Valid: true},
 			spanner.NullFloat64{Valid: false},
 			spanner.NullFloat64{Float64: 2.2, Valid: true},
 			spanner.NullFloat64{Float64: 3.3, Valid: true}}},
-		{"int64 array", ddl.Int64{}, true, "", "{NULL,1,2,3}", []spanner.NullInt64{
+		{"int64 array", ddl.Type{Name: ddl.Int64, IsArray: true}, "", "{NULL,1,2,3}", []spanner.NullInt64{
 			spanner.NullInt64{Valid: false},
 			spanner.NullInt64{Int64: 1, Valid: true},
 			spanner.NullInt64{Int64: 2, Valid: true},
 			spanner.NullInt64{Int64: 3, Valid: true}}},
-		{"string array", ddl.String{Len: ddl.MaxLength{}}, true, "", `{1,NULL,3,"NULL"}`, []spanner.NullString{
+		{"string array", ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}, "", `{1,NULL,3,"NULL"}`, []spanner.NullString{
 			spanner.NullString{StringVal: "1", Valid: true},
 			spanner.NullString{Valid: false},
 			spanner.NullString{StringVal: "3", Valid: true},
 			spanner.NullString{StringVal: "NULL", Valid: true}}},
-		{"timestamp array", ddl.Timestamp{}, true, "timestamptz", `{"2019-10-29 05:30:00+10",NULL}`, []spanner.NullTime{
+		{"timestamp array", ddl.Type{Name: ddl.Timestamp, IsArray: true}, "timestamptz", `{"2019-10-29 05:30:00+10",NULL}`, []spanner.NullTime{
 			spanner.NullTime{Time: getTime(t, "2019-10-29T05:30:00+10:00"), Valid: true},
 			spanner.NullTime{Valid: false}}},
-		{"empty array", ddl.String{Len: ddl.MaxLength{}}, true, "", "{}", []spanner.NullString{}},
+		{"empty array", ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}, "", "{}", []spanner.NullString{}},
 	}
 	tableName := "testtable"
 	for _, tc := range singleColTests {
@@ -116,7 +115,7 @@ func TestConvertData(t *testing.T) {
 			ddl.CreateTable{
 				Name:     tableName,
 				ColNames: []string{col},
-				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: tc.ty, IsArray: tc.isArray, NotNull: false}},
+				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: tc.ty, NotNull: false}},
 				Pks:      []ddl.IndexKey{}},
 			schema.Table{Name: tableName, ColNames: []string{col}, ColDefs: map[string]schema.Column{col: schema.Column{Type: schema.Type{Name: tc.srcTy}}}})
 		conv.SetLocation(time.UTC)
@@ -141,7 +140,7 @@ func TestConvertData(t *testing.T) {
 			ddl.CreateTable{
 				Name:     tableName,
 				ColNames: []string{col},
-				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: ddl.Timestamp{}}}},
+				ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: ddl.Type{Name: ddl.Timestamp}}}},
 			schema.Table{
 				Name:     tableName,
 				ColNames: []string{col},
@@ -202,9 +201,9 @@ func TestConvertData(t *testing.T) {
 		Name:     tableName,
 		ColNames: []string{"a", "b", "c"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-			"c": ddl.ColumnDef{Name: "c", T: ddl.Bool{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+			"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.Bool}},
 		}}
 	srcTable := schema.Table{
 		Name:     tableName,
diff --git a/postgres/infoschema_test.go b/postgres/infoschema_test.go
index a4468e2..cab9a85 100644
--- a/postgres/infoschema_test.go
+++ b/postgres/infoschema_test.go
@@ -101,36 +101,36 @@ func TestProcessInfoSchema(t *testing.T) {
 			Name:     "cart",
 			ColNames: []string{"productid", "userid", "quantity"},
 			ColDefs: map[string]ddl.ColumnDef{
-				"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-				"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-				"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
+				"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+				"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+				"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
 			},
 			Pks: []ddl.IndexKey{ddl.IndexKey{Col: "productid"}, ddl.IndexKey{Col: "userid"}}},
 		"test": ddl.CreateTable{
 			Name:     "test",
 			ColNames: []string{"id", "aint", "atext", "b", "bs", "by", "c", "c8", "d", "f8", "f4", "i8", "i4", "i2", "num", "s", "ts", "tz", "txt", "vc", "vc6"},
 			ColDefs: map[string]ddl.ColumnDef{
-				"id":    ddl.ColumnDef{Name: "id", T: ddl.Int64{}, NotNull: true},
-				"aint":  ddl.ColumnDef{Name: "aint", T: ddl.Int64{}, IsArray: true},
-				"atext": ddl.ColumnDef{Name: "atext", T: ddl.String{Len: ddl.MaxLength{}}, IsArray: true},
-				"b":     ddl.ColumnDef{Name: "b", T: ddl.Bool{}},
-				"bs":    ddl.ColumnDef{Name: "bs", T: ddl.Int64{}, NotNull: true},
-				"by":    ddl.ColumnDef{Name: "by", T: ddl.Bytes{Len: ddl.MaxLength{}}},
-				"c":     ddl.ColumnDef{Name: "c", T: ddl.String{Len: ddl.Int64Length{Value: 1}}},
-				"c8":    ddl.ColumnDef{Name: "c8", T: ddl.String{Len: ddl.Int64Length{Value: 8}}},
-				"d":     ddl.ColumnDef{Name: "d", T: ddl.Date{}},
-				"f8":    ddl.ColumnDef{Name: "f8", T: ddl.Float64{}},
-				"f4":    ddl.ColumnDef{Name: "f4", T: ddl.Float64{}},
-				"i8":    ddl.ColumnDef{Name: "i8", T: ddl.Int64{}},
-				"i4":    ddl.ColumnDef{Name: "i4", T: ddl.Int64{}},
-				"i2":    ddl.ColumnDef{Name: "i2", T: ddl.Int64{}},
-				"num":   ddl.ColumnDef{Name: "num", T: ddl.Float64{}},
-				"s":     ddl.ColumnDef{Name: "s", T: ddl.Int64{}, NotNull: true},
-				"ts":    ddl.ColumnDef{Name: "ts", T: ddl.Timestamp{}},
-				"tz":    ddl.ColumnDef{Name: "tz", T: ddl.Timestamp{}},
-				"txt":   ddl.ColumnDef{Name: "txt", T: ddl.String{Len: ddl.MaxLength{}}},
-				"vc":    ddl.ColumnDef{Name: "vc", T: ddl.String{Len: ddl.MaxLength{}}},
-				"vc6":   ddl.ColumnDef{Name: "vc6", T: ddl.String{Len: ddl.Int64Length{Value: 6}}},
+				"id":    ddl.ColumnDef{Name: "id", T: ddl.Type{Name: ddl.Int64}, NotNull: true},
+				"aint":  ddl.ColumnDef{Name: "aint", T: ddl.Type{Name: ddl.Int64, IsArray: true}},
+				"atext": ddl.ColumnDef{Name: "atext", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}},
+				"b":     ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Bool}},
+				"bs":    ddl.ColumnDef{Name: "bs", T: ddl.Type{Name: ddl.Int64}, NotNull: true},
+				"by":    ddl.ColumnDef{Name: "by", T: ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}},
+				"c":     ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: int64(1)}},
+				"c8":    ddl.ColumnDef{Name: "c8", T: ddl.Type{Name: ddl.String, Len: int64(8)}},
+				"d":     ddl.ColumnDef{Name: "d", T: ddl.Type{Name: ddl.Date}},
+				"f8":    ddl.ColumnDef{Name: "f8", T: ddl.Type{Name: ddl.Float64}},
+				"f4":    ddl.ColumnDef{Name: "f4", T: ddl.Type{Name: ddl.Float64}},
+				"i8":    ddl.ColumnDef{Name: "i8", T: ddl.Type{Name: ddl.Int64}},
+				"i4":    ddl.ColumnDef{Name: "i4", T: ddl.Type{Name: ddl.Int64}},
+				"i2":    ddl.ColumnDef{Name: "i2", T: ddl.Type{Name: ddl.Int64}},
+				"num":   ddl.ColumnDef{Name: "num", T: ddl.Type{Name: ddl.Float64}},
+				"s":     ddl.ColumnDef{Name: "s", T: ddl.Type{Name: ddl.Int64}, NotNull: true},
+				"ts":    ddl.ColumnDef{Name: "ts", T: ddl.Type{Name: ddl.Timestamp}},
+				"tz":    ddl.ColumnDef{Name: "tz", T: ddl.Type{Name: ddl.Timestamp}},
+				"txt":   ddl.ColumnDef{Name: "txt", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+				"vc":    ddl.ColumnDef{Name: "vc", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+				"vc6":   ddl.ColumnDef{Name: "vc6", T: ddl.Type{Name: ddl.String, Len: int64(6)}},
 			},
 			Pks: []ddl.IndexKey{ddl.IndexKey{Col: "id"}}},
 	}
@@ -175,9 +175,9 @@ func TestProcessSqlData(t *testing.T) {
 			Name:     "te_st",
 			ColNames: []string{"a a", " b", " c "},
 			ColDefs: map[string]ddl.ColumnDef{
-				"a_a": ddl.ColumnDef{Name: "a_a", T: ddl.Float64{}},
-				"Ab":  ddl.ColumnDef{Name: "Ab", T: ddl.Int64{}},
-				"Ac_": ddl.ColumnDef{Name: "Ac_", T: ddl.String{Len: ddl.MaxLength{}}},
+				"a_a": ddl.ColumnDef{Name: "a_a", T: ddl.Type{Name: ddl.Float64}},
+				"Ab":  ddl.ColumnDef{Name: "Ab", T: ddl.Type{Name: ddl.Int64}},
+				"Ac_": ddl.ColumnDef{Name: "Ac_", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 			}},
 		schema.Table{
 			Name:     "te st",
@@ -211,49 +211,48 @@ func TestConvertSqlRow_SingleCol(t *testing.T) {
 	tc := []struct {
 		name    string
 		srcType schema.Type
-		spType  ddl.ScalarType
-		isArray bool
+		spType  ddl.Type
 		in      interface{} // Input value for conversion.
 		e       interface{} // Expected result.
 	}{
-		{name: "bool", srcType: schema.Type{Name: "bool"}, spType: ddl.Bool{}, in: true, e: true},
-		{name: "bool string", srcType: schema.Type{Name: "bool"}, spType: ddl.Bool{}, in: "true", e: true},
-		{name: "bytes", srcType: schema.Type{Name: "bytea"}, spType: ddl.Bytes{Len: ddl.MaxLength{}}, in: []byte{0x0, 0x1, 0xbe, 0xef}, e: []byte{0x0, 0x1, 0xbe, 0xef}},
-		{name: "date", srcType: schema.Type{Name: "date"}, spType: ddl.Date{}, in: tDate, e: getDate("2019-10-29")},
-		{name: "date string", srcType: schema.Type{Name: "date"}, spType: ddl.Date{}, in: "2019-10-29", e: getDate("2019-10-29")},
-		{name: "int64", srcType: schema.Type{Name: "bigint"}, spType: ddl.Int64{}, in: int64(42), e: int64(42)},
-		{name: "int64 string", srcType: schema.Type{Name: "text"}, spType: ddl.Int64{}, in: "42", e: int64(42)},
-		{name: "int64 float64", srcType: schema.Type{Name: "float8"}, spType: ddl.Int64{}, in: float64(42), e: int64(42)},
-		{name: "int64 byte", srcType: schema.Type{Name: "bytea"}, spType: ddl.Int64{}, in: []byte("42"), e: int64(42)},
-		{name: "float64", srcType: schema.Type{Name: "float8"}, spType: ddl.Float64{}, in: float64(42.6), e: float64(42.6)},
-		{name: "float64 string", srcType: schema.Type{Name: "text"}, spType: ddl.Float64{}, in: "42.6", e: float64(42.6)},
-		{name: "float64 int", srcType: schema.Type{Name: "bigint"}, spType: ddl.Float64{}, in: int64(42), e: float64(42)},
-		{name: "float64 byte", srcType: schema.Type{Name: "numeric"}, spType: ddl.Float64{}, in: []byte("42.6"), e: float64(42.6)},
-		{name: "string", srcType: schema.Type{Name: "text"}, spType: ddl.String{Len: ddl.MaxLength{}}, in: "eh", e: "eh"},
-		{name: "string bool", srcType: schema.Type{Name: "bool"}, spType: ddl.String{Len: ddl.MaxLength{}}, in: true, e: "true"},
-		{name: "string byte", srcType: schema.Type{Name: "bytea"}, spType: ddl.String{Len: ddl.MaxLength{}}, in: []byte("abc"), e: "abc"},
-		{name: "string int64", srcType: schema.Type{Name: "bigint"}, spType: ddl.String{Len: ddl.MaxLength{}}, in: int64(42), e: "42"},
-		{name: "string float64", srcType: schema.Type{Name: "float8"}, spType: ddl.String{Len: ddl.MaxLength{}}, in: float64(42.3), e: "42.3"},
-		{name: "string time", srcType: schema.Type{Name: "timestamp"}, spType: ddl.String{Len: ddl.MaxLength{}},
+		{name: "bool", srcType: schema.Type{Name: "bool"}, spType: ddl.Type{Name: ddl.Bool}, in: true, e: true},
+		{name: "bool string", srcType: schema.Type{Name: "bool"}, spType: ddl.Type{Name: ddl.Bool}, in: "true", e: true},
+		{name: "bytes", srcType: schema.Type{Name: "bytea"}, spType: ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, in: []byte{0x0, 0x1, 0xbe, 0xef}, e: []byte{0x0, 0x1, 0xbe, 0xef}},
+		{name: "date", srcType: schema.Type{Name: "date"}, spType: ddl.Type{Name: ddl.Date}, in: tDate, e: getDate("2019-10-29")},
+		{name: "date string", srcType: schema.Type{Name: "date"}, spType: ddl.Type{Name: ddl.Date}, in: "2019-10-29", e: getDate("2019-10-29")},
+		{name: "int64", srcType: schema.Type{Name: "bigint"}, spType: ddl.Type{Name: ddl.Int64}, in: int64(42), e: int64(42)},
+		{name: "int64 string", srcType: schema.Type{Name: "text"}, spType: ddl.Type{Name: ddl.Int64}, in: "42", e: int64(42)},
+		{name: "int64 float64", srcType: schema.Type{Name: "float8"}, spType: ddl.Type{Name: ddl.Int64}, in: float64(42), e: int64(42)},
+		{name: "int64 byte", srcType: schema.Type{Name: "bytea"}, spType: ddl.Type{Name: ddl.Int64}, in: []byte("42"), e: int64(42)},
+		{name: "float64", srcType: schema.Type{Name: "float8"}, spType: ddl.Type{Name: ddl.Float64}, in: float64(42.6), e: float64(42.6)},
+		{name: "float64 string", srcType: schema.Type{Name: "text"}, spType: ddl.Type{Name: ddl.Float64}, in: "42.6", e: float64(42.6)},
+		{name: "float64 int", srcType: schema.Type{Name: "bigint"}, spType: ddl.Type{Name: ddl.Float64}, in: int64(42), e: float64(42)},
+		{name: "float64 byte", srcType: schema.Type{Name: "numeric"}, spType: ddl.Type{Name: ddl.Float64}, in: []byte("42.6"), e: float64(42.6)},
+		{name: "string", srcType: schema.Type{Name: "text"}, spType: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, in: "eh", e: "eh"},
+		{name: "string bool", srcType: schema.Type{Name: "bool"}, spType: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, in: true, e: "true"},
+		{name: "string byte", srcType: schema.Type{Name: "bytea"}, spType: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, in: []byte("abc"), e: "abc"},
+		{name: "string int64", srcType: schema.Type{Name: "bigint"}, spType: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, in: int64(42), e: "42"},
+		{name: "string float64", srcType: schema.Type{Name: "float8"}, spType: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, in: float64(42.3), e: "42.3"},
+		{name: "string time", srcType: schema.Type{Name: "timestamp"}, spType: ddl.Type{Name: ddl.String, Len: ddl.MaxLength},
 			in: getTime(t, "2019-10-29T05:30:00+10:00"), e: "2019-10-29 05:30:00 +1000 +1000"},
-		{name: "timestamptz", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Timestamp{},
+		{name: "timestamptz", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Type{Name: ddl.Timestamp},
 			in: getTime(t, "2019-10-29T05:30:00+10:00"), e: getTime(t, "2019-10-29T05:30:00+10:00")},
-		{name: "timestamptz string", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Timestamp{},
+		{name: "timestamptz string", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Type{Name: ddl.Timestamp},
 			in: "2019-10-29 05:30:00+10:00", e: getTime(t, "2019-10-29T05:30:00+10:00")},
-		{name: "timestamp", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Timestamp{},
+		{name: "timestamp", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Type{Name: ddl.Timestamp},
 			in: getTime(t, "2019-10-29T05:30:00Z"), e: getTime(t, "2019-10-29T05:30:00Z")},
-		{name: "timestamp string", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Timestamp{},
+		{name: "timestamp string", srcType: schema.Type{Name: "timestamptz"}, spType: ddl.Type{Name: ddl.Timestamp},
 			in: "2019-10-29 05:30:00", e: getTime(t, "2019-10-29T05:30:00Z")},
 
 		// ConvertSqlRow uses convArray for conversion of array types.
 		// Since convArray is extensively tested in data_test.go, we
 		// only test a few cases here.
-		{name: "array bool", srcType: schema.Type{Name: "bool", ArrayBounds: []int64{-1}}, spType: ddl.Bool{}, isArray: true,
+		{name: "array bool", srcType: schema.Type{Name: "bool", ArrayBounds: []int64{-1}}, spType: ddl.Type{Name: ddl.Bool, IsArray: true},
 			in: []byte("{true,false,NULL}"), e: []spanner.NullBool{
 				spanner.NullBool{Bool: true, Valid: true},
 				spanner.NullBool{Bool: false, Valid: true},
 				spanner.NullBool{Valid: false}}},
-		{name: "timestamp array", srcType: schema.Type{Name: "timestamptz", ArrayBounds: []int64{-1}}, spType: ddl.Timestamp{}, isArray: true,
+		{name: "timestamp array", srcType: schema.Type{Name: "timestamptz", ArrayBounds: []int64{-1}}, spType: ddl.Type{Name: ddl.Timestamp, IsArray: true},
 			in: []byte(`{"2019-10-29 05:30:00+10",NULL}`),
 			e: []spanner.NullTime{
 				spanner.NullTime{Time: getTime(t, "2019-10-29T05:30:00+10:00"), Valid: true},
@@ -269,7 +268,7 @@ func TestConvertSqlRow_SingleCol(t *testing.T) {
 		spSchema := ddl.CreateTable{
 			Name:     tableName,
 			ColNames: []string{col},
-			ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: tc.spType, IsArray: tc.isArray}}}
+			ColDefs:  map[string]ddl.ColumnDef{col: ddl.ColumnDef{Name: col, T: tc.spType}}}
 		ac, av, err := ConvertSQLRow(conv, tableName, cols, srcSchema, tableName, cols, spSchema, []interface{}{tc.in})
 		assert.Equal(t, cols, ac)
 		assert.Equal(t, []interface{}{tc.e}, av)
diff --git a/postgres/pgdump_test.go b/postgres/pgdump_test.go
index fb3b500..aa03f80 100644
--- a/postgres/pgdump_test.go
+++ b/postgres/pgdump_test.go
@@ -40,33 +40,33 @@ func TestProcessPgDump(t *testing.T) {
 	// First, test scalar types.
 	scalarTests := []struct {
 		ty       string
-		expected ddl.ScalarType
+		expected ddl.Type
 	}{
-		{"bigint", ddl.Int64{}},
-		{"bool", ddl.Bool{}},
-		{"boolean", ddl.Bool{}},
-		{"bytea", ddl.Bytes{Len: ddl.MaxLength{}}},
-		{"char(42)", ddl.String{Len: ddl.Int64Length{Value: 42}}},
-		{"date", ddl.Date{}},
-		{"decimal", ddl.Float64{}}, // pg parser maps this to numeric.
-		{"double precision", ddl.Float64{}},
-		{"float8", ddl.Float64{}},
-		{"float4", ddl.Float64{}},
-		{"integer", ddl.Int64{}},
-		{"numeric", ddl.Float64{}},
-		{"numeric(4)", ddl.Float64{}},
-		{"numeric(6, 4)", ddl.Float64{}},
-		{"real", ddl.Float64{}},
-		{"smallint", ddl.Int64{}},
-		{"text", ddl.String{Len: ddl.MaxLength{}}},
-		{"timestamp", ddl.Timestamp{}},
-		{"timestamp without time zone", ddl.Timestamp{}},
-		{"timestamp(5)", ddl.Timestamp{}},
-		{"timestamptz", ddl.Timestamp{}},
-		{"timestamp with time zone", ddl.Timestamp{}},
-		{"timestamptz(5)", ddl.Timestamp{}},
-		{"varchar", ddl.String{Len: ddl.MaxLength{}}},
-		{"varchar(42)", ddl.String{Len: ddl.Int64Length{Value: 42}}},
+		{"bigint", ddl.Type{Name: ddl.Int64}},
+		{"bool", ddl.Type{Name: ddl.Bool}},
+		{"boolean", ddl.Type{Name: ddl.Bool}},
+		{"bytea", ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}},
+		{"char(42)", ddl.Type{Name: ddl.String, Len: int64(42)}},
+		{"date", ddl.Type{Name: ddl.Date}},
+		{"decimal", ddl.Type{Name: ddl.Float64}}, // pg parser maps this to numeric.
+		{"double precision", ddl.Type{Name: ddl.Float64}},
+		{"float8", ddl.Type{Name: ddl.Float64}},
+		{"float4", ddl.Type{Name: ddl.Float64}},
+		{"integer", ddl.Type{Name: ddl.Int64}},
+		{"numeric", ddl.Type{Name: ddl.Float64}},
+		{"numeric(4)", ddl.Type{Name: ddl.Float64}},
+		{"numeric(6, 4)", ddl.Type{Name: ddl.Float64}},
+		{"real", ddl.Type{Name: ddl.Float64}},
+		{"smallint", ddl.Type{Name: ddl.Int64}},
+		{"text", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+		{"timestamp", ddl.Type{Name: ddl.Timestamp}},
+		{"timestamp without time zone", ddl.Type{Name: ddl.Timestamp}},
+		{"timestamp(5)", ddl.Type{Name: ddl.Timestamp}},
+		{"timestamptz", ddl.Type{Name: ddl.Timestamp}},
+		{"timestamp with time zone", ddl.Type{Name: ddl.Timestamp}},
+		{"timestamptz(5)", ddl.Type{Name: ddl.Timestamp}},
+		{"varchar", ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+		{"varchar(42)", ddl.Type{Name: ddl.String, Len: int64(42)}},
 	}
 	for _, tc := range scalarTests {
 		conv, _ := runProcessPgDump(fmt.Sprintf("CREATE TABLE t (a %s);", tc.ty))
@@ -78,12 +78,12 @@ func TestProcessPgDump(t *testing.T) {
 		ty       string
 		expected ddl.ColumnDef
 	}{
-		{"text", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}}},
-		{"text NOT NULL", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true}},
-		{"text array[4]", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, IsArray: true}},
-		{"text[4]", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, IsArray: true}},
-		{"text[]", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, IsArray: true}},
-		{"text[][]", ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}}}, // Unrecognized array type mapped to string.
+		{"text", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}}},
+		{"text NOT NULL", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true}},
+		{"text array[4]", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}}},
+		{"text[4]", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}}},
+		{"text[]", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength, IsArray: true}}},
+		{"text[][]", ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}}}, // Unrecognized array type mapped to string.
 	}
 	for _, tc := range singleColTests {
 		conv, _ := runProcessPgDump(fmt.Sprintf("CREATE TABLE t (a %s);", tc.ty))
@@ -109,9 +109,9 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "cart",
 					ColNames: []string{"productid", "userid", "quantity"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
+						"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "productid"}, ddl.IndexKey{Col: "userid"}}}},
 		},
@@ -123,10 +123,10 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "cart",
 					ColNames: []string{"productid", "userid", "quantity", "synth_id"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}},
-						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
-						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Int64{}},
+						"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
+						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}}}},
 		},
@@ -138,8 +138,8 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "test",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}},
 		},
@@ -151,9 +151,9 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "test",
 					ColNames: []string{"a", "b", "n"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"n": ddl.ColumnDef{Name: "n", T: ddl.Int64{}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"n": ddl.ColumnDef{Name: "n", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}, ddl.IndexKey{Col: "b"}}}},
 		},
@@ -165,8 +165,8 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "myschema_test",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}},
 		},
@@ -179,8 +179,8 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "test",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}}},
 		},
@@ -194,14 +194,14 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "t1",
 					ColNames: []string{"a", "b"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a": ddl.ColumnDef{Name: "a", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b": ddl.ColumnDef{Name: "b", T: ddl.String{Len: ddl.MaxLength{}}}},
+						"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}}},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}}},
 				"t2": ddl.CreateTable{
 					Name:     "t2",
 					ColNames: []string{"c"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"c": ddl.ColumnDef{Name: "c", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true}},
+						"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true}},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "c"}}}},
 		},
 		{
@@ -325,9 +325,9 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "te_s_t",
 					ColNames: []string{"a__", "b_b", "n_n"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a__": ddl.ColumnDef{Name: "a__", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.String{Len: ddl.MaxLength{}}},
-						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Int64{}},
+						"a__": ddl.ColumnDef{Name: "a__", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a__"}}}},
 		},
@@ -343,9 +343,9 @@ func TestProcessPgDump(t *testing.T) {
 					Name:     "te_s_t",
 					ColNames: []string{"a__", "b_b", "n_n"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"a__": ddl.ColumnDef{Name: "a__", T: ddl.String{Len: ddl.MaxLength{}}, NotNull: true},
-						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.String{Len: ddl.MaxLength{}}},
-						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Int64{}},
+						"a__": ddl.ColumnDef{Name: "a__", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, NotNull: true},
+						"b_b": ddl.ColumnDef{Name: "b_b", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"n_n": ddl.ColumnDef{Name: "n_n", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a__"}}}},
 		},
@@ -514,10 +514,10 @@ func TestProcessPgDump_AddPrimaryKeys(t *testing.T) {
 					Name:     "cart",
 					ColNames: []string{"productid", "userid", "quantity", "synth_id"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"productid": ddl.ColumnDef{Name: "productid", T: ddl.String{Len: ddl.MaxLength{}}},
-						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.String{Len: ddl.MaxLength{}}},
-						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Int64{}},
-						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Int64{}},
+						"productid": ddl.ColumnDef{Name: "productid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"userid":    ddl.ColumnDef{Name: "userid", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"quantity":  ddl.ColumnDef{Name: "quantity", T: ddl.Type{Name: ddl.Int64}},
+						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id"}}}},
 		},
@@ -529,10 +529,10 @@ func TestProcessPgDump_AddPrimaryKeys(t *testing.T) {
 					Name:     "test",
 					ColNames: []string{"synth_id", "synth_id0", "synth_id1", "synth_id2"},
 					ColDefs: map[string]ddl.ColumnDef{
-						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.String{Len: ddl.MaxLength{}}},
-						"synth_id0": ddl.ColumnDef{Name: "synth_id0", T: ddl.String{Len: ddl.MaxLength{}}},
-						"synth_id1": ddl.ColumnDef{Name: "synth_id1", T: ddl.Int64{}},
-						"synth_id2": ddl.ColumnDef{Name: "synth_id2", T: ddl.Int64{}},
+						"synth_id":  ddl.ColumnDef{Name: "synth_id", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"synth_id0": ddl.ColumnDef{Name: "synth_id0", T: ddl.Type{Name: ddl.String, Len: ddl.MaxLength}},
+						"synth_id1": ddl.ColumnDef{Name: "synth_id1", T: ddl.Type{Name: ddl.Int64}},
+						"synth_id2": ddl.ColumnDef{Name: "synth_id2", T: ddl.Type{Name: ddl.Int64}},
 					},
 					Pks: []ddl.IndexKey{ddl.IndexKey{Col: "synth_id2"}}}},
 		},
diff --git a/postgres/toddl_test.go b/postgres/toddl_test.go
index c05513e..5e06fee 100644
--- a/postgres/toddl_test.go
+++ b/postgres/toddl_test.go
@@ -50,12 +50,12 @@ func TestToSpannerType(t *testing.T) {
 		Name:     name,
 		ColNames: []string{"a", "b", "c", "d", "e", "f"},
 		ColDefs: map[string]ddl.ColumnDef{
-			"a": ddl.ColumnDef{Name: "a", T: ddl.Int64{}},
-			"b": ddl.ColumnDef{Name: "b", T: ddl.Float64{}},
-			"c": ddl.ColumnDef{Name: "c", T: ddl.Bool{}},
-			"d": ddl.ColumnDef{Name: "d", T: ddl.String{Len: ddl.Int64Length{Value: 6}}},
-			"e": ddl.ColumnDef{Name: "e", T: ddl.Float64{}},
-			"f": ddl.ColumnDef{Name: "f", T: ddl.Timestamp{}},
+			"a": ddl.ColumnDef{Name: "a", T: ddl.Type{Name: ddl.Int64}},
+			"b": ddl.ColumnDef{Name: "b", T: ddl.Type{Name: ddl.Float64}},
+			"c": ddl.ColumnDef{Name: "c", T: ddl.Type{Name: ddl.Bool}},
+			"d": ddl.ColumnDef{Name: "d", T: ddl.Type{Name: ddl.String, Len: int64(6)}},
+			"e": ddl.ColumnDef{Name: "e", T: ddl.Type{Name: ddl.Float64}},
+			"f": ddl.ColumnDef{Name: "f", T: ddl.Type{Name: ddl.Timestamp}},
 		},
 		Pks: []ddl.IndexKey{ddl.IndexKey{Col: "a"}},
 	}
diff --git a/spanner/ddl/ast_test.go b/spanner/ddl/ast_test.go
index 44b3db8..01805e1 100644
--- a/spanner/ddl/ast_test.go
+++ b/spanner/ddl/ast_test.go
@@ -23,21 +23,21 @@ import (
 
 func TestPrintScalarType(t *testing.T) {
 	tests := []struct {
-		in       ScalarType
+		in       Type
 		expected string
 	}{
-		{Bool{}, "BOOL"},
-		{Int64{}, "INT64"},
-		{Float64{}, "FLOAT64"},
-		{String{MaxLength{}}, "STRING(MAX)"},
-		{String{Int64Length{42}}, "STRING(42)"},
-		{Bytes{MaxLength{}}, "BYTES(MAX)"},
-		{Bytes{Int64Length{42}}, "BYTES(42)"},
-		{Date{}, "DATE"},
-		{Timestamp{}, "TIMESTAMP"},
+		{Type{Name: Bool}, "BOOL"},
+		{Type{Name: Int64}, "INT64"},
+		{Type{Name: Float64}, "FLOAT64"},
+		{Type{Name: String, Len: MaxLength}, "STRING(MAX)"},
+		{Type{Name: String, Len: int64(42)}, "STRING(42)"},
+		{Type{Name: Bytes, Len: MaxLength}, "BYTES(MAX)"},
+		{Type{Name: Bytes, Len: int64(42)}, "BYTES(42)"},
+		{Type{Name: Date}, "DATE"},
+		{Type{Name: Timestamp}, "TIMESTAMP"},
 	}
 	for _, tc := range tests {
-		assert.Equal(t, normalizeSpace(tc.expected), normalizeSpace(tc.in.PrintScalarType()))
+		assert.Equal(t, normalizeSpace(tc.expected), normalizeSpace(tc.in.PrintColumnDefType()))
 	}
 }
 
@@ -47,11 +47,11 @@ func TestPrintColumnDef(t *testing.T) {
 		protectIds bool
 		expected   string
 	}{
-		{in: ColumnDef{Name: "col1", T: Int64{}}, expected: "col1 INT64"},
-		{in: ColumnDef{Name: "col1", T: Int64{}, IsArray: true}, expected: "col1 ARRAY<INT64>"},
-		{in: ColumnDef{Name: "col1", T: Int64{}, NotNull: true}, expected: "col1 INT64 NOT NULL"},
-		{in: ColumnDef{Name: "col1", T: Int64{}, IsArray: true, NotNull: true}, expected: "col1 ARRAY<INT64> NOT NULL"},
-		{in: ColumnDef{Name: "col1", T: Int64{}}, protectIds: true, expected: "`col1` INT64"},
+		{in: ColumnDef{Name: "col1", T: Type{Name: Int64}}, expected: "col1 INT64"},
+		{in: ColumnDef{Name: "col1", T: Type{Name: Int64, IsArray: true}}, expected: "col1 ARRAY<INT64>"},
+		{in: ColumnDef{Name: "col1", T: Type{Name: Int64}, NotNull: true}, expected: "col1 INT64 NOT NULL"},
+		{in: ColumnDef{Name: "col1", T: Type{Name: Int64, IsArray: true}, NotNull: true}, expected: "col1 ARRAY<INT64> NOT NULL"},
+		{in: ColumnDef{Name: "col1", T: Type{Name: Int64}}, protectIds: true, expected: "`col1` INT64"},
 	}
 	for _, tc := range tests {
 		s, _ := tc.in.PrintColumnDef(Config{ProtectIds: tc.protectIds})
@@ -76,9 +76,9 @@ func TestPrintIndexKey(t *testing.T) {
 
 func TestPrintCreateTable(t *testing.T) {
 	cds := make(map[string]ColumnDef)
-	cds["col1"] = ColumnDef{Name: "col1", T: Int64{}, NotNull: true}
-	cds["col2"] = ColumnDef{Name: "col2", T: String{MaxLength{}}, NotNull: false}
-	cds["col3"] = ColumnDef{Name: "col3", T: Bytes{Int64Length{42}}, NotNull: false}
+	cds["col1"] = ColumnDef{Name: "col1", T: Type{Name: Int64}, NotNull: true}
+	cds["col2"] = ColumnDef{Name: "col2", T: Type{Name: String, Len: MaxLength}, NotNull: false}
+	cds["col3"] = ColumnDef{Name: "col3", T: Type{Name: Bytes, Len: int64(42)}, NotNull: false}
 	ct := CreateTable{
 		"mytable",
 		[]string{"col1", "col2", "col3"},