Repository: ariga/atlas
Pull request #1443 (2023-02-03 20:34:32)

sql/postgres: add internal and special types
See: https://www.postgresql.org/docs/current/datatype-oid.html
Ref: https://github.com/ariga/atlas/issues/1428


Patch:
diff --git a/sql/postgres/convert.go b/sql/postgres/convert.go
index 7b6ec0fe..d12c478e 100644
--- a/sql/postgres/convert.go
+++ b/sql/postgres/convert.go
@@ -62,7 +62,7 @@ func FormatType(t schema.Type) (string, error) {
 		}
 	case *schema.StringType:
 		switch f = strings.ToLower(t.T); f {
-		case TypeText:
+		case TypeText, typeName:
 		// CHAR(n) is alias for CHARACTER(n). If not length was
 		// specified, the definition is equivalent to CHARACTER(1).
 		case TypeChar, TypeCharacter:
@@ -149,6 +149,13 @@ func FormatType(t schema.Type) (string, error) {
 		default:
 			return "", fmt.Errorf("postgres: unsupported range type: %q", t.T)
 		}
+	case *OIDType:
+		switch f = strings.ToLower(t.T); f {
+		case typeOID, typeRegClass, typeRegCollation, typeRegConfig, typeRegDictionary, typeRegNamespace,
+			typeRegOper, typeRegOperator, typeRegProc, typeRegProcedure, typeRegRole, typeRegType:
+		default:
+			return "", fmt.Errorf("postgres: unsupported object identfier 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)
@@ -203,7 +210,7 @@ func columnType(c *columnDesc) (schema.Type, error) {
 		typ = &schema.BoolType{T: t}
 	case TypeBytea:
 		typ = &schema.BinaryType{T: t}
-	case TypeCharacter, TypeChar, TypeCharVar, TypeVarChar, TypeText:
+	case TypeCharacter, TypeChar, TypeCharVar, TypeVarChar, TypeText, typeName:
 		// A `character` column without length specifier is equivalent to `character(1)`,
 		// but `varchar` without length accepts strings of any size (same as `text`).
 		typ = &schema.StringType{T: t, Size: int(c.size)}
@@ -268,6 +275,9 @@ func columnType(c *columnDesc) (schema.Type, error) {
 	case TypeInt4Range, TypeInt4MultiRange, TypeInt8Range, TypeInt8MultiRange, TypeNumRange, TypeNumMultiRange,
 		TypeTSRange, TypeTSMultiRange, TypeTSTZRange, TypeTSTZMultiRange, TypeDateRange, TypeDateMultiRange:
 		typ = &RangeType{T: t}
+	case typeOID, typeRegClass, typeRegCollation, typeRegConfig, typeRegDictionary, typeRegNamespace,
+		typeRegOper, typeRegOperator, typeRegProc, typeRegProcedure, typeRegRole, typeRegType:
+		typ = &OIDType{T: t}
 	case TypeUserDefined:
 		typ = &UserDefinedType{T: c.fmtype}
 	default:
diff --git a/sql/postgres/diff.go b/sql/postgres/diff.go
index 74fd6edf..86d8d669 100644
--- a/sql/postgres/diff.go
+++ b/sql/postgres/diff.go
@@ -236,8 +236,8 @@ 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, *RangeType, *SerialType, *schema.SpatialType,
+	case *schema.BinaryType, *BitType, *schema.BoolType, *schema.DecimalType, *schema.FloatType, *IntervalType,
+		*schema.IntegerType, *schema.JSONType, *OIDType, *RangeType, *SerialType, *schema.SpatialType,
 		*schema.StringType, *schema.TimeType, *TextSearchType, *NetworkType, *UserDefinedType, *schema.UUIDType:
 		t1, err := FormatType(toT)
 		if err != nil {
diff --git a/sql/postgres/driver.go b/sql/postgres/driver.go
index a978c416..5ad263c3 100644
--- a/sql/postgres/driver.go
+++ b/sql/postgres/driver.go
@@ -333,6 +333,7 @@ const (
 	TypeCharVar   = "character varying"
 	TypeVarChar   = "varchar" // character varying
 	TypeText      = "text"
+	typeName      = "name" // internal type for object names
 
 	TypeSmallInt = "smallint"
 	TypeInteger  = "integer"
@@ -404,6 +405,20 @@ const (
 	TypeTSTZMultiRange = "tstzmultirange"
 	TypeDateRange      = "daterange"
 	TypeDateMultiRange = "datemultirange"
+
+	// PostgreSQL internal object types and their aliases.
+	typeOID           = "oid"
+	typeRegClass      = "regclass"
+	typeRegCollation  = "regcollation"
+	typeRegConfig     = "regconfig"
+	typeRegDictionary = "regdictionary"
+	typeRegNamespace  = "regnamespace"
+	typeRegOper       = "regoper"
+	typeRegOperator   = "regoperator"
+	typeRegProc       = "regproc"
+	typeRegProcedure  = "regprocedure"
+	typeRegRole       = "regrole"
+	typeRegType       = "regtype"
 )
 
 // List of supported index types.
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index 8f74a996..ca193e38 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -741,6 +741,12 @@ type (
 	// Defined here for backward compatibility reasons.
 	UUIDType = schema.UUIDType
 
+	// OIDType defines an object identifier type.
+	OIDType struct {
+		schema.Type
+		T string
+	}
+
 	// A XMLType defines an XML type.
 	XMLType struct {
 		schema.Type
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 3366fc87..a7d3fb31 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -734,6 +734,16 @@ var TypeRegistry = schemahcl.NewRegistry(
 		schemahcl.NewTypeSpec("hstore"),
 		schemahcl.NewTypeSpec("sql", schemahcl.WithAttributes(&schemahcl.TypeAttr{Name: "def", Required: true, Kind: reflect.String})),
 	),
+	// PostgreSQL internal and special types.
+	schemahcl.WithSpecs(func() (specs []*schemahcl.TypeSpec) {
+		for _, t := range []string{
+			typeOID, typeRegClass, typeRegCollation, typeRegConfig, typeRegDictionary, typeRegNamespace,
+			typeName, typeRegOper, typeRegOperator, typeRegProc, typeRegProcedure, typeRegRole, typeRegType,
+		} {
+			specs = append(specs, schemahcl.NewTypeSpec(t))
+		}
+		return specs
+	}()...),
 	schemahcl.WithSpecs(func() (specs []*schemahcl.TypeSpec) {
 		opts := []schemahcl.TypeSpecOption{
 			schemahcl.WithToSpec(func(t schema.Type) (*schemahcl.Type, error) {


Test patch:
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index 0c0189bb..21d82d26 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -1518,6 +1518,18 @@ func TestTypes(t *testing.T) {
 			typeExpr: `sql("varchar(2) [ 2 ] [  ]")`,
 			expected: &ArrayType{Type: &schema.StringType{T: "varchar", Size: 2}, T: "varchar(2)[]"},
 		},
+		{
+			typeExpr: "oid",
+			expected: &OIDType{T: typeOID},
+		},
+		{
+			typeExpr: "regclass",
+			expected: &OIDType{T: typeRegClass},
+		},
+		{
+			typeExpr: "name",
+			expected: &schema.StringType{T: typeName},
+		},
 	} {
 		t.Run(tt.typeExpr, func(t *testing.T) {
 			var test schema.Schema