Repository: ariga/atlas
Pull request #690 (2022-04-08 07:05:49)

sql/schema: use pointer for time precision
The zero value of int does not represent the default value of the precision field. In PostgreSQL, it's 6, and in MySQL is 0.


Patch:
diff --git a/sql/internal/specutil/types.go b/sql/internal/specutil/types.go
index 212194e6..7d3962d1 100644
--- a/sql/internal/specutil/types.go
+++ b/sql/internal/specutil/types.go
@@ -206,10 +206,10 @@ func (r *TypeRegistry) Convert(typ schema.Type) (*schemaspec.Type, error) {
 		n := inflect.Camelize(attr.Name)
 		field := rv.FieldByName(n)
 		// If TypeSpec has an attribute that isn't mapped to a field on the schema.Type skip it.
-		if !field.IsValid() {
+		if !field.IsValid() || field.Kind() == reflect.Ptr && field.IsNil() {
 			continue
 		}
-		if field.Kind() != attr.Kind {
+		if field = reflect.Indirect(field); field.Kind() != attr.Kind {
 			return nil, errors.New("incompatible kinds on typespec attr and typefield")
 		}
 		switch attr.Kind {
diff --git a/sql/mysql/convert.go b/sql/mysql/convert.go
index 46a1e443..4e8359eb 100644
--- a/sql/mysql/convert.go
+++ b/sql/mysql/convert.go
@@ -91,8 +91,8 @@ func FormatType(t schema.Type) (string, error) {
 		f = strings.ToLower(t.T)
 	case *schema.TimeType:
 		f = strings.ToLower(t.T)
-		if t.Precision > 0 {
-			f = fmt.Sprintf("%s(%d)", f, t.Precision)
+		if p := t.Precision; p != nil && *p > 0 {
+			f = fmt.Sprintf("%s(%d)", f, *p)
 		}
 	case *schema.UnsupportedType:
 		// Do not accept unsupported types as we should cover all cases.
@@ -222,11 +222,11 @@ func ParseType(raw string) (schema.Type, error) {
 			T: t,
 		}
 		if len(parts) > 1 {
-			p, err := strconv.ParseInt(parts[1], 10, 64)
+			p, err := strconv.Atoi(parts[1])
 			if err != nil {
 				return nil, fmt.Errorf("parse precision %q", parts[1])
 			}
-			tt.Precision = int(p)
+			tt.Precision = &p
 		}
 		return tt, nil
 	case TypeJSON:
diff --git a/sql/postgres/convert.go b/sql/postgres/convert.go
index c0d2d402..3468127b 100644
--- a/sql/postgres/convert.go
+++ b/sql/postgres/convert.go
@@ -74,8 +74,8 @@ func FormatType(t schema.Type) (string, error) {
 		}
 	case *schema.TimeType:
 		f = timeAlias(t.T)
-		if t.Precision != defaultTimePrecision && strings.HasPrefix(f, "time") {
-			f += fmt.Sprintf("(%d)", t.Precision)
+		if p := t.Precision; p != nil && *p != defaultTimePrecision && strings.HasPrefix(f, "time") {
+			f += fmt.Sprintf("(%d)", *p)
 		}
 	case *schema.FloatType:
 		switch f = strings.ToLower(t.T); f {
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index f54495ce..cd03f4da 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -235,11 +235,11 @@ func columnType(c *columnDesc) schema.Type {
 		typ = &schema.TimeType{T: t}
 	case TypeTime, TypeTimeWOTZ, TypeTimeTZ, TypeTimeWTZ, TypeTimestamp,
 		TypeTimestampTZ, TypeTimestampWTZ, TypeTimestampWOTZ:
-		t := &schema.TimeType{T: t, Precision: defaultTimePrecision}
+		p := defaultTimePrecision
 		if c.timePrecision != nil {
-			t.Precision = int(*c.timePrecision)
+			p = int(*c.timePrecision)
 		}
-		typ = t
+		typ = &schema.TimeType{T: t, Precision: &p}
 	case TypeInterval:
 		// TODO: get 'interval_type' from query above before implementing.
 		typ = &schema.UnsupportedType{T: t}
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 484eb0e3..4b8baaf0 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -216,7 +216,8 @@ func convertColumnType(spec *sqlspec.Column) (schema.Type, error) {
 	// Handle default values for time precision types.
 	if t, ok := typ.(*schema.TimeType); ok && strings.HasPrefix(t.T, "time") {
 		if _, ok := attr(spec.Type, "precision"); !ok {
-			t.Precision = defaultTimePrecision
+			p := defaultTimePrecision
+			t.Precision = &p
 		}
 	}
 	return typ, nil
@@ -456,8 +457,8 @@ func attr(typ *schemaspec.Type, key string) (*schemaspec.Attr, bool) {
 func typeSpec(t schema.Type) (*schemaspec.Type, error) {
 	if t, ok := t.(*schema.TimeType); ok && t.T != TypeDate {
 		spec := &schemaspec.Type{T: timeAlias(t.T)}
-		if t.Precision != defaultTimePrecision {
-			spec.Attrs = []*schemaspec.Attr{specutil.LitAttr("precision", strconv.Itoa(t.Precision))}
+		if p := t.Precision; p != nil && *p != defaultTimePrecision {
+			spec.Attrs = []*schemaspec.Attr{specutil.IntAttr("precision", *p)}
 		}
 		return spec, nil
 	}
@@ -479,6 +480,6 @@ func formatTime() specutil.TypeSpecOption {
 		if err != nil {
 			return "", fmt.Errorf(`postgres: parsing attribute "precision": %w`, err)
 		}
-		return FormatType(&schema.TimeType{T: t.T, Precision: p})
+		return FormatType(&schema.TimeType{T: t.T, Precision: &p})
 	})
 }
diff --git a/sql/schema/dsl.go b/sql/schema/dsl.go
index 427f2da4..1a9b6fff 100644
--- a/sql/schema/dsl.go
+++ b/sql/schema/dsl.go
@@ -410,7 +410,7 @@ type TimeOption func(*TimeType)
 // TimePrecision configures the precision of the time type.
 func TimePrecision(precision int) TimeOption {
 	return func(b *TimeType) {
-		b.Precision = precision
+		b.Precision = &precision
 	}
 }
 
diff --git a/sql/schema/schema.go b/sql/schema/schema.go
index 7793fa04..536c58e6 100644
--- a/sql/schema/schema.go
+++ b/sql/schema/schema.go
@@ -233,7 +233,7 @@ type (
 	// TimeType represents a date/time type.
 	TimeType struct {
 		T         string
-		Precision int
+		Precision *int
 	}
 
 	// JSONType represents a JSON type.


Test patch:
diff --git a/internal/integration/mysql_test.go b/internal/integration/mysql_test.go
index b38ce364..28576ca3 100644
--- a/internal/integration/mysql_test.go
+++ b/internal/integration/mysql_test.go
@@ -825,7 +825,7 @@ create table atlas_types_sanity
 					},
 					{
 						Name: "tTimestampFraction",
-						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: 6},
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: intp(6)},
 							Raw: "timestamp(6)", Null: true},
 						Default: &schema.RawExpr{
 							X: func() string {
@@ -861,8 +861,7 @@ create table atlas_types_sanity
 					},
 					{
 						Name: "tTimestampFractionOnUpdate",
-						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: 6},
-							Raw: "timestamp(6)", Null: true},
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: intp(6)}, Raw: "timestamp(6)", Null: true},
 						Default: &schema.RawExpr{
 							X: func() string {
 								if t.mariadb() {
@@ -899,7 +898,7 @@ create table atlas_types_sanity
 					},
 					{
 						Name: "tYear",
-						Type: &schema.ColumnType{Type: &schema.TimeType{T: "year", Precision: t.intByVersion(map[string]int{"mysql8": 0}, 4)},
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "year", Precision: intp(t.intByVersion(map[string]int{"mysql8": 0}, 4))},
 							Raw: t.valueByVersion(map[string]string{"mysql8": "year"}, "year(4)"), Null: true},
 					},
 					{
@@ -1359,3 +1358,5 @@ func rmCreateStmt(t *schema.Table) {
 		}
 	}
 }
+
+func intp(i int) *int { return &i }
diff --git a/internal/integration/postgres_test.go b/internal/integration/postgres_test.go
index 0bab9011..70fcac64 100644
--- a/internal/integration/postgres_test.go
+++ b/internal/integration/postgres_test.go
@@ -812,42 +812,42 @@ create table atlas_types_sanity
 				},
 				{
 					Name:    "tTime",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time without time zone", Precision: 6}, Raw: "time without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time without time zone", Precision: intp(6)}, Raw: "time without time zone", Null: true},
 					Default: &schema.RawExpr{X: "CURRENT_TIME"},
 				},
 				{
 					Name:    "tTimeWTZ",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time with time zone", Precision: 6}, Raw: "time with time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time with time zone", Precision: intp(6)}, Raw: "time with time zone", Null: true},
 					Default: &schema.RawExpr{X: "CURRENT_TIME"},
 				},
 				{
 					Name:    "tTimeWOTZ",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time without time zone", Precision: 6}, Raw: "time without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time without time zone", Precision: intp(6)}, Raw: "time without time zone", Null: true},
 					Default: &schema.RawExpr{X: "CURRENT_TIME"},
 				},
 				{
 					Name:    "tTimestamp",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: 6}, Raw: "timestamp without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: intp(6)}, Raw: "timestamp without time zone", Null: true},
 					Default: &schema.RawExpr{X: "now()"},
 				},
 				{
 					Name:    "tTimestampTZ",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp with time zone", Precision: 6}, Raw: "timestamp with time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp with time zone", Precision: intp(6)}, Raw: "timestamp with time zone", Null: true},
 					Default: &schema.RawExpr{X: "now()"},
 				},
 				{
 					Name:    "tTimestampWTZ",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp with time zone", Precision: 6}, Raw: "timestamp with time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp with time zone", Precision: intp(6)}, Raw: "timestamp with time zone", Null: true},
 					Default: &schema.RawExpr{X: "now()"},
 				},
 				{
 					Name:    "tTimestampWOTZ",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: 6}, Raw: "timestamp without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: intp(6)}, Raw: "timestamp without time zone", Null: true},
 					Default: &schema.RawExpr{X: "now()"},
 				},
 				{
 					Name:    "tTimestampPrec",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: 4}, Raw: "timestamp without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: intp(4)}, Raw: "timestamp without time zone", Null: true},
 					Default: &schema.RawExpr{X: "now()"},
 				},
 				{
diff --git a/internal/integration/tidb_test.go b/internal/integration/tidb_test.go
index 98fc62bb..a3881420 100644
--- a/internal/integration/tidb_test.go
+++ b/internal/integration/tidb_test.go
@@ -822,7 +822,7 @@ create table atlas_types_sanity
 					},
 					{
 						Name: "tTimestampFraction",
-						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: 6},
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: intp(6)},
 							Raw: "timestamp(6)", Null: true},
 						Default: &schema.RawExpr{
 							X: "CURRENT_TIMESTAMP(6)",
@@ -843,7 +843,7 @@ create table atlas_types_sanity
 					},
 					{
 						Name: "tTimestampFractionOnUpdate",
-						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: 6},
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: intp(6)},
 							Raw: "timestamp(6)", Null: true},
 						Default: &schema.RawExpr{
 							X: "CURRENT_TIMESTAMP(6)",
@@ -871,7 +871,7 @@ create table atlas_types_sanity
 					},
 					{
 						Name: "tYear",
-						Type: &schema.ColumnType{Type: &schema.TimeType{T: "year", Precision: t.intByVersion(map[string]int{"mysql8": 0}, 4)},
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "year", Precision: intp(t.intByVersion(map[string]int{"mysql8": 0}, 4))},
 							Raw: t.valueByVersion(map[string]string{"mysql8": "year"}, "year(4) unsigned"), Null: true},
 					},
 					{
diff --git a/sql/mysql/inspect_test.go b/sql/mysql/inspect_test.go
index d3308f7b..b66763df 100644
--- a/sql/mysql/inspect_test.go
+++ b/sql/mysql/inspect_test.go
@@ -376,6 +376,7 @@ func TestDriver_InspectTable(t *testing.T) {
 				m.noFKs()
 			},
 			expect: func(require *require.Assertions, t *schema.Table, err error) {
+				p := func(i int) *int { return &i }
 				require.NoError(err)
 				require.Equal("users", t.Name)
 				require.EqualValues([]*schema.Column{
@@ -383,9 +384,9 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c2", Type: &schema.ColumnType{Raw: "datetime", Type: &schema.TimeType{T: "datetime"}}},
 					{Name: "c3", Type: &schema.ColumnType{Raw: "time", Type: &schema.TimeType{T: "time"}}},
 					{Name: "c4", Type: &schema.ColumnType{Raw: "timestamp", Type: &schema.TimeType{T: "timestamp"}}, Default: &schema.RawExpr{X: "CURRENT_TIMESTAMP"}, Attrs: []schema.Attr{&OnUpdate{A: "CURRENT_TIMESTAMP"}}},
-					{Name: "c5", Type: &schema.ColumnType{Raw: "year(4)", Type: &schema.TimeType{T: "year", Precision: 4}}},
+					{Name: "c5", Type: &schema.ColumnType{Raw: "year(4)", Type: &schema.TimeType{T: "year", Precision: p(4)}}},
 					{Name: "c6", Type: &schema.ColumnType{Raw: "year", Type: &schema.TimeType{T: "year"}}},
-					{Name: "c7", Type: &schema.ColumnType{Raw: "timestamp(6)", Type: &schema.TimeType{T: "timestamp", Precision: 6}}, Default: &schema.RawExpr{X: "CURRENT_TIMESTAMP(6)"}, Attrs: []schema.Attr{&OnUpdate{A: "CURRENT_TIMESTAMP(6)"}}},
+					{Name: "c7", Type: &schema.ColumnType{Raw: "timestamp(6)", Type: &schema.TimeType{T: "timestamp", Precision: p(6)}}, Default: &schema.RawExpr{X: "CURRENT_TIMESTAMP(6)"}, Attrs: []schema.Attr{&OnUpdate{A: "CURRENT_TIMESTAMP(6)"}}},
 				}, t.Columns)
 			},
 		},
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index 4a29ec6b..10ce039a 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -156,20 +156,14 @@ table "accounts" {
 				{
 					Name: "created_at",
 					Type: &schema.ColumnType{
-						Type: &schema.TimeType{
-							T:         TypeDateTime,
-							Precision: 4,
-						},
+						Type: typeTime(TypeDateTime, 4),
 					},
 					Default: &schema.RawExpr{X: "now(4)"},
 				},
 				{
 					Name: "updated_at",
 					Type: &schema.ColumnType{
-						Type: &schema.TimeType{
-							T:         TypeTimestamp,
-							Precision: 6,
-						},
+						Type: typeTime(TypeTimestamp, 6),
 					},
 					Default: &schema.RawExpr{X: "current_timestamp(6)"},
 					Attrs:   []schema.Attr{&OnUpdate{A: "current_timestamp(6)"}},
@@ -898,7 +892,7 @@ func TestTypes(t *testing.T) {
 		},
 		{
 			typeExpr: "timestamp(6)",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 6},
+			expected: typeTime(TypeTimestamp, 6),
 		},
 		{
 			typeExpr: "date",
@@ -906,7 +900,7 @@ func TestTypes(t *testing.T) {
 		},
 		{
 			typeExpr: "date(2)",
-			expected: &schema.TimeType{T: TypeDate, Precision: 2},
+			expected: typeTime(TypeDate, 2),
 		},
 		{
 			typeExpr: "time",
@@ -914,7 +908,7 @@ func TestTypes(t *testing.T) {
 		},
 		{
 			typeExpr: "time(6)",
-			expected: &schema.TimeType{T: TypeTime, Precision: 6},
+			expected: typeTime(TypeTime, 6),
 		},
 		{
 			typeExpr: "datetime",
@@ -922,7 +916,7 @@ func TestTypes(t *testing.T) {
 		},
 		{
 			typeExpr: "datetime(6)",
-			expected: &schema.TimeType{T: TypeDateTime, Precision: 6},
+			expected: typeTime(TypeDateTime, 6),
 		},
 		{
 			typeExpr: "year",
@@ -930,7 +924,7 @@ func TestTypes(t *testing.T) {
 		},
 		{
 			typeExpr: "year(2)",
-			expected: &schema.TimeType{T: TypeYear, Precision: 2},
+			expected: typeTime(TypeYear, 2),
 		},
 		{
 			typeExpr: "varchar(10)",
@@ -1060,6 +1054,10 @@ schema "test" {
 	}
 }
 
+func typeTime(t string, p int) schema.Type {
+	return &schema.TimeType{T: t, Precision: &p}
+}
+
 func lineIfSet(s string) string {
 	if s != "" {
 		return "\n" + s
diff --git a/sql/postgres/inspect_test.go b/sql/postgres/inspect_test.go
index efdb3a8e..773f4ee0 100644
--- a/sql/postgres/inspect_test.go
+++ b/sql/postgres/inspect_test.go
@@ -84,6 +84,7 @@ func TestDriver_InspectTable(t *testing.T) {
 				m.noChecks()
 			},
 			expect: func(require *require.Assertions, t *schema.Table, err error) {
+				p := func(i int) *int { return &i }
 				require.NoError(err)
 				require.Equal("users", t.Name)
 				require.EqualValues([]*schema.Column{
@@ -99,7 +100,7 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c8", Type: &schema.ColumnType{Raw: "cidr", Type: &NetworkType{T: "cidr"}}},
 					{Name: "c9", Type: &schema.ColumnType{Raw: "circle", Type: &schema.SpatialType{T: "circle"}}},
 					{Name: "c10", Type: &schema.ColumnType{Raw: "date", Type: &schema.TimeType{T: "date"}}},
-					{Name: "c11", Type: &schema.ColumnType{Raw: "time with time zone", Type: &schema.TimeType{T: "time with time zone"}}},
+					{Name: "c11", Type: &schema.ColumnType{Raw: "time with time zone", Type: &schema.TimeType{T: "time with time zone", Precision: p(0)}}},
 					{Name: "c12", Type: &schema.ColumnType{Raw: "double precision", Type: &schema.FloatType{T: "double precision", Precision: 53}}},
 					{Name: "c13", Type: &schema.ColumnType{Raw: "real", Type: &schema.FloatType{T: "real", Precision: 24}}, Default: &schema.RawExpr{X: "random()"}},
 					{Name: "c14", Type: &schema.ColumnType{Raw: "json", Type: &schema.JSONType{T: "json"}}, Default: &schema.Literal{V: "'{}'"}},
@@ -113,9 +114,9 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c22", Type: &schema.ColumnType{Raw: "ARRAY", Null: true, Type: &ArrayType{T: "int4[]"}}},
 					{Name: "c23", Type: &schema.ColumnType{Raw: "USER-DEFINED", Null: true, Type: &UserDefinedType{T: "ltree"}}},
 					{Name: "c24", Type: &schema.ColumnType{Raw: "state", Type: &schema.EnumType{T: "state", Values: []string{"on", "off"}}}},
-					{Name: "c25", Type: &schema.ColumnType{Raw: "timestamp without time zone", Type: &schema.TimeType{T: "timestamp without time zone", Precision: 4}}, Default: &schema.RawExpr{X: "now()"}},
-					{Name: "c26", Type: &schema.ColumnType{Raw: "timestamp with time zone", Type: &schema.TimeType{T: "timestamp with time zone", Precision: 6}}},
-					{Name: "c27", Type: &schema.ColumnType{Raw: "time without time zone", Type: &schema.TimeType{T: "time without time zone", Precision: 6}}},
+					{Name: "c25", Type: &schema.ColumnType{Raw: "timestamp without time zone", Type: &schema.TimeType{T: "timestamp without time zone", Precision: p(4)}}, Default: &schema.RawExpr{X: "now()"}},
+					{Name: "c26", Type: &schema.ColumnType{Raw: "timestamp with time zone", Type: &schema.TimeType{T: "timestamp with time zone", Precision: p(6)}}},
+					{Name: "c27", Type: &schema.ColumnType{Raw: "time without time zone", Type: &schema.TimeType{T: "time without time zone", Precision: p(6)}}},
 				}, t.Columns)
 			},
 		},
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index ab4584d0..d89a1be8 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -168,20 +168,14 @@ enum "account_type" {
 				{
 					Name: "created_at",
 					Type: &schema.ColumnType{
-						Type: &schema.TimeType{
-							T:         TypeTimestamp,
-							Precision: 4,
-						},
+						Type: typeTime(TypeTimestamp, 4),
 					},
 					Default: &schema.RawExpr{X: "current_timestamp(4)"},
 				},
 				{
 					Name: "updated_at",
 					Type: &schema.ColumnType{
-						Type: &schema.TimeType{
-							T:         TypeTime,
-							Precision: 6,
-						},
+						Type: typeTime(TypeTime, 6),
 					},
 					Default: &schema.RawExpr{X: "current_time"},
 				},
@@ -567,31 +561,31 @@ func TestTypes(t *testing.T) {
 		},
 		{
 			typeExpr: "time",
-			expected: &schema.TimeType{T: TypeTime, Precision: 6},
+			expected: typeTime(TypeTime, 6),
 		},
 		{
 			typeExpr: "time(4)",
-			expected: &schema.TimeType{T: TypeTime, Precision: 4},
+			expected: typeTime(TypeTime, 4),
 		},
 		{
 			typeExpr: "timetz",
-			expected: &schema.TimeType{T: TypeTimeTZ, Precision: 6},
+			expected: typeTime(TypeTimeTZ, 6),
 		},
 		{
 			typeExpr: "timestamp",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 6},
+			expected: typeTime(TypeTimestamp, 6),
 		},
 		{
 			typeExpr: "timestamp(4)",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 4},
+			expected: typeTime(TypeTimestamp, 4),
 		},
 		{
 			typeExpr: "timestamptz",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 6},
+			expected: typeTime(TypeTimestampTZ, 6),
 		},
 		{
 			typeExpr: "timestamptz(4)",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 4},
+			expected: typeTime(TypeTimestampTZ, 4),
 		},
 		{
 			typeExpr: "real",
@@ -649,10 +643,6 @@ func TestTypes(t *testing.T) {
 			typeExpr: "jsonb",
 			expected: &schema.JSONType{T: TypeJSONB},
 		},
-		{
-			typeExpr: "timestamptz(3)",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 3},
-		},
 		{
 			typeExpr: "uuid",
 			expected: &UUIDType{T: TypeUUID},
@@ -712,6 +702,10 @@ schema "test" {
 	}
 }
 
+func typeTime(t string, p int) schema.Type {
+	return &schema.TimeType{T: t, Precision: &p}
+}
+
 func TestParseType_Time(t *testing.T) {
 	for _, tt := range []struct {
 		typ      string
@@ -719,75 +713,75 @@ func TestParseType_Time(t *testing.T) {
 	}{
 		{
 			typ:      "timestamptz",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 6},
+			expected: typeTime(TypeTimestampTZ, 6),
 		},
 		{
 			typ:      "timestamptz(0)",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 0},
+			expected: typeTime(TypeTimestampTZ, 0),
 		},
 		{
 			typ:      "timestamptz(6)",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 6},
+			expected: typeTime(TypeTimestampTZ, 6),
 		},
 		{
 			typ:      "timestamp with time zone",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 6},
+			expected: typeTime(TypeTimestampTZ, 6),
 		},
 		{
 			typ:      "timestamp(1) with time zone",
-			expected: &schema.TimeType{T: TypeTimestampTZ, Precision: 1},
+			expected: typeTime(TypeTimestampTZ, 1),
 		},
 		{
 			typ:      "timestamp",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 6},
+			expected: typeTime(TypeTimestamp, 6),
 		},
 		{
 			typ:      "timestamp(0)",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 0},
+			expected: typeTime(TypeTimestamp, 0),
 		},
 		{
 			typ:      "timestamp(6)",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 6},
+			expected: typeTime(TypeTimestamp, 6),
 		},
 		{
 			typ:      "timestamp without time zone",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 6},
+			expected: typeTime(TypeTimestamp, 6),
 		},
 		{
 			typ:      "timestamp(1) without time zone",
-			expected: &schema.TimeType{T: TypeTimestamp, Precision: 1},
+			expected: typeTime(TypeTimestamp, 1),
 		},
 		{
 			typ:      "time",
-			expected: &schema.TimeType{T: TypeTime, Precision: 6},
+			expected: typeTime(TypeTime, 6),
 		},
 		{
 			typ:      "time(3)",
-			expected: &schema.TimeType{T: TypeTime, Precision: 3},
+			expected: typeTime(TypeTime, 3),
 		},
 		{
 			typ:      "time without time zone",
-			expected: &schema.TimeType{T: TypeTime, Precision: 6},
+			expected: typeTime(TypeTime, 6),
 		},
 		{
 			typ:      "time(3) without time zone",
-			expected: &schema.TimeType{T: TypeTime, Precision: 3},
+			expected: typeTime(TypeTime, 3),
 		},
 		{
 			typ:      "timetz",
-			expected: &schema.TimeType{T: TypeTimeTZ, Precision: 6},
+			expected: typeTime(TypeTimeTZ, 6),
 		},
 		{
 			typ:      "timetz(4)",
-			expected: &schema.TimeType{T: TypeTimeTZ, Precision: 4},
+			expected: typeTime(TypeTimeTZ, 4),
 		},
 		{
 			typ:      "time with time zone",
-			expected: &schema.TimeType{T: TypeTimeTZ, Precision: 6},
+			expected: typeTime(TypeTimeTZ, 6),
 		},
 		{
 			typ:      "time(4) with time zone",
-			expected: &schema.TimeType{T: TypeTimeTZ, Precision: 4},
+			expected: typeTime(TypeTimeTZ, 4),
 		},
 	} {
 		t.Run(tt.typ, func(t *testing.T) {
diff --git a/sql/schema/dsl_test.go b/sql/schema/dsl_test.go
index 7c7b1fbc..b721028b 100644
--- a/sql/schema/dsl_test.go
+++ b/sql/schema/dsl_test.go
@@ -73,6 +73,7 @@ func TestSchema_AddTables(t *testing.T) {
 	require.Equal(
 		t,
 		func() *schema.Schema {
+			p := 6
 			s := &schema.Schema{Name: "public"}
 			users := &schema.Table{
 				Name:   "users",
@@ -84,7 +85,7 @@ func TestSchema_AddTables(t *testing.T) {
 					{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "int"}}},
 					{Name: "active", Type: &schema.ColumnType{Type: &schema.BoolType{T: "boolean"}}},
 					{Name: "name", Type: &schema.ColumnType{Null: true, Type: &schema.StringType{T: "varchar", Size: 255}}},
-					{Name: "registered_at", Type: &schema.ColumnType{Null: false, Type: &schema.TimeType{T: "timestamp", Precision: 6}}},
+					{Name: "registered_at", Type: &schema.ColumnType{Null: false, Type: &schema.TimeType{T: "timestamp", Precision: &p}}},
 				},
 			}
 			s.Tables = append(s.Tables, users)