Repository: ariga/atlas
Pull request #475 (2022-01-24 13:29:56)

doc/md: update release notes to latest


Patch:
diff --git a/README.md b/README.md
index a3605367..fcf2a10e 100644
--- a/README.md
+++ b/README.md
@@ -23,17 +23,17 @@ be used individually but are designed to work very well together.
 
 Download latest release.
 ```shell
-curl -LO https://release.ariga.io/atlas/atlas-darwin-amd64-v0.3.1
+curl -LO https://release.ariga.io/atlas/atlas-darwin-amd64-latest
 ```
 
 Make the atlas binary executable.
 ```shell
-chmod +x ./atlas-darwin-amd64-v0.3.1
+chmod +x ./atlas-darwin-amd64-latest
 ```
 
 Move the atlas binary to a file location on your system PATH.
 ```shell
-sudo mv ./atlas-darwin-amd64-v0.3.1 /usr/local/bin/atlas
+sudo mv ./atlas-darwin-amd64-latest /usr/local/bin/atlas
 ```
 ```shell
 sudo chown root: /usr/local/bin/atlas
diff --git a/doc/md/getting-started/01-introduction.mdx b/doc/md/getting-started/01-introduction.mdx
index ce73e1fc..b96ee489 100644
--- a/doc/md/getting-started/01-introduction.mdx
+++ b/doc/md/getting-started/01-introduction.mdx
@@ -30,17 +30,17 @@ values={[
 
 Download latest release.
 ```shell
-curl -LO https://release.ariga.io/atlas/atlas-darwin-amd64-v0.3.1
+curl -LO https://release.ariga.io/atlas/atlas-darwin-amd64-latest
 ```
 
 Make the atlas binary executable.
 ```shell
-chmod +x ./atlas-darwin-amd64-v0.3.1
+chmod +x ./atlas-darwin-amd64-latest
 ```
 
 Move the atlas binary to a file location on your system PATH.
 ```shell
-sudo mv ./atlas-darwin-amd64-v0.3.1 /usr/local/bin/atlas
+sudo mv ./atlas-darwin-amd64-latest /usr/local/bin/atlas
 ```
 ```shell
 sudo chown root: /usr/local/bin/atlas
@@ -51,18 +51,18 @@ sudo chown root: /usr/local/bin/atlas
 
 Download latest release.
 ```shell
-curl -LO https://release.ariga.io/atlas/atlas-linux-amd64-v0.3.1
+curl -LO https://release.ariga.io/atlas/atlas-linux-amd64-latest
 ```
 
 Move the atlas binary to a file location on your system PATH.
 ```shell
-sudo install -o root -g root -m 0755 ./atlas-linux-amd64-v0.3.1 /usr/local/bin/atlas
+sudo install -o root -g root -m 0755 ./atlas-linux-amd64-latest /usr/local/bin/atlas
 ```
 
 </TabItem>
 <TabItem value="windows">
 
-Download the [latest release](https://release.ariga.io/atlas/atlas-windows-amd64-v0.3.1.exe) and
+Download the [latest release](https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe) and
 move the atlas binary to a file location on your system PATH.
 
 
diff --git a/internal/integration/go.mod b/internal/integration/go.mod
index 43a33c39..6b32c6e0 100644
--- a/internal/integration/go.mod
+++ b/internal/integration/go.mod
@@ -1,4 +1,4 @@
-module ariga.io/atlas/integration
+module ariga.io/atlas/internal/integration
 
 go 1.17
 
diff --git a/internal/integration/go.sum b/internal/integration/go.sum
index b0fb2e6d..87b9876b 100644
--- a/internal/integration/go.sum
+++ b/internal/integration/go.sum
@@ -384,8 +384,6 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
-github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
 github.com/rogpeppe/go-internal v1.8.2-0.20220112175052-f3cb5c2c6412 h1:BVgfta5l+NeAx6l597tEbF2TkiVi+Mw+2vxJjwSfyRU=
 github.com/rogpeppe/go-internal v1.8.2-0.20220112175052-f3cb5c2c6412/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
diff --git a/internal/integration/testdata/mysql/add-drop-index.txt b/internal/integration/testdata/mysql/index-add-drop.txt
similarity index 100%
rename from internal/integration/testdata/mysql/add-drop-index.txt
rename to internal/integration/testdata/mysql/index-add-drop.txt
diff --git a/internal/typedoc/go.mod b/internal/typedoc/go.mod
new file mode 100644
index 00000000..e16970a8
--- /dev/null
+++ b/internal/typedoc/go.mod
@@ -0,0 +1,19 @@
+module ariga.io/atlas/internal/typedoc
+
+go 1.17
+
+replace ariga.io/atlas => ../../
+
+require ariga.io/atlas v0.0.0
+
+require (
+	github.com/agext/levenshtein v1.2.1 // indirect
+	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
+	github.com/go-openapi/inflect v0.19.0 // indirect
+	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/hashicorp/hcl/v2 v2.10.0 // indirect
+	github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
+	github.com/zclconf/go-cty v1.8.0 // indirect
+	golang.org/x/mod v0.5.0 // indirect
+	golang.org/x/text v0.3.7 // indirect
+)
diff --git a/internal/typedoc/go.sum b/internal/typedoc/go.sum
new file mode 100644
index 00000000..d02b659d
--- /dev/null
+++ b/internal/typedoc/go.sum
@@ -0,0 +1,57 @@
+github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
+github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
+github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
+github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
+github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
+github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
+github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
+github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
+github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/hashicorp/hcl/v2 v2.10.0 h1:1S1UnuhDGlv3gRFV4+0EdwB+znNP5HmcGbIqwnSCByg=
+github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
+github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
+github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
+github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
+github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
+github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
+github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
+github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
+github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/integration/typedoc/main.go b/internal/typedoc/main.go
similarity index 98%
rename from internal/integration/typedoc/main.go
rename to internal/typedoc/main.go
index 37cb6d56..2356fa23 100644
--- a/internal/integration/typedoc/main.go
+++ b/internal/typedoc/main.go
@@ -64,7 +64,7 @@ enum "my_enum" &#123;
 			return drv.Types[i].Name < drv.Types[j].Name
 		})
 	}
-	f, err := os.Create("../../../doc/md/ddl/sql_types.md")
+	f, err := os.Create("../../doc/md/ddl/sql_types.md")
 	if err != nil {
 		log.Fatalf("error: %s", err)
 	}
diff --git a/internal/integration/typedoc/output.tmpl b/internal/typedoc/output.tmpl
similarity index 100%
rename from internal/integration/typedoc/output.tmpl
rename to internal/typedoc/output.tmpl
diff --git a/sql/mysql/convert.go b/sql/mysql/convert.go
index 42fd778a..f59d26a7 100644
--- a/sql/mysql/convert.go
+++ b/sql/mysql/convert.go
@@ -106,6 +106,12 @@ func ParseType(raw string) (schema.Type, error) {
 		return &BitType{
 			T: t,
 		}, nil
+	// bool and booleans are synonyms for
+	// tinyint with display-width set to 1.
+	case TypeBool, TypeBoolean:
+		return &schema.BoolType{
+			T: TypeBool,
+		}, nil
 	case TypeTinyInt, TypeSmallInt, TypeMediumInt, TypeInt, TypeBigInt:
 		if size == 1 {
 			return &schema.BoolType{
@@ -201,9 +207,17 @@ func ParseType(raw string) (schema.Type, error) {
 			Values: values,
 		}, nil
 	case TypeDate, TypeDateTime, TypeTime, TypeTimestamp, TypeYear:
-		return &schema.TimeType{
+		tt := &schema.TimeType{
 			T: t,
-		}, nil
+		}
+		if len(parts) > 1 {
+			p, err := strconv.ParseInt(parts[1], 10, 64)
+			if err != nil {
+				return nil, fmt.Errorf("parse precision %q", parts[1])
+			}
+			tt.Precision = int(p)
+		}
+		return tt, nil
 	case TypeJSON:
 		return &schema.JSONType{
 			T: t,
diff --git a/sql/mysql/driver.go b/sql/mysql/driver.go
index 9627f426..ce7ed624 100644
--- a/sql/mysql/driver.go
+++ b/sql/mysql/driver.go
@@ -134,7 +134,11 @@ func unescape(s string) string {
 // https://github.com/mysql/mysql-server/blob/8.0/sql/dd/types/column.h
 // https://github.com/mysql/mysql-server/blob/8.0/sql/sql_show.cc
 // https://github.com/mysql/mysql-server/blob/8.0/sql/gis/geometries.cc
+// https://dev.mysql.com/doc/refman/8.0/en/other-vendor-data-types.html
 const (
+	TypeBool    = "bool"
+	TypeBoolean = "boolean"
+
 	TypeBit       = "bit"       // MYSQL_TYPE_BIT
 	TypeInt       = "int"       // MYSQL_TYPE_LONG
 	TypeTinyInt   = "tinyint"   // MYSQL_TYPE_TINY
diff --git a/sql/mysql/inspect.go b/sql/mysql/inspect.go
index 2e944648..d98fe4b2 100644
--- a/sql/mysql/inspect.go
+++ b/sql/mysql/inspect.go
@@ -450,15 +450,18 @@ func (i *inspect) tableNames(ctx context.Context, schema string, opts *schema.In
 	return names, nil
 }
 
+var reTimeOnUpdate = regexp.MustCompile(`^(?:default_generated )?on update current_timestamp(?:\(\d?\))?$`)
+
 // extraAttr parses the EXTRA column from the INFORMATION_SCHEMA.COLUMNS table
 // and appends its parsed representation to the column.
 func (i *inspect) extraAttr(t *schema.Table, c *schema.Column, extra string) error {
-	switch extra := strings.ToLower(extra); extra {
-	case "", "null": // ignore.
-	case defaultGen:
+	extra = strings.ToLower(extra)
+	switch {
+	case extra == "", extra == "null": // ignore.
+	case extra == defaultGen:
 		// The column has an expression default value
 		// and it is handled in Driver.addColumn.
-	case autoIncrement:
+	case extra == autoIncrement:
 		a := &AutoIncrement{}
 		// A table can have only one AUTO_INCREMENT column. If it was returned as NULL
 		// from INFORMATION_SCHEMA, it is due to information_schema_stats_expiry and we
@@ -472,8 +475,7 @@ func (i *inspect) extraAttr(t *schema.Table, c *schema.Column, extra string) err
 			t.Attrs = append(t.Attrs, a)
 		}
 		c.Attrs = append(c.Attrs, a)
-	case "default_generated on update current_timestamp", "on update current_timestamp",
-		"on update current_timestamp()" /* MariaDB format. */ :
+	case reTimeOnUpdate.MatchString(extra):
 		c.Attrs = append(c.Attrs, &OnUpdate{A: extra})
 	default:
 		return fmt.Errorf("unknown attribute %q", extra)
@@ -514,6 +516,8 @@ func (i *inspect) createStmt(ctx context.Context, t *schema.Table) error {
 	return nil
 }
 
+var reCurrTimestamp = regexp.MustCompile(`(?i)^current_timestamp(?:\(\d?\))?$`)
+
 // myDefaultExpr returns the correct schema.Expr based on the column attributes for MySQL.
 func (i *inspect) myDefaultExpr(c *schema.Column, x, extra string) schema.Expr {
 	// In MySQL, the DEFAULT_GENERATED indicates the column has an expression default value.
@@ -531,7 +535,7 @@ func (i *inspect) myDefaultExpr(c *schema.Column, x, extra string) schema.Expr {
 	case *schema.TimeType:
 		// "current_timestamp" is exceptional in old versions
 		// of MySQL for timestamp and datetime data types.
-		if strings.ToLower(x) == currentTS {
+		if reCurrTimestamp.MatchString(x) {
 			return &schema.RawExpr{X: x}
 		}
 	}
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index eef8db23..9b55be71 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -261,6 +261,8 @@ var TypeRegistry = specutil.NewRegistry(
 			},
 			RType: reflect.TypeOf(SetType{}),
 		},
+		specutil.TypeSpec(TypeBool),
+		specutil.TypeSpec(TypeBoolean),
 		specutil.TypeSpec(TypeBit, specutil.SizeTypeAttr(false)),
 		specutil.TypeSpec(TypeInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
 		specutil.TypeSpec(TypeTinyInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
@@ -272,11 +274,11 @@ var TypeRegistry = specutil.NewRegistry(
 		specutil.TypeSpec(TypeFloat, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
 		specutil.TypeSpec(TypeDouble, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
 		specutil.TypeSpec(TypeReal, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
-		specutil.TypeSpec(TypeTimestamp),
-		specutil.TypeSpec(TypeDate),
-		specutil.TypeSpec(TypeTime),
-		specutil.TypeSpec(TypeDateTime),
-		specutil.TypeSpec(TypeYear),
+		specutil.TypeSpec(TypeTimestamp, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
+		specutil.TypeSpec(TypeDate, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
+		specutil.TypeSpec(TypeTime, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
+		specutil.TypeSpec(TypeDateTime, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
+		specutil.TypeSpec(TypeYear, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}),
 		specutil.TypeSpec(TypeVarchar, specutil.SizeTypeAttr(true)),
 		specutil.TypeSpec(TypeChar, specutil.SizeTypeAttr(false)),
 		specutil.TypeSpec(TypeVarBinary, specutil.SizeTypeAttr(false)),
diff --git a/sql/postgres/convert.go b/sql/postgres/convert.go
index be045698..5e3fba04 100644
--- a/sql/postgres/convert.go
+++ b/sql/postgres/convert.go
@@ -184,14 +184,15 @@ func arrayType(t string) (string, bool) {
 
 // columnDesc represents a column descriptor.
 type columnDesc struct {
-	typ       string
-	size      int64
-	udt       string
-	precision int64
-	scale     int64
-	typtype   string
-	typid     int64
-	parts     []string
+	typ           string
+	size          int64
+	udt           string
+	precision     int64
+	timePrecision int64
+	scale         int64
+	typtype       string
+	typid         int64
+	parts         []string
 }
 
 func parseColumn(s string) (*columnDesc, error) {
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index 255896a8..94655bd1 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -173,10 +173,13 @@ func (i *inspect) columns(ctx context.Context, t *schema.Table) error {
 // addColumn scans the current row and adds a new column from it to the table.
 func (i *inspect) addColumn(t *schema.Table, rows *sql.Rows) error {
 	var (
-		typid, maxlen, precision, scale, seqstart, seqinc                                              sql.NullInt64
+		typid, maxlen, precision, timeprecision, scale, seqstart, seqinc                               sql.NullInt64
 		name, typ, nullable, defaults, udt, identity, generation, charset, collation, comment, typtype sql.NullString
 	)
-	if err := rows.Scan(&name, &typ, &nullable, &defaults, &maxlen, &precision, &scale, &charset, &collation, &udt, &identity, &seqstart, &seqinc, &generation, &comment, &typtype, &typid); err != nil {
+	if err := rows.Scan(
+		&name, &typ, &nullable, &defaults, &maxlen, &precision, &timeprecision, &scale, &charset,
+		&collation, &udt, &identity, &seqstart, &seqinc, &generation, &comment, &typtype, &typid,
+	); err != nil {
 		return err
 	}
 	c := &schema.Column{
@@ -187,13 +190,14 @@ func (i *inspect) addColumn(t *schema.Table, rows *sql.Rows) error {
 		},
 	}
 	c.Type.Type = columnType(&columnDesc{
-		typ:       typ.String,
-		size:      maxlen.Int64,
-		udt:       udt.String,
-		precision: precision.Int64,
-		scale:     scale.Int64,
-		typtype:   typtype.String,
-		typid:     typid.Int64,
+		typ:           typ.String,
+		size:          maxlen.Int64,
+		udt:           udt.String,
+		precision:     precision.Int64,
+		timePrecision: timeprecision.Int64,
+		scale:         scale.Int64,
+		typtype:       typtype.String,
+		typid:         typid.Int64,
 	})
 	if sqlx.ValidString(defaults) {
 		c.Default = defaultExpr(c, defaults.String)
@@ -247,7 +251,7 @@ func columnType(c *columnDesc) schema.Type {
 		typ = &schema.SpatialType{T: t}
 	case TypeDate, TypeTime, TypeTimeWTZ, TypeTimeWOTZ,
 		TypeTimestamp, TypeTimestampTZ, TypeTimestampWTZ, TypeTimestampWOTZ:
-		typ = &schema.TimeType{T: t}
+		typ = &schema.TimeType{T: t, Precision: int(c.timePrecision)}
 	case TypeInterval:
 		// TODO: get 'interval_type' from query above before implementing.
 		typ = &schema.UnsupportedType{T: t}
@@ -755,6 +759,7 @@ SELECT
 	t1.column_default,
 	t1.character_maximum_length,
 	t1.numeric_precision,
+	t1.datetime_precision,
 	t1.numeric_scale,
 	t1.character_set_name,
 	t1.collation_name,
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index d29f458d..c695e56a 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -339,6 +339,7 @@ var TypeRegistry = specutil.NewRegistry(
 		specutil.TypeSpec(TypeTime),
 		specutil.AliasTypeSpec("time_with_time_zone", TypeTimeWTZ),
 		specutil.AliasTypeSpec("time_without_time_zone", TypeTimeWOTZ),
+		specutil.TypeSpec(TypeTimestampTZ),
 		specutil.TypeSpec(TypeTimestamp),
 		specutil.AliasTypeSpec("timestamp_with_time_zone", TypeTimestampWTZ),
 		specutil.AliasTypeSpec("timestamp_without_time_zone", TypeTimestampWOTZ),
diff --git a/sql/schema/dsl.go b/sql/schema/dsl.go
index 378b9f25..ba9acb7c 100644
--- a/sql/schema/dsl.go
+++ b/sql/schema/dsl.go
@@ -355,10 +355,23 @@ func NewNullFloatColumn(name, typ string, opts ...FloatOption) *Column {
 		SetNull(true)
 }
 
+// TimeOption allows configuring TimeType using functional options.
+type TimeOption func(*TimeType)
+
+// TimePrecision configures the precision of the time type.
+func TimePrecision(precision int) TimeOption {
+	return func(b *TimeType) {
+		b.Precision = precision
+	}
+}
+
 // NewTimeColumn creates a new TimeType column.
-func NewTimeColumn(name, typ string) *Column {
-	return NewColumn(name).
-		SetType(&TimeType{T: typ})
+func NewTimeColumn(name, typ string, opts ...TimeOption) *Column {
+	t := &TimeType{T: typ}
+	for _, opt := range opts {
+		opt(t)
+	}
+	return NewColumn(name).SetType(t)
 }
 
 // NewNullTimeColumn creates a new nullable TimeType column.
diff --git a/sql/schema/schema.go b/sql/schema/schema.go
index 44bc6d78..14451cb2 100644
--- a/sql/schema/schema.go
+++ b/sql/schema/schema.go
@@ -225,7 +225,8 @@ type (
 
 	// TimeType represents a date/time type.
 	TimeType struct {
-		T string
+		T         string
+		Precision int
 	}
 
 	// JSONType represents a JSON type.
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index 80dc9c4d..9207e032 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -189,7 +189,7 @@ func (i *inspect) indexes(ctx context.Context, t *schema.Table) error {
 		return fmt.Errorf("sqlite: scan %q indexes: %w", t.Name, err)
 	}
 	for _, idx := range t.Indexes {
-		if err := i.indexColumns(ctx, t, idx); err != nil {
+		if err := i.indexInfo(ctx, t, idx); err != nil {
 			return err
 		}
 	}
@@ -233,33 +233,36 @@ func (i *inspect) addIndexes(t *schema.Table, rows *sql.Rows) error {
 	return nil
 }
 
-func (i *inspect) indexColumns(ctx context.Context, t *schema.Table, idx *schema.Index) error {
+func (i *inspect) indexInfo(ctx context.Context, t *schema.Table, idx *schema.Index) error {
 	rows, err := i.QueryContext(ctx, fmt.Sprintf(indexColumnsQuery, idx.Name))
 	if err != nil {
 		return fmt.Errorf("sqlite: querying %q indexes: %w", t.Name, err)
 	}
 	defer rows.Close()
 	for rows.Next() {
-		var name sql.NullString
-		if err := rows.Scan(&name); err != nil {
+		var (
+			desc sql.NullBool
+			name sql.NullString
+		)
+		if err := rows.Scan(&name, &desc); err != nil {
 			return fmt.Errorf("sqlite: scanning index names: %w", err)
 		}
+		part := &schema.IndexPart{SeqNo: len(idx.Parts) + 1}
 		switch c, ok := t.Column(name.String); {
 		case ok:
-			idx.Parts = append(idx.Parts, &schema.IndexPart{
-				SeqNo: len(idx.Parts) + 1,
-				C:     c,
-			})
+			part.C = c
 		// NULL name indicates that the index-part is an expression and we
 		// should extract it from the `CREATE INDEX` statement (not supported atm).
 		case !sqlx.ValidString(name):
-			idx.Parts = append(idx.Parts, &schema.IndexPart{
-				SeqNo: len(idx.Parts) + 1,
-				X:     &schema.RawExpr{X: "<unsupported>"},
-			})
+			part.X = &schema.RawExpr{X: "<unsupported>"}
 		default:
 			return fmt.Errorf("sqlite: column %q was not found for index %q", name.String, idx.Name)
 		}
+		if desc.Bool {
+			// The default sort order is "ASC".
+			part.Attrs = append(part.Attrs, &IndexOrder{Desc: desc.Bool})
+		}
+		idx.Parts = append(idx.Parts, part)
 	}
 	return nil
 }
@@ -441,6 +444,13 @@ type (
 		P string
 	}
 
+	// IndexOrder describes the sort order of an index.
+	// See: https://www.sqlite.org/lang_createindex.html#descending_indexes
+	IndexOrder struct {
+		schema.Attr
+		Desc bool
+	}
+
 	// IndexOrigin describes how the index was created.
 	// See: https://www.sqlite.org/pragma.html#pragma_index_list
 	IndexOrigin struct {
@@ -663,7 +673,7 @@ const (
 	// Query to list table indexes.
 	indexesQuery = "SELECT `il`.`name`, `il`.`unique`, `il`.`origin`, `il`.`partial`, `m`.`sql` FROM pragma_index_list('%s') AS il JOIN sqlite_master AS m ON il.name = m.name"
 	// Query to list index columns.
-	indexColumnsQuery = "SELECT name FROM pragma_index_info('%s') ORDER BY seqno"
+	indexColumnsQuery = "SELECT name, desc FROM pragma_index_xinfo('%s') WHERE key = 1 ORDER BY seqno"
 	// Query to list table foreign-keys.
 	fksQuery = "SELECT `id`, `from`, `to`, `table`, `on_update`, `on_delete` FROM pragma_foreign_key_list('%s') ORDER BY id, seq"
 )


Test patch:
diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go
index 64fa948b..bd870160 100644
--- a/internal/integration/integration_test.go
+++ b/internal/integration/integration_test.go
@@ -4,25 +4,18 @@ import (
 	"bytes"
 	"context"
 	"database/sql"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"path/filepath"
-	"regexp"
-	"strings"
 	"testing"
 	"time"
 
 	"ariga.io/atlas/schema/schemaspec"
-	"ariga.io/atlas/sql/mysql"
 	"ariga.io/atlas/sql/schema"
 
 	entsql "entgo.io/ent/dialect/sql"
 	"entgo.io/ent/entc/integration/ent"
-	"github.com/pkg/diff"
-	"github.com/rogpeppe/go-internal/testscript"
 	"github.com/stretchr/testify/require"
 )
 
@@ -322,109 +315,3 @@ func sanity(c *ent.Client) {
 		SetInfo(c.GroupInfo.Create().SetDesc("desc").SaveX(ctx)).
 		SaveX(ctx)
 }
-
-func TestMySQLScript(t *testing.T) {
-	myRun(t, func(t *myTest) {
-		var (
-			attrs            = t.defaultAttrs()
-			charset, collate = attrs[0].(*schema.Charset).V, attrs[1].(*schema.Collation).V
-		)
-		testscript.Run(t.T, testscript.Params{
-			Dir: "testdata/mysql",
-			Setup: func(env *testscript.Env) error {
-				ctx := context.Background()
-				conn, err := t.db.Conn(ctx)
-				if err != nil {
-					return err
-				}
-				name := strings.ReplaceAll(filepath.Base(env.WorkDir), "-", "_")
-				env.Setenv("db", name)
-				if _, err := conn.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", name)); err != nil {
-					return err
-				}
-				if _, err := conn.ExecContext(ctx, fmt.Sprintf("USE `%s`", name)); err != nil {
-					return err
-				}
-				env.Defer(func() {
-					if _, err := conn.ExecContext(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", name)); err != nil {
-						t.Fatal(err)
-					}
-					if _, err := conn.ExecContext(ctx, "USE test"); err != nil {
-						t.Fatal(err)
-					}
-					if err := conn.Close(); err != nil {
-						t.Fatal(err)
-					}
-				})
-				return nil
-			},
-			Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
-				"apply": func(ts *testscript.TestScript, neg bool, args []string) {
-					var (
-						desired schema.Schema
-						f       = ts.ReadFile(args[0])
-						r       = strings.NewReplacer("$charset", charset, "$collate", collate, "$db", ts.Getenv("db"))
-					)
-					ts.Check(mysql.UnmarshalHCL([]byte(r.Replace(f)), &desired))
-					current, err := t.drv.InspectSchema(context.Background(), desired.Name, nil)
-					ts.Check(err)
-					changes, err := t.drv.SchemaDiff(current, &desired)
-					ts.Check(err)
-					switch err := t.drv.ApplyChanges(context.Background(), changes); {
-					case err != nil && !neg:
-						ts.Fatalf("apply changes: %v", err)
-					case err == nil && neg:
-						ts.Fatalf("unexpected apply success")
-					// If we expect to fail, and there's a specific error to compare.
-					case err != nil && len(args) == 2:
-						re, rerr := regexp.Compile(`(?m)` + args[1])
-						ts.Check(rerr)
-						if !re.MatchString(err.Error()) {
-							t.Fatalf("mismatched errors: %v != %s", err, args[1])
-						}
-					}
-				},
-				"exist": func(ts *testscript.TestScript, neg bool, args []string) {
-					for _, name := range args {
-						var b bool
-						if err := t.db.QueryRow("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", ts.Getenv("db"), name).Scan(&b); err != nil {
-							ts.Fatalf("failed query table existence %q: %v", name, err)
-						}
-						if !b != neg {
-							ts.Fatalf("table %q existence failed", name)
-						}
-					}
-				},
-				"cmpshow": func(ts *testscript.TestScript, neg bool, args []string) {
-					if len(args) < 2 {
-						ts.Fatalf("invalid number of args to 'cmpshow': %d", len(args))
-					}
-					var (
-						fname = args[len(args)-1]
-						stmts = make([]string, 0, len(args)-1)
-					)
-					for _, name := range args[:len(args)-1] {
-						var create string
-						if err := t.db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE `%s`.`%s`", ts.Getenv("db"), name)).Scan(&name, &create); err != nil {
-							ts.Fatalf("show table %q: %v", name, err)
-						}
-						// Trim the "table_options" if it was not requested explicitly.
-						stmts = append(stmts, create[:strings.LastIndexByte(create, ')')+1])
-					}
-
-					// Check if there is a file prefixed by database version (1.sql and <version>/1.sql).
-					if _, err := os.Stat(ts.MkAbs(filepath.Join(t.version, fname))); err == nil {
-						fname = filepath.Join(t.version, fname)
-					}
-					t1, t2 := strings.Join(stmts, "\n"), ts.ReadFile(fname)
-					if strings.TrimSpace(t1) == strings.TrimSpace(t2) {
-						return
-					}
-					var sb strings.Builder
-					ts.Check(diff.Text("show", fname, t1, t2, &sb))
-					ts.Fatalf(sb.String())
-				},
-			},
-		})
-	})
-}
diff --git a/internal/integration/mysql_test.go b/internal/integration/mysql_test.go
index d9acef87..da2254c5 100644
--- a/internal/integration/mysql_test.go
+++ b/internal/integration/mysql_test.go
@@ -8,14 +8,20 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
 	"strings"
 	"sync"
 	"testing"
 
 	"ariga.io/atlas/sql/mysql"
 	"ariga.io/atlas/sql/schema"
+
 	"entgo.io/ent/dialect"
 	_ "github.com/go-sql-driver/mysql"
+	"github.com/pkg/diff"
+	"github.com/rogpeppe/go-internal/testscript"
 	"github.com/stretchr/testify/require"
 )
 
@@ -594,44 +600,45 @@ func TestMySQL_Sanity(t *testing.T) {
 		ddl := `
 create table atlas_types_sanity
 (
-    tBit                 bit(10)              default b'100'            null,
-    tInt                 int(10)              default 4             not null,
-    tTinyInt             tinyint(10)          default 8                 null,
-    tSmallInt            smallint(10)         default 2                 null,
-    tMediumInt           mediumint(10)        default 11                null,
-    tBigInt              bigint(10)           default 4                 null,
-    tDecimal             decimal              default 4                 null,
-    tNumeric             numeric              default 4             not null,
-    tFloat               float(10, 0)         default 4                 null,
-    tDouble              double(10, 0)        default 4                 null,
-    tReal                double(10, 0)        default 4                 null,
-    tTimestamp           timestamp            default CURRENT_TIMESTAMP null,
-    tDate                date                                           null,
-    tTime                time                                           null,
-    tDateTime            datetime                                       null,
-    tYear                year                                           null,
-    tVarchar             varchar(10)          default 'Titan'           null,
-    tChar                char(25)             default 'Olimpia'     not null,
-    tVarBinary           varbinary(30)        default 'Titan'           null,
-    tBinary              binary(5)            default 'Titan'           null,
-    tBlob                blob(5)              default                   null,
-    tTinyBlob            tinyblob                                       null,
-    tMediumBlob          mediumblob           default                   null,
-    tLongBlob            longblob             default                   null,
-    tText                text(13)             default                   null,
-    tTinyText            tinytext             default                   null,
-    tMediumText          mediumtext           default                   null,
-    tLongText            longtext             default                   null,
-    tEnum                enum('a','b')        default                   null,
-    tSet                 set('a','b')         default                   null,
-    tGeometry            geometry             default                   null,
-    tPoint               point                default                   null,
-    tMultiPoint          multipoint           default                   null,
-    tLineString          linestring           default                   null,
-    tMultiLineString     multilinestring      default                   null,
-    tPolygon             polygon              default                   null,
-    tMultiPolygon        multipolygon         default                   null,
-    tGeometryCollection  geometrycollection   default                   null
+    tBit                 bit(10)              default b'100'               null,
+    tInt                 int(10)              default 4                not null,
+    tTinyInt             tinyint(10)          default 8                    null,
+    tSmallInt            smallint(10)         default 2                    null,
+    tMediumInt           mediumint(10)        default 11                   null,
+    tBigInt              bigint(10)           default 4                    null,
+    tDecimal             decimal              default 4                    null,
+    tNumeric             numeric              default 4                not null,
+    tFloat               float(10, 0)         default 4                    null,
+    tDouble              double(10, 0)        default 4                    null,
+    tReal                double(10, 0)        default 4                    null,
+    tTimestamp           timestamp            default CURRENT_TIMESTAMP    null,
+    tTimestampFraction   timestamp(6)         default CURRENT_TIMESTAMP(6) null,
+    tDate                date                                              null,
+    tTime                time                                              null,
+    tDateTime            datetime                                          null,
+    tYear                year                                              null,
+    tVarchar             varchar(10)          default 'Titan'              null,
+    tChar                char(25)             default 'Olimpia'        not null,
+    tVarBinary           varbinary(30)        default 'Titan'              null,
+    tBinary              binary(5)            default 'Titan'              null,
+    tBlob                blob(5)              default                      null,
+    tTinyBlob            tinyblob                                          null,
+    tMediumBlob          mediumblob           default                      null,
+    tLongBlob            longblob             default                      null,
+    tText                text(13)             default                      null,
+    tTinyText            tinytext             default                      null,
+    tMediumText          mediumtext           default                      null,
+    tLongText            longtext             default                      null,
+    tEnum                enum('a','b')        default                      null,
+    tSet                 set('a','b')         default                      null,
+    tGeometry            geometry             default                      null,
+    tPoint               point                default                      null,
+    tMultiPoint          multipoint           default                      null,
+    tLineString          linestring           default                      null,
+    tMultiLineString     multilinestring      default                      null,
+    tPolygon             polygon              default                      null,
+    tMultiPolygon        multipolygon         default                      null,
+    tGeometryCollection  geometrycollection   default                      null
 ) CHARSET = latin1 COLLATE latin1_swedish_ci;
 `
 		myRun(t, func(t *myTest) {
@@ -728,6 +735,19 @@ create table atlas_types_sanity
 							}(),
 						},
 					},
+					{
+						Name: "tTimestampFraction",
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "timestamp", Precision: 6},
+							Raw: "timestamp(6)", Null: true},
+						Default: &schema.RawExpr{
+							X: func() string {
+								if t.mariadb() {
+									return "current_timestamp(6)"
+								}
+								return "CURRENT_TIMESTAMP(6)"
+							}(),
+						},
+					},
 					{
 						Name: "tDate",
 						Type: &schema.ColumnType{Type: &schema.TimeType{T: "date"},
@@ -745,7 +765,7 @@ create table atlas_types_sanity
 					},
 					{
 						Name: "tYear",
-						Type: &schema.ColumnType{Type: &schema.TimeType{T: "year"},
+						Type: &schema.ColumnType{Type: &schema.TimeType{T: "year", Precision: t.intByVersion(map[string]int{"8": 0}, 4)},
 							Raw: t.valueByVersion(map[string]string{"8": "year"}, "year(4)"), Null: true},
 					},
 					{
@@ -1108,6 +1128,13 @@ func (t *myTest) valueByVersion(values map[string]string, defaults string) strin
 	return defaults
 }
 
+func (t *myTest) intByVersion(values map[string]int, defaults int) int {
+	if v, ok := values[t.version]; ok {
+		return v
+	}
+	return defaults
+}
+
 func (t *myTest) quoted(s string) string {
 	c := "\""
 	if t.mariadb() {
@@ -1188,3 +1215,137 @@ func rmCreateStmt(t *schema.Table) {
 		}
 	}
 }
+
+func TestMySQL_Script(t *testing.T) {
+	myRun(t, func(t *myTest) {
+		var (
+			attrs            = t.defaultAttrs()
+			charset, collate = attrs[0].(*schema.Charset).V, attrs[1].(*schema.Collation).V
+		)
+		testscript.Run(t.T, testscript.Params{
+			Dir: "testdata/mysql",
+			Setup: func(env *testscript.Env) error {
+				ctx := context.Background()
+				conn, err := t.db.Conn(ctx)
+				if err != nil {
+					return err
+				}
+				name := strings.ReplaceAll(filepath.Base(env.WorkDir), "-", "_")
+				env.Setenv("db", name)
+				env.Setenv("charset", charset)
+				env.Setenv("collate", collate)
+				if _, err := conn.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", name)); err != nil {
+					return err
+				}
+				if _, err := conn.ExecContext(ctx, fmt.Sprintf("USE `%s`", name)); err != nil {
+					return err
+				}
+				env.Defer(func() {
+					if _, err := conn.ExecContext(ctx, fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", name)); err != nil {
+						t.Fatal(err)
+					}
+					if _, err := conn.ExecContext(ctx, "USE test"); err != nil {
+						t.Fatal(err)
+					}
+					if err := conn.Close(); err != nil {
+						t.Fatal(err)
+					}
+				})
+				return nil
+			},
+			Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
+				"apply":   t.cmdApply,
+				"exist":   t.cmdExist,
+				"synced":  t.cmdSynced,
+				"cmpshow": t.cmdCmpShow,
+			},
+		})
+	})
+}
+
+func (t *myTest) cmdCmpShow(ts *testscript.TestScript, neg bool, args []string) {
+	if len(args) < 2 {
+		ts.Fatalf("invalid number of args to 'cmpshow': %d", len(args))
+	}
+	var (
+		fname = args[len(args)-1]
+		stmts = make([]string, 0, len(args)-1)
+	)
+	for _, name := range args[:len(args)-1] {
+		var create string
+		if err := t.db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE `%s`.`%s`", ts.Getenv("db"), name)).Scan(&name, &create); err != nil {
+			ts.Fatalf("show table %q: %v", name, err)
+		}
+		// Trim the "table_options" if it was not requested explicitly.
+		stmts = append(stmts, create[:strings.LastIndexByte(create, ')')+1])
+	}
+
+	// Check if there is a file prefixed by database version (1.sql and <version>/1.sql).
+	if _, err := os.Stat(ts.MkAbs(filepath.Join(t.version, fname))); err == nil {
+		fname = filepath.Join(t.version, fname)
+	}
+	t1, t2 := strings.Join(stmts, "\n"), ts.ReadFile(fname)
+	if strings.TrimSpace(t1) == strings.TrimSpace(t2) {
+		return
+	}
+	var sb strings.Builder
+	ts.Check(diff.Text("show", fname, t1, t2, &sb))
+	ts.Fatalf(sb.String())
+}
+
+func (t *myTest) cmdExist(ts *testscript.TestScript, neg bool, args []string) {
+	for _, name := range args {
+		var b bool
+		if err := t.db.QueryRow("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", ts.Getenv("db"), name).Scan(&b); err != nil {
+			ts.Fatalf("failed query table existence %q: %v", name, err)
+		}
+		if !b != neg {
+			ts.Fatalf("table %q existence failed", name)
+		}
+	}
+}
+
+func (t *myTest) cmdSynced(ts *testscript.TestScript, neg bool, args []string) {
+	switch changes := t.hclDiff(ts, args[0]); {
+	case len(changes) > 0 && !neg:
+		ts.Fatalf("expect no schema changes, but got: %d", len(changes))
+	case len(changes) == 0 && neg:
+		ts.Fatalf("expect schema changes, but there are none")
+	}
+}
+
+func (t *myTest) cmdApply(ts *testscript.TestScript, neg bool, args []string) {
+	changes := t.hclDiff(ts, args[0])
+	switch err := t.drv.ApplyChanges(context.Background(), changes); {
+	case err != nil && !neg:
+		ts.Fatalf("apply changes: %v", err)
+	case err == nil && neg:
+		ts.Fatalf("unexpected apply success")
+	// If we expect to fail, and there's a specific error to compare.
+	case err != nil && len(args) == 2:
+		re, rerr := regexp.Compile(`(?m)` + args[1])
+		ts.Check(rerr)
+		if !re.MatchString(err.Error()) {
+			t.Fatalf("mismatched errors: %v != %s", err, args[1])
+		}
+	// Apply passed. Make sure there is no drift.
+	case !neg:
+		if changes := t.hclDiff(ts, args[0]); len(changes) > 0 {
+			ts.Fatalf("unexpected schema changes: %d", len(changes))
+		}
+	}
+}
+
+func (t *myTest) hclDiff(ts *testscript.TestScript, name string) []schema.Change {
+	var (
+		desired schema.Schema
+		f       = ts.ReadFile(name)
+		r       = strings.NewReplacer("$charset", ts.Getenv("charset"), "$collate", ts.Getenv("collate"), "$db", ts.Getenv("db"))
+	)
+	ts.Check(mysql.UnmarshalHCL([]byte(r.Replace(f)), &desired))
+	current, err := t.drv.InspectSchema(context.Background(), desired.Name, nil)
+	ts.Check(err)
+	changes, err := t.drv.SchemaDiff(current, &desired)
+	ts.Check(err)
+	return changes
+}
diff --git a/internal/integration/postgres_test.go b/internal/integration/postgres_test.go
index c833ec29..f9fa5013 100644
--- a/internal/integration/postgres_test.go
+++ b/internal/integration/postgres_test.go
@@ -546,6 +546,7 @@ create table atlas_types_sanity
     "tTimestampTZ"         timestamptz                 default now()                                    null,
     "tTimestampWTZ"        timestamp with time zone    default now()                                    null,
     "tTimestampWOTZ"       timestamp without time zone default now()                                    null,
+    "tTimestampPrec"       timestamp(4)                default now()                                    null,
     "tDouble"              double precision            default 0                                        null,
     "tReal"                real                        default 0                                        null,
     "tFloat8"              float8                      default 0                                        null,
@@ -716,37 +717,42 @@ create table atlas_types_sanity
 				},
 				{
 					Name:    "tTime",
-					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time without time zone"}, Raw: "time without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time without time zone", Precision: 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"}, Raw: "time with time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time with time zone", Precision: 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"}, Raw: "time without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "time without time zone", Precision: 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"}, Raw: "timestamp without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: 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"}, Raw: "timestamp with time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp with time zone", Precision: 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"}, Raw: "timestamp with time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp with time zone", Precision: 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"}, Raw: "timestamp without time zone", Null: true},
+					Type:    &schema.ColumnType{Type: &schema.TimeType{T: "timestamp without time zone", Precision: 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},
 					Default: &schema.RawExpr{X: "now()"},
 				},
 				{
diff --git a/internal/integration/testdata/mysql/add-drop-column.txt b/internal/integration/testdata/mysql/column-add-drop.txt
similarity index 100%
rename from internal/integration/testdata/mysql/add-drop-column.txt
rename to internal/integration/testdata/mysql/column-add-drop.txt
diff --git a/internal/integration/testdata/mysql/column-bool.txt b/internal/integration/testdata/mysql/column-bool.txt
new file mode 100644
index 00000000..fbb9e6d5
--- /dev/null
+++ b/internal/integration/testdata/mysql/column-bool.txt
@@ -0,0 +1,90 @@
+# Each test runs on a clean database.
+
+apply 1.hcl
+cmpshow users 1.sql
+
+# "bool", "boolean" and "tinyint(1)" are equal.
+synced 2.hcl
+
+# Changing "tinyint(1)" to "tinyint" should cause a schema change.
+apply 3.hcl
+cmpshow users 3.sql
+synced 3.hcl
+
+-- 1.hcl --
+schema "$db" {
+    charset = "$charset"
+    collation = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    column "a" {
+        type = bool
+    }
+    column "b" {
+        type = boolean
+    }
+    column "c" {
+        type = tinyint(1)
+    }
+}
+
+-- 1.sql --
+CREATE TABLE `users` (
+  `a` tinyint(1) NOT NULL,
+  `b` tinyint(1) NOT NULL,
+  `c` tinyint(1) NOT NULL
+)
+
+-- 2.hcl --
+schema "$db" {
+    charset = "$charset"
+    collation = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    column "a" {
+        type = boolean
+    }
+    column "b" {
+        type = tinyint(1)
+    }
+    column "c" {
+        type = bool
+    }
+}
+
+-- 3.hcl --
+schema "$db" {
+    charset = "$charset"
+    collation = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    column "a" {
+        type = boolean
+    }
+    column "b" {
+        type = tinyint
+    }
+    column "c" {
+        type = bool
+    }
+}
+
+-- 3.sql --
+CREATE TABLE `users` (
+  `a` tinyint(1) NOT NULL,
+  `b` tinyint(4) NOT NULL,
+  `c` tinyint(1) NOT NULL
+)
+
+-- 8/3.sql --
+CREATE TABLE `users` (
+  `a` tinyint(1) NOT NULL,
+  `b` tinyint NOT NULL,
+  `c` tinyint(1) NOT NULL
+)
diff --git a/internal/integration/testdata/mysql/add-foreign-key.txt b/internal/integration/testdata/mysql/foreign-key-add.txt
similarity index 100%
rename from internal/integration/testdata/mysql/add-foreign-key.txt
rename to internal/integration/testdata/mysql/foreign-key-add.txt
diff --git a/internal/integration/testdata/mysql/foreign-key-modify-action.txt b/internal/integration/testdata/mysql/foreign-key-modify-action.txt
new file mode 100644
index 00000000..84cd3068
--- /dev/null
+++ b/internal/integration/testdata/mysql/foreign-key-modify-action.txt
@@ -0,0 +1,133 @@
+# Each test runs on a clean database.
+
+# Apply schema "1.hcl" on fresh database.
+apply 1.hcl
+cmpshow users posts 1.sql
+
+apply 2.hcl
+cmpshow users posts 2.sql
+
+# Below files represent HCL and SQL. File names defined their index in
+# execution order. 1.hcl is executed first, 2.hcl executed second, etc.
+-- 1.hcl --
+schema "$db" {
+    charset = "$charset"
+    collation = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    column "id" {
+        type = int
+    }
+    primary_key {
+        columns = [table.users.column.id]
+    }
+}
+
+table "posts" {
+    schema = schema.$db
+    column "id" {
+        type = int
+    }
+    column "author_id" {
+        type = int
+        null = true
+    }
+    primary_key {
+        columns = [table.posts.column.id]
+    }
+    foreign_key "owner_id" {
+    	columns = [table.posts.column.author_id]
+    	ref_columns = [table.users.column.id]
+    	on_update = "SET NULL"
+    }
+}
+
+-- 1.sql --
+CREATE TABLE `users` (
+  `id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+)
+CREATE TABLE `posts` (
+  `id` int(11) NOT NULL,
+  `author_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `owner_id` (`author_id`),
+  CONSTRAINT `owner_id` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE SET NULL
+)
+
+-- 8/1.sql --
+CREATE TABLE `users` (
+  `id` int NOT NULL,
+  PRIMARY KEY (`id`)
+)
+CREATE TABLE `posts` (
+  `id` int NOT NULL,
+  `author_id` int DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `owner_id` (`author_id`),
+  CONSTRAINT `owner_id` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON UPDATE SET NULL
+)
+
+-- 2.hcl --
+schema "$db" {
+    charset = "$charset"
+    collation = "$collate"
+}
+
+table "users" {
+    schema = schema.$db
+    column "id" {
+        type = int
+    }
+    primary_key {
+        columns = [table.users.column.id]
+    }
+}
+
+table "posts" {
+    schema = schema.$db
+    column "id" {
+        type = int
+    }
+    column "author_id" {
+        type = int
+        null = true
+    }
+    primary_key {
+        columns = [table.posts.column.id]
+    }
+    foreign_key "owner_id" {
+    	columns = [table.posts.column.author_id]
+    	ref_columns = [table.users.column.id]
+    	on_update = "NO ACTION"
+    	on_delete = "CASCADE"
+    }
+}
+
+-- 2.sql --
+CREATE TABLE `users` (
+  `id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+)
+CREATE TABLE `posts` (
+  `id` int(11) NOT NULL,
+  `author_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `owner_id` (`author_id`),
+  CONSTRAINT `owner_id` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
+)
+
+-- 8/2.sql --
+CREATE TABLE `users` (
+  `id` int NOT NULL,
+  PRIMARY KEY (`id`)
+)
+CREATE TABLE `posts` (
+  `id` int NOT NULL,
+  `author_id` int DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `owner_id` (`author_id`),
+  CONSTRAINT `owner_id` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
+)
\ No newline at end of file
diff --git a/sql/mysql/inspect_test.go b/sql/mysql/inspect_test.go
index 2f4586ed..2158c7c0 100644
--- a/sql/mysql/inspect_test.go
+++ b/sql/mysql/inspect_test.go
@@ -361,16 +361,17 @@ func TestDriver_InspectTable(t *testing.T) {
 				m.ExpectQuery(sqltest.Escape(columnsQuery)).
 					WithArgs("public", "users").
 					WillReturnRows(sqltest.Rows(`
-+-------------+-------------+-------------------+-------------+------------+-------------------+-----------------------------+--------------------+----------------+
-| column_name | column_type | column_comment    | is_nullable | column_key | column_default    | extra                       | character_set_name | collation_name |
-+-------------+-------------+-------------------+-------------+------------+-------------------+-----------------------------+--------------------+----------------+
-| c1          | date        |                   | NO          |            | NULL              |                             | NULL               | NULL           |
-| c2          | datetime    |                   | NO          |            | NULL              |                             | NULL               | NULL           |
-| c3          | time        |                   | NO          |            | NULL              |                             | NULL               | NULL           |
-| c4          | timestamp   |                   | NO          |            | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | NULL               | NULL           |
-| c5          | year(4)     |                   | NO          |            | NULL              |                             | NULL               | NULL           |
-| c6          | year        |                   | NO          |            | NULL              |                             | NULL               | NULL           |
-+-------------+-------------+-------------------+-------------+------------+-------------------+-----------------------------+--------------------+----------------+
++-------------+--------------+-------------------+-------------+------------+----------------------+--------------------------------+--------------------+----------------+
+| column_name | column_type  | column_comment    | is_nullable | column_key | column_default       | extra                          | character_set_name | collation_name |
++-------------+--------------+-------------------+-------------+------------+----------------------+--------------------------------+--------------------+----------------+
+| c1          | date         |                   | NO          |            | NULL                 |                                | NULL               | NULL           |
+| c2          | datetime     |                   | NO          |            | NULL                 |                                | NULL               | NULL           |
+| c3          | time         |                   | NO          |            | NULL                 |                                | NULL               | NULL           |
+| c4          | timestamp    |                   | NO          |            | CURRENT_TIMESTAMP    | on update CURRENT_TIMESTAMP    | NULL               | NULL           |
+| c5          | year(4)      |                   | NO          |            | NULL                 |                                | NULL               | NULL           |
+| c6          | year         |                   | NO          |            | NULL                 |                                | NULL               | NULL           |
+| c7          | timestamp(6) |                   | NO          |            | CURRENT_TIMESTAMP(6) | on update CURRENT_TIMESTAMP(6) | NULL               | NULL           |
++-------------+--------------+-------------------+-------------+------------+----------------------+--------------------------------+--------------------+----------------+
 `))
 				m.noIndexes()
 				m.noFKs()
@@ -383,8 +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: "on update current_timestamp"}}},
-					{Name: "c5", Type: &schema.ColumnType{Raw: "year(4)", Type: &schema.TimeType{T: "year"}}},
+					{Name: "c5", Type: &schema.ColumnType{Raw: "year(4)", Type: &schema.TimeType{T: "year", Precision: 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: "on update current_timestamp(6)"}}},
 				}, t.Columns)
 			},
 		},
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index 7a6af0ca..45c39566 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -441,6 +441,54 @@ schema "test" {
 	require.EqualValues(t, expected, string(buf))
 }
 
+func TestMarshalSpec_TimePrecision(t *testing.T) {
+	s := schema.New("test").
+		AddTables(
+			schema.NewTable("times").
+				AddColumns(
+					schema.NewTimeColumn("tTimeDef", TypeTime),
+					schema.NewTimeColumn("tTime", TypeTime, schema.TimePrecision(1)),
+					schema.NewTimeColumn("tDatetime", TypeDateTime, schema.TimePrecision(2)),
+					schema.NewTimeColumn("tTimestamp", TypeTimestamp, schema.TimePrecision(3)),
+					schema.NewTimeColumn("tDate", TypeDate, schema.TimePrecision(2)),
+					schema.NewTimeColumn("tYear", TypeYear, schema.TimePrecision(2)),
+				),
+		)
+	buf, err := MarshalSpec(s, hclState)
+	require.NoError(t, err)
+	const expected = `table "times" {
+  schema = schema.test
+  column "tTimeDef" {
+    null = false
+    type = time
+  }
+  column "tTime" {
+    null = false
+    type = time(1)
+  }
+  column "tDatetime" {
+    null = false
+    type = datetime(2)
+  }
+  column "tTimestamp" {
+    null = false
+    type = timestamp(3)
+  }
+  column "tDate" {
+    null = false
+    type = date(2)
+  }
+  column "tYear" {
+    null = false
+    type = year(2)
+  }
+}
+schema "test" {
+}
+`
+	require.EqualValues(t, expected, string(buf))
+}
+
 func TestTypes(t *testing.T) {
 	tests := []struct {
 		typeExpr  string
@@ -600,22 +648,42 @@ func TestTypes(t *testing.T) {
 			typeExpr: "timestamp",
 			expected: &schema.TimeType{T: TypeTimestamp},
 		},
+		{
+			typeExpr: "timestamp(6)",
+			expected: &schema.TimeType{T: TypeTimestamp, Precision: 6},
+		},
 		{
 			typeExpr: "date",
 			expected: &schema.TimeType{T: TypeDate},
 		},
+		{
+			typeExpr: "date(2)",
+			expected: &schema.TimeType{T: TypeDate, Precision: 2},
+		},
 		{
 			typeExpr: "time",
 			expected: &schema.TimeType{T: TypeTime},
 		},
+		{
+			typeExpr: "time(6)",
+			expected: &schema.TimeType{T: TypeTime, Precision: 6},
+		},
 		{
 			typeExpr: "datetime",
 			expected: &schema.TimeType{T: TypeDateTime},
 		},
+		{
+			typeExpr: "datetime(6)",
+			expected: &schema.TimeType{T: TypeDateTime, Precision: 6},
+		},
 		{
 			typeExpr: "year",
 			expected: &schema.TimeType{T: TypeYear},
 		},
+		{
+			typeExpr: "year(2)",
+			expected: &schema.TimeType{T: TypeYear, Precision: 2},
+		},
 		{
 			typeExpr: "varchar(10)",
 			expected: &schema.StringType{T: TypeVarchar, Size: 10},
@@ -704,6 +772,14 @@ func TestTypes(t *testing.T) {
 			typeExpr: "geometrycollection",
 			expected: &schema.SpatialType{T: TypeGeometryCollection},
 		},
+		{
+			typeExpr: "bool",
+			expected: &schema.BoolType{T: TypeBool},
+		},
+		{
+			typeExpr: "boolean",
+			expected: &schema.BoolType{T: TypeBool},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.typeExpr, func(t *testing.T) {
diff --git a/sql/postgres/inspect_test.go b/sql/postgres/inspect_test.go
index 641f99be..b76e1063 100644
--- a/sql/postgres/inspect_test.go
+++ b/sql/postgres/inspect_test.go
@@ -58,34 +58,37 @@ func TestDriver_InspectTable(t *testing.T) {
 				m.ExpectQuery(sqltest.Escape(columnsQuery)).
 					WithArgs("public", "users").
 					WillReturnRows(sqltest.Rows(`
- column_name |      data_type      | is_nullable |         column_default          | character_maximum_length | numeric_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment | identity_generation | comment | typtype |  oid  
--------------+---------------------+-------------+---------------------------------+--------------------------+-------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+---------------------+---------+---------+-------
- id          | bigint              | NO          |                                 |                          |                64 |             0 |                    |                | int8     | YES         |      100       |          1         |    BY DEFAULT       |         | b       |    20
- rank        | integer             | YES         |                                 |                          |                32 |             0 |                    |                | int4     | NO          |                |                    |                     | rank    | b       |    23
- c1          | smallint            | NO          |           1000                  |                          |                16 |             0 |                    |                | int2     | NO          |                |                    |                     |         | b       |    21
- c2          | bit                 | NO          |                                 |                        1 |                   |               |                    |                | bit      | NO          |                |                    |                     |         | b       |  1560
- c3          | bit varying         | NO          |                                 |                       10 |                   |               |                    |                | varbit   | NO          |                |                    |                     |         | b       |  1562
- c4          | boolean             | NO          |                                 |                          |                   |               |                    |                | bool     | NO          |                |                    |                     |         | b       |    16
- c5          | bytea               | NO          |                                 |                          |                   |               |                    |                | bytea    | NO          |                |                    |                     |         | b       |    17
- c6          | character           | NO          |                                 |                      100 |                   |               |                    |                | bpchar   | NO          |                |                    |                     |         | b       |  1042
- c7          | character varying   | NO          |                                 |                          |                   |               |                    |                | varchar  | NO          |                |                    |                     |         | b       |  1043
- c8          | cidr                | NO          |                                 |                          |                   |               |                    |                | cidr     | NO          |                |                    |                     |         | b       |   650
- c9          | circle              | NO          |                                 |                          |                   |               |                    |                | circle   | NO          |                |                    |                     |         | b       |   718
- c10         | date                | NO          |                                 |                          |                   |               |                    |                | date     | NO          |                |                    |                     |         | b       |  1082
- c11         | time with time zone | NO          |                                 |                          |                   |               |                    |                | timetz   | NO          |                |                    |                     |         | b       |  1266
- c12         | double precision    | NO          |                                 |                          |                53 |               |                    |                | float8   | NO          |                |                    |                     |         | b       |   701
- c13         | real                | NO          |           random()              |                          |                24 |               |                    |                | float4   | NO          |                |                    |                     |         | b       |   700
- c14         | json                | NO          |           '{}'::json            |                          |                   |               |                    |                | json     | NO          |                |                    |                     |         | b       |   114
- c15         | jsonb               | NO          |           '{}'::jsonb           |                          |                   |               |                    |                | jsonb    | NO          |                |                    |                     |         | b       |  3802
- c16         | money               | NO          |                                 |                          |                   |               |                    |                | money    | NO          |                |                    |                     |         | b       |   790
- c17         | numeric             | NO          |                                 |                          |                   |               |                    |                | numeric  | NO          |                |                    |                     |         | b       |  1700
- c18         | numeric             | NO          |                                 |                          |                 4 |             4 |                    |                | numeric  | NO          |                |                    |                     |         | b       |  1700
- c19         | integer             | NO          | nextval('t1_c19_seq'::regclass) |                          |                32 |             0 |                    |                | int4     | NO          |                |                    |                     |         | b       |    23
- c20         | uuid                | NO          |                                 |                          |                   |               |                    |                | uuid     | NO          |                |                    |                     |         | b       |  2950
- c21         | xml                 | NO          |                                 |                          |                   |               |                    |                | xml      | NO          |                |                    |                     |         | b       |   142
- c22         | ARRAY               | YES         |                                 |                          |                   |               |                    |                | _int4    | NO          |                |                    |                     |         | b       |  1007
- c23         | USER-DEFINED        | YES         |                                 |                          |                   |               |                    |                | ltree    | NO          |                |                    |                     |         | b       | 16535
- c24         | USER-DEFINED        | NO          |                                 |                          |                   |               |                    |                | state    | NO          |                |                    |                     |         | e       | 16774
+ column_name |          data_type          | is_nullable |         column_default          | character_maximum_length | numeric_precision | datetime_precision | numeric_scale | character_set_name | collation_name |  udt_name   | is_identity | identity_start | identity_increment | identity_generation | comment | typtype |  oid  
+-------------+-----------------------------+-------------+---------------------------------+--------------------------+-------------------+--------------------+---------------+--------------------+----------------+-------------+-------------+----------------+--------------------+---------------------+---------+---------+-------
+ id          | bigint                      | NO          |                                 |                          |                64 |                    |             0 |                    |                | int8        | YES         |      100       |          1         |    BY DEFAULT       |         | b       |    20
+ rank        | integer                     | YES         |                                 |                          |                32 |                    |             0 |                    |                | int4        | NO          |                |                    |                     | rank    | b       |    23
+ c1          | smallint                    | NO          |           1000                  |                          |                16 |                    |             0 |                    |                | int2        | NO          |                |                    |                     |         | b       |    21
+ c2          | bit                         | NO          |                                 |                        1 |                   |                    |               |                    |                | bit         | NO          |                |                    |                     |         | b       |  1560
+ c3          | bit varying                 | NO          |                                 |                       10 |                   |                    |               |                    |                | varbit      | NO          |                |                    |                     |         | b       |  1562
+ c4          | boolean                     | NO          |                                 |                          |                   |                    |               |                    |                | bool        | NO          |                |                    |                     |         | b       |    16
+ c5          | bytea                       | NO          |                                 |                          |                   |                    |               |                    |                | bytea       | NO          |                |                    |                     |         | b       |    17
+ c6          | character                   | NO          |                                 |                      100 |                   |                    |               |                    |                | bpchar      | NO          |                |                    |                     |         | b       |  1042
+ c7          | character varying           | NO          |                                 |                          |                   |                    |               |                    |                | varchar     | NO          |                |                    |                     |         | b       |  1043
+ c8          | cidr                        | NO          |                                 |                          |                   |                    |               |                    |                | cidr        | NO          |                |                    |                     |         | b       |   650
+ c9          | circle                      | NO          |                                 |                          |                   |                    |               |                    |                | circle      | NO          |                |                    |                     |         | b       |   718
+ c10         | date                        | NO          |                                 |                          |                   |                    |               |                    |                | date        | NO          |                |                    |                     |         | b       |  1082
+ c11         | time with time zone         | NO          |                                 |                          |                   |                    |               |                    |                | timetz      | NO          |                |                    |                     |         | b       |  1266
+ c12         | double precision            | NO          |                                 |                          |                53 |                    |               |                    |                | float8      | NO          |                |                    |                     |         | b       |   701
+ c13         | real                        | NO          |           random()              |                          |                24 |                    |               |                    |                | float4      | NO          |                |                    |                     |         | b       |   700
+ c14         | json                        | NO          |           '{}'::json            |                          |                   |                    |               |                    |                | json        | NO          |                |                    |                     |         | b       |   114
+ c15         | jsonb                       | NO          |           '{}'::jsonb           |                          |                   |                    |               |                    |                | jsonb       | NO          |                |                    |                     |         | b       |  3802
+ c16         | money                       | NO          |                                 |                          |                   |                    |               |                    |                | money       | NO          |                |                    |                     |         | b       |   790
+ c17         | numeric                     | NO          |                                 |                          |                   |                    |               |                    |                | numeric     | NO          |                |                    |                     |         | b       |  1700
+ c18         | numeric                     | NO          |                                 |                          |                 4 |                    |             4 |                    |                | numeric     | NO          |                |                    |                     |         | b       |  1700
+ c19         | integer                     | NO          | nextval('t1_c19_seq'::regclass) |                          |                32 |                    |             0 |                    |                | int4        | NO          |                |                    |                     |         | b       |    23
+ c20         | uuid                        | NO          |                                 |                          |                   |                    |               |                    |                | uuid        | NO          |                |                    |                     |         | b       |  2950
+ c21         | xml                         | NO          |                                 |                          |                   |                    |               |                    |                | xml         | NO          |                |                    |                     |         | b       |   142
+ c22         | ARRAY                       | YES         |                                 |                          |                   |                    |               |                    |                | _int4       | NO          |                |                    |                     |         | b       |  1007
+ c23         | USER-DEFINED                | YES         |                                 |                          |                   |                    |               |                    |                | ltree       | NO          |                |                    |                     |         | b       | 16535
+ c24         | USER-DEFINED                | NO          |                                 |                          |                   |                    |               |                    |                | state       | NO          |                |                    |                     |         | e       | 16774
+ c25         | timestamp without time zone | NO          |            now()                |                          |                   |                  4 |               |                    |                | timestamp   | NO          |                |                    |                     |         | b       |  1114
+ c26         | timestamp with time zone    | NO          |                                 |                          |                   |                  6 |               |                    |                | timestamptz | NO          |                |                    |                     |         | b       |  1184
+ c27         | time without time zone      | NO          |                                 |                          |                   |                  6 |               |                    |                | time        | NO          |                |                    |                     |         | b       |  1266
 `))
 				m.ExpectQuery(sqltest.Escape(`SELECT enumtypid, enumlabel FROM pg_enum WHERE enumtypid IN ($1)`)).
 					WithArgs(16774).
@@ -129,6 +132,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}}},
 				}, t.Columns)
 			},
 		},
@@ -140,10 +146,10 @@ func TestDriver_InspectTable(t *testing.T) {
 				m.ExpectQuery(sqltest.Escape(columnsQuery)).
 					WithArgs("public", "users").
 					WillReturnRows(sqltest.Rows(`
- column_name |      data_type      | is_nullable |         column_default          | character_maximum_length | numeric_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment | identity_generation | comment | typtype |  oid
--------------+---------------------+-------------+---------------------------------+--------------------------+-------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+---------------------+---------+---------+-------
- id          | bigint              | NO          |                                 |                          |                64 |             0 |                    |                | int8     | NO          |                |                    |                     |         | b       |    20
- c1          | smallint            | NO          |                                 |                          |                16 |             0 |                    |                | int2     | NO          |                |                    |                     |         | b       |    21
+ column_name |      data_type      | is_nullable |         column_default          | character_maximum_length | numeric_precision | datetime_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment | identity_generation | comment | typtype |  oid
+-------------+---------------------+-------------+---------------------------------+--------------------------+-------------------+--------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+---------------------+---------+---------+-------
+ id          | bigint              | NO          |                                 |                          |                64 |                    |             0 |                    |                | int8     | NO          |                |                    |                     |         | b       |    20
+ c1          | smallint            | NO          |                                 |                          |                16 |                    |             0 |                    |                | int2     | NO          |                |                    |                     |         | b       |    21
 `))
 				m.ExpectQuery(sqltest.Escape(indexesQuery)).
 					WithArgs("public", "users").
@@ -197,11 +203,11 @@ func TestDriver_InspectTable(t *testing.T) {
 					WithArgs("public", "users").
 					WithArgs("public", "users").
 					WillReturnRows(sqltest.Rows(`
- column_name |      data_type      | is_nullable |         column_default          | character_maximum_length | numeric_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment | identity_generation | comment | typtype |  oid
--------------+---------------------+-------------+---------------------------------+--------------------------+-------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+---------------------+---------+---------+-------
- id          | integer             | NO          |                                 |                          |                32 |             0 |                    |                | int      | NO          |                |                    |                     |         | b       |    20
- oid         | integer             | NO          |                                 |                          |                32 |             0 |                    |                | int      | NO          |                |                    |                     |         | b       |    21
- uid         | integer             | NO          |                                 |                          |                32 |             0 |                    |                | int      | NO          |                |                    |                     |         | b       |    21
+ column_name |      data_type      | is_nullable |         column_default          | character_maximum_length | numeric_precision | datetime_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment | identity_generation | comment | typtype |  oid
+-------------+---------------------+-------------+---------------------------------+--------------------------+-------------------+--------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+---------------------+---------+---------+-------
+ id          | integer             | NO          |                                 |                          |                32 |                    |             0 |                    |                | int      | NO          |                |                    |                     |         | b       |    20
+ oid         | integer             | NO          |                                 |                          |                32 |                    |             0 |                    |                | int      | NO          |                |                    |                     |         | b       |    21
+ uid         | integer             | NO          |                                 |                          |                32 |                    |             0 |                    |                | int      | NO          |                |                    |                     |         | b       |    21
 `))
 				m.noIndexes()
 				m.ExpectQuery(sqltest.Escape(fksQuery)).
@@ -247,11 +253,11 @@ func TestDriver_InspectTable(t *testing.T) {
 					WithArgs("public", "users").
 					WithArgs("public", "users").
 					WillReturnRows(sqltest.Rows(`
- column_name | data_type | is_nullable | column_default | character_maximum_length | numeric_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment | identity_generation | comment | typtype | oid
--------------+-----------+-------------+----------------+--------------------------+-------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+---------------------+---------+---------+-----
- c1          | integer   | NO          |                |                          |                32 |             0 |                    |                | int4     | NO          |                |                    |                     |         | b       |  23
- c2          | integer   | NO          |                |                          |                32 |             0 |                    |                | int4     | NO          |                |                    |                     |         | b       |  23
- c3          | integer   | NO          |                |                          |                32 |             0 |                    |                | int4     | NO          |                |                    |                     |         | b       |  23
+ column_name | data_type | is_nullable | column_default | character_maximum_length | numeric_precision | datetime_precision | numeric_scale | character_set_name | collation_name | udt_name | is_identity | identity_start | identity_increment | identity_generation | comment | typtype | oid
+-------------+-----------+-------------+----------------+--------------------------+-------------------+--------------------+---------------+--------------------+----------------+----------+-------------+----------------+--------------------+---------------------+---------+---------+-----
+ c1          | integer   | NO          |                |                          |                32 |                    |             0 |                    |                | int4     | NO          |                |                    |                     |         | b       |  23
+ c2          | integer   | NO          |                |                          |                32 |                    |             0 |                    |                | int4     | NO          |                |                    |                     |         | b       |  23
+ c3          | integer   | NO          |                |                          |                32 |                    |             0 |                    |                | int4     | NO          |                |                    |                     |         | b       |  23
 `))
 				m.noIndexes()
 				m.noFKs()
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index 694af026..65c9f4e8 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -232,7 +232,7 @@ enum "account_type" {
 }
 
 func TestTypes(t *testing.T) {
-	// TODO(rotemtam): enum, timestamptz, interval
+	// TODO(rotemtam) interval
 	for _, tt := range []struct {
 		typeExpr string
 		expected schema.Type
@@ -366,6 +366,10 @@ func TestTypes(t *testing.T) {
 			typeExpr: "timestamp",
 			expected: &schema.TimeType{T: TypeTimestamp},
 		},
+		{
+			typeExpr: "timestamptz",
+			expected: &schema.TimeType{T: TypeTimestampTZ},
+		},
 		{
 			typeExpr: "timestamp_with_time_zone",
 			expected: &schema.TimeType{T: TypeTimestampWTZ},
diff --git a/sql/schema/dsl_test.go b/sql/schema/dsl_test.go
index 894ed960..7c7b1fbc 100644
--- a/sql/schema/dsl_test.go
+++ b/sql/schema/dsl_test.go
@@ -42,6 +42,7 @@ func TestSchema_AddTables(t *testing.T) {
 		schema.NewIntColumn("id", "int"),
 		schema.NewBoolColumn("active", "boolean"),
 		schema.NewNullStringColumn("name", "varchar", schema.StringSize(255)),
+		schema.NewTimeColumn("registered_at", "timestamp", schema.TimePrecision(6)),
 	}
 	users := schema.NewTable("users").
 		AddColumns(userColumns...).
@@ -83,6 +84,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}}},
 				},
 			}
 			s.Tables = append(s.Tables, users)
diff --git a/sql/sqlite/inspect_test.go b/sql/sqlite/inspect_test.go
index fadd14e4..a516cf73 100644
--- a/sql/sqlite/inspect_test.go
+++ b/sql/sqlite/inspect_test.go
@@ -105,17 +105,17 @@ func TestDriver_InspectTable(t *testing.T) {
 `))
 				m.ExpectQuery(sqltest.Escape(fmt.Sprintf(indexColumnsQuery, "c1u"))).
 					WillReturnRows(sqltest.Rows(`
- name 
-------
- c1   
- c2
+ name  |   desc |
+-------+--------+
+ c1   |  1      |
+ c2   |  0      |
 `))
 				m.ExpectQuery(sqltest.Escape(fmt.Sprintf(indexColumnsQuery, "c1_c2"))).
 					WillReturnRows(sqltest.Rows(`
- name 
-------
- c1   
- nil
+ name  |   desc |     
+-------+--------+     
+ c1    |  0     |     
+ nil   |  0     |     
 `))
 				m.noFKs("users")
 			},
@@ -131,7 +131,7 @@ func TestDriver_InspectTable(t *testing.T) {
 						Unique: true,
 						Table:  t,
 						Parts: []*schema.IndexPart{
-							{SeqNo: 1, C: columns[0]},
+							{SeqNo: 1, C: columns[0], Attrs: []schema.Attr{&IndexOrder{Desc: true}}},
 							{SeqNo: 2, C: columns[1]},
 						},
 						Attrs: []schema.Attr{