Repository: ariga/atlas
Pull request #1312 (2022-11-25 23:46:18)

cmd/atlas: Update go.work.sum
The sumfile line for the newer version of sqlite isn't in this file which is breaking CI for me.

This is the result of running `go run main.go migrate apply --dir file://internal/cmdapi/testdata/sqlite --url sqlite://db?_fk=1` locally.

a8m: Hey @tmc, the CI is red. Any idea? 

Patch:
diff --git a/cmd/atlas/go.mod b/cmd/atlas/go.mod
index 3215f547..5af0ccb4 100644
--- a/cmd/atlas/go.mod
+++ b/cmd/atlas/go.mod
@@ -3,7 +3,7 @@ module ariga.io/atlas/cmd/atlas
 go 1.18
 
 require (
-	ariga.io/atlas v0.8.3-0.20221125101339-09fef822b161
+	ariga.io/atlas v0.8.4-0.20221128070800-1ce9c6ac2c02
 	entgo.io/ent v0.11.5-0.20221123165602-b6a475f066c4
 	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220816024939-bc8df83d7b9d
 	github.com/auxten/postgresql-parser v1.0.1
diff --git a/cmd/atlas/go.sum b/cmd/atlas/go.sum
index 87f3eecb..722af241 100644
--- a/cmd/atlas/go.sum
+++ b/cmd/atlas/go.sum
@@ -8,6 +8,8 @@ ariga.io/atlas v0.8.3-0.20221125080853-38c247d043e2 h1:gspypFhq2QR3sX/LyBu7GKTAA
 ariga.io/atlas v0.8.3-0.20221125080853-38c247d043e2/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU=
 ariga.io/atlas v0.8.3-0.20221125101339-09fef822b161 h1:c/cNpEeTta97cz70VKYHyRnXwo6+Qc7EMwHeJL+y4S4=
 ariga.io/atlas v0.8.3-0.20221125101339-09fef822b161/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU=
+ariga.io/atlas v0.8.4-0.20221128070800-1ce9c6ac2c02 h1:OvAZ24R0xneE3I8UR3bqCyiMAR/yETEbhI1XNzpkrUY=
+ariga.io/atlas v0.8.4-0.20221128070800-1ce9c6ac2c02/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 entgo.io/ent v0.11.1 h1:im67R+2W3Nee2bNS2YnoYz8oAF0Qz4AOlIvKRIAEISY=
diff --git a/cmd/atlas/go.work.sum b/cmd/atlas/go.work.sum
index b3ad39fe..0348b703 100644
--- a/cmd/atlas/go.work.sum
+++ b/cmd/atlas/go.work.sum
@@ -81,6 +81,7 @@ github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSo
 github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
 github.com/mattn/goveralls v0.0.2 h1:7eJB6EqsPhRVxvwEXGnqdO2sJI0PTsrWoTMXEk9/OQc=
 github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed h1:3dQJqqDouawQgl3gBE1PNHKFkJYGEuFb1DbSlaxdosE=
 github.com/mediocregopher/radix/v3 v3.3.0 h1:oacPXPKHJg0hcngVVrdtTnfGJiS+PtwoQwTBZGFlV4k=
diff --git a/doc/md/atlas-schema/sql-types.md b/doc/md/atlas-schema/sql-types.md
index edcaaa1d..2626edec 100644
--- a/doc/md/atlas-schema/sql-types.md
+++ b/doc/md/atlas-schema/sql-types.md
@@ -620,6 +620,54 @@ table "t" {
 }
 ```
 
+### Range
+
+PostgreSQL supports the creation of range types for storing range of values of some element type.
+Learn more about them in the [PostgreSQL website](https://www.postgresql.org/docs/current/rangetypes.html).
+
+
+```hcl
+table "t" {
+  schema = schema.test
+  column "r1" {
+    type = int4range
+  }
+  column "r2" {
+    type = int8range
+  }
+  column "r3" {
+    type = numrange
+  }
+  column "r4" {
+    type = tsrange
+  }
+  column "r5" {
+    type = tstzrange
+  }
+  column "r6" {
+    type = daterange
+  }
+  column "r7" {
+    type = int4multirange
+  }
+  column "r8" {
+    type = int8multirange
+  }
+  column "r9" {
+    type = nummultirange
+  }
+  column "r10" {
+    type = tsmultirange
+  }
+  column "r11" {
+    type = tstzmultirange
+  }
+  column "r12" {
+    type = datemultirange
+  }
+}
+```
+
 ### Serial
 
 PostgreSQL supports creating columns of types `smallserial`, `serial`, and `bigserial`. Note that these types are not
diff --git a/internal/integration/go.mod b/internal/integration/go.mod
index 93182592..14639391 100644
--- a/internal/integration/go.mod
+++ b/internal/integration/go.mod
@@ -7,7 +7,7 @@ replace ariga.io/atlas => ../../
 replace ariga.io/atlas/cmd/atlas => ../../cmd/atlas
 
 require (
-	ariga.io/atlas v0.8.3-0.20221125101339-09fef822b161
+	ariga.io/atlas v0.8.4-0.20221128070800-1ce9c6ac2c02
 	entgo.io/ent v0.11.5-0.20221123165602-b6a475f066c4
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/hashicorp/hcl/v2 v2.13.0
@@ -20,7 +20,7 @@ require (
 )
 
 require (
-	ariga.io/atlas/cmd/atlas v0.8.3-0.20221125101339-09fef822b161 // indirect
+	ariga.io/atlas/cmd/atlas v0.8.4-0.20221128070800-1ce9c6ac2c02 // indirect
 	github.com/agext/levenshtein v1.2.3 // indirect
 	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220816024939-bc8df83d7b9d // indirect
 	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
diff --git a/sql/mysql/driver.go b/sql/mysql/driver.go
index 27303eae..6d5eb8de 100644
--- a/sql/mysql/driver.go
+++ b/sql/mysql/driver.go
@@ -191,6 +191,11 @@ func (d *Driver) CheckClean(ctx context.Context, revT *migrate.TableIdent) error
 	return nil
 }
 
+// Version returns the version of the connected database.
+func (d *Driver) Version() string {
+	return string(d.conn.V)
+}
+
 func acquire(ctx context.Context, conn schema.ExecQuerier, name string, timeout time.Duration) error {
 	rows, err := conn.QueryContext(ctx, "SELECT GET_LOCK(?, ?)", name, int(timeout.Seconds()))
 	if err != nil {
diff --git a/sql/postgres/convert.go b/sql/postgres/convert.go
index 86f90428..3162cc70 100644
--- a/sql/postgres/convert.go
+++ b/sql/postgres/convert.go
@@ -142,6 +142,13 @@ func FormatType(t schema.Type) (string, error) {
 		f = strings.ToLower(t.T)
 	case *NetworkType:
 		f = strings.ToLower(t.T)
+	case *RangeType:
+		switch f = strings.ToLower(t.T); f {
+		case TypeInt4Range, TypeInt4MultiRange, TypeInt8Range, TypeInt8MultiRange, TypeNumRange, TypeNumMultiRange,
+			TypeTSRange, TypeTSMultiRange, TypeTSTZRange, TypeTSTZMultiRange, TypeDateRange, TypeDateMultiRange:
+		default:
+			return "", fmt.Errorf("postgres: unsupported range type: %q", t.T)
+		}
 	case *TextSearchType:
 		if f = strings.ToLower(t.T); f != TypeTSVector && f != TypeTSQuery {
 			return "", fmt.Errorf("postgres: unsupported text search type: %q", t.T)
@@ -258,6 +265,9 @@ func columnType(c *columnDesc) (schema.Type, error) {
 		}
 	case TypeTSVector, TypeTSQuery:
 		typ = &TextSearchType{T: t}
+	case TypeInt4Range, TypeInt4MultiRange, TypeInt8Range, TypeInt8MultiRange, TypeNumRange, TypeNumMultiRange,
+		TypeTSRange, TypeTSMultiRange, TypeTSTZRange, TypeTSTZMultiRange, TypeDateRange, TypeDateMultiRange:
+		typ = &RangeType{T: t}
 	case TypeUserDefined:
 		typ = &UserDefinedType{T: c.fmtype}
 	default:
diff --git a/sql/postgres/diff.go b/sql/postgres/diff.go
index 71059fb3..328f5c1b 100644
--- a/sql/postgres/diff.go
+++ b/sql/postgres/diff.go
@@ -232,7 +232,7 @@ func (d *diff) typeChanged(from, to *schema.Column) (bool, error) {
 	var changed bool
 	switch fromT := fromT.(type) {
 	case *schema.BinaryType, *BitType, *schema.BoolType, *schema.DecimalType, *schema.FloatType,
-		*IntervalType, *schema.IntegerType, *schema.JSONType, *SerialType, *schema.SpatialType,
+		*IntervalType, *schema.IntegerType, *schema.JSONType, *RangeType, *SerialType, *schema.SpatialType,
 		*schema.StringType, *schema.TimeType, *TextSearchType, *NetworkType, *UserDefinedType:
 		t1, err := FormatType(toT)
 		if err != nil {
diff --git a/sql/postgres/driver.go b/sql/postgres/driver.go
index 511e2652..21877b44 100644
--- a/sql/postgres/driver.go
+++ b/sql/postgres/driver.go
@@ -250,6 +250,11 @@ func (d *Driver) CheckClean(ctx context.Context, revT *migrate.TableIdent) error
 	return nil
 }
 
+// Version returns the version of the connected database.
+func (d *Driver) Version() string {
+	return strconv.Itoa(d.conn.version)
+}
+
 func acquire(ctx context.Context, conn schema.ExecQuerier, id uint32, timeout time.Duration) error {
 	switch {
 	// With timeout (context-based).
@@ -378,6 +383,19 @@ const (
 	TypeTSQuery     = "tsquery"
 	TypeTSVector    = "tsvector"
 	TypeUserDefined = "user-defined"
+
+	TypeInt4Range      = "int4range"
+	TypeInt4MultiRange = "int4multirange"
+	TypeInt8Range      = "int8range"
+	TypeInt8MultiRange = "int8multirange"
+	TypeNumRange       = "numrange"
+	TypeNumMultiRange  = "nummultirange"
+	TypeTSRange        = "tsrange"
+	TypeTSMultiRange   = "tsmultirange"
+	TypeTSTZRange      = "tstzrange"
+	TypeTSTZMultiRange = "tstzmultirange"
+	TypeDateRange      = "daterange"
+	TypeDateMultiRange = "datemultirange"
 )
 
 // List of supported index types.
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index eb5ebec5..f4fb7e19 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -711,6 +711,13 @@ type (
 		T string
 	}
 
+	// A RangeType defines a range type.
+	// https://www.postgresql.org/docs/current/rangetypes.html
+	RangeType struct {
+		schema.Type
+		T string
+	}
+
 	// A SerialType defines a serial type.
 	// https://postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL
 	SerialType struct {
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index e3d92ff1..6aec4d70 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -679,6 +679,18 @@ var TypeRegistry = schemahcl.NewRegistry(
 		schemahcl.NewTypeSpec(TypeMoney),
 		schemahcl.NewTypeSpec(TypeTSVector),
 		schemahcl.NewTypeSpec(TypeTSQuery),
+		schemahcl.NewTypeSpec(TypeInt4Range),
+		schemahcl.NewTypeSpec(TypeInt4MultiRange),
+		schemahcl.NewTypeSpec(TypeInt8Range),
+		schemahcl.NewTypeSpec(TypeInt8MultiRange),
+		schemahcl.NewTypeSpec(TypeNumRange),
+		schemahcl.NewTypeSpec(TypeNumMultiRange),
+		schemahcl.NewTypeSpec(TypeTSRange),
+		schemahcl.NewTypeSpec(TypeTSMultiRange),
+		schemahcl.NewTypeSpec(TypeTSTZRange),
+		schemahcl.NewTypeSpec(TypeTSTZMultiRange),
+		schemahcl.NewTypeSpec(TypeDateRange),
+		schemahcl.NewTypeSpec(TypeDateMultiRange),
 		schemahcl.NewTypeSpec("hstore"),
 		schemahcl.NewTypeSpec("sql", schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "def", Required: true, Kind: reflect.String})),
 	),
diff --git a/sql/sqlite/driver.go b/sql/sqlite/driver.go
index 5d800645..e32d690a 100644
--- a/sql/sqlite/driver.go
+++ b/sql/sqlite/driver.go
@@ -153,6 +153,11 @@ func (d *Driver) Lock(_ context.Context, name string, timeout time.Duration) (sc
 	return acquireLock(path, timeout)
 }
 
+// Version returns the version of the connected database.
+func (d *Driver) Version() string {
+	return d.conn.version
+}
+
 func acquireLock(path string, timeout time.Duration) (schema.UnlockFunc, error) {
 	lock, err := os.Create(path)
 	if err != nil {


Test patch:
diff --git a/internal/integration/testdata/postgres/column-range.txt b/internal/integration/testdata/postgres/column-range.txt
new file mode 100644
index 00000000..756a3139
--- /dev/null
+++ b/internal/integration/testdata/postgres/column-range.txt
@@ -0,0 +1,64 @@
+only postgres14 postgres15
+
+apply 1.hcl
+cmpshow t 1.sql
+
+-- 1.hcl --
+schema "$db" {}
+
+table "t" {
+  schema = schema.$db
+  column "r1" {
+    type = int4range
+  }
+  column "r2" {
+    type = int8range
+  }
+  column "r3" {
+    type = numrange
+  }
+  column "r4" {
+    type = tsrange
+  }
+  column "r5" {
+    type = tstzrange
+  }
+  column "r6" {
+    type = daterange
+  }
+  column "r7" {
+    type = int4multirange
+  }
+  column "r8" {
+    type = int8multirange
+  }
+  column "r9" {
+    type = nummultirange
+  }
+  column "r10" {
+    type = tsmultirange
+  }
+  column "r11" {
+    type = tstzmultirange
+  }
+  column "r12" {
+    type = datemultirange
+  }
+}
+
+-- 1.sql --
+            Table "script_column_range.t"
+ Column |      Type      | Collation | Nullable | Default
+--------+----------------+-----------+----------+---------
+ r1     | int4range      |           | not null |
+ r2     | int8range      |           | not null |
+ r3     | numrange       |           | not null |
+ r4     | tsrange        |           | not null |
+ r5     | tstzrange      |           | not null |
+ r6     | daterange      |           | not null |
+ r7     | int4multirange |           | not null |
+ r8     | int8multirange |           | not null |
+ r9     | nummultirange  |           | not null |
+ r10    | tsmultirange   |           | not null |
+ r11    | tstzmultirange |           | not null |
+ r12    | datemultirange |           | not null |
diff --git a/sql/mysql/driver_test.go b/sql/mysql/driver_test.go
index 610ad873..b88b6f53 100644
--- a/sql/mysql/driver_test.go
+++ b/sql/mysql/driver_test.go
@@ -171,6 +171,18 @@ func TestDriver_CheckClean(t *testing.T) {
 	require.EqualError(t, err, `sql/migrate: connected database is not clean: found multiple tables: 2`)
 }
 
+func TestDriver_Version(t *testing.T) {
+	db, m, err := sqlmock.New()
+	require.NoError(t, err)
+	mock{m}.version("8.0.13")
+	drv, err := Open(db)
+	require.NoError(t, err)
+
+	type vr interface{ Version() string }
+	require.Implements(t, (*vr)(nil), drv)
+	require.Equal(t, "8.0.13", drv.(vr).Version())
+}
+
 type mockInspector struct {
 	schema.Inspector
 	realm  *schema.Realm
diff --git a/sql/postgres/driver_test.go b/sql/postgres/driver_test.go
index 997c254a..e6286a00 100644
--- a/sql/postgres/driver_test.go
+++ b/sql/postgres/driver_test.go
@@ -144,6 +144,18 @@ func TestDriver_CheckClean(t *testing.T) {
 	require.NoError(t, err)
 }
 
+func TestDriver_Version(t *testing.T) {
+	db, m, err := sqlmock.New()
+	require.NoError(t, err)
+	mock{m}.version("130000")
+	drv, err := Open(db)
+	require.NoError(t, err)
+
+	type vr interface{ Version() string }
+	require.Implements(t, (*vr)(nil), drv)
+	require.Equal(t, "130000", drv.(vr).Version())
+}
+
 type mockInspector struct {
 	schema.Inspector
 	realm  *schema.Realm
diff --git a/sql/postgres/inspect_test.go b/sql/postgres/inspect_test.go
index 8b27f3e2..57593367 100644
--- a/sql/postgres/inspect_test.go
+++ b/sql/postgres/inspect_test.go
@@ -82,6 +82,8 @@ func TestDriver_InspectTable(t *testing.T) {
  users       |  c35         | character                   | domain_char         | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | d       |  16774  |  e      | 16779
  users       |  c36         | tsvector                    | tsvector            | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  16774  |         | 16779
  users       |  c37         | tsquery                     | tsquery             | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | b       |  16774  |         | 16779
+ users       |  c38         | datemultirange              | datemultirange      | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | m       |         |         | 4535
+ users       |  c39         | numrange                    | numrange            | NO          |                                        |                          |                   |                    |               |                     |                    |                | NO          |                |                    |                  |                     |                       |         | m       |         |         | 4536
 `))
 				m.ExpectQuery(sqltest.Escape(`SELECT enumtypid, enumlabel FROM pg_enum WHERE enumtypid IN ($1, $2)`)).
 					WithArgs(16774, 16775).
