Repository: ariga/atlas
Pull request #1069 (2022-08-19 08:10:30)

sql/migrate: introduce Dir.Checksum() and deprecate HashSum()


Patch:
diff --git a/cmd/atlas/internal/cmdapi/migrate.go b/cmd/atlas/internal/cmdapi/migrate.go
index d927e346..d347f22c 100644
--- a/cmd/atlas/internal/cmdapi/migrate.go
+++ b/cmd/atlas/internal/cmdapi/migrate.go
@@ -471,7 +471,7 @@ func CmdMigrateHashRun(*cobra.Command, []string) error {
 	if err != nil {
 		return err
 	}
-	sum, err := migrate.HashSum(dir)
+	sum, err := dir.Checksum()
 	if err != nil {
 		return err
 	}
diff --git a/sql/migrate/dir.go b/sql/migrate/dir.go
index 9eba56e5..c611d693 100644
--- a/sql/migrate/dir.go
+++ b/sql/migrate/dir.go
@@ -31,6 +31,9 @@ type (
 
 		// Files returns a set of files stored in this Dir to be executed on a database.
 		Files() ([]File, error)
+
+		// Checksum returns a HashFile of the migration directory.
+		Checksum() (HashFile, error)
 	}
 
 	// Formatter wraps the Format method.
@@ -110,6 +113,32 @@ func (d *LocalDir) Files() ([]File, error) {
 	return ret, nil
 }
 
+// Checksum implements Dir.Checksum. By default, it calls Files() and creates a checksum from them.
+func (d *LocalDir) Checksum() (HashFile, error) {
+	var (
+		hs HashFile
+		h  = sha256.New()
+	)
+	files, err := d.Files()
+	if err != nil {
+		return nil, err
+	}
+	for _, f := range files {
+		if _, err = h.Write([]byte(f.Name())); err != nil {
+			return nil, err
+		}
+		// Check if this file contains an "atlas:sum" directive and if so, act to it.
+		if mode, ok := directive(string(f.Bytes()), directiveSum); ok && mode == sumModeIgnore {
+			continue
+		}
+		if _, err = h.Write(f.Bytes()); err != nil {
+			return nil, err
+		}
+		hs = append(hs, struct{ N, H string }{f.Name(), base64.StdEncoding.EncodeToString(h.Sum(nil))})
+	}
+	return hs, nil
+}
+
 // LocalFile is used by LocalDir to implement the Scanner interface.
 type LocalFile struct {
 	n string
@@ -218,30 +247,10 @@ const HashFileName = "atlas.sum"
 type HashFile []struct{ N, H string }
 
 // HashSum reads the whole dir, sorts the files by name and creates a HashSum from its contents.
-// If a files first line matches the above regex, it will be excluded from hash creation.
+//
+// Deprecated: Use Dir.Checksum instead.
 func HashSum(dir Dir) (HashFile, error) {
-	var (
-		hs HashFile
-		h  = sha256.New()
-	)
-	files, err := dir.Files()
-	if err != nil {
-		return nil, err
-	}
-	for _, f := range files {
-		if _, err = h.Write([]byte(f.Name())); err != nil {
-			return nil, err
-		}
-		// Check if this file contains an "atlas:sum" directive and if so, act to it.
-		if mode, ok := directive(string(f.Bytes()), directiveSum); ok && mode == sumModeIgnore {
-			continue
-		}
-		if _, err = h.Write(f.Bytes()); err != nil {
-			return nil, err
-		}
-		hs = append(hs, struct{ N, H string }{f.Name(), base64.StdEncoding.EncodeToString(h.Sum(nil))})
-	}
-	return hs, nil
+	return dir.Checksum()
 }
 
 // WriteSumFile writes the given HashFile to the Dir. If the file does not exist, it is created.
@@ -324,7 +333,7 @@ func Validate(dir Dir) error {
 	if err != nil {
 		return err
 	}
-	mh, err := HashSum(dir)
+	mh, err := dir.Checksum()
 	if err != nil {
 		return err
 	}
diff --git a/sql/migrate/migrate.go b/sql/migrate/migrate.go
index 8b855706..c4865998 100644
--- a/sql/migrate/migrate.go
+++ b/sql/migrate/migrate.go
@@ -375,7 +375,7 @@ func (p *Planner) WritePlan(plan *Plan) error {
 	}
 	// If enabled, update the sum file.
 	if p.sum {
-		sum, err := HashSum(p.dir)
+		sum, err := p.dir.Checksum()
 		if err != nil {
 			return err
 		}
@@ -547,7 +547,7 @@ func (e *Executor) Pending(ctx context.Context) ([]File, error) {
 // Execute executes the given migration file on the database. If it sees a file, that has been partially applied, it
 // will continue with the next statement in line.
 func (e *Executor) Execute(ctx context.Context, m File) (err error) {
-	hf, err := HashSum(e.dir)
+	hf, err := e.dir.Checksum()
 	if err != nil {
 		return fmt.Errorf("sql/migrate: execute: compute hash: %w", err)
 	}


Test patch:
diff --git a/cmd/atlas/internal/cmdapi/migrate_test.go b/cmd/atlas/internal/cmdapi/migrate_test.go
index 09d3e382..b669ca31 100644
--- a/cmd/atlas/internal/cmdapi/migrate_test.go
+++ b/cmd/atlas/internal/cmdapi/migrate_test.go
@@ -452,7 +452,7 @@ func TestMigrate_Hash(t *testing.T) {
 	require.NoError(t, err)
 	dir, err := migrate.NewLocalDir(p)
 	require.NoError(t, err)
-	sum, err := migrate.HashSum(dir)
+	sum, err := dir.Checksum()
 	require.NoError(t, err)
 	b, err := sum.MarshalText()
 	require.NoError(t, err)
diff --git a/sql/migrate/dir_test.go b/sql/migrate/dir_test.go
index c9975415..861907fa 100644
--- a/sql/migrate/dir_test.go
+++ b/sql/migrate/dir_test.go
@@ -130,7 +130,7 @@ func TestValidate(t *testing.T) {
 func TestHash_MarshalText(t *testing.T) {
 	d, err := migrate.NewLocalDir("testdata/migrate")
 	require.NoError(t, err)
-	h, err := migrate.HashSum(d)
+	h, err := d.Checksum()
 	require.NoError(t, err)
 	ac, err := h.MarshalText()
 	require.Equal(t, hash, ac)
@@ -139,7 +139,7 @@ func TestHash_MarshalText(t *testing.T) {
 func TestHash_UnmarshalText(t *testing.T) {
 	d, err := migrate.NewLocalDir("testdata/migrate")
 	require.NoError(t, err)
-	h, err := migrate.HashSum(d)
+	h, err := d.Checksum()
 	require.NoError(t, err)
 	var ac migrate.HashFile
 	require.NoError(t, ac.UnmarshalText(hash))
diff --git a/sql/sqltool/testdata/dbmate/atlas.sum b/sql/sqltool/testdata/dbmate/atlas.sum
deleted file mode 100644
index 312a249d..00000000
--- a/sql/sqltool/testdata/dbmate/atlas.sum
+++ /dev/null
@@ -1,3 +0,0 @@
-h1:bpWcrOjC8i9SkrTqTKi5WiZxs14d9kzj1ZtgRn2pe8k=
-1_initial.sql h1:Ifwt1OSc53epUlXURSjMhu1FH2kqRTu9kGtOYA7E0zg=
-2_second_migration.sql h1:0G2qai762kh4Ug7NyouaCICtrH5uZxn/8xTNBvsRhTw=
diff --git a/sql/sqltool/testdata/flyway/atlas.sum b/sql/sqltool/testdata/flyway/atlas.sum
deleted file mode 100644
index ff4a6285..00000000
--- a/sql/sqltool/testdata/flyway/atlas.sum
+++ /dev/null
@@ -1,7 +0,0 @@
-h1:cqB3h2YgDucOvuJK1m+065STk4tE5r8TChc/fKR9Lsw=
-B2__baseline.sql h1:15n4zZKX1qkIf4v58ac7FvUZiZj+cR4TBjUMZI1E2K4=
-R__views.sql h1:WeCOf0LhETAdcFpd/Xejo8oiReGZ1uA3jPBGubWmiXk=
-U1__initial.sql h1:b64Db/YPV8vkNdPklYgbP7cjsrbhjObvKfCtMI4Mfd0=
-V1__initial.sql h1:TFqDsXIhPH1xnfv/nbSG9laQzWkidSTw6DnwFjfpei0=
-V2__second_migration.sql h1:6tXT74XoxjYfLbwgoSHkeWyDJIp93FfhjN3vRpPqEZI=
-V3__third_migration.sql h1:dCrdx+mvqPfuQWXLmPc7DP53n35XfdGvx2AbVZEf+Sc=
diff --git a/sql/sqltool/testdata/goose/atlas.sum b/sql/sqltool/testdata/goose/atlas.sum
deleted file mode 100644
index 3d2ec76e..00000000
--- a/sql/sqltool/testdata/goose/atlas.sum
+++ /dev/null
@@ -1,3 +0,0 @@
-h1:MFwmYzrSq09/I/jZgUuUoiRTBCtz+0UWOTotkcBdkIE=
-1_initial.sql h1:mOgAYIay1aUHP9BgbtNlwjSPsiw0yXJ5HIW+kOXyt1s=
-2_second_migration.sql h1:kOL5bbnrzs3M/MDRmLLvuwrO95p/pocjHAP9hdrR0e8=
diff --git a/sql/sqltool/testdata/liquibase/atlas.sum b/sql/sqltool/testdata/liquibase/atlas.sum
deleted file mode 100644
index 3e8d88d8..00000000
--- a/sql/sqltool/testdata/liquibase/atlas.sum
+++ /dev/null
@@ -1,3 +0,0 @@
-h1:6I2+EO1XlJwbhoffBnOS8+i/IP3jlrGgI2DIJvRpcPs=
-1_initial.sql h1:WhCKKuYeV1R6mHBSeM+MNwdlyKFPfSD8I55+pxFBiOA=
-2_second_migration.sql h1:dyEY5EIahGAWgcDS3k5CcD7JGKSyaXL6GeG4AvyZObY=
diff --git a/sql/sqltool/tool_test.go b/sql/sqltool/tool_test.go
index 4ca377ea..71c1be18 100644
--- a/sql/sqltool/tool_test.go
+++ b/sql/sqltool/tool_test.go
@@ -242,6 +242,90 @@ func TestScanners(t *testing.T) {
 	}
 }
 
+func TestChecksum(t *testing.T) {
+	for _, tt := range []struct {
+		name  string
+		dir   migrate.Dir
+		files []string // files expected to be part of the checksum (in order)
+	}{
+		{
+			name: "golang-migrate",
+			dir: func() migrate.Dir {
+				d, err := sqltool.NewGolangMigrateDir("testdata/golang-migrate")
+				require.NoError(t, err)
+				return d
+			}(),
+			files: []string{
+				"1_initial.down.sql",
+				"1_initial.up.sql",
+				"2_second_migration.down.sql",
+				"2_second_migration.up.sql",
+			},
+		},
+		{
+			name: "goose",
+			dir: func() migrate.Dir {
+				d, err := sqltool.NewGooseDir("testdata/goose")
+				require.NoError(t, err)
+				return d
+			}(),
+			files: []string{
+				"1_initial.sql",
+				"2_second_migration.sql",
+			},
+		},
+		{
+			name: "flyway",
+			dir: func() migrate.Dir {
+				d, err := sqltool.NewFlywayDir("testdata/flyway")
+				require.NoError(t, err)
+				return d
+			}(),
+			files: []string{
+				"B2__baseline.sql",
+				"R__views.sql",
+				"U1__initial.sql",
+				"V1__initial.sql",
+				"V2__second_migration.sql",
+				"V3__third_migration.sql",
+			},
+		},
+		{
+			name: "liquibase",
+			dir: func() migrate.Dir {
+				d, err := sqltool.NewLiquibaseDir("testdata/liquibase")
+				require.NoError(t, err)
+				return d
+			}(),
+			files: []string{
+				"1_initial.sql",
+				"2_second_migration.sql",
+			},
+		},
+		{
+			name: "dbmate",
+			dir: func() migrate.Dir {
+				d, err := sqltool.NewDBMateDir("testdata/dbmate")
+				require.NoError(t, err)
+				return d
+			}(),
+			files: []string{
+				"1_initial.sql",
+				"2_second_migration.sql",
+			},
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			sum, err := tt.dir.Checksum()
+			require.NoError(t, err)
+			require.Len(t, sum, len(tt.files))
+			for i := range tt.files {
+				require.Equal(t, tt.files[i], sum[i].N)
+			}
+		})
+	}
+}
+
 func dir(t *testing.T) migrate.Dir {
 	p := t.TempDir()
 	d, err := migrate.NewLocalDir(p)