~samwhited/migration

4ae757ad3c892d6cb9e4c222e1f9847d9296cd27 — Sam Whited 3 months ago 7a307fa v0.0.4
Use Go 1.16 io/fs and remove deprecated io/ioutil
4 files changed, 27 insertions(+), 28 deletions(-)

M go.mod
M go.sum
M migration.go
M migration_test.go
M go.mod => go.mod +2 -5
@@ 1,8 1,5 @@
module code.soquee.net/migration

go 1.11
go 1.16

require (
	github.com/lib/pq v1.1.1
	golang.org/x/tools v0.0.0-20190525145741-7be61e1b0e51
)
require github.com/lib/pq v1.9.0

M go.sum => go.sum +2 -9
@@ 1,9 1,2 @@
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190525145741-7be61e1b0e51 h1:RhYYBLDB5MoVkvoNGMNk+DSj7WoGhySvIvtEjTyiP74=
golang.org/x/tools v0.0.0-20190525145741-7be61e1b0e51/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=

M migration.go => migration.go +7 -7
@@ 7,6 7,7 @@ import (
	"database/sql"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path"
	"sort"


@@ 14,7 15,6 @@ import (
	"time"

	"github.com/lib/pq"
	"golang.org/x/tools/godoc/vfs"
)

// Generator returns a function that creates migration files at the given base


@@ 102,7 102,7 @@ func getRunMigrations(ctx context.Context, tx *sql.Tx, migrationsTable string) (

// LastRun returns the last migration directory by lexical order that exists in
// the database and on disk.
func LastRun(ctx context.Context, migrationsTable string, fs vfs.FileSystem, tx *sql.Tx) (ident, name string, err error) {
func LastRun(ctx context.Context, migrationsTable string, vfs fs.FS, tx *sql.Tx) (ident, name string, err error) {
	var version string
	if tx != nil {
		err = tx.QueryRowContext(ctx,


@@ 118,7 118,7 @@ func LastRun(ctx context.Context, migrationsTable string, fs vfs.FileSystem, tx 
	if err != nil {
		return version, fpath, err
	}
	err = walker(fs, func(name string, info os.FileInfo, status RunStatus) error {
	err = walker(vfs, func(name string, info fs.DirEntry, status RunStatus) error {
		if tx != nil && name != version {
			return nil
		}


@@ 137,11 137,11 @@ func LastRun(ctx context.Context, migrationsTable string, fs vfs.FileSystem, tx 

// WalkFunc is the type of the function called for each file or directory
// visited by a Walker.
type WalkFunc func(name string, info os.FileInfo, status RunStatus) error
type WalkFunc func(name string, info fs.DirEntry, status RunStatus) error

// Walker is a function that can be used to walk a filesystem and calls WalkFunc
// for each migration.
type Walker func(fs vfs.FileSystem, f WalkFunc) error
type Walker func(vfs fs.FS, f WalkFunc) error

// NewWalker queries the database for migration status information and returns a
// function that walks the migrations it finds on the filesystem in lexical


@@ 168,8 168,8 @@ func NewWalker(ctx context.Context, migrationsTable string, tx *sql.Tx) (Walker,
		}
	}

	return func(fs vfs.FileSystem, f WalkFunc) error {
		files, err := fs.ReadDir("/")
	return func(vfs fs.FS, f WalkFunc) error {
		files, err := fs.ReadDir(vfs, ".")
		if err != nil {
			return err
		}

M migration_test.go => migration_test.go +16 -7
@@ 2,18 2,27 @@ package migration_test

import (
	"context"
	"io/ioutil"
	"io/fs"
	"os"
	"path"
	"strconv"
	"strings"
	"testing"
	"testing/fstest"

	"code.soquee.net/migration"
	"golang.org/x/tools/godoc/vfs"
	"golang.org/x/tools/godoc/vfs/mapfs"
)

func newMapFS(m map[string]string) fstest.MapFS {
	mfs := make(fstest.MapFS)
	for k, v := range m {
		mfs[k] = &fstest.MapFile{
			Data: []byte(v),
		}
	}
	return mfs
}

const migrationsTable = "__migrations"

var genTestCases = [...]struct {


@@ 37,8 46,8 @@ func TestLastRun(t *testing.T) {
	for i, tc := range genTestCases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			migrationDir := "001_" + tc.dir
			fs := mapfs.New(map[string]string{
				path.Join("/", migrationDir, "up.sql"): "-- up.sql",
			fs := newMapFS(map[string]string{
				path.Join(migrationDir, "up.sql"): "-- up.sql",
			})
			ident, name, err := migration.LastRun(context.Background(), migrationsTable, fs, nil)
			if ident != "" {


@@ 57,7 66,7 @@ func TestLastRun(t *testing.T) {
func TestGenerate(t *testing.T) {
	for i, tc := range genTestCases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			dir, err := ioutil.TempDir("", "migration_test")
			dir, err := os.MkdirTemp("", "migration_test")
			if err != nil {
				t.Fatalf("Error creating temp directory for tests: %q", err)
			}


@@ 78,7 87,7 @@ func TestGenerate(t *testing.T) {
			if err != nil {
				t.Fatalf("error creating walker: %q", err)
			}
			err = walker(vfs.OS(dir), func(name string, info os.FileInfo, status migration.RunStatus) error {
			err = walker(os.DirFS(dir), func(name string, info fs.DirEntry, status migration.RunStatus) error {
				dirName := info.Name()
				if dirName == path.Base(dir) {
					t.Fatalf("Walk included top level directory but should only hit migrations")