-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a `pgroll latest` command to show the latest version in either the target database or a local directory of migration files. ## Documentation `pgroll latest` prints the latest schema version in either the target database or a local directory of migration files. By default, `pgroll latest` prints the latest version in the target database. Use the `--local` flag to print the latest version in a local directory of migration files instead. In both cases, the `--with-schema` flag can be used to prefix the latest version with the schema name. #### Database Assuming that the [example migrations](https://github.com/xataio/pgroll/tree/main/examples) have been applied to the `public` schema in the target database, running: ``` $ pgroll latest ``` will print the latest version in the target database: ``` 45_add_table_check_constraint ``` The exact output will vary as the `examples/` directory is updated. #### Local Assuming that the [example migrations](https://github.com/xataio/pgroll/tree/main/examples) are on disk in a directory called `examples`, running: ``` $ pgroll latest --local examples/ ``` will print the latest migration in the directory: ``` 45_add_table_check_constraint ``` The exact output will vary as the `examples/` directory is updated. --- Part of #446
- Loading branch information
1 parent
6d48386
commit eaaabf9
Showing
6 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
func latestCmd() *cobra.Command { | ||
var withSchema bool | ||
var migrationsDir string | ||
|
||
latestCmd := &cobra.Command{ | ||
Use: "latest <directory>", | ||
Short: "Print the name of the latest schema version, either in the target database or a local directory", | ||
Example: "latest --local ./migrations", | ||
Args: cobra.NoArgs, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
ctx := cmd.Context() | ||
|
||
m, err := NewRoll(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
defer m.Close() | ||
|
||
var latestVersion string | ||
if migrationsDir != "" { | ||
info, err := os.Stat(migrationsDir) | ||
if err != nil { | ||
return fmt.Errorf("failed to stat directory: %w", err) | ||
} | ||
if !info.IsDir() { | ||
return fmt.Errorf("migrations directory %q is not a directory", migrationsDir) | ||
} | ||
|
||
latestVersion, err = m.LatestVersionLocal(ctx, os.DirFS(migrationsDir)) | ||
if err != nil { | ||
return fmt.Errorf("failed to get latest version from directory %q: %w", migrationsDir, err) | ||
} | ||
} else { | ||
latestVersion, err = m.LatestVersionRemote(ctx) | ||
if err != nil { | ||
return fmt.Errorf("failed to get latest version from database: %w", err) | ||
} | ||
} | ||
|
||
var prefix string | ||
if withSchema { | ||
prefix = m.Schema() + "_" | ||
} | ||
|
||
fmt.Printf("%s%s\n", prefix, latestVersion) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
latestCmd.Flags().BoolVarP(&withSchema, "with-schema", "s", false, "prefix the version with the schema name") | ||
latestCmd.Flags().StringVarP(&migrationsDir, "local", "l", "", "retrieve the latest version from a local migration directory") | ||
|
||
return latestCmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package roll | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io/fs" | ||
) | ||
|
||
var ( | ||
ErrNoMigrationFiles = fmt.Errorf("no migration files found") | ||
ErrNoMigrationApplied = fmt.Errorf("no migrations applied") | ||
) | ||
|
||
// LatestVersionLocal returns the name of the last migration in `dir`, where the | ||
// migration files are lexicographically ordered by filename. | ||
func (m *Roll) LatestVersionLocal(ctx context.Context, dir fs.FS) (string, error) { | ||
files, err := fs.Glob(dir, "*.json") | ||
if err != nil { | ||
return "", fmt.Errorf("reading directory: %w", err) | ||
} | ||
|
||
if len(files) == 0 { | ||
return "", ErrNoMigrationFiles | ||
} | ||
|
||
latest := files[len(files)-1] | ||
|
||
migration, err := openAndReadMigrationFile(dir, latest) | ||
if err != nil { | ||
return "", fmt.Errorf("reading migration file %q: %w", latest, err) | ||
} | ||
|
||
return migration.Name, nil | ||
} | ||
|
||
// LatestVersionRemote returns the name of the last migration to have been | ||
// applied to the target schema. | ||
func (m *Roll) LatestVersionRemote(ctx context.Context) (string, error) { | ||
latestVersion, err := m.State().LatestVersion(ctx, m.Schema()) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get latest version: %w", err) | ||
} | ||
|
||
if latestVersion == nil { | ||
return "", ErrNoMigrationApplied | ||
} | ||
|
||
return *latestVersion, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package roll_test | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"testing" | ||
"testing/fstest" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/xataio/pgroll/internal/testutils" | ||
"github.com/xataio/pgroll/pkg/migrations" | ||
"github.com/xataio/pgroll/pkg/roll" | ||
) | ||
|
||
func TestLatestVersionLocal(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("returns the name of the last migration in the directory", func(t *testing.T) { | ||
fs := fstest.MapFS{ | ||
"01_migration_1.json": &fstest.MapFile{Data: exampleMigration(t, "01_migration_1")}, | ||
"02_migration_2.json": &fstest.MapFile{Data: exampleMigration(t, "02_migration_2")}, | ||
"03_migration_3.json": &fstest.MapFile{Data: exampleMigration(t, "03_migration_3")}, | ||
} | ||
|
||
testutils.WithMigratorAndConnectionToContainer(t, func(roll *roll.Roll, _ *sql.DB) { | ||
ctx := context.Background() | ||
|
||
// Get the latest migration in the directory | ||
latest, err := roll.LatestVersionLocal(ctx, fs) | ||
require.NoError(t, err) | ||
|
||
// Assert last migration name | ||
assert.Equal(t, "03_migration_3", latest) | ||
}) | ||
}) | ||
|
||
t.Run("returns an error if the directory is empty", func(t *testing.T) { | ||
fs := fstest.MapFS{} | ||
|
||
testutils.WithMigratorAndConnectionToContainer(t, func(m *roll.Roll, _ *sql.DB) { | ||
ctx := context.Background() | ||
|
||
// Get the latest migration in the directory | ||
_, err := m.LatestVersionLocal(ctx, fs) | ||
|
||
// Assert expected error | ||
assert.ErrorIs(t, err, roll.ErrNoMigrationFiles) | ||
}) | ||
}) | ||
} | ||
|
||
func TestLatestVersionRemote(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("returns the name of the latest version in the target schema", func(t *testing.T) { | ||
testutils.WithMigratorAndConnectionToContainer(t, func(m *roll.Roll, _ *sql.DB) { | ||
ctx := context.Background() | ||
|
||
// Start and complete a migration | ||
err := m.Start(ctx, &migrations.Migration{ | ||
Name: "01_first_migration", | ||
Operations: migrations.Operations{ | ||
&migrations.OpRawSQL{Up: "SELECT 1"}, | ||
}, | ||
}) | ||
require.NoError(t, err) | ||
err = m.Complete(ctx) | ||
require.NoError(t, err) | ||
|
||
// Get the latest version in the target schema | ||
latestVersion, err := m.LatestVersionRemote(ctx) | ||
require.NoError(t, err) | ||
|
||
// Assert latest migration name | ||
assert.Equal(t, "01_first_migration", latestVersion) | ||
}) | ||
}) | ||
|
||
t.Run("returns an error if no migrations have been applied", func(t *testing.T) { | ||
testutils.WithMigratorAndConnectionToContainer(t, func(m *roll.Roll, _ *sql.DB) { | ||
ctx := context.Background() | ||
|
||
// Get the latest migration in the directory | ||
_, err := m.LatestVersionRemote(ctx) | ||
|
||
// Assert expected error | ||
assert.ErrorIs(t, err, roll.ErrNoMigrationApplied) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters