From 101347c94a09431aae3ca8e55c99836b41f06f94 Mon Sep 17 00:00:00 2001 From: Joel Hess Date: Thu, 4 Dec 2025 21:23:30 -0600 Subject: [PATCH 1/9] Update go to 1.24.11 (#3628) --- cmd/atlas/go.mod | 2 +- go.mod | 2 +- internal/integration/go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/atlas/go.mod b/cmd/atlas/go.mod index 887625b743d..035530b0d10 100644 --- a/cmd/atlas/go.mod +++ b/cmd/atlas/go.mod @@ -1,6 +1,6 @@ module ariga.io/atlas/cmd/atlas -go 1.24.9 +go 1.24.11 replace ariga.io/atlas => ../.. diff --git a/go.mod b/go.mod index e5e86209d92..7d372721364 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module ariga.io/atlas -go 1.24.9 +go 1.24.11 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 diff --git a/internal/integration/go.mod b/internal/integration/go.mod index d389e13762b..cb4967d269f 100644 --- a/internal/integration/go.mod +++ b/internal/integration/go.mod @@ -1,6 +1,6 @@ module ariga.io/atlas/internal/integration -go 1.24.9 +go 1.24.11 replace ariga.io/atlas => ../../ From 6e2d61fe296a80db7714b16ad67f529f03b12359 Mon Sep 17 00:00:00 2001 From: Ariel Mashraki <7413593+a8m@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:04:32 +0200 Subject: [PATCH 2/9] cmd/atlas: suggest standard build in case of errors (#3626) --- cmd/atlas/main_oss.go | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/cmd/atlas/main_oss.go b/cmd/atlas/main_oss.go index 7ad1f543516..f31f432dde2 100644 --- a/cmd/atlas/main_oss.go +++ b/cmd/atlas/main_oss.go @@ -8,6 +8,13 @@ package main import ( "context" + "fmt" + "os" + "runtime" + "time" + + "ariga.io/atlas/cmd/atlas/internal/cmdlog" + "ariga.io/atlas/cmd/atlas/internal/cmdstate" ) func extendContext(ctx context.Context) (context.Context, error) { @@ -20,5 +27,32 @@ func vercheckEndpoint(context.Context) string { // initialize is a no-op for the OSS version. func initialize(ctx context.Context) (context.Context, func(error)) { - return ctx, func(error) {} + return ctx, func(err error) { + if err == nil { + return + } + const errorsFileName = "community_error.json" + type prompt struct { + LastSuggested time.Time `json:"last_suggested"` + } + state := &cmdstate.File[prompt]{Name: errorsFileName} + prev, err := state.Read() + if err != nil || time.Since(prev.LastSuggested) < 24*time.Hour { + return + } + release := "curl -sSf https://atlasgo.sh | sh" + if runtime.GOOS == "windows" { + release = "https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe" + } + if err := cmdlog.WarnOnce(os.Stderr, cmdlog.ColorCyan(fmt.Sprintf(`You're running the community build of Atlas, which may differ from the official version. +If this error persists, try installing the official version as a troubleshooting step: + + %s + +More installation options: https://atlasgo.io/docs#installation +`, release))); err == nil { + prev.LastSuggested = time.Now() + _ = state.Write(prev) + } + } } From 428c4cf84cd4790b08e35f383b4c2510c9e93f4a Mon Sep 17 00:00:00 2001 From: Noam Cattan <62568565+noamcattan@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:12:54 +0200 Subject: [PATCH 3/9] atlasexec: update schema stats inspect command (#3632) * atlasexec: update schema stats inspect command * test * remove unused function --- atlasexec/atlas_models.go | 43 ---------------------------------- atlasexec/atlas_schema.go | 14 +++-------- atlasexec/atlas_schema_test.go | 32 ------------------------- go.mod | 7 ++---- go.sum | 16 ++++--------- 5 files changed, 9 insertions(+), 103 deletions(-) diff --git a/atlasexec/atlas_models.go b/atlasexec/atlas_models.go index d9b3d6c54e0..bc7e313137a 100644 --- a/atlasexec/atlas_models.go +++ b/atlasexec/atlas_models.go @@ -6,16 +6,11 @@ package atlasexec import ( "errors" - "fmt" - "strings" "time" "ariga.io/atlas/sql/schema" "ariga.io/atlas/sql/sqlcheck" "ariga.io/atlas/sql/sqlclient" - - "github.com/prometheus/common/expfmt" - "github.com/prometheus/common/model" ) type ( @@ -174,41 +169,3 @@ func (r *SummaryReport) Errors() []error { } return errs } - -// ParsePrometheusMetrics parses Prometheus format metrics and extract table size metrics -// data is expected to be of this format: -// atlas_table_size_bytes{schema="public",table="users"} 123456 -// atlas_table_size_bytes{schema="public",table="orders"} 789012 -func ParsePrometheusMetrics(data string) ([]TableSizeMetric, error) { - var metrics []TableSizeMetric - parser := expfmt.NewTextParser(model.LegacyValidation) - metricFamilies, err := parser.TextToMetricFamilies(strings.NewReader(data)) - if err != nil { - return nil, fmt.Errorf("failed to parse prometheus metrics: %w", err) - } - for _, mf := range metricFamilies { - if mf.GetName() == MetricTableSizeBytes { - for _, metric := range mf.GetMetric() { - var schema, table string - for _, label := range metric.GetLabel() { - switch label.GetName() { - case "schema": - schema = label.GetValue() - case "table": - table = label.GetValue() - } - } - var value float64 - if gauge := metric.GetGauge(); gauge != nil { - value = gauge.GetValue() - } - metrics = append(metrics, TableSizeMetric{ - Schema: schema, - Table: table, - Value: value, - }) - } - } - } - return metrics, nil -} diff --git a/atlasexec/atlas_schema.go b/atlasexec/atlas_schema.go index 4f2ee6cc128..86928d6b366 100644 --- a/atlasexec/atlas_schema.go +++ b/atlasexec/atlas_schema.go @@ -267,7 +267,6 @@ type ( Vars VarArgs URL string - Format string Exclude []string Include []string Schema []string @@ -804,8 +803,8 @@ func (c *Client) SchemaLint(ctx context.Context, params *SchemaLintParams) (*Sch } // SchemaStatsInspect runs the 'schema stats inspect' command. -func (c *Client) SchemaStatsInspect(ctx context.Context, params *SchemaStatsInspectParams) ([]TableSizeMetric, error) { - args := []string{"schema", "stats", "inspect"} +func (c *Client) SchemaStatsInspect(ctx context.Context, params *SchemaStatsInspectParams) (string, error) { + args := []string{"schema", "stats", "inspect", "--format", "{{ json .Realm }}"} if params.Env != "" { args = append(args, "--env", params.Env) } @@ -815,9 +814,6 @@ func (c *Client) SchemaStatsInspect(ctx context.Context, params *SchemaStatsInsp if params.URL != "" { args = append(args, "--url", params.URL) } - if params.Format != "" { - args = append(args, "--format", params.Format) - } if len(params.Schema) > 0 { args = append(args, "--schema", listString(params.Schema)) } @@ -830,11 +826,7 @@ func (c *Client) SchemaStatsInspect(ctx context.Context, params *SchemaStatsInsp if params.Vars != nil { args = append(args, params.Vars.AsArgs()...) } - output, err := stringVal(c.runCommand(ctx, args)) - if err != nil { - return nil, err - } - return ParsePrometheusMetrics(output) + return stringVal(c.runCommand(ctx, args)) } // AsArgs returns the parameters as arguments. diff --git a/atlasexec/atlas_schema_test.go b/atlasexec/atlas_schema_test.go index 30e0ff16d92..91561b5acd9 100644 --- a/atlasexec/atlas_schema_test.go +++ b/atlasexec/atlas_schema_test.go @@ -914,35 +914,3 @@ func TestSchema_Lint(t *testing.T) { }) } } - -func TestSchema_StatsInspect(t *testing.T) { - wd, err := os.Getwd() - require.NoError(t, err) - c, err := atlasexec.NewClient(t.TempDir(), filepath.Join(wd, "./mock-atlas.sh")) - require.NoError(t, err) - - // Test case with Prometheus metrics output - prometheusOutput := `# HELP atlas_table_size_bytes Size of the table in bytes. -# TYPE atlas_table_size_bytes gauge -atlas_table_size_bytes{schema="test",table="test"} 16384.0 -atlas_table_size_bytes{schema="test",table="users"} 6.832128e+06 -` - t.Run("basic stats with prometheus metrics", func(t *testing.T) { - params := &atlasexec.SchemaStatsInspectParams{ - URL: "sqlite://test.db", - } - t.Setenv("TEST_ARGS", "schema stats inspect --url sqlite://test.db") - t.Setenv("TEST_STDOUT", prometheusOutput) - result, err := c.SchemaStatsInspect(context.Background(), params) - - require.NoError(t, err) - require.Len(t, result, 2) - // Verify metrics - require.Equal(t, "test", result[0].Schema) - require.Equal(t, "test", result[0].Table) - require.Equal(t, 16384.0, result[0].Value) - require.Equal(t, "test", result[1].Schema) - require.Equal(t, "users", result[1].Table) - require.Equal(t, 6832128.0, result[1].Value) - }) -} diff --git a/go.mod b/go.mod index 7d372721364..47ccfafcf1b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/go-openapi/inflect v0.19.0 github.com/hashicorp/hcl/v2 v2.13.0 github.com/mattn/go-sqlite3 v1.14.28 - github.com/prometheus/common v0.66.1 github.com/stretchr/testify v1.11.1 github.com/zclconf/go-cty v1.14.4 github.com/zclconf/go-cty-yaml v1.1.0 @@ -22,13 +21,11 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/text v0.28.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 62201d80e66..339ef10b0b9 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -29,14 +31,8 @@ github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEu github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -45,14 +41,10 @@ github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8 github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 7af35996ed4fd61c415bdb46e19fd79a13ba4581 Mon Sep 17 00:00:00 2001 From: Tran Minh Luan Date: Mon, 12 Jan 2026 18:12:35 +0700 Subject: [PATCH 4/9] sql/migrate: fix truncation of Unicode arguments in directives (#3648) --- sql/migrate/dir.go | 2 +- sql/migrate/dir_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/migrate/dir.go b/sql/migrate/dir.go index 715c29a8a1f..5e341f6370d 100644 --- a/sql/migrate/dir.go +++ b/sql/migrate/dir.go @@ -274,7 +274,7 @@ const ( directivePrefixSQL = "-- " ) -var reDirective = regexp.MustCompile(`^([ -~]*)atlas:(\w+)(?: +([ -~]*))*`) +var reDirective = regexp.MustCompile(`^([ -~]*)atlas:(\w+)(?: +(.+))*`) // directive searches in the content a line that matches a directive // with the given prefix and name. For example: diff --git a/sql/migrate/dir_test.go b/sql/migrate/dir_test.go index 79a529b8baf..8b6aae69cc1 100644 --- a/sql/migrate/dir_test.go +++ b/sql/migrate/dir_test.go @@ -430,11 +430,11 @@ alter table users drop column id; -- atlas:import -- atlas:import bar baz -- atlas:import qux +-- atlas:import files_📄 alter table users drop column id; `)) - require.Equal(t, []string{"foo", "", "bar baz", "qux"}, f.Directive("import")) - + require.Equal(t, []string{"foo", "", "bar baz", "qux", "files_📄"}, f.Directive("import")) f = migrate.NewLocalFile("1.sql", []byte("-- atlas:import foo\n")) require.Equal(t, []string{"foo"}, f.Directive("import")) } From 1a19b3deb9b228120e4fb17cf461772da48bde73 Mon Sep 17 00:00:00 2001 From: Ronen Lubin <63970571+ronenlu@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:08:58 +0200 Subject: [PATCH 5/9] atlasexec: add migrate set cmd to sdk (#3652) Co-authored-by: ronenlu --- atlasexec/atlas_migrate.go | 39 ++++++++++++++ atlasexec/atlas_migrate_example_test.go | 30 +++++++++++ atlasexec/atlas_migrate_test.go | 71 +++++++++++++++++++++++++ 3 files changed, 140 insertions(+) diff --git a/atlasexec/atlas_migrate.go b/atlasexec/atlas_migrate.go index fa2dc034baa..d0ca67a895b 100644 --- a/atlasexec/atlas_migrate.go +++ b/atlasexec/atlas_migrate.go @@ -165,6 +165,17 @@ type ( URL string RevisionsSchema string } + // MigrateSetParams are the parameters for the `migrate set` command. + MigrateSetParams struct { + ConfigURL string + Env string + Vars VarArgs + + DirURL string + URL string + RevisionsSchema string + Version string + } // MigrateDiffParams are the parameters for the `migrate diff` command. MigrateDiffParams struct { ConfigURL string @@ -419,6 +430,34 @@ func (c *Client) MigrateStatus(ctx context.Context, params *MigrateStatusParams) return firstResult(jsonDecode[MigrateStatus](c.runCommand(ctx, args))) } +// MigrateSet runs the 'migrate set' command. +func (c *Client) MigrateSet(ctx context.Context, params *MigrateSetParams) error { + args := []string{"migrate", "set"} + if params.Env != "" { + args = append(args, "--env", params.Env) + } + if params.ConfigURL != "" { + args = append(args, "--config", params.ConfigURL) + } + if params.URL != "" { + args = append(args, "--url", params.URL) + } + if params.DirURL != "" { + args = append(args, "--dir", params.DirURL) + } + if params.RevisionsSchema != "" { + args = append(args, "--revisions-schema", params.RevisionsSchema) + } + if params.Vars != nil { + args = append(args, params.Vars.AsArgs()...) + } + if params.Version != "" { + args = append(args, params.Version) + } + _, err := c.runCommand(ctx, args) + return err +} + // MigrateDiff runs the 'migrate diff --dry-run' command and returns the generated migration files without changing the filesystem. // Requires atlas CLI to be logged in to the cloud. func (c *Client) MigrateDiff(ctx context.Context, params *MigrateDiffParams) (*MigrateDiff, error) { diff --git a/atlasexec/atlas_migrate_example_test.go b/atlasexec/atlas_migrate_example_test.go index 43e375dc98a..f0dc97fa945 100644 --- a/atlasexec/atlas_migrate_example_test.go +++ b/atlasexec/atlas_migrate_example_test.go @@ -41,3 +41,33 @@ func ExampleClient_MigrateApply() { } fmt.Printf("Applied %d migrations\n", len(res.Applied)) } + +func ExampleClient_MigrateSet() { + // Define the execution context, supplying a migration directory + // and potentially an `atlas.hcl` configuration file using `atlasexec.WithHCL`. + workdir, err := atlasexec.NewWorkingDir( + atlasexec.WithMigrations( + os.DirFS("./migrations"), + ), + ) + if err != nil { + log.Fatalf("failed to load working directory: %v", err) + } + // atlasexec works on a temporary directory, so we need to close it + defer workdir.Close() + + // Initialize the client. + client, err := atlasexec.NewClient(workdir.Path(), "atlas") + if err != nil { + log.Fatalf("failed to initialize client: %v", err) + } + // Run `atlas migrate set` to mark migrations as applied up to version "3". + err = client.MigrateSet(context.Background(), &atlasexec.MigrateSetParams{ + URL: "sqlite:///tmp/demo.db?_fk=1&cache=shared", + Version: "3", + }) + if err != nil { + log.Fatalf("failed to set migrations: %v", err) + } + fmt.Println("Migration version set successfully") +} diff --git a/atlasexec/atlas_migrate_test.go b/atlasexec/atlas_migrate_test.go index 3d103714436..c77b915731e 100644 --- a/atlasexec/atlas_migrate_test.go +++ b/atlasexec/atlas_migrate_test.go @@ -129,6 +129,77 @@ func TestMigrate_Apply(t *testing.T) { } } +func TestMigrate_Set(t *testing.T) { + wd, err := os.Getwd() + require.NoError(t, err) + c, err := atlasexec.NewClient(t.TempDir(), filepath.Join(wd, "./mock-atlas.sh")) + require.NoError(t, err) + + for _, tt := range []struct { + name string + params *atlasexec.MigrateSetParams + args string + }{ + { + name: "no params", + params: &atlasexec.MigrateSetParams{}, + args: "migrate set", + }, + { + name: "with env", + params: &atlasexec.MigrateSetParams{ + Env: "test", + }, + args: "migrate set --env test", + }, + { + name: "with url", + params: &atlasexec.MigrateSetParams{ + URL: "sqlite://file?_fk=1&cache=shared&mode=memory", + }, + args: "migrate set --url sqlite://file?_fk=1&cache=shared&mode=memory", + }, + { + name: "with dir", + params: &atlasexec.MigrateSetParams{ + DirURL: "file://migrations", + }, + args: "migrate set --dir file://migrations", + }, + { + name: "with revisions-schema", + params: &atlasexec.MigrateSetParams{ + RevisionsSchema: "my_revisions", + }, + args: "migrate set --revisions-schema my_revisions", + }, + { + name: "with version", + params: &atlasexec.MigrateSetParams{ + Version: "3", + }, + args: "migrate set 3", + }, + { + name: "with all params", + params: &atlasexec.MigrateSetParams{ + URL: "sqlite://file?_fk=1&cache=shared&mode=memory", + DirURL: "file://migrations", + RevisionsSchema: "my_revisions", + Version: "1.2.4", + }, + args: "migrate set --url sqlite://file?_fk=1&cache=shared&mode=memory --dir file://migrations --revisions-schema my_revisions 1.2.4", + }, + } { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("TEST_ARGS", tt.args) + t.Setenv("TEST_STDOUT", "ok") + err := c.MigrateSet(context.Background(), tt.params) + require.NoError(t, err) + }) + } +} + func TestMigrate_Down(t *testing.T) { wd, err := os.Getwd() require.NoError(t, err) From 6d81150f68d8477721af321ac3c70a4011ee2d64 Mon Sep 17 00:00:00 2001 From: "Dat. Ba Dao" Date: Sat, 7 Feb 2026 10:58:21 +0700 Subject: [PATCH 6/9] atlasexec: improve error handling in migrate and schema apply (#3661) --- atlasexec/atlas_migrate.go | 10 ++++- atlasexec/atlas_migrate_test.go | 67 +++++++++++++++++++++++++++++++++ atlasexec/atlas_schema.go | 11 +++++- atlasexec/atlas_schema_test.go | 67 +++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/atlasexec/atlas_migrate.go b/atlasexec/atlas_migrate.go index d0ca67a895b..3095cc3aa07 100644 --- a/atlasexec/atlas_migrate.go +++ b/atlasexec/atlas_migrate.go @@ -773,10 +773,16 @@ func newMigrateApplyError(r []*MigrateApply, stderr string) error { // Error implements the error interface. func (e *MigrateApplyError) Error() string { + var errs []string + for _, r := range e.Result { + if r.Error != "" { + errs = append(errs, r.Error) + } + } if e.Stderr != "" { - return e.Stderr + errs = append(errs, e.Stderr) } - return last(e.Result).Error + return strings.Join(errs, "\n") } func plural(n int) (s string) { diff --git a/atlasexec/atlas_migrate_test.go b/atlasexec/atlas_migrate_test.go index c77b915731e..4f8e537c350 100644 --- a/atlasexec/atlas_migrate_test.go +++ b/atlasexec/atlas_migrate_test.go @@ -388,6 +388,73 @@ func TestAtlasMigrate_ApplyBroken(t *testing.T) { }, report.Result[0].Applied[0].Error) } +func TestMigrateApplyError_Error(t *testing.T) { + t.Run("single result error only", func(t *testing.T) { + e := &atlasexec.MigrateApplyError{ + Result: []*atlasexec.MigrateApply{ + {Error: "sql/migrate: execution failed"}, + }, + } + require.Equal(t, "sql/migrate: execution failed", e.Error()) + }) + + t.Run("stderr only", func(t *testing.T) { + e := &atlasexec.MigrateApplyError{ + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "Error: unable to acquire lock", e.Error()) + }) + + t.Run("single result error and stderr", func(t *testing.T) { + e := &atlasexec.MigrateApplyError{ + Result: []*atlasexec.MigrateApply{ + {Error: "sql/migrate: execution failed"}, + }, + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "sql/migrate: execution failed\nError: unable to acquire lock", e.Error()) + }) + + t.Run("multiple result errors and stderr", func(t *testing.T) { + e := &atlasexec.MigrateApplyError{ + Result: []*atlasexec.MigrateApply{ + {Error: "error on target 1"}, + {Error: "error on target 2"}, + }, + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "error on target 1\nerror on target 2\nError: unable to acquire lock", e.Error()) + }) + + t.Run("multiple results with some having no error", func(t *testing.T) { + e := &atlasexec.MigrateApplyError{ + Result: []*atlasexec.MigrateApply{ + {Error: ""}, + {Error: "error on target 2"}, + {Error: ""}, + }, + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "error on target 2\nError: unable to acquire lock", e.Error()) + }) + + t.Run("no errors at all", func(t *testing.T) { + e := &atlasexec.MigrateApplyError{ + Result: []*atlasexec.MigrateApply{ + {Error: ""}, + }, + } + require.Equal(t, "", e.Error()) + }) + + t.Run("nil result with stderr", func(t *testing.T) { + e := &atlasexec.MigrateApplyError{ + Stderr: "Error: connection refused", + } + require.Equal(t, "Error: connection refused", e.Error()) + }) +} + func TestAtlasMigrate_Apply(t *testing.T) { ec, err := atlasexec.NewWorkingDir( atlasexec.WithMigrations(os.DirFS(filepath.Join("testdata", "migrations"))), diff --git a/atlasexec/atlas_schema.go b/atlasexec/atlas_schema.go index 86928d6b366..f9260a00a1e 100644 --- a/atlasexec/atlas_schema.go +++ b/atlasexec/atlas_schema.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "strconv" + "strings" "time" "ariga.io/atlas/sql/migrate" @@ -867,8 +868,14 @@ func newSchemaApplyError(r []*SchemaApply, stderr string) error { // Error implements the error interface. func (e *SchemaApplyError) Error() string { + var errs []string + for _, r := range e.Result { + if r.Error != "" { + errs = append(errs, r.Error) + } + } if e.Stderr != "" { - return e.Stderr + errs = append(errs, e.Stderr) } - return last(e.Result).Error + return strings.Join(errs, "\n") } diff --git a/atlasexec/atlas_schema_test.go b/atlasexec/atlas_schema_test.go index 91561b5acd9..1c334ef541c 100644 --- a/atlasexec/atlas_schema_test.go +++ b/atlasexec/atlas_schema_test.go @@ -803,6 +803,73 @@ func TestSchema_ApplyEnvs(t *testing.T) { require.Equal(t, "sqlite://local-bu.db", err2.Result[2].URL.String()) } +func TestSchemaApplyError_Error(t *testing.T) { + t.Run("single result error only", func(t *testing.T) { + e := &atlasexec.SchemaApplyError{ + Result: []*atlasexec.SchemaApply{ + {Error: "schema apply failed"}, + }, + } + require.Equal(t, "schema apply failed", e.Error()) + }) + + t.Run("stderr only", func(t *testing.T) { + e := &atlasexec.SchemaApplyError{ + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "Error: unable to acquire lock", e.Error()) + }) + + t.Run("single result error and stderr", func(t *testing.T) { + e := &atlasexec.SchemaApplyError{ + Result: []*atlasexec.SchemaApply{ + {Error: "schema apply failed"}, + }, + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "schema apply failed\nError: unable to acquire lock", e.Error()) + }) + + t.Run("multiple result errors and stderr", func(t *testing.T) { + e := &atlasexec.SchemaApplyError{ + Result: []*atlasexec.SchemaApply{ + {Error: "error on target 1"}, + {Error: "error on target 2"}, + }, + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "error on target 1\nerror on target 2\nError: unable to acquire lock", e.Error()) + }) + + t.Run("multiple results with some having no error", func(t *testing.T) { + e := &atlasexec.SchemaApplyError{ + Result: []*atlasexec.SchemaApply{ + {Error: ""}, + {Error: "error on target 2"}, + {Error: ""}, + }, + Stderr: "Error: unable to acquire lock", + } + require.Equal(t, "error on target 2\nError: unable to acquire lock", e.Error()) + }) + + t.Run("no errors at all", func(t *testing.T) { + e := &atlasexec.SchemaApplyError{ + Result: []*atlasexec.SchemaApply{ + {Error: ""}, + }, + } + require.Equal(t, "", e.Error()) + }) + + t.Run("nil result with stderr", func(t *testing.T) { + e := &atlasexec.SchemaApplyError{ + Stderr: "Error: connection refused", + } + require.Equal(t, "Error: connection refused", e.Error()) + }) +} + func TestAtlasSchema_Lint(t *testing.T) { t.Run("with broken config", func(t *testing.T) { c, err := atlasexec.NewClient(".", "atlas") From 2a5b118a67c5e9f85e16900c9aecd9918bba782c Mon Sep 17 00:00:00 2001 From: Jannik Clausen <12862103+masseelch@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:39:09 +0100 Subject: [PATCH 7/9] atlasexec: migrate ls (#3675) --- atlasexec/atlas_migrate.go | 34 ++++++++++ atlasexec/atlas_migrate_test.go | 106 ++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/atlasexec/atlas_migrate.go b/atlasexec/atlas_migrate.go index 3095cc3aa07..452e1ae36ff 100644 --- a/atlasexec/atlas_migrate.go +++ b/atlasexec/atlas_migrate.go @@ -165,6 +165,16 @@ type ( URL string RevisionsSchema string } + // MigrateLsParams are the parameters for the `migrate ls` command. + MigrateLsParams struct { + ConfigURL string + Env string + Vars VarArgs + + DirURL string + Short bool // -s: print only migration version (omit description and .sql suffix) + Latest bool // -l: print only the latest migration file + } // MigrateSetParams are the parameters for the `migrate set` command. MigrateSetParams struct { ConfigURL string @@ -430,6 +440,30 @@ func (c *Client) MigrateStatus(ctx context.Context, params *MigrateStatusParams) return firstResult(jsonDecode[MigrateStatus](c.runCommand(ctx, args))) } +// MigrateLs runs the 'migrate ls' command and returns the listed migration file names (or versions when Short is true), one per line. +func (c *Client) MigrateLs(ctx context.Context, params *MigrateLsParams) (string, error) { + args := []string{"migrate", "ls"} + if params.ConfigURL != "" { + args = append(args, "--config", params.ConfigURL) + } + if params.Env != "" { + args = append(args, "--env", params.Env) + } + if params.DirURL != "" { + args = append(args, "--dir", params.DirURL) + } + if params.Vars != nil { + args = append(args, params.Vars.AsArgs()...) + } + if params.Short { + args = append(args, "--short") + } + if params.Latest { + args = append(args, "--latest") + } + return stringVal(c.runCommand(ctx, args)) +} + // MigrateSet runs the 'migrate set' command. func (c *Client) MigrateSet(ctx context.Context, params *MigrateSetParams) error { args := []string{"migrate", "set"} diff --git a/atlasexec/atlas_migrate_test.go b/atlasexec/atlas_migrate_test.go index 4f8e537c350..47985d67af5 100644 --- a/atlasexec/atlas_migrate_test.go +++ b/atlasexec/atlas_migrate_test.go @@ -129,6 +129,112 @@ func TestMigrate_Apply(t *testing.T) { } } +func TestMigrate_Ls(t *testing.T) { + wd, err := os.Getwd() + require.NoError(t, err) + c, err := atlasexec.NewClient(t.TempDir(), filepath.Join(wd, "./mock-atlas.sh")) + require.NoError(t, err) + + for _, tt := range []struct { + name string + params *atlasexec.MigrateLsParams + args string + stdout string + }{ + { + name: "no params", + params: &atlasexec.MigrateLsParams{}, + args: "migrate ls", + stdout: "\n", + }, + { + name: "with dir", + params: &atlasexec.MigrateLsParams{ + DirURL: "file://migrations", + }, + args: "migrate ls --dir file://migrations", + stdout: "\n", + }, + { + name: "with short", + params: &atlasexec.MigrateLsParams{ + Short: true, + }, + args: "migrate ls --short", + stdout: "\n", + }, + { + name: "with latest", + params: &atlasexec.MigrateLsParams{ + Latest: true, + }, + args: "migrate ls --latest", + stdout: "\n", + }, + { + name: "with short and latest", + params: &atlasexec.MigrateLsParams{ + DirURL: "file://migrations", + Short: true, + Latest: true, + }, + args: "migrate ls --dir file://migrations --short --latest", + stdout: "20230727105615\n", + }, + { + name: "with config and env", + params: &atlasexec.MigrateLsParams{ + ConfigURL: "file://atlas.hcl", + Env: "dev", + }, + args: "migrate ls --config file://atlas.hcl --env dev", + stdout: "\n", + }, + } { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("TEST_ARGS", tt.args) + t.Setenv("TEST_STDOUT", tt.stdout) + got, err := c.MigrateLs(context.Background(), tt.params) + require.NoError(t, err) + require.Equal(t, tt.stdout, got) + }) + } +} + +func TestMigrate_Ls_Integration(t *testing.T) { + wd, err := os.Getwd() + require.NoError(t, err) + c, err := atlasexec.NewClient(wd, "atlas") + require.NoError(t, err) + + dirURL := "file://testdata/migrations" + out, err := c.MigrateLs(context.Background(), &atlasexec.MigrateLsParams{DirURL: dirURL}) + if err != nil { + if strings.Contains(err.Error(), "unknown") || strings.Contains(err.Error(), "unknown command") { + t.Skip("atlas binary does not support 'migrate ls' (e.g. OSS build)") + } + require.NoError(t, err) + } + lines := strings.Split(strings.TrimSpace(out), "\n") + require.GreaterOrEqual(t, len(lines), 2) + require.Contains(t, out, "20230727105553_init.sql") + require.Contains(t, out, "20230727105615_t2.sql") + + outShort, err := c.MigrateLs(context.Background(), &atlasexec.MigrateLsParams{DirURL: dirURL, Short: true}) + if err != nil && strings.Contains(err.Error(), "unknown flag") { + t.Skip("atlas binary does not support --short/--latest (use enterprise or newer build)") + } + require.NoError(t, err) + require.Contains(t, outShort, "20230727105553") + require.Contains(t, outShort, "20230727105615") + require.NotContains(t, outShort, ".sql") + + outLatest, err := c.MigrateLs(context.Background(), &atlasexec.MigrateLsParams{DirURL: dirURL, Latest: true}) + require.NoError(t, err) + require.Equal(t, 1, len(strings.Split(strings.TrimSpace(outLatest), "\n"))) + require.Contains(t, outLatest, "20230926085734_destructive-change.sql") +} + func TestMigrate_Set(t *testing.T) { wd, err := os.Getwd() require.NoError(t, err) From f5d871faa34a8a3fe8cf95acbe9922477a40c1ae Mon Sep 17 00:00:00 2001 From: Chinh Nguyen Date: Thu, 26 Feb 2026 19:58:56 +0700 Subject: [PATCH 8/9] all: bump Go version to 1.24.13 (#3677) --- cmd/atlas/go.mod | 2 +- go.mod | 2 +- internal/integration/go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/atlas/go.mod b/cmd/atlas/go.mod index 035530b0d10..c33f6a74539 100644 --- a/cmd/atlas/go.mod +++ b/cmd/atlas/go.mod @@ -1,6 +1,6 @@ module ariga.io/atlas/cmd/atlas -go 1.24.11 +go 1.24.13 replace ariga.io/atlas => ../.. diff --git a/go.mod b/go.mod index 47ccfafcf1b..8333dada0f4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module ariga.io/atlas -go 1.24.11 +go 1.24.13 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 diff --git a/internal/integration/go.mod b/internal/integration/go.mod index cb4967d269f..60123373043 100644 --- a/internal/integration/go.mod +++ b/internal/integration/go.mod @@ -1,6 +1,6 @@ module ariga.io/atlas/internal/integration -go 1.24.11 +go 1.24.13 replace ariga.io/atlas => ../../ From c7a906b3ab573cd6a134ebbd32b2b15329103aba Mon Sep 17 00:00:00 2001 From: Jannik Clausen <12862103+masseelch@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:48:38 +0100 Subject: [PATCH 9/9] atlasexec: fix multi file rebase (#3681) --- atlasexec/atlas_migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlasexec/atlas_migrate.go b/atlasexec/atlas_migrate.go index 452e1ae36ff..b8243a7bc38 100644 --- a/atlasexec/atlas_migrate.go +++ b/atlasexec/atlas_migrate.go @@ -599,7 +599,7 @@ func (c *Client) MigrateRebase(ctx context.Context, params *MigrateRebaseParams) if params.DirURL != "" { args = append(args, "--dir", params.DirURL) } - args = append(args, strings.Join(params.Files, " ")) + args = append(args, params.Files...) _, err := c.runCommand(ctx, args) return err }