Skip to content

Commit

Permalink
Update to latest schemadiff from upstream, 2024-03-19, support `--t…
Browse files Browse the repository at this point in the history
…extual` (#21)

* Update to latest schemadiff from upstream, 2024-03-19

Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com>

* exitWithError: print to stderr

Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com>

* Support --textual flag, generating diff-style output

Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com>

---------

Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com>
  • Loading branch information
shlomi-noach committed Apr 1, 2024
1 parent 4be027b commit 711487b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 35 deletions.
27 changes: 27 additions & 0 deletions README.md
Expand Up @@ -246,6 +246,33 @@ ALTER VIEW `v1` AS SELECT `id`, `name` FROM `t1`;

Consider that running `schemadiff diff` on the same views above results with validation error, because the referenced table `t1` does not appear in the schema definition. `diff-view` does not attempt to resolve dependencies.

### Textual diff format output

You may add `--textual` flag to get a diff-format output rather than semantic SQL output:

```sh
echo "create table t (id int primary key, i int); create view v as select id from t" > /tmp/schema_v1.sql
echo "create table t (id bigint primary key, i int, key (i)); create table t2 (id int primary key, name varchar(128) not null default '')" > /tmp/schema_v2.sql
schemadiff diff --source /tmp/schema_v1.sql --target /tmp/schema_v2.sql --textual
```

```diff
-CREATE VIEW `v` AS SELECT `id` FROM `t`;
CREATE TABLE `t` (
- `id` int,
+ `id` bigint,
`i` int,
PRIMARY KEY (`id`)
+ KEY `i` (`i`)
);
+CREATE TABLE `t2` (
+ `id` int,
+ `name` varchar(128) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`)
+);
```

The textual diff still works semantically under the hood, and it will ignore trailing comma changes, index reordering, cosntraint name changes, etc.

## Binaries

Expand Down
5 changes: 3 additions & 2 deletions cmd/schemadiff/main.go
Expand Up @@ -11,7 +11,7 @@ import (
)

func exitWithError(err error) {
fmt.Println(err)
fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(2)
}

Expand All @@ -20,14 +20,15 @@ func main() {

source := flag.String("source", "", "Input source (file name / directory / empty for stdin)")
target := flag.String("target", "", "Input target (file name / directory / empty for stdin)")
textual := flag.Bool("textual", false, "Output textual diff rather than semantic SQL diff")
flag.Parse()

args := flag.Args()
if len(args) != 1 {
exitWithError(errors.New("one argument expected. Usage: schemadiff [flags...] <load|diff|ordered-diff|diff-table|diff-view>"))
}
command := args[0]
output, err := core.Exec(ctx, command, *source, *target)
output, err := core.Exec(ctx, command, *source, *target, *textual)
if err != nil {
exitWithError(err)
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Expand Up @@ -7,15 +7,15 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
golang.org/x/sync v0.6.0
vitess.io/vitess v0.19.0
vitess.io/vitess v0.10.3-0.20240319060307-a1acddeb5e12
)

require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
Expand Down
13 changes: 4 additions & 9 deletions go.sum
Expand Up @@ -8,10 +8,8 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
Expand Down Expand Up @@ -49,19 +47,16 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
vitess.io/vitess v0.19.0 h1:KMrB658Mv992I8YPcttrxL9sGpv/PqfuZBTLyuCMLFA=
vitess.io/vitess v0.19.0/go.mod h1:Vx3bJe7ZE6fFCjS2hH91YQNXu0EuUvVbFYlvXnbmAqo=
vitess.io/vitess v0.10.3-0.20240319060307-a1acddeb5e12 h1:a3DmN0BgeFcjsFHfaUSh8Eyk4ol1K1MlXdfmPBi/NEU=
vitess.io/vitess v0.10.3-0.20240319060307-a1acddeb5e12/go.mod h1:0pNTpZxA0KrDRoJc93GM4p97pjcMrpcxv1hW9XmFu/Q=
23 changes: 14 additions & 9 deletions pkg/core/exec.go
Expand Up @@ -22,7 +22,7 @@ const mysqlVersion = "8.0.35"

// Exec is the main execution entry for this app, called by the main() function.
// Teh function returns a textual output, which is later send to standard output.
func Exec(ctx context.Context, command string, source string, target string) (output string, err error) {
func Exec(ctx context.Context, command string, source string, target string, textual bool) (output string, err error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

Expand All @@ -35,6 +35,15 @@ func Exec(ctx context.Context, command string, source string, target string) (ou
}
env := schemadiff.NewEnv(vtenv, collEnv.DefaultConnectionCharset())
var bld strings.Builder
writeDiff := func(d schemadiff.EntityDiff) {
if textual {
_, _, unified := d.Annotated()
bld.WriteString(unified.Export())
} else {
bld.WriteString(d.CanonicalStatementString())
}
bld.WriteString(";\n")
}
getDiffs := func(ordered bool) (err error) {
if source == target {
return ErrIdenticalSourceTarget
Expand All @@ -54,8 +63,7 @@ func Exec(ctx context.Context, command string, source string, target string) (ou
diffs = diff.UnorderedDiffs()
}
for _, d := range diffs {
bld.WriteString(d.CanonicalStatementString())
bld.WriteString(";\n")
writeDiff(d)
}
return nil
}
Expand All @@ -66,8 +74,7 @@ func Exec(ctx context.Context, command string, source string, target string) (ou
return "", err
}
for _, e := range schema.Entities() {
bld.WriteString(e.Create().CanonicalStatementString())
bld.WriteString(";\n")
writeDiff(e.Create())
}
case "diff":
if err := getDiffs(false); err != nil {
Expand All @@ -86,8 +93,7 @@ func Exec(ctx context.Context, command string, source string, target string) (ou
return "", err
}
if !diff.IsEmpty() {
bld.WriteString(diff.CanonicalStatementString())
bld.WriteString(";\n")
writeDiff(diff)
}
case "diff-view":
if source == target {
Expand All @@ -98,8 +104,7 @@ func Exec(ctx context.Context, command string, source string, target string) (ou
return "", err
}
if !diff.IsEmpty() {
bld.WriteString(diff.CanonicalStatementString())
bld.WriteString(";\n")
writeDiff(diff)
}
case "apply":
default:
Expand Down
57 changes: 44 additions & 13 deletions pkg/core/exec_test.go
Expand Up @@ -101,16 +101,33 @@ func TestExecLoad(t *testing.T) {
fileFrom := writeSchemaFile(t, schemaFrom)
require.NotEmpty(t, fileFrom)
defer os.RemoveAll(fileFrom)
schema, err := Exec(ctx, "load", fileFrom, "")
schema, err := Exec(ctx, "load", fileFrom, "", false)
assert.NoError(t, err)
assert.Equal(t, sqlsToMultiStatementText(loadFrom), schema)
})
t.Run("from-file-textual", func(t *testing.T) {
fileFrom := writeSchemaFile(t, schemaFrom)
require.NotEmpty(t, fileFrom)
defer os.RemoveAll(fileFrom)
schema, err := Exec(ctx, "load", fileFrom, "", true)
assert.NoError(t, err)
expect := []string{}
for _, sql := range loadFrom {
lines := strings.Split(sql, "\n")
for i := range lines {
lines[i] = "+" + lines[i]
}
sql = strings.Join(lines, "\n")
expect = append(expect, sql)
}
assert.Equal(t, sqlsToMultiStatementText(expect), schema)
})

t.Run("from-dir", func(t *testing.T) {
dirFrom := writeSchemaDir(t, schemaFrom)
require.NotEmpty(t, dirFrom)
defer os.RemoveAll(dirFrom)
schema, err := Exec(ctx, "load", dirFrom, "")
schema, err := Exec(ctx, "load", dirFrom, "", false)
assert.NoError(t, err)
assert.Equal(t, sqlsToMultiStatementText(loadFrom), schema)
})
Expand All @@ -119,7 +136,7 @@ func TestExecLoad(t *testing.T) {
fileTo := writeSchemaFile(t, schemaTo)
require.NotEmpty(t, fileTo)
defer os.RemoveAll(fileTo)
schema, err := Exec(ctx, "load", fileTo, "")
schema, err := Exec(ctx, "load", fileTo, "", false)
assert.NoError(t, err)
assert.Equal(t, sqlsToMultiStatementText(loadTo), schema)
})
Expand All @@ -128,7 +145,7 @@ func TestExecLoad(t *testing.T) {
dirTo := writeSchemaDir(t, schemaTo)
require.NotEmpty(t, dirTo)
defer os.RemoveAll(dirTo)
schema, err := Exec(ctx, "load", dirTo, "")
schema, err := Exec(ctx, "load", dirTo, "", false)
assert.NoError(t, err)
assert.Equal(t, sqlsToMultiStatementText(loadTo), schema)
})
Expand All @@ -137,7 +154,7 @@ func TestExecLoad(t *testing.T) {
require.NotEmpty(t, emptyFile) // testing that the *name* is not empty...
defer os.RemoveAll(emptyFile)

schema, err := Exec(ctx, "load", emptyFile, "")
schema, err := Exec(ctx, "load", emptyFile, "", false)
assert.NoError(t, err)
assert.Equal(t, "", schema)
})
Expand Down Expand Up @@ -169,6 +186,7 @@ func TestExecDiff(t *testing.T) {
name string
source string
target string
textual bool
expectDiff []string
expectError string
}{
Expand Down Expand Up @@ -262,7 +280,7 @@ func TestExecDiff(t *testing.T) {
t.Run(cmd, func(t *testing.T) {
for _, tcase := range tcases {
t.Run(tcase.name, func(t *testing.T) {
diff, err := Exec(ctx, cmd, tcase.source, tcase.target)
diff, err := Exec(ctx, cmd, tcase.source, tcase.target, tcase.textual)
if tcase.expectError == "" {
assert.NoError(t, err)
switch cmd {
Expand Down Expand Up @@ -314,7 +332,7 @@ func TestExecDiffTable(t *testing.T) {
require.NotEmpty(t, to)
defer os.RemoveAll(to)

diff, err := Exec(ctx, "diff-table", from, to)
diff, err := Exec(ctx, "diff-table", from, to, false)
assert.NoError(t, err)
assert.Equal(t, "ALTER TABLE `t1` MODIFY COLUMN `id` int unsigned;\n", diff)
})
Expand All @@ -327,10 +345,23 @@ func TestExecDiffTable(t *testing.T) {
require.NotEmpty(t, to)
defer os.RemoveAll(to)

diff, err := Exec(ctx, "diff-table", from, to)
diff, err := Exec(ctx, "diff-table", from, to, false)
assert.NoError(t, err)
assert.Equal(t, "ALTER TABLE `t1` ADD COLUMN `age` int unsigned;\n", diff)
})
t.Run("t1-t3-textual", func(t *testing.T) {
from := writeSchemaFile(t, schemaFrom[0:1])
require.NotEmpty(t, from)
defer os.RemoveAll(from)

to := writeSchemaFile(t, schemaTo[3:])
require.NotEmpty(t, to)
defer os.RemoveAll(to)

diff, err := Exec(ctx, "diff-table", from, to, true)
assert.NoError(t, err)
assert.Equal(t, " CREATE TABLE `t1` (\n \t`id` int,\n+\t`age` int unsigned,\n \tPRIMARY KEY (`id`)\n );\n", diff)
})
t.Run("t1-vone", func(t *testing.T) {
from := writeSchemaFile(t, schemaFrom[0:1])
require.NotEmpty(t, from)
Expand All @@ -340,7 +371,7 @@ func TestExecDiffTable(t *testing.T) {
require.NotEmpty(t, to)
defer os.RemoveAll(to)

_, err := Exec(ctx, "diff-table", from, to)
_, err := Exec(ctx, "diff-table", from, to, false)
assert.Error(t, err)
assert.ErrorIs(t, err, schemadiff.ErrExpectedCreateTable)
})
Expand All @@ -353,7 +384,7 @@ func TestExecDiffTable(t *testing.T) {
require.NotEmpty(t, to)
defer os.RemoveAll(to)

diff, err := Exec(ctx, "diff-view", from, to)
diff, err := Exec(ctx, "diff-view", from, to, false)
assert.NoError(t, err)
assert.Empty(t, diff)
})
Expand All @@ -366,7 +397,7 @@ func TestExecDiffTable(t *testing.T) {
require.NotEmpty(t, to)
defer os.RemoveAll(to)

diff, err := Exec(ctx, "diff-view", from, to)
diff, err := Exec(ctx, "diff-view", from, to, false)
assert.NoError(t, err)
assert.Equal(t, "ALTER VIEW `v1` AS SELECT `id`, 1 FROM `t1`;\n", diff)
})
Expand All @@ -380,12 +411,12 @@ func TestExecDiffTable(t *testing.T) {
defer os.RemoveAll(to)

{
_, err := Exec(ctx, "diff-table", from, to)
_, err := Exec(ctx, "diff-table", from, to, false)
assert.Error(t, err)
assert.ErrorContains(t, err, "expected one CREATE TABLE statement")
}
{
_, err := Exec(ctx, "diff-table", to, from)
_, err := Exec(ctx, "diff-table", to, from, false)
assert.Error(t, err)
assert.ErrorContains(t, err, "expected one CREATE TABLE statement")
}
Expand Down

0 comments on commit 711487b

Please sign in to comment.