Repository: ariga/atlas
Pull request #2133 (2023-09-28 14:44:25)

doc/website: bump website version to 0.0.28


Patch:
diff --git a/cmd/atlas/internal/cmdapi/migrate.go b/cmd/atlas/internal/cmdapi/migrate.go
index 672870e5..7f089562 100644
--- a/cmd/atlas/internal/cmdapi/migrate.go
+++ b/cmd/atlas/internal/cmdapi/migrate.go
@@ -195,7 +195,7 @@ func migrateApplyRun(cmd *cobra.Command, args []string, flags migrateApplyFlags,
 		return err
 	}
 	// Setup reporting info.
-	report := cmdlog.NewMigrateApply(client, dir)
+	report := cmdlog.NewMigrateApply(ctx, client, dir)
 	mr.Init(client, report, mrrw)
 	// If cloud reporting is enabled, and we cannot obtain the current
 	// target identifier, abort and report it to the user.
@@ -1115,7 +1115,7 @@ func migrateSetRun(cmd *cobra.Command, args []string, flags migrateSetFlags) (re
 	default:
 		return fmt.Errorf("accepts 1 arg(s), received %d", n)
 	}
-	log := &cmdlog.MigrateSet{}
+	log := &cmdlog.MigrateSet{Context: ctx}
 	for _, r := range revs {
 		// Check all existing revisions and ensure they precede the given version. If we encounter a partially
 		// applied revision, or one with errors, mark them "fixed".
diff --git a/cmd/atlas/internal/cmdapi/schema.go b/cmd/atlas/internal/cmdapi/schema.go
index 141a1c0d..f00f84a6 100644
--- a/cmd/atlas/internal/cmdapi/schema.go
+++ b/cmd/atlas/internal/cmdapi/schema.go
@@ -204,7 +204,7 @@ func schemaApplyRun(cmd *cobra.Command, flags schemaApplyFlags, env *Env) error
 		} else {
 			cause = &cmdlog.StmtError{Text: err.Error()}
 		}
-		err1 := format.Execute(out, cmdlog.NewSchemaApply(cmdlog.NewEnv(client, nil), plan.Changes[:applied], plan.Changes[applied:], cause))
+		err1 := format.Execute(out, cmdlog.NewSchemaApply(ctx, cmdlog.NewEnv(client, nil), plan.Changes[:applied], plan.Changes[applied:], cause))
 		return errors.Join(err, err1)
 	default:
 		switch err := summary(cmd, client, changes, format); {
@@ -417,6 +417,7 @@ func schemaDiffRun(cmd *cobra.Command, _ []string, flags schemaDiffFlags, env *E
 	}
 	return format.Execute(cmd.OutOrStdout(), &cmdlog.SchemaDiff{
 		Client:  c,
+		Context: ctx,
 		From:    diff.from,
 		To:      diff.to,
 		Changes: diff.changes,
@@ -521,8 +522,9 @@ func schemaInspectRun(cmd *cobra.Command, _ []string, flags schemaInspectFlags)
 		return err
 	}
 	return format.Execute(cmd.OutOrStdout(), &cmdlog.SchemaInspect{
-		Client: client,
-		Realm:  s,
+		Client:  client,
+		Context: ctx,
+		Realm:   s,
 	})
 }
 
@@ -693,7 +695,7 @@ func summary(cmd *cobra.Command, c *sqlclient.Client, changes []schema.Change, t
 	}
 	return t.Execute(
 		cmd.OutOrStdout(),
-		cmdlog.NewSchemaPlan(cmdlog.NewEnv(c, nil), p.Changes, nil),
+		cmdlog.NewSchemaPlan(cmd.Context(), cmdlog.NewEnv(c, nil), p.Changes, nil),
 	)
 }
 
diff --git a/cmd/atlas/internal/cmdlog/cmdlog.go b/cmd/atlas/internal/cmdlog/cmdlog.go
index 4e5cbeed..dae1f3b5 100644
--- a/cmd/atlas/internal/cmdlog/cmdlog.go
+++ b/cmd/atlas/internal/cmdlog/cmdlog.go
@@ -126,17 +126,18 @@ Last migration attempt had errors:
 
 // MigrateStatus contains a summary of the migration status of a database.
 type MigrateStatus struct {
-	Env       `json:"Env"`
-	Available Files               `json:"Available,omitempty"` // Available migration files
-	Pending   Files               `json:"Pending,omitempty"`   // Pending migration files
-	Applied   []*migrate.Revision `json:"Applied,omitempty"`   // Applied migration files
-	Current   string              `json:"Current,omitempty"`   // Current migration version
-	Next      string              `json:"Next,omitempty"`      // Next migration version
-	Count     int                 `json:"Count,omitempty"`     // Count of applied statements of the last revision
-	Total     int                 `json:"Total,omitempty"`     // Total statements of the last migration
-	Status    string              `json:"Status,omitempty"`    // Status of migration (OK, PENDING)
-	Error     string              `json:"Error,omitempty"`     // Last Error that occurred
-	SQL       string              `json:"SQL,omitempty"`       // SQL that caused the last Error
+	context.Context `json:"-"`
+	Env             `json:"Env"`
+	Available       Files               `json:"Available,omitempty"` // Available migration files
+	Pending         Files               `json:"Pending,omitempty"`   // Pending migration files
+	Applied         []*migrate.Revision `json:"Applied,omitempty"`   // Applied migration files
+	Current         string              `json:"Current,omitempty"`   // Current migration version
+	Next            string              `json:"Next,omitempty"`      // Next migration version
+	Count           int                 `json:"Count,omitempty"`     // Count of applied statements of the last revision
+	Total           int                 `json:"Total,omitempty"`     // Total statements of the last migration
+	Status          string              `json:"Status,omitempty"`    // Status of migration (OK, PENDING)
+	Error           string              `json:"Error,omitempty"`     // Last Error that occurred
+	SQL             string              `json:"SQL,omitempty"`       // SQL that caused the last Error
 }
 
 // Left returns the amount of statements left to apply (if any).
