Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for --statement #375

Merged
merged 7 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ require (
github.com/siddontang/loggers v1.0.3
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/sync v0.4.0
)

Expand All @@ -34,6 +33,7 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-mysql-org/go-mysql v1.8.1-0.20240805131754-ccf204bf2b2a h1:VO6kiE9ex1uNaCCgDz/q0EhTueLrr3vmxkjJpU2x6pk=
github.com/go-mysql-org/go-mysql v1.8.1-0.20240805131754-ccf204bf2b2a/go.mod h1:+SgFgTlqjqOQoMc98n9oyUWEgn2KkOL1VmXDoq2ONOs=
github.com/go-mysql-org/go-mysql v1.9.1 h1:W2ZKkHkoM4mmkasJCoSYfaE4RQNxXTb6VqiaMpKFrJc=
github.com/go-mysql-org/go-mysql v1.9.1/go.mod h1:+SgFgTlqjqOQoMc98n9oyUWEgn2KkOL1VmXDoq2ONOs=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
Expand Down
3 changes: 2 additions & 1 deletion pkg/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type Resources struct {
DB *sql.DB
Replica *sql.DB
Table *table.TableInfo
Alter string
Alter string // as ALTER
Statement string // as full SQL statement
TargetChunkTime time.Duration
Threads int
ReplicaMaxLag time.Duration
Expand Down
5 changes: 2 additions & 3 deletions pkg/check/dropadd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ func init() {
// - We only allow a column name to be mentioned once across all
// DROP and ADD parts of the alter statement.
func dropAddCheck(ctx context.Context, r Resources, logger loggers.Advanced) error {
sql := fmt.Sprintf("ALTER TABLE %s %s", r.Table.TableName, r.Alter)
p := parser.New()
stmtNodes, _, err := p.Parse(sql, "", "")
stmtNodes, _, err := p.Parse(r.Statement, "", "")
if err != nil {
return fmt.Errorf("could not parse alter table statement: %s", sql)
return fmt.Errorf("could not parse alter table statement: %s", r.Statement)
}
stmt := &stmtNodes[0]
alterStmt, ok := (*stmt).(*ast.AlterTableStmt)
Expand Down
8 changes: 3 additions & 5 deletions pkg/check/dropadd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,23 @@ import (
"context"
"testing"

"github.com/cashapp/spirit/pkg/table"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestDropAdd(t *testing.T) {
r := Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "DROP b, ADD b INT",
Statement: "ALTER TABLE t.t1 DROP b, ADD b INT",
}
err := dropAddCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "column b is mentioned 2 times in the same statement")

r.Alter = "DROP b1, ADD b2 INT"
r.Statement = "ALTER TABLE t.t1 DROP b1, ADD b2 INT"
err = dropAddCheck(context.Background(), r, logrus.New())
assert.NoError(t, err)

r.Alter = "bogus"
r.Statement = "bogus"
err = dropAddCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
}
5 changes: 2 additions & 3 deletions pkg/check/foreignkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ func hasForeignKeysCheck(ctx context.Context, r Resources, logger loggers.Advanc
}

func addForeignKeyCheck(ctx context.Context, r Resources, logger loggers.Advanced) error {
sql := fmt.Sprintf("ALTER TABLE %s %s", r.Table.TableName, r.Alter)
p := parser.New()
stmtNodes, _, err := p.Parse(sql, "", "")
stmtNodes, _, err := p.Parse(r.Statement, "", "")
if err != nil {
return fmt.Errorf("could not parse alter table statement: %s", sql)
return fmt.Errorf("could not parse alter table statement: %s", r.Statement)
}
stmt := &stmtNodes[0]
alterStmt, ok := (*stmt).(*ast.AlterTableStmt)
Expand Down
7 changes: 3 additions & 4 deletions pkg/check/foreignkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,17 @@ import (

func TestAddForeignKey(t *testing.T) {
r := Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "ADD FOREIGN KEY (customer_id) REFERENCES customers (id)",
Statement: "ALTER TABLE t1 ADD FOREIGN KEY (customer_id) REFERENCES customers (id)",
}
err := addForeignKeyCheck(context.Background(), r, logrus.New())
assert.Error(t, err) // add foreign key
assert.ErrorContains(t, err, "adding foreign key constraints is not supported")

r.Alter = "DROP COLUMN foo"
r.Statement = "ALTER TABLE t1 DROP COLUMN foo"
err = addForeignKeyCheck(context.Background(), r, logrus.New())
assert.NoError(t, err) // regular DDL

r.Alter = "bogus"
r.Statement = "bogus"
err = addForeignKeyCheck(context.Background(), r, logrus.New())
assert.Error(t, err) // not a valid ddl
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/check/illegalclause.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import (
)

func init() {
registerCheck("illegalClause", illegalClauseCheck, ScopePreRun)
registerCheck("illegalClause", illegalClauseCheck, ScopePreflight)
}

// illegalClauseCheck checks for the presence of specific, unsupported
// clauses in the ALTER statement, such as ALGORITHM= and LOCK=.
func illegalClauseCheck(ctx context.Context, r Resources, logger loggers.Advanced) error {
sql := "ALTER TABLE x.x " + r.Alter
return utils.AlterContainsUnsupportedClause(sql)
return utils.AlterContainsUnsupportedClause(r.Statement)
}
27 changes: 8 additions & 19 deletions pkg/check/illegalclause_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,30 @@ import (
"context"
"testing"

"github.com/cashapp/spirit/pkg/table"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestIllegalClauseCheck(t *testing.T) {
r := Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "ALGORITHM=INPLACE",
Statement: "ALTER TABLE t1 ADD INDEX (b), ALGORITHM=INPLACE",
}
err := illegalClauseCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "ALTER contains unsupported clause")
assert.ErrorContains(t, err, "contains unsupported clause")

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "ALGORITHM=INPLACE, LOCK=shared",
}
r.Statement = "ALTER TABLE t1 ADD c INT, ALGORITHM=INPLACE, LOCK=shared"
err = illegalClauseCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "ALTER contains unsupported clause")
assert.ErrorContains(t, err, "contains unsupported clause")

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "lock=none",
}
r.Statement = "ALTER TABLE t1 ADD c INT, lock=none"
err = illegalClauseCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "ALTER contains unsupported clause")
assert.ErrorContains(t, err, "contains unsupported clause")

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "engine=innodb, algorithm=copy",
}
r.Statement = "ALTER TABLE t1 engine=innodb, algorithm=copy"
err = illegalClauseCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "ALTER contains unsupported clause")
assert.ErrorContains(t, err, "contains unsupported clause")
}
5 changes: 2 additions & 3 deletions pkg/check/primarykey.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ func init() {
}

