Skip to content

Commit

Permalink
DDL Checker Tool (#110)
Browse files Browse the repository at this point in the history
 add ddl checker tool
  • Loading branch information
LeopPro committed Nov 16, 2018
1 parent d57120b commit 23cc1d4
Show file tree
Hide file tree
Showing 9 changed files with 610 additions and 4 deletions.
9 changes: 6 additions & 3 deletions Makefile
@@ -1,4 +1,4 @@
.PHONY: build importer checker dump_region binlogctl sync_diff_inspector test check deps
.PHONY: build importer checker dump_region binlogctl sync_diff_inspector ddl_checker test check deps

# Ensure GOPATH is set before running build process.
ifeq "$(GOPATH)" ""
Expand All @@ -23,7 +23,7 @@ FILES := $$(find . -name '*.go' -type f | grep -vE 'vendor')
VENDOR_TIDB := vendor/github.com/pingcap/tidb


build: prepare check test importer checker dump_region binlogctl sync_diff_inspector finish
build: prepare check test importer checker dump_region binlogctl sync_diff_inspector ddl_checker finish

prepare:
cp go.mod1 go.mod
Expand All @@ -44,6 +44,9 @@ binlogctl:
sync_diff_inspector:
$(GO) build -ldflags '$(LDFLAGS)' -o bin/sync_diff_inspector ./sync_diff_inspector

ddl_checker:
$(GO) build -ldflags '$(LDFLAGS)' -o bin/ddl_checker ./ddl_checker

test:
@export log_level=error; \
$(GOTEST) -cover $(PACKAGES)
Expand All @@ -65,4 +68,4 @@ check:

finish:
cp go.mod go.mod1
cp go.sum go.sum1
cp go.sum go.sum1
5 changes: 5 additions & 0 deletions README.md
Expand Up @@ -14,6 +14,8 @@ make checker # build checker
make sync_diff_inspector # build sync_diff_inspector
make binlogctl # build binlogctl
make ddl_checker # build ddl_checker
```

When tidb-tools are built successfully, you can find the binary in the `bin` directory.
Expand All @@ -36,6 +38,9 @@ When tidb-tools are built successfully, you can find the binary in the `bin` dir

A tool for performing some tidb-binlog related operations, like querying the status of Pump/Drainer and pause/offline some Pump/Drainer.

- [ddl_checker](./ddl_checker)

A tool for checking if DDL SQL can be successfully executed by TiDB.

## License

Expand Down
58 changes: 58 additions & 0 deletions ddl_checker/README.md
@@ -0,0 +1,58 @@
## DDLChecker

DDLChecker is a tool for checking if DDL SQL can be successfully executed by TiDB.

## How to use

```
Usage of ./ddl_checker:
-host string
MySQL host (default "127.0.0.1")
-password string
Password
-port int
MySQL port (default 3306)
-schema string
Schema
-user string
User name (default "root")
cd ddl_checker
go build
./ddl_checker --host [host] --port [port] --user [user] --password [password] --schema [schema]
```

## Modes

You can switch modes using the `SETMOD` command.
Auto mode: The program will automatically synchronize the dependent table structure from MySQL and delete the conflict table
Prompt mode: The program will ask you before synchronizing the dependent table structure from MYSQL
Offline mode: This program doesn't need to connect to MySQL, and doesn't perform anything other than executing the input SQL.

SETMOD usage: SETMOD <MODCODE>; MODCODE = ["Auto", "Prompt", "Offline"] (case insensitive).

## Example

```
# ./bin/checker --host 127.0.0.1 --port 3306 --user root --password 123 --schema test
[Auto] > ALTER TABLE table_name MODIFY column_1 int(11) NOT NULL;
[DDLChecker] Syncing Table table_name
[DDLChecker] SQL execution succeeded
[Auto] > SETMOD PROMPT;
[Query] > ALTER TABLE table_name MODIFY column_1 int(11) NOT NULL;
[DDLChecker] Do you want to synchronize table [table_name] from MySQL and drop table [] in ExecutableChecker?(Y/N)y
[DDLChecker] Syncing Table table_name
[DDLChecker] SQL execution succeeded
[Query] > setmod manual;
[Manual] > ALTER TABLE table_name MODIFY column_1 int(11) NOT NULL;
[DDLChecker] SQL execution failed: [schema:1146]Table 'test.table_name' doesn't exist
```

## License
Apache 2.0 license. See the [LICENSE](../LICENSE) file for details.

212 changes: 212 additions & 0 deletions ddl_checker/main.go
@@ -0,0 +1,212 @@
// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"bufio"
"context"
"flag"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/pingcap/errors"
"github.com/pingcap/tidb-tools/pkg/dbutil"
"github.com/pingcap/tidb-tools/pkg/ddl-checker"
"os"
"strings"
"unicode"
)

var (
mode = auto
executableChecker *checker.ExecutableChecker
ddlSyncer *checker.DDLSyncer
reader *bufio.Reader
tidbContext = context.Background()

host = flag.String("host", "127.0.0.1", "MySQL host")
port = flag.Int("port", 3306, "MySQL port")
username = flag.String("user", "root", "User name")
password = flag.String("password", "", "Password")
schema = flag.String("schema", "", "Schema")
)

const (
welcomeInfo = "ExecutableChecker: Check if SQL can be successfully executed by TiDB\n" +
"Copyright 2018 PingCAP, Inc.\n\n" +
"You can switch modes using the `SETMOD` command.\n" +
"Auto mode: The program will automatically synchronize the dependent table structure from MySQL " +
"and delete the conflict table\n" +
"Prompt mode: The program will ask you before synchronizing the dependent table structure from MYSQL\n" +
"Offline mode: This program doesn't need to connect to MySQL, and doesn't perform anything other than executing the input SQL.\n\n" +
setmodUsage + "\n"

setmodUsage = "SETMOD usage: SETMOD <MODCODE>; MODCODE = [\"Auto\", \"Prompt\", \"Offline\"] (case insensitive).\n"

auto = "auto"
prompt = "prompt"
offline = "offline"
)

func main() {
fmt.Print(welcomeInfo)
initialise()
mainLoop()
destroy()
}

func initialise() {
flag.Parse()
var err error
reader = bufio.NewReader(os.Stdin)
executableChecker, err = checker.NewExecutableChecker()
if err != nil {
fmt.Printf("[DDLChecker] Init failed, can't create ExecutableChecker: %s\n", err.Error())
os.Exit(1)
}
executableChecker.Execute(tidbContext, "use test;")
dbInfo := &dbutil.DBConfig{
User: *username,
Password: *password,
Host: *host,
Port: *port,
Schema: *schema,
}
ddlSyncer, err = checker.NewDDLSyncer(dbInfo, executableChecker)
if err != nil {
fmt.Printf("[DDLChecker] Init failed, can't open mysql database: %s\n", err.Error())
os.Exit(1)
}
}

func destroy() {
executableChecker.Close()
ddlSyncer.Close()
}

func mainLoop() {
var input string
var err error
for isContinue := true; isContinue; isContinue = handler(input) {
fmt.Printf("[%s] > ", strings.ToTitle(mode))
input, err = reader.ReadString(';')
if err != nil {
fmt.Printf("[DDLChecker] Read stdin error: %s\n", err.Error())
os.Exit(1)
}
}
}
func handler(input string) bool {
lowerTrimInput := strings.ToLower(strings.TrimFunc(input, func(r rune) bool {
return unicode.IsSpace(r) || r == ';'
}))
// cmd exit
if lowerTrimInput == "exit" {
return false
}
// cmd setmod
if strings.HasPrefix(lowerTrimInput, "setmod") {
x := strings.TrimSpace(lowerTrimInput[6:])
switch x {
case auto, prompt, offline:
mode = x
default:
fmt.Print(setmodUsage)
}
return true
}
stmt, err := executableChecker.Parse(input)
if err != nil {
fmt.Println("[DDLChecker] SQL parse error: ", err.Error())
return true
}
if !checker.IsDDL(stmt) {
fmt.Println("[DDLChecker] Warning: The input SQL isn't a DDL")
}
if mode != offline {
// auto and query mod
neededTables, _ := checker.GetTablesNeededExist(stmt)
nonNeededTables, err := checker.GetTablesNeededNonExist(stmt)
// skip when stmt isn't a DDLNode
if err == nil && (mode == auto || (mode == prompt && promptAutoSync(neededTables, nonNeededTables))) {
err := syncTablesFromMysql(neededTables)
if err != nil {
return true
}
err = dropTables(nonNeededTables)
if err != nil {
return true
}
}
}
err = executableChecker.Execute(tidbContext, input)
if err == nil {
fmt.Println("[DDLChecker] SQL execution succeeded")
} else {
fmt.Println("[DDLChecker] SQL execution failed:", err.Error())
}
return true
}

func syncTablesFromMysql(tableNames []string) error {
for _, tableName := range tableNames {
fmt.Println("[DDLChecker] Syncing Table", tableName)
err := ddlSyncer.SyncTable(tidbContext, *schema, tableName)
if err != nil {
fmt.Println("[DDLChecker] Sync table failure:", err.Error())
return errors.Trace(err)
}
}
return nil
}

func dropTables(tableNames []string) error {
for _, tableName := range tableNames {
fmt.Println("[DDLChecker] Dropping table", tableName)
err := executableChecker.DropTable(tidbContext, tableName)
if err != nil {
fmt.Println("[DDLChecker] Drop table", tableName, "Error:", err.Error())
return errors.Trace(err)
}
}
return nil
}

func promptAutoSync(neededTable []string, nonNeededTable []string) bool {
return promptYorN("[DDLChecker] Do you want to synchronize table %v from MySQL "+
"and drop table %v in DDLChecker?(Y/N)", neededTable, nonNeededTable)
}

func promptYorN(format string, a ...interface{}) bool {
for {
fmt.Printf(format, a...)
innerLoop:
for {
result, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("[DDLChecker] Read stdin error: %s\n", err.Error())
return false
}
switch strings.ToLower(strings.TrimSpace(result)) {
case "y", "yes":
return true
case "n", "no":
return false
case "":
continue innerLoop
default:
break innerLoop
}
}
}
}
6 changes: 5 additions & 1 deletion go.mod1
Expand Up @@ -12,6 +12,7 @@ require (
github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142 // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/cznic/mathutil v0.0.0-20181021201202-eba54fb065b7 // indirect
github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/eapache/go-resiliency v1.1.0 // indirect
Expand Down Expand Up @@ -39,6 +40,9 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/montanaflynn/stats v0.0.0-20180911141734-db72e6cae808 // indirect
github.com/ngaut/log v0.0.0-20180314031856-b8e36e7ba5ac
github.com/ngaut/pools v0.0.0-20180318154953-b7bc8c42aac7 // indirect
github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef // indirect
github.com/opentracing/basictracer-go v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.0.2 // indirect
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/pingcap/check v0.0.0-20171206051426-1c287c953996
Expand Down Expand Up @@ -67,7 +71,7 @@ require (
github.com/ugorji/go v1.1.1 // indirect
github.com/unrolled/render v0.0.0-20180914162206-b9786414de4d // indirect
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/atomic v1.3.2
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum1
Expand Up @@ -21,6 +21,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cznic/mathutil v0.0.0-20181021201202-eba54fb065b7 h1:y+DH9ARrWiiNBV+6waYP2IPcsRbxdU1qsnycPfShF4c=
github.com/cznic/mathutil v0.0.0-20181021201202-eba54fb065b7/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65 h1:hxuZop6tSoOi0sxFzoGGYdRqNrPubyaIf9KoBG9tPiE=
github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
Expand Down Expand Up @@ -77,6 +79,12 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/montanaflynn/stats v0.0.0-20180911141734-db72e6cae808/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/ngaut/log v0.0.0-20180314031856-b8e36e7ba5ac h1:wyheT2lPXRQqYPWY2IVW5BTLrbqCsnhL61zK2R5goLA=
github.com/ngaut/log v0.0.0-20180314031856-b8e36e7ba5ac/go.mod h1:ueVCjKQllPmX7uEvCYnZD5b8qjidGf1TCH61arVe4SU=
github.com/ngaut/pools v0.0.0-20180318154953-b7bc8c42aac7 h1:7KAv7KMGTTqSmYZtNdcNTgsos+vFzULLwyElndwn+5c=
github.com/ngaut/pools v0.0.0-20180318154953-b7bc8c42aac7/go.mod h1:iWMfgwqYW+e8n5lC/jjNEhwcjbRDpl5NT7n2h+4UNcI=
github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef h1:K0Fn+DoFqNqktdZtdV3bPQ/0cuYh2H4rkg0tytX/07k=
github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef/go.mod h1:7WjlapSfwQyo6LNmIvEWzsW1hbBQfpUO4JWnuQRmva8=
github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
Expand Down

0 comments on commit 23cc1d4

Please sign in to comment.