Repository: ariga/atlas
Pull request #1057 (2022-08-16 06:41:34)

sql/sqltool: support replaying of dbmate migration files


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5779ba7a..932851f9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,7 +26,7 @@ jobs:
       - uses: actions/checkout@v2.4.0
       - uses: actions/setup-go@v2
         with:
-          go-version: '1.18'
+          go-version: '1.19'
       - uses: actions/cache@v2.1.5
         with:
           path: ~/go/pkg/mod
diff --git a/cmd/atlas/internal/ci/run.go b/cmd/atlas/internal/ci/run.go
index 4a14c699..0c279640 100644
--- a/cmd/atlas/internal/ci/run.go
+++ b/cmd/atlas/internal/ci/run.go
@@ -119,7 +119,7 @@ func (r *Runner) summary(ctx context.Context) error {
 var (
 	// TemplateFuncs are global functions available in templates.
 	TemplateFuncs = template.FuncMap{
-		"json": func(v interface{}) (string, error) {
+		"json": func(v any) (string, error) {
 			b, err := json.Marshal(v)
 			return string(b), err
 		},
diff --git a/cmd/atlas/internal/cmdapi/migrate.go b/cmd/atlas/internal/cmdapi/migrate.go
index fcb0acc7..7bcf8daf 100644
--- a/cmd/atlas/internal/cmdapi/migrate.go
+++ b/cmd/atlas/internal/cmdapi/migrate.go
@@ -740,6 +740,7 @@ func dir(create bool) (migrate.Dir, error) {
 	case formatFlyway:
 	case formatLiquibase:
 	case formatDbmate:
+		f = func() (migrate.Dir, error) { return sqltool.NewDBMateDir(parts[1]) }
 	default:
 		return nil, fmt.Errorf("unknown dir format %q", MigrateFlags.DirFormat)
 	}
@@ -1091,12 +1092,12 @@ type (
 )
 
 // QueryContext overrides the wrapped schema.ExecQuerier to not execute any SQL.
-func (dryRunDriver) QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) {
+func (dryRunDriver) QueryContext(context.Context, string, ...any) (*sql.Rows, error) {
 	return nil, nil
 }
 
 // ExecContext overrides the wrapped schema.ExecQuerier to not execute any SQL.
-func (dryRunDriver) ExecContext(context.Context, string, ...interface{}) (sql.Result, error) {
+func (dryRunDriver) ExecContext(context.Context, string, ...any) (sql.Result, error) {
 	return nil, nil
 }
 
diff --git a/cmd/atlas/internal/migrate/ent/client.go b/cmd/atlas/internal/migrate/ent/client.go
index ae264b05..2fb118b8 100644
--- a/cmd/atlas/internal/migrate/ent/client.go
+++ b/cmd/atlas/internal/migrate/ent/client.go
@@ -103,7 +103,6 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
 //		Revision.
 //		Query().
 //		Count(ctx)
-//
 func (c *Client) Debug() *Client {
 	if c.debug {
 		return c
diff --git a/cmd/atlas/internal/migrate/ent/ent.go b/cmd/atlas/internal/migrate/ent/ent.go
index 7a9f46f9..48cbd0c6 100644
--- a/cmd/atlas/internal/migrate/ent/ent.go
+++ b/cmd/atlas/internal/migrate/ent/ent.go
@@ -85,7 +85,6 @@ type AggregateFunc func(*sql.Selector) string
 //	GroupBy(field1, field2).
 //	Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")).
 //	Scan(ctx, &v)
-//
 func As(fn AggregateFunc, end string) AggregateFunc {
 	return func(s *sql.Selector) string {
 		return sql.As(fn(s), end)
diff --git a/cmd/atlas/internal/migrate/ent/hook/hook.go b/cmd/atlas/internal/migrate/ent/hook/hook.go
index 290222a6..88e85a56 100644
--- a/cmd/atlas/internal/migrate/ent/hook/hook.go
+++ b/cmd/atlas/internal/migrate/ent/hook/hook.go
@@ -121,7 +121,6 @@ func HasFields(field string, fields ...string) Condition {
 // If executes the given hook under condition.
 //
 //	hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...)))
-//
 func If(hk ent.Hook, cond Condition) ent.Hook {
 	return func(next ent.Mutator) ent.Mutator {
 		return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
@@ -136,7 +135,6 @@ func If(hk ent.Hook, cond Condition) ent.Hook {
 // On executes the given hook only for the given operation.
 //
 //	hook.On(Log, ent.Delete|ent.Create)
-//
 func On(hk ent.Hook, op ent.Op) ent.Hook {
 	return If(hk, HasOp(op))
 }
@@ -144,7 +142,6 @@ func On(hk ent.Hook, op ent.Op) ent.Hook {
 // Unless skips the given hook only for the given operation.
 //
 //	hook.Unless(Log, ent.Update|ent.UpdateOne)
-//
 func Unless(hk ent.Hook, op ent.Op) ent.Hook {
 	return If(hk, Not(HasOp(op)))
 }
@@ -165,7 +162,6 @@ func FixedError(err error) ent.Hook {
 //			Reject(ent.Delete|ent.Update),
 //		}
 //	}
-//
 func Reject(op ent.Op) ent.Hook {
 	hk := FixedError(fmt.Errorf("%s operation is not allowed", op))
 	return On(hk, op)
diff --git a/cmd/atlas/internal/migrate/ent/migrate/migrate.go b/cmd/atlas/internal/migrate/ent/migrate/migrate.go
index 0e1a3042..0a18daee 100644
--- a/cmd/atlas/internal/migrate/ent/migrate/migrate.go
+++ b/cmd/atlas/internal/migrate/ent/migrate/migrate.go
@@ -60,10 +60,9 @@ func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...sche
 
 // WriteTo writes the schema changes to w instead of running them against the database.
 //
-// 	if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil {
+//	if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil {
 //		log.Fatal(err)
-// 	}
-//
+//	}
 func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error {
 	return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...)
 }
diff --git a/cmd/atlas/internal/migrate/ent/revision_create.go b/cmd/atlas/internal/migrate/ent/revision_create.go
index fade030e..c40dbaca 100644
--- a/cmd/atlas/internal/migrate/ent/revision_create.go
+++ b/cmd/atlas/internal/migrate/ent/revision_create.go
@@ -349,7 +349,6 @@ func (rc *RevisionCreate) createSpec() (*Revision, *sqlgraph.CreateSpec) {
 //			SetDescription(v+v).
 //		}).
 //		Exec(ctx)
-//
 func (rc *RevisionCreate) OnConflict(opts ...sql.ConflictOption) *RevisionUpsertOne {
 	rc.conflict = opts
 	return &RevisionUpsertOne{
@@ -363,7 +362,6 @@ func (rc *RevisionCreate) OnConflict(opts ...sql.ConflictOption) *RevisionUpsert
 //	client.Revision.Create().
 //		OnConflict(sql.ConflictColumns(columns...)).
 //		Exec(ctx)
-//
 func (rc *RevisionCreate) OnConflictColumns(columns ...string) *RevisionUpsertOne {
 	rc.conflict = append(rc.conflict, sql.ConflictColumns(columns...))
 	return &RevisionUpsertOne{
@@ -551,7 +549,6 @@ func (u *RevisionUpsert) UpdateOperatorVersion() *RevisionUpsert {
 //			}),
 //		).
 //		Exec(ctx)
-//
 func (u *RevisionUpsertOne) UpdateNewValues() *RevisionUpsertOne {
 	u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
 	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
@@ -571,10 +568,9 @@ func (u *RevisionUpsertOne) UpdateNewValues() *RevisionUpsertOne {
 // Ignore sets each column to itself in case of conflict.
 // Using this option is equivalent to using:
 //
-//  client.Revision.Create().
-//      OnConflict(sql.ResolveWithIgnore()).
-//      Exec(ctx)
-//
+//	client.Revision.Create().
+//	    OnConflict(sql.ResolveWithIgnore()).
+//	    Exec(ctx)
 func (u *RevisionUpsertOne) Ignore() *RevisionUpsertOne {
 	u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
 	return u
@@ -912,7 +908,6 @@ func (rcb *RevisionCreateBulk) ExecX(ctx context.Context) {
 //			SetDescription(v+v).
 //		}).
 //		Exec(ctx)
-//
 func (rcb *RevisionCreateBulk) OnConflict(opts ...sql.ConflictOption) *RevisionUpsertBulk {
 	rcb.conflict = opts
 	return &RevisionUpsertBulk{
@@ -926,7 +921,6 @@ func (rcb *RevisionCreateBulk) OnConflict(opts ...sql.ConflictOption) *RevisionU
 //	client.Revision.Create().
 //		OnConflict(sql.ConflictColumns(columns...)).
 //		Exec(ctx)
-//
 func (rcb *RevisionCreateBulk) OnConflictColumns(columns ...string) *RevisionUpsertBulk {
 	rcb.conflict = append(rcb.conflict, sql.ConflictColumns(columns...))
 	return &RevisionUpsertBulk{
@@ -951,7 +945,6 @@ type RevisionUpsertBulk struct {
 //			}),
 //		).
 //		Exec(ctx)
-//
 func (u *RevisionUpsertBulk) UpdateNewValues() *RevisionUpsertBulk {
 	u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
 	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
@@ -977,7 +970,6 @@ func (u *RevisionUpsertBulk) UpdateNewValues() *RevisionUpsertBulk {
 //	client.Revision.Create().
 //		OnConflict(sql.ResolveWithIgnore()).
 //		Exec(ctx)
-//
 func (u *RevisionUpsertBulk) Ignore() *RevisionUpsertBulk {
 	u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
 	return u
diff --git a/cmd/atlas/internal/migrate/ent/revision_query.go b/cmd/atlas/internal/migrate/ent/revision_query.go
index 54a93f21..141ab731 100644
--- a/cmd/atlas/internal/migrate/ent/revision_query.go
+++ b/cmd/atlas/internal/migrate/ent/revision_query.go
@@ -266,7 +266,6 @@ func (rq *RevisionQuery) Clone() *RevisionQuery {
 //		GroupBy(revision.FieldDescription).
 //		Aggregate(ent.Count()).
 //		Scan(ctx, &v)
-//
 func (rq *RevisionQuery) GroupBy(field string, fields ...string) *RevisionGroupBy {
 	grbuild := &RevisionGroupBy{config: rq.config}
 	grbuild.fields = append([]string{field}, fields...)
@@ -293,7 +292,6 @@ func (rq *RevisionQuery) GroupBy(field string, fields ...string) *RevisionGroupB
 //	client.Revision.Query().
 //		Select(revision.FieldDescription).
 //		Scan(ctx, &v)
-//
 func (rq *RevisionQuery) Select(fields ...string) *RevisionSelect {
 	rq.fields = append(rq.fields, fields...)
 	selbuild := &RevisionSelect{RevisionQuery: rq}
diff --git a/cmd/atlas/internal/update/update.go b/cmd/atlas/internal/update/update.go
index 89da4546..907f5909 100644
--- a/cmd/atlas/internal/update/update.go
+++ b/cmd/atlas/internal/update/update.go
@@ -42,7 +42,7 @@ const (
 // 2. If last poll was more than 24h, poll GitHub public API https://docs.github.com/en/rest/reference/releases#get-the-latest-release
 // 3. Store the latest release metadata
 // 4. If current build Version, that is not development, is lower than the latest release, notify user
-func Check(version string, logF func(i ...interface{})) {
+func Check(version string, logF func(i ...any)) {
 	if !enabled(version) {
 		return
 	}
diff --git a/internal/ci/ci.tmpl b/internal/ci/ci.tmpl
index b91a123b..d292b0d6 100644
--- a/internal/ci/ci.tmpl
+++ b/internal/ci/ci.tmpl
@@ -26,7 +26,7 @@ jobs:
       - uses: actions/checkout@v2.4.0
       - uses: actions/setup-go@v2
         with:
-          go-version: '1.18'
+          go-version: '1.19'
       - uses: actions/cache@v2.1.5
         with:
           path: ~/go/pkg/mod
diff --git a/schemahcl/context.go b/schemahcl/context.go
index ff9444fc..32c9f346 100644
--- a/schemahcl/context.go
+++ b/schemahcl/context.go
@@ -226,11 +226,11 @@ var (
 	ctySchemaLit = cty.CapsuleWithOps("lit", reflect.TypeOf(LiteralValue{}), &cty.CapsuleOps{
 		// ConversionFrom facilitates reading the encapsulated type as a string, as is needed, for example,
 		// when interpolating it in a string expression.
-		ConversionFrom: func(src cty.Type) func(interface{}, cty.Path) (cty.Value, error) {
+		ConversionFrom: func(src cty.Type) func(any, cty.Path) (cty.Value, error) {
 			if src != cty.String {
 				return nil
 			}
-			return func(i interface{}, path cty.Path) (cty.Value, error) {
+			return func(i any, path cty.Path) (cty.Value, error) {
 				lit, ok := i.(*LiteralValue)
 				if !ok {
 					return cty.Value{}, fmt.Errorf("schemahcl: expected *schemahcl.LiteralValue got %T", i)
diff --git a/schemahcl/extension.go b/schemahcl/extension.go
index 28b40493..472f9e78 100644
--- a/schemahcl/extension.go
+++ b/schemahcl/extension.go
@@ -39,14 +39,14 @@ func (d *DefaultExtension) Attr(name string) (*Attr, bool) {
 	return d.Extra.Attr(name)
 }
 
-type registry map[string]interface{}
+type registry map[string]any
 
 var (
 	extensions   = make(registry)
 	extensionsMu sync.RWMutex
 )
 
-func (r registry) lookup(ext interface{}) (string, bool) {
+func (r registry) lookup(ext any) (string, bool) {
 	extensionsMu.RLock()
 	defer extensionsMu.RUnlock()
 	for k, v := range r {
@@ -74,7 +74,7 @@ func (r registry) implementers(i reflect.Type) ([]string, error) {
 // Register records the type of ext in the global extension registry.
 // If Register is called twice with the same name or if ext is nil,
 // it panics.
-func Register(name string, ext interface{}) {
+func Register(name string, ext any) {
 	extensionsMu.Lock()
 	defer extensionsMu.Unlock()
 	if ext == nil {
@@ -87,7 +87,7 @@ func Register(name string, ext interface{}) {
 }
 
 // As reads the attributes and children resources of the resource into the target struct.
-func (r *Resource) As(target interface{}) error {
+func (r *Resource) As(target any) error {
 	if err := validateStructPtr(target); err != nil {
 		return err
 	}
@@ -228,7 +228,7 @@ func (r *Resource) FinalName() (string, error) {
 	return "", fmt.Errorf("extension %q has no name field", r.Type)
 }
 
-func validateStructPtr(target interface{}) error {
+func validateStructPtr(target any) error {
 	typeOf := reflect.TypeOf(target)
 	if typeOf.Kind() != reflect.Ptr {
 		return errors.New("schemahcl: expected target to be a pointer")
@@ -402,7 +402,7 @@ func setSliceAttr(field reflect.Value, attr *Attr) error {
 
 // Scan reads the Extension into the Resource. Scan will override the Resource
 // name or type if they are set for the extension.
-func (r *Resource) Scan(ext interface{}) error {
+func (r *Resource) Scan(ext any) error {
 	if lookup, ok := extensions.lookup(ext); ok {
 		r.Type = lookup
 	}
@@ -559,7 +559,7 @@ func scanSliceAttr(key string, r *Resource, field reflect.Value) error {
 
 // specFields uses reflection to find struct fields that are tagged with "spec"
 // and returns a list of mappings from the tag to the field name.
-func specFields(ext interface{}) []fieldDesc {
+func specFields(ext any) []fieldDesc {
 	t := reflect.TypeOf(ext)
 	var fields []fieldDesc
 	for i := 0; i < t.Elem().NumField(); i++ {
diff --git a/schemahcl/hcl.go b/schemahcl/hcl.go
index 347b6bc7..3fd3d04c 100644
--- a/schemahcl/hcl.go
+++ b/schemahcl/hcl.go
@@ -32,22 +32,22 @@ type (
 	// Evaluator is the interface that wraps the Eval function.
 	Evaluator interface {
 		// Eval evaluates parsed HCL files using input variables into a schema.Realm.
-		Eval(*hclparse.Parser, interface{}, map[string]string) error
+		Eval(*hclparse.Parser, any, map[string]string) error
 	}
 	// EvalFunc is an adapter that allows the use of an ordinary function as an Evaluator.
-	EvalFunc func(*hclparse.Parser, interface{}, map[string]string) error
+	EvalFunc func(*hclparse.Parser, any, map[string]string) error
 	// Marshaler is the interface that wraps the MarshalSpec function.
 	Marshaler interface {
 		// MarshalSpec marshals the provided input into a valid Atlas HCL document.
-		MarshalSpec(interface{}) ([]byte, error)
+		MarshalSpec(any) ([]byte, error)
 	}
 	// MarshalerFunc is the function type that is implemented by the MarshalSpec
 	// method of the Marshaler interface.
-	MarshalerFunc func(interface{}) ([]byte, error)
+	MarshalerFunc func(any) ([]byte, error)
 )
 
 // MarshalSpec implements Marshaler for Atlas HCL documents.
-func (s *State) MarshalSpec(v interface{}) ([]byte, error) {
+func (s *State) MarshalSpec(v any) ([]byte, error) {
 	r := &Resource{}
 	if err := r.Scan(v); err != nil {
 		return nil, fmt.Errorf("schemahcl: failed scanning %T to resource: %w", v, err)
@@ -57,7 +57,7 @@ func (s *State) MarshalSpec(v interface{}) ([]byte, error) {
 
 // EvalFiles evaluates the files in the provided paths using the input variables and
 // populates v with the result.
-func (s *State) EvalFiles(paths []string, v interface{}, input map[string]string) error {
+func (s *State) EvalFiles(paths []string, v any, input map[string]string) error {
 	parser := hclparse.NewParser()
 	for _, path := range paths {
 		if _, diag := parser.ParseHCLFile(path); diag.HasErrors() {
@@ -69,7 +69,7 @@ func (s *State) EvalFiles(paths []string, v interface{}, input map[string]string
 
 // Eval evaluates the parsed HCL documents using the input variables and populates v
 // using the result.
-func (s *State) Eval(parsed *hclparse.Parser, v interface{}, input map[string]string) error {
+func (s *State) Eval(parsed *hclparse.Parser, v any, input map[string]string) error {
 	ctx := s.config.newCtx()
 	reg := &blockDef{
 		fields:   make(map[string]struct{}),
@@ -128,7 +128,7 @@ func (s *State) Eval(parsed *hclparse.Parser, v interface{}, input map[string]st
 
 // EvalBytes evaluates the data byte-slice as an Atlas HCL document using the input variables
 // and stores the result in v.
-func (s *State) EvalBytes(data []byte, v interface{}, input map[string]string) error {
+func (s *State) EvalBytes(data []byte, v any, input map[string]string) error {
 	parser := hclparse.NewParser()
 	if _, diag := parser.ParseHCL(data, ""); diag.HasErrors() {
 		return diag
@@ -599,6 +599,6 @@ func hclList(items []hclwrite.Tokens) hclwrite.Tokens {
 }
 
 // Eval implements the Evaluator interface.
-func (f EvalFunc) Eval(p *hclparse.Parser, i interface{}, input map[string]string) error {
+func (f EvalFunc) Eval(p *hclparse.Parser, i any, input map[string]string) error {
 	return f(p, i, input)
 }
diff --git a/schemahcl/spec.go b/schemahcl/spec.go
index 86b42c95..dd0129ce 100644
--- a/schemahcl/spec.go
+++ b/schemahcl/spec.go
@@ -219,7 +219,7 @@ func (r *Resource) SetAttr(attr *Attr) {
 }
 
 // MarshalSpec implements Marshaler.
-func (f MarshalerFunc) MarshalSpec(v interface{}) ([]byte, error) {
+func (f MarshalerFunc) MarshalSpec(v any) ([]byte, error) {
 	return f(v)
 }
 
diff --git a/sql/internal/specutil/spec.go b/sql/internal/specutil/spec.go
index 86ef797e..c059c254 100644
--- a/sql/internal/specutil/spec.go
+++ b/sql/internal/specutil/spec.go
@@ -94,7 +94,7 @@ type doc struct {
 
 // Marshal marshals v into an Atlas DDL document using a schemahcl.Marshaler. Marshal uses the given
 // schemaSpec function to convert a *schema.Schema into *sqlspec.Schema and []*sqlspec.Table.
-func Marshal(v interface{}, marshaler schemahcl.Marshaler, schemaSpec func(schem *schema.Schema) (*sqlspec.Schema, []*sqlspec.Table, error)) ([]byte, error) {
+func Marshal(v any, marshaler schemahcl.Marshaler, schemaSpec func(schem *schema.Schema) (*sqlspec.Schema, []*sqlspec.Table, error)) ([]byte, error) {
 	d := &doc{}
 	switch s := v.(type) {
 	case *schema.Schema:
@@ -174,8 +174,8 @@ func QualifyReferencedTables(tableSpecs []*sqlspec.Table, realm *schema.Realm) e
 
 // HCLBytesFunc returns a helper that evaluates an HCL document from a byte slice instead
 // of from an hclparse.Parser instance.
-func HCLBytesFunc(ev schemahcl.Evaluator) func(b []byte, v interface{}, inp map[string]string) error {
-	return func(b []byte, v interface{}, inp map[string]string) error {
+func HCLBytesFunc(ev schemahcl.Evaluator) func(b []byte, v any, inp map[string]string) error {
+	return func(b []byte, v any, inp map[string]string) error {
 		parser := hclparse.NewParser()
 		if _, diag := parser.ParseHCL(b, ""); diag.HasErrors() {
 			return diag
diff --git a/sql/internal/sqlx/diff.go b/sql/internal/sqlx/diff.go
index aaacc808..fa5c00ca 100644
--- a/sql/internal/sqlx/diff.go
+++ b/sql/internal/sqlx/diff.go
@@ -376,7 +376,7 @@ var (
 // Has finds the first element in the elements list that
 // matches target, and if so, sets target to that attribute
 // value and returns true.
-func Has(elements, target interface{}) bool {
+func Has(elements, target any) bool {
 	ev := reflect.ValueOf(elements)
 	if t := ev.Type(); t != attrsType && t != clausesType && t != exprsType {
 		panic(fmt.Sprintf("unexpected elements type: %T", elements))
diff --git a/sql/internal/sqlx/plan.go b/sql/internal/sqlx/plan.go
index a6a96b4b..6c156b9e 100644
--- a/sql/internal/sqlx/plan.go
+++ b/sql/internal/sqlx/plan.go
@@ -16,7 +16,7 @@ import (
 )
 
 type execPlanner interface {
-	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	ExecContext(context.Context, string, ...any) (sql.Result, error)
 	PlanChanges(context.Context, string, []schema.Change, ...migrate.PlanOption) (*migrate.Plan, error)
 }
 
diff --git a/sql/internal/sqlx/sqlx.go b/sql/internal/sqlx/sqlx.go
index 45e9fad3..34348964 100644
--- a/sql/internal/sqlx/sqlx.go
+++ b/sql/internal/sqlx/sqlx.go
@@ -59,7 +59,7 @@ func ValidString(s sql.NullString) bool {
 }
 
 // ScanOne scans one record and closes the rows at the end.
-func ScanOne(rows *sql.Rows, dest ...interface{}) error {
+func ScanOne(rows *sql.Rows, dest ...any) error {
 	defer rows.Close()
 	if !rows.Next() {
 		return sql.ErrNoRows
@@ -283,7 +283,7 @@ func (b *Builder) Comma() *Builder {
 
 // MapComma maps the slice x using the function f and joins the result with
 // a comma separating between the written elements.
-func (b *Builder) MapComma(x interface{}, f func(i int, b *Builder)) *Builder {
+func (b *Builder) MapComma(x any, f func(i int, b *Builder)) *Builder {
 	s := reflect.ValueOf(x)
 	for i := 0; i < s.Len(); i++ {
 		if i > 0 {
@@ -295,7 +295,7 @@ func (b *Builder) MapComma(x interface{}, f func(i int, b *Builder)) *Builder {
 }
 
 // MapCommaErr is like MapComma, but returns an error if f returns an error.
-func (b *Builder) MapCommaErr(x interface{}, f func(i int, b *Builder) error) error {
+func (b *Builder) MapCommaErr(x any, f func(i int, b *Builder) error) error {
 	s := reflect.ValueOf(x)
 	for i := 0; i < s.Len(); i++ {
 		if i > 0 {
diff --git a/sql/migrate/migrate.go b/sql/migrate/migrate.go
index 241e7318..0044c743 100644
--- a/sql/migrate/migrate.go
+++ b/sql/migrate/migrate.go
@@ -42,7 +42,7 @@ type (
 		Cmd string
 
 		// Args for placeholder parameters in the statement above.
-		Args []interface{}
+		Args []any
 
 		// A Comment describes the change.
 		Comment string
diff --git a/sql/mysql/inspect.go b/sql/mysql/inspect.go
index b1df854e..5a8da41d 100644
--- a/sql/mysql/inspect.go
+++ b/sql/mysql/inspect.go
@@ -97,7 +97,7 @@ func (i *inspect) inspectTables(ctx context.Context, r *schema.Realm, opts *sche
 // schemas returns the list of the schemas in the database.
 func (i *inspect) schemas(ctx context.Context, opts *schema.InspectRealmOption) ([]*schema.Schema, error) {
 	var (
-		args  []interface{}
+		args  []any
 		query = schemasQuery
 	)
 	if opts != nil {
@@ -142,7 +142,7 @@ func (i *inspect) schemas(ctx context.Context, opts *schema.InspectRealmOption)
 
 func (i *inspect) tables(ctx context.Context, realm *schema.Realm, opts *schema.InspectOptions) error {
 	var (
-		args  []interface{}
+		args  []any
 		query = fmt.Sprintf(tablesQuery, nArgs(len(realm.Schemas)))
 	)
 	for _, s := range realm.Schemas {
@@ -665,7 +665,7 @@ func (i *inspect) marDefaultExpr(c *schema.Column, x string) schema.Expr {
 }
 
 func (i *inspect) querySchema(ctx context.Context, query string, s *schema.Schema) (*sql.Rows, error) {
-	args := []interface{}{s.Name}
+	args := []any{s.Name}
 	for _, t := range s.Tables {
 		args = append(args, t.Name)
 	}
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index 50a3673d..780db6d0 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -24,7 +24,7 @@ type doc struct {
 }
 
 // evalSpec evaluates an Atlas DDL document into v using the input.
-func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v any, input map[string]string) error {
 	var d doc
 	if err := hclState.Eval(p, &d, input); err != nil {
 		return err
@@ -64,7 +64,7 @@ func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error
 }
 
 // MarshalSpec marshals v into an Atlas DDL document using a schemahcl.Marshaler.
-func MarshalSpec(v interface{}, marshaler schemahcl.Marshaler) ([]byte, error) {
+func MarshalSpec(v any, marshaler schemahcl.Marshaler) ([]byte, error) {
 	return specutil.Marshal(v, marshaler, schemaSpec)
 }
 
@@ -77,7 +77,7 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
 	// MarshalHCL marshals v into an Atlas HCL DDL document.
-	MarshalHCL = schemahcl.MarshalerFunc(func(v interface{}) ([]byte, error) {
+	MarshalHCL = schemahcl.MarshalerFunc(func(v any) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
 	// EvalHCL implements the schemahcl.Evaluator interface.
diff --git a/sql/postgres/inspect.go b/sql/postgres/inspect.go
index f28e5999..94c83421 100644
--- a/sql/postgres/inspect.go
+++ b/sql/postgres/inspect.go
@@ -99,7 +99,7 @@ func (i *inspect) inspectTables(ctx context.Context, r *schema.Realm, opts *sche
 // table returns the table from the database, or a NotExistError if the table was not found.
 func (i *inspect) tables(ctx context.Context, realm *schema.Realm, opts *schema.InspectOptions) error {
 	var (
-		args  []interface{}
+		args  []any
 		query = fmt.Sprintf(tablesQuery, nArgs(0, len(realm.Schemas)))
 	)
 	for _, s := range realm.Schemas {
@@ -239,7 +239,7 @@ func (i *inspect) addColumn(s *schema.Schema, rows *sql.Rows) (err error) {
 // enumValues fills enum columns with their values from the database.
 func (i *inspect) enumValues(ctx context.Context, s *schema.Schema) error {
 	var (
-		args  []interface{}
+		args  []any
 		ids   = make(map[int64][]*schema.EnumType)
 		query = "SELECT enumtypid, enumlabel FROM pg_enum WHERE enumtypid IN (%s)"
 		newE  = func(e1 *enumType) *schema.EnumType {
@@ -497,7 +497,7 @@ func (i *inspect) addChecks(s *schema.Schema, rows *sql.Rows) error {
 // schemas returns the list of the schemas in the database.
 func (i *inspect) schemas(ctx context.Context, opts *schema.InspectRealmOption) ([]*schema.Schema, error) {
 	var (
-		args  []interface{}
+		args  []any
 		query = schemasQuery
 	)
 	if opts != nil {
@@ -536,7 +536,7 @@ func (i *inspect) schemas(ctx context.Context, opts *schema.InspectRealmOption)
 }
 
 func (i *inspect) querySchema(ctx context.Context, query string, s *schema.Schema) (*sql.Rows, error) {
-	args := []interface{}{s.Name}
+	args := []any{s.Name}
 	for _, t := range s.Tables {
 		args = append(args, t.Name)
 	}
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 1a2c2533..1b030e41 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -38,7 +38,7 @@ func init() {
 }
 
 // evalSpec evaluates an Atlas DDL document into v using the input.
-func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v any, input map[string]string) error {
 	var d doc
 	if err := hclState.Eval(p, &d, input); err != nil {
 		return err
@@ -72,7 +72,7 @@ func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error
 }
 
 // MarshalSpec marshals v into an Atlas DDL document using a schemahcl.Marshaler.
-func MarshalSpec(v interface{}, marshaler schemahcl.Marshaler) ([]byte, error) {
+func MarshalSpec(v any, marshaler schemahcl.Marshaler) ([]byte, error) {
 	var d doc
 	switch s := v.(type) {
 	case *schema.Schema:
@@ -117,7 +117,7 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
 	// MarshalHCL marshals v into an Atlas HCL DDL document.
-	MarshalHCL = schemahcl.MarshalerFunc(func(v interface{}) ([]byte, error) {
+	MarshalHCL = schemahcl.MarshalerFunc(func(v any) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
 	// EvalHCL implements the schemahcl.Evaluator interface.
diff --git a/sql/schema/inspect.go b/sql/schema/inspect.go
index ea2de5f6..080794db 100644
--- a/sql/schema/inspect.go
+++ b/sql/schema/inspect.go
@@ -29,8 +29,8 @@ func IsNotExistError(err error) bool {
 
 // ExecQuerier wraps the two standard sql.DB methods.
 type ExecQuerier interface {
-	QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
-	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
+	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
+	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
 }
 
 // An InspectMode controls the amount and depth of information returned on inspection.
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index 86cf8ff3..6d7b57fc 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -351,7 +351,7 @@ func (i *inspect) addFKs(t *schema.Table, rows *sql.Rows) error {
 // tableNames returns a list of all tables exist in the schema.
 func (i *inspect) tables(ctx context.Context, opts *schema.InspectOptions) ([]*schema.Table, error) {
 	var (
-		args  []interface{}
+		args  []any
 		query = tablesQuery
 	)
 	if opts != nil && len(opts.Tables) > 0 {
@@ -389,7 +389,7 @@ func (i *inspect) tables(ctx context.Context, opts *schema.InspectOptions) ([]*s
 // schemas returns the list of the schemas in the database.
 func (i *inspect) databases(ctx context.Context, opts *schema.InspectRealmOption) ([]*schema.Schema, error) {
 	var (
-		args  []interface{}
+		args  []any
 		query = databasesQuery
 	)
 	if opts != nil && len(opts.Schemas) > 0 {
diff --git a/sql/sqlite/sqlspec.go b/sql/sqlite/sqlspec.go
index 0987d3b3..d67484b7 100644
--- a/sql/sqlite/sqlspec.go
+++ b/sql/sqlite/sqlspec.go
@@ -19,7 +19,7 @@ import (
 )
 
 // evalSpec evaluates an Atlas DDL document using an unmarshaler into v by using the input.
-func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error {
+func evalSpec(p *hclparse.Parser, v any, input map[string]string) error {
 	var d doc
 	if err := hclState.Eval(p, &d, input); err != nil {
 		return err
@@ -47,7 +47,7 @@ func evalSpec(p *hclparse.Parser, v interface{}, input map[string]string) error
 }
 
 // MarshalSpec marshals v into an Atlas DDL document using a schemahcl.Marshaler.
-func MarshalSpec(v interface{}, marshaler schemahcl.Marshaler) ([]byte, error) {
+func MarshalSpec(v any, marshaler schemahcl.Marshaler) ([]byte, error) {
 	return specutil.Marshal(v, marshaler, schemaSpec)
 }
 
@@ -198,7 +198,7 @@ var (
 		schemahcl.WithScopedEnums("table.foreign_key.on_delete", specutil.ReferenceVars...),
 	)
 	// MarshalHCL marshals v into an Atlas HCL DDL document.
-	MarshalHCL = schemahcl.MarshalerFunc(func(v interface{}) ([]byte, error) {
+	MarshalHCL = schemahcl.MarshalerFunc(func(v any) ([]byte, error) {
 		return MarshalSpec(v, hclState)
 	})
 	// EvalHCL implements the schemahcl.Evaluator interface.
diff --git a/sql/sqltool/tool.go b/sql/sqltool/tool.go
index 158ed25d..6e79722d 100644
--- a/sql/sqltool/tool.go
+++ b/sql/sqltool/tool.go
@@ -148,43 +148,43 @@ func (f *GooseFile) Stmts() ([]string, error) {
 		line++
 		s := sc.Text()
 		// Handle pragmas.
-		if strings.HasPrefix(s, pragma) {
-			switch strings.TrimSpace(strings.TrimPrefix(s, pragma)) {
+		if strings.HasPrefix(s, goosePragma) {
+			switch strings.TrimSpace(strings.TrimPrefix(s, goosePragma)) {
 			case "Up":
 				switch state {
 				case none: // found the "up" part of the file
 					state = up
 				default:
-					return nil, unexpectedGoosePragmaErr(f, line, "Up")
+					return nil, unexpectedPragmaErr(f, line, "Up")
 				}
 			case "Down":
 				switch state {
 				case up: // found the "down" part
 					if rest := strings.TrimSpace(buf.String()); len(rest) > 0 {
-						return nil, unexpectedGoosePragmaErr(f, line, "Down")
+						return nil, unexpectedPragmaErr(f, line, "Down")
 					}
 					return stmts, nil
 				default:
-					return nil, unexpectedGoosePragmaErr(f, line, "Down")
+					return nil, unexpectedPragmaErr(f, line, "Down")
 				}
 			case "StatementBegin":
 				switch state {
 				case up:
 					state = begin // begin of a statement
 				default:
-					return nil, unexpectedGoosePragmaErr(f, line, "StatementBegin")
+					return nil, unexpectedPragmaErr(f, line, "StatementBegin")
 				}
 			case "StatementEnd":
 				switch state {
 				case begin:
 					state = end // end of a statement
 				default:
-					return nil, unexpectedGoosePragmaErr(f, line, "StatementEnd")
+					return nil, unexpectedPragmaErr(f, line, "StatementEnd")
 				}
 			}
 		}
 		// Write the line of the statement.
-		if !rePragma.MatchString(s) {
+		if !reGoosePragma.MatchString(s) {
 			if _, err := buf.WriteString(s + "\n"); err != nil {
 				return nil, err
 			}
@@ -195,7 +195,7 @@ func (f *GooseFile) Stmts() ([]string, error) {
 				stmts = append(stmts, strings.TrimSpace(buf.String()))
 				buf.Reset()
 			}
-		case end: // end of statement marked by pragma
+		case end: // end of statement marked by goosePragma
 			stmts = append(stmts, strings.TrimSpace(buf.String()))
 			buf.Reset()
 			state = up // back in up block
@@ -211,22 +211,117 @@ func (f *GooseFile) Stmts() ([]string, error) {
 }
 
 const (
-	none int = iota // state when parsing goose sql file
+	none int = iota // state when parsing goose/dbmate sql file
 	up
 	begin
 	end
-	pragma = "-- +goose"
+	goosePragma  = "-- +goose"
+	dbmatePragma = "-- migrate:"
 )
 
-var rePragma = regexp.MustCompile("-- \\+goose Up|Down|StatementBegin|StatementEnd")
+var (
+	reGoosePragma  = regexp.MustCompile(regexp.QuoteMeta(goosePragma) + " Up|Down|StatementBegin|StatementEnd")
+	reDBMatePragma = regexp.MustCompile(dbmatePragma + "up|down")
+)
 
-func unexpectedGoosePragmaErr(f *GooseFile, line int, pragma string) error {
+func unexpectedPragmaErr(f migrate.File, line int, pragma string) error {
+	var tool string
+	switch f := f.(type) {
+	case *GooseFile:
+		tool = "goose"
+	case *DBMateFile:
+		tool = "dbmate"
+	default:
+		return fmt.Errorf("sql/migrate: unexpected migration file type '%T'", f)
+	}
 	return fmt.Errorf(
-		"sql/migrate: goose: %s:%d unexpected pragma '%s'",
-		f.Name(), line, pragma,
+		"sql/migrate: %s: %s:%d unexpected goosePragma '%s'",
+		tool, f.Name(), line, pragma,
 	)
 }
 
+// DBMateDir wraps migrate.LocalDir and provides implementation compatible with amacneil/dbmate.
+type DBMateDir struct{ *migrate.LocalDir }
+
+// NewDBMateDir returns a new DBMateDir.
+func NewDBMateDir(path string) (*DBMateDir, error) {
+	dir, err := migrate.NewLocalDir(path)
+	if err != nil {
+		return nil, err
+	}
+	return &DBMateDir{dir}, nil
+}
+
+// DBMateFile wraps migrate.LocalFile with custom statements function.
+type DBMateFile struct {
+	*migrate.LocalFile
+}
+
+// Files implements Scanner.Files. It looks for all files with up.sql suffix and orders them by filename.
+func (d *DBMateDir) Files() ([]migrate.File, error) {
+	files, err := d.LocalDir.Files()
+	if err != nil {
+		return nil, err
+	}
+	for i, f := range files {
+		files[i] = &DBMateFile{f.(*migrate.LocalFile)}
+	}
+	return files, nil
+}
+
+// Stmts implements Scanner.Stmts. It understands the migration format used by pressly/DBMate sql migration files.
+func (f *DBMateFile) Stmts() ([]string, error) {
+	var (
+		state, line int
+		stmts       []string
+		buf         strings.Builder
+		sc          = bufio.NewScanner(bytes.NewReader(f.Bytes()))
+	)
+	for sc.Scan() {
+		line++
+		s := sc.Text()
+		// Handle pragmas.
+		if strings.HasPrefix(s, dbmatePragma) {
+			switch strings.TrimSpace(strings.TrimPrefix(s, dbmatePragma)) {
+			case "up":
+				switch state {
+				case none: // found the "up" part of the file
+					state = up
+				default:
+					return nil, unexpectedPragmaErr(f, line, "up")
+				}
+			case "down":
+				switch state {
+				case up: // found the "down" part
+					if rest := strings.TrimSpace(buf.String()); len(rest) > 0 {
+						return nil, unexpectedPragmaErr(f, line, "down")
+					}
+					return stmts, nil
+				default:
+					return nil, unexpectedPragmaErr(f, line, "down")
+				}
+			}
+		}
+		// Write the line of the statement.
+		if !reDBMatePragma.MatchString(s) {
+			if _, err := buf.WriteString(s + "\n"); err != nil {
+				return nil, err
+			}
+		}
+		if s := strings.TrimSpace(s); strings.HasSuffix(s, ";") && !strings.HasPrefix(s, "--") {
+			stmts = append(stmts, strings.TrimSpace(buf.String()))
+			buf.Reset()
+		}
+	}
+	if err := sc.Err(); err != nil {
+		return nil, fmt.Errorf("sql/migrate: scanning migration %q: %w", f.Name(), err)
+	}
+	if state == none {
+		return nil, fmt.Errorf("sql/migrate: empty migration %q", f.Name())
+	}
+	return stmts, nil
+}
+
 // funcs contains the template.FuncMap for the different formatters.
 var funcs = template.FuncMap{
 	"inc": func(x int) int { return x + 1 },


Test patch:
diff --git a/cmd/atlas/internal/cmdapi/diff_test.go b/cmd/atlas/internal/cmdapi/diff_test.go
index d13d6014..32c1b04b 100644
--- a/cmd/atlas/internal/cmdapi/diff_test.go
+++ b/cmd/atlas/internal/cmdapi/diff_test.go
@@ -9,7 +9,6 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -40,7 +39,7 @@ func TestDiffCmd_Synced(t *testing.T) {
 
 // openSQLite creates a sqlite db, seeds it with the seed query and returns the url to it.
 func openSQLite(t *testing.T, seed string) string {
-	f, err := ioutil.TempFile("", "sqlite.db")
+	f, err := os.CreateTemp("", "sqlite.db")
 	require.NoError(t, err)
 	t.Cleanup(func() {
 		os.Remove(f.Name())
diff --git a/cmd/atlas/internal/cmdapi/migrate_test.go b/cmd/atlas/internal/cmdapi/migrate_test.go
index 34a9af8d..e54e2df8 100644
--- a/cmd/atlas/internal/cmdapi/migrate_test.go
+++ b/cmd/atlas/internal/cmdapi/migrate_test.go
@@ -400,7 +400,8 @@ func TestMigrate_New(t *testing.T) {
 
 func TestMigrate_Validate(t *testing.T) {
 	// Without re-playing.
-	MigrateFlags.DevURL = "" // global flags are set from other tests ...
+	MigrateFlags.DevURL = ""         // global flags are set from other tests ...
+	MigrateFlags.DirFormat = "atlas" // global flags are set from other tests ...
 	s, err := runCmd(Root, "migrate", "validate", "--dir", "file://testdata/mysql")
 	require.Zero(t, s)
 	require.NoError(t, err)
@@ -419,6 +420,9 @@ func TestMigrate_Validate(t *testing.T) {
 	require.NoError(t, os.WriteFile(filepath.Join(p, "2_second.sql"), []byte("create table t2 (c2 int)"), 0644))
 	_, err = runCmd(Root, "migrate", "hash", "--force", "--dir", "file://"+p)
 	require.NoError(t, err)
+	b, err := os.ReadFile(filepath.Join(p, "1_initial.sql"))
+	require.NoError(t, err)
+	fmt.Println(string(b))
 	s, err = runCmd(
 		Root, "migrate", "validate",
 		"--dir", "file://"+p,
diff --git a/internal/integration/hclsqlspec/hclsqlspec_test.go b/internal/integration/hclsqlspec/hclsqlspec_test.go
index fc31558f..91998fea 100644
--- a/internal/integration/hclsqlspec/hclsqlspec_test.go
+++ b/internal/integration/hclsqlspec/hclsqlspec_test.go
@@ -20,7 +20,7 @@ import (
 var dialects = []struct {
 	name string
 	schemahcl.Marshaler
-	Eval func(b []byte, v interface{}, inp map[string]string) error
+	Eval func(b []byte, v any, inp map[string]string) error
 }{
 	{
 		name:      "mysql",
diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go
index 6e32d73a..4b013967 100644
--- a/internal/integration/integration_test.go
+++ b/internal/integration/integration_test.go
@@ -565,7 +565,7 @@ func buildCmd(t *testing.T) (string, error) {
 	return filepath.Join(td, "atlas"), nil
 }
 
-func evalBytes(b []byte, v interface{}, ev schemahcl.Evaluator) error {
+func evalBytes(b []byte, v any, ev schemahcl.Evaluator) error {
 	p := hclparse.NewParser()
 	if _, diag := p.ParseHCL(b, ""); diag.HasErrors() {
 		return diag
diff --git a/internal/integration/testdata/mysql/cli-migrate-diff-format.txt b/internal/integration/testdata/mysql/cli-migrate-diff-format.txt
index b51871c6..36d431e5 100644
--- a/internal/integration/testdata/mysql/cli-migrate-diff-format.txt
+++ b/internal/integration/testdata/mysql/cli-migrate-diff-format.txt
@@ -19,6 +19,15 @@ cmpmig 0 goose/1.sql
 atlas migrate diff --dev-url URL --to file://2.hcl --dir-format goose second
 cmpmig 1 goose/2.sql
 
+rm migrations
+mkdir migrations
+
+atlas migrate diff --dev-url URL --to file://1.hcl --dir-format dbmate first
+cmpmig 0 dbmate/1.sql
+
+atlas migrate diff --dev-url URL --to file://2.hcl --dir-format dbmate second
+cmpmig 1 dbmate/2.sql
+
 -- golang-migrate/1.up.sql --
 -- create "users" table
 CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_general_ci;
@@ -53,6 +62,24 @@ ALTER TABLE `users` ADD COLUMN `email` varchar(100) NULL;
 -- reverse: modify "users" table
 ALTER TABLE `users` DROP COLUMN `email`;
 
+-- dbmate/1.sql --
+-- migrate:up
+-- create "users" table
+CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_general_ci;
+
+-- migrate:down
+-- reverse: create "users" table
+DROP TABLE `users`;
+
+-- dbmate/2.sql --
+-- migrate:up
+-- modify "users" table
+ALTER TABLE `users` ADD COLUMN `email` varchar(100) NULL;
+
+-- migrate:down
+-- reverse: modify "users" table
+ALTER TABLE `users` DROP COLUMN `email`;
+
 -- 1.hcl --
 schema "script_cli_migrate_diff_format" {}
 
diff --git a/sql/migrate/migrate_test.go b/sql/migrate/migrate_test.go
index 06495d74..fccc04e9 100644
--- a/sql/migrate/migrate_test.go
+++ b/sql/migrate/migrate_test.go
@@ -438,7 +438,7 @@ func (m *mockDriver) failOn(n int, err error) {
 	m.failWith = err
 }
 
-func (m *mockDriver) ExecContext(_ context.Context, query string, _ ...interface{}) (sql.Result, error) {
+func (m *mockDriver) ExecContext(_ context.Context, query string, _ ...any) (sql.Result, error) {
 	if m.failCounter > 0 {
 		m.failCounter--
 		if m.failCounter == 0 {
diff --git a/sql/sqlclient/client_test.go b/sql/sqlclient/client_test.go
index 971de93b..3ea67565 100644
--- a/sql/sqlclient/client_test.go
+++ b/sql/sqlclient/client_test.go
@@ -123,6 +123,6 @@ type mockDriver struct {
 	db schema.ExecQuerier
 }
 
-func (m *mockDriver) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
+func (m *mockDriver) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
 	return m.db.ExecContext(ctx, query, args...)
 }
diff --git a/sql/sqltool/testdata/dbmate/1_initial.sql b/sql/sqltool/testdata/dbmate/1_initial.sql
new file mode 100644
index 00000000..83ea267e
--- /dev/null
+++ b/sql/sqltool/testdata/dbmate/1_initial.sql
@@ -0,0 +1,19 @@
+-- migrate:up
+CREATE TABLE post
+(
+    id    int NOT NULL,
+    title text,
+    body  text,
+    PRIMARY KEY (id)
+);
+
+ALTER TABLE post ADD created_at TIMESTAMP NOT NULL;
+
+INSERT INTO post (title) VALUES (
+'This is
+my multiline
+
+value');
+
+-- migrate:down
+DROP TABLE post;
\ No newline at end of file
diff --git a/sql/sqltool/testdata/dbmate/2_second_migration.sql b/sql/sqltool/testdata/dbmate/2_second_migration.sql
new file mode 100644
index 00000000..f9fa036e
--- /dev/null
+++ b/sql/sqltool/testdata/dbmate/2_second_migration.sql
@@ -0,0 +1,9 @@
+
+
+
+-- migrate:up
+
+
+
+
+CREATE TABLE tbl_2 (col INT);
\ No newline at end of file
diff --git a/sql/sqltool/testdata/dbmate/atlas.sum b/sql/sqltool/testdata/dbmate/atlas.sum
new file mode 100644
index 00000000..312a249d
--- /dev/null
+++ b/sql/sqltool/testdata/dbmate/atlas.sum
@@ -0,0 +1,3 @@
+h1:bpWcrOjC8i9SkrTqTKi5WiZxs14d9kzj1ZtgRn2pe8k=
+1_initial.sql h1:Ifwt1OSc53epUlXURSjMhu1FH2kqRTu9kGtOYA7E0zg=
+2_second_migration.sql h1:0G2qai762kh4Ug7NyouaCICtrH5uZxn/8xTNBvsRhTw=
diff --git a/sql/sqltool/tool_test.go b/sql/sqltool/tool_test.go
index 2cf16498..c481ec72 100644
--- a/sql/sqltool/tool_test.go
+++ b/sql/sqltool/tool_test.go
@@ -169,6 +169,24 @@ func TestScanners(t *testing.T) {
 				{"CREATE\nOR REPLACE FUNCTION histories_partition_creation( DATE, DATE )\nreturns void AS $$\nDECLARE\ncreate_query text;\nBEGIN\nFOR create_query IN\nSELECT 'CREATE TABLE IF NOT EXISTS histories_'\n           || TO_CHAR(d, 'YYYY_MM')\n           || ' ( CHECK( created_at >= timestamp '''\n           || TO_CHAR(d, 'YYYY-MM-DD 00:00:00')\n           || ''' AND created_at < timestamp '''\n           || TO_CHAR(d + INTERVAL '1 month', 'YYYY-MM-DD 00:00:00')\n           || ''' ) ) inherits ( histories );'\nFROM generate_series($1, $2, '1 month') AS d LOOP\n    EXECUTE create_query;\nEND LOOP;  -- LOOP END\nEND;         -- FUNCTION END\n$$\nlanguage plpgsql;"},
 			},
 		},
+		{
+			name: "dbmate",
+			dir: func() migrate.Dir {
+				d, err := sqltool.NewDBMateDir("testdata/dbmate")
+				require.NoError(t, err)
+				return d
+			}(),
+			versions:     []string{"1", "2"},
+			descriptions: []string{"initial", "second_migration"},
+			stmts: [][]string{
+				{
+					"CREATE TABLE post\n(\n    id    int NOT NULL,\n    title text,\n    body  text,\n    PRIMARY KEY (id)\n);",
+					"ALTER TABLE post ADD created_at TIMESTAMP NOT NULL;",
+					"INSERT INTO post (title) VALUES (\n'This is\nmy multiline\n\nvalue');",
+				},
+				{"CREATE TABLE tbl_2 (col INT);"},
+			},
+		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
 			files, err := tt.dir.Files()