Repository: ariga/atlas
Pull request #344 (2021-12-27 19:31:28)

sql/postgres: support literal expressions


Patch:
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 51133610..2ed98a9f 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -9,6 +9,7 @@ import (
 	"database/sql"
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 
 	"ariga.io/atlas/sql/schema"
@@ -262,3 +263,46 @@ func (b *Builder) rewriteLastByte(c byte) {
 	buf := b.Buffer.Bytes()
 	buf[len(buf)-1] = c
 }
+
+// IsQuoted reports if the given string is quoted with one of the given quotes (e.g. '\'', '"', '`').
+func IsQuoted(s string, q ...byte) bool {
+	for i := range q {
+		if l, r := strings.IndexByte(s, q[i]), strings.LastIndexByte(s, q[i]); l < r && l == 0 && r == len(s)-1 {
+			return true
+		}
+	}
+	return false
+}
+
+// IsLiteralBool reports if the given string is a valid literal bool.
+func IsLiteralBool(s string) bool {
+	_, err := strconv.ParseBool(s)
+	return err == nil
+}
+
+// IsLiteralNumber reports if the given string is a literal number.
+func IsLiteralNumber(s string) bool {
+	// Hex digits.
+	if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
+		// Some databases allow odd length hex string.
+		_, err := strconv.ParseUint(s[2:], 16, 64)
+		return err == nil
+	}
+	// Digits with optional exponent.
+	_, err := strconv.ParseFloat(s, 64)
+	return err == nil
+}
+
+// DefaultValue returns the string represents the DEFAULT of a column.
+func DefaultValue(c *schema.Column) (string, bool) {
+	switch x := c.Default.(type) {
+	case nil:
+		return "", false
+	case *schema.Literal:
+		return x.V, true
+	case *schema.RawExpr:
+		return x.X, true
+	default:
+		panic(fmt.Sprintf("unexpected default value type: %T", x))
+	}
+}
diff --git a/sql/mysql/convert.go b/sql/mysql/convert.go
index 6ab89679..72dfa6fa 100644
--- a/sql/mysql/convert.go
+++ b/sql/mysql/convert.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"strings"
 
+	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 )
 