@@ -221,6 +222,7 @@ Current version is {{ cyan .Current.Version }} ({{ .Summary }}):
 type (
 	// MigrateSet contains a summary of the migrate set command.
 	MigrateSet struct {
+		context.Context `json:"-"`
 		// Revisions that were added, removed or updated.
 		Revisions []RevisionOp `json:"Revisions,omitempty"`
 		// Current version in the revisions table.
@@ -326,12 +328,13 @@ type (
 	// MigrateApply contains a summary of a migration applying attempt on a database.
 	MigrateApply struct {
 		Env
-		Pending Files          `json:"Pending,omitempty"` // Pending migration files
-		Applied []*AppliedFile `json:"Applied,omitempty"` // Applied files
-		Current string         `json:"Current,omitempty"` // Current migration version
-		Target  string         `json:"Target,omitempty"`  // Target migration version
-		Start   time.Time
-		End     time.Time
+		context.Context `json:"-"`
+		Pending         Files          `json:"Pending,omitempty"` // Pending migration files
+		Applied         []*AppliedFile `json:"Applied,omitempty"` // Applied files
+		Current         string         `json:"Current,omitempty"` // Current migration version
+		Target          string         `json:"Target,omitempty"`  // Target migration version
+		Start           time.Time
+		End             time.Time
 		// Error is set even then, if it was not caused by a statement in a migration file,
 		// but by Atlas, e.g. when committing or rolling back a transaction.
 		Error string `json:"Error,omitempty"`
@@ -349,10 +352,11 @@ type (
 )
 
 // NewMigrateApply returns an MigrateApply.
-func NewMigrateApply(client *sqlclient.Client, dir migrate.Dir) *MigrateApply {
+func NewMigrateApply(ctx context.Context, client *sqlclient.Client, dir migrate.Dir) *MigrateApply {
 	return &MigrateApply{
-		Env:   NewEnv(client, dir),
-		Start: time.Now(),
+		Context: ctx,
+		Env:     NewEnv(client, dir),
+		Start:   time.Now(),
 	}
 }
 
@@ -471,7 +475,8 @@ type (
 	// SchemaApply contains a summary of a 'schema apply' execution on a database.
 	SchemaApply struct {
 		Env
-		Changes Changes `json:"Changes,omitempty"`
+		context.Context `json:"-"`
+		Changes         Changes `json:"Changes,omitempty"`
 		// General error that occurred during execution.
 		// e.g., when committing or rolling back a transaction.
 		Error string `json:"Error,omitempty"`
@@ -485,9 +490,10 @@ type (
 )
 
 // NewSchemaApply returns a SchemaApply.
-func NewSchemaApply(env Env, applied, pending []*migrate.Change, err *StmtError) *SchemaApply {
+func NewSchemaApply(ctx context.Context, env Env, applied, pending []*migrate.Change, err *StmtError) *SchemaApply {
 	return &SchemaApply{
-		Env: env,
+		Env:     env,
+		Context: ctx,
 		Changes: Changes{
 			Applied: applied,
 			Pending: pending,
@@ -497,8 +503,8 @@ func NewSchemaApply(env Env, applied, pending []*migrate.Change, err *StmtError)
 }
 
 // NewSchemaPlan returns a SchemaApply only with pending changes.
-func NewSchemaPlan(env Env, pending []*migrate.Change, err *StmtError) *SchemaApply {
-	return NewSchemaApply(env, nil, pending, err)
+func NewSchemaPlan(ctx context.Context, env Env, pending []*migrate.Change, err *StmtError) *SchemaApply {
+	return NewSchemaApply(ctx, env, nil, pending, err)
 }
 
 // MarshalJSON implements json.Marshaler.
@@ -520,6 +526,7 @@ func (c Changes) MarshalJSON() ([]byte, error) {
 
 // SchemaInspect contains a summary of the 'schema inspect' command.
 type SchemaInspect struct {
+	context.Context   `json:"-"`
 	*sqlclient.Client `json:"-"`
 	Realm             *schema.Realm `json:"Schema,omitempty"` // Inspected realm.
 }
@@ -693,14 +700,15 @@ func sqlInspect(report *SchemaInspect, indent ...string) (string, error) {
 			changes = append(changes, &schema.AddObject{O: o})
 		}
 	}
-	return fmtPlan(report.Client, changes, indent)
+	return fmtPlan(report.Context, report.Client, changes, indent)
 }
 
 // SchemaDiff contains a summary of the 'schema diff' command.
 type SchemaDiff struct {
-	*sqlclient.Client
-	From, To *schema.Realm
-	Changes  []schema.Change
+	context.Context   `json:"-"`
+	*sqlclient.Client `json:"-"`
+	From, To          *schema.Realm
+	Changes           []schema.Change
 }
 
 var (
@@ -726,14 +734,14 @@ func (s *SchemaDiff) MarshalSQL(indent ...string) (string, error) {
 }
 
 func sqlDiff(diff *SchemaDiff, indent ...string) (string, error) {
-	return fmtPlan(diff.Client, diff.Changes, indent)
+	return fmtPlan(diff.Context, diff.Client, diff.Changes, indent)
 }
 
-func fmtPlan(client *sqlclient.Client, changes schema.Changes, indent []string) (string, error) {
+func fmtPlan(ctx context.Context, client *sqlclient.Client, changes schema.Changes, indent []string) (string, error) {
 	if len(indent) > 1 {
 		return "", fmt.Errorf("unexpected number of arguments: %d", len(indent))
 	}
-	plan, err := client.PlanChanges(context.Background(), "plan", changes, func(o *migrate.PlanOptions) {
+	plan, err := client.PlanChanges(ctx, "plan", changes, func(o *migrate.PlanOptions) {
 		o.Mode = migrate.PlanModeDump
 		// Disable tables qualifier in schema-mode.
 		if client.URL.Schema != "" {
diff --git a/doc/website/package-lock.json b/doc/website/package-lock.json
index be98f861..ba0fddc6 100644
--- a/doc/website/package-lock.json
+++ b/doc/website/package-lock.json
@@ -8,7 +8,7 @@
       "name": "website",
       "version": "0.0.0",
       "dependencies": {
-        "@ariga/atlas-website": "0.0.23",
+        "@ariga/atlas-website": "0.0.28",
         "@docusaurus/core": "^2.0.0-rc.1",
         "@docusaurus/plugin-client-redirects": "^2.4.1",
         "@docusaurus/plugin-ideal-image": "^2.4.1",
@@ -296,9 +296,9 @@
       }
     },
     "node_modules/@ariga/atlas-website": {
-      "version": "0.0.23",
-      "resolved": "https://registry.npmjs.org/@ariga/atlas-website/-/atlas-website-0.0.23.tgz",
-      "integrity": "sha512-q7Em13tS/8kXhLGmREOKeAv/Cg4+GgMBPMxzUSNb8bGf3fpcX/iiAqxmSTisKXahGmMct7mm/hVNa1NFocMpKg==",
+      "version": "0.0.28",
+      "resolved": "https://registry.npmjs.org/@ariga/atlas-website/-/atlas-website-0.0.28.tgz",
+      "integrity": "sha512-jMduDgTn+3okBCPz0R9IzsQGnpO9Z/iICmOQTtHxdYflIovgu0wB7pkfHAuteHuT/0jQxobaY57guwL2vmaXUQ==",
       "dependencies": {
         "@amplitude/analytics-browser": "^1.9.4",
         "@types/amplitude-js": "^8.16.2",
@@ -24578,9 +24578,9 @@
       }
     },
     "@ariga/atlas-website": {
-      "version": "0.0.23",
-      "resolved": "https://registry.npmjs.org/@ariga/atlas-website/-/atlas-website-0.0.23.tgz",
-      "integrity": "sha512-q7Em13tS/8kXhLGmREOKeAv/Cg4+GgMBPMxzUSNb8bGf3fpcX/iiAqxmSTisKXahGmMct7mm/hVNa1NFocMpKg==",
+      "version": "0.0.28",
+      "resolved": "https://registry.npmjs.org/@ariga/atlas-website/-/atlas-website-0.0.28.tgz",
+      "integrity": "sha512-jMduDgTn+3okBCPz0R9IzsQGnpO9Z/iICmOQTtHxdYflIovgu0wB7pkfHAuteHuT/0jQxobaY57guwL2vmaXUQ==",
       "requires": {
         "@amplitude/analytics-browser": "^1.9.4",
         "@types/amplitude-js": "^8.16.2",
diff --git a/doc/website/package.json b/doc/website/package.json
index bd19bcc7..12c28dff 100644
--- a/doc/website/package.json
+++ b/doc/website/package.json
@@ -16,7 +16,7 @@
     "invalidate-cdn": "aws cloudfront create-invalidation --distribution-id E3VOJYSV9YO33D --paths \"/*\""
   },
   "dependencies": {
-    "@ariga/atlas-website": "0.0.26",
+    "@ariga/atlas-website": "0.0.28",
     "@docusaurus/core": "^2.0.0-rc.1",
     "@docusaurus/plugin-client-redirects": "^2.4.1",
     "@docusaurus/plugin-ideal-image": "^2.4.1",
diff --git a/schemahcl/spec.go b/schemahcl/spec.go
index ba87124e..bc4c105b 100644
--- a/schemahcl/spec.go
+++ b/schemahcl/spec.go
@@ -460,6 +460,16 @@ func RefAttr(k string, v *Ref) *Attr {
 	}
 }
 
+// RefValue is a helper method for constructing a cty.Value that contains a Ref value.
+func RefValue(v string) cty.Value {
+	return cty.CapsuleVal(ctyRefType, &Ref{V: v})
+}
+
+// TypeValue is a helper method for constructing a cty.Value that contains a Type value.
+func TypeValue(t *Type) cty.Value {
+	return cty.CapsuleVal(ctyTypeSpec, t)
+}
+
 // StringsAttr is a helper method for constructing *schemahcl.Attr instances that contain list strings.
 func StringsAttr(k string, vs ...string) *Attr {
 	vv := make([]cty.Value, len(vs))
diff --git a/sql/internal/specutil/convert.go b/sql/internal/specutil/convert.go
index 0fcbd8c6..17c433eb 100644
--- a/sql/internal/specutil/convert.go
+++ b/sql/internal/specutil/convert.go
@@ -47,12 +47,22 @@ type (
 		Views        []*sqlspec.View
 		Materialized []*sqlspec.View
 	}
+
 	// ScanFuncs represents a set of scan functions
 	// used to convert the HCL document to the Realm.
 	ScanFuncs struct {
 		Table ConvertTableFunc
 		View  ConvertViewFunc
 	}
+
+	// Funcs represents a set of spec functions
+	// used to convert the Realm to an HCL document.
+	Funcs struct {
+		Table TableSpecFunc
+		View  ViewSpecFunc
+		Func  func(*schema.Func) (*sqlspec.Func, error)
+		Proc  func(*schema.Proc) (*sqlspec.Func, error)
+	}
 )
 
 const (
@@ -441,27 +451,27 @@ func linkForeignKeys(tbl *schema.Table, fks []*sqlspec.ForeignKey) error {
 }
 
 // FromSchema converts a schema.Schema into sqlspec.Schema and []sqlspec.Table.
-func FromSchema(s *schema.Schema, specT TableSpecFunc, specV ViewSpecFunc) (*SchemaSpec, error) {
-	var (
-		spec = &sqlspec.Schema{
+func FromSchema(s *schema.Schema, funcs Funcs) (*SchemaSpec, error) {
+	spec := &SchemaSpec{
+		Schema: &sqlspec.Schema{
 			Name: s.Name,
-		}
-		views  = make([]*sqlspec.View, 0, len(s.Views))
-		mviews = make([]*sqlspec.View, 0, len(s.Views))
-		tables = make([]*sqlspec.Table, 0, len(s.Tables))
-	)
+		},
+		Tables:       make([]*sqlspec.Table, 0, len(s.Tables)),
+		Views:        make([]*sqlspec.View, 0, len(s.Views)),
+		Materialized: make([]*sqlspec.View, 0, len(s.Views)),
+	}
 	for _, t := range s.Tables {
-		table, err := specT(t)
+		table, err := funcs.Table(t)
 		if err != nil {
 			return nil, err
 		}
 		if s.Name != "" {
 			table.Schema = SchemaRef(s.Name)
 		}
-		tables = append(tables, table)
+		spec.Tables = append(spec.Tables, table)
 	}
 	for _, v := range s.Views {
-		view, err := specV(v)
+		view, err := funcs.View(v)
 		if err != nil {
 			return nil, err
 		}
@@ -469,18 +479,37 @@ func FromSchema(s *schema.Schema, specT TableSpecFunc, specV ViewSpecFunc) (*Sch
 			view.Schema = SchemaRef(s.Name)
 		}
 		if v.Materialized() {
-			mviews = append(mviews, view)
+			spec.Materialized = append(spec.Materialized, view)
 		} else {
-			views = append(views, view)
+			spec.Views = append(spec.Views, view)
 		}
 	}
-	convertCommentFromSchema(s.Attrs, &spec.Extra.Attrs)
-	return &SchemaSpec{
-		Schema:       spec,
-		Tables:       tables,
-		Views:        views,
-		Materialized: mviews,
-	}, nil
+	if funcs.Func != nil {
+		for _, f := range s.Funcs {
+			fn, err := funcs.Func(f)
+			if err != nil {
+				return nil, err
+			}
+			if s.Name != "" {
+				fn.Schema = SchemaRef(s.Name)
+			}
+			spec.Funcs = append(spec.Funcs, fn)
+		}
+	}
+	if funcs.Proc != nil {
+		for _, p := range s.Procs {
+			pr, err := funcs.Proc(p)
+			if err != nil {
+				return nil, err
+			}
+			if s.Name != "" {
+				pr.Schema = SchemaRef(s.Name)
+			}
+			spec.Procs = append(spec.Procs, pr)
+		}
+	}
+	convertCommentFromSchema(s.Attrs, &spec.Schema.Extra.Attrs)
+	return spec, nil
 }
 
 // FromTable converts a schema.Table to a sqlspec.Table.
diff --git a/sql/internal/specutil/spec.go b/sql/internal/specutil/spec.go
index 8b5ccc3f..6967fd1c 100644
--- a/sql/internal/specutil/spec.go
+++ b/sql/internal/specutil/spec.go
@@ -27,12 +27,16 @@ type (
 		Schema       *sqlspec.Schema
 		Tables       []*sqlspec.Table
 		Views        []*sqlspec.View
+		Funcs        []*sqlspec.Func
+		Procs        []*sqlspec.Func
 		Materialized []*sqlspec.View
 	}
 	doc struct {
 		Tables       []*sqlspec.Table  `spec:"table"`
 		Views        []*sqlspec.View   `spec:"view"`
 		Materialized []*sqlspec.View   `spec:"materialized"`
+		Funcs        []*sqlspec.Func   `spec:"function"`
+		Procs        []*sqlspec.Func   `spec:"procedure"`
 		Schemas      []*sqlspec.Schema `spec:"schema"`
 	}
 )
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index b516cd66..bfa073fe 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -224,7 +224,7 @@ func ValuesEqual(v1, v2 []string) bool {
 // ModeInspectSchema returns the InspectMode or its default.
 func ModeInspectSchema(o *schema.InspectOptions) schema.InspectMode {
 	if o == nil || o.Mode == 0 {
-		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews
+		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews | schema.InspectFuncs
 	}
 	return o.Mode
 }
@@ -232,7 +232,7 @@ func ModeInspectSchema(o *schema.InspectOptions) schema.InspectMode {
 // ModeInspectRealm returns the InspectMode or its default.
 func ModeInspectRealm(o *schema.InspectRealmOption) schema.InspectMode {
 	if o == nil || o.Mode == 0 {
-		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews
+		return schema.InspectSchemas | schema.InspectTables | schema.InspectViews | schema.InspectFuncs
 	}
 	return o.Mode
 }
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index 9ee5dc95..b3d58f90 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -276,7 +276,10 @@ func convertColumnType(spec *sqlspec.Column) (schema.Type, error) {
 
 // schemaSpec converts from a concrete MySQL schema to Atlas specification.
 func schemaSpec(s *schema.Schema) (*specutil.SchemaSpec, error) {
-	spec, err := specutil.FromSchema(s, tableSpec, viewSpec)
+	spec, err := specutil.FromSchema(s, specutil.Funcs{
+		Table: tableSpec,
+		View:  viewSpec,
+	})
 	if err != nil {
 		return nil, err
 	}
diff --git a/sql/postgres/convert.go b/sql/postgres/convert.go
index ade320f7..6ac49020 100644
--- a/sql/postgres/convert.go
+++ b/sql/postgres/convert.go
@@ -164,6 +164,8 @@ func FormatType(t schema.Type) (string, error) {
 		f = strings.ToLower(t.T)
 	case *XMLType:
 		f = strings.ToLower(t.T)
+	case *PseudoType:
+		f = strings.ToLower(t.T)
 	case *schema.UnsupportedType:
 		return "", fmt.Errorf("postgres: unsupported type: %q", t.T)
 	default:
@@ -280,6 +282,9 @@ func columnType(c *columnDesc) (schema.Type, error) {
 		typ = &OIDType{T: t}
 	case TypeUserDefined:
 		typ = &UserDefinedType{T: c.fmtype}
+	case typeAny, typeAnyElement, typeAnyArray, typeAnyNonArray, typeAnyEnum, typeInternal,
+		typeRecord, typeTrigger, typeVoid, typeUnknown:
+		typ = &PseudoType{T: t}
 	default:
 		typ = &schema.UnsupportedType{T: t}
 	}
diff --git a/sql/postgres/driver.go b/sql/postgres/driver.go
index ef78e730..bbdfa2cf 100644
--- a/sql/postgres/driver.go
+++ b/sql/postgres/driver.go
@@ -435,6 +435,18 @@ const (
 	typeRegProcedure  = "regprocedure"
 	typeRegRole       = "regrole"
 	typeRegType       = "regtype"
+
+	// PostgreSQL of supported pseudo-types.
+	typeAny         = "any"
+	typeAnyElement  = "anyelement"
+	typeAnyArray    = "anyarray"
+	typeAnyNonArray = "anynonarray"
+	typeAnyEnum     = "anyenum"
+	typeInternal    = "internal"
+	typeRecord      = "record"
+	typeTrigger     = "trigger"
+	typeVoid        = "void"
+	typeUnknown     = "unknown"
 )
 
 // List of supported index types.
diff --git a/sql/postgres/driver_oss.go b/sql/postgres/driver_oss.go
index 4534c13e..d1034a42 100644
--- a/sql/postgres/driver_oss.go
+++ b/sql/postgres/driver_oss.go
@@ -10,15 +10,26 @@ import (
 	"context"
 
 	"ariga.io/atlas/schemahcl"
+	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/schema"
 )
 
-var specOptions []schemahcl.Option
+var (
+	specOptions []schemahcl.Option
+	specFuncs   = specutil.Funcs{
+		Table: tableSpec,
+		View:  viewSpec,
+	}
+)
 
 func (*inspect) inspectViews(context.Context, *schema.Realm, *schema.InspectOptions) error {
 	return nil // unimplemented.
 }
 
+func (i *inspect) inspectFuncs(context.Context, *schema.Realm, *schema.InspectOptions) error {
+	return nil // unimplemented.
+}
+
 func (*state) addView(*schema.AddView) error {
 	return nil // unimplemented.
 }
@@ -38,3 +49,7 @@ func (*state) renameView(*schema.RenameView) {
 func (d *diff) ViewAttrChanged(_, _ *schema.View) bool {
 	return false // unimplemented.
 }
+
+func verifyChanges(context.Context, []schema.Change) error {
+	return nil // unimplemented.
+}
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index 27f431ca..a38580f5 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -48,6 +48,11 @@ func (i *inspect) InspectRealm(ctx context.Context, opts *schema.InspectRealmOpt
 				return nil, err
 			}
 		}
+		if mode.Is(schema.InspectFuncs) {
+			if err := i.inspectFuncs(ctx, r, nil); err != nil {
+				return nil, err
+			}
+		}
 		if err := i.inspectEnums(ctx, r); err != nil {
 			return nil, err
 		}
@@ -83,6 +88,11 @@ func (i *inspect) InspectSchema(ctx context.Context, name string, opts *schema.I
 			return nil, err
 		}
 	}
+	if sqlx.ModeInspectSchema(opts).Is(schema.InspectFuncs) {
+		if err := i.inspectFuncs(ctx, r, opts); err != nil {
+			return nil, err
+		}
+	}
 	if err := i.inspectEnums(ctx, r); err != nil {
 		return nil, err
 	}
@@ -220,7 +230,7 @@ func (i *inspect) addColumn(s *schema.Schema, rows *sql.Rows) (err error) {
 		timePrecision: &timeprecision.Int64,
 	})
 	if defaults.Valid {
-		defaultExpr(c, defaults.String)
+		columnDefault(c, defaults.String)
 	}
 	if identity.String == "YES" {
 		c.Attrs = append(c.Attrs, &Identity{
@@ -677,7 +687,7 @@ func nArgs(start, n int) string {
 // nextval('<optional (quoted) schema>.<sequence name>'::regclass).
 var reNextval = regexp.MustCompile(`(?i) *nextval\('(?:"?[\w$]+"?\.)?"?([\w$]+_[\w$]+_seq)"?'(?:::regclass)*\) *$`)
 
-func defaultExpr(c *schema.Column, s string) {
+func columnDefault(c *schema.Column, s string) {
 	switch m := reNextval.FindStringSubmatch(s); {
 	// The definition of "<column> <serial type>" is equivalent to specifying:
 	// "<column> <int type> NOT NULL DEFAULT nextval('<table>_<column>_seq')".
@@ -691,26 +701,33 @@ func defaultExpr(c *schema.Column, s string) {
 		st.SetType(tt)
 		c.Type.Raw = st.T
 		c.Type.Type = st
+	default:
+		c.Default = defaultExpr(c.Type.Type, s)
+	}
+}
+
+func defaultExpr(t schema.Type, s string) schema.Expr {
+	switch {
 	case sqlx.IsLiteralBool(s), sqlx.IsLiteralNumber(s), sqlx.IsQuoted(s, '\''):
-		c.Default = &schema.Literal{V: s}
+		return &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, s); ok {
+		if v, ok := canConvert(t, s); ok {
 			x = &schema.Literal{V: v}
 		}
-		c.Default = x
+		return x
 	}
 }
 
-func canConvert(t *schema.ColumnType, x string) (string, bool) {
+func canConvert(t schema.Type, x string) (string, bool) {
 	i := strings.LastIndex(x, "::")
 	if i == -1 || !sqlx.IsQuoted(x[:i], '\'') {
 		return "", false
 	}
 	q := x[0:i]
 	x = x[1 : i-1]
-	switch t.Type.(type) {
+	switch t.(type) {
 	case *enumType:
 		return q, true
 	case *schema.BoolType:
@@ -734,6 +751,13 @@ type (
 		T string
 	}
 
+	// PseudoType defines a non-column pseudo-type, such as function arguments and return types.
+	// https://www.postgresql.org/docs/current/datatype-pseudo.html
+	PseudoType struct {
+		schema.Type
+		T string // e.g., void, any, cstring, etc.
+	}
+
 	// enumType represents an enum type. It serves aa intermediate representation of a Postgres enum type,
 	// to temporary save TypeID and TypeName of an enum column until the enum values can be extracted.
 	enumType struct {
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index c31cba51..def5ea94 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -26,7 +26,7 @@ var DefaultPlan migrate.PlanApplier = &planApply{conn: &conn{ExecQuerier: sqlx.N
 type planApply struct{ *conn }
 
 // PlanChanges returns a migration plan for the given schema changes.
-func (p *planApply) PlanChanges(_ context.Context, name string, changes []schema.Change, opts ...migrate.PlanOption) (*migrate.Plan, error) {
+func (p *planApply) PlanChanges(ctx context.Context, name string, changes []schema.Change, opts ...migrate.PlanOption) (*migrate.Plan, error) {
 	s := &state{
 		conn: p.conn,
 		Plan: migrate.Plan{
@@ -37,6 +37,9 @@ func (p *planApply) PlanChanges(_ context.Context, name string, changes []schema
 	for _, o := range opts {
 		o(&s.PlanOptions)
 	}
+	if err := verifyChanges(ctx, changes); err != nil {
+		return nil, err
+	}
 	if err := s.plan(changes); err != nil {
 		return nil, err
 	}
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 2cb7cf20..1be2197d 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -27,6 +27,8 @@ type (
 		Views        []*sqlspec.View   `spec:"view"`
 		Materialized []*sqlspec.View   `spec:"materialized"`
 		Enums        []*Enum           `spec:"enum"`
+		Funcs        []*sqlspec.Func   `spec:"function"`
+		Procs        []*sqlspec.Func   `spec:"procedure"`
 		Schemas      []*sqlspec.Schema `spec:"schema"`
 	}
 	// Enum holds a specification for an enum, that can be referenced as a column type.
@@ -45,6 +47,8 @@ func (d *doc) merge(d1 *doc) {
 	d.Views = append(d.Views, d1.Views...)
 	d.Materialized = append(d.Materialized, d1.Materialized...)
 	d.Enums = append(d.Enums, d1.Enums...)
+	d.Funcs = append(d.Funcs, d1.Funcs...)
+	d.Procs = append(d.Procs, d1.Procs...)
 	d.Schemas = append(d.Schemas, d1.Schemas...)
 }
 
@@ -140,6 +144,12 @@ func MarshalSpec(v any, marshaler schemahcl.Marshaler) ([]byte, error) {
 		if err := specutil.QualifyObjects(d.Enums); err != nil {
 			return nil, err
 		}
+		if err := specutil.QualifyObjects(d.Funcs); err != nil {
+			return nil, err
+		}
+		if err := specutil.QualifyObjects(d.Procs); err != nil {
+			return nil, err
+		}
 		if err := specutil.QualifyReferences(d.Tables, s); err != nil {
 			return nil, err
 		}
@@ -573,7 +583,7 @@ func enumRef(n string) *schemahcl.Ref {
 
 // schemaSpec converts from a concrete Postgres schema to Atlas specification.
 func schemaSpec(s *schema.Schema) (*doc, error) {
-	spec, err := specutil.FromSchema(s, tableSpec, viewSpec)
+	spec, err := specutil.FromSchema(s, specFuncs)
 	if err != nil {
 		return nil, err
 	}
@@ -581,6 +591,8 @@ func schemaSpec(s *schema.Schema) (*doc, error) {
 		Tables:       spec.Tables,
 		Views:        spec.Views,
 		Materialized: spec.Materialized,
+		Funcs:        spec.Funcs,
+		Procs:        spec.Procs,
 		Schemas:      []*sqlspec.Schema{spec.Schema},
 		Enums:        make([]*Enum, 0, len(s.Objects)),
 	}
@@ -812,11 +824,13 @@ 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.
+	// PostgreSQL internal, pseudo, 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,
+			typeAny, typeAnyElement, typeAnyArray, typeAnyNonArray, typeAnyEnum, typeInternal, typeRecord,
+			typeTrigger, typeVoid, typeUnknown,
 		} {
 			specs = append(specs, schemahcl.NewTypeSpec(t))
 		}
diff --git a/sql/schema/inspect.go b/sql/schema/inspect.go
index 61df2c01..fe2cce17 100644
--- a/sql/schema/inspect.go
+++ b/sql/schema/inspect.go
@@ -46,6 +46,9 @@ const (
 
 	// InspectViews enables schema views inspection.
 	InspectViews
+
+	// InspectFuncs enables schema functions / procedures inspection.
+	InspectFuncs
 )
 
 // Is reports whether the given mode is enabled.
diff --git a/sql/schema/schema.go b/sql/schema/schema.go
index c8563e98..2b6adc0b 100644
--- a/sql/schema/schema.go
+++ b/sql/schema/schema.go
@@ -18,6 +18,8 @@ type (
 		Realm   *Realm
 		Tables  []*Table
 		Views   []*View
+		Funcs   []*Func
+		Procs   []*Proc
 		Attrs   []Attr   // Attrs and options.
 		Objects []Object // Driver specific objects.
 	}
@@ -105,6 +107,46 @@ type (
 		OnUpdate   ReferenceOption
 		OnDelete   ReferenceOption
 	}
+
+	// Func represents a function definition.
+	Func struct {
+		Name   string
+		Schema *Schema
+		Args   []*FuncArg
+		Ret    Type
+		Body   string // Function body only.
+		Lang   string // Language (e.g. SQL, PL/pgSQL, etc.).
+		Attrs  []Attr // Extra driver specific attributes.
+	}
+
+	// Proc represents a procedure definition.
+	Proc struct {
+		Name   string
+		Schema *Schema
+		Args   []*FuncArg
+		Body   string // Function body only.
+		Lang   string // Language (e.g. SQL, PL/pgSQL, etc.).
+		Attrs  []Attr // Extra driver specific attributes.
+	}
+
+	// A FuncArg represents a single function argument.
+	FuncArg struct {
+		Name    string      // Optional name.
+		Type    Type        // Argument type.
+		Default Expr        // Default value.
+		Mode    FuncArgMode // Argument mode.
+	}
+
+	// FuncArgMode represents a function argument mode.
+	FuncArgMode string
+)
+
+// List of supported function argument modes.
+const (
+	FuncArgModeIn       FuncArgMode = "IN"
+	FuncArgModeOut      FuncArgMode = "OUT"
+	FuncArgModeInOut    FuncArgMode = "INOUT"
+	FuncArgModeVariadic FuncArgMode = "VARIADIC"
 )
 
 // Schema returns the first schema that matched the given name.
diff --git a/sql/sqlite/sqlspec.go b/sql/sqlite/sqlspec.go
index 10df474c..747f2fe3 100644
--- a/sql/sqlite/sqlspec.go
+++ b/sql/sqlite/sqlspec.go
@@ -158,7 +158,10 @@ func convertColumnType(spec *sqlspec.Column) (schema.Type, error) {
 
 // schemaSpec converts from a concrete SQLite schema to Atlas specification.
 func schemaSpec(s *schema.Schema) (*specutil.SchemaSpec, error) {
-	return specutil.FromSchema(s, tableSpec, viewSpec)
+	return specutil.FromSchema(s, specutil.Funcs{
+		Table: tableSpec,
+		View:  viewSpec,
+	})
 }
 
 // tableSpec converts from a concrete SQLite sqlspec.Table to a schema.Table.
diff --git a/sql/sqlspec/sqlspec.go b/sql/sqlspec/sqlspec.go
index e163d279..6c2e400b 100644
--- a/sql/sqlspec/sqlspec.go
+++ b/sql/sqlspec/sqlspec.go
@@ -93,8 +93,27 @@ type (
 		schemahcl.DefaultExtension
 	}
 
-	// Type represents a database agnostic column type.
-	Type string
+	// Func holds the specification for a function.
+	Func struct {
+		Name      string         `spec:",name"`
+		Qualifier string         `spec:",qualifier"`
+		Schema    *schemahcl.Ref `spec:"schema"`
+		Args      []*FuncArg     `spec:"arg"`
+		Lang      cty.Value      `spec:"lang"`
+		// The definition and the return type are appended as additional
+		// attribute by the spec creator to marshal it after the arguments.
+		schemahcl.DefaultExtension
+	}
+
+	// FuncArg holds the specification for a function argument.
+	FuncArg struct {
+		Name    string          `spec:",name"`
+		Type    *schemahcl.Type `spec:"type"`
+		Default cty.Value       `spec:"default"`
+		// Optional attributes such as mode are added by the driver,
+		// as their definition can be either a string or an enum (ref).
+		schemahcl.DefaultExtension
+	}
 )
 
 // Label returns the defaults label used for the table resource.
@@ -112,7 +131,7 @@ func (t *Table) SchemaRef() *schemahcl.Ref { return t.Schema }
 // Label returns the defaults label used for the view resource.
 func (v *View) Label() string { return v.Name }
 
-// QualifierLabel returns the qualifier label used for the table resource, if any.
+// QualifierLabel returns the qualifier label used for the view resource, if any.
 func (v *View) QualifierLabel() string { return v.Qualifier }
 
 // SetQualifier sets the qualifier label used for the view resource.
@@ -121,9 +140,23 @@ func (v *View) SetQualifier(q string) { v.Qualifier = q }
 // SchemaRef returns the schema reference for the view.
 func (v *View) SchemaRef() *schemahcl.Ref { return v.Schema }
 
+// Label returns the defaults label used for the function resource.
+func (f *Func) Label() string { return f.Name }
+
+// QualifierLabel returns the qualifier label used for the function resource, if any.
+func (f *Func) QualifierLabel() string { return f.Qualifier }
+
+// SetQualifier sets the qualifier label used for the function resource.
+func (f *Func) SetQualifier(q string) { f.Qualifier = q }
+
+// SchemaRef returns the schema reference for the function.
+func (f *Func) SchemaRef() *schemahcl.Ref { return f.Schema }
+
 func init() {
 	schemahcl.Register("view", &View{})
 	schemahcl.Register("materialized", &View{})
 	schemahcl.Register("table", &Table{})
+	schemahcl.Register("function", &Func{})
+	schemahcl.Register("procedure", &Func{})
 	schemahcl.Register("schema", &Schema{})
 }


Test patch:
diff --git a/sql/internal/specutil/convert_test.go b/sql/internal/specutil/convert_test.go
index 6879a5a3..72528f14 100644
--- a/sql/internal/specutil/convert_test.go
+++ b/sql/internal/specutil/convert_test.go
@@ -22,15 +22,14 @@ func TestFromSpec_SchemaName(t *testing.T) {
 		},
 	}
 	sc.Tables[0].Schema = sc
-	spec, err := FromSchema(
-		sc,
-		func(*schema.Table) (*sqlspec.Table, error) {
+	spec, err := FromSchema(sc, Funcs{
+		Table: func(*schema.Table) (*sqlspec.Table, error) {
 			return &sqlspec.Table{}, nil
 		},
-		func(*schema.View) (*sqlspec.View, error) {
+		View: func(*schema.View) (*sqlspec.View, error) {
 			return &sqlspec.View{}, nil
 		},
-	)
+	})
 	require.NoError(t, err)
 	require.Equal(t, sc.Name, spec.Schema.Name)
 	require.Equal(t, "$schema."+sc.Name, spec.Tables[0].Schema.V)