@@ -140,6 +142,8 @@ func TestDriver_InspectTable(t *testing.T) {
 					{Name: "c35", Type: &schema.ColumnType{Raw: "character", Type: &UserDefinedType{T: "domain_char"}}},
 					{Name: "c36", Type: &schema.ColumnType{Raw: "tsvector", Type: &TextSearchType{T: "tsvector"}}},
 					{Name: "c37", Type: &schema.ColumnType{Raw: "tsquery", Type: &TextSearchType{T: "tsquery"}}},
+					{Name: "c38", Type: &schema.ColumnType{Raw: "datemultirange", Type: &RangeType{T: "datemultirange"}}},
+					{Name: "c39", Type: &schema.ColumnType{Raw: "numrange", Type: &RangeType{T: "numrange"}}},
 				}, t.Columns)
 			},
 		},
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index 59a930e5..220984c0 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -1363,6 +1363,54 @@ func TestTypes(t *testing.T) {
 			typeExpr: "money",
 			expected: &CurrencyType{T: TypeMoney},
 		},
+		{
+			typeExpr: "int4range",
+			expected: &RangeType{T: TypeInt4Range},
+		},
+		{
+			typeExpr: "int4multirange",
+			expected: &RangeType{T: TypeInt4MultiRange},
+		},
+		{
+			typeExpr: "int8range",
+			expected: &RangeType{T: TypeInt8Range},
+		},
+		{
+			typeExpr: "int8multirange",
+			expected: &RangeType{T: TypeInt8MultiRange},
+		},
+		{
+			typeExpr: "numrange",
+			expected: &RangeType{T: TypeNumRange},
+		},
+		{
+			typeExpr: "nummultirange",
+			expected: &RangeType{T: TypeNumMultiRange},
+		},
+		{
+			typeExpr: "tsrange",
+			expected: &RangeType{T: TypeTSRange},
+		},
+		{
+			typeExpr: "tsmultirange",
+			expected: &RangeType{T: TypeTSMultiRange},
+		},
+		{
+			typeExpr: "tstzrange",
+			expected: &RangeType{T: TypeTSTZRange},
+		},
+		{
+			typeExpr: "tstzmultirange",
+			expected: &RangeType{T: TypeTSTZMultiRange},
+		},
+		{
+			typeExpr: "daterange",
+			expected: &RangeType{T: TypeDateRange},
+		},
+		{
+			typeExpr: "datemultirange",
+			expected: &RangeType{T: TypeDateMultiRange},
+		},
 		{
 			typeExpr: `sql("int[]")`,
 			expected: &ArrayType{Type: &schema.IntegerType{T: "int"}, T: "int[]"},
diff --git a/sql/sqlite/driver_test.go b/sql/sqlite/driver_test.go
index 3cb0235c..adffa8a1 100644
--- a/sql/sqlite/driver_test.go
+++ b/sql/sqlite/driver_test.go
@@ -13,8 +13,9 @@ import (
 	"time"
 
 	"ariga.io/atlas/sql/migrate"
-
 	"ariga.io/atlas/sql/schema"
+
+	"github.com/DATA-DOG/go-sqlmock"
 	"github.com/stretchr/testify/require"
 )
 
@@ -76,6 +77,18 @@ func TestDriver_CheckClean(t *testing.T) {
 	require.EqualError(t, err, `sql/migrate: connected database is not clean: found multiple tables: 2`)
 }
 
+func TestDriver_Version(t *testing.T) {
+	db, m, err := sqlmock.New()
+	require.NoError(t, err)
+	mock{m}.systemVars("3.36.0")
+	drv, err := Open(db)
+	require.NoError(t, err)
+
+	type vr interface{ Version() string }
+	require.Implements(t, (*vr)(nil), drv)
+	require.Equal(t, "3.36.0", drv.(vr).Version())
+}
+
 type mockInspector struct {
 	schema.Inspector
 	realm *schema.Realm