@@ -86,7 +87,7 @@ func formatValues(vs []string) string {
 	values := make([]string, len(vs))
 	for i := range vs {
 		values[i] = vs[i]
-		if !strings.HasPrefix(values[i], "'") || !strings.HasSuffix(values[i], "'") {
+		if !sqlx.IsQuoted(values[i], '"', '\'') {
 			values[i] = "'" + values[i] + "'"
 		}
 	}
diff --git a/sql/mysql/diff.go b/sql/mysql/diff.go
index baaf024f..5a93ca9b 100644
--- a/sql/mysql/diff.go
+++ b/sql/mysql/diff.go
@@ -275,20 +275,9 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 // defaultChanged reports if the a default value of a column
 // type was changed.
 func (d *diff) defaultChanged(from, to *schema.Column) (bool, error) {
-	var d1, d2 string
-	switch d := from.Default.(type) {
-	case *schema.Literal:
-		d1 = d.V
-	case *schema.RawExpr:
-		d1 = d.X
-	}
-	switch d := to.Default.(type) {
-	case *schema.Literal:
-		d2 = d.V
-	case *schema.RawExpr:
-		d2 = d.X
-	}
-	if (d1 != "") != (d2 != "") {
+	d1, ok1 := sqlx.DefaultValue(from)
+	d2, ok2 := sqlx.DefaultValue(to)
+	if ok1 != ok2 {
 		return true, nil
 	}
 	if d1 == d2 {
diff --git a/sql/mysql/inspect.go b/sql/mysql/inspect.go
index 4754d99e..593bf43d 100644
--- a/sql/mysql/inspect.go
+++ b/sql/mysql/inspect.go
@@ -619,7 +619,7 @@ func isHex(x string) bool { return len(x) > 2 && strings.ToLower(x[:2]) == "0x"
 // marDefaultExpr returns the correct schema.Expr based on the column attributes for MariaDB.
 func (i *inspect) marDefaultExpr(c *schema.Column, x string) schema.Expr {
 	// From MariaDB 10.2.7, string-based literals are quoted to distinguish them from expressions.
-	if i.gteV("10.2.7") && strings.HasPrefix(x, "'") && strings.HasSuffix(x, "'") {
+	if i.gteV("10.2.7") && sqlx.IsQuoted(x, '\'') {
 		return &schema.Literal{V: x}
 	}
 	// In this case, we need to manually check if the expression is literal, or fallback to raw expression.
diff --git a/sql/mysql/migrate.go b/sql/mysql/migrate.go
index d95ed2ef..f41cc357 100644
--- a/sql/mysql/migrate.go
+++ b/sql/mysql/migrate.go
@@ -474,8 +474,7 @@ search:
 }
 
 func quote(s string) string {
-	if strings.HasPrefix(s, "'") && strings.HasSuffix(s, "'") ||
-		strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") {
+	if sqlx.IsQuoted(s, '"', '\'') {
 		return s
 	}
 	return strconv.Quote(s)
@@ -483,9 +482,9 @@ func quote(s string) string {
 
 func unquote(s string) (string, error) {
 	switch {
-	case strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\""):
+	case sqlx.IsQuoted(s, '"'):
 		return strconv.Unquote(s)
-	case strings.HasPrefix(s, "'") && strings.HasSuffix(s, "'"):
+	case sqlx.IsQuoted(s, '\''):
 		return strings.ReplaceAll(s[1:len(s)-1], "''", "'"), nil
 	default:
 		return s, nil
diff --git a/sql/postgres/diff.go b/sql/postgres/diff.go
index b558f76c..403c47fa 100644
--- a/sql/postgres/diff.go
+++ b/sql/postgres/diff.go
@@ -77,16 +77,16 @@ func (d *diff) ColumnChange(from, to *schema.Column) (schema.ChangeKind, error)
 // defaultChanged reports if the a default value of a column
 // type was changed.
 func (d *diff) defaultChanged(from, to *schema.Column) (bool, error) {
-	d1, ok1 := from.Default.(*schema.RawExpr)
-	d2, ok2 := to.Default.(*schema.RawExpr)
+	d1, ok1 := sqlx.DefaultValue(from)
+	d2, ok2 := sqlx.DefaultValue(to)
 	if ok1 != ok2 {
 		return true, nil
 	}
-	if d1 == nil || d1.X == d2.X || trimCast(d1.X) == trimCast(d2.X) {
+	if trimCast(d1) == trimCast(d2) {
 		return false, nil
 	}
 	// Use database comparison in case of mismatch (e.g. `SELECT ARRAY[1] = '{1}'::int[]`).
-	equals, err := d.valuesEqual(d1.X, d2.X)
+	equals, err := d.valuesEqual(d1, d2)
 	if err != nil {
 		return false, err
 	}
@@ -143,6 +143,24 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 	if fromT == nil || toT == nil {
 		return false, fmt.Errorf("postgres: missing type infromation 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 tSmallSerial:
+			it = tSmallInt
+		case tSerial:
+			it = tInteger
+		case tBigSerial:
+			it = tBigInt
+		}
+		return i.T != it, nil
+	}
 	if reflect.TypeOf(fromT) != reflect.TypeOf(toT) {
 		return true, nil
 	}
@@ -150,7 +168,7 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 	switch fromT := fromT.(type) {
 	case *schema.BinaryType, *schema.BoolType, *schema.DecimalType, *schema.FloatType,
 		*schema.IntegerType, *schema.JSONType, *schema.SpatialType, *schema.StringType,
-		*schema.TimeType, *BitType, *SerialType, *NetworkType, *UserDefinedType:
+		*schema.TimeType, *BitType, *NetworkType, *UserDefinedType:
 		changed = mustFormat(toT) != mustFormat(fromT)
 	case *EnumType:
 		toT := toT.(*schema.EnumType)
@@ -228,26 +246,11 @@ func (d *diff) normalize(table *schema.Table) {
 		case *EnumType:
 			c.Type.Type = &schema.EnumType{T: t.T, Values: t.Values}
 		case *SerialType:
-			// The smallserial, serial and bigserial data types are not true types, but merely a
-			// notational convenience for creating integers types with AUTO_INCREMENT property.
-			it := &schema.IntegerType{}
-			switch t.T {
-			case "smallserial":
-				it.T = "smallint"
-			case "serial":
-				it.T = "integer"
-			case "bigserial":
-				it.T = "bigint"
-			default:
-				panic(fmt.Sprintf("unexpected serial type: %q", it.T))
-			}
 			// The definition of "<column> <serial type>" is equivalent to specifying:
 			// "<column> <int type> NOT NULL DEFAULT nextval('<table>_<column>_seq')".
-			c.Default = &SeqFuncExpr{
-				X: fmt.Sprintf("nextval('%s_%s_seq')", table.Name, c.Name),
+			c.Default = &schema.RawExpr{
+				X: fmt.Sprintf("nextval('%s_%s_seq'::regclass)", table.Name, c.Name),
 			}
-			c.Type.Type = it
-			c.Type.Null = false
 		}
 	}
 }
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index c1a59c6a..9428653e 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -179,7 +179,7 @@ func (i *inspect) addColumn(t *schema.Table, rows *sql.Rows) error {
 		typid:     typid.Int64,
 	})
 	if sqlx.ValidString(defaults) {
-		c.Default = defaultExpr(defaults.String)
+		c.Default = defaultExpr(c, defaults.String)
 	}
 	if identity.String == "YES" {
 		c.Attrs = append(c.Attrs, &Identity{
@@ -504,13 +504,43 @@ func inStrings(s []string, query string, args []interface{}) (string, []interfac
 	return query, args
 }
 
-func defaultExpr(x string) schema.Expr {
-	for _, fn := range [...]string{"currval", "lastval", "setval", "nextval"} {
-		if strings.HasPrefix(x, fn) {
-			return &SeqFuncExpr{X: x}
+func defaultExpr(c *schema.Column, x string) schema.Expr {
+	switch {
+	case sqlx.IsLiteralBool(x), sqlx.IsLiteralNumber(x), sqlx.IsQuoted(x, '\''):
+		return &schema.Literal{V: x}
+	default:
+		// 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}
+		}
+		return &schema.RawExpr{X: x}
+	}
+}
+
+func canConvert(t *schema.ColumnType, x string) (string, bool) {
+	r := t.Raw
+	if t, ok := t.Type.(*ArrayType); ok {
+		r = t.T
+	}
+	i := strings.Index(x, "::"+r)
+	if i == -1 || !sqlx.IsQuoted(x[:i], '\'') {
+		return "", false
+	}
+	q := x[0:i]
+	x = x[1 : i-1]
+	switch t.Type.(type) {
+	case *schema.BoolType:
+		if sqlx.IsLiteralBool(x) {
+			return x, true
+		}
+	case *schema.DecimalType, *schema.IntegerType, *schema.FloatType:
+		if sqlx.IsLiteralNumber(x) {
+			return x, true
 		}
+	case *ArrayType, *schema.BinaryType, *schema.JSONType, *NetworkType, *schema.SpatialType, *schema.StringType, *schema.TimeType, *UUIDType, *XMLType:
+		return q, true
 	}
-	return &schema.RawExpr{X: x}
+	return "", false
 }
 
 type (
@@ -628,13 +658,6 @@ type (
 		NoInherit bool
 		Columns   []string
 	}
-
-	// SeqFuncExpr describe a sequence generator function.
-	// https://www.postgresql.org/docs/current/functions-sequence.html
-	SeqFuncExpr struct {
-		schema.Expr
-		X string
-	}
 )
 
 const (
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index 38cc0d2f..02300cff 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -389,9 +389,7 @@ func (s *state) column(b *sqlx.Builder, c *schema.Column) {
 		b.P("NOT")
 	}
 	b.P("NULL")
-	if x, ok := c.Default.(*schema.RawExpr); ok {
-		b.P("DEFAULT", x.X)
-	}
+	s.columnDefault(b, c)
 	for _, attr := range c.Attrs {
 		switch attr := attr.(type) {
 		case *schema.Comment:
@@ -408,6 +406,25 @@ func (s *state) column(b *sqlx.Builder, c *schema.Column) {
 	}
 }
 
+// columnDefault writes the default value of column to the builder.
+func (s *state) columnDefault(b *sqlx.Builder, c *schema.Column) {
+	switch x := c.Default.(type) {
+	case *schema.Literal:
+		v := x.V
+		switch c.Type.Type.(type) {
+		case *schema.BoolType, *schema.DecimalType, *schema.IntegerType, *schema.FloatType:
+		default:
+			v = quote(v)
+		}
+		b.P("DEFAULT", v)
+	case *schema.RawExpr:
+		// Ignore identity functions added by the differ.
+		if _, ok := c.Type.Type.(*SerialType); !ok {
+			b.P("DEFAULT", x.X)
+		}
+	}
+}
+
 func (s *state) alterColumn(b *sqlx.Builder, k schema.ChangeKind, c *schema.Column) {
 	for !k.Is(schema.NoChange) {
 		b.P("ALTER COLUMN").Ident(c.Name)
@@ -572,8 +589,8 @@ func skipAutoChanges(changes []schema.Change) []schema.Change {
 }
 
 func quote(s string) string {
-	if strings.HasPrefix(s, "'") && strings.HasSuffix(s, "'") {
+	if sqlx.IsQuoted(s, '\'') {
 		return s
 	}
-	return "'" + s + "'"
+	return "'" + strings.ReplaceAll(s, "'", "''") + "'"
 }
diff --git a/sql/sqlite/diff.go b/sql/sqlite/diff.go
index 7949cb1e..1e713351 100644
--- a/sql/sqlite/diff.go
+++ b/sql/sqlite/diff.go
@@ -70,10 +70,9 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 // defaultChanged reports if the a default value of a column
 // type was changed.
 func (d *diff) defaultChanged(from, to *schema.Column) bool {
-	if ok1, ok2 := from.Default != nil, to.Default != nil; !ok1 || !ok2 {
-		return ok1 != ok2
-	}
-	return defaultValue(from) != defaultValue(to)
+	d1, ok1 := sqlx.DefaultValue(from)
+	d2, ok2 := sqlx.DefaultValue(to)
+	return ok1 != ok2 || d1 != d2
 }
 
 // IndexAttrChanged reports if the index attributes were changed.
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index 3b766d56..a3f5009e 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -518,9 +518,7 @@ func defaultExpr(x string) schema.Expr {
 	switch {
 	// Literals definition.
 	// https://www.sqlite.org/syntax/literal-value.html
-	case isBlob(x), isBool(x), isNumeric(x),
-		strings.HasPrefix(x, `"`) && strings.HasSuffix(x, `"`),
-		strings.HasPrefix(x, "'") && strings.HasSuffix(x, "'"):
+	case sqlx.IsLiteralBool(x), sqlx.IsLiteralNumber(x), sqlx.IsQuoted(x, '"', '\''), isBlob(x):
 		return &schema.Literal{V: x}
 	default:
 		// We wrap the CURRENT_TIMESTAMP literals in raw-expressions
@@ -529,20 +527,6 @@ func defaultExpr(x string) schema.Expr {
 	}
 }
 
-// isNumeric reports if the given string is a valid numeric literal
-// according to https://www.sqlite.org/syntax/numeric-literal.html.
-func isNumeric(s string) bool {
-	// Hex digits.
-	if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
-		// SQLite allows odd length hex string.
-		_, err := strconv.ParseUint(s[2:], 16, 64)
-		return err == nil
-	}
-	// Digits with optional exponent.
-	_, err := strconv.ParseFloat(s, 64)
-	return err == nil
-}
-
 // isNumber reports whether the string is a number (category N).
 func isNumber(s string) bool {
 	for _, r := range s {
@@ -562,11 +546,6 @@ func isBlob(s string) bool {
 	return false
 }
 
-func isBool(s string) bool {
-	_, err := strconv.ParseBool(s)
-	return err == nil
-}
-
 var reAutoinc = regexp.MustCompile(`(?i)PRIMARY\s+KEY\s+AUTOINCREMENT`)
 
 // autoinc checks if the table contains a "PRIMARY KEY AUTOINCREMENT" on its


Test patch:
diff --git a/internal/integration/postgres_test.go b/internal/integration/postgres_test.go
index e8156415..d34400c3 100644
--- a/internal/integration/postgres_test.go
+++ b/internal/integration/postgres_test.go
@@ -76,15 +76,15 @@ func TestPostgres_AddIndexedColumns(t *testing.T) {
 		usersT.Columns = append(usersT.Columns, &schema.Column{
 			Name:    "a",
 			Type:    &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}, Null: true},
-			Default: &schema.RawExpr{X: "10"},
+			Default: &schema.Literal{V: "10"},
 		}, &schema.Column{
 			Name:    "b",
 			Type:    &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}, Null: true},
-			Default: &schema.RawExpr{X: "10"},
+			Default: &schema.Literal{V: "10"},
 		}, &schema.Column{
 			Name:    "c",
 			Type:    &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}, Null: true},
-			Default: &schema.RawExpr{X: "10"},
+			Default: &schema.Literal{V: "10"},
 		})
 		parts := usersT.Columns[len(usersT.Columns)-3:]
 		usersT.Indexes = append(usersT.Indexes, &schema.Index{
@@ -119,22 +119,22 @@ func TestPostgres_AddColumns(t *testing.T) {
 		usersT.Columns = append(
 			usersT.Columns,
 			&schema.Column{Name: "a", Type: &schema.ColumnType{Type: &schema.BinaryType{T: "bytea"}}},
-			&schema.Column{Name: "b", Type: &schema.ColumnType{Type: &schema.FloatType{T: "double precision", Precision: 10}}, Default: &schema.RawExpr{X: "10.1"}},
-			&schema.Column{Name: "c", Type: &schema.ColumnType{Type: &schema.StringType{T: "character"}}, Default: &schema.RawExpr{X: "'y'"}},
-			&schema.Column{Name: "d", Type: &schema.ColumnType{Type: &schema.DecimalType{T: "numeric", Precision: 10, Scale: 2}}, Default: &schema.RawExpr{X: "0.99"}},
-			&schema.Column{Name: "e", Type: &schema.ColumnType{Type: &schema.JSONType{T: "json"}}, Default: &schema.RawExpr{X: "'{}'"}},
-			&schema.Column{Name: "f", Type: &schema.ColumnType{Type: &schema.JSONType{T: "jsonb"}}, Default: &schema.RawExpr{X: "'1'"}},
-			&schema.Column{Name: "g", Type: &schema.ColumnType{Type: &schema.FloatType{T: "float", Precision: 10}}, Default: &schema.RawExpr{X: "'1'"}},
-			&schema.Column{Name: "h", Type: &schema.ColumnType{Type: &schema.FloatType{T: "float", Precision: 30}}, Default: &schema.RawExpr{X: "'1'"}},
-			&schema.Column{Name: "i", Type: &schema.ColumnType{Type: &schema.FloatType{T: "float", Precision: 53}}, Default: &schema.RawExpr{X: "1"}},
+			&schema.Column{Name: "b", Type: &schema.ColumnType{Type: &schema.FloatType{T: "double precision", Precision: 10}}, Default: &schema.Literal{V: "10.1"}},
+			&schema.Column{Name: "c", Type: &schema.ColumnType{Type: &schema.StringType{T: "character"}}, Default: &schema.Literal{V: "'y'"}},
+			&schema.Column{Name: "d", Type: &schema.ColumnType{Type: &schema.DecimalType{T: "numeric", Precision: 10, Scale: 2}}, Default: &schema.Literal{V: "0.99"}},
+			&schema.Column{Name: "e", Type: &schema.ColumnType{Type: &schema.JSONType{T: "json"}}, Default: &schema.Literal{V: "'{}'"}},
+			&schema.Column{Name: "f", Type: &schema.ColumnType{Type: &schema.JSONType{T: "jsonb"}}, Default: &schema.Literal{V: "'1'"}},
+			&schema.Column{Name: "g", Type: &schema.ColumnType{Type: &schema.FloatType{T: "float", Precision: 10}}, Default: &schema.Literal{V: "'1'"}},
+			&schema.Column{Name: "h", Type: &schema.ColumnType{Type: &schema.FloatType{T: "float", Precision: 30}}, Default: &schema.Literal{V: "'1'"}},
+			&schema.Column{Name: "i", Type: &schema.ColumnType{Type: &schema.FloatType{T: "float", Precision: 53}}, Default: &schema.Literal{V: "1"}},
 			&schema.Column{Name: "j", Type: &schema.ColumnType{Type: &postgres.SerialType{T: "serial"}}},
-			&schema.Column{Name: "k", Type: &schema.ColumnType{Type: &postgres.CurrencyType{T: "money"}}, Default: &schema.RawExpr{X: "100"}},
+			&schema.Column{Name: "k", Type: &schema.ColumnType{Type: &postgres.CurrencyType{T: "money"}}, Default: &schema.Literal{V: "'100'"}},
 			&schema.Column{Name: "l", Type: &schema.ColumnType{Type: &postgres.CurrencyType{T: "money"}, Null: true}, Default: &schema.RawExpr{X: "'52093.89'::money"}},
-			&schema.Column{Name: "m", Type: &schema.ColumnType{Type: &schema.BoolType{T: "boolean"}, Null: true}, Default: &schema.RawExpr{X: "false"}},
-			&schema.Column{Name: "n", Type: &schema.ColumnType{Type: &schema.SpatialType{T: "point"}, Null: true}, Default: &schema.RawExpr{X: "'(1,2)'"}},
-			&schema.Column{Name: "o", Type: &schema.ColumnType{Type: &schema.SpatialType{T: "line"}, Null: true}, Default: &schema.RawExpr{X: "'{1,2,3}'"}},
+			&schema.Column{Name: "m", Type: &schema.ColumnType{Type: &schema.BoolType{T: "boolean"}, Null: true}, Default: &schema.Literal{V: "false"}},
+			&schema.Column{Name: "n", Type: &schema.ColumnType{Type: &schema.SpatialType{T: "point"}, Null: true}, Default: &schema.Literal{V: "'(1,2)'"}},
+			&schema.Column{Name: "o", Type: &schema.ColumnType{Type: &schema.SpatialType{T: "line"}, Null: true}, Default: &schema.Literal{V: "'{1,2,3}'"}},
 			&schema.Column{Name: "p", Type: &schema.ColumnType{Type: &postgres.UserDefinedType{T: "hstore"}, Null: true}, Default: &schema.RawExpr{X: "'a => 1'"}},
-			&schema.Column{Name: "q", Type: &schema.ColumnType{Type: &postgres.ArrayType{T: "text[]"}, Null: true}, Default: &schema.RawExpr{X: "'{}'"}},
+			&schema.Column{Name: "q", Type: &schema.ColumnType{Type: &postgres.ArrayType{T: "text[]"}, Null: true}, Default: &schema.Literal{V: "'{}'"}},
 		)
 		changes := t.diff(t.loadUsers(), usersT)
 		require.Len(t, changes, 17)
@@ -190,7 +190,7 @@ func TestPostgres_ColumnArray(t *testing.T) {
 		// Add column.
 		usersT.Columns = append(
 			usersT.Columns,
-			&schema.Column{Name: "a", Type: &schema.ColumnType{Raw: "int[]", Type: &postgres.ArrayType{T: "int[]"}}, Default: &schema.RawExpr{X: "'{1}'"}},
+			&schema.Column{Name: "a", Type: &schema.ColumnType{Raw: "int[]", Type: &postgres.ArrayType{T: "int[]"}}, Default: &schema.Literal{V: "'{1}'"}},
 		)
 		changes := t.diff(t.loadUsers(), usersT)
 		require.Len(t, changes, 1)
@@ -528,17 +528,17 @@ create table atlas_types_sanity
 				{
 					Name:    "tBoolean",
 					Type:    &schema.ColumnType{Type: &schema.BoolType{T: "boolean"}, Raw: "boolean", Null: false},
-					Default: &schema.RawExpr{X: "false"},
+					Default: &schema.Literal{V: "false"},
 				},
 				{
 					Name:    "tBool",
 					Type:    &schema.ColumnType{Type: &schema.BoolType{T: "boolean"}, Raw: "boolean", Null: false},
-					Default: &schema.RawExpr{X: "false"},
+					Default: &schema.Literal{V: "false"},
 				},
 				{
 					Name:    "tBytea",
 					Type:    &schema.ColumnType{Type: &schema.BinaryType{T: "bytea"}, Raw: "bytea", Null: false},
-					Default: &schema.RawExpr{X: "'\\x01'::bytea"},
+					Default: &schema.Literal{V: "'\\x01'"},
 				},
 				{
 					Name:    "tCharacter",
@@ -553,72 +553,72 @@ create table atlas_types_sanity
 				{
 					Name:    "tCharVar",
 					Type:    &schema.ColumnType{Type: &schema.StringType{T: "character varying", Size: 10}, Raw: "character varying", Null: true},
-					Default: &schema.RawExpr{X: "'atlas'::character varying"},
+					Default: &schema.Literal{V: "'atlas'"},
 				},
 				{
 					Name:    "tVarChar",
 					Type:    &schema.ColumnType{Type: &schema.StringType{T: "character varying", Size: 10}, Raw: "character varying", Null: true},
-					Default: &schema.RawExpr{X: "'atlas'::character varying"},
+					Default: &schema.Literal{V: "'atlas'"},
 				},
 				{
 					Name:    "tText",
 					Type:    &schema.ColumnType{Type: &schema.StringType{T: "text"}, Raw: "text", Null: true},
-					Default: &schema.RawExpr{X: "'atlas'::text"},
+					Default: &schema.Literal{V: "'atlas'"},
 				},
 				{
 					Name:    "tSmallInt",
 					Type:    &schema.ColumnType{Type: &schema.IntegerType{T: "smallint"}, Raw: "smallint", Null: true},
-					Default: &schema.RawExpr{X: "'10'::smallint"},
+					Default: &schema.Literal{V: "10"},
 				},
 				{
 					Name:    "tInteger",
 					Type:    &schema.ColumnType{Type: &schema.IntegerType{T: "integer"}, Raw: "integer", Null: true},
-					Default: &schema.RawExpr{X: "10"},
+					Default: &schema.Literal{V: "10"},
 				},
 				{
 					Name:    "tBigInt",
 					Type:    &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}, Raw: "bigint", Null: true},
-					Default: &schema.RawExpr{X: "'10'::bigint"},
+					Default: &schema.Literal{V: "10"},
 				},
 				{
 					Name:    "tInt",
 					Type:    &schema.ColumnType{Type: &schema.IntegerType{T: "integer"}, Raw: "integer", Null: true},
-					Default: &schema.RawExpr{X: "10"},
+					Default: &schema.Literal{V: "10"},
 				},
 				{
 					Name:    "tInt2",
 					Type:    &schema.ColumnType{Type: &schema.IntegerType{T: "smallint"}, Raw: "smallint", Null: true},
-					Default: &schema.RawExpr{X: "'10'::smallint"},
+					Default: &schema.Literal{V: "10"},
 				},
 				{
 					Name:    "tInt4",
 					Type:    &schema.ColumnType{Type: &schema.IntegerType{T: "integer"}, Raw: "integer", Null: true},
-					Default: &schema.RawExpr{X: "10"},
+					Default: &schema.Literal{V: "10"},
 				},
 				{
 					Name:    "tInt8",
 					Type:    &schema.ColumnType{Type: &schema.IntegerType{T: "bigint"}, Raw: "bigint", Null: true},
-					Default: &schema.RawExpr{X: "'10'::bigint"},
+					Default: &schema.Literal{V: "10"},
 				},
 				{
 					Name:    "tCIDR",
 					Type:    &schema.ColumnType{Type: &postgres.NetworkType{T: "cidr"}, Raw: "cidr", Null: true},
-					Default: &schema.RawExpr{X: "'127.0.0.1/32'::cidr"},
+					Default: &schema.Literal{V: "'127.0.0.1/32'"},
 				},
 				{
 					Name:    "tInet",
 					Type:    &schema.ColumnType{Type: &postgres.NetworkType{T: "inet"}, Raw: "inet", Null: true},
-					Default: &schema.RawExpr{X: "'127.0.0.1'::inet"},
+					Default: &schema.Literal{V: "'127.0.0.1'"},
 				},
 				{
 					Name:    "tMACAddr",
 					Type:    &schema.ColumnType{Type: &postgres.NetworkType{T: "macaddr"}, Raw: "macaddr", Null: true},
-					Default: &schema.RawExpr{X: "'08:00:2b:01:02:03'::macaddr"},
+					Default: &schema.Literal{V: "'08:00:2b:01:02:03'"},
 				},
 				{
 					Name:    "tMACAddr8",
 					Type:    &schema.ColumnType{Type: &postgres.NetworkType{T: "macaddr8"}, Raw: "macaddr8", Null: true},
-					Default: &schema.RawExpr{X: "'08:00:2b:01:02:03:04:05'::macaddr8"},
+					Default: &schema.Literal{V: "'08:00:2b:01:02:03:04:05'"},
 				},
 				{
 					Name: "tCircle",
@@ -687,121 +687,115 @@ create table atlas_types_sanity
 				{
 					Name:    "tDouble",
 					Type:    &schema.ColumnType{Type: &schema.FloatType{T: "double precision", Precision: 53}, Raw: "double precision", Null: true},
-					Default: &schema.RawExpr{X: "0"},
+					Default: &schema.Literal{V: "0"},
 				},
 				{
 					Name:    "tReal",
 					Type:    &schema.ColumnType{Type: &schema.FloatType{T: "real", Precision: 24}, Raw: "real", Null: true},
-					Default: &schema.RawExpr{X: "0"},
+					Default: &schema.Literal{V: "0"},
 				},
 				{
 					Name:    "tFloat8",
 					Type:    &schema.ColumnType{Type: &schema.FloatType{T: "double precision", Precision: 53}, Raw: "double precision", Null: true},
-					Default: &schema.RawExpr{X: "0"},
+					Default: &schema.Literal{V: "0"},
 				},
 				{
 					Name:    "tFloat4",
 					Type:    &schema.ColumnType{Type: &schema.FloatType{T: "real", Precision: 24}, Raw: "real", Null: true},
-					Default: &schema.RawExpr{X: "0"},
+					Default: &schema.Literal{V: "0"},
 				},
 				{
 					Name:    "tNumeric",
 					Type:    &schema.ColumnType{Type: &schema.DecimalType{T: "numeric", Precision: 0}, Raw: "numeric", Null: true},
-					Default: &schema.RawExpr{X: "0"},
+					Default: &schema.Literal{V: "0"},
 				},
 				{
 					Name:    "tDecimal",
 					Type:    &schema.ColumnType{Type: &schema.DecimalType{T: "numeric", Precision: 0}, Raw: "numeric", Null: true},
-					Default: &schema.RawExpr{X: "0"},
+					Default: &schema.Literal{V: "0"},
 				},
 				{
 					Name: "tSmallSerial",
 					Type: &schema.ColumnType{Type: &schema.IntegerType{T: "smallint", Unsigned: false}, Raw: "smallint", Null: false},
-					Default: &postgres.SeqFuncExpr{
-						Expr: nil,
-						X:    "nextval('\"atlas_types_sanity_tSmallSerial_seq\"'::regclass)",
+					Default: &schema.RawExpr{
+						X: "nextval('\"atlas_types_sanity_tSmallSerial_seq\"'::regclass)",
 					},
 				},
 				{
 					Name: "tSerial",
 					Type: &schema.ColumnType{Type: &schema.IntegerType{T: "integer", Unsigned: false}, Raw: "integer", Null: false},
-					Default: &postgres.SeqFuncExpr{
-						Expr: nil,
-						X:    "nextval('\"atlas_types_sanity_tSerial_seq\"'::regclass)",
+					Default: &schema.RawExpr{
+						X: "nextval('\"atlas_types_sanity_tSerial_seq\"'::regclass)",
 					},
 				},
 				{
 					Name: "tBigSerial",
 					Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint", Unsigned: false}, Raw: "bigint", Null: false},
-					Default: &postgres.SeqFuncExpr{
-						Expr: nil,
-						X:    "nextval('\"atlas_types_sanity_tBigSerial_seq\"'::regclass)",
+					Default: &schema.RawExpr{
+						X: "nextval('\"atlas_types_sanity_tBigSerial_seq\"'::regclass)",
 					},
 				},
 				{
 					Name: "tSerial2",
 					Type: &schema.ColumnType{Type: &schema.IntegerType{T: "smallint", Unsigned: false}, Raw: "smallint", Null: false},
-					Default: &postgres.SeqFuncExpr{
-						Expr: nil,
-						X:    "nextval('\"atlas_types_sanity_tSerial2_seq\"'::regclass)",
+					Default: &schema.RawExpr{
+						X: "nextval('\"atlas_types_sanity_tSerial2_seq\"'::regclass)",
 					},
 				},
 				{
 					Name: "tSerial4",
 					Type: &schema.ColumnType{Type: &schema.IntegerType{T: "integer", Unsigned: false}, Raw: "integer", Null: false},
-					Default: &postgres.SeqFuncExpr{
-						Expr: nil,
-						X:    "nextval('\"atlas_types_sanity_tSerial4_seq\"'::regclass)",
+					Default: &schema.RawExpr{
+						X: "nextval('\"atlas_types_sanity_tSerial4_seq\"'::regclass)",
 					},
 				},
 				{
 					Name: "tSerial8",
 					Type: &schema.ColumnType{Type: &schema.IntegerType{T: "bigint", Unsigned: false}, Raw: "bigint", Null: false},
-					Default: &postgres.SeqFuncExpr{
-						Expr: nil,
-						X:    "nextval('\"atlas_types_sanity_tSerial8_seq\"'::regclass)",
+					Default: &schema.RawExpr{
+						X: "nextval('\"atlas_types_sanity_tSerial8_seq\"'::regclass)",
 					},
 				},
 				{
 					Name: "tArray",
 					Type: &schema.ColumnType{Type: &postgres.ArrayType{T: "text[]"}, Raw: "ARRAY", Null: true},
-					Default: &schema.RawExpr{
-						X: "'{}'::text[]",
+					Default: &schema.Literal{
+						V: "'{}'",
 					},
 				},
 				{
 					Name: "tXML",
 					Type: &schema.ColumnType{Type: &postgres.XMLType{T: "xml"}, Raw: "xml", Null: true},
-					Default: &schema.RawExpr{
-						X: "'<a>foo</a>'::xml",
+					Default: &schema.Literal{
+						V: "'<a>foo</a>'",
 					},
 				},
 				{
 					Name: "tJSON",
 					Type: &schema.ColumnType{Type: &schema.JSONType{T: "json"}, Raw: "json", Null: true},
-					Default: &schema.RawExpr{
-						X: "'{\"key\":\"value\"}'::json",
+					Default: &schema.Literal{
+						V: "'{\"key\":\"value\"}'",
 					},
 				},
 				{
 					Name: "tJSONB",
 					Type: &schema.ColumnType{Type: &schema.JSONType{T: "jsonb"}, Raw: "jsonb", Null: true},
-					Default: &schema.RawExpr{
-						X: "'{\"key\": \"value\"}'::jsonb",
+					Default: &schema.Literal{
+						V: "'{\"key\": \"value\"}'",
 					},
 				},
 				{
 					Name: "tUUID",
 					Type: &schema.ColumnType{Type: &postgres.UUIDType{T: "uuid"}, Raw: "uuid", Null: true},
-					Default: &schema.RawExpr{
-						X: "'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid",
+					Default: &schema.Literal{
+						V: "'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'",
 					},
 				},
 				{
 					Name: "tMoney",
 					Type: &schema.ColumnType{Type: &postgres.CurrencyType{T: "money"}, Raw: "money", Null: true},
-					Default: &schema.RawExpr{
-						X: "18",
+					Default: &schema.Literal{
+						V: "18",
 					},
 				},
 				{
@@ -905,7 +899,7 @@ func (t *pgTest) posts() *schema.Table {
 			{
 				Name:    "author_id",
 				Type:    &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}, Null: true},
-				Default: &schema.RawExpr{X: "10"},
+				Default: &schema.Literal{V: "10"},
 			},
 			{
 				Name: "ctime",
diff --git a/sql/postgres/inspect_test.go b/sql/postgres/inspect_test.go
index 299f6ce4..90952f3a 100644
--- a/sql/postgres/inspect_test.go
+++ b/sql/postgres/inspect_test.go
@@ -61,7 +61,7 @@ func TestDriver_InspectTable(t *testing.T) {
 -------------+---------------------+-------------+---------------------------------+--------------------------+-------------------+---------------+--------------------+----------------+----------+-------------+---------------------+---------+---------+-------
  id          | bigint              | NO          |                                 |                          |                64 |             0 |                    |                | int8     | NO          |                     |         | b       |    20
  rank        | integer             | YES         |                                 |                          |                32 |             0 |                    |                | int4     | NO          |                     | rank    | b       |    23
- c1          | smallint            | NO          |                                 |                          |                16 |             0 |                    |                | int2     | NO          |                     |         | b       |    21
+ 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
@@ -73,9 +73,9 @@ func TestDriver_InspectTable(t *testing.T) {
  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          |                                 |                          |                24 |               |                    |                | float4   | NO          |                     |         | b       |   700
- c14         | json                | NO          |                                 |                          |                   |               |                    |                | json     | NO          |                     |         | b       |   114
- c15         | jsonb               | NO          |                                 |                          |                   |               |                    |                | jsonb    | NO          |                     |         | b       |  3802
+ 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
@@ -104,7 +104,7 @@ func TestDriver_InspectTable(t *testing.T) {
 				require.EqualValues([]*schema.Column{
 					{Name: "id", Type: &schema.ColumnType{Raw: "bigint", Type: &schema.IntegerType{T: "bigint"}}},
 					{Name: "rank", Type: &schema.ColumnType{Raw: "integer", Null: true, Type: &schema.IntegerType{T: "integer"}}, Attrs: []schema.Attr{&schema.Comment{Text: "rank"}}},
-					{Name: "c1", Type: &schema.ColumnType{Raw: "smallint", Type: &schema.IntegerType{T: "smallint"}}},
+					{Name: "c1", Type: &schema.ColumnType{Raw: "smallint", Type: &schema.IntegerType{T: "smallint"}}, Default: &schema.Literal{V: "1000"}},
 					{Name: "c2", Type: &schema.ColumnType{Raw: "bit", Type: &BitType{T: "bit", Len: 1}}},
 					{Name: "c3", Type: &schema.ColumnType{Raw: "bit varying", Type: &BitType{T: "bit varying", Len: 10}}},
 					{Name: "c4", Type: &schema.ColumnType{Raw: "boolean", Type: &schema.BoolType{T: "boolean"}}},
@@ -116,13 +116,13 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c10", Type: &schema.ColumnType{Raw: "date", Type: &schema.TimeType{T: "date"}}},
 					{Name: "c11", Type: &schema.ColumnType{Raw: "time with time zone", Type: &schema.TimeType{T: "time with time zone"}}},
 					{Name: "c12", Type: &schema.ColumnType{Raw: "double precision", Type: &schema.FloatType{T: "double precision", Precision: 53}}},
-					{Name: "c13", Type: &schema.ColumnType{Raw: "real", Type: &schema.FloatType{T: "real", Precision: 24}}},
-					{Name: "c14", Type: &schema.ColumnType{Raw: "json", Type: &schema.JSONType{T: "json"}}},
-					{Name: "c15", Type: &schema.ColumnType{Raw: "jsonb", Type: &schema.JSONType{T: "jsonb"}}},
+					{Name: "c13", Type: &schema.ColumnType{Raw: "real", Type: &schema.FloatType{T: "real", Precision: 24}}, Default: &schema.RawExpr{X: "random()"}},
+					{Name: "c14", Type: &schema.ColumnType{Raw: "json", Type: &schema.JSONType{T: "json"}}, Default: &schema.Literal{V: "'{}'"}},
+					{Name: "c15", Type: &schema.ColumnType{Raw: "jsonb", Type: &schema.JSONType{T: "jsonb"}}, Default: &schema.Literal{V: "'{}'"}},
 					{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: &SeqFuncExpr{X: "nextval('t1_c19_seq'::regclass)"}},
+					{Name: "c19", Type: &schema.ColumnType{Raw: "integer", Type: &schema.IntegerType{T: "integer"}}, Default: &schema.RawExpr{X: "nextval('t1_c19_seq'::regclass)"}},
 					{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{T: "int4[]"}}},