func primaryKeyCheck(ctx context.Context, r Resources, logger loggers.Advanced) error {
sql := fmt.Sprintf("ALTER TABLE %s %s", r.Table.TableName, r.Alter)
p := parser.New()
stmtNodes, _, err := p.Parse(sql, "", "")
stmtNodes, _, err := p.Parse(r.Statement, "", "")
if err != nil {
return fmt.Errorf("could not parse alter table statement: %s", sql)
return fmt.Errorf("could not parse alter table statement: %s", r.Statement)
}
stmt := &stmtNodes[0]
alterStmt, ok := (*stmt).(*ast.AlterTableStmt)
Expand Down
10 changes: 3 additions & 7 deletions pkg/check/primarykey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,25 @@ import (
"context"
"testing"

"github.com/cashapp/spirit/pkg/table"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestPrimaryKey(t *testing.T) {
r := Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "DROP PRIMARY KEY, ADD PRIMARY KEY (anothercol)",
Statement: "ALTER TABLE t.t1 DROP PRIMARY KEY, ADD PRIMARY KEY (anothercol)",
}
err := primaryKeyCheck(context.Background(), r, logrus.New())
assert.Error(t, err) // drop primary key

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "ADD INDEX (anothercol)",
Statement: "ALTER TABLE t.t1 ADD INDEX (anothercol)",
}
err = primaryKeyCheck(context.Background(), r, logrus.New())
assert.NoError(t, err) // safe modification

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "gibberish",
Statement: "gibberish",
}
err = primaryKeyCheck(context.Background(), r, logrus.New())
assert.Error(t, err) // gibberish
Expand Down
5 changes: 2 additions & 3 deletions pkg/check/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ func init() {

// renameCheck checks for any renames, which are not supported.
func renameCheck(ctx context.Context, r Resources, logger loggers.Advanced) error {
sql := fmt.Sprintf("ALTER TABLE %s %s", r.Table.TableName, r.Alter)
p := parser.New()
stmtNodes, _, err := p.Parse(sql, "", "")
stmtNodes, _, err := p.Parse(r.Statement, "", "")
if err != nil {
return fmt.Errorf("could not parse alter table statement: %s", sql)
return fmt.Errorf("could not parse alter table statement: %s", r.Statement)
}
stmt := &stmtNodes[0]
alterStmt, ok := (*stmt).(*ast.AlterTableStmt)
Expand Down
19 changes: 6 additions & 13 deletions pkg/check/rename_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,47 @@ import (
"context"
"testing"

"github.com/cashapp/spirit/pkg/table"
_ "github.com/pingcap/tidb/pkg/parser/test_driver"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestRename(t *testing.T) {
r := Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "RENAME TO newtablename",
Statement: "ALTER TABLE t.t1 RENAME TO newtablename",
}
err := renameCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "renames are not supported")

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "RENAME COLUMN c1 TO c2",
Statement: "ALTER TABLE t.t1 RENAME COLUMN c1 TO c2",
}
err = renameCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "renames are not supported")

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "CHANGE c1 c2 VARCHAR(100)",
Statement: "ALTER TABLE t.t1 CHANGE c1 c2 VARCHAR(100)",
}
err = renameCheck(context.Background(), r, logrus.New())
assert.Error(t, err)
assert.ErrorContains(t, err, "renames are not supported")

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "CHANGE c1 c1 VARCHAR(100)", //nolint: dupword
Statement: "ALTER TABLE t.t1 CHANGE c1 c1 VARCHAR(100)", //nolint: dupword
}
err = renameCheck(context.Background(), r, logrus.New())
assert.NoError(t, err)

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "ADD INDEX (anothercol)",
Statement: "ALTER TABLE t.t1 ADD INDEX (anothercol)",
}
err = renameCheck(context.Background(), r, logrus.New())
assert.NoError(t, err) // safe modification

r = Resources{
Table: &table.TableInfo{TableName: "test"},
Alter: "gibberish",
Statement: "gibberish",
}
err = renameCheck(context.Background(), r, logrus.New())
assert.Error(t, err) // gibberish
Expand Down
20 changes: 5 additions & 15 deletions pkg/check/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package check
import (
"context"
"errors"
"fmt"
"strings"

"github.com/pingcap/tidb/pkg/parser"
"github.com/pingcap/tidb/pkg/parser/ast"
_ "github.com/pingcap/tidb/pkg/parser/test_driver"
"github.com/siddontang/loggers"
)
Expand All @@ -21,6 +18,9 @@ func init() {
func hasTriggersCheck(ctx context.Context, r Resources, logger loggers.Advanced) error {
sql := `SELECT * FROM information_schema.triggers WHERE
(event_object_schema=? AND event_object_table=?)`
if r.DB == nil {
return errors.New("no database connection")
}
rows, err := r.DB.QueryContext(ctx, sql, r.Table.SchemaName, r.Table.TableName)
if err != nil {
return err
Expand All @@ -36,21 +36,11 @@ func hasTriggersCheck(ctx context.Context, r Resources, logger loggers.Advanced)
}

// addTriggersCheck checks for trigger creations, which is not supported
// Since the TiDB parser doesn't support this, we are using strings.Contains :(
func addTriggersCheck(ctx context.Context, r Resources, logger loggers.Advanced) error {
isAddingTrigger := strings.Contains(strings.ToUpper(r.Alter), "CREATE TRIGGER")
isAddingTrigger := strings.Contains(strings.ToUpper(strings.TrimSpace(r.Statement)), "CREATE TRIGGER")
if isAddingTrigger {
return errors.New("adding triggers is not supported")
}
sql := fmt.Sprintf("ALTER TABLE %s %s", r.Table.TableName, r.Alter)
p := parser.New()
stmtNodes, _, err := p.Parse(sql, "", "")
if err != nil {
return fmt.Errorf("could not parse alter table statement: %s", sql)
}
stmt := &stmtNodes[0]
_, ok := (*stmt).(*ast.AlterTableStmt)
if !ok {
return errors.New("not a valid alter table statement")
}
return nil // no problems
}
14 changes: 7 additions & 7 deletions pkg/check/triggers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ import (

func TestAddTriggers(t *testing.T) {
r := Resources{
Table: &table.TableInfo{TableName: "account"},
Alter: "CREATE TRIGGER ins_sum BEFORE INSERT ON account FOR EACH ROW SET @sum = @sum + NEW.amount;",
Table: &table.TableInfo{TableName: "account"},
Statement: "CREATE TRIGGER ins_sum BEFORE INSERT ON account FOR EACH ROW SET @sum = @sum + NEW.amount;",
}
err := addTriggersCheck(context.Background(), r, logrus.New())
assert.Error(t, err) // add triggers
assert.ErrorContains(t, err, "adding triggers is not supported")

r.Alter = "DROP COLUMN foo"
err = addForeignKeyCheck(context.Background(), r, logrus.New())
r.Statement = "ALTER TABLE t.t1 DROP COLUMN foo"
err = addTriggersCheck(context.Background(), r, logrus.New())
assert.NoError(t, err) // regular DDL

r.Alter = "bogus"
err = addForeignKeyCheck(context.Background(), r, logrus.New())
assert.Error(t, err) // not a valid ddl
r.Statement = "bogus"
err = addTriggersCheck(context.Background(), r, logrus.New())
assert.NoError(t, err) // not a valid ddl, but thats ok
}

func TestHasTriggers(t *testing.T) {
Expand Down
Loading