Repository: ariga/atlas
Pull request #945 (2022-07-14 12:19:21)

sql/postgres: support creating and deleting serial columns
Next PRs will add support for modifying serial columns.
Add sequence, drop sequence and change the underlying type


Patch:
diff --git a/internal/integration/go.mod b/internal/integration/go.mod
index f2efa608..ba97011b 100644
--- a/internal/integration/go.mod
+++ b/internal/integration/go.mod
@@ -7,8 +7,8 @@ replace ariga.io/atlas => ../../
 replace ariga.io/atlas/cmd/atlas => ../../cmd/atlas
 
 require (
-	ariga.io/atlas v0.4.3
-	entgo.io/ent v0.10.2-0.20220502113020-4ac82f5bb3f0
+	ariga.io/atlas v0.5.0
+	entgo.io/ent v0.11.1
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/hashicorp/hcl/v2 v2.10.1
 	github.com/lib/pq v1.10.5
@@ -23,15 +23,25 @@ require (
 	ariga.io/atlas/cmd/atlas v0.0.0-00010101000000-000000000000 // indirect
 	github.com/agext/levenshtein v1.2.3 // indirect
 	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
+	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/fatih/color v1.13.0 // indirect
 	github.com/go-openapi/inflect v0.19.0 // indirect
 	github.com/google/go-cmp v0.5.6 // indirect
 	github.com/google/uuid v1.3.0 // indirect
+	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/kr/pretty v0.2.1 // indirect
+	github.com/manifoldco/promptui v0.9.0 // indirect
+	github.com/mattn/go-colorable v0.1.12 // indirect
+	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/go-wordwrap v1.0.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/sergi/go-diff v1.2.0 // indirect
+	github.com/spf13/cobra v1.5.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
 	golang.org/x/mod v0.5.1 // indirect
+	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
 	golang.org/x/text v0.3.7 // indirect
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/errgo.v2 v2.1.0 // indirect
diff --git a/internal/integration/go.sum b/internal/integration/go.sum
index 0926bec2..a2ffc662 100644
--- a/internal/integration/go.sum
+++ b/internal/integration/go.sum
@@ -1,5 +1,7 @@
 entgo.io/ent v0.10.2-0.20220502113020-4ac82f5bb3f0 h1:qHA4+ANAzDj6BcDLxNgZuzKxFre/RI9r5wwsI2O+1M4=
 entgo.io/ent v0.10.2-0.20220502113020-4ac82f5bb3f0/go.mod h1:Zh61BPvB+cL6VWEyN8f1YoDacrMjQf2KDlDeX26xq2k=
+entgo.io/ent v0.11.1 h1:im67R+2W3Nee2bNS2YnoYz8oAF0Qz4AOlIvKRIAEISY=
+entgo.io/ent v0.11.1/go.mod h1:X5b1YfMayrRTgKGO//8IqpL7XJx0uqdeReEkxNpXROA=
 github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
 github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
 github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
@@ -8,11 +10,16 @@ github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:o
 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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 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-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
@@ -29,6 +36,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/hashicorp/hcl/v2 v2.10.1 h1:h4Xx4fsrRE26ohAk/1iGF/JBqRQbyUqu5Lvj60U54ys=
 github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -40,11 +49,17 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LE
 github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
 github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
+github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
 github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
 github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
@@ -54,12 +69,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 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.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
+github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
+github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
 github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -82,10 +101,16 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 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/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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=
@@ -105,6 +130,7 @@ gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/sql/postgres/crdb.go b/sql/postgres/crdb.go
index 61e76059..37b65065 100644
--- a/sql/postgres/crdb.go
+++ b/sql/postgres/crdb.go
@@ -42,12 +42,11 @@ func (i *crdbInspect) patchSchema(s *schema.Schema) {
 			if !ok {
 				continue
 			}
+			c.Default = nil
 			if g := strings.ToUpper(id.Generation); strings.Contains(g, "ALWAYS") {
 				id.Generation = "ALWAYS"
-				c.Default = nil
 			} else if strings.Contains(g, "BY DEFAULT") {
 				id.Generation = "BY DEFAULT"
-				c.Default = nil
 			}
 			schema.ReplaceOrAppend(&c.Attrs, id)
 		}
@@ -84,13 +83,14 @@ func (cd *crdbDiff) Normalize(from, to *schema.Table) error {
 func (cd *crdbDiff) ColumnChange(fromT *schema.Table, from, to *schema.Column) (schema.ChangeKind, error) {
 	// All serial types in Cockroach are implemented as bigint.
 	// See: https://www.cockroachlabs.com/docs/stable/serial.html#generated-values-for-mode-sql_sequence-and-sql_sequence_cached.
-	if s, ok := to.Type.Type.(*SerialType); ok {
-		to.Type.Type = &schema.IntegerType{
-			T: TypeBigInt,
+	for _, c := range []*schema.Column{from, to} {
+		if _, ok := c.Type.Type.(*SerialType); ok {
+			c.Type.Type = &schema.IntegerType{
+				T: TypeBigInt,
+			}
+			to.Default = nil
+			from.Default = nil
 		}
-		defer func() {
-			to.Type.Type = s
-		}()
 	}
 	return cd.diff.ColumnChange(fromT, from, to)
 }
diff --git a/sql/postgres/diff.go b/sql/postgres/diff.go
index f600c399..ed1d9877 100644
--- a/sql/postgres/diff.go
+++ b/sql/postgres/diff.go
@@ -196,32 +196,14 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 	if fromT == nil || toT == nil {
 		return false, fmt.Errorf("postgres: missing type information for column %q", from.Name)
 	}
-	// Skip checking SERIAL types as they are not real types in the database, but more
-	// like a convenience way for creating integers types with AUTO_INCREMENT property.
-	if s, ok := to.Type.Type.(*SerialType); ok {
-		i, ok := from.Type.Type.(*schema.IntegerType)
-		if !ok {
-			return true, nil
-		}
-		var it string
-		switch s.T {
-		case TypeSmallSerial:
-			it = TypeSmallInt
-		case TypeSerial:
-			it = TypeInteger
-		case TypeBigSerial:
-			it = TypeBigInt
-		}
-		return i.T != it, nil
-	}
 	if reflect.TypeOf(fromT) != reflect.TypeOf(toT) {
 		return true, nil
 	}
 	var changed bool
 	switch fromT := fromT.(type) {
-	case *schema.BinaryType, *schema.BoolType, *schema.DecimalType, *schema.FloatType,
-		*schema.IntegerType, *schema.JSONType, *schema.SpatialType, *schema.StringType,
-		*schema.TimeType, *BitType, *IntervalType, *NetworkType, *UserDefinedType:
+	case *schema.BinaryType, *BitType, *schema.BoolType, *schema.DecimalType, *schema.FloatType,
+		*IntervalType, *schema.IntegerType, *schema.JSONType, *SerialType, *schema.SpatialType,
+		*schema.StringType, *schema.TimeType, *NetworkType, *UserDefinedType:
 		t1, err := FormatType(toT)
 		if err != nil {
 			return false, err
@@ -319,12 +301,6 @@ func (d *diff) normalize(table *schema.Table) {
 			}
 		case *enumType:
 			c.Type.Type = &schema.EnumType{T: t.T, Values: t.Values}
-		case *SerialType:
-			// The definition of "<column> <serial type>" is equivalent to specifying:
-			// "<column> <int type> NOT NULL DEFAULT nextval('<table>_<column>_seq')".
-			c.Default = &schema.RawExpr{
-				X: fmt.Sprintf("nextval('%s_%s_seq'::regclass)", table.Name, c.Name),
-			}
 		}
 	}
 }
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index 42edb454..b0145dd1 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -8,6 +8,7 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
+	"regexp"
 	"strconv"
 	"strings"
 
@@ -197,7 +198,7 @@ func (i *inspect) addColumn(s *schema.Schema, rows *sql.Rows) (err error) {
 		timePrecision: &timeprecision.Int64,
 	})
 	if defaults.Valid {
-		c.Default = defaultExpr(c, defaults.String)
+		defaultExpr(c, defaults.String)
 	}
 	if identity.String == "YES" {
 		c.Attrs = append(c.Attrs, &Identity{
@@ -614,16 +615,40 @@ func nArgs(start, n int) string {
 	return b.String()
 }
 
-func defaultExpr(c *schema.Column, x string) schema.Expr {
+var nextval = regexp.MustCompile(`(?i) *nextval\('(?:[\w$]+\.)*[\w$]+_[\w$]+_seq'(?:::regclass)*\) *$`)
+
+func defaultExpr(c *schema.Column, s string) {
 	switch {
-	case sqlx.IsLiteralBool(x), sqlx.IsLiteralNumber(x), sqlx.IsQuoted(x, '\''):
-		return &schema.Literal{V: x}
+	// The definition of "<column> <serial type>" is equivalent to specifying:
+	// "<column> <int type> NOT NULL DEFAULT nextval('<table>_<column>_seq')".
+	// https://postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL.
+	case nextval.MatchString(s):
+		var (
+			st     string
+			tt, ok = c.Type.Type.(*schema.IntegerType)
+		)
+		if !ok {
+			return
+		}
+		switch tt.T {
+		case TypeSmallInt:
+			st = TypeSmallSerial
+		case TypeInteger:
+			st = TypeSerial
+		case TypeBigInt:
+			st = TypeBigSerial
+		}
+		c.Type.Raw = st
+		c.Type.Type = &SerialType{T: st}
+	case sqlx.IsLiteralBool(s), sqlx.IsLiteralNumber(s), sqlx.IsQuoted(s, '\''):
+		c.Default = &schema.Literal{V: s}
 	default:
+		var x schema.Expr = &schema.RawExpr{X: s}
 		// Try casting or fallback to raw expressions (e.g. column text[] has the default of '{}':text[]).
-		if v, ok := canConvert(c.Type, x); ok {
-			return &schema.Literal{V: v}
+		if v, ok := canConvert(c.Type, s); ok {
+			x = &schema.Literal{V: v}
 		}
-		return &schema.RawExpr{X: x}
+		c.Default = x
 	}
 }
 
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index 24ac70cf..f059423d 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -587,6 +587,8 @@ func (s *state) column(b *sqlx.Builder, c *schema.Column) error {
 	b.Ident(c.Name).P(t)
 	if !c.Type.Null {
 		b.P("NOT")
+	} else if t, ok := c.Type.Type.(*SerialType); ok {
+		return fmt.Errorf("NOT NULL constraint is required for %s column %q", t.T, c.Name)
 	}
 	b.P("NULL")
 	s.columnDefault(b, c)
@@ -659,6 +661,9 @@ func (s *state) alterColumn(b *sqlx.Builder, c *schema.ModifyColumn) error {
 			}
 			k &= ^schema.ChangeType
 		case k.Is(schema.ChangeNull) && c.To.Type.Null:
+			if t, ok := c.To.Type.Type.(*SerialType); ok {
+				return fmt.Errorf("NOT NULL constraint is required for %s column %q", t.T, c.To.Name)
+			}
 			b.P("DROP NOT NULL")
 			k &= ^schema.ChangeNull
 		case k.Is(schema.ChangeNull) && !c.To.Type.Null:


Test patch:
diff --git a/sql/postgres/inspect_test.go b/sql/postgres/inspect_test.go
index cd7f2a97..24a08c90 100644
--- a/sql/postgres/inspect_test.go
+++ b/sql/postgres/inspect_test.go
@@ -41,41 +41,42 @@ func TestDriver_InspectTable(t *testing.T) {
 				m.ExpectQuery(queryColumns).
 					WithArgs("public", "users").
 					WillReturnRows(sqltest.Rows(`
- table_name  |  column_name |          data_type          |  formatted  | is_nullable |         column_default          | character_maximum_length | numeric_precision | datetime_precision | numeric_scale |    interval_type    | character_set_name | collation_name | is_identity | identity_start | identity_increment |   identity_last  | identity_generation | generation_expression | comment | typtype |  oid
--------------+--------------+-----------------------------+-------------|-------------+---------------------------------+--------------------------+-------------------+--------------------+---------------+---------------------+--------------------+----------------+-------------+----------------+--------------------+------------------+---------------------+-----------------------+---------+---------+-------
- users       |  id          | bigint                      | int8        | NO          |                                 |                          |                64 |                    |             0 |                     |                    |                | YES         |      100       |          1         |          1       |    BY DEFAULT       |                       |         | b       |    20
- users       |  rank        | integer                     | int4        | YES         |                                 |                          |                32 |                    |             0 |                     |                    |                | NO          |                |                    |                  |                     |                       | rank    | b       |    23
- users       |  c1          | smallint                    | int2        | NO          |           1000                  |                          |                16 |                    |             0 |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    21
- users       |  c2          | bit                         | bit         | NO          |                                 |                        1 |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1560
- users       |  c3          | bit varying                 | varbit      | NO          |                                 |                       10 |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1562
- users       |  c4          | boolean                     | bool        | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    16
- users       |  c5          | bytea                       | bytea       | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    17
- users       |  c6          | character                   | bpchar      | NO          |                                 |                      100 |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1042
- users       |  c7          | character varying           | varchar     | NO          | 'logged_in'::character varying  |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1043
- users       |  c8          | cidr                        | cidr        | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   650
- users       |  c9          | circle                      | circle      | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   718
- users       |  c10         | date                        | date        | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1082
- users       |  c11         | time with time zone         | timetz      | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1266
- users       |  c12         | double precision            | float8      | NO          |                                 |                          |                53 |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   701
- users       |  c13         | real                        | float4      | NO          |           random()              |                          |                24 |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   700
- users       |  c14         | json                        | json        | NO          |           '{}'::json            |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   114
- users       |  c15         | jsonb                       | jsonb       | NO          |           '{}'::jsonb           |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  3802
- users       |  c16         | money                       | money       | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   790
- users       |  c17         | numeric                     | numeric     | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1700
- users       |  c18         | numeric                     | numeric     | NO          |                                 |                          |                 4 |                    |             4 |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1700
- users       |  c19         | integer                     | int4        | NO          | nextval('t1_c19_seq'::regclass) |                          |                32 |                    |             0 |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    23
- users       |  c20         | uuid                        | uuid        | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  2950
- users       |  c21         | xml                         | xml         | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   142
- users       |  c22         | ARRAY                       | integer[]   | YES         |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1007
- users       |  c23         | USER-DEFINED                | ltree       | YES         |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       | 16535
- users       |  c24         | USER-DEFINED                | state       | NO          |                                 |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | e       | 16774
- users       |  c25         | timestamp without time zone | timestamp   | NO          |            now()                |                          |                   |                  4 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1114
- users       |  c26         | timestamp with time zone    | timestamptz | NO          |                                 |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1184
- users       |  c27         | time without time zone      | time        | NO          |                                 |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1266
- users       |  c28         | int                         | int8        | NO          |                                 |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |        (c1 + c2)      |         | b       |  1267
- users       |  c29         | interval                    | interval    | NO          |                                 |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1268
- users       |  c30         | interval                    | interval    | NO          |                                 |                          |                   |                  6 |               |        MONTH        |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1269
- users       |  c31         | interval                    | interval    | NO          |                                 |                          |                   |                  6 |               | MINUTE TO SECOND(6) |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1233
+ table_name  |  column_name |          data_type          |  formatted  | is_nullable |         column_default                 | character_maximum_length | numeric_precision | datetime_precision | numeric_scale |    interval_type    | character_set_name | collation_name | is_identity | identity_start | identity_increment |   identity_last  | identity_generation | generation_expression | comment | typtype |  oid
+-------------+--------------+-----------------------------+-------------|-------------+----------------------------------------+--------------------------+-------------------+--------------------+---------------+---------------------+--------------------+----------------+-------------+----------------+--------------------+------------------+---------------------+-----------------------+---------+---------+-------
+ users       |  id          | bigint                      | int8        | NO          |                                        |                          |                64 |                    |             0 |                     |                    |                | YES         |      100       |          1         |          1       |    BY DEFAULT       |                       |         | b       |    20
+ users       |  rank        | integer                     | int4        | YES         |                                        |                          |                32 |                    |             0 |                     |                    |                | NO          |                |                    |                  |                     |                       | rank    | b       |    23
+ users       |  c1          | smallint                    | int2        | NO          |           1000                         |                          |                16 |                    |             0 |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    21
+ users       |  c2          | bit                         | bit         | NO          |                                        |                        1 |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1560
+ users       |  c3          | bit varying                 | varbit      | NO          |                                        |                       10 |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1562
+ users       |  c4          | boolean                     | bool        | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    16
+ users       |  c5          | bytea                       | bytea       | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    17
+ users       |  c6          | character                   | bpchar      | NO          |                                        |                      100 |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1042
+ users       |  c7          | character varying           | varchar     | NO          | 'logged_in'::character varying         |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1043
+ users       |  c8          | cidr                        | cidr        | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   650
+ users       |  c9          | circle                      | circle      | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   718
+ users       |  c10         | date                        | date        | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1082
+ users       |  c11         | time with time zone         | timetz      | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1266
+ users       |  c12         | double precision            | float8      | NO          |                                        |                          |                53 |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   701
+ users       |  c13         | real                        | float4      | NO          |           random()                     |                          |                24 |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   700
+ users       |  c14         | json                        | json        | NO          |           '{}'::json                   |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   114
+ users       |  c15         | jsonb                       | jsonb       | NO          |           '{}'::jsonb                  |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  3802
+ users       |  c16         | money                       | money       | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   790
+ users       |  c17         | numeric                     | numeric     | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1700
+ users       |  c18         | numeric                     | numeric     | NO          |                                        |                          |                 4 |                    |             4 |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1700
+ users       |  c19         | integer                     | int4        | NO          | nextval('t1_c19_seq'::regclass)        |                          |                32 |                    |             0 |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    23
+ users       |  c20         | uuid                        | uuid        | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  2950
+ users       |  c21         | xml                         | xml         | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |   142
+ users       |  c22         | ARRAY                       | integer[]   | YES         |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1007
+ users       |  c23         | USER-DEFINED                | ltree       | YES         |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       | 16535
+ users       |  c24         | USER-DEFINED                | state       | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | e       | 16774
+ users       |  c25         | timestamp without time zone | timestamp   | NO          |            now()                       |                          |                   |                  4 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1114
+ users       |  c26         | timestamp with time zone    | timestamptz | NO          |                                        |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1184
+ users       |  c27         | time without time zone      | time        | NO          |                                        |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1266
+ users       |  c28         | int                         | int8        | NO          |                                        |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |        (c1 + c2)      |         | b       |  1267
+ users       |  c29         | interval                    | interval    | NO          |                                        |                          |                   |                  6 |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1268
+ users       |  c30         | interval                    | interval    | NO          |                                        |                          |                   |                  6 |               |        MONTH        |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1269
+ users       |  c31         | interval                    | interval    | NO          |                                        |                          |                   |                  6 |               | MINUTE TO SECOND(6) |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  1233
+ users       |  c32         | bigint                      | int4        | NO          | nextval('public.t1_c32_seq'::regclass) |                          |                32 |                    |             0 |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |    23
 `))
 				m.ExpectQuery(sqltest.Escape(`SELECT enumtypid, enumlabel FROM pg_enum WHERE enumtypid IN ($1)`)).
 					WithArgs(16774).
@@ -114,7 +115,7 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c16", Type: &schema.ColumnType{Raw: "money", Type: &CurrencyType{T: "money"}}},
 					{Name: "c17", Type: &schema.ColumnType{Raw: "numeric", Type: &schema.DecimalType{T: "numeric"}}},
 					{Name: "c18", Type: &schema.ColumnType{Raw: "numeric", Type: &schema.DecimalType{T: "numeric", Precision: 4, Scale: 4}}},
-					{Name: "c19", Type: &schema.ColumnType{Raw: "integer", Type: &schema.IntegerType{T: "integer"}}, Default: &schema.RawExpr{X: "nextval('t1_c19_seq'::regclass)"}},
+					{Name: "c19", Type: &schema.ColumnType{Raw: "serial", Type: &SerialType{T: "serial"}}},
 					{Name: "c20", Type: &schema.ColumnType{Raw: "uuid", Type: &UUIDType{T: "uuid"}}},
 					{Name: "c21", Type: &schema.ColumnType{Raw: "xml", Type: &XMLType{T: "xml"}}},
 					{Name: "c22", Type: &schema.ColumnType{Raw: "ARRAY", Null: true, Type: &ArrayType{Type: &schema.IntegerType{T: "integer"}, T: "integer[]"}}},
@@ -127,6 +128,7 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c29", Type: &schema.ColumnType{Raw: "interval", Type: &IntervalType{T: "interval", Precision: p(6)}}},
 					{Name: "c30", Type: &schema.ColumnType{Raw: "interval", Type: &IntervalType{T: "interval", F: "MONTH", Precision: p(6)}}},
 					{Name: "c31", Type: &schema.ColumnType{Raw: "interval", Type: &IntervalType{T: "interval", F: "MINUTE TO SECOND", Precision: p(6)}}},
+					{Name: "c32", Type: &schema.ColumnType{Raw: "bigserial", Type: &SerialType{T: "bigserial"}}},
 				}, t.Columns)
 			},
 		},
diff --git a/sql/postgres/migrate_test.go b/sql/postgres/migrate_test.go
index b0175a32..82479d75 100644
--- a/sql/postgres/migrate_test.go
+++ b/sql/postgres/migrate_test.go
@@ -19,9 +19,10 @@ import (
 
 func TestPlanChanges(t *testing.T) {
 	tests := []struct {
-		changes []schema.Change
-		mock    func(mock)
-		plan    *migrate.Plan
+		changes  []schema.Change
+		mock     func(mock)
+		wantPlan *migrate.Plan
+		wantErr  bool
 	}{
 		{
 			changes: []schema.Change{
@@ -29,7 +30,7 @@ func TestPlanChanges(t *testing.T) {
 				&schema.DropSchema{S: schema.New("test"), Extra: []schema.Clause{&schema.IfExists{}}},
 				&schema.DropSchema{S: schema.New("test"), Extra: []schema.Clause{}},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    false,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -86,7 +87,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -117,7 +118,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -149,7 +150,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -164,7 +165,7 @@ func TestPlanChanges(t *testing.T) {
 			changes: []schema.Change{
 				&schema.AddSchema{S: &schema.Schema{Name: "test"}},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes:       []*migrate.Change{{Cmd: `CREATE SCHEMA "test"`, Reverse: `DROP SCHEMA "test" CASCADE`}}},
@@ -173,11 +174,24 @@ func TestPlanChanges(t *testing.T) {
 			changes: []schema.Change{
 				&schema.DropSchema{S: &schema.Schema{Name: "atlas"}},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Transactional: true,
 				Changes:       []*migrate.Change{{Cmd: `DROP SCHEMA "atlas" CASCADE`}},
 			},
 		},
+		{
+			changes: []schema.Change{
+				&schema.AddTable{
+					T: &schema.Table{
+						Name: "posts",
+						Columns: []*schema.Column{
+							{Name: "id", Type: &schema.ColumnType{Type: &SerialType{T: "serial"}, Null: true}},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
 		{
 			changes: []schema.Change{
 				&schema.AddTable{
@@ -197,7 +211,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes:       []*migrate.Change{{Cmd: `CREATE TABLE "posts" ("id" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY, "text" text NULL, CONSTRAINT "id_nonzero" CHECK ("id" > 0), CONSTRAINT "text_len" CHECK (length("text") > 0) NO INHERIT, CONSTRAINT "a_in_b" CHECK ((a) in (b))) PARTITION BY HASH ("text")`, Reverse: `DROP TABLE "posts"`}},
@@ -214,7 +228,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes:       []*migrate.Change{{Cmd: `CREATE TABLE "posts" ("id" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1024))`, Reverse: `DROP TABLE "posts"`}},
@@ -232,7 +246,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -261,7 +275,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    false,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -282,7 +296,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes:       []*migrate.Change{{Cmd: `CREATE TABLE "posts" ("id" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 2))`, Reverse: `DROP TABLE "posts"`}},
@@ -299,7 +313,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes:       []*migrate.Change{{Cmd: `CREATE TABLE "posts" ("id" integer NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 100 INCREMENT BY 2))`, Reverse: `DROP TABLE "posts"`}},
@@ -309,7 +323,7 @@ func TestPlanChanges(t *testing.T) {
 			changes: []schema.Change{
 				&schema.DropTable{T: &schema.Table{Name: "posts"}},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    false,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -373,7 +387,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -428,7 +442,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -467,7 +481,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -489,7 +503,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -509,7 +523,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -542,7 +556,7 @@ func TestPlanChanges(t *testing.T) {
 				m.ExpectQuery(sqltest.Escape("SELECT * FROM pg_type WHERE typname = $1 AND typtype = 'e'")).
 					WithArgs("state").WillReturnRows(sqlmock.NewRows([]string{"name"}))
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -572,7 +586,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    false,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -602,7 +616,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    false,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -633,7 +647,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    false,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -664,7 +678,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -698,7 +712,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -740,7 +754,7 @@ func TestPlanChanges(t *testing.T) {
 					}
 				}(),
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -758,7 +772,7 @@ func TestPlanChanges(t *testing.T) {
 					To:   schema.NewTable("t2").SetSchema(schema.New("s2")),
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -781,7 +795,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -807,7 +821,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -834,7 +848,7 @@ func TestPlanChanges(t *testing.T) {
 					},
 				},
 			},
-			plan: &migrate.Plan{
+			wantPlan: &migrate.Plan{
 				Reversible:    true,
 				Transactional: true,
 				Changes: []*migrate.Change{
@@ -857,13 +871,17 @@ func TestPlanChanges(t *testing.T) {
 			}
 			drv, err := Open(db)
 			require.NoError(t, err)
-			plan, err := drv.PlanChanges(context.Background(), "plan", tt.changes)
+			plan, err := drv.PlanChanges(context.Background(), "wantPlan", tt.changes)
+			if tt.wantErr {
+				require.Error(t, err, "expect plan to fail")
+				return
+			}
 			require.NoError(t, err)
-			require.Equal(t, tt.plan.Reversible, plan.Reversible)
-			require.Equal(t, tt.plan.Transactional, plan.Transactional)
+			require.Equal(t, tt.wantPlan.Reversible, plan.Reversible)
+			require.Equal(t, tt.wantPlan.Transactional, plan.Transactional)
 			for i, c := range plan.Changes {
-				require.Equal(t, tt.plan.Changes[i].Cmd, c.Cmd)
-				require.Equal(t, tt.plan.Changes[i].Reverse, c.Reverse)
+				require.Equal(t, tt.wantPlan.Changes[i].Cmd, c.Cmd)
+				require.Equal(t, tt.wantPlan.Changes[i].Reverse, c.Reverse)
 			}
 		})
 	}