├── .gitignore ├── Makefile ├── README.md ├── cmd ├── pivot │ └── main.go └── randgen │ └── main.go ├── go.mod ├── go.sum └── pkg ├── connection ├── connection.go ├── exec.go ├── log.go ├── schema.go ├── sqls.go └── types.go ├── executor ├── connection.go ├── executor.go └── generate.go ├── generator └── interface.go ├── go-sqlsmith ├── README.md ├── builtin │ ├── function.go │ ├── function_common.go │ ├── function_control.go │ ├── function_datetime.go │ ├── function_encryption.go │ ├── function_information.go │ ├── function_json.go │ ├── function_list.go │ ├── function_logic.go │ ├── function_math.go │ ├── function_miscellaneous.go │ ├── function_string.go │ ├── function_tidb.go │ ├── genetate.go │ ├── hint.go │ └── types.go ├── data_gen.go ├── data_gen │ ├── generate_data_item.go │ └── generate_data_item_test.go ├── data_generator.go ├── data_generator_test.go ├── ddl_ast.go ├── ddl_sql.go ├── ddl_test.go ├── debug.go ├── dml_ast.go ├── dml_sql.go ├── dml_test.go ├── rand.go ├── schema.go ├── schema_test.go ├── sqlsmith.go ├── sqlsmith_test.go ├── stateflow │ ├── schema.go │ ├── stateflow.go │ ├── util.go │ ├── walker.go │ └── walker_ddl.go ├── test.sh ├── types │ ├── column.go │ ├── table.go │ ├── table_test.go │ └── types.go ├── util │ ├── conversion.go │ ├── log.go │ ├── rand.go │ └── util.go └── walker_test.go ├── logger └── logger.go ├── mysql └── mysql.go ├── pivot ├── config.go ├── dml.go ├── generator.go ├── generator_test.go ├── operator.go ├── pivot.go └── util.go └── types ├── duration.go ├── generator.go ├── log.go ├── log_test.go ├── order.go ├── order_test.go ├── sort_string.go └── sql.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | /bin 4 | .idea 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOARCH := $(if $(GOARCH),$(GOARCH),amd64) 2 | GO=GO15VENDOREXPERIMENT="1" CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) GO111MODULE=on go 3 | GOTEST=GO15VENDOREXPERIMENT="1" CGO_ENABLED=1 GO111MODULE=on go test # go race detector requires cgo 4 | VERSION := $(if $(VERSION),$(VERSION),latest) 5 | 6 | GOBUILD=$(GO) build 7 | 8 | default: pivot 9 | 10 | randgen: 11 | $(GOBUILD) $(GOMOD) -o bin/randgen cmd/randgen/*.go 12 | 13 | pivot: 14 | $(GOBUILD) $(GOMOD) -o bin/pivot cmd/pivot/*.go 15 | 16 | fmt: 17 | go fmt ./... 18 | 19 | tidy: 20 | @echo "go mod tidy" 21 | GO111MODULE=on go mod tidy 22 | @git diff --exit-code -- go.mod 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Inspired by Manuel Rigger's paper [Testing Database Engines via Pivoted Query Synthesis](https://arxiv.org/pdf/2001.04174.pdf) 2 | # How to run 3 | ``` 4 | make pivot 5 | bin/pivot -d "root@tcp(127.0.0.1:4000)/" 6 | ``` 7 | -------------------------------------------------------------------------------- /cmd/pivot/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "time" 8 | 9 | _ "github.com/go-sql-driver/mysql" 10 | "github.com/zhouqiang-cl/wreck-it/pkg/pivot" 11 | ) 12 | 13 | var ( 14 | dsn string 15 | ) 16 | 17 | func main() { 18 | // p.Start(context.Background()) 19 | flag.StringVar(&dsn, "d", "", "dsn of target db for testing") 20 | flag.Parse() 21 | if dsn == "" { 22 | panic("no dsn in arguments") 23 | } 24 | 25 | p, err := pivot.NewPivot(dsn, "test") 26 | if err != nil { 27 | panic(fmt.Sprintf("new pivot failed, error: %+v\n", err)) 28 | } 29 | 30 | //p.Conf.Dsn = "root@tcp(127.0.0.1:4000)/test" 31 | ctx, _ := context.WithTimeout(context.Background(), 5*time.Minute) 32 | fmt.Printf("start pivot test\n") 33 | p.Start(ctx) 34 | fmt.Printf("wait for finish\n") 35 | for { 36 | select { 37 | case <-ctx.Done(): 38 | p.Close() 39 | default: 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /cmd/randgen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/pingcap/log" 10 | "github.com/zhouqiang-cl/wreck-it/pkg/executor" 11 | "github.com/zhouqiang-cl/wreck-it/pkg/generator" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | var ( 16 | dsn = flag.String("dsn", "", "config file path") 17 | ) 18 | 19 | func main() { 20 | if err := doGenerate(); err != nil { 21 | panic(err) 22 | } 23 | } 24 | 25 | func doGenerate() error { 26 | flag.Parse() 27 | 28 | //client, err := sql.Open("mysql", *dsn) 29 | //if err != nil { 30 | // log.Fatal(err) 31 | // return 32 | //} 33 | // 34 | //tableNum := rand.Intn(5) + 1 35 | 36 | dbDsn := "root@tcp(127.0.0.1:4000)/" 37 | e, err := executor.New(dbDsn, "test") 38 | if err != nil { 39 | return err 40 | } 41 | 42 | e.Exec("drop database if exists test") 43 | e.Exec("create database test") 44 | e.Exec("use test") 45 | 46 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 47 | for i := 0; i < r.Intn(10)+1; i++ { 48 | sql, _ := e.GenerateDDLCreateTable(nil) 49 | err := e.Exec(sql.SQLStmt) 50 | if err != nil { 51 | log.L().Error("create table failed", zap.String("sql", sql.SQLStmt), zap.Error(err)) 52 | return err 53 | } 54 | } 55 | 56 | err = e.ReloadSchema() 57 | if err != nil { 58 | log.Error("reload data failed!") 59 | return err 60 | } 61 | ddlOpt := &generator.DDLOptions{ 62 | OnlineDDL: true, 63 | Tables: []string{}, 64 | } 65 | for i := 0; i < r.Intn(10); i++ { 66 | sql, _ := e.GenerateDDLCreateIndex(ddlOpt) 67 | fmt.Println(sql) 68 | err = e.Exec(sql.SQLStmt) 69 | if err != nil { 70 | log.L().Error("create index failed", zap.String("sql", sql.SQLStmt), zap.Error(err)) 71 | return err 72 | } 73 | } 74 | 75 | for i := 0; i < r.Intn(30)+2; i++ { 76 | sql, _ := e.GenerateDMLInsert() 77 | err = e.Exec(sql.SQLStmt) 78 | if err != nil { 79 | log.L().Error("insert data failed", zap.String("sql", sql.SQLStmt), zap.Error(err)) 80 | return err 81 | } 82 | } 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zhouqiang-cl/wreck-it 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 7 | github.com/go-sql-driver/mysql v1.5.0 8 | github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9 9 | github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect 10 | github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect 11 | github.com/ngaut/log v0.0.0-20180314031856-b8e36e7ba5ac 12 | github.com/pingcap/log v0.0.0-20200117041106-d28c14d3b1cd 13 | github.com/pingcap/parser v0.0.0-20200317021010-cd90cc2a7d87 14 | github.com/pingcap/tidb v2.0.11+incompatible 15 | github.com/satori/go.uuid v1.2.0 16 | github.com/sirupsen/logrus v1.5.0 // indirect 17 | github.com/stretchr/testify v1.5.1 18 | go.uber.org/zap v1.14.0 19 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e 20 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect 21 | ) 22 | 23 | replace github.com/pingcap/tidb => github.com/pingcap/tidb v0.0.0-20200317142013-5268094afe05 24 | -------------------------------------------------------------------------------- /pkg/connection/connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package connection 15 | 16 | import ( 17 | "github.com/juju/errors" 18 | 19 | "github.com/zhouqiang-cl/wreck-it/pkg/logger" 20 | "github.com/zhouqiang-cl/wreck-it/pkg/mysql" 21 | ) 22 | 23 | // Option struct 24 | type Option struct { 25 | Name string 26 | Log string 27 | Mute bool 28 | GeneralLog bool 29 | } 30 | 31 | // Connection define connection struct 32 | type Connection struct { 33 | logger *logger.Logger 34 | db *mysql.DBConnect 35 | opt *Option 36 | } 37 | 38 | // New create Connection instance from dsn 39 | func New(dsn string, opt *Option) (*Connection, error) { 40 | l, err := logger.New(opt.Name, "", opt.Mute) 41 | if err != nil { 42 | return nil, errors.Trace(err) 43 | } 44 | db, err := mysql.OpenDB(dsn, 1) 45 | if err != nil { 46 | return nil, errors.Trace(err) 47 | } 48 | c := &Connection{ 49 | logger: l, 50 | db: db, 51 | opt: opt, 52 | } 53 | if err := c.Prepare(); err != nil { 54 | return nil, errors.Trace(err) 55 | } 56 | return c, nil 57 | } 58 | 59 | // Prepare connection 60 | func (c *Connection) Prepare() error { 61 | if c.opt.GeneralLog { 62 | return errors.Trace(c.GeneralLog(1)) 63 | } 64 | return nil 65 | } 66 | 67 | // CloseDB close connection 68 | func (c *Connection) CloseDB() error { 69 | return c.db.CloseDB() 70 | } 71 | 72 | // ReConnect rebuild connection 73 | func (c *Connection) ReConnect() error { 74 | if err := c.db.ReConnect(); err != nil { 75 | return err 76 | } 77 | return c.Prepare() 78 | } 79 | -------------------------------------------------------------------------------- /pkg/connection/exec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package connection 15 | 16 | import ( 17 | "fmt" 18 | "time" 19 | 20 | "github.com/ngaut/log" 21 | ) 22 | 23 | // Select run select statement and return query result 24 | func (c *Connection) Select(stmt string, args ...interface{}) ([][]*QueryItem, error) { 25 | start := time.Now() 26 | rows, err := c.db.Query(stmt, args...) 27 | if err != nil { 28 | c.logSQL(stmt, time.Now().Sub(start), err) 29 | return [][]*QueryItem{}, err 30 | } 31 | 32 | columnTypes, _ := rows.ColumnTypes() 33 | var result [][]*QueryItem 34 | 35 | for rows.Next() { 36 | var ( 37 | rowResultSets []interface{} 38 | resultRow []*QueryItem 39 | ) 40 | for range columnTypes { 41 | rowResultSets = append(rowResultSets, new(interface{})) 42 | } 43 | if err := rows.Scan(rowResultSets...); err != nil { 44 | log.Info(err) 45 | } 46 | for index, resultItem := range rowResultSets { 47 | r := *resultItem.(*interface{}) 48 | item := QueryItem{ 49 | ValType: columnTypes[index], 50 | } 51 | if r != nil { 52 | bytes := r.([]byte) 53 | item.ValString = string(bytes) 54 | } else { 55 | item.Null = true 56 | } 57 | resultRow = append(resultRow, &item) 58 | } 59 | result = append(result, resultRow) 60 | } 61 | 62 | // TODO: make args and stmt together 63 | c.logSQL(stmt, time.Now().Sub(start), nil) 64 | return result, nil 65 | } 66 | 67 | // Update run update statement and return error 68 | func (c *Connection) Update(stmt string) (int64, error) { 69 | var affectedRows int64 70 | start := time.Now() 71 | result, err := c.db.Exec(stmt) 72 | if err == nil { 73 | affectedRows, _ = result.RowsAffected() 74 | } 75 | c.logSQL(stmt, time.Now().Sub(start), err, affectedRows) 76 | return affectedRows, err 77 | } 78 | 79 | // Insert run insert statement and return error 80 | func (c *Connection) Insert(stmt string) (int64, error) { 81 | var affectedRows int64 82 | start := time.Now() 83 | result, err := c.db.Exec(stmt) 84 | if err == nil { 85 | affectedRows, _ = result.RowsAffected() 86 | } 87 | c.logSQL(stmt, time.Now().Sub(start), err, affectedRows) 88 | return affectedRows, err 89 | } 90 | 91 | // Delete run delete statement and return error 92 | func (c *Connection) Delete(stmt string) (int64, error) { 93 | var affectedRows int64 94 | start := time.Now() 95 | result, err := c.db.Exec(stmt) 96 | if err == nil { 97 | affectedRows, _ = result.RowsAffected() 98 | } 99 | c.logSQL(stmt, time.Now().Sub(start), err, affectedRows) 100 | return affectedRows, err 101 | } 102 | 103 | // ExecDDL do DDL actions 104 | func (c *Connection) ExecDDL(query string, args ...interface{}) error { 105 | start := time.Now() 106 | _, err := c.db.Exec(query, args...) 107 | c.logSQL(query, time.Now().Sub(start), err) 108 | return err 109 | } 110 | 111 | // Exec do any exec actions 112 | func (c *Connection) Exec(stmt string) error { 113 | _, err := c.db.Exec(stmt) 114 | return err 115 | } 116 | 117 | // Begin a txn 118 | func (c *Connection) Begin() error { 119 | start := time.Now() 120 | err := c.db.Begin() 121 | c.logSQL("BEGIN", time.Now().Sub(start), err) 122 | return err 123 | } 124 | 125 | // Commit a txn 126 | func (c *Connection) Commit() error { 127 | start := time.Now() 128 | err := c.db.Commit() 129 | c.logSQL("COMMIT", time.Now().Sub(start), err) 130 | return err 131 | } 132 | 133 | // Rollback a txn 134 | func (c *Connection) Rollback() error { 135 | start := time.Now() 136 | err := c.db.Rollback() 137 | c.logSQL("ROLLBACK", time.Now().Sub(start), err) 138 | return err 139 | } 140 | 141 | // IfTxn show if in a transaction 142 | func (c *Connection) IfTxn() bool { 143 | return c.db.IfTxn() 144 | } 145 | 146 | // GetBeginTime get the begin time of a transaction 147 | // if not in transaction, return 0 148 | func (c *Connection) GetBeginTime() time.Time { 149 | return c.db.GetBeginTime() 150 | } 151 | 152 | // GeneralLog turn on or turn off general_log in TiDB 153 | func (c *Connection) GeneralLog(v int) error { 154 | _, err := c.db.Exec(fmt.Sprintf("set @@tidb_general_log=\"%d\"", v)) 155 | return err 156 | } 157 | 158 | // ShowDatabases list databases 159 | func (c *Connection) ShowDatabases() ([]string, error) { 160 | res, err := c.Select("SHOW DATABASES") 161 | if err != nil { 162 | return nil, err 163 | } 164 | var dbs []string 165 | for _, db := range res { 166 | if len(db) == 1 { 167 | dbs = append(dbs, db[0].ValString) 168 | } 169 | } 170 | return dbs, nil 171 | } 172 | -------------------------------------------------------------------------------- /pkg/connection/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package connection 15 | 16 | import ( 17 | "fmt" 18 | "time" 19 | 20 | "github.com/ngaut/log" 21 | ) 22 | 23 | func (c *Connection) logSQL(sql string, duration time.Duration, err error, args ...interface{}) { 24 | line := fmt.Sprintf("Success: %t, Duration: %s", err == nil, duration) 25 | for index, arg := range args { 26 | if index == 0 { 27 | if affectedRows, ok := arg.(int64); ok { 28 | line = fmt.Sprintf("%s, Affected Rows: %d", line, affectedRows) 29 | } 30 | } 31 | } 32 | if err := c.logger.Infof("%s, SQL: %s", line, sql); err != nil { 33 | log.Fatalf("fail to log to file %v", err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/connection/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package connection 15 | 16 | import ( 17 | "fmt" 18 | "regexp" 19 | 20 | "github.com/juju/errors" 21 | ) 22 | 23 | const ( 24 | indexColumnName = "Key_name" 25 | ) 26 | 27 | var binlogSyncTablePattern = regexp.MustCompile(`^t[0-9]+$`) 28 | 29 | // FetchDatabases database list 30 | func (c *Connection) FetchDatabases() ([]string, error) { 31 | databaseSlice := []string{} 32 | 33 | databases, err := c.db.Query(showDatabasesSQL) 34 | if err != nil { 35 | return []string{}, errors.Trace(err) 36 | } 37 | 38 | for databases.Next() { 39 | var dbname string 40 | if err := databases.Scan(&dbname); err != nil { 41 | return []string{}, errors.Trace(err) 42 | } 43 | databaseSlice = append(databaseSlice, dbname) 44 | } 45 | return databaseSlice, nil 46 | } 47 | 48 | // FetchTables table list 49 | func (c *Connection) FetchTables(db string) ([]string, error) { 50 | tableSlice := []string{} 51 | 52 | tables, err := c.db.Query(schemaSQL) 53 | if err != nil { 54 | return []string{}, errors.Trace(err) 55 | } 56 | 57 | // fetch tables need to be described 58 | for tables.Next() { 59 | var schemaName, tableName string 60 | if err := tables.Scan(&schemaName, &tableName, new(interface{})); err != nil { 61 | return []string{}, errors.Trace(err) 62 | } 63 | if schemaName == db { 64 | tableSlice = append(tableSlice, tableName) 65 | } 66 | } 67 | return tableSlice, nil 68 | } 69 | 70 | // FetchSchema get schema of given database from database 71 | func (c *Connection) FetchSchema(db string) ([][6]string, error) { 72 | var ( 73 | schema [][6]string 74 | tablesInDB [][3]string 75 | ) 76 | tables, err := c.db.Query(schemaSQL) 77 | if err != nil { 78 | return schema, errors.Trace(err) 79 | } 80 | 81 | // fetch tables need to be described 82 | for tables.Next() { 83 | var schemaName, tableName, tableType string 84 | if err = tables.Scan(&schemaName, &tableName, &tableType); err != nil { 85 | return [][6]string{}, errors.Trace(err) 86 | } 87 | // do not generate SQL about sync table 88 | // because this can not be reproduced from logs 89 | if schemaName == db && !ifBinlogSyncTable(tableName) { 90 | tablesInDB = append(tablesInDB, [3]string{schemaName, tableName, tableType}) 91 | } 92 | } 93 | 94 | // desc tables 95 | for _, table := range tablesInDB { 96 | var ( 97 | schemaName = table[0] 98 | tableName = table[1] 99 | tableType = table[2] 100 | ) 101 | columns, err := c.FetchColumns(schemaName, tableName) 102 | if err != nil { 103 | return [][6]string{}, errors.Trace(err) 104 | } 105 | for _, column := range columns { 106 | schema = append(schema, [6]string{schemaName, tableName, tableType, column[0], column[1], column[2]}) 107 | } 108 | } 109 | return schema, nil 110 | } 111 | 112 | // FetchColumns get columns for given table 113 | func (c *Connection) FetchColumns(db, table string) ([][3]string, error) { 114 | var columns [][3]string 115 | res, err := c.db.Query(fmt.Sprintf(tableSQL, db, table)) 116 | if err != nil { 117 | return [][3]string{}, errors.Trace(err) 118 | } 119 | for res.Next() { 120 | var columnName, columnType, nullValue, index string 121 | var defaultValue, extra interface{} 122 | if err = res.Scan(&columnName, &columnType, &nullValue, &index, &defaultValue, &extra); err != nil { 123 | return [][3]string{}, errors.Trace(err) 124 | } 125 | columns = append(columns, [3]string{columnName, columnType, nullValue}) 126 | } 127 | return columns, nil 128 | } 129 | 130 | // FetchIndexes get indexes for given table 131 | func (c *Connection) FetchIndexes(db, table string) ([]string, error) { 132 | var indexes []string 133 | res, err := c.db.Query(fmt.Sprintf(indexSQL, db, table)) 134 | if err != nil { 135 | return []string{}, errors.Trace(err) 136 | } 137 | 138 | columnTypes, err := res.ColumnTypes() 139 | if err != nil { 140 | return indexes, errors.Trace(err) 141 | } 142 | for res.Next() { 143 | var ( 144 | keyname string 145 | rowResultSets []interface{} 146 | ) 147 | 148 | for range columnTypes { 149 | rowResultSets = append(rowResultSets, new(interface{})) 150 | } 151 | if err = res.Scan(rowResultSets...); err != nil { 152 | return []string{}, errors.Trace(err) 153 | } 154 | 155 | for index, resultItem := range rowResultSets { 156 | if columnTypes[index].Name() != indexColumnName { 157 | continue 158 | } 159 | r := *resultItem.(*interface{}) 160 | if r != nil { 161 | bytes := r.([]byte) 162 | keyname = string(bytes) 163 | } 164 | } 165 | 166 | if keyname != "" { 167 | indexes = append(indexes, keyname) 168 | } 169 | } 170 | return indexes, nil 171 | } 172 | 173 | func ifBinlogSyncTable(t string) bool { 174 | return binlogSyncTablePattern.MatchString(t) 175 | } 176 | -------------------------------------------------------------------------------- /pkg/connection/sqls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package connection 15 | 16 | const ( 17 | showDatabasesSQL = "SHOW DATABASES" 18 | dropDatabaseSQL = "DROP DATABASE IF EXISTS %s" 19 | createDatabaseSQL = "CREATE DATABASE %s" 20 | schemaSQL = "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM information_schema.tables" 21 | tableSQL = "DESC %s.%s" 22 | indexSQL = "SHOW INDEX FROM %s.%s" 23 | ) 24 | -------------------------------------------------------------------------------- /pkg/connection/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package connection 15 | 16 | import ( 17 | "database/sql" 18 | 19 | "github.com/juju/errors" 20 | ) 21 | 22 | // QueryItem define query result 23 | type QueryItem struct { 24 | Null bool 25 | ValType *sql.ColumnType 26 | ValString string 27 | } 28 | 29 | // MustSame compare tow QueryItem and return error if not same 30 | func (q *QueryItem) MustSame(q1 *QueryItem) error { 31 | if (q == nil) != (q1 == nil) { 32 | return errors.Errorf("one is nil but another is not, self: %t, another: %t", q == nil, q1 == nil) 33 | } 34 | 35 | if q.Null != q1.Null { 36 | return errors.Errorf("one is NULL but another is not, self: %t, another: %t", q.Null, q1.Null) 37 | } 38 | 39 | if q.ValType.Name() != q1.ValType.Name() { 40 | return errors.Errorf("column type diff, self: %s, another: %s", q.ValType.Name(), q1.ValType.Name()) 41 | } 42 | 43 | if q.ValString != q1.ValString { 44 | return errors.Errorf("column data diff, self: %s, another: %s", q.ValString, q1.ValString) 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /pkg/executor/connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package executor 15 | 16 | import ( 17 | "github.com/zhouqiang-cl/wreck-it/pkg/connection" 18 | ) 19 | 20 | // GetConn get connection of first connection 21 | func (e *Executor) GetConn() *connection.Connection { 22 | return e.conn 23 | } 24 | 25 | // ReConnect rebuild connection 26 | func (e *Executor) ReConnect() error { 27 | return e.conn.ReConnect() 28 | } 29 | 30 | // Close close connection 31 | func (e *Executor) Close() error { 32 | return e.conn.CloseDB() 33 | } 34 | 35 | // Exec function for quick executor some SQLs 36 | func (e *Executor) Exec(sql string) error { 37 | if err := e.conn.Exec(sql); err != nil { 38 | return err 39 | } 40 | return e.conn.Commit() 41 | } 42 | 43 | // ExecIgnoreErr function for quick executor some SQLs with error tolerance 44 | func (e *Executor) ExecIgnoreErr(sql string) { 45 | _ = e.conn.Exec(sql) 46 | _ = e.conn.Commit() 47 | } 48 | -------------------------------------------------------------------------------- /pkg/executor/executor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package executor 15 | 16 | import ( 17 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 18 | "regexp" 19 | 20 | "github.com/juju/errors" 21 | 22 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith" 23 | 24 | "github.com/zhouqiang-cl/wreck-it/pkg/connection" 25 | ) 26 | 27 | var ( 28 | dbnameRegex = regexp.MustCompile(`([a-z0-9A-Z_]+)$`) 29 | ) 30 | 31 | // Executor define test executor 32 | type Executor struct { 33 | conn *connection.Connection 34 | ss *sqlsmith.SQLSmith 35 | db string 36 | } 37 | 38 | // New create Executor 39 | func New(dsn string, db string) (*Executor, error) { 40 | conn, err := connection.New(dsn, &connection.Option{ 41 | Log: "", 42 | Mute: false, 43 | GeneralLog: true, 44 | }) 45 | if err != nil { 46 | return nil, errors.Trace(err) 47 | } 48 | 49 | e := Executor{ 50 | conn: conn, 51 | ss: sqlsmith.New(), 52 | db: db, 53 | } 54 | return &e, nil 55 | } 56 | 57 | func (e *Executor) GetTables() map[string]*types.Table { 58 | return e.ss.Databases[e.db].Tables 59 | } 60 | -------------------------------------------------------------------------------- /pkg/executor/generate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package executor 15 | 16 | import ( 17 | "fmt" 18 | "strings" 19 | 20 | "github.com/juju/errors" 21 | "github.com/ngaut/log" 22 | 23 | "github.com/zhouqiang-cl/wreck-it/pkg/generator" 24 | smith "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith" 25 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 26 | "github.com/zhouqiang-cl/wreck-it/pkg/types" 27 | ) 28 | 29 | // ReloadSchema expose reloadSchema 30 | func (e *Executor) ReloadSchema() error { 31 | return errors.Trace(e.reloadSchema()) 32 | } 33 | 34 | func (e *Executor) reloadSchema() error { 35 | schema, err := e.conn.FetchSchema(e.db) 36 | if err != nil { 37 | return errors.Trace(err) 38 | } 39 | indexes := make(map[string][]string) 40 | for _, col := range schema { 41 | if _, ok := indexes[col[2]]; ok { 42 | continue 43 | } 44 | index, err := e.conn.FetchIndexes(e.db, col[1]) 45 | // may not return error here 46 | // just disable indexes 47 | if err != nil { 48 | return errors.Trace(err) 49 | } 50 | indexes[col[1]] = index 51 | } 52 | 53 | e.ss = smith.New() 54 | e.ss.LoadSchema(schema, indexes) 55 | e.ss.SetDB(e.db) 56 | e.ss.SetStable(false) 57 | e.ss.SetHint(true) 58 | return nil 59 | } 60 | 61 | // Generate DDL 62 | 63 | // GenerateDDLCreateTable rand create table statement 64 | func (e *Executor) GenerateDDLCreateTable(colTypes []string) (*types.SQL, error) { 65 | stmt, table, err := e.ss.CreateTableStmt(colTypes) 66 | if err != nil { 67 | return nil, errors.Trace(err) 68 | } 69 | return &types.SQL{ 70 | SQLType: types.SQLTypeDDLCreateTable, 71 | SQLTable: table, 72 | SQLStmt: stmt, 73 | }, nil 74 | } 75 | 76 | // GenerateDDLCreateIndex rand create index statement 77 | func (e *Executor) GenerateDDLCreateIndex(opt *generator.DDLOptions) (*types.SQL, error) { 78 | stmt, err := e.ss.CreateIndexStmt(opt) 79 | if err != nil { 80 | return nil, errors.Trace(err) 81 | } 82 | return &types.SQL{ 83 | SQLType: types.SQLTypeDDLCreateIndex, 84 | SQLStmt: stmt, 85 | }, nil 86 | } 87 | 88 | // GenerateDDLAlterTable rand alter table statement 89 | func (e *Executor) GenerateDDLAlterTable(opt *generator.DDLOptions) (*types.SQL, error) { 90 | stmt, err := e.ss.AlterTableStmt(opt) 91 | if err != nil { 92 | return nil, errors.Trace(err) 93 | } 94 | return &types.SQL{ 95 | SQLType: types.SQLTypeDDLAlterTable, 96 | SQLStmt: stmt, 97 | }, nil 98 | } 99 | 100 | // GenerateDMLSelect rand select statement 101 | func (e *Executor) GenerateDMLSelect() (*types.SQL, error) { 102 | stmt, table, err := e.ss.SelectStmt(4) 103 | if err != nil { 104 | return nil, errors.Trace(err) 105 | } 106 | return &types.SQL{ 107 | SQLType: types.SQLTypeDMLSelect, 108 | SQLStmt: stmt, 109 | SQLTable: table, 110 | }, nil 111 | } 112 | 113 | // GenerateDMLSelectForUpdate rand update statement 114 | func (e *Executor) GenerateDMLSelectForUpdate() (*types.SQL, error) { 115 | stmt, table, err := e.ss.SelectForUpdateStmt(4) 116 | if err != nil { 117 | return nil, errors.Trace(err) 118 | } 119 | return &types.SQL{ 120 | SQLType: types.SQLTypeDMLSelectForUpdate, 121 | SQLStmt: stmt, 122 | SQLTable: table, 123 | }, nil 124 | } 125 | 126 | // GenerateDMLUpdate rand update statement 127 | func (e *Executor) GenerateDMLUpdate() (*types.SQL, error) { 128 | stmt, table, err := e.ss.UpdateStmt() 129 | if err != nil { 130 | return nil, errors.Trace(err) 131 | } 132 | if strings.HasPrefix(stmt, "UPDATE s0") { 133 | log.Info(stmt, table) 134 | log.Info(e.conn.FetchSchema(e.db)) 135 | } 136 | return &types.SQL{ 137 | SQLType: types.SQLTypeDMLUpdate, 138 | SQLStmt: stmt, 139 | SQLTable: table, 140 | }, nil 141 | } 142 | 143 | // GenerateDMLDelete rand update statement 144 | func (e *Executor) GenerateDMLDelete() (*types.SQL, error) { 145 | stmt, table, err := e.ss.DeleteStmt() 146 | if err != nil { 147 | return nil, errors.Trace(err) 148 | } 149 | return &types.SQL{ 150 | SQLType: types.SQLTypeDMLDelete, 151 | SQLStmt: stmt, 152 | SQLTable: table, 153 | }, nil 154 | } 155 | 156 | func (e *Executor) GenerateDMLInsertByTable(table string) (*types.SQL, error) { 157 | stmt, err := e.ss.InsertStmtForTable(table) 158 | if err != nil { 159 | return nil, errors.Trace(err) 160 | } 161 | return &types.SQL{ 162 | SQLType: types.SQLTypeDMLInsert, 163 | SQLStmt: stmt, 164 | SQLTable: table, 165 | }, nil 166 | } 167 | 168 | // GenerateDMLInsert rand insert statement 169 | func (e *Executor) GenerateDMLInsert() (*types.SQL, error) { 170 | stmt, table, err := e.ss.InsertStmt(false) 171 | if err != nil { 172 | return nil, errors.Trace(err) 173 | } 174 | return &types.SQL{ 175 | SQLType: types.SQLTypeDMLInsert, 176 | SQLStmt: stmt, 177 | SQLTable: table, 178 | }, nil 179 | } 180 | 181 | // GenerateSleep rand insert statement 182 | func (e *Executor) GenerateSleep() *types.SQL { 183 | duration := util.Rd(25) 184 | return &types.SQL{ 185 | SQLType: types.SQLTypeSleep, 186 | SQLStmt: fmt.Sprintf("SELECT SLEEP(%d)", duration), 187 | ExecTime: duration, 188 | } 189 | } 190 | 191 | // GenerateTxnBegin start transaction 192 | func (e *Executor) GenerateTxnBegin() *types.SQL { 193 | return &types.SQL{ 194 | SQLType: types.SQLTypeTxnBegin, 195 | SQLStmt: "BEGIN", 196 | } 197 | } 198 | 199 | // GenerateTxnCommit commit transaction 200 | func (e *Executor) GenerateTxnCommit() *types.SQL { 201 | return &types.SQL{ 202 | SQLType: types.SQLTypeTxnCommit, 203 | SQLStmt: "COMMIT", 204 | } 205 | } 206 | 207 | // GenerateTxnRollback rollback transaction 208 | func (e *Executor) GenerateTxnRollback() *types.SQL { 209 | return &types.SQL{ 210 | SQLType: types.SQLTypeTxnRollback, 211 | SQLStmt: "ROLLBACK", 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /pkg/generator/interface.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Generator interface to unify the usage of sqlsmith and sqlspider 4 | type Generator interface { 5 | // ReloadSchema function read raw scheme 6 | // For each record 7 | // record[0] dbname 8 | // record[1] table name 9 | // record[2] table type 10 | // record[3] column name 11 | // record[4] column type 12 | // record[5] column support null value, YES/NO 13 | // indexes map table name to index string slice 14 | LoadSchema(records [][6]string, indexes map[string][]string) 15 | // SetDB set operation database 16 | // the generated SQLs after this will be under this database 17 | SetDB(db string) 18 | // SetStable is a trigger for whether generate random or some database-basicinfo-dependent data 19 | // eg. SetStable(true) will disable both rand() and user() functions since they both make unstable result in different database 20 | SetStable(stable bool) 21 | // SetHint can control if hints would be generated or not 22 | SetHint(hint bool) 23 | // BeginWithOnlineTables to get online tables and begin transaction 24 | // if DMLOptions.OnlineTable is set to true 25 | // this function will return a table slice 26 | // and the tables beyond this slice will not be affected by DMLs during the generation 27 | BeginWithOnlineTables(opt *DMLOptions) []string 28 | // EndTransaction end transaction and remove online tables limitation 29 | EndTransaction() []string 30 | // SelectStmt generate select SQL 31 | SelectStmt(depth int) (string, string, error) 32 | // SelectForUpdateStmt generate select SQL with for update lock 33 | SelectForUpdateStmt(depth int) (string, string, error) 34 | // InsertStmt generate insert SQL 35 | InsertStmt(fn bool) (string, string, error) 36 | // UpdateStmt generate update SQL 37 | UpdateStmt() (string, string, error) 38 | // DeleteStmt generate delete SQL 39 | DeleteStmt() (string, string, error) 40 | // CreateTableStmt generate create table SQL 41 | CreateTableStmt() (string, string, error) 42 | // AlterTableStmt generate create table SQL, table slice for avoiding online DDL 43 | AlterTableStmt(opt *DDLOptions) (string, error) 44 | // CreateIndexStmt generate create index SQL, table slice for avoiding online DDL 45 | CreateIndexStmt(opt *DDLOptions) (string, error) 46 | } 47 | 48 | // DMLOptions for DML generation 49 | type DMLOptions struct { 50 | // if OnlineTable is set to true 51 | // generator will rand some tables from the target database 52 | // and the following DML statements before transaction closed 53 | // should only affects these tables 54 | // else there will be no limit for affected tables 55 | // if you OnlineDDL field in DDLOptions is true, you may got the following error from TiDB 56 | // "ERROR 1105 (HY000): Information schema is changed. [try again later]" 57 | OnlineTable bool 58 | } 59 | 60 | // DDLOptions for DDL generation 61 | type DDLOptions struct { 62 | // if OnlineDDL is set to false 63 | // DDL generation will look up tables other generators which are doing DMLs with the tables 64 | // the DDL generated will avoid modifing these tables 65 | // if OnlineDDL set to true 66 | // DDL generation will not avoid online tables 67 | OnlineDDL bool 68 | // if OnlineDDL is set to false 69 | // Tables contains all online tables which should not be modified with DDL 70 | // pocket will collect them from other generator instances 71 | Tables []string 72 | } 73 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/README.md: -------------------------------------------------------------------------------- 1 | # SQLsmith-GO 2 | 3 | Go version of [SQLsmith](https://github.com/anse1/sqlsmith). 4 | 5 | ## Usage 6 | 7 | ```go 8 | import ( 9 | sqlsmith_go "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith" 10 | ) 11 | 12 | func gosmith() { 13 | ss := sqlsmith_go.New() 14 | 15 | // load schema 16 | ss.LoadSchema([][5]string{ 17 | // members table 18 | [5]string{"games", "members", "BASE TABLE", "id", "int(11)"}, 19 | [5]string{"games", "members", "BASE TABLE", "name", "varchar(255)"}, 20 | [5]string{"games", "members", "BASE TABLE", "age", "int(11)"}, 21 | [5]string{"games", "members", "BASE TABLE", "team_id", "int(11)"}, 22 | // teams table 23 | [5]string{"games", "teams", "BASE TABLE", "id", "int(11)"}, 24 | [5]string{"games", "teams", "BASE TABLE", "team_name", "varchar(255)"}, 25 | [5]string{"games", "teams", "BASE TABLE", "created_at", "timestamp"}, 26 | }) 27 | 28 | // use games database 29 | ss.SetDB("games") 30 | 31 | // generate select statement AST without scema information 32 | node := ss.SelectStmt(5) 33 | 34 | // fill the tree with selected schema and get SQL string 35 | sql, err := ss.Walk(node) 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import ( 17 | "github.com/pingcap/parser/ast" 18 | "github.com/pingcap/parser/model" 19 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/data_gen" 20 | 21 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 22 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 23 | ) 24 | 25 | // GenerateFuncCallExpr generate random builtin chain 26 | func GenerateFuncCallExpr(table *types.Table, args int, stable bool) ast.ExprNode { 27 | if args == 0 && util.Rd(2) == 0 { 28 | return ast.NewValueExpr(data_gen.GenerateRandDataItem(), "", "") 29 | } 30 | 31 | funcCallExpr := ast.FuncCallExpr{} 32 | 33 | fns := getValidArgsFunc(args, stable) 34 | fn := copyFunc(fns[util.Rd(len(fns))]) 35 | funcCallExpr.FnName = model.NewCIStr(fn.name) 36 | for i := 0; i < args; i++ { 37 | r := util.Rd(100) 38 | if r > 80 { 39 | funcCallExpr.Args = append(funcCallExpr.Args, GenerateFuncCallExpr(table, 1+util.Rd(3), stable)) 40 | } 41 | funcCallExpr.Args = append(funcCallExpr.Args, GenerateFuncCallExpr(table, 0, stable)) 42 | } 43 | // if args != 0 { 44 | // log.Println(funcCallExpr) 45 | // for _, arg := range funcCallExpr.Args { 46 | // log.Println(arg) 47 | // } 48 | // } 49 | return &funcCallExpr 50 | } 51 | 52 | func getValidArgsFunc(args int, stable bool) []*functionClass { 53 | var fns []*functionClass 54 | for _, fn := range getFuncMap() { 55 | if fn.minArg > args { 56 | continue 57 | } 58 | if fn.stable != stable { 59 | continue 60 | } 61 | if fn.maxArg == -1 || fn.maxArg >= args { 62 | fns = append(fns, fn) 63 | } 64 | } 65 | return fns 66 | } 67 | 68 | func copyFunc(fn *functionClass) *functionClass { 69 | return &functionClass{ 70 | name: fn.name, 71 | minArg: fn.minArg, 72 | maxArg: fn.maxArg, 73 | constArg: fn.constArg, 74 | mysql: fn.mysql, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var commonFunctions = []*functionClass{ 19 | {ast.Coalesce, 1, -1, false, true, false}, 20 | {ast.IsNull, 1, 1, false, true, false}, 21 | {ast.Greatest, 2, -1, false, true, false}, 22 | {ast.Least, 2, -1, false, true, false}, 23 | {ast.Interval, 2, -1, false, true, false}, 24 | } 25 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_control.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var controlFunctions = []*functionClass{ 19 | {ast.If, 3, 3, false, true, false}, 20 | {ast.Ifnull, 3, 3, false, true, false}, 21 | } 22 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_datetime.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var datetimeFunctions = []*functionClass{ 19 | {ast.AddDate, 3, 3, false, true, false}, 20 | {ast.DateAdd, 3, 3, false, true, false}, 21 | {ast.SubDate, 3, 3, false, true, false}, 22 | {ast.DateSub, 3, 3, false, true, false}, 23 | {ast.AddTime, 2, 2, false, true, false}, 24 | {ast.ConvertTz, 3, 3, false, true, false}, 25 | // {ast.Curdate, 0, 0, false, true, false}, 26 | // {ast.CurrentDate, 0, 0, false, true, false}, 27 | // {ast.CurrentTime, 0, 1, false, true, false}, 28 | // {ast.CurrentTimestamp, 0, 1, false, true, false}, 29 | // {ast.Curtime, 0, 1, false, true, false}, 30 | {ast.Date, 1, 1, false, true, false}, 31 | {ast.DateLiteral, 1, 1, false, true, false}, 32 | {ast.DateFormat, 2, 2, false, true, false}, 33 | {ast.DateDiff, 2, 2, false, true, false}, 34 | {ast.Day, 1, 1, false, true, false}, 35 | {ast.DayName, 1, 1, false, true, false}, 36 | {ast.DayOfMonth, 1, 1, false, true, false}, 37 | {ast.DayOfWeek, 1, 1, false, true, false}, 38 | {ast.DayOfYear, 1, 1, false, true, false}, 39 | {ast.Extract, 2, 2, false, true, false}, 40 | {ast.FromDays, 1, 1, false, true, false}, 41 | {ast.FromUnixTime, 1, 2, false, true, false}, 42 | {ast.GetFormat, 2, 2, false, true, false}, 43 | {ast.Hour, 1, 1, false, true, false}, 44 | // {ast.LocalTime, 0, 1, false, true, false}, 45 | // {ast.LocalTimestamp, 0, 1, true, true, false}, 46 | {ast.MakeDate, 2, 2, false, true, false}, 47 | {ast.MakeTime, 3, 3, false, true, false}, 48 | {ast.MicroSecond, 1, 1, false, true, false}, 49 | {ast.Minute, 1, 1, false, true, false}, 50 | {ast.Month, 1, 1, false, true, false}, 51 | {ast.MonthName, 1, 1, false, true, false}, 52 | // {ast.Now, 0, 1, false, true, false}, 53 | {ast.PeriodAdd, 2, 2, false, true, false}, 54 | // {ast.PeriodDiff, 2, 2, false, true, false}, 55 | {ast.Quarter, 1, 1, false, true, false}, 56 | {ast.SecToTime, 1, 1, false, true, false}, 57 | {ast.Second, 1, 1, false, true, false}, 58 | {ast.StrToDate, 2, 2, false, true, false}, 59 | {ast.SubTime, 2, 2, false, true, false}, 60 | // will make diff 61 | // {ast.Sysdate, 0, 1, false, true, false}, 62 | {ast.Time, 1, 1, false, true, false}, 63 | {ast.TimeLiteral, 1, 1, false, true, false}, 64 | {ast.TimeFormat, 2, 2, false, true, false}, 65 | {ast.TimeToSec, 1, 1, false, true, false}, 66 | {ast.TimeDiff, 2, 2, false, true, false}, 67 | {ast.Timestamp, 1, 2, false, true, false}, 68 | {ast.TimestampLiteral, 1, 2, false, true, false}, 69 | {ast.TimestampAdd, 3, 3, false, true, false}, 70 | {ast.TimestampDiff, 3, 3, false, true, false}, 71 | {ast.ToDays, 1, 1, false, true, false}, 72 | {ast.ToSeconds, 1, 1, false, true, false}, 73 | // {ast.UnixTimestamp, 0, 1, false, true, false}, 74 | // {ast.UTCDate, 0, 0, false, true, false}, 75 | // {ast.UTCTime, 0, 1, false, true, false}, 76 | // {ast.UTCTimestamp, 0, 1, false, true, false}, 77 | {ast.Week, 1, 2, false, true, false}, 78 | {ast.Weekday, 1, 1, false, true, false}, 79 | {ast.WeekOfYear, 1, 1, false, true, false}, 80 | {ast.Year, 1, 1, false, true, false}, 81 | {ast.YearWeek, 1, 2, false, true, false}, 82 | {ast.LastDay, 1, 1, false, true, false}, 83 | } 84 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_encryption.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | // compress and uncompress function make diff 19 | var encryptionFunctions = []*functionClass{ 20 | {ast.AesDecrypt, 2, 3, false, true, false}, 21 | {ast.AesEncrypt, 2, 3, false, true, false}, 22 | // {ast.Compress, 1, 1, false, true, false}, 23 | // removed in MySQL 8.0.3 24 | // {ast.Decode, 2, 2, false, true, false}, 25 | {ast.DesDecrypt, 1, 2, false, true, false}, 26 | {ast.DesEncrypt, 1, 2, false, true, false}, 27 | // {ast.Encode, 2, 2, false, true, false}, 28 | {ast.Encrypt, 1, 2, false, true, false}, 29 | {ast.MD5, 1, 1, false, true, false}, 30 | {ast.OldPassword, 1, 1, false, true, false}, 31 | // {ast.PasswordFunc, 1, 1, false, true, false}, 32 | {ast.RandomBytes, 1, 1, false, true, false}, 33 | {ast.SHA1, 1, 1, false, true, false}, 34 | {ast.SHA, 1, 1, false, true, false}, 35 | {ast.SHA2, 2, 2, false, true, false}, 36 | // {ast.Uncompress, 1, 1, false, true, false}, 37 | // {ast.UncompressedLength, 1, 1, false, true, false}, 38 | // {ast.ValidatePasswordStrength, 1, 1, false, true, false}, 39 | } 40 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_information.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var informationFunctions = []*functionClass{ 19 | // will make diff 20 | // {ast.ConnectionID, 0, 0, false, true, false}, 21 | {ast.CurrentUser, 0, 0, false, true, true}, 22 | // should be fix 23 | // {ast.CurrentRole, 0, 0, false, true, false}, 24 | {ast.Database, 0, 0, false, true, false}, 25 | // This function is a synonym for DATABASE(). 26 | // See http://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_schema 27 | {ast.Schema, 0, 0, false, true, false}, 28 | {ast.FoundRows, 0, 0, false, true, false}, 29 | {ast.LastInsertId, 0, 1, false, true, false}, 30 | {ast.User, 0, 0, false, true, true}, 31 | {ast.Version, 0, 0, false, true, true}, 32 | {ast.Benchmark, 2, 2, false, true, false}, 33 | // {ast.Charset, 1, 1, false, true, false}, 34 | // {ast.Coercibility, 1, 1, false, true, false}, 35 | // {ast.Collation, 1, 1, false, true, false}, 36 | {ast.RowCount, 0, 0, false, true, false}, 37 | // Will make difference in abtest 38 | // {ast.SessionUser, 0, 0, false, true, false}, 39 | // {ast.SystemUser, 0, 0, false, true, false}, 40 | } 41 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | // import "github.com/pingcap/parser/ast" 17 | 18 | var jsonFunctions = []*functionClass{ 19 | // {ast.JSONType, 1, 1, false, true, false}, 20 | // {ast.JSONExtract, 2, -1, false, true, false}, 21 | // {ast.JSONUnquote, 1, 1, false, true, false}, 22 | // {ast.JSONSet, 3, -1, false, true, false}, 23 | // {ast.JSONInsert, 3, -1, false, true, false}, 24 | // {ast.JSONReplace, 3, -1, false, true, false}, 25 | // // {ast.JSONRemove, 2, -1, false, true, false}, 26 | // // {ast.JSONMerge, 2, -1, false, true, false}, 27 | // {ast.JSONObject, 0, -1, false, true, false}, 28 | // {ast.JSONArray, 0, -1, false, true, false}, 29 | // {ast.JSONContains, 2, 3, false, true, false}, 30 | // {ast.JSONContainsPath, 3, -1, false, true, false}, 31 | // {ast.JSONValid, 1, 1, false, true, false}, 32 | // {ast.JSONArrayAppend, 3, -1, false, true, false}, 33 | // {ast.JSONArrayInsert, 3, -1, false, true, false}, 34 | // // {ast.JSONMergePatch, 2, -1, false, true, false}, 35 | // {ast.JSONMergePreserve, 2, -1, false, true, false}, 36 | // // {ast.JSONPretty, 1, 1, false, true, false}, 37 | // {ast.JSONQuote, 1, 1, false, true, false}, 38 | // {ast.JSONSearch, 3, -1, false, true, false}, 39 | // // {ast.JSONStorageSize, 1, 1, false, true, false}, 40 | // {ast.JSONDepth, 1, 1, false, true, false}, 41 | // {ast.JSONKeys, 1, 2, false, true, false}, 42 | // {ast.JSONLength, 1, 2, false, true, false}, 43 | } 44 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "log" 17 | 18 | var funcLists = [][]*functionClass{ 19 | commonFunctions, 20 | mathFunctions, 21 | datetimeFunctions, 22 | stringFunctions, 23 | informationFunctions, 24 | controlFunctions, 25 | miscellaneousFunctions, 26 | encryptionFunctions, 27 | jsonFunctions, 28 | tidbFunctions, 29 | } 30 | 31 | func getFuncMap() map[string]*functionClass { 32 | funcs := make(map[string]*functionClass) 33 | for _, funcSet := range funcLists { 34 | for _, fn := range funcSet { 35 | if _, ok := funcs[fn.name]; ok { 36 | log.Fatalf("duplicated func name %s", fn.name) 37 | } else { 38 | funcs[fn.name] = fn 39 | } 40 | } 41 | } 42 | return funcs 43 | } 44 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_logic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var logicFunctions = []*functionClass{ 19 | {ast.LogicAnd, 2, 2, false, true, false}, 20 | {ast.LogicOr, 2, 2, false, true, false}, 21 | {ast.LogicXor, 2, 2, false, true, false}, 22 | {ast.GE, 2, 2, false, true, false}, 23 | {ast.LE, 2, 2, false, true, false}, 24 | {ast.EQ, 2, 2, false, true, false}, 25 | {ast.NE, 2, 2, false, true, false}, 26 | {ast.LT, 2, 2, false, true, false}, 27 | {ast.GT, 2, 2, false, true, false}, 28 | {ast.NullEQ, 2, 2, false, true, false}, 29 | {ast.Plus, 2, 2, false, true, false}, 30 | {ast.Minus, 2, 2, false, true, false}, 31 | {ast.Mod, 2, 2, false, true, false}, 32 | {ast.Div, 2, 2, false, true, false}, 33 | {ast.Mul, 2, 2, false, true, false}, 34 | {ast.IntDiv, 2, 2, false, true, false}, 35 | {ast.BitNeg, 1, 1, false, true, false}, 36 | {ast.And, 2, 2, false, true, false}, 37 | {ast.LeftShift, 2, 2, false, true, false}, 38 | {ast.RightShift, 2, 2, false, true, false}, 39 | {ast.UnaryNot, 1, 1, false, true, false}, 40 | {ast.Or, 2, 2, false, true, false}, 41 | {ast.Xor, 2, 2, false, true, false}, 42 | {ast.UnaryMinus, 1, 1, false, true, false}, 43 | {ast.In, 2, -1, false, true, false}, 44 | {ast.IsTruth, 1, 1, false, true, false}, 45 | {ast.IsFalsity, 1, 1, false, true, false}, 46 | {ast.Like, 3, 3, false, true, false}, 47 | {ast.Regexp, 2, 2, false, true, false}, 48 | {ast.Case, 1, -1, false, true, false}, 49 | {ast.RowFunc, 2, -1, false, true, false}, 50 | {ast.SetVar, 2, 2, false, true, false}, 51 | {ast.GetVar, 1, 1, false, true, false}, 52 | {ast.BitCount, 1, 1, false, true, false}, 53 | {ast.GetParam, 1, 1, false, true, false}, 54 | } 55 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_math.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var mathFunctions = []*functionClass{ 19 | {ast.Abs, 1, 1, false, true, false}, 20 | {ast.Acos, 1, 1, false, true, false}, 21 | {ast.Asin, 1, 1, false, true, false}, 22 | {ast.Atan, 1, 2, false, true, false}, 23 | {ast.Atan2, 2, 2, false, true, false}, 24 | {ast.Ceil, 1, 1, false, true, false}, 25 | {ast.Ceiling, 1, 1, false, true, false}, 26 | {ast.Conv, 3, 3, false, true, false}, 27 | {ast.Cos, 1, 1, false, true, false}, 28 | {ast.Cot, 1, 1, false, true, false}, 29 | {ast.CRC32, 1, 1, false, true, false}, 30 | {ast.Degrees, 1, 1, false, true, false}, 31 | {ast.Exp, 1, 1, false, true, false}, 32 | {ast.Floor, 1, 1, false, true, false}, 33 | {ast.Ln, 1, 1, false, true, false}, 34 | {ast.Log, 1, 2, false, true, false}, 35 | {ast.Log2, 1, 1, false, true, false}, 36 | {ast.Log10, 1, 1, false, true, false}, 37 | {ast.PI, 0, 0, false, true, false}, 38 | {ast.Pow, 2, 2, false, true, false}, 39 | {ast.Power, 2, 2, false, true, false}, 40 | {ast.Radians, 1, 1, false, true, false}, 41 | {ast.Rand, 0, 1, false, true, true}, 42 | {ast.Round, 1, 2, false, true, false}, 43 | {ast.Sign, 1, 1, false, true, false}, 44 | {ast.Sin, 1, 1, false, true, false}, 45 | {ast.Sqrt, 1, 1, false, true, false}, 46 | {ast.Tan, 1, 1, false, true, false}, 47 | {ast.Truncate, 2, 2, false, true, false}, 48 | } 49 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_miscellaneous.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var miscellaneousFunctions = []*functionClass{ 19 | {ast.AnyValue, 1, 1, false, true, false}, 20 | {ast.InetAton, 1, 1, false, true, false}, 21 | {ast.InetNtoa, 1, 1, false, true, false}, 22 | {ast.Inet6Aton, 1, 1, false, true, false}, 23 | {ast.Inet6Ntoa, 1, 1, false, true, false}, 24 | // {ast.IsFreeLock, 1, 1, false, true, false}, 25 | {ast.IsIPv4, 1, 1, false, true, false}, 26 | {ast.IsIPv4Compat, 1, 1, false, true, false}, 27 | {ast.IsIPv4Mapped, 1, 1, false, true, false}, 28 | {ast.IsIPv6, 1, 1, false, true, false}, 29 | // {ast.IsUsedLock, 1, 1, false, true, false}, 30 | // {ast.MasterPosWait, 2, 4, false, true, false}, 31 | {ast.NameConst, 2, 2, false, true, false}, 32 | // {ast.ReleaseAllLocks, 0, 0, false, true, false}, 33 | {ast.UUID, 0, 0, false, true, true}, 34 | // {ast.UUIDShort, 0, 0, false, true, false}, 35 | } 36 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | var stringFunctions = []*functionClass{ 19 | {ast.ASCII, 1, 1, false, true, false}, 20 | {ast.Bin, 1, 1, false, true, false}, 21 | {ast.Concat, 1, -1, false, true, false}, 22 | {ast.ConcatWS, 2, -1, false, true, false}, 23 | {ast.Convert, 2, 2, false, true, false}, 24 | {ast.Elt, 2, -1, false, true, false}, 25 | {ast.ExportSet, 3, 5, false, true, false}, 26 | {ast.Field, 2, -1, false, true, false}, 27 | {ast.Format, 2, 3, false, true, false}, 28 | {ast.FromBase64, 1, 1, false, true, false}, 29 | {ast.InsertFunc, 4, 4, false, true, false}, 30 | {ast.Instr, 2, 2, false, true, false}, 31 | {ast.Lcase, 1, 1, false, true, false}, 32 | {ast.Left, 2, 2, false, true, false}, 33 | {ast.Right, 2, 2, false, true, false}, 34 | {ast.Length, 1, 1, false, true, false}, 35 | // {ast.LoadFile, 1, 1, false, true, false}, 36 | {ast.Locate, 2, 3, false, true, false}, 37 | {ast.Lower, 1, 1, false, true, false}, 38 | {ast.Lpad, 3, 3, false, true, false}, 39 | {ast.LTrim, 1, 1, false, true, false}, 40 | {ast.Mid, 3, 3, false, true, false}, 41 | {ast.MakeSet, 2, -1, false, true, false}, 42 | {ast.Oct, 1, 1, false, true, false}, 43 | {ast.OctetLength, 1, 1, false, true, false}, 44 | {ast.Ord, 1, 1, false, true, false}, 45 | {ast.Position, 2, 2, false, true, false}, 46 | {ast.Quote, 1, 1, false, true, false}, 47 | {ast.Repeat, 2, 2, false, true, false}, 48 | {ast.Replace, 3, 3, false, true, false}, 49 | {ast.Reverse, 1, 1, false, true, false}, 50 | {ast.RTrim, 1, 1, false, true, false}, 51 | {ast.Space, 1, 1, false, true, false}, 52 | {ast.Strcmp, 2, 2, false, true, false}, 53 | {ast.Substring, 2, 3, false, true, false}, 54 | {ast.Substr, 2, 3, false, true, false}, 55 | {ast.SubstringIndex, 3, 3, false, true, false}, 56 | {ast.ToBase64, 1, 1, false, true, false}, 57 | // not support fully trim, since the trim expression is special 58 | // SELECT TRIM('miyuri' FROM 'miyuri-jinja-no-miyuri'); 59 | // {ast.Trim, 1, 3, false, true, false}, 60 | // {ast.Trim, 1, 1, false, true}, 61 | {ast.Upper, 1, 1, false, true, false}, 62 | {ast.Ucase, 1, 1, false, true, false}, 63 | {ast.Hex, 1, 1, false, true, false}, 64 | {ast.Unhex, 1, 1, false, true, false}, 65 | {ast.Rpad, 3, 3, false, true, false}, 66 | {ast.BitLength, 1, 1, false, true, false}, 67 | // {ast.CharFunc, 2, -1, false, true, false}, 68 | {ast.CharLength, 1, 1, false, true, false}, 69 | {ast.CharacterLength, 1, 1, false, true, false}, 70 | {ast.FindInSet, 2, 2, false, true, false}, 71 | } 72 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/function_tidb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | // import "github.com/pingcap/parser/ast" 17 | 18 | var tidbFunctions = []*functionClass{ 19 | // {ast.TiDBDecodeKey, 1, 1, false, false, false}, 20 | // {ast.TiDBVersion, 0, 0, false, false, false}, 21 | // {ast.TiDBIsDDLOwner, 0, 0, false, false, false}, 22 | // {ast.TiDBParseTso, 1, 1, false, false, false}, 23 | // {ast.TiDBDecodePlan, 1, 1, false, false, false}, 24 | } 25 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/genetate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | // generate is for future usage 15 | // which aim to make a builtin function combination or chain by a given type 16 | // the returned builtin function combination can be multiple function call 17 | 18 | package builtin 19 | 20 | import ( 21 | "github.com/pingcap/parser/ast" 22 | "github.com/pingcap/parser/model" 23 | ) 24 | 25 | // GenerateTypeFuncCallExpr generate FuncCallExpr by given type 26 | func GenerateTypeFuncCallExpr(t string) *ast.FuncCallExpr { 27 | switch t { 28 | case "timestamp": 29 | return GenerateTimestampFuncCallExpr() 30 | } 31 | return nil 32 | } 33 | 34 | // GenerateTimestampFuncCallExpr generate builtin func which will return date type 35 | func GenerateTimestampFuncCallExpr() *ast.FuncCallExpr { 36 | return &ast.FuncCallExpr{ 37 | FnName: model.NewCIStr("CURRENT_TIMESTAMP"), 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/hint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | import ( 17 | "log" 18 | "math/rand" 19 | 20 | "github.com/pingcap/parser/ast" 21 | "github.com/pingcap/parser/model" 22 | 23 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 24 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 25 | ) 26 | 27 | type hintClass struct { 28 | name string 29 | minArg int 30 | maxArg int 31 | constArg bool 32 | mysql bool 33 | stable bool 34 | } 35 | 36 | var hintKeywords = []*hintClass{ 37 | // with no args 38 | {"hash_agg", 0, 0, false, false, false}, 39 | {"stream_agg", 0, 0, false, false, false}, 40 | {"agg_to_cop", 0, 0, false, false, false}, 41 | {"read_consistent_replica", 0, 0, false, false, false}, 42 | {"no_index_merge", 0, 0, false, false, false}, 43 | 44 | // with bool (TRUE or FALSE) 45 | {"use_toja", 1, 1, false, false, false}, 46 | {"enable_plan_cache", 1, 1, false, false, false}, 47 | {"use_cascades", 1, 1, false, false, false}, 48 | 49 | // these have been renamed 50 | // {"tidb_hj", 2, 3, false, false, true}, 51 | // {"tidb_smj", 2, 3, false, false, true}, 52 | // {"tidb_inlj", 2, 3, false, false, true}, 53 | // with 2 or more args 54 | {"hash_join", 1, -1, false, true, false}, 55 | {"merge_join", 1, -1, false, false, false}, 56 | {"inl_join", 1, -1, false, false, false}, 57 | 58 | // with int (byte) 59 | {"memory_quota", 1, 1, false, false, false}, 60 | // with int (ms) 61 | {"max_execution_time", 1, 1, false, false, false}, 62 | } 63 | 64 | var indexHintKeywords = []*hintClass{ 65 | // with table name and at least one idx name 66 | {"use_index", 2, -1, false, false, false}, 67 | {"ignore_index", 2, -1, false, false, false}, 68 | {"use_index_merge", 2, -1, false, false, false}, 69 | } 70 | 71 | // these will not be generated for some reason 72 | var disabledHintKeywords = []*hintClass{ 73 | {"qb_name", 0, 0, false, false, false}, 74 | 75 | // not released? 76 | {"time_range", 2, -1, false, false, false}, 77 | // storage type with tablename: TIKV[t1] 78 | {"read_from_storage", 2, -1, false, false, false}, 79 | // not released? 80 | {"query_type", 1, 1, false, false, false}, 81 | 82 | {"inl_hash_join", 1, -1, false, false, false}, 83 | {"inl_merge_join", 1, -1, false, false, false}, 84 | } 85 | 86 | // GenerateHintExpr ... 87 | func GenerateHintExpr(table *types.Table) (h *ast.TableOptimizerHint) { 88 | enabledKeywords := hintKeywords 89 | if len(table.Indexes) > 0 { 90 | enabledKeywords = append(enabledKeywords, indexHintKeywords...) 91 | } 92 | h = new(ast.TableOptimizerHint) 93 | hintKeyword := enabledKeywords[util.Rd(len(enabledKeywords))] 94 | h.HintName = model.NewCIStr(hintKeyword.name) 95 | 96 | if hintKeyword.maxArg == 0 { 97 | return 98 | } 99 | 100 | if hintKeyword.maxArg == 1 { 101 | switch hintKeyword.name { 102 | case "use_toja", "enable_plan_cache", "use_cascades": 103 | h.HintData = util.RdBool() 104 | case "memory_quota": 105 | h.HintData = int64(util.RdRange(30720000, 40960000)) 106 | case "max_execution_time": 107 | h.HintData = uint64(util.RdRange(500, 1500)) 108 | default: 109 | log.Fatalf("unreachable hintKeyword.name:%s", hintKeyword.name) 110 | } 111 | return 112 | } 113 | 114 | shuffledTables := make([]ast.HintTable, 0) 115 | for _, t := range table.InnerTableList { 116 | shuffledTables = append(shuffledTables, ast.HintTable{ 117 | TableName: model.NewCIStr(t.Table), 118 | }) 119 | } 120 | rand.Shuffle(len(shuffledTables), func(i, j int) { 121 | shuffledTables[i], shuffledTables[j] = shuffledTables[j], shuffledTables[i] 122 | }) 123 | 124 | shuffledIndexes := make([]model.CIStr, 0) 125 | for _, idx := range table.Indexes { 126 | if idx != "" { 127 | shuffledIndexes = append(shuffledIndexes, model.NewCIStr(idx)) 128 | } 129 | } 130 | rand.Shuffle(len(shuffledIndexes), func(i, j int) { 131 | shuffledIndexes[i], shuffledIndexes[j] = shuffledIndexes[j], shuffledIndexes[i] 132 | }) 133 | 134 | switch hintKeyword.name { 135 | case "hash_join", "merge_join", "inl_join", "inl_hash_join", "inl_merge_join": 136 | if len(shuffledTables) < 2 { 137 | h = nil 138 | return 139 | } 140 | 141 | n := util.MinInt(util.Rd(4)+2, len(shuffledTables)) // avoid case n < 2 142 | for ; n > 0; n-- { 143 | h.Tables = append(h.Tables, shuffledTables[n-1]) 144 | } 145 | case "use_index", "ignore_index", "use_index_merge": 146 | // if no table nor index return empty 147 | if len(shuffledTables) == 0 || len(shuffledIndexes) == 0 { 148 | h = nil 149 | return 150 | } 151 | h.Tables = append(h.Tables, shuffledTables[util.Rd(len(shuffledTables))]) 152 | n := util.MinInt(util.Rd(4)+1, len(shuffledIndexes)) // avoid case n == 0 153 | for ; n > 0; n-- { 154 | h.Indexes = append(h.Indexes, shuffledIndexes[n-1]) 155 | } 156 | default: 157 | log.Fatalf("unreachable hintKeyword.name:%s", hintKeyword.name) 158 | } 159 | return 160 | } 161 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/builtin/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package builtin 15 | 16 | type functionClass struct { 17 | name string 18 | minArg int 19 | maxArg int 20 | constArg bool 21 | mysql bool 22 | stable bool 23 | } 24 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/data_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/data_gen" 20 | "strings" 21 | 22 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 23 | ) 24 | 25 | // BatchData generate testing data by schema in given batch 26 | // return SQLs with insert statement 27 | func (s *SQLSmith) BatchData(total, batchSize int) ([]string, error) { 28 | if s.currDB == "" { 29 | return []string{}, errors.New("no selected database") 30 | } 31 | database, ok := s.Databases[s.currDB] 32 | if !ok { 33 | return []string{}, errors.New("selected database's schema not loaded") 34 | } 35 | 36 | var sqls []string 37 | for _, table := range database.Tables { 38 | var columns []*types.Column 39 | for _, column := range table.Columns { 40 | if column.Column == "id" { 41 | continue 42 | } 43 | columns = append(columns, column) 44 | } 45 | 46 | var lines [][]string 47 | count := 0 48 | for i := 0; i < total; i++ { 49 | var line []string 50 | for _, column := range columns { 51 | line = append(line, data_gen.GenerateDataItemString(column)) 52 | } 53 | lines = append(lines, line) 54 | count++ 55 | if count >= batchSize { 56 | count = 0 57 | sqls = append(sqls, makeSQL(table, columns, lines)) 58 | lines = [][]string{} 59 | } 60 | } 61 | if len(lines) != 0 { 62 | sqls = append(sqls, makeSQL(table, columns, lines)) 63 | } 64 | } 65 | return sqls, nil 66 | } 67 | 68 | func makeSQL(table *types.Table, columns []*types.Column, lines [][]string) string { 69 | var columnNames []string 70 | for _, column := range columns { 71 | columnNames = append(columnNames, fmt.Sprintf("`%s`", column.Column)) 72 | } 73 | return fmt.Sprintf("INSERT INTO `%s` (%s) VALUES \n %s", 74 | table.Table, 75 | strings.Join(columnNames, ", "), 76 | strings.Join(mapFn(lines, func(line []string) string { 77 | return fmt.Sprintf("(%s)", strings.Join(line, ", ")) 78 | }), ",\n")) 79 | } 80 | 81 | func mapFn(arr [][]string, fn func([]string) string) []string { 82 | var res []string 83 | for _, item := range arr { 84 | res = append(res, fn(item)) 85 | } 86 | return res 87 | } 88 | 89 | func (s *SQLSmith) generateDataItem(columnType string) string { 90 | switch columnType { 91 | case "varchar": 92 | return s.generateStringItem() 93 | case "text": 94 | return s.generateStringItem() 95 | case "int": 96 | return s.generateIntItem() 97 | case "timestamp": 98 | return s.generateDateItem() 99 | case "float": 100 | return s.generateFloatItem() 101 | } 102 | return "" 103 | } 104 | 105 | func (s *SQLSmith) generateStringItem() string { 106 | return fmt.Sprintf("\"%s\"", s.rdString(s.rd(100))) 107 | } 108 | 109 | func (s *SQLSmith) generateIntItem() string { 110 | return fmt.Sprintf("%d", s.rd(2147483647)) 111 | } 112 | 113 | func (s *SQLSmith) generateFloatItem() string { 114 | return fmt.Sprintf("%f", float64(s.rd(100000))*s.rdFloat64()) 115 | } 116 | 117 | func (s *SQLSmith) generateDateItem() string { 118 | return fmt.Sprintf("\"%s\"", s.rdDate().Format("2006-01-02 15:04:05")) 119 | } 120 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/data_gen/generate_data_item.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package data_gen 15 | 16 | import ( 17 | "fmt" 18 | //"github.com/pingcap/parser/ast" 19 | "strings" 20 | "time" 21 | 22 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 23 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 24 | 25 | "github.com/pingcap/parser/mysql" 26 | tidbTypes "github.com/pingcap/tidb/types" 27 | uuid "github.com/satori/go.uuid" 28 | ) 29 | 30 | var ( 31 | stringEnums = []string{"", "0", "-0", "1", "-1", "true", "false", " ", "NULL", "2020-02-02 02:02:00"} 32 | intEnums = []int{0, 1, -1, 65535} 33 | floatEnums = []float64{0, 0.1, 1.0000, -0.1, -1.0000, 0.5, 1.5} 34 | timeEnums = []tidbTypes.CoreTime{ 35 | tidbTypes.FromDate(0, 0, 0, 0, 0, 0, 0), 36 | tidbTypes.FromDate(1, 1, 1, 0, 0, 0, 0), 37 | tidbTypes.FromDate(2020, 2, 2, 2, 2, 2, 0), 38 | tidbTypes.FromDate(9999, 9, 9, 9, 9, 9, 0), 39 | } 40 | ) 41 | 42 | // GetUUID return uuid 43 | func GetUUID() string { 44 | return strings.ToUpper(uuid.NewV4().String()) 45 | } 46 | 47 | // GenerateRandDataItem rand data item with rand type 48 | func GenerateRandDataItem() interface{} { 49 | var dataType string 50 | switch util.Rd(6) { 51 | case 0: 52 | dataType = "varchar" 53 | case 1: 54 | dataType = "text" 55 | case 2: 56 | dataType = "int" 57 | case 3: 58 | dataType = "float" 59 | case 4: 60 | dataType = "timestamp" 61 | case 5: 62 | dataType = "datetime" 63 | } 64 | column := &types.Column{DataType: dataType} 65 | if dataType == "varchar" { 66 | column.DataLen = 100 67 | } 68 | return GenerateDataItem(column) 69 | } 70 | 71 | // GenerateDataItemString rand data with given type 72 | func GenerateDataItemString(column *types.Column) string { 73 | d := GenerateDataItem(column) 74 | switch c := d.(type) { 75 | case string: 76 | return c 77 | case int: 78 | return fmt.Sprintf("\"%d\"", c) 79 | case time.Time: 80 | return c.Format("2006-01-02 15:04:05") 81 | case tidbTypes.Time: 82 | return c.String() 83 | case float64: 84 | return fmt.Sprintf("%f", c) 85 | } 86 | return "not implement data transfer" 87 | } 88 | 89 | // GenerateDataItem rand data interface with given type 90 | func GenerateDataItem(column *types.Column) interface{} { 91 | var res interface{} 92 | // there will be 1/3 possibility return nil 93 | //if !column.HasOption(ast.ColumnOptionNotNull) && util.RdRange(0, 3) == 0 { 94 | // return nil 95 | //} 96 | switch column.DataType { 97 | case "varchar": 98 | res = GenerateStringItemLen(column.DataLen) 99 | case "text": 100 | res = GenerateStringItem() 101 | case "int": 102 | res = GenerateIntItem() 103 | case "datetime": 104 | res = GenerateTiDBDateItem() 105 | case "timestamp": 106 | res = GenerateTiDBTimestampItem() 107 | case "float": 108 | res = GenerateFloatItem() 109 | } 110 | return res 111 | } 112 | 113 | // GenerateZeroDataItem gets zero data interface with given type 114 | func GenerateZeroDataItem(column *types.Column) interface{} { 115 | var res interface{} 116 | // there will be 1/3 possibility return nil 117 | //if !column.HasOption(ast.ColumnOptionNotNull) && util.RdRange(0, 3) == 0 { 118 | // return nil 119 | //} 120 | switch column.DataType { 121 | case "varchar": 122 | res = "" 123 | case "text": 124 | res = "" 125 | case "int": 126 | res = 0 127 | case "datetime": 128 | res = tidbTypes.NewTime(tidbTypes.FromDate(0, 0, 0, 0, 0, 0, 0), mysql.TypeDatetime, 0) 129 | case "timestamp": 130 | res = tidbTypes.NewTime(tidbTypes.FromDate(0, 0, 0, 0, 0, 0, 0), mysql.TypeTimestamp, 0) 131 | case "float": 132 | res = float64(0) 133 | } 134 | return res 135 | } 136 | 137 | // GenerateEnumDataItem gets enum data interface with given type 138 | func GenerateEnumDataItem(column *types.Column) interface{} { 139 | var res interface{} 140 | // there will be 1/3 possibility return nil 141 | //if !column.HasOption(ast.ColumnOptionNotNull) && util.RdRange(0, 3) == 0 { 142 | // return nil 143 | //} 144 | switch column.DataType { 145 | case "varchar": 146 | res = stringEnums[util.Rd(len(stringEnums))] 147 | case "text": 148 | res = stringEnums[util.Rd(len(stringEnums))] 149 | case "int": 150 | res = intEnums[util.Rd(len(intEnums))] 151 | case "datetime": 152 | res = tidbTypes.NewTime(timeEnums[util.Rd(len(timeEnums))], mysql.TypeDatetime, 0) 153 | case "timestamp": 154 | res = tidbTypes.NewTime(timeEnums[util.Rd(len(timeEnums))], mysql.TypeDatetime, 0) 155 | case "float": 156 | res = floatEnums[util.Rd(len(floatEnums))] 157 | } 158 | return res 159 | } 160 | 161 | // GenerateStringItem generate string item 162 | func GenerateStringItem() string { 163 | return strings.ToUpper(GenerateStringItemLen(100)) 164 | } 165 | 166 | func GenerateStringItemLen(length int) string { 167 | return util.RdStringChar(util.Rd(length)) 168 | } 169 | 170 | // GenerateIntItem generate int item 171 | func GenerateIntItem() int { 172 | return util.Rd(2147483647) 173 | } 174 | 175 | // GenerateFloatItem generate float item 176 | func GenerateFloatItem() float64 { 177 | return float64(util.Rd(100000)) * util.RdFloat64() 178 | } 179 | 180 | // GenerateDateItem generate date item 181 | func GenerateDateItem() time.Time { 182 | t := util.RdDate() 183 | for ifDaylightTime(t) { 184 | t = util.RdDate() 185 | } 186 | return t 187 | } 188 | 189 | // GenerateTimestampItem generate timestamp item 190 | func GenerateTimestampItem() time.Time { 191 | t := util.RdTimestamp() 192 | for ifDaylightTime(t) { 193 | t = util.RdTimestamp() 194 | } 195 | return t 196 | } 197 | 198 | // GenerateTiDBDateItem generate date item 199 | func GenerateTiDBDateItem() tidbTypes.Time { 200 | // return tidbTypes.Time{ 201 | // Time: tidbTypes.FromGoTime(GenerateDateItem()), 202 | // Type: mysql.TypeDatetime, 203 | // } 204 | return tidbTypes.NewTime(tidbTypes.FromGoTime(GenerateDateItem()), mysql.TypeDatetime, 0) 205 | } 206 | 207 | func GenerateTiDBTimestampItem() tidbTypes.Time { 208 | return tidbTypes.NewTime(tidbTypes.FromGoTime(GenerateTimestampItem()), mysql.TypeTimestamp, 0) 209 | } 210 | 211 | func ifDaylightTime(t time.Time) bool { 212 | if t.Year() < 1986 || t.Year() > 1991 { 213 | return false 214 | } 215 | if t.Month() < 4 || t.Month() > 9 { 216 | return false 217 | } 218 | return true 219 | } 220 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/data_gen/generate_data_item_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package data_gen 15 | 16 | import ( 17 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | const ( 24 | layout = "2006-01-02 15:04:05" 25 | ) 26 | 27 | func TestSQLSmith_TestIfDaylightTime(t *testing.T) { 28 | assert.Equal(t, ifDaylightTime(util.TimeMustParse(layout, "1986-05-05 11:45:14")), true) 29 | assert.Equal(t, ifDaylightTime(util.TimeMustParse(layout, "1991-09-05 11:45:14")), true) 30 | assert.Equal(t, ifDaylightTime(util.TimeMustParse(layout, "1985-08-05 11:45:14")), false) 31 | assert.Equal(t, ifDaylightTime(util.TimeMustParse(layout, "1992-06-05 11:45:14")), false) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/data_generator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import "errors" 17 | 18 | // DataGenerator defines data generator 19 | type DataGenerator struct { 20 | total int 21 | batch int 22 | curr int 23 | sqlsmith *SQLSmith 24 | } 25 | 26 | // GenData returns data generator 27 | func (s *SQLSmith) GenData(total, batch int) (*DataGenerator, error) { 28 | if s.currDB == "" { 29 | return nil, errors.New("no selected database") 30 | } 31 | _, ok := s.Databases[s.currDB] 32 | if !ok { 33 | return nil, errors.New("selected database's schema not loaded") 34 | } 35 | return &DataGenerator{ 36 | total, 37 | batch, 38 | 0, 39 | s, 40 | }, nil 41 | } 42 | 43 | // Next returns data batch 44 | func (d *DataGenerator) Next() []string { 45 | if d.curr >= d.total { 46 | return []string{} 47 | } else if d.curr < d.total-d.batch { 48 | sqls, _ := d.sqlsmith.BatchData(d.batch, d.batch) 49 | d.curr += d.batch 50 | return sqls 51 | } 52 | sqls, _ := d.sqlsmith.BatchData(d.total-d.curr, d.batch) 53 | d.curr += (d.total - d.curr) 54 | return sqls 55 | } 56 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/data_generator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import "testing" 17 | 18 | // TestSQLSmith_DataGenerator tests with data generator 19 | func TestSQLSmith_DataGenerator(t *testing.T) { 20 | ss := new() 21 | 22 | ss.LoadSchema(schema, indexes) 23 | 24 | ss.SetDB(dbname) 25 | 26 | gen, _ := ss.GenData(10, 5) 27 | 28 | for sqls := gen.Next(); len(sqls) != 0; sqls = gen.Next() { 29 | t.Log(sqls) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/ddl_ast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "github.com/pingcap/parser/ast" 18 | "github.com/pingcap/parser/model" 19 | 20 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 21 | ) 22 | 23 | func (s *SQLSmith) createTableStmt() ast.Node { 24 | createTableNode := ast.CreateTableStmt{ 25 | Table: &ast.TableName{}, 26 | Cols: []*ast.ColumnDef{}, 27 | Constraints: []*ast.Constraint{}, 28 | Options: []*ast.TableOption{}, 29 | } 30 | // TODO: config for enable partition 31 | // partitionStmt is disabled 32 | // createTableNode.Partition = s.partitionStmt() 33 | 34 | return &createTableNode 35 | } 36 | 37 | func (s *SQLSmith) alterTableStmt() ast.Node { 38 | return &ast.AlterTableStmt{ 39 | Table: &ast.TableName{}, 40 | Specs: []*ast.AlterTableSpec{ 41 | s.alterTableSpec(), 42 | }, 43 | } 44 | } 45 | 46 | func (s *SQLSmith) partitionStmt() *ast.PartitionOptions { 47 | return &ast.PartitionOptions{ 48 | PartitionMethod: ast.PartitionMethod{ 49 | ColumnNames: []*ast.ColumnName{}, 50 | }, 51 | Definitions: []*ast.PartitionDefinition{}, 52 | } 53 | } 54 | 55 | func (s *SQLSmith) alterTableSpec() *ast.AlterTableSpec { 56 | switch util.Rd(2) { 57 | case 0: 58 | return s.alterTableSpecAddColumns() 59 | default: 60 | return s.alterTableSpecDropIndex() 61 | } 62 | // switch util.Rd(4) { 63 | // case 0: 64 | // return s.alterTableSpecDropColumn() 65 | // case 1: 66 | // return s.alterTableSpecDropIndex() 67 | // default: 68 | // // return s.alterTableSpecAddColumns() 69 | // return s.alterTableSpecDropColumn() 70 | // } 71 | } 72 | 73 | func (s *SQLSmith) alterTableSpecAddColumns() *ast.AlterTableSpec { 74 | return &ast.AlterTableSpec{ 75 | Tp: ast.AlterTableAddColumns, 76 | NewColumns: []*ast.ColumnDef{{}}, 77 | } 78 | } 79 | 80 | func (s *SQLSmith) alterTableSpecDropIndex() *ast.AlterTableSpec { 81 | return &ast.AlterTableSpec{ 82 | Tp: ast.AlterTableDropIndex, 83 | } 84 | } 85 | 86 | func (s *SQLSmith) alterTableSpecDropColumn() *ast.AlterTableSpec { 87 | return &ast.AlterTableSpec{ 88 | Tp: ast.AlterTableDropColumn, 89 | OldColumnName: &ast.ColumnName{}, 90 | } 91 | } 92 | 93 | func (s *SQLSmith) createIndexStmt() *ast.CreateIndexStmt { 94 | var indexType model.IndexType 95 | switch util.Rd(2) { 96 | case 0: 97 | indexType = model.IndexTypeBtree 98 | default: 99 | indexType = model.IndexTypeHash 100 | } 101 | 102 | node := ast.CreateIndexStmt{ 103 | Table: &ast.TableName{}, 104 | IndexPartSpecifications: []*ast.IndexPartSpecification{}, 105 | IndexOption: &ast.IndexOption{ 106 | Tp: indexType, 107 | }, 108 | } 109 | return &node 110 | } 111 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/ddl_sql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "fmt" 18 | "strings" 19 | 20 | "github.com/pingcap/parser/ast" 21 | "github.com/pingcap/parser/model" 22 | parserTypes "github.com/pingcap/parser/types" 23 | 24 | "github.com/zhouqiang-cl/wreck-it/pkg/generator" 25 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/stateflow" 26 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 27 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 28 | ) 29 | 30 | // CreateTableStmt create table 31 | func (s *SQLSmith) CreateTableStmt(colTypes []string) (string, string, error) { 32 | tree := s.createTableStmt().(*ast.CreateTableStmt) 33 | 34 | table := fmt.Sprintf("%s_%s", "table", strings.Join(colTypes, "_")) 35 | 36 | sf := stateflow.New(s.GetDB(s.currDB), s.stable) 37 | idFieldType := parserTypes.NewFieldType(util.Type2Tp("int")) 38 | idFieldType.Flen = dataType2Len("int") 39 | idCol := &ast.ColumnDef{ 40 | Name: &ast.ColumnName{Name: model.NewCIStr(fmt.Sprintf("id"))}, 41 | Tp: idFieldType, 42 | Options: []*ast.ColumnOption{{Tp: ast.ColumnOptionAutoIncrement}}, 43 | } 44 | tree.Cols = append(tree.Cols, idCol) 45 | sf.MakeConstraintPrimaryKey(tree, &types.Column{Column: "id"}) 46 | 47 | tree.Table.Name = model.NewCIStr(table) 48 | for _, colType := range colTypes { 49 | fieldType := parserTypes.NewFieldType(util.Type2Tp(colType)) 50 | fieldType.Flen = dataType2Len(colType) 51 | tree.Cols = append(tree.Cols, &ast.ColumnDef{ 52 | Name: &ast.ColumnName{Name: model.NewCIStr(fmt.Sprintf("col_%s", colType))}, 53 | Tp: fieldType, 54 | }) 55 | } 56 | stmt, err := util.BufferOut(tree) 57 | return stmt, table, err 58 | } 59 | 60 | // AlterTableStmt alter table 61 | func (s *SQLSmith) AlterTableStmt(opt *generator.DDLOptions) (string, error) { 62 | s.setOnlineOtherTables(opt) 63 | defer s.freeOnlineOtherTables() 64 | tree := s.alterTableStmt() 65 | stmt, _, err := s.Walk(tree) 66 | return stmt, err 67 | } 68 | 69 | // CreateIndexStmt create index 70 | func (s *SQLSmith) CreateIndexStmt(opt *generator.DDLOptions) (string, error) { 71 | s.setOnlineOtherTables(opt) 72 | defer s.freeOnlineOtherTables() 73 | tree := s.createIndexStmt() 74 | stmt, _, err := s.Walk(tree) 75 | return stmt, err 76 | } 77 | 78 | func dataType2Len(t string) int { 79 | switch t { 80 | case "int": 81 | return 16 82 | case "varchar": 83 | return 1023 84 | case "timestamp": 85 | return 255 86 | case "datetime": 87 | return 255 88 | case "text": 89 | return 1023 90 | case "float": 91 | return 64 92 | } 93 | return 16 94 | } 95 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/ddl_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "testing" 18 | ) 19 | 20 | // TestSQLSmith_CreateTable tests create table statement 21 | func TestSQLSmith_CreateTable(t *testing.T) { 22 | ss := New() 23 | ss.LoadSchema([][6]string{}, make(map[string][]string)) 24 | ss.SetDB(dbname) 25 | sql, _, _ := ss.CreateTableStmt() 26 | t.Log(sql) 27 | } 28 | 29 | // TestSQLSmith_AlterTable tests alter table statement 30 | //func TestSQLSmith_AlterTable(t *testing.T) { 31 | // ss := New() 32 | // indexes["users"] = []string{"idx1", "idx2"} 33 | // ss.LoadSchema(schema, indexes) 34 | // ss.SetDB(dbname) 35 | // 36 | // sql, _ := ss.AlterTableStmt(&generator.DDLOptions{OnlineDDL: true}) 37 | // t.Log(sql) 38 | //} 39 | // 40 | //// TestSQLSmith_CreateIndex tests create index statement 41 | //func TestSQLSmith_CreateIndex(t *testing.T) { 42 | // ss := New() 43 | // ss.LoadSchema(schema, indexes) 44 | // ss.SetDB(dbname) 45 | // 46 | // sql, _ := ss.CreateIndexStmt(&generator.DDLOptions{OnlineDDL: true}) 47 | // t.Log(sql) 48 | //} 49 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import "log" 17 | 18 | func (s *SQLSmith) debugPrintln(a ...interface{}) { 19 | if s.debug { 20 | log.Println(a...) 21 | } 22 | } 23 | 24 | func (s *SQLSmith) debugPrintf(f string, a ...interface{}) { 25 | if s.debug { 26 | log.Printf(f, a...) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/dml_ast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "github.com/pingcap/parser/ast" 18 | "github.com/pingcap/parser/opcode" 19 | 20 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 21 | ) 22 | 23 | func (s *SQLSmith) selectStmt(depth int) ast.Node { 24 | selectStmtNode := ast.SelectStmt{ 25 | SelectStmtOpts: &ast.SelectStmtOpts{ 26 | SQLCache: true, 27 | }, 28 | Fields: &ast.FieldList{ 29 | Fields: []*ast.SelectField{}, 30 | }, 31 | OrderBy: &ast.OrderByClause{}, 32 | } 33 | 34 | if depth <= 1 { 35 | complex := 0 36 | if util.Rd(3) == 0 { 37 | complex = 1 38 | } 39 | selectStmtNode.Where = s.binaryOperationExpr(0, complex) 40 | } else { 41 | selectStmtNode.Where = s.binaryOperationExpr(util.Rd(depth), 1) 42 | } 43 | 44 | selectStmtNode.TableHints = s.tableHintsExpr() 45 | selectStmtNode.From = s.tableRefsClause(depth) 46 | 47 | return &selectStmtNode 48 | } 49 | 50 | func (s *SQLSmith) selectForUpdateStmt(depth int) ast.Node { 51 | node := s.selectStmt(depth) 52 | node.(*ast.SelectStmt).LockTp = ast.SelectLockForUpdate 53 | return node 54 | } 55 | 56 | func (s *SQLSmith) updateStmt() ast.Node { 57 | updateStmtNode := ast.UpdateStmt{ 58 | List: []*ast.Assignment{}, 59 | TableRefs: &ast.TableRefsClause{ 60 | TableRefs: &ast.Join{ 61 | Left: &ast.TableName{}, 62 | }, 63 | }, 64 | } 65 | 66 | whereRand := s.rd(10) 67 | if whereRand < 8 { 68 | updateStmtNode.Where = s.binaryOperationExpr(whereRand, 0) 69 | } else { 70 | updateStmtNode.Where = ast.NewValueExpr(1, "", "") 71 | } 72 | 73 | updateStmtNode.TableHints = s.tableHintsExpr() 74 | 75 | return &updateStmtNode 76 | } 77 | 78 | func (s *SQLSmith) insertStmt() ast.Node { 79 | insertStmtNode := ast.InsertStmt{ 80 | Table: &ast.TableRefsClause{ 81 | TableRefs: &ast.Join{ 82 | Left: &ast.TableName{}, 83 | }, 84 | }, 85 | Lists: [][]ast.ExprNode{}, 86 | Columns: []*ast.ColumnName{}, 87 | } 88 | return &insertStmtNode 89 | } 90 | 91 | func (s *SQLSmith) deleteStmt() ast.Node { 92 | deleteStmtNode := ast.DeleteStmt{ 93 | TableRefs: s.tableRefsClause(1), 94 | } 95 | 96 | whereRand := s.rd(10) 97 | if whereRand < 8 { 98 | deleteStmtNode.Where = s.binaryOperationExpr(whereRand, 0) 99 | } else { 100 | deleteStmtNode.Where = ast.NewValueExpr(1, "", "") 101 | } 102 | 103 | deleteStmtNode.TableHints = s.tableHintsExpr() 104 | 105 | return &deleteStmtNode 106 | } 107 | 108 | func (s *SQLSmith) tableHintsExpr() (hints []*ast.TableOptimizerHint) { 109 | if !s.Hint() { 110 | return 111 | } 112 | length := 0 113 | switch n := util.Rd(7)*util.Rd(7) - 17; { 114 | case n <= 0: 115 | length = 0 116 | case n < 4: 117 | length = 1 118 | case n < 9: 119 | length = 2 120 | case n < 14: 121 | length = 3 122 | default: 123 | length = 4 124 | } 125 | for i := 0; i < length; i++ { 126 | hints = append(hints, &ast.TableOptimizerHint{}) 127 | } 128 | return 129 | } 130 | 131 | func (s *SQLSmith) tableRefsClause(depth int) *ast.TableRefsClause { 132 | tableRefsClause := ast.TableRefsClause{ 133 | TableRefs: &ast.Join{ 134 | Left: &ast.TableName{}, 135 | }, 136 | } 137 | 138 | if depth > 1 { 139 | // if s.rd(100) > 50 { 140 | // tableRefsClause.TableRefs.Right = &ast.TableName{} 141 | // } else { 142 | // tableRefsClause.TableRefs.Right = &ast.TableSource{ 143 | // Source: s.selectStmt(depth + 1), 144 | // } 145 | // } 146 | tableRefsClause.TableRefs.Right = &ast.TableSource{ 147 | Source: s.selectStmt(depth - 1), 148 | } 149 | if s.rd(100) > 30 { 150 | tableRefsClause.TableRefs.On = &ast.OnCondition{ 151 | Expr: &ast.BinaryOperationExpr{ 152 | Op: opcode.EQ, 153 | L: &ast.ColumnNameExpr{}, 154 | R: &ast.ColumnNameExpr{}, 155 | }, 156 | } 157 | } 158 | } 159 | 160 | return &tableRefsClause 161 | } 162 | 163 | func (s *SQLSmith) binaryOperationExpr(depth, complex int) ast.ExprNode { 164 | node := ast.BinaryOperationExpr{} 165 | if depth > 0 { 166 | r := util.Rd(4) 167 | switch r { 168 | case 0: 169 | node.Op = opcode.LogicXor 170 | case 1: 171 | node.Op = opcode.LogicOr 172 | default: 173 | node.Op = opcode.LogicAnd 174 | } 175 | node.L = s.binaryOperationExpr(depth-1, complex) 176 | node.R = s.binaryOperationExpr(0, complex) 177 | } else { 178 | if complex > 0 { 179 | switch util.Rd(4) { 180 | case 0: 181 | return s.patternInExpr() 182 | default: 183 | switch util.Rd(4) { 184 | case 0: 185 | node.Op = opcode.GT 186 | case 1: 187 | node.Op = opcode.LT 188 | case 2: 189 | node.Op = opcode.NE 190 | default: 191 | node.Op = opcode.EQ 192 | } 193 | node.L = s.exprNode(false) 194 | node.R = s.exprNode(true) 195 | } 196 | } else { 197 | switch util.Rd(4) { 198 | case 0: 199 | node.Op = opcode.GT 200 | case 1: 201 | node.Op = opcode.LT 202 | case 2: 203 | node.Op = opcode.NE 204 | default: 205 | node.Op = opcode.EQ 206 | } 207 | node.L = &ast.ColumnNameExpr{} 208 | node.R = ast.NewValueExpr(1, "", "") 209 | } 210 | } 211 | return &node 212 | } 213 | 214 | func (s *SQLSmith) patternInExpr() *ast.PatternInExpr { 215 | // expr := s.exprNode() 216 | // switch node := expr.(type) { 217 | // case *ast.SubqueryExpr: 218 | // // may need refine after fully support of ResultSetNode interface 219 | // node.Query.(*ast.SelectStmt).Limit = &ast.Limit { 220 | // Count: ast.NewValueExpr(1, "", ""), 221 | // } 222 | // } 223 | 224 | return &ast.PatternInExpr{ 225 | Expr: &ast.ColumnNameExpr{}, 226 | Sel: s.subqueryExpr(), 227 | } 228 | } 229 | 230 | func (s *SQLSmith) subqueryExpr() *ast.SubqueryExpr { 231 | return &ast.SubqueryExpr{ 232 | Query: s.selectStmt(1), 233 | MultiRows: true, 234 | } 235 | } 236 | 237 | func (s *SQLSmith) exprNode(cons bool) ast.ExprNode { 238 | switch util.Rd(6) { 239 | case 0: 240 | return &ast.ColumnNameExpr{} 241 | case 1: 242 | return s.subqueryExpr() 243 | default: 244 | // hope there is an empty value type 245 | if cons { 246 | return ast.NewValueExpr(1, "", "") 247 | } 248 | return &ast.ColumnNameExpr{} 249 | } 250 | // panic("unhandled switch") 251 | } 252 | 253 | // func (s *SQLSmith) whereExprNode(depth int) ast.ExprNode { 254 | // whereCount := util.Rd(4) 255 | // if whereCount == 0 { 256 | // return nil 257 | // } 258 | // var binaryOperation *ast.BinaryOperationExpr 259 | // for i := 0; i < whereCount; i++ { 260 | // binaryOperation = 261 | // } 262 | // return binaryOperation 263 | // } 264 | 265 | // func (s *SQLSmith) whereExprNode(depth int) 266 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/dml_sql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "github.com/pingcap/parser/ast" 20 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/stateflow" 21 | "strings" 22 | 23 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/builtin" 24 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 25 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 26 | ) 27 | 28 | // SelectStmt make random select statement SQL 29 | func (s *SQLSmith) SelectStmt(depth int) (string, string, error) { 30 | tree := s.selectStmt(depth) 31 | return s.Walk(tree) 32 | } 33 | 34 | // SelectForUpdateStmt make random select statement SQL with for update lock 35 | func (s *SQLSmith) SelectForUpdateStmt(depth int) (string, string, error) { 36 | tree := s.selectForUpdateStmt(depth) 37 | return s.Walk(tree) 38 | } 39 | 40 | // UpdateStmt make random update statement SQL 41 | func (s *SQLSmith) UpdateStmt() (string, string, error) { 42 | tree := s.updateStmt() 43 | return s.Walk(tree) 44 | } 45 | 46 | // InsertStmt implement insert statement from AST 47 | func (s *SQLSmith) InsertStmt(fn bool) (string, string, error) { 48 | tree := s.insertStmt() 49 | return s.Walk(tree) 50 | } 51 | 52 | func (s *SQLSmith) InsertStmtForTable(tableName string) (string, error) { 53 | node := s.insertStmt() 54 | sf := stateflow.New(s.GetDB(s.currDB), s.stable) 55 | sf.WalkInsertStmtForTable(node.(*ast.InsertStmt), tableName) 56 | return util.BufferOut(node) 57 | } 58 | 59 | // DeleteStmt implement delete statement from AST 60 | func (s *SQLSmith) DeleteStmt() (string, string, error) { 61 | tree := s.deleteStmt() 62 | return s.Walk(tree) 63 | } 64 | 65 | // InsertStmtStr make random insert statement SQL 66 | func (s *SQLSmith) InsertStmtStr(fn bool) (string, string, error) { 67 | if s.currDB == "" { 68 | return "", "", errors.New("no table selected") 69 | } 70 | var table *types.Table 71 | rdTableIndex := s.rd(len(s.Databases[s.currDB].Tables)) 72 | tableIndex := 0 73 | for _, t := range s.Databases[s.currDB].Tables { 74 | if rdTableIndex == tableIndex { 75 | table = t 76 | break 77 | } 78 | tableIndex++ 79 | } 80 | 81 | var columns []*types.Column 82 | var columnNames []string 83 | for _, column := range table.Columns { 84 | if column.Column == "id" { 85 | continue 86 | } 87 | columns = append(columns, column) 88 | columnNames = append(columnNames, fmt.Sprintf("`%s`", column.Column)) 89 | } 90 | 91 | var vals []string 92 | for _, column := range columns { 93 | if fn && s.rdFloat64() < 0.5 { 94 | builtinFn := builtin.GenerateFuncCallExpr(nil, s.rd(3), s.stable) 95 | builtinStr, err := util.BufferOut(builtinFn) 96 | if err != nil { 97 | return "", "", err 98 | } 99 | vals = append(vals, builtinStr) 100 | } else { 101 | vals = append(vals, s.generateDataItem(column.DataType)) 102 | } 103 | } 104 | sql := fmt.Sprintf("INSERT INTO %s(%s) VALUES(%s)", 105 | table.Table, 106 | strings.Join(columnNames, ", "), 107 | strings.Join(vals, ", ")) 108 | return sql, table.Table, nil 109 | } 110 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/dml_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "math/rand" 18 | "testing" 19 | ) 20 | 21 | // TestSQLSmith_Select tests select statment 22 | func TestSQLSmith_Select(t *testing.T) { 23 | ss := new() 24 | ss.LoadSchema(schema, indexes) 25 | 26 | ss.SetDB(dbname) 27 | for i := 0; i < 500; i++ { 28 | sql, _, err := ss.SelectStmt(1 + rand.Intn(5)) 29 | if err != nil { 30 | t.Log(sql, err) 31 | } 32 | } 33 | sql, _, _ := ss.SelectStmt(1) 34 | t.Log(sql) 35 | } 36 | 37 | // TestSQLSmith_SelectForUpdateStmt tests select for update statement 38 | func TestSQLSmith_SelectForUpdateStmt(t *testing.T) { 39 | ss := new() 40 | ss.LoadSchema(schema, indexes) 41 | 42 | ss.SetDB(dbname) 43 | for i := 0; i < 500; i++ { 44 | sql, _, err := ss.SelectForUpdateStmt(1 + rand.Intn(5)) 45 | if err != nil { 46 | t.Log(sql, err) 47 | } 48 | } 49 | sql, _, _ := ss.SelectForUpdateStmt(6) 50 | t.Log(sql) 51 | } 52 | 53 | // TestSQLSmith_Update tests update statement 54 | func TestSQLSmith_Update(t *testing.T) { 55 | ss := new() 56 | ss.LoadSchema(schema, indexes) 57 | 58 | ss.SetDB(dbname) 59 | 60 | for i := 0; i < 1000; i++ { 61 | sql, _, err := ss.UpdateStmt() 62 | if err != nil { 63 | t.Log(sql, err) 64 | } 65 | } 66 | sql, _, _ := ss.UpdateStmt() 67 | t.Log(sql) 68 | } 69 | 70 | // TestSQLSmith_Insert tests insert statment 71 | func TestSQLSmith_Insert(t *testing.T) { 72 | ss := new() 73 | ss.LoadSchema(schema, indexes) 74 | 75 | ss.SetDB(dbname) 76 | 77 | for i := 0; i < 1000; i++ { 78 | sql, _, err := ss.InsertStmt(false) 79 | if err != nil { 80 | t.Log(sql, err) 81 | } 82 | } 83 | sql, _, err := ss.InsertStmt(false) 84 | t.Log(sql, err) 85 | } 86 | 87 | // TestSQLSmith_Delete tests delete statment 88 | func TestSQLSmith_Delete(t *testing.T) { 89 | ss := new() 90 | ss.LoadSchema(schema, indexes) 91 | 92 | ss.SetDB(dbname) 93 | 94 | for i := 0; i < 1000; i++ { 95 | sql, _, err := ss.DeleteStmt() 96 | if err != nil { 97 | t.Log(sql, err) 98 | } 99 | } 100 | sql, _, err := ss.DeleteStmt() 101 | t.Log(sql, err) 102 | } 103 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/rand.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "fmt" 18 | "time" 19 | ) 20 | 21 | func (s *SQLSmith) rd(n int) int { 22 | return s.Rand.Intn(n) 23 | } 24 | 25 | func (s *SQLSmith) rdRange(n, m int) int { 26 | if m < n { 27 | n, m = m, n 28 | } 29 | return n + s.Rand.Intn(m-n) 30 | } 31 | 32 | func (s *SQLSmith) rdFloat64() float64 { 33 | return s.Rand.Float64() 34 | } 35 | 36 | func (s *SQLSmith) rdDate() time.Time { 37 | min := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC).Unix() 38 | max := time.Date(2100, 1, 0, 0, 0, 0, 0, time.UTC).Unix() 39 | delta := max - min 40 | 41 | sec := s.Rand.Int63n(delta) + min 42 | return time.Unix(sec, 0) 43 | } 44 | 45 | func (s *SQLSmith) getSubTableName() string { 46 | name := fmt.Sprintf("ss_sub_%d", s.subTableIndex) 47 | s.subTableIndex++ 48 | return name 49 | } 50 | 51 | func (s *SQLSmith) rdString(length int) string { 52 | res := "" 53 | for i := 0; i < length; i++ { 54 | charCode := s.rdRange(33, 127) 55 | // char '\' and '"' should be escaped 56 | if charCode == 92 || charCode == 34 { 57 | res = fmt.Sprintf("%s%s", res, "\\") 58 | } 59 | res = fmt.Sprintf("%s%s", res, string(rune(charCode))) 60 | } 61 | return res 62 | } 63 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "github.com/pingcap/parser/ast" 18 | "regexp" 19 | "strconv" 20 | "strings" 21 | 22 | "github.com/zhouqiang-cl/wreck-it/pkg/generator" 23 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 24 | ) 25 | 26 | const typeRegex = `\(\d+\)` 27 | 28 | // LoadSchema init schemas, tables and columns 29 | // record[0] dbname 30 | // record[1] table name 31 | // record[2] table type 32 | // record[3] column name 33 | // record[4] column type 34 | func (s *SQLSmith) LoadSchema(records [][6]string, indexes map[string][]string) { 35 | // init databases 36 | for _, record := range records { 37 | dbname := record[0] 38 | tableName := record[1] 39 | tableType := record[2] 40 | columnName := record[3] 41 | columnType := record[4] 42 | options := make([]ast.ColumnOptionType, 0) 43 | if record[5] == "NO" { 44 | options = append(options, ast.ColumnOptionNotNull) 45 | } 46 | index, ok := indexes[tableName] 47 | if !ok { 48 | index = []string{} 49 | } 50 | if _, ok := s.Databases[dbname]; !ok { 51 | s.Databases[dbname] = &types.Database{ 52 | Name: dbname, 53 | Tables: make(map[string]*types.Table), 54 | } 55 | } 56 | if _, ok := s.Databases[dbname].Tables[tableName]; !ok { 57 | s.Databases[dbname].Tables[tableName] = &types.Table{ 58 | DB: dbname, 59 | Table: tableName, 60 | Type: tableType, 61 | Columns: make(map[string]*types.Column), 62 | Indexes: index, 63 | } 64 | } 65 | if _, ok := s.Databases[dbname].Tables[tableName].Columns[columnName]; !ok { 66 | dataLenReg := regexp.MustCompile(typeRegex) 67 | dataLengthStr := dataLenReg.FindString(columnType) 68 | length := 0 69 | if dataLengthStr != "" { 70 | length, _ = strconv.Atoi(dataLengthStr[1 : len(dataLengthStr)-1]) 71 | } 72 | s.Databases[dbname].Tables[tableName].Columns[columnName] = &types.Column{ 73 | DB: dbname, 74 | Table: tableName, 75 | Column: columnName, 76 | // remove the data size in type definition 77 | DataType: dataLenReg.ReplaceAllString(strings.ToLower(columnType), ""), 78 | DataLen: length, 79 | Options: options, 80 | } 81 | } 82 | } 83 | } 84 | 85 | // BeginWithOnlineTables begins a transaction with some online tables 86 | func (s *SQLSmith) BeginWithOnlineTables(opt *generator.DMLOptions) []string { 87 | if !opt.OnlineTable { 88 | return []string{} 89 | } 90 | var ( 91 | res = []string{} 92 | db = s.GetDB(s.GetCurrDBName()) 93 | tables = db.RandTables() 94 | ) 95 | for _, t := range tables { 96 | res = append(res, t.Table) 97 | t.Online = true 98 | } 99 | db.Online = true 100 | return res 101 | } 102 | 103 | // EndTransaction ends transaction and set every table offline 104 | func (s *SQLSmith) EndTransaction() []string { 105 | var ( 106 | res = []string{} 107 | db = s.GetDB(s.GetCurrDBName()) 108 | tables = db.Tables 109 | ) 110 | for _, t := range tables { 111 | res = append(res, t.Table) 112 | t.Online = false 113 | } 114 | db.Online = false 115 | return res 116 | } 117 | 118 | // setOnlineOtherTables is for avoiding online DDL 119 | func (s *SQLSmith) setOnlineOtherTables(opt *generator.DDLOptions) { 120 | if opt.OnlineDDL { 121 | return 122 | } 123 | db := s.GetDB(s.currDB) 124 | for _, table := range opt.Tables { 125 | if db.Tables[table] != nil { 126 | db.Tables[table].OnlineOther = true 127 | } 128 | } 129 | } 130 | 131 | // freeOnlineOtherTables clear online tables obtain by other instances 132 | func (s *SQLSmith) freeOnlineOtherTables() { 133 | db := s.GetDB(s.currDB) 134 | for _, table := range db.Tables { 135 | table.OnlineOther = false 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/schema_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | // test schema 23 | var ( 24 | schema = [][5]string{ 25 | {"community", "comments", "BASE TABLE", "id", "int(11)"}, 26 | {"community", "comments", "BASE TABLE", "owner", "varchar(255)"}, 27 | {"community", "comments", "BASE TABLE", "repo", "varchar(255)"}, 28 | {"community", "comments", "BASE TABLE", "comment_id", "int(11)"}, 29 | {"community", "comments", "BASE TABLE", "comment_type", "varchar(128)"}, 30 | {"community", "comments", "BASE TABLE", "pull_number", "int(11)"}, 31 | {"community", "comments", "BASE TABLE", "body", "text"}, 32 | {"community", "comments", "BASE TABLE", "user", "varchar(255)"}, 33 | {"community", "comments", "BASE TABLE", "url", "varchar(1023)"}, 34 | {"community", "comments", "BASE TABLE", "association", "varchar(255)"}, 35 | {"community", "comments", "BASE TABLE", "relation", "varchar(255)"}, 36 | {"community", "comments", "BASE TABLE", "created_at", "timestamp"}, 37 | {"community", "comments", "BASE TABLE", "updated_at", "timestamp"}, 38 | {"community", "picks", "BASE TABLE", "id", "int(11)"}, 39 | {"community", "picks", "BASE TABLE", "season", "int(11)"}, 40 | {"community", "picks", "BASE TABLE", "task_id", "int(11)"}, 41 | {"community", "picks", "BASE TABLE", "teamID", "int(11)"}, 42 | {"community", "picks", "BASE TABLE", "user", "varchar(255)"}, 43 | {"community", "picks", "BASE TABLE", "pull_number", "int(11)"}, 44 | {"community", "picks", "BASE TABLE", "status", "varchar(128)"}, 45 | {"community", "picks", "BASE TABLE", "created_at", "timestamp"}, 46 | {"community", "picks", "BASE TABLE", "updated_at", "timestamp"}, 47 | {"community", "picks", "BASE TABLE", "closed_at", "datetime"}, 48 | {"community", "pulls", "BASE TABLE", "id", "int(11)"}, 49 | {"community", "pulls", "BASE TABLE", "owner", "varchar(255)"}, 50 | {"community", "pulls", "BASE TABLE", "repo", "varchar(255)"}, 51 | {"community", "pulls", "BASE TABLE", "pull_number", "int(11)"}, 52 | {"community", "pulls", "BASE TABLE", "title", "text"}, 53 | {"community", "pulls", "BASE TABLE", "body", "text"}, 54 | {"community", "pulls", "BASE TABLE", "user", "varchar(255)"}, 55 | {"community", "pulls", "BASE TABLE", "association", "varchar(255)"}, 56 | {"community", "pulls", "BASE TABLE", "relation", "varchar(255)"}, 57 | {"community", "pulls", "BASE TABLE", "label", "text"}, 58 | {"community", "pulls", "BASE TABLE", "status", "varchar(128)"}, 59 | {"community", "pulls", "BASE TABLE", "created_at", "timestamp"}, 60 | {"community", "pulls", "BASE TABLE", "updated_at", "timestamp"}, 61 | {"community", "pulls", "BASE TABLE", "closed_at", "datetime"}, 62 | {"community", "pulls", "BASE TABLE", "merged_at", "datetime"}, 63 | {"community", "tasks", "BASE TABLE", "id", "int(11)"}, 64 | {"community", "tasks", "BASE TABLE", "season", "int(11)"}, 65 | {"community", "tasks", "BASE TABLE", "complete_user", "varchar(255)"}, 66 | {"community", "tasks", "BASE TABLE", "complete_team", "int(11)"}, 67 | {"community", "tasks", "BASE TABLE", "owner", "varchar(255)"}, 68 | {"community", "tasks", "BASE TABLE", "repo", "varchar(255)"}, 69 | {"community", "tasks", "BASE TABLE", "title", "varchar(2047)"}, 70 | {"community", "tasks", "BASE TABLE", "issue_number", "int(11)"}, 71 | {"community", "tasks", "BASE TABLE", "pull_number", "int(11)"}, 72 | {"community", "tasks", "BASE TABLE", "level", "varchar(255)"}, 73 | {"community", "tasks", "BASE TABLE", "min_score", "int(11)"}, 74 | {"community", "tasks", "BASE TABLE", "score", "int(11)"}, 75 | {"community", "tasks", "BASE TABLE", "status", "varchar(255)"}, 76 | {"community", "tasks", "BASE TABLE", "created_at", "timestamp"}, 77 | {"community", "tasks", "BASE TABLE", "expired", "varchar(255)"}, 78 | {"community", "teams", "BASE TABLE", "id", "int(11)"}, 79 | {"community", "teams", "BASE TABLE", "season", "int(11)"}, 80 | {"community", "teams", "BASE TABLE", "name", "varchar(255)"}, 81 | {"community", "teams", "BASE TABLE", "issue_url", "varchar(1023)"}, 82 | {"community", "users", "BASE TABLE", "id", "int(11)"}, 83 | {"community", "users", "BASE TABLE", "season", "int(11)"}, 84 | {"community", "users", "BASE TABLE", "user", "varchar(255)"}, 85 | {"community", "users", "BASE TABLE", "team_id", "int(11)"}, 86 | } 87 | dbname = "community" 88 | indexes = make(map[string][]string) 89 | tables = []string{"comments", "picks", "pulls", "tasks", "teams", "users"} 90 | ) 91 | 92 | // TestSQLSmith_LoadIndexes tests with load table indexes 93 | func TestSQLSmith_LoadIndexes(t *testing.T) { 94 | ss := new() 95 | indexes["users"] = []string{"idx1", "idx2"} 96 | ss.LoadSchema(schema, indexes) 97 | ss.SetDB(dbname) 98 | 99 | assert.Equal(t, len(ss.Databases[dbname].Tables), 6) 100 | assert.Equal(t, len(ss.Databases[dbname].Tables["users"].Indexes), 2) 101 | } 102 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/sqlsmith.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "math/rand" 18 | "time" 19 | 20 | "github.com/juju/errors" 21 | "github.com/pingcap/parser/ast" 22 | 23 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/stateflow" 24 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 25 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 26 | // _ "github.com/pingcap/tidb/types/parser_driver" 27 | ) 28 | 29 | // SQLSmith defines SQLSmith struct 30 | type SQLSmith struct { 31 | depth int 32 | maxDepth int 33 | Rand *rand.Rand 34 | Databases map[string]*types.Database 35 | subTableIndex int 36 | Node ast.Node 37 | currDB string 38 | debug bool 39 | stable bool 40 | hint bool 41 | } 42 | 43 | // New create SQLSmith instance 44 | func New() *SQLSmith { 45 | return new() 46 | } 47 | 48 | func new() *SQLSmith { 49 | return &SQLSmith{ 50 | Rand: rand.New(rand.NewSource(time.Now().UnixNano())), 51 | Databases: make(map[string]*types.Database), 52 | } 53 | } 54 | 55 | // Debug turn on debug mode 56 | func (s *SQLSmith) Debug() { 57 | s.debug = true 58 | } 59 | 60 | // SetDB set current database 61 | func (s *SQLSmith) SetDB(db string) { 62 | s.currDB = db 63 | } 64 | 65 | // GetCurrDBName returns current selected dbname 66 | func (s *SQLSmith) GetCurrDBName() string { 67 | return s.currDB 68 | } 69 | 70 | // GetDB get current database without nil 71 | func (s *SQLSmith) GetDB(db string) *types.Database { 72 | if db, ok := s.Databases[db]; ok { 73 | return db 74 | } 75 | return &types.Database{ 76 | Name: db, 77 | } 78 | } 79 | 80 | // Stable set generated SQLs no rand 81 | func (s *SQLSmith) Stable() { 82 | s.stable = true 83 | } 84 | 85 | // SetStable set stable to given value 86 | func (s *SQLSmith) SetStable(stable bool) { 87 | s.stable = stable 88 | } 89 | 90 | // Hint ... 91 | func (s *SQLSmith) Hint() bool { 92 | return s.hint 93 | } 94 | 95 | // SetHint ... 96 | func (s *SQLSmith) SetHint(hint bool) { 97 | s.hint = hint 98 | } 99 | 100 | // WalkRaw will walk the tree and fillin tables and columns data 101 | func (s *SQLSmith) WalkRaw(tree ast.Node) (string, *types.Table, error) { 102 | //node, table, err := 103 | table, err := stateflow.New(s.GetDB(s.currDB), s.stable).WalkTreeRaw(tree) 104 | if err != nil { 105 | return "", nil, errors.Trace(err) 106 | } 107 | s.debugPrintf("node AST %+v\n", tree) 108 | sql, err := util.BufferOut(tree) 109 | return sql, table, errors.Trace(err) 110 | } 111 | 112 | // Walk will walk the tree and fillin tables and columns data 113 | func (s *SQLSmith) Walk(tree ast.Node) (string, string, error) { 114 | sql, table, err := s.WalkRaw(tree) 115 | name := "" 116 | if table != nil { 117 | name = table.Table 118 | } 119 | return sql, name, err 120 | } 121 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/sqlsmith_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import "testing" 17 | 18 | func TestSQLSmith_ToSQL(t *testing.T) { 19 | ss := New() 20 | ss.LoadSchema(schema, indexes) 21 | 22 | ss.SetDB(dbname) 23 | 24 | ss.SetDB("community") 25 | sql, _, _ := ss.SelectStmt(3) 26 | t.Log(sql) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/stateflow/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stateflow 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/pingcap/parser/ast" 20 | 21 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 22 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 23 | ) 24 | 25 | func (s *StateFlow) randTableFromTable(table *types.Table, newName bool, fn bool) *types.Table { 26 | newTableName := "" 27 | if newName { 28 | newTableName = s.getSubTableName() 29 | } 30 | columns := s.randColumns(table) 31 | newTable := types.Table{ 32 | DB: table.DB, 33 | Type: table.Type, 34 | Columns: make(map[string]*types.Column), 35 | } 36 | if newName { 37 | newTable.OriginTable = table.Table 38 | newTable.Table = newTableName 39 | } else { 40 | newTable.Table = table.Table 41 | } 42 | for _, column := range columns { 43 | newColumn := &types.Column{ 44 | DB: column.DB, 45 | Table: column.Table, 46 | OriginTable: column.OriginTable, 47 | Column: column.Column, 48 | OriginColumn: column.OriginColumn, 49 | DataType: column.DataType, 50 | Func: column.Func, 51 | } 52 | if newName { 53 | newColumn.Table = newTableName 54 | } else { 55 | newColumn.Table = table.Table 56 | } 57 | newTable.Columns[newColumn.Column] = newColumn 58 | } 59 | if fn { 60 | r := util.Rd(4) 61 | for i := 0; i < r; i++ { 62 | k := fmt.Sprintf("f%d", i) 63 | newTable.Columns[k] = &types.Column{ 64 | DB: newTable.DB, 65 | Table: newTable.Table, 66 | Column: k, 67 | DataType: "func", 68 | Func: true, 69 | NewFunc: true, 70 | } 71 | } 72 | } 73 | return &newTable 74 | } 75 | 76 | func (s *StateFlow) randTable(newName bool, fn bool, online bool) *types.Table { 77 | tables := []*types.Table{} 78 | for _, table := range s.db.Tables { 79 | // get online tables 80 | if !online || !s.db.Online || table.Online { 81 | tables = append(tables, table) 82 | } 83 | } 84 | 85 | if len(tables) == 0 { 86 | // return nil 87 | // FIXME: nil not panic 88 | for _, table := range s.db.Tables { 89 | tables = append(tables, table) 90 | } 91 | } 92 | 93 | return tables[util.Rd(len(tables))].Clone() 94 | } 95 | 96 | func (s *StateFlow) randOfflineTable(newName bool, fn bool) *types.Table { 97 | tables := []*types.Table{} 98 | for _, table := range s.db.Tables { 99 | // avoid online tables 100 | if !table.OnlineOther { 101 | tables = append(tables, table) 102 | } 103 | } 104 | 105 | if len(tables) == 0 { 106 | return nil 107 | } 108 | return tables[util.Rd(len(tables))].Clone() 109 | } 110 | 111 | func (s *StateFlow) randOriginTable() *types.Table { 112 | tables := s.db.Tables 113 | index := 0 114 | k := util.Rd(len(tables)) 115 | for _, table := range tables { 116 | if index == k { 117 | return table.Clone() 118 | } 119 | index++ 120 | } 121 | return nil 122 | } 123 | 124 | func (s *StateFlow) randColumns(table *types.Table) []*types.Column { 125 | var columns []*types.Column 126 | rate := float64(0.75) 127 | for _, column := range table.Columns { 128 | if util.RdFloat64() < rate { 129 | columns = append(columns, column) 130 | } 131 | } 132 | return columns 133 | } 134 | 135 | func (s *StateFlow) mergeTable(table1 *types.Table, table2 *types.Table) (*types.Table, [2]*types.Column) { 136 | subTableName := s.getSubTableName() 137 | table := types.Table{ 138 | DB: table1.DB, 139 | Table: subTableName, 140 | Type: "SUB TABLE", 141 | Columns: make(map[string]*types.Column), 142 | } 143 | 144 | var onColumns [2]*types.Column 145 | index := 0 146 | 147 | for _, column := range table1.Columns { 148 | newColumn := types.Column{ 149 | DB: column.DB, 150 | Table: subTableName, 151 | OriginTable: column.Table, 152 | DataType: column.DataType, 153 | Column: fmt.Sprintf("c%d", index), 154 | OriginColumn: column.Column, 155 | } 156 | if column.NewFunc { 157 | newColumn.Func = column.Func 158 | } 159 | table.Columns[fmt.Sprintf("c%d", index)] = &newColumn 160 | index++ 161 | } 162 | for _, column := range table2.Columns { 163 | newColumn := types.Column{ 164 | DB: column.DB, 165 | Table: subTableName, 166 | OriginTable: column.Table, 167 | DataType: column.DataType, 168 | Column: fmt.Sprintf("c%d", index), 169 | OriginColumn: column.Column, 170 | } 171 | if column.NewFunc { 172 | newColumn.Func = column.Func 173 | } 174 | table.Columns[fmt.Sprintf("c%d", index)] = &newColumn 175 | index++ 176 | } 177 | 178 | for _, column1 := range table1.Columns { 179 | for _, column2 := range table2.Columns { 180 | if column1.DataType == column2.DataType { 181 | onColumns[0] = column1 182 | onColumns[1] = column2 183 | return &table, onColumns 184 | } 185 | } 186 | } 187 | 188 | return &table, onColumns 189 | } 190 | 191 | func (s *StateFlow) renameTable(table *types.Table) *types.Table { 192 | newName := s.getSubTableName() 193 | table.OriginTable = table.Table 194 | table.Table = newName 195 | for _, column := range table.Columns { 196 | column.Table = newName 197 | } 198 | return table 199 | } 200 | 201 | func (s *StateFlow) randNewTable() *types.Table { 202 | table := types.Table{ 203 | DB: s.db.Name, 204 | Table: util.RdStringChar(util.RdRange(5, 10)), 205 | Type: "BASE TABLE", 206 | Columns: make(map[string]*types.Column), 207 | } 208 | table.Columns["id"] = &types.Column{ 209 | DB: table.DB, 210 | Table: table.Table, 211 | Column: "id", 212 | DataType: "int", 213 | DataLen: 11, 214 | } 215 | table.Columns["id"].AddOption(ast.ColumnOptionNotNull) 216 | table.Columns["id"].AddOption(ast.ColumnOptionAutoIncrement) 217 | table.Columns["uuid"] = &types.Column{ 218 | DB: table.DB, 219 | Table: table.Table, 220 | Column: "uuid", 221 | DataType: "varchar", 222 | DataLen: 253, 223 | } 224 | columnCount := util.RdRange(1, 20) 225 | for i := 0; i < columnCount; i++ { 226 | col := s.randNewColumn() 227 | col.DB = table.DB 228 | col.Table = table.Table 229 | table.Columns[col.Column] = col 230 | } 231 | 232 | return &table 233 | } 234 | 235 | // TIMESTAMP with CURRENT_TIMESTAMP in ALTER TABLE will make difference by design in binlog test 236 | func (s *StateFlow) randNewColumn() *types.Column { 237 | columnName := util.RdStringChar(util.RdRange(5, 10)) 238 | columnType := util.RdType() 239 | columnLen := util.RdDataLen(columnType) 240 | columnOptions := util.RdColumnOptions(columnType) 241 | if s.stable { 242 | for i, o := range columnOptions { 243 | if o == ast.ColumnOptionDefaultValue { 244 | columnOptions = append(columnOptions[:i], columnOptions[i+1:]...) 245 | } 246 | } 247 | } 248 | return &types.Column{ 249 | Column: columnName, 250 | DataType: columnType, 251 | DataLen: columnLen, 252 | Options: columnOptions, 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/stateflow/stateflow.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stateflow 15 | 16 | import ( 17 | "math/rand" 18 | "time" 19 | 20 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 21 | ) 22 | 23 | // StateFlow defines the struct 24 | type StateFlow struct { 25 | // db is a ref took from SQLSmith, should never change it 26 | db *types.Database 27 | rand *rand.Rand 28 | tableIndex int 29 | stable bool 30 | } 31 | 32 | // New Create StateFlow 33 | func New(db *types.Database, stable bool) *StateFlow { 34 | // copy whole db here may cost time, but ensure the global safety 35 | // maybe a future TODO 36 | return &StateFlow{ 37 | db: db, 38 | rand: rand.New(rand.NewSource(time.Now().UnixNano())), 39 | stable: stable, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/stateflow/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stateflow 15 | 16 | import ( 17 | "fmt" 18 | "runtime/debug" 19 | ) 20 | 21 | func (s *StateFlow) getSubTableName() string { 22 | name := fmt.Sprintf("s%d", s.tableIndex) 23 | s.tableIndex++ 24 | return name 25 | } 26 | 27 | func (s *StateFlow) shouldNotWalkHere(a ...interface{}) { 28 | fmt.Println("should not walk here") 29 | fmt.Println(a...) 30 | debug.PrintStack() 31 | } 32 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/stateflow/walker_ddl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stateflow 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/cznic/mathutil" 20 | "github.com/juju/errors" 21 | "github.com/pingcap/parser/ast" 22 | "github.com/pingcap/parser/model" 23 | parserTypes "github.com/pingcap/parser/types" 24 | tidbTypes "github.com/pingcap/tidb/types" 25 | driver "github.com/pingcap/tidb/types/parser_driver" 26 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/data_gen" 27 | 28 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/types" 29 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 30 | ) 31 | 32 | const ( 33 | timeParseFormat = "2006-01-02 15:04:05" 34 | ) 35 | 36 | var ( 37 | intPartition = []int64{1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11} 38 | datetimePartition = []string{"1980-01-01 00:00:00", "1990-01-01 00:00:00", "2000-01-01 00:00:00", "2010-01-01 00:00:00", "2020-01-01 00:00:00"} 39 | timestampPartition = []string{"1980-01-01 00:00:00", "1990-01-01 00:00:00", "2000-01-01 00:00:00", "2010-01-01 00:00:00", "2020-01-01 00:00:00"} 40 | ) 41 | 42 | func (s *StateFlow) walkCreateTableStmt(node *ast.CreateTableStmt) *types.Table { 43 | table := s.randNewTable() 44 | for _, column := range table.Columns { 45 | node.Cols = append(node.Cols, &ast.ColumnDef{ 46 | Name: &ast.ColumnName{ 47 | Name: model.NewCIStr(column.Column), 48 | }, 49 | Tp: s.makeFieldType(column.DataType, column.DataLen), 50 | Options: s.makeColumnOptions(column, column.Options), 51 | }) 52 | if column.HasOption(ast.ColumnOptionAutoIncrement) { 53 | s.makeConstraintPrimaryKey(node, column) 54 | } 55 | } 56 | node.Table.Name = model.NewCIStr(table.Table) 57 | s.walkTableOption(node) 58 | if column := table.RandColumn(); node.Partition != nil && column != nil { 59 | s.walkPartition(node.Partition, column) 60 | if column.Column != "id" { 61 | s.makeConstraintPrimaryKey(node, column) 62 | } 63 | } 64 | return table 65 | } 66 | 67 | func (s *StateFlow) walkAlterTableStmt(node *ast.AlterTableStmt) *types.Table { 68 | table := s.randTable(false, false, false) 69 | node.Table.Name = model.NewCIStr(table.Table) 70 | // we support only one spec now 71 | // unless TiDB will print error 72 | // ERROR 8200 (HY000): Unsupported multi schema change 73 | switch node.Specs[0].Tp { 74 | case ast.AlterTableAddColumns: 75 | { 76 | s.alterTableSpecAddColumns(node.Specs[0], table) 77 | } 78 | case ast.AlterTableDropColumn: 79 | { 80 | s.alterTableSpecDropColumn(node.Specs[0], table) 81 | } 82 | case ast.AlterTableDropIndex: 83 | { 84 | s.alterTableSpecDropIndex(node.Specs[0], table) 85 | } 86 | } 87 | return table 88 | } 89 | 90 | func (s *StateFlow) walkCreateIndexStmt(node *ast.CreateIndexStmt) (*types.Table, error) { 91 | table := s.randTable(false, false, false) 92 | if table == nil { 93 | return nil, errors.New("no table available") 94 | } 95 | node.Table.Name = model.NewCIStr(table.Table) 96 | node.IndexName = util.RdStringChar(5) 97 | for _, column := range table.Columns { 98 | name := column.Column 99 | if column.DataType == "text" { 100 | length := s.rand.Intn(31) + 1 101 | name = fmt.Sprintf("%s(%d)", name, length) 102 | } else if column.DataType == "varchar" { 103 | length := 1 104 | if column.DataLen > 1 { 105 | maxLen := mathutil.Min(column.DataLen, 32) 106 | length = s.rand.Intn(maxLen-1) + 1 107 | } 108 | name = fmt.Sprintf("%s(%d)", name, length) 109 | } 110 | node.IndexPartSpecifications = append(node.IndexPartSpecifications, 111 | &ast.IndexPartSpecification{ 112 | Column: &ast.ColumnName{ 113 | Name: model.NewCIStr(name), 114 | }, 115 | }) 116 | } 117 | if len(node.IndexPartSpecifications) > 10 { 118 | node.IndexPartSpecifications = node.IndexPartSpecifications[:s.rand.Intn(10)+1] 119 | } 120 | 121 | return table, nil 122 | } 123 | 124 | func (s *StateFlow) makeFieldType(t string, l int) *parserTypes.FieldType { 125 | fieldType := parserTypes.NewFieldType(util.Type2Tp(t)) 126 | fieldType.Flen = l 127 | return fieldType 128 | } 129 | 130 | func (s *StateFlow) makeColumnOptions(column *types.Column, options []ast.ColumnOptionType) (columnOptions []*ast.ColumnOption) { 131 | for _, opt := range options { 132 | columnOptions = append(columnOptions, s.makeColumnOption(column, opt)) 133 | } 134 | return 135 | } 136 | 137 | func (s *StateFlow) makeColumnOption(column *types.Column, option ast.ColumnOptionType) *ast.ColumnOption { 138 | columnOption := ast.ColumnOption{ 139 | Tp: option, 140 | } 141 | if option == ast.ColumnOptionDefaultValue { 142 | // columnOption.Expr = builtin.GenerateTypeFuncCallExpr(column.DataType) 143 | node := driver.ValueExpr{} 144 | s.walkValueExpr(&node, nil, column) 145 | columnOption.Expr = &node 146 | } 147 | return &columnOption 148 | } 149 | 150 | // MakeConstraintPrimaryKey expose makeConstraintPrimaryKey 151 | func (s *StateFlow) MakeConstraintPrimaryKey(node *ast.CreateTableStmt, column *types.Column) { 152 | s.makeConstraintPrimaryKey(node, column) 153 | } 154 | 155 | // makeConstraintPromaryKey is for convenience 156 | func (s *StateFlow) makeConstraintPrimaryKey(node *ast.CreateTableStmt, column *types.Column) { 157 | for _, constraint := range node.Constraints { 158 | if constraint.Tp == ast.ConstraintPrimaryKey { 159 | constraint.Keys = append(constraint.Keys, &ast.IndexPartSpecification{ 160 | Column: &ast.ColumnName{ 161 | Name: model.NewCIStr(column.Column), 162 | }, 163 | }) 164 | return 165 | } 166 | } 167 | node.Constraints = append(node.Constraints, &ast.Constraint{ 168 | Tp: ast.ConstraintPrimaryKey, 169 | Keys: []*ast.IndexPartSpecification{ 170 | { 171 | Column: &ast.ColumnName{ 172 | Name: model.NewCIStr(column.Column), 173 | }, 174 | }, 175 | }, 176 | }) 177 | } 178 | 179 | func (s *StateFlow) walkTableOption(node *ast.CreateTableStmt) { 180 | node.Options = append(node.Options, &ast.TableOption{ 181 | Tp: ast.TableOptionEngine, 182 | StrValue: "InnoDB", 183 | }) 184 | node.Options = append(node.Options, &ast.TableOption{ 185 | Tp: ast.TableOptionCharset, 186 | StrValue: util.RdCharset(), 187 | }) 188 | } 189 | 190 | func (s *StateFlow) walkPartition(node *ast.PartitionOptions, column *types.Column) { 191 | // set partition Tp 192 | node.Tp = model.PartitionTypeRange 193 | 194 | // set to int func 195 | var funcCallNode = new(ast.FuncCallExpr) 196 | switch column.DataType { 197 | case "timestamp": 198 | funcCallNode.FnName = model.NewCIStr("UNIX_TIMESTAMP") 199 | case "datetime": 200 | funcCallNode.FnName = model.NewCIStr("TO_DAYS") 201 | case "varchar", "text": 202 | funcCallNode.FnName = model.NewCIStr("ASCII") 203 | } 204 | 205 | // partition by column 206 | partitionByFuncCall := funcCallNode 207 | if funcCallNode.FnName.String() == "" { 208 | node.Expr = &ast.ColumnNameExpr{ 209 | Name: &ast.ColumnName{ 210 | Name: model.NewCIStr(column.Column), 211 | }, 212 | } 213 | } else { 214 | partitionByFuncCall.Args = []ast.ExprNode{ 215 | &ast.ColumnNameExpr{ 216 | Name: &ast.ColumnName{ 217 | Name: model.NewCIStr(column.Column), 218 | }, 219 | }, 220 | } 221 | node.Expr = partitionByFuncCall 222 | } 223 | 224 | // set partition definitions 225 | s.walkPartitionDefinitions(&node.Definitions, column) 226 | } 227 | 228 | func (s *StateFlow) walkPartitionDefinitions(definitions *[]*ast.PartitionDefinition, column *types.Column) { 229 | switch column.DataType { 230 | case "int": 231 | s.walkPartitionDefinitionsInt(definitions) 232 | case "varchar", "text": 233 | s.walkPartitionDefinitionsString(definitions) 234 | case "datetime": 235 | s.walkPartitionDefinitionsDatetime(definitions) 236 | case "timestamp": 237 | s.walkPartitionDefinitionsTimestamp(definitions) 238 | } 239 | 240 | *definitions = append(*definitions, &ast.PartitionDefinition{ 241 | Name: model.NewCIStr("pn"), 242 | Clause: &ast.PartitionDefinitionClauseLessThan{ 243 | Exprs: []ast.ExprNode{ 244 | &ast.MaxValueExpr{}, 245 | }, 246 | }, 247 | }) 248 | } 249 | 250 | func (s *StateFlow) walkPartitionDefinitionsInt(definitions *[]*ast.PartitionDefinition) { 251 | for i := 0; i < len(intPartition); i += util.RdRange(1, 3) { 252 | val := driver.ValueExpr{} 253 | val.SetInt64(intPartition[i]) 254 | *definitions = append(*definitions, &ast.PartitionDefinition{ 255 | Name: model.NewCIStr(fmt.Sprintf("p%d", i)), 256 | Clause: &ast.PartitionDefinitionClauseLessThan{ 257 | Exprs: []ast.ExprNode{ 258 | &val, 259 | }, 260 | }, 261 | }) 262 | } 263 | } 264 | 265 | func (s *StateFlow) walkPartitionDefinitionsString(definitions *[]*ast.PartitionDefinition) { 266 | for i := 0; i < 256; i += util.RdRange(1, 10) { 267 | val := driver.ValueExpr{} 268 | val.SetInt64(int64(i)) 269 | *definitions = append(*definitions, &ast.PartitionDefinition{ 270 | Name: model.NewCIStr(fmt.Sprintf("p%d", i)), 271 | Clause: &ast.PartitionDefinitionClauseLessThan{ 272 | Exprs: []ast.ExprNode{ 273 | &ast.FuncCallExpr{ 274 | FnName: model.NewCIStr("ASCII"), 275 | Args: []ast.ExprNode{&val}, 276 | }, 277 | }, 278 | }, 279 | }) 280 | } 281 | } 282 | 283 | func (s *StateFlow) walkPartitionDefinitionsDatetime(definitions *[]*ast.PartitionDefinition) { 284 | for i := 0; i < len(datetimePartition); i += util.RdRange(1, 3) { 285 | val := driver.ValueExpr{} 286 | val.SetMysqlTime(tidbTypes.NewTime(tidbTypes.FromGoTime(util.TimeMustParse(timeParseFormat, datetimePartition[i])), 0, 0)) 287 | *definitions = append(*definitions, &ast.PartitionDefinition{ 288 | Name: model.NewCIStr(fmt.Sprintf("p%d", i)), 289 | Clause: &ast.PartitionDefinitionClauseLessThan{ 290 | Exprs: []ast.ExprNode{ 291 | &ast.FuncCallExpr{ 292 | FnName: model.NewCIStr("TO_DAYS"), 293 | Args: []ast.ExprNode{&val}, 294 | }, 295 | }, 296 | }, 297 | }) 298 | } 299 | } 300 | 301 | func (s *StateFlow) walkPartitionDefinitionsTimestamp(definitions *[]*ast.PartitionDefinition) { 302 | for i := 0; i < len(timestampPartition); i += util.RdRange(1, 3) { 303 | val := driver.ValueExpr{} 304 | val.SetMysqlTime(tidbTypes.NewTime(tidbTypes.FromGoTime(data_gen.GenerateTimestampItem()), 0, 0)) 305 | *definitions = append(*definitions, &ast.PartitionDefinition{ 306 | Name: model.NewCIStr(fmt.Sprintf("p%d", i)), 307 | Clause: &ast.PartitionDefinitionClauseLessThan{ 308 | Exprs: []ast.ExprNode{ 309 | &ast.FuncCallExpr{ 310 | FnName: model.NewCIStr("TO_DAYS"), 311 | Args: []ast.ExprNode{&val}, 312 | }, 313 | }, 314 | }, 315 | }) 316 | } 317 | } 318 | 319 | func (s *StateFlow) alterTableSpecAddColumns(node *ast.AlterTableSpec, table *types.Table) { 320 | column := s.randNewColumn() 321 | node.NewColumns[0] = &ast.ColumnDef{ 322 | Name: &ast.ColumnName{ 323 | Name: model.NewCIStr(column.Column), 324 | }, 325 | Tp: s.makeFieldType(column.DataType, column.DataLen), 326 | Options: s.makeColumnOptions(column, column.Options), 327 | } 328 | } 329 | 330 | func (s *StateFlow) alterTableSpecDropColumn(node *ast.AlterTableSpec, table *types.Table) { 331 | column := table.RandColumn() 332 | node.OldColumnName = &ast.ColumnName{ 333 | Name: model.NewCIStr(column.Column), 334 | } 335 | } 336 | 337 | func (s *StateFlow) alterTableSpecDropIndex(node *ast.AlterTableSpec, table *types.Table) { 338 | // when index is a empty string 339 | // there will be a SQL error will 340 | // not it doesn't matter 341 | node.Name = table.RandIndex() 342 | } 343 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/test.sh: -------------------------------------------------------------------------------- 1 | cp go.mod go.mod1 2 | cp go.sum go.sum1 3 | go test ./... 4 | mv go.mod1 go.mod 5 | mv go.sum1 go.sum 6 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/types/column.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import "github.com/pingcap/parser/ast" 17 | 18 | // Column defines database column 19 | type Column struct { 20 | DB string 21 | Table string 22 | OriginTable string 23 | Column string 24 | OriginColumn string 25 | DataType string 26 | DataLen int 27 | Func bool 28 | NewFunc bool 29 | Options []ast.ColumnOptionType 30 | } 31 | 32 | // Clone makes a replica of column 33 | func (c *Column) Clone() *Column { 34 | return &Column{ 35 | DB: c.DB, 36 | Table: c.Table, 37 | OriginTable: c.OriginTable, 38 | Column: c.Column, 39 | OriginColumn: c.OriginColumn, 40 | DataType: c.DataType, 41 | DataLen: c.DataLen, 42 | Func: c.Func, 43 | NewFunc: c.NewFunc, 44 | Options: c.Options, 45 | } 46 | } 47 | 48 | // AddOption add option for column 49 | func (c *Column) AddOption(opt ast.ColumnOptionType) { 50 | for _, option := range c.Options { 51 | if option == opt { 52 | return 53 | } 54 | } 55 | c.Options = append(c.Options, opt) 56 | } 57 | 58 | // HasOption return is has the given option 59 | func (c *Column) HasOption(opt ast.ColumnOptionType) bool { 60 | for _, option := range c.Options { 61 | if option == opt { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/types/table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "sort" 18 | "strings" 19 | 20 | "github.com/zhouqiang-cl/wreck-it/pkg/go-sqlsmith/util" 21 | ) 22 | 23 | // Table defines database table 24 | type Table struct { 25 | DB string 26 | Table string 27 | OriginTable string 28 | Type string 29 | Columns map[string]*Column 30 | Indexes []string 31 | // Online is for self obtain, 32 | // which means this table is online and will be manipulated in a txn 33 | Online bool 34 | // OnlineOther is for other instances obtain, 35 | // which means this table is being manipulated in other txns and should not be a DDL table 36 | OnlineOther bool 37 | InnerTableList []*Table 38 | } 39 | 40 | type byColumn []*Column 41 | 42 | func (a byColumn) Len() int { return len(a) } 43 | func (a byColumn) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 44 | func (a byColumn) Less(i, j int) bool { 45 | var ( 46 | bi = []byte(a[i].Column) 47 | bj = []byte(a[j].Column) 48 | ) 49 | 50 | for i := 0; i < min(len(bi), len(bj)); i++ { 51 | if bi[i] != bj[i] { 52 | return bi[i] < bj[i] 53 | } 54 | } 55 | return len(bi) < len(bj) 56 | } 57 | 58 | func min(a, b int) int { 59 | if a < b { 60 | return a 61 | } 62 | return b 63 | } 64 | 65 | // Clone copy table struct 66 | func (t *Table) Clone() *Table { 67 | newTable := Table{ 68 | DB: t.DB, 69 | Table: t.Table, 70 | OriginTable: t.OriginTable, 71 | Type: t.Type, 72 | Columns: make(map[string]*Column), 73 | Indexes: t.Indexes, 74 | Online: t.Online, 75 | OnlineOther: t.OnlineOther, 76 | InnerTableList: t.InnerTableList, 77 | } 78 | for k, column := range t.Columns { 79 | newTable.Columns[k] = column.Clone() 80 | } 81 | return &newTable 82 | } 83 | 84 | // RandColumn rand column from table 85 | func (t *Table) RandColumn() *Column { 86 | if len(t.Columns) == 0 { 87 | return nil 88 | } 89 | rdIndex := util.Rd(len(t.Columns)) 90 | index := 0 91 | for _, column := range t.Columns { 92 | if rdIndex == index { 93 | return column.Clone() 94 | } 95 | index++ 96 | } 97 | // should not reach here 98 | return nil 99 | } 100 | 101 | // GetColumns get ordered columns 102 | func (t *Table) GetColumns() []*Column { 103 | var r []*Column 104 | for _, column := range t.Columns { 105 | r = append(r, column) 106 | } 107 | sort.Sort(byColumn(r)) 108 | return r 109 | } 110 | 111 | // RandIndex rand indexes 112 | func (t *Table) RandIndex() string { 113 | if len(t.Indexes) == 0 { 114 | return "" 115 | } 116 | return t.Indexes[util.Rd(len(t.Indexes))] 117 | } 118 | 119 | // AddToInnerTables Do NOT set InnerTableList directly 120 | func (t *Table) AddToInnerTables(tables ...*Table) { 121 | t.InnerTableList = append(t.InnerTableList, tables...) 122 | 123 | // for removing duplicated items 124 | sort.Slice(t.InnerTableList, func(i, j int) bool { 125 | return strings.Compare(t.InnerTableList[i].Table, t.InnerTableList[j].Table) >= 0 126 | }) 127 | tableList := make([]*Table, 0) 128 | for i := range t.InnerTableList { 129 | if i == 0 { 130 | tableList = append(tableList, t.InnerTableList[i]) 131 | continue 132 | } 133 | if t.InnerTableList[i-1].Table == t.InnerTableList[i].Table { 134 | continue 135 | } 136 | tableList = append(tableList, t.InnerTableList[i]) 137 | } 138 | t.InnerTableList = tableList 139 | } 140 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/types/table_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestSQLSmith_TableGetColumns(t *testing.T) { 23 | table := Table{ 24 | Columns: make(map[string]*Column), 25 | } 26 | table.Columns["id"] = &Column{Column: "id"} 27 | table.Columns["a"] = &Column{Column: "a"} 28 | table.Columns["c"] = &Column{Column: "c"} 29 | table.Columns["b"] = &Column{Column: "b"} 30 | 31 | columns := table.GetColumns() 32 | assert.Equal(t, columns[0].Column, "a") 33 | assert.Equal(t, columns[1].Column, "b") 34 | assert.Equal(t, columns[2].Column, "c") 35 | assert.Equal(t, columns[3].Column, "id") 36 | } 37 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/types/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "math/rand" 18 | ) 19 | 20 | // Database defines database database 21 | type Database struct { 22 | Name string 23 | Online bool 24 | Tables map[string]*Table 25 | } 26 | 27 | // RandTables rand tables 28 | func (d *Database) RandTables() []*Table { 29 | tables := []*Table{} 30 | for _, t := range d.Tables { 31 | if rand.Float64() < 0.3 { 32 | tables = append(tables, t) 33 | } 34 | } 35 | if len(tables) == 0 { 36 | for _, t := range d.Tables { 37 | tables = append(tables, t) 38 | return tables 39 | } 40 | } 41 | return tables 42 | } 43 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/util/conversion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import "github.com/pingcap/parser/mysql" 17 | 18 | // Type2Tp conver type string to tp byte 19 | // TODO: complete conversion map 20 | func Type2Tp(t string) byte { 21 | switch t { 22 | case "int": 23 | return mysql.TypeLong 24 | case "varchar": 25 | return mysql.TypeVarchar 26 | case "timestamp": 27 | return mysql.TypeTimestamp 28 | case "datetime": 29 | return mysql.TypeDatetime 30 | case "text": 31 | return mysql.TypeBlob 32 | case "float": 33 | return mysql.TypeFloat 34 | } 35 | return mysql.TypeNull 36 | } 37 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/util/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import "log" 17 | 18 | // Info ... 19 | func Info(a ...interface{}) { 20 | log.Println(a...) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/util/rand.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import ( 17 | "fmt" 18 | "math/rand" 19 | "time" 20 | 21 | "github.com/pingcap/parser/ast" 22 | ) 23 | 24 | // Rd same to rand.Intn 25 | func Rd(n int) int { 26 | return rand.Intn(n) 27 | } 28 | 29 | // RdRange rand int in range 30 | func RdRange(n, m int) int { 31 | if n == m { 32 | return n 33 | } 34 | if m < n { 35 | n, m = m, n 36 | } 37 | return n + rand.Intn(m-n) 38 | } 39 | 40 | // RdFloat64 rand float64 41 | func RdFloat64() float64 { 42 | return rand.Float64() 43 | } 44 | 45 | // RdDate rand date 46 | func RdDate() time.Time { 47 | min := time.Date(1970, 1, 0, 0, 0, 1, 0, time.UTC).Unix() 48 | max := time.Date(2100, 1, 0, 0, 0, 0, 0, time.UTC).Unix() 49 | delta := max - min 50 | 51 | sec := rand.Int63n(delta) + min 52 | return time.Unix(sec, 0) 53 | } 54 | 55 | // RdTimestamp return same format as RdDate except rand range 56 | // TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' 57 | func RdTimestamp() time.Time { 58 | min := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC).Unix() 59 | max := time.Date(2038, 1, 19, 3, 14, 7, 0, time.UTC).Unix() 60 | delta := max - min 61 | 62 | sec := rand.Int63n(delta) + min 63 | return time.Unix(sec, 0) 64 | } 65 | 66 | // RdString rand string with given length 67 | func RdString(length int) string { 68 | res := "" 69 | for i := 0; i < length; i++ { 70 | charCode := RdRange(33, 127) 71 | // char '\' and '"' should be escaped 72 | if charCode == 92 || charCode == 34 { 73 | charCode++ 74 | // res = fmt.Sprintf("%s%s", res, "\\") 75 | } 76 | res = fmt.Sprintf("%s%s", res, string(rune(charCode))) 77 | } 78 | return res 79 | } 80 | 81 | // RdStringChar rand string with given length, letter chars only 82 | func RdStringChar(length int) string { 83 | res := "" 84 | for i := 0; i < length; i++ { 85 | charCode := RdRange(97, 123) 86 | res = fmt.Sprintf("%s%s", res, string(rune(charCode))) 87 | } 88 | return res 89 | } 90 | 91 | // RdType rand data type 92 | func RdType() string { 93 | switch Rd(6) { 94 | case 0: 95 | return "varchar" 96 | case 1: 97 | return "text" 98 | case 2: 99 | return "timestamp" 100 | case 3: 101 | return "datetime" 102 | } 103 | return "int" 104 | } 105 | 106 | // RdDataLen rand data with given type 107 | func RdDataLen(t string) int { 108 | switch t { 109 | case "int": 110 | return RdRange(1, 20) 111 | case "varchar": 112 | return RdRange(1, 2047) 113 | case "float": 114 | return RdRange(16, 64) 115 | case "timestamp": 116 | return -1 117 | case "datetime": 118 | return -1 119 | case "text": 120 | return -1 121 | } 122 | return 10 123 | } 124 | 125 | // RdColumnOptions for rand column option with given type 126 | func RdColumnOptions(t string) (options []ast.ColumnOptionType) { 127 | if Rd(3) == 0 { 128 | options = append(options, ast.ColumnOptionNotNull) 129 | } else if Rd(2) == 0 { 130 | options = append(options, ast.ColumnOptionNull) 131 | } 132 | switch t { 133 | case "varchar", "timestamp", "datetime", "int": 134 | if Rd(2) == 0 { 135 | options = append(options, ast.ColumnOptionDefaultValue) 136 | } 137 | } 138 | return 139 | } 140 | 141 | // RdCharset rand charset 142 | func RdCharset() string { 143 | switch Rd(4) { 144 | default: 145 | return "utf8" 146 | } 147 | } 148 | 149 | // RdBool ... 150 | func RdBool() bool { 151 | return Rd(2) == 0 152 | } 153 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import ( 17 | "bytes" 18 | "time" 19 | 20 | "github.com/ngaut/log" 21 | "github.com/pingcap/parser/ast" 22 | "github.com/pingcap/parser/format" 23 | ) 24 | 25 | // BufferOut parser ast node to SQL string 26 | func BufferOut(node ast.Node) (string, error) { 27 | out := new(bytes.Buffer) 28 | err := node.Restore(format.NewRestoreCtx(format.RestoreStringDoubleQuotes, out)) 29 | if err != nil { 30 | return "", err 31 | } 32 | return string(out.Bytes()), nil 33 | } 34 | 35 | // TimeMustParse wrap time.Parse and panic when error 36 | func TimeMustParse(layout, value string) time.Time { 37 | t, err := time.Parse(layout, value) 38 | if err != nil { 39 | log.Fatalf("parse time err %+v, layout: %s, value: %s", err, layout, value) 40 | } 41 | return t 42 | } 43 | 44 | // MinInt ... 45 | func MinInt(a, b int) int { 46 | if a > b { 47 | return b 48 | } 49 | return a 50 | } 51 | 52 | // MaxInt ... 53 | func MaxInt(a, b int) int { 54 | if a < b { 55 | return b 56 | } 57 | return a 58 | } 59 | -------------------------------------------------------------------------------- /pkg/go-sqlsmith/walker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package sqlsmith 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/pingcap/parser/ast" 20 | "github.com/pingcap/parser/opcode" 21 | ) 22 | 23 | func TestSQLSmith_Walker(t *testing.T) { 24 | ss := new() 25 | ss.LoadSchema(schema, indexes) 26 | 27 | ss.SetDB(dbname) 28 | 29 | // walker should fill in 30 | // ast.FieldList 31 | // ast.ColumnNameExpr 32 | // ast.TableName 33 | 34 | joinSub := ast.TableSource{ 35 | Source: &ast.SelectStmt{ 36 | SelectStmtOpts: &ast.SelectStmtOpts{ 37 | SQLCache: true, 38 | }, 39 | Fields: &ast.FieldList{ 40 | Fields: []*ast.SelectField{}, 41 | }, 42 | From: &ast.TableRefsClause{ 43 | TableRefs: &ast.Join{ 44 | Left: &ast.TableName{}, 45 | }, 46 | }, 47 | }, 48 | } 49 | 50 | node := ast.SelectStmt{ 51 | SelectStmtOpts: &ast.SelectStmtOpts{ 52 | SQLCache: true, 53 | }, 54 | Fields: &ast.FieldList{ 55 | Fields: []*ast.SelectField{}, 56 | }, 57 | From: &ast.TableRefsClause{ 58 | TableRefs: &ast.Join{ 59 | Left: &ast.TableName{}, 60 | Right: &joinSub, 61 | On: &ast.OnCondition{ 62 | Expr: &ast.BinaryOperationExpr{ 63 | Op: opcode.EQ, 64 | L: &ast.ColumnNameExpr{}, 65 | R: &ast.ColumnNameExpr{}, 66 | }, 67 | }, 68 | }, 69 | }, 70 | } 71 | 72 | ss.SetDB("community") 73 | sql, _, err := ss.Walk(&node) 74 | 75 | if err != nil { 76 | t.Fatalf("walk error %v", err) 77 | } 78 | t.Log(sql) 79 | } 80 | -------------------------------------------------------------------------------- /pkg/logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package logger 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | "time" 20 | 21 | "github.com/juju/errors" 22 | "github.com/ngaut/log" 23 | ) 24 | 25 | // Logger struct 26 | type Logger struct { 27 | name string 28 | logPath string 29 | print bool 30 | mute bool 31 | } 32 | 33 | // New init Logger struct 34 | func New(name, logPath string, mute bool) (*Logger, error) { 35 | logger := Logger{ 36 | name: name, 37 | logPath: logPath, 38 | print: logPath == "", 39 | mute: mute, 40 | } 41 | 42 | return logger.init() 43 | } 44 | 45 | func (l *Logger) init() (*Logger, error) { 46 | if l.print || l.mute { 47 | return l, nil 48 | } 49 | if err := l.writeLine("start file_logger log"); err != nil { 50 | return nil, errors.Trace(err) 51 | } 52 | return l, nil 53 | } 54 | 55 | func (l *Logger) writeLine(line string) error { 56 | line = fmt.Sprintf("%s %s", CurrentTimeStrAsLog(), line) 57 | if l.mute { 58 | return nil 59 | } else if l.print { 60 | log.Info(fmt.Sprintf("[%s] %s", l.name, line)) 61 | return nil 62 | } 63 | f, err := os.OpenFile(l.logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) 64 | if err != nil { 65 | return errors.Trace(err) 66 | } 67 | defer func() { 68 | if err := f.Close(); err != nil { 69 | log.Error(err) 70 | } 71 | }() 72 | 73 | if _, err = f.WriteString(fmt.Sprintf("%s\n", line)); err != nil { 74 | return errors.Trace(err) 75 | } 76 | 77 | return nil 78 | } 79 | 80 | // CurrentTimeStrAsLog return time format as "[2006/01/02 15:06:02.886 +08:00]" 81 | func CurrentTimeStrAsLog() string { 82 | return fmt.Sprintf("[%s]", FormatTimeStrAsLog(time.Now())) 83 | } 84 | 85 | // FormatTimeStrAsLog format given time as as "[2006/01/02 15:06:02.886 +08:00]" 86 | func FormatTimeStrAsLog(t time.Time) string { 87 | return t.Format("2006/01/02 15:04:05.000 -07:00") 88 | } 89 | 90 | // Info log line to log file 91 | func (l *Logger) Info(line string) error { 92 | return errors.Trace(l.writeLine(line)) 93 | } 94 | 95 | // Infof log line with format to log file 96 | func (l *Logger) Infof(line string, args ...interface{}) error { 97 | return errors.Trace(l.writeLine(fmt.Sprintf(line, args...))) 98 | } 99 | 100 | // Fatal log line to log file 101 | func (l *Logger) Fatal(line string) error { 102 | return errors.Trace(l.writeLine(line)) 103 | } 104 | 105 | // Fatalf log line with format to log file 106 | func (l *Logger) Fatalf(line string, args ...interface{}) error { 107 | return errors.Trace(l.writeLine(fmt.Sprintf(line, args...))) 108 | } 109 | -------------------------------------------------------------------------------- /pkg/mysql/mysql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package mysql 15 | 16 | import ( 17 | "context" 18 | "database/sql" 19 | "sync" 20 | "time" 21 | 22 | "github.com/go-sql-driver/mysql" 23 | "github.com/juju/errors" 24 | "github.com/ngaut/log" 25 | ) 26 | 27 | // DBConnect wraps db 28 | type DBConnect struct { 29 | sync.Mutex 30 | dsn string 31 | db *sql.DB 32 | txn *sql.Tx 33 | begin time.Time 34 | } 35 | 36 | // DBAccessor can be txn snapshot or db it self 37 | type DBAccessor interface { 38 | Exec(query string, args ...interface{}) (sql.Result, error) 39 | Query(query string, args ...interface{}) (*sql.Rows, error) 40 | QueryRow(query string, args ...interface{}) *sql.Row 41 | } 42 | 43 | // MustExec must execute sql or fatal 44 | func (conn *DBConnect) MustExec(query string, args ...interface{}) sql.Result { 45 | r, err := conn.db.Exec(query, args...) 46 | if err != nil { 47 | log.Errorf("exec %s err %v", query, err) 48 | } 49 | return r 50 | } 51 | 52 | // Exec execute sql 53 | func (conn *DBConnect) Exec(query string, args ...interface{}) (sql.Result, error) { 54 | conn.Lock() 55 | defer conn.Unlock() 56 | return conn.GetDBAccessor().Exec(query, args...) 57 | } 58 | 59 | // Query execute select statement 60 | func (conn *DBConnect) Query(query string, args ...interface{}) (*sql.Rows, error) { 61 | conn.Lock() 62 | defer conn.Unlock() 63 | return conn.GetDBAccessor().Query(query, args...) 64 | } 65 | 66 | // GetDB get real db object 67 | func (conn *DBConnect) GetDB() *sql.DB { 68 | return conn.db 69 | } 70 | 71 | // GetDBAccessor get DBAccessor interface 72 | func (conn *DBConnect) GetDBAccessor() DBAccessor { 73 | if conn.txn != nil { 74 | return conn.txn 75 | } 76 | return conn.db 77 | } 78 | 79 | // IfTxn show if in a transaction 80 | func (conn *DBConnect) IfTxn() bool { 81 | return conn.txn != nil 82 | } 83 | 84 | // GetTiDBTS get the txn begin timestamp from TiDB 85 | func (conn *DBConnect) GetTiDBTS() (uint64, error) { 86 | conn.Lock() 87 | defer conn.Unlock() 88 | 89 | if !conn.IfTxn() { 90 | return 0, nil 91 | } 92 | 93 | var tso uint64 94 | if err := conn.GetDBAccessor().QueryRow("SELECT @@TIDB_CURRENT_TS").Scan(&tso); err != nil { 95 | return 0, err 96 | } 97 | return tso, nil 98 | } 99 | 100 | // GetBeginTime get the begin time of a transaction 101 | // if not in transaction, return 0 102 | func (conn *DBConnect) GetBeginTime() time.Time { 103 | conn.Lock() 104 | defer conn.Unlock() 105 | if conn.IfTxn() { 106 | return conn.begin 107 | } 108 | return time.Time{} 109 | } 110 | 111 | // Begin a transaction 112 | func (conn *DBConnect) Begin() error { 113 | conn.Lock() 114 | defer conn.Unlock() 115 | if conn.txn != nil { 116 | return nil 117 | } 118 | txn, err := conn.db.Begin() 119 | if err != nil { 120 | return err 121 | } 122 | conn.txn = txn 123 | conn.begin = time.Now() 124 | return nil 125 | } 126 | 127 | // Commit a transaction 128 | func (conn *DBConnect) Commit() error { 129 | conn.Lock() 130 | defer func() { 131 | conn.txn = nil 132 | conn.Unlock() 133 | }() 134 | if conn.txn == nil { 135 | return nil 136 | } 137 | return conn.txn.Commit() 138 | } 139 | 140 | // Rollback a transaction 141 | func (conn *DBConnect) Rollback() error { 142 | conn.Lock() 143 | defer func() { 144 | conn.txn = nil 145 | conn.Unlock() 146 | }() 147 | if conn.txn == nil { 148 | return nil 149 | } 150 | return conn.txn.Rollback() 151 | } 152 | 153 | // CloseDB turn off db connection 154 | func (conn *DBConnect) CloseDB() error { 155 | return conn.db.Close() 156 | } 157 | 158 | // ReConnect rebuild connection 159 | func (conn *DBConnect) ReConnect() error { 160 | if err := conn.CloseDB(); err != nil { 161 | return err 162 | } 163 | db, err := sql.Open("mysql", conn.dsn) 164 | if err != nil { 165 | return err 166 | } 167 | conn.db = db 168 | return nil 169 | } 170 | 171 | // RunWithRetry tries to run func in specified count 172 | func RunWithRetry(ctx context.Context, retryCnt int, interval time.Duration, f func() error) error { 173 | var ( 174 | err error 175 | ) 176 | for i := 0; retryCnt < 0 || i < retryCnt; i++ { 177 | err = f() 178 | if err == nil { 179 | return nil 180 | } 181 | 182 | select { 183 | case <-ctx.Done(): 184 | return nil 185 | case <-time.After(interval): 186 | } 187 | } 188 | return err 189 | } 190 | 191 | // OpenDB opens db 192 | func OpenDB(dsn string, maxIdleConns int) (*DBConnect, error) { 193 | db, err := sql.Open("mysql", dsn) 194 | if err != nil { 195 | return nil, err 196 | } 197 | 198 | db.SetMaxIdleConns(maxIdleConns) 199 | // log.Info("DB opens successfully") 200 | return &DBConnect{ 201 | db: db, 202 | dsn: dsn, 203 | }, nil 204 | } 205 | 206 | // IsErrDupEntry returns true if error code = 1062 207 | func IsErrDupEntry(err error) bool { 208 | return isMySQLError(err, 1062) 209 | } 210 | 211 | func isMySQLError(err error, code uint16) bool { 212 | err = originError(err) 213 | e, ok := err.(*mysql.MySQLError) 214 | return ok && e.Number == code 215 | } 216 | 217 | // originError return original error 218 | func originError(err error) error { 219 | for { 220 | e := errors.Cause(err) 221 | if e == err { 222 | break 223 | } 224 | err = e 225 | } 226 | return err 227 | } 228 | -------------------------------------------------------------------------------- /pkg/pivot/config.go: -------------------------------------------------------------------------------- 1 | package pivot 2 | 3 | type Config struct { 4 | Dsn string 5 | 6 | // TODO implement them 7 | PrepareStmt bool 8 | Hint bool 9 | } 10 | -------------------------------------------------------------------------------- /pkg/pivot/dml.go: -------------------------------------------------------------------------------- 1 | package pivot 2 | 3 | import ( 4 | "github.com/pingcap/parser/ast" 5 | parser_driver "github.com/pingcap/tidb/types/parser_driver" 6 | ) 7 | 8 | type ExprNode struct { 9 | ast.ExprNode 10 | } 11 | 12 | // TODO: can it copy from tidb/types? 13 | const ( 14 | StringArg = 1 << iota 15 | DatetimeAsStringArg = 1 << iota // datetime formatted string: "1990-10-11" 16 | IntArg = 1 << iota 17 | FloatArg = 1 << iota 18 | DatetimeArg = 1 << iota 19 | NullArg = 1 << iota 20 | 21 | AnyArg = 0xFFFF 22 | ) 23 | 24 | type Function struct { 25 | // [0] indicates first param; if args are infinite, last item will be duplicated 26 | // [0] is [StringArg] => [AnyArg^DatetimeArg]: first arg is string then datetime is invalid as second arg 27 | AcceptType []map[int]int 28 | // min and max; -1 indicates infinite 29 | MinArgs int 30 | MaxArgs int 31 | Name string 32 | Eval func(a ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) 33 | } 34 | 35 | func (f *Function) NewAcceptTypeMap() *map[int]int { 36 | m := make(map[int]int) 37 | m[StringArg] = AnyArg 38 | m[DatetimeAsStringArg] = AnyArg 39 | m[IntArg] = AnyArg 40 | m[FloatArg] = AnyArg 41 | m[DatetimeArg] = AnyArg 42 | m[NullArg] = AnyArg 43 | return &m 44 | } 45 | -------------------------------------------------------------------------------- /pkg/pivot/generator_test.go: -------------------------------------------------------------------------------- 1 | package pivot 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/pingcap/parser" 7 | "github.com/pingcap/parser/ast" 8 | "github.com/pingcap/parser/model" 9 | "github.com/pingcap/tidb/sessionctx/stmtctx" 10 | parserdriver "github.com/pingcap/tidb/types/parser_driver" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func parse(t *testing.T, sql string) ast.Node { 15 | p := parser.New() 16 | stmtNodes, _, err := p.Parse(sql, "", "") 17 | if err != nil { 18 | t.Fatalf("got %v", err) 19 | } 20 | sel := stmtNodes[0].(*ast.SelectStmt) 21 | return sel.Where 22 | } 23 | 24 | func isTrueValue(expr parserdriver.ValueExpr) bool { 25 | zero := parserdriver.ValueExpr{} 26 | zero.SetInt64(0) 27 | res, _ := expr.CompareDatum(&stmtctx.StatementContext{AllowInvalidDate: true, IgnoreTruncate: true}, &zero.Datum) 28 | return res == 1 29 | } 30 | 31 | func TestCase1(t *testing.T) { 32 | //CREATE TABLE t0(c0 TEXT(10)); 33 | //INSERT INTO t0(c0) VALUES (1); 34 | //CREATE INDEX i0 ON t0(c0(10)); 35 | //SELECT * FROM t0 WHERE ('a' != t0.c0) AND t0.c0; -- expected: {1}, actual: {} 36 | 37 | value := EvaluateRow(parse(t, "SELECT * FROM t0 WHERE ('a' != t0.c0) AND t0.c0"), []Table{{ 38 | Name: model.NewCIStr("t0"), 39 | Columns: [][3]string{{"c0", "float", "YES"}}, 40 | Indexes: nil, 41 | }}, map[TableColumn]interface{}{ 42 | TableColumn{Table: "t0", Name: "c0"}: 1, 43 | }) 44 | require.Equal(t, true, isTrueValue(value)) 45 | } 46 | 47 | func TestCase2(t *testing.T) { 48 | //CREATE TABLE t0(c0 DOUBLE UNSIGNED UNIQUE); 49 | //INSERT INTO t0(c0) VALUES (0); 50 | //SELECT * FROM t0 WHERE t0.c0 = -1; -- expected: {}, actual: {0} 51 | value := EvaluateRow(parse(t, "SELECT * FROM t0 WHERE t0.c0 = -1"), []Table{{ 52 | Name: model.NewCIStr("t0"), 53 | Columns: [][3]string{{"c0", "double unsigned", "YES"}}, 54 | Indexes: nil, 55 | }}, map[TableColumn]interface{}{ 56 | TableColumn{Table: "t0", Name: "c0"}: 1.0, 57 | }) 58 | require.Equal(t, false, isTrueValue(value)) 59 | } 60 | 61 | func TestCase4(t *testing.T) { 62 | //CREATE TABLE t0(c0 NUMERIC PRIMARY KEY); 63 | //INSERT IGNORE INTO t0(c0) VALUES (NULL); 64 | //SELECT * FROM t0 WHERE c0; -- expected: {}, actual: {0} 65 | value := EvaluateRow(parse(t, "SELECT * FROM t0 WHERE t0.c0"), []Table{{ 66 | Name: model.NewCIStr("t0"), 67 | Columns: [][3]string{{"c0", "NUMERIC", "YES"}}, 68 | Indexes: nil, 69 | }}, map[TableColumn]interface{}{ 70 | TableColumn{Table: "t0", Name: "c0"}: 0.0, 71 | }) 72 | require.Equal(t, false, isTrueValue(value)) 73 | } 74 | 75 | func TestCase6(t *testing.T) { 76 | // CREATE TABLE t0(c0 CHAR AS (c1) UNIQUE, c1 INT); 77 | // INSERT INTO t0(c1) VALUES (0), (1); 78 | // SELECT * FROM t0; -- connection running loop panic 79 | value := EvaluateRow(parse(t, "SELECT * FROM t0"), []Table{{ 80 | Name: model.NewCIStr("t0"), 81 | Columns: [][3]string{{"c0", "CHAR", "YES"}, {"t1", "INT"}}, 82 | Indexes: nil, 83 | }}, map[TableColumn]interface{}{ 84 | TableColumn{Table: "t0", Name: "c0"}: "0", 85 | }) 86 | require.Equal(t, true, isTrueValue(value)) 87 | } 88 | 89 | func TestCase8(t *testing.T) { 90 | // CREATE TABLE t0(c0 INT AS (1), c1 INT PRIMARY KEY); 91 | // INSERT INTO t0(c1) VALUES (0); 92 | // CREATE INDEX i0 ON t0(c0); 93 | // SELECT /*+ USE_INDEX_MERGE(t0, i0, PRIMARY)*/ t0.c0 FROM t0 WHERE t0.c1 OR t0.c0; 94 | // SELECT t0.c0 FROM t0 WHERE t0.c1 OR t0.c0; -- expected: {1}, actual: {NULL} 95 | value := EvaluateRow(parse(t, "SELECT t0.c0 FROM t0 WHERE t0.c1 OR t0.c0"), []Table{{ 96 | Name: model.NewCIStr("t0"), 97 | Columns: [][3]string{{"c0", "INT", "YES"}, {"t1", "INT"}}, 98 | Indexes: nil, 99 | }}, map[TableColumn]interface{}{ 100 | TableColumn{Table: "t0", Name: "c0"}: 1, 101 | TableColumn{Table: "t0", Name: "c1"}: 0, 102 | }) 103 | require.Equal(t, true, isTrueValue(value)) 104 | } 105 | 106 | func TestCase11(t *testing.T) { 107 | //CREATE TABLE t0(c0 INT, c1 INT, PRIMARY KEY(c1)); 108 | //CREATE INDEX i0 ON t0(c0); 109 | //SELECT /*+ USE_INDEX_MERGE(t0, PRIMARY) */ * FROM t0 WHERE 1 OR t0.c1; 110 | value := EvaluateRow(parse(t, "SELECT t0.c0 FROM t0 WHERE t0.c1 OR t0.c0"), []Table{{ 111 | Name: model.NewCIStr("t0"), 112 | Columns: [][3]string{{"c0", "INT", "YES"}, {"t1", "INT"}}, 113 | Indexes: nil, 114 | }}, map[TableColumn]interface{}{ 115 | TableColumn{Table: "t0", Name: "c0"}: 1, 116 | TableColumn{Table: "t0", Name: "c1"}: 0, 117 | }) 118 | require.Equal(t, true, isTrueValue(value)) 119 | } 120 | 121 | func TestCase12(t *testing.T) { 122 | //CREATE TABLE t0(c0 TEXT(10)); 123 | //INSERT INTO t0(c0) VALUES (1); 124 | //CREATE INDEX i0 ON t0(c0(10)); 125 | //SELECT * FROM t0 WHERE ('a' != t0.c0) AND t0.c0; -- expected: {1}, actual: {} 126 | value := EvaluateRow(parse(t, "SELECT * FROM t0 WHERE ('a' != t0.c0) AND t0.c0"), []Table{{ 127 | Name: model.NewCIStr("t0"), 128 | Columns: [][3]string{{"c0", "TEXT", "YES"}}, 129 | Indexes: nil, 130 | }}, map[TableColumn]interface{}{ 131 | TableColumn{Table: "t0", Name: "c0"}: "1", 132 | }) 133 | require.Equal(t, true, isTrueValue(value)) 134 | } 135 | 136 | func TestCase14(t *testing.T) { 137 | //CREATE TABLE t0(c0 FLOAT); 138 | //INSERT INTO t0(c0) VALUES (NULL); 139 | //SELECT * FROM t0 WHERE NOT(0 OR t0.c0); -- expected: {}, actual: {NULL} 140 | value := EvaluateRow(parse(t, "SELECT * FROM t0 WHERE NOT(0 OR t0.c0)"), []Table{{ 141 | Name: model.NewCIStr("t0"), 142 | Columns: [][3]string{{"c0", "float", "YES"}}, 143 | Indexes: nil, 144 | }}, map[TableColumn]interface{}{ 145 | TableColumn{Table: "t0", Name: "c0"}: nil, 146 | }) 147 | require.Equal(t, false, isTrueValue(value)) 148 | } 149 | 150 | func TestCase15(t *testing.T) { 151 | //CREATE TABLE t0(c0 INT); 152 | //INSERT INTO t0(c0) VALUES (0); 153 | //SELECT t0.c0 FROM t0 WHERE CHAR(204355900); -- expected: {0}, actual: {} 154 | 155 | value := EvaluateRow(parse(t, "SELECT t0.c0 FROM t0 WHERE CHAR(204355900)"), []Table{{ 156 | Name: model.NewCIStr("t0"), 157 | Columns: [][3]string{{"c0", "int", "YES"}}, 158 | Indexes: nil, 159 | }}, map[TableColumn]interface{}{ 160 | TableColumn{Table: "t0", Name: "c0"}: 0, 161 | }) 162 | require.Equal(t, true, isTrueValue(value)) 163 | } 164 | 165 | func TestCase16(t *testing.T) { 166 | //CREATE TABLE t0(c0 INT); 167 | //CREATE VIEW v0(c0) AS SELECT 0 FROM t0 ORDER BY -t0.c0; 168 | //SELECT * FROM v0 RIGHT JOIN t0 ON false; -- connection running loop panic 169 | } 170 | 171 | func TestCase22(t *testing.T) { 172 | // CREATE TABLE t0(c0 FLOAT); 173 | // CREATE TABLE t1(c0 FLOAT); 174 | // INSERT INTO t1(c0) VALUES (0); 175 | // INSERT INTO t0(c0) VALUES (0); 176 | // SELECT t1.c0 FROM t1, t0 WHERE t0.c0=-t1.c0; -- expected: {0}, actual: {} 177 | value := EvaluateRow(parse(t, "SELECT t1.c0 FROM t1, t0 WHERE t0.c0=-t1.c0"), []Table{{ 178 | Name: model.NewCIStr("t0"), 179 | Columns: [][3]string{{"c0", "float", "YES"}}, 180 | Indexes: nil, 181 | }, { 182 | Name: model.NewCIStr("t1"), 183 | Columns: [][3]string{{"c0", "float", "YES"}}, 184 | Indexes: nil, 185 | }}, map[TableColumn]interface{}{ 186 | TableColumn{Table: "t0", Name: "c0"}: 0.0, 187 | TableColumn{Table: "t1", Name: "c0"}: 0.0, 188 | }) 189 | require.Equal(t, true, isTrueValue(value)) 190 | } 191 | 192 | func TestCase29(t *testing.T) { 193 | //CREATE TABLE t0(c0 BOOL); 194 | //INSERT INTO t0 VALUES (0); 195 | //SELECT * FROM t0 WHERE 1 AND 0.4; -- expected: {0}, actual: {} 196 | value := EvaluateRow(parse(t, "SELECT * FROM t0 WHERE 1 AND 0.4"), []Table{{ 197 | Name: model.NewCIStr("t0"), 198 | Columns: [][3]string{{"c0", "bool", "YES"}}, 199 | Indexes: nil, 200 | }}, map[TableColumn]interface{}{ 201 | TableColumn{Table: "t0", Name: "c0"}: false, 202 | }) 203 | require.Equal(t, true, isTrueValue(value)) 204 | } 205 | 206 | func TestCase30(t *testing.T) { 207 | // CREATE TABLE t0(c0 INT, c1 TEXT AS (0.9)); 208 | // INSERT INTO t0(c0) VALUES (0); 209 | // SELECT 0 FROM t0 WHERE false UNION SELECT 0 FROM t0 WHERE NOT t0.c1; -- expected: {0}, actual: {} 210 | } 211 | 212 | func TestCase31(t *testing.T) { 213 | //CREATE TABLE t0(c0 INT); 214 | //INSERT INTO t0(c0) VALUES (2); 215 | //SELECT t0.c0 FROM t0 WHERE (NOT NOT t0.c0) = t0.c0; -- expected: {}, actual: {2} 216 | 217 | value := EvaluateRow(parse(t, "SELECT t0.c0 FROM t0 WHERE (NOT NOT t0.c0) = t0.c0"), []Table{{ 218 | Name: model.NewCIStr("t0"), 219 | Columns: [][3]string{{"c0", "int", "YES"}}, 220 | Indexes: nil, 221 | }}, map[TableColumn]interface{}{ 222 | TableColumn{Table: "t0", Name: "c0"}: 2, 223 | }) 224 | require.Equal(t, false, isTrueValue(value)) 225 | } 226 | 227 | func TestCase_s01(t *testing.T) { 228 | value := EvaluateRow(parse(t, "SELECT table_int_varchar_text.id,table_int_varchar_text.col_int,table_int_varchar_text.col_varchar,table_int_varchar_text.col_text,table_int_text.id,table_int_text.col_int,table_int_text.col_text FROM table_int_varchar_text JOIN table_int_text WHERE ((table_int_varchar_text.col_varchar!=-1) AND (table_int_varchar_text.col_text>=0e+00))"), []Table{{ 229 | Name: model.NewCIStr("table_int_varchar_text"), 230 | Columns: [][3]string{{"col_varchar", "varchar", "YES"}, {"col_text", "text", "YES"}}, 231 | Indexes: nil, 232 | }}, map[TableColumn]interface{}{ 233 | TableColumn{Table: "table_int_varchar_text", Name: "col_varchar"}: 0, 234 | TableColumn{Table: "table_int_varchar_text", Name: "col_text"}: nil, 235 | }) 236 | require.Equal(t, false, isTrueValue(value)) 237 | } 238 | 239 | func TestCase_s02(t *testing.T) { 240 | value := EvaluateRow(parse(t, "select * from t0 where !null"), []Table{{ 241 | Name: model.NewCIStr("t0"), 242 | Columns: [][3]string{{"c0", "int", "YES"}}, 243 | Indexes: nil, 244 | }}, map[TableColumn]interface{}{ 245 | TableColumn{Table: "t0", Name: "c0"}: 2, 246 | }) 247 | require.Equal(t, false, isTrueValue(value)) 248 | } 249 | 250 | func TestCase_s03(t *testing.T) { 251 | value := EvaluateRow(parse(t, ` 252 | 253 | SELECT table_int_varchar_float_text.id, 254 | table_int_varchar_float_text.col_int, 255 | table_int_varchar_float_text.col_varchar, 256 | table_int_varchar_float_text.col_float, 257 | table_int_varchar_float_text.col_text 258 | FROM table_int_varchar_float_text 259 | WHERE (((!table_int_varchar_float_text.id) XOR (-1 260 | AND table_int_varchar_float_text.col_float))) 261 | 262 | `), []Table{{ 263 | Name: model.NewCIStr("table_int_varchar_float_text"), 264 | Columns: [][3]string{{"id", "int", "YES"}, {"col_int", "int", "YES"}}, 265 | Indexes: nil, 266 | }}, map[TableColumn]interface{}{ 267 | TableColumn{Table: "table_int_varchar_float_text", Name: "id"}: 5, 268 | TableColumn{Table: "table_int_varchar_float_text", Name: "col_float"}: nil, 269 | }) 270 | require.Equal(t, false, isTrueValue(value)) 271 | } 272 | 273 | func TestCase_s04(t *testing.T) { 274 | // table_varchar_float_text.id:15 275 | // table_varchar_float_text.col_varchar:true 276 | // table_varchar_float_text.col_float:-0.1 277 | // table_varchar_float_text.col_text:-1 278 | // table_int.id:9 279 | // table_int.col_int:1 280 | value := EvaluateRow(parse(t, ` 281 | SELECT table_varchar_float_text.id, 282 | table_varchar_float_text.col_varchar, 283 | table_varchar_float_text.col_float, 284 | table_varchar_float_text.col_text, 285 | table_int.id, 286 | table_int.col_int 287 | FROM table_varchar_float_text 288 | JOIN table_int 289 | WHERE !((table_varchar_float_text.col_float XOR 15)) 290 | `), []Table{{ 291 | Name: model.NewCIStr("table_varchar_float_text"), 292 | Columns: [][3]string{ 293 | {"id", "int", "YES"}, 294 | {"col_varchar", "varchar", "YES"}, 295 | {"col_float", "float", "YES"}, 296 | {"col_text", "text", "YES"}, 297 | }, 298 | Indexes: nil, 299 | }, { 300 | Name: model.NewCIStr("table_int"), 301 | Columns: [][3]string{ 302 | {"id", "int", "YES"}, 303 | {"col_int", "int", "YES"}, 304 | }, 305 | Indexes: nil, 306 | }}, map[TableColumn]interface{}{ 307 | TableColumn{Table: "table_varchar_float_text", Name: "id"}: 15, 308 | TableColumn{Table: "table_varchar_float_text", Name: "col_varchar"}: true, 309 | TableColumn{Table: "table_varchar_float_text", Name: "col_float"}: -0.1, 310 | TableColumn{Table: "table_varchar_float_text", Name: "col_text"}: -1, 311 | TableColumn{Table: "table_int", Name: "id"}: 9, 312 | TableColumn{Table: "table_int", Name: "col_int"}: 1, 313 | }) 314 | require.Equal(t, true, isTrueValue(value)) 315 | } 316 | 317 | func TestCase_s05(t *testing.T) { 318 | //row: 319 | // table_int_varchar_text.id:8 320 | // table_int_varchar_text.col_int: 321 | // table_int_varchar_text.col_varchar: 322 | // table_int_varchar_text.col_text:0 323 | // table_float.id:12 324 | // table_float.col_float: 325 | // table_int.id:11 326 | // table_int.col_int:0 327 | //panic: data verified failed 328 | value := EvaluateRow(parse(t, ` 329 | SELECT table_int_varchar_text.id, 330 | table_int_varchar_text.col_int, 331 | table_int_varchar_text.col_varchar, 332 | table_int_varchar_text.col_text, 333 | table_float.id, 334 | table_float.col_float, 335 | table_int.id, 336 | table_int.col_int 337 | FROM (table_int_varchar_text 338 | JOIN table_float) 339 | JOIN table_int 340 | WHERE ((table_int_varchar_text.id XOR 4.0631823313220344e-01) XOR (!table_int_varchar_text.col_text)) 341 | `), []Table{{ 342 | Name: model.NewCIStr("table_int_varchar_text"), 343 | Columns: [][3]string{ 344 | {"id", "int", "YES"}, 345 | {"col_text", "text", "YES"}, 346 | }, 347 | Indexes: nil, 348 | }}, map[TableColumn]interface{}{ 349 | TableColumn{Table: "table_int_varchar_text", Name: "id"}: 8, 350 | TableColumn{Table: "table_int_varchar_text", Name: "col_text"}: "0", 351 | }) 352 | require.Equal(t, true, isTrueValue(value)) 353 | } 354 | -------------------------------------------------------------------------------- /pkg/pivot/operator.go: -------------------------------------------------------------------------------- 1 | package pivot 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "github.com/pingcap/tidb/sessionctx/stmtctx" 8 | "github.com/pingcap/tidb/types" 9 | parser_driver "github.com/pingcap/tidb/types/parser_driver" 10 | ) 11 | 12 | var ( 13 | LogicXor = Function{nil, 2, 2, "XOR", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 14 | if len(v) != 2 { 15 | panic("error param numbers") 16 | } 17 | a := v[0] 18 | b := v[1] 19 | e := parser_driver.ValueExpr{} 20 | if a.Kind() == types.KindNull || b.Kind() == types.KindNull { 21 | e.SetNull() 22 | return e, nil 23 | } 24 | e.SetValue(ConvertToBoolOrNull(a) != ConvertToBoolOrNull(b)) 25 | return e, nil 26 | }} 27 | LogicAnd = Function{nil, 2, 2, "AND", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 28 | if len(v) != 2 { 29 | panic("error param numbers") 30 | } 31 | a := v[0] 32 | b := v[1] 33 | e := parser_driver.ValueExpr{} 34 | boolA := ConvertToBoolOrNull(a) 35 | boolB := ConvertToBoolOrNull(b) 36 | if boolA*boolB == 0 { 37 | e.SetValue(false) 38 | return e, nil 39 | } 40 | if boolA == -1 || boolB == -1 { 41 | e.SetValue(nil) 42 | return e, nil 43 | } 44 | e.SetValue(true) 45 | return e, nil 46 | }} 47 | LogicOr = Function{nil, 2, 2, "OR", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 48 | if len(v) != 2 { 49 | panic("error param numbers") 50 | } 51 | a := v[0] 52 | b := v[1] 53 | e := parser_driver.ValueExpr{} 54 | boolA := ConvertToBoolOrNull(a) 55 | boolB := ConvertToBoolOrNull(b) 56 | if boolA == 1 || boolB == 1 { 57 | e.SetValue(true) 58 | return e, nil 59 | } 60 | if boolA == -1 || boolB == -1 { 61 | e.SetValue(nil) 62 | return e, nil 63 | } 64 | e.SetValue(false) 65 | return e, nil 66 | }} 67 | Not = Function{nil, 1, 1, "NOT", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 68 | if len(v) != 1 { 69 | panic("error param numbers") 70 | } 71 | a := v[0] 72 | e := parser_driver.ValueExpr{} 73 | boolA := ConvertToBoolOrNull(a) 74 | if boolA == -1 { 75 | e.SetValue(nil) 76 | return e, nil 77 | } 78 | if boolA == 1 { 79 | e.SetValue(false) 80 | return e, nil 81 | } 82 | e.SetValue(true) 83 | return e, nil 84 | }} 85 | 86 | Gt = Function{nil, 2, 2, "GT", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 87 | if len(v) != 2 { 88 | panic("error param numbers") 89 | } 90 | a := v[0] 91 | b := v[1] 92 | e := parser_driver.ValueExpr{} 93 | if a.Kind() == types.KindNull || b.Kind() == types.KindNull { 94 | e.SetNull() 95 | return e, nil 96 | } 97 | e.SetValue(compare(a, b) > 0) 98 | return e, nil 99 | }} 100 | Lt = Function{nil, 2, 2, "LT", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 101 | if len(v) != 2 { 102 | panic("error param numbers") 103 | } 104 | a := v[0] 105 | b := v[1] 106 | e := parser_driver.ValueExpr{} 107 | if a.Kind() == types.KindNull || b.Kind() == types.KindNull { 108 | e.SetNull() 109 | return e, nil 110 | } 111 | e.SetValue(compare(a, b) < 0) 112 | return e, nil 113 | }} 114 | Ne = Function{nil, 2, 2, "NE", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 115 | if len(v) != 2 { 116 | panic("error param numbers") 117 | } 118 | a := v[0] 119 | b := v[1] 120 | e := parser_driver.ValueExpr{} 121 | if a.Kind() == types.KindNull || b.Kind() == types.KindNull { 122 | e.SetNull() 123 | return e, nil 124 | } 125 | e.SetValue(compare(a, b) != 0) 126 | return e, nil 127 | }} 128 | Eq = Function{nil, 2, 2, "EQ", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 129 | if len(v) != 2 { 130 | panic("error param numbers") 131 | } 132 | a := v[0] 133 | b := v[1] 134 | e := parser_driver.ValueExpr{} 135 | if a.Kind() == types.KindNull || b.Kind() == types.KindNull { 136 | e.SetNull() 137 | return e, nil 138 | } 139 | e.SetValue(compare(a, b) == 0) 140 | return e, nil 141 | }} 142 | Ge = Function{nil, 2, 2, "GE", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 143 | if len(v) != 2 { 144 | panic("error param numbers") 145 | } 146 | a := v[0] 147 | b := v[1] 148 | e := parser_driver.ValueExpr{} 149 | if a.Kind() == types.KindNull || b.Kind() == types.KindNull { 150 | e.SetNull() 151 | return e, nil 152 | } 153 | e.SetValue(compare(a, b) >= 0) 154 | return e, nil 155 | }} 156 | Le = Function{nil, 2, 2, "LE", func(v ...parser_driver.ValueExpr) (parser_driver.ValueExpr, error) { 157 | if len(v) != 2 { 158 | panic("error param numbers") 159 | } 160 | a := v[0] 161 | b := v[1] 162 | e := parser_driver.ValueExpr{} 163 | if a.Kind() == types.KindNull || b.Kind() == types.KindNull { 164 | e.SetNull() 165 | return e, nil 166 | } 167 | e.SetValue(compare(a, b) <= 0) 168 | return e, nil 169 | }} 170 | ) 171 | 172 | func init() { 173 | for _, f := range []*Function{&LogicXor, &LogicAnd, &LogicOr, &Not} { 174 | f.AcceptType = make([]map[int]int, 0) 175 | f.AcceptType = append(f.AcceptType, *f.NewAcceptTypeMap()) 176 | f.AcceptType = append(f.AcceptType, *f.NewAcceptTypeMap()) 177 | // is useless for NOT op 178 | f.AcceptType = append(f.AcceptType, *f.NewAcceptTypeMap()) 179 | } 180 | 181 | for _, f := range []*Function{&Lt, &Gt, &Le, &Ge, &Ne, &Eq} { 182 | f.AcceptType = make([]map[int]int, 0) 183 | mArg := *f.NewAcceptTypeMap() 184 | mArg[DatetimeArg] = AnyArg ^ StringArg ^ IntArg ^ FloatArg 185 | mArg[StringArg] = AnyArg ^ DatetimeArg 186 | mArg[IntArg] = AnyArg ^ DatetimeArg 187 | mArg[FloatArg] = AnyArg ^ DatetimeArg 188 | f.AcceptType = append(f.AcceptType, mArg, mArg) 189 | } 190 | } 191 | 192 | // -1 NULL; 0 false; 1 true 193 | func ConvertToBoolOrNull(a parser_driver.ValueExpr) int8 { 194 | switch a.Kind() { 195 | case types.KindNull: 196 | return -1 197 | case types.KindInt64: 198 | if a.GetValue().(int64) != 0 { 199 | return 1 200 | } 201 | return 0 202 | case types.KindUint64: 203 | if a.GetValue().(uint64) != 0 { 204 | return 1 205 | } 206 | return 0 207 | case types.KindFloat32: 208 | if a.GetFloat32() == 0 { 209 | return 0 210 | } 211 | return 1 212 | case types.KindFloat64: 213 | if a.GetFloat64() == 0 { 214 | return 0 215 | } 216 | return 1 217 | case types.KindString: 218 | s := a.GetValue().(string) 219 | match, _ := regexp.MatchString(`^\-{0,1}[1-9]+|^\-{0,1}0+[1-9]`, s) 220 | if match { 221 | return 1 222 | } 223 | return 0 224 | case types.KindMysqlDecimal: 225 | d := a.GetMysqlDecimal() 226 | if d.IsZero() { 227 | return 0 228 | } 229 | return 1 230 | case types.KindMysqlTime: 231 | t := a.GetMysqlTime() 232 | if t.IsZero() { 233 | return 0 234 | } 235 | return 1 236 | default: 237 | panic(fmt.Sprintf("unreachable kind: %d", a.Kind())) 238 | } 239 | } 240 | 241 | func compare(a, b parser_driver.ValueExpr) int { 242 | res, _ := a.CompareDatum(&stmtctx.StatementContext{AllowInvalidDate: true, IgnoreTruncate: true}, &b.Datum) 243 | // NOTE: err is warning, not really error 244 | //fmt.Printf("@@compare a: %v t(a): %d b: %v r: %d err: %v\n", a.GetValue(), a.GetType().Tp, b.GetValue(), res, err) 245 | return res 246 | } 247 | -------------------------------------------------------------------------------- /pkg/pivot/pivot.go: -------------------------------------------------------------------------------- 1 | package pivot 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "math/rand" 8 | "sync" 9 | "time" 10 | 11 | "github.com/juju/errors" 12 | "github.com/pingcap/log" 13 | "github.com/zhouqiang-cl/wreck-it/pkg/connection" 14 | "github.com/zhouqiang-cl/wreck-it/pkg/executor" 15 | "github.com/zhouqiang-cl/wreck-it/pkg/generator" 16 | "go.uber.org/zap" 17 | "golang.org/x/sync/errgroup" 18 | 19 | "github.com/pingcap/parser/model" 20 | ) 21 | 22 | type Pivot struct { 23 | wg sync.WaitGroup 24 | Conf *Config 25 | DB *sql.DB 26 | DBName string 27 | Executor *executor.Executor 28 | round int 29 | 30 | Generator 31 | } 32 | 33 | func NewPivot(dsn string, DBName string) (*Pivot, error) { 34 | e, err := executor.New(dsn, "test") 35 | if err != nil { 36 | return nil, err 37 | } 38 | conf := &Config{ 39 | Dsn: dsn, 40 | PrepareStmt: false, 41 | Hint: false, 42 | } 43 | return &Pivot{ 44 | Conf: conf, 45 | DBName: DBName, 46 | Executor: e, 47 | Generator: Generator{}, 48 | }, nil 49 | } 50 | 51 | const ( 52 | tableSQL = "DESC %s.%s" 53 | indexSQL = "SHOW INDEX FROM %s.%s" 54 | schemaSQL = "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM information_schema.tables" 55 | indexColumnName = "Key_name" 56 | ) 57 | 58 | func (p *Pivot) Start(ctx context.Context) { 59 | p.cleanup(ctx) 60 | p.kickup(ctx) 61 | } 62 | 63 | func (p *Pivot) Close() { 64 | p.wg.Wait() 65 | p.cleanup(context.Background()) 66 | p.Executor.Close() 67 | 68 | } 69 | 70 | func (p *Pivot) Init(ctx context.Context) { 71 | rand.Seed(time.Now().UnixNano()) 72 | p.Tables = make([]Table, 0) 73 | 74 | // Warn: Hard code db name 75 | tables, err := p.Executor.GetConn().FetchTables(p.DBName) 76 | if err != nil { 77 | panic(err) 78 | } 79 | for _, i := range tables { 80 | t := Table{Name: model.NewCIStr(i)} 81 | t.Columns, err = p.Executor.GetConn().FetchColumns(p.DBName, i) 82 | if err != nil { 83 | panic(err) 84 | } 85 | idx, err := p.Executor.GetConn().FetchIndexes(p.DBName, i) 86 | if err != nil { 87 | panic(err) 88 | } 89 | for _, j := range idx { 90 | t.Indexes = append(t.Indexes, model.NewCIStr(j)) 91 | } 92 | p.Tables = append(p.Tables, t) 93 | } 94 | } 95 | 96 | func (p *Pivot) prepare(ctx context.Context) { 97 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 98 | 99 | g, _ := errgroup.WithContext(ctx) 100 | for _, columnTypes := range ComposeAllColumnTypes(-1) { 101 | colTs := make([]string, len(columnTypes)) 102 | copy(colTs, columnTypes) 103 | g.Go(func() error { 104 | sql, _ := p.Executor.GenerateDDLCreateTable(colTs) 105 | return p.Executor.Exec(sql.SQLStmt) 106 | }) 107 | } 108 | if err := g.Wait(); err != nil { 109 | log.L().Error("create table failed", zap.Error(err)) 110 | } 111 | 112 | err := p.Executor.ReloadSchema() 113 | if err != nil { 114 | log.Error("reload data failed!") 115 | } 116 | ddlOpt := &generator.DDLOptions{ 117 | OnlineDDL: true, 118 | Tables: []string{}, 119 | } 120 | for i := 0; i < r.Intn(10); i++ { 121 | sql, _ := p.Executor.GenerateDDLCreateIndex(ddlOpt) 122 | fmt.Println(sql) 123 | err = p.Executor.Exec(sql.SQLStmt) 124 | if err != nil { 125 | log.L().Error("create index failed", zap.String("sql", sql.SQLStmt), zap.Error(err)) 126 | } 127 | } 128 | 129 | for _, table := range p.Executor.GetTables() { 130 | sql, err := p.Executor.GenerateDMLInsertByTable(table.Table) 131 | if err != nil { 132 | panic(errors.ErrorStack(err)) 133 | } 134 | err = p.Executor.Exec(sql.SQLStmt) 135 | if err != nil { 136 | log.L().Error("insert data failed", zap.String("sql", sql.SQLStmt), zap.Error(err)) 137 | } 138 | } 139 | } 140 | 141 | func (p *Pivot) cleanup(ctx context.Context) { 142 | p.Executor.Exec("drop database if exists " + p.DBName) 143 | p.Executor.Exec("create database " + p.DBName) 144 | p.Executor.Exec("use " + p.DBName) 145 | } 146 | 147 | func (p *Pivot) kickup(ctx context.Context) { 148 | p.wg.Add(1) 149 | p.prepare(ctx) 150 | p.Init(ctx) 151 | 152 | go func() { 153 | defer p.wg.Done() 154 | for { 155 | select { 156 | case <-ctx.Done(): 157 | return 158 | default: 159 | for { 160 | p.round++ 161 | p.progress(ctx) 162 | } 163 | } 164 | } 165 | 166 | }() 167 | } 168 | 169 | func (p *Pivot) progress(ctx context.Context) { 170 | // rand one pivot row for one table 171 | pivotRows, usedTables, err := p.ChoosePivotedRow() 172 | if err != nil { 173 | panic(err) 174 | } 175 | // generate sql ast tree and 176 | // generate sql where clause 177 | selectStmt, columns, err := p.GenSelectStmt(pivotRows, usedTables) 178 | if err != nil { 179 | panic(err) 180 | } 181 | // execute sql, ensure not null result set 182 | resultRows, err := p.execSelect(selectStmt) 183 | if err != nil { 184 | log.L().Error("execSelect failed", zap.Error(err)) 185 | return 186 | } 187 | // verify pivot row in result row set 188 | correct := p.verify(pivotRows, columns, resultRows) 189 | if !correct { 190 | fmt.Printf("query:\n%s\n", selectStmt) 191 | fmt.Printf("row:\n") 192 | for column, value := range pivotRows { 193 | fmt.Printf("%s.%s:%v\n", column.Table, column.Name, value.ValString) 194 | } 195 | panic("data verified failed") 196 | } 197 | fmt.Printf("run one statment [%s] successfully!\n", selectStmt) 198 | // log.Info("run one statement successfully!", zap.String("query", selectStmt)) 199 | } 200 | 201 | // may move to another struct 202 | func (p *Pivot) ChoosePivotedRow() (map[TableColumn]*connection.QueryItem, []Table, error) { 203 | result := make(map[TableColumn]*connection.QueryItem) 204 | count := 1 205 | if len(p.Tables) > 1 { 206 | // avoid too deep joins 207 | if count = Rd(len(p.Tables)-1) + 1; count > 4 { 208 | count = Rd(4) + 1 209 | } 210 | } 211 | fmt.Printf("#####count :%d", count) 212 | rand.Shuffle(len(p.Tables), func(i, j int) { p.Tables[i], p.Tables[j] = p.Tables[j], p.Tables[i] }) 213 | usedTables := p.Tables[:count] 214 | var reallyUsed []Table 215 | 216 | for _, i := range usedTables { 217 | sql := fmt.Sprintf("SELECT * FROM %s ORDER BY RAND() LIMIT 1;", i.Name) 218 | exeRes, err := p.execSelect(sql) 219 | if err != nil { 220 | panic(err) 221 | } 222 | if len(exeRes) > 0 { 223 | for _, c := range exeRes[0] { 224 | // panic(fmt.Sprintf("no rows in table %s", i.Name)) 225 | tableColumn := TableColumn{i.Name.O, c.ValType.Name()} 226 | result[tableColumn] = c 227 | } 228 | reallyUsed = append(reallyUsed, i) 229 | 230 | } 231 | } 232 | fmt.Printf("####used %+v", reallyUsed) 233 | return result, reallyUsed, nil 234 | } 235 | 236 | func (p *Pivot) GenSelectStmt(pivotRows map[TableColumn]*connection.QueryItem, usedTables []Table) (string, []TableColumn, error) { 237 | stmtAst, err := p.selectStmtAst(1, usedTables) 238 | if err != nil { 239 | return "", nil, err 240 | } 241 | sql, columns, err := p.selectStmt(&stmtAst, usedTables, pivotRows) 242 | if err != nil { 243 | return "", nil, err 244 | } 245 | return sql, columns, nil 246 | } 247 | 248 | func (p *Pivot) ExecAndVerify(stmt string, originRow map[TableColumn]*connection.QueryItem, columns []TableColumn) (bool, error) { 249 | resultSets, err := p.execSelect(stmt) 250 | if err != nil { 251 | return false, err 252 | } 253 | res := p.verify(originRow, columns, resultSets) 254 | return res, nil 255 | } 256 | 257 | // may not return string 258 | func (p *Pivot) execSelect(stmt string) ([][]*connection.QueryItem, error) { 259 | fmt.Printf("exec: %s", stmt) 260 | return p.Executor.GetConn().Select(stmt) 261 | } 262 | 263 | func (p *Pivot) verify(originRow map[TableColumn]*connection.QueryItem, columns []TableColumn, resultSets [][]*connection.QueryItem) bool { 264 | for _, row := range resultSets { 265 | if p.checkRow(originRow, columns, row) { 266 | fmt.Printf("Round %d, verify pass! \n", p.round) 267 | return true 268 | } 269 | } 270 | //fmt.Println("========= ORIGIN ROWS ======") 271 | //for k, v := range originRow { 272 | // fmt.Printf("key: %+v, value: [null: %v, value: %s]\n", k, v.Null, v.ValString) 273 | //} 274 | // 275 | //fmt.Println("========= COLUMNS ======") 276 | //for _, c := range columns { 277 | // fmt.Printf("Table: %s, Name: %s\n", c.Table, c.Name) 278 | //} 279 | // fmt.Printf("========= DATA ======, count: %d\n", len(resultSets)) 280 | // for i, r := range resultSets { 281 | // fmt.Printf("$$$$$$$$$ line %d\n", i) 282 | // for j, c := range r { 283 | // fmt.Printf(" table: %s, field: %s, field: %s, value: %s\n", columns[j].Table, columns[j].Name, c.ValType.Name(), c.ValString) 284 | // } 285 | // } 286 | 287 | fmt.Printf("Round %d, verify failed! \n", p.round) 288 | return false 289 | } 290 | 291 | func (p *Pivot) checkRow(originRow map[TableColumn]*connection.QueryItem, columns []TableColumn, resultSet []*connection.QueryItem) bool { 292 | for i, c := range columns { 293 | // fmt.Printf("i: %d, column: %+v, left: %+v, right: %+v", i, c, originRow[c], resultSet[i]) 294 | if !compareQueryItem(originRow[c], resultSet[i]) { 295 | return false 296 | } 297 | } 298 | return true 299 | } 300 | 301 | func compareQueryItem(left *connection.QueryItem, right *connection.QueryItem) bool { 302 | if left.ValType.Name() != right.ValType.Name() { 303 | return false 304 | } 305 | if left.Null != right.Null { 306 | return false 307 | } 308 | 309 | return (left.Null && right.Null) || (left.ValString == right.ValString) 310 | } 311 | -------------------------------------------------------------------------------- /pkg/pivot/util.go: -------------------------------------------------------------------------------- 1 | package pivot 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/rand" 7 | "strings" 8 | "time" 9 | 10 | "github.com/pingcap/parser/ast" 11 | "github.com/pingcap/parser/format" 12 | "github.com/pingcap/parser/mysql" 13 | "github.com/pingcap/parser/types" 14 | ) 15 | 16 | var ( 17 | allColumnTypes = []string{ 18 | "int", 19 | "varchar", 20 | "float", 21 | //"timestamp", 22 | //"datetime", 23 | "text", 24 | } 25 | ) 26 | 27 | // Rd same to rand.Intn 28 | func Rd(n int) int { 29 | return rand.Intn(n) 30 | } 31 | 32 | func RdInt63(n int64) int64 { 33 | return rand.Int63n(n) 34 | } 35 | 36 | // RdRange rand int in range 37 | func RdRange(n, m int64) int64 { 38 | if n == m { 39 | return n 40 | } 41 | if m < n { 42 | n, m = m, n 43 | } 44 | return n + rand.Int63n(m-n) 45 | } 46 | 47 | func RdInt64() int64 { 48 | if Rd(2) == 1 { 49 | return rand.Int63() 50 | } 51 | return -rand.Int63() - 1 52 | } 53 | 54 | // RdFloat64 rand float64 55 | func RdFloat64() float64 { 56 | return rand.Float64() 57 | } 58 | 59 | // RdDate rand date 60 | func RdDate() time.Time { 61 | min := time.Date(1970, 1, 0, 0, 0, 1, 0, time.UTC).Unix() 62 | max := time.Date(2100, 1, 0, 0, 0, 0, 0, time.UTC).Unix() 63 | delta := max - min 64 | 65 | sec := rand.Int63n(delta) + min 66 | return time.Unix(sec, 0) 67 | } 68 | 69 | // RdTimestamp return same format as RdDate except rand range 70 | // TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' 71 | func RdTimestamp() time.Time { 72 | min := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC).Unix() 73 | max := time.Date(2038, 1, 19, 3, 14, 7, 0, time.UTC).Unix() 74 | delta := max - min 75 | 76 | sec := rand.Int63n(delta) + min 77 | return time.Unix(sec, 0) 78 | } 79 | 80 | // TODO: support rand multi-byte utf8 81 | // RdString rand string with given length 82 | func RdString(length int) string { 83 | res := "" 84 | for i := 0; i < length; i++ { 85 | charCode := RdRange(33, 127) 86 | // char '\' and '"' should be escaped 87 | if charCode == 92 || charCode == 34 { 88 | charCode++ 89 | // res = fmt.Sprintf("%s%s", res, "\\") 90 | } 91 | res = fmt.Sprintf("%s%s", res, string(rune(charCode))) 92 | } 93 | return res 94 | } 95 | 96 | // RdStringChar rand string with given length, letter chars only 97 | func RdStringChar(length int) string { 98 | res := "" 99 | for i := 0; i < length; i++ { 100 | charCode := RdRange(97, 123) 101 | res = fmt.Sprintf("%s%s", res, string(rune(charCode))) 102 | } 103 | return res 104 | } 105 | 106 | // RdType rand data type 107 | func RdType() string { 108 | switch Rd(6) { 109 | case 0: 110 | return "varchar" 111 | case 1: 112 | return "text" 113 | case 2: 114 | return "timestamp" 115 | case 3: 116 | return "datetime" 117 | } 118 | return "int" 119 | } 120 | 121 | // RdDataLen rand data with given type 122 | func RdDataLen(t string) int64 { 123 | switch t { 124 | case "int": 125 | return RdRange(8, 20) 126 | case "varchar": 127 | return RdRange(255, 2047) 128 | case "float": 129 | return RdRange(16, 64) 130 | case "timestamp": 131 | return -1 132 | case "datetime": 133 | return -1 134 | case "text": 135 | return -1 136 | } 137 | return 10 138 | } 139 | 140 | // RdColumnOptions for rand column option with given type 141 | func RdColumnOptions(t string) (options []ast.ColumnOptionType) { 142 | if Rd(3) == 0 { 143 | options = append(options, ast.ColumnOptionNotNull) 144 | } else if Rd(2) == 0 { 145 | options = append(options, ast.ColumnOptionNull) 146 | } 147 | switch t { 148 | case "varchar", "timestamp", "datetime", "int": 149 | if Rd(2) == 0 { 150 | options = append(options, ast.ColumnOptionDefaultValue) 151 | } 152 | } 153 | return 154 | } 155 | 156 | // RdCharset rand charset 157 | func RdCharset() string { 158 | switch Rd(4) { 159 | default: 160 | return "utf8" 161 | } 162 | } 163 | 164 | func RdBool() bool { 165 | return Rd(2) == 0 166 | } 167 | 168 | func BufferOut(node ast.Node) (string, error) { 169 | out := new(bytes.Buffer) 170 | err := node.Restore(format.NewRestoreCtx(format.RestoreStringDoubleQuotes, out)) 171 | if err != nil { 172 | return "", err 173 | } 174 | return out.String(), nil 175 | } 176 | 177 | // TODO: decimal NOT Equals to float 178 | func TransMysqlType(t *types.FieldType) int { 179 | switch t.Tp { 180 | case mysql.TypeTiny, mysql.TypeShort, mysql.TypeLong, mysql.TypeLonglong, mysql.TypeInt24: 181 | return IntArg 182 | case mysql.TypeDecimal, mysql.TypeFloat, mysql.TypeDouble: 183 | return FloatArg 184 | case mysql.TypeTimestamp, mysql.TypeDate, mysql.TypeDatetime: 185 | return DatetimeArg 186 | case mysql.TypeVarchar, mysql.TypeJSON, mysql.TypeVarString, mysql.TypeString: 187 | return StringArg 188 | case mysql.TypeNull: 189 | return NullArg 190 | default: 191 | panic(fmt.Sprintf("no implement for type: %s", t.String())) 192 | } 193 | } 194 | 195 | // TODO: decimal NOT Equals to float 196 | func TransStringType(s string) int { 197 | s = strings.ToLower(s) 198 | switch { 199 | case strings.Contains(s, "string"), strings.Contains(s, "char"), strings.Contains(s, "text"), strings.Contains(s, "json"): 200 | return StringArg 201 | case strings.Contains(s, "int"), strings.Contains(s, "long"), strings.Contains(s, "short"), strings.Contains(s, "tiny"): 202 | return IntArg 203 | case strings.Contains(s, "float"), strings.Contains(s, "decimal"), strings.Contains(s, "double"): 204 | return FloatArg 205 | case strings.Contains(s, "time"), strings.Contains(s, "date"): 206 | return DatetimeArg 207 | default: 208 | panic(fmt.Sprintf("no implement for type: %s", s)) 209 | } 210 | } 211 | 212 | func TransToMysqlType(i int) byte { 213 | switch i { 214 | case IntArg: 215 | return mysql.TypeLong 216 | case FloatArg: 217 | return mysql.TypeDouble 218 | case DatetimeArg: 219 | return mysql.TypeDatetime 220 | case StringArg: 221 | return mysql.TypeVarchar 222 | default: 223 | panic(fmt.Sprintf("no implement this type: %d", i)) 224 | } 225 | } 226 | 227 | // ComposeAllColumnTypes get all column type combinations 228 | // length stands for the max types combined in one table, -1 to unlimit 229 | func ComposeAllColumnTypes(length int) [][]string { 230 | var res [][]string 231 | if length == -1 { 232 | length = len(allColumnTypes) 233 | } 234 | for i := 1; i <= length; i++ { 235 | indexes := make([]int, i, i) 236 | for j := 0; j < i; j++ { 237 | indexes[j] = j 238 | } 239 | 240 | for { 241 | table := make([]string, i, i) 242 | for j, index := range indexes { 243 | table[j] = allColumnTypes[index] 244 | } 245 | res = append(res, table) 246 | 247 | finish := true 248 | for j := len(indexes) - 1; j >= 0; j-- { 249 | if indexes[j] < len(allColumnTypes)-(len(indexes)-j) { 250 | indexes[j]++ 251 | for k := j + 1; k < len(indexes); k++ { 252 | indexes[k] = indexes[k-1] + 1 253 | } 254 | finish = false 255 | break 256 | } 257 | } 258 | if finish { 259 | break 260 | } 261 | } 262 | } 263 | return res 264 | } 265 | -------------------------------------------------------------------------------- /pkg/types/duration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "time" 18 | 19 | "github.com/juju/errors" 20 | ) 21 | 22 | // Duration wrapper 23 | type Duration struct { 24 | time.Duration 25 | } 26 | 27 | // UnmarshalText implement time.ParseDuration function for Duration 28 | func (d *Duration) UnmarshalText(text []byte) error { 29 | var err error 30 | d.Duration, err = time.ParseDuration(string(text)) 31 | return errors.Trace(err) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/types/generator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | // Generator interface to unify the usage of sqlsmith and sqlspider 17 | type Generator interface { 18 | // ReloadSchema function read raw scheme 19 | // For each record 20 | // record[0] dbname 21 | // record[1] table name 22 | // record[2] table type 23 | // record[3] column name 24 | // record[4] column type 25 | ReloadSchema([][5]string) error 26 | // SetDB set operation database 27 | // the generated SQLs after this will be under this database 28 | SetDB(db string) 29 | // SetStable is a trigger for whether generate random or some database-basicinfo-dependent data 30 | // eg. SetStable(true) will disable both rand() and user() functions since they both make unstable result in different database 31 | SetStable(stable bool) 32 | // SetHint can control if hints would be generated or not 33 | SetHint(hint bool) 34 | // SelectStmt generate select SQL 35 | SelectStmt() string 36 | // InsertStmt generate insert SQL 37 | InsertStmt() string 38 | // UpdateStmt generate update SQL 39 | UpdateStmt() string 40 | // CreateTableStmt generate create table SQL 41 | CreateTableStmt() string 42 | } 43 | -------------------------------------------------------------------------------- /pkg/types/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "time" 18 | ) 19 | 20 | // Log line struct 21 | type Log struct { 22 | Time time.Time 23 | Node int 24 | SQL *SQL 25 | State string 26 | } 27 | 28 | // ByLog implement sort interface for log 29 | type ByLog []*Log 30 | 31 | func (a ByLog) Len() int { return len(a) } 32 | func (a ByLog) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 33 | func (a ByLog) Less(i, j int) bool { 34 | return a[i].GetTime().Before(a[j].GetTime()) 35 | } 36 | 37 | // GetTime get log time 38 | func (l *Log) GetTime() time.Time { 39 | return l.Time 40 | } 41 | 42 | // GetNode get executor id 43 | func (l *Log) GetNode() int { 44 | return l.Node 45 | } 46 | 47 | // GetSQL get SQL struct 48 | func (l *Log) GetSQL() *SQL { 49 | if l.SQL != nil { 50 | return l.SQL 51 | } 52 | return &SQL{} 53 | } 54 | -------------------------------------------------------------------------------- /pkg/types/log_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "sort" 18 | "testing" 19 | "time" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | const timeLayout = `2006/01/02 15:04:05.000 -07:00` 25 | 26 | func TestSQLLogSort(t *testing.T) { 27 | var logs = []*Log{ 28 | { 29 | Time: mustParse("2006-01-02 15:04:05", "2019-08-10 11:45:16"), 30 | SQL: &SQL{ 31 | SQLType: SQLTypeDMLSelect, 32 | SQLStmt: "SELECT * FROM t", 33 | }, 34 | }, 35 | { 36 | Time: mustParse("2006-01-02 15:04:05", "2019-08-10 11:45:15"), 37 | SQL: &SQL{ 38 | SQLType: SQLTypeDMLUpdate, 39 | SQLStmt: "UPDATE t SET c = 1", 40 | }, 41 | }, 42 | { 43 | Time: mustParse("2006-01-02 15:04:05", "2019-08-10 11:45:14"), 44 | SQL: &SQL{ 45 | SQLType: SQLTypeDMLInsert, 46 | SQLStmt: "INSERT INTO t(c) VALUES(1), (2)", 47 | }, 48 | }, 49 | { 50 | Time: mustParse("2006-01-02 15:04:05", "2019-08-10 11:45:17"), 51 | SQL: &SQL{ 52 | SQLType: SQLTypeDMLDelete, 53 | SQLStmt: "DELETE FROM t", 54 | }, 55 | }, 56 | } 57 | sort.Sort(ByLog(logs)) 58 | 59 | assert.Equal(t, logs[0].GetSQL().SQLType, SQLTypeDMLInsert) 60 | assert.Equal(t, logs[1].GetSQL().SQLType, SQLTypeDMLUpdate) 61 | assert.Equal(t, logs[2].GetSQL().SQLType, SQLTypeDMLSelect) 62 | assert.Equal(t, logs[3].GetSQL().SQLType, SQLTypeDMLDelete) 63 | } 64 | 65 | func mustParse(layout, value string) time.Time { 66 | t, err := time.Parse(layout, value) 67 | if err != nil { 68 | panic("parse failed") 69 | } 70 | return t 71 | } 72 | -------------------------------------------------------------------------------- /pkg/types/order.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "sync" 18 | ) 19 | 20 | // Without general type, the "container/list" will not be graceful 21 | 22 | // Order for log executor orders 23 | type Order struct { 24 | sync.Mutex 25 | history []int 26 | index int 27 | } 28 | 29 | // NewOrder create Order 30 | func NewOrder() *Order { 31 | o := &Order{ 32 | history: []int{}, 33 | index: -1, 34 | } 35 | return o 36 | } 37 | 38 | // Push push a history, will move the id to end of 39 | func (o *Order) Push(id int) { 40 | o.Lock() 41 | defer o.Unlock() 42 | for index, i := range o.history { 43 | if i == id { 44 | if index == len(o.history)-1 { 45 | return 46 | } 47 | o.history = append(o.history[:index], o.history[index+1:]...) 48 | } 49 | } 50 | o.history = append(o.history, id) 51 | } 52 | 53 | // Start reset index 54 | func (o *Order) Start() { 55 | o.index = -1 56 | } 57 | 58 | // Next is for walking through 59 | func (o *Order) Next() bool { 60 | if o.index == -1 { 61 | o.Lock() 62 | } 63 | o.index++ 64 | if o.index == len(o.history) { 65 | o.Start() 66 | o.Reset() 67 | o.Unlock() 68 | return false 69 | } 70 | return true 71 | } 72 | 73 | // Val return value for current index 74 | func (o *Order) Val() int { 75 | return o.history[o.index] 76 | } 77 | 78 | // Reset history 79 | func (o *Order) Reset() { 80 | o.history = []int{} 81 | } 82 | 83 | // GetHistroy get copied history slice 84 | func (o *Order) GetHistroy() []int { 85 | h := o.history 86 | return h 87 | } 88 | 89 | // Has given value in order list 90 | func (o *Order) Has(i int) bool { 91 | o.Lock() 92 | defer o.Unlock() 93 | for _, h := range o.history { 94 | if i == h { 95 | return true 96 | } 97 | } 98 | return false 99 | } 100 | -------------------------------------------------------------------------------- /pkg/types/order_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestOrder(t *testing.T) { 23 | o := NewOrder() 24 | o.Push(11) 25 | o.Push(12) 26 | o.Push(13) 27 | o.Push(14) 28 | o.Push(12) 29 | o.Push(12) 30 | 31 | orders := []int{} 32 | for o.Next() { 33 | orders = append(orders, o.Val()) 34 | } 35 | assert.Equal(t, []int{11, 13, 14, 12}, orders) 36 | 37 | o.Push(11) 38 | o.Push(10) 39 | o.Push(11) 40 | o.Push(11) 41 | assert.False(t, o.Has(100)) 42 | assert.True(t, o.Has(11)) 43 | orders = []int{} 44 | for o.Next() { 45 | orders = append(orders, o.Val()) 46 | } 47 | assert.Equal(t, []int{10, 11}, orders) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/types/sort_string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | // BySQL implement sort interface for string 17 | type BySQL []string 18 | 19 | func (a BySQL) Len() int { return len(a) } 20 | func (a BySQL) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 21 | func (a BySQL) Less(i, j int) bool { 22 | var ( 23 | bi = []byte(a[i]) 24 | bj = []byte(a[j]) 25 | ) 26 | 27 | for i := 0; i < min(len(bi), len(bj)); i++ { 28 | if bi[i] != bj[i] { 29 | return bi[i] < bj[i] 30 | } 31 | } 32 | return len(bi) < len(bj) 33 | } 34 | 35 | func min(a, b int) int { 36 | if a < b { 37 | return a 38 | } 39 | return b 40 | } 41 | -------------------------------------------------------------------------------- /pkg/types/sql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package types 15 | 16 | // SQLType enums for SQL types 17 | type SQLType int 18 | 19 | // SQLTypeDMLSelect 20 | const ( 21 | SQLTypeUnknown SQLType = iota 22 | SQLTypeReloadSchema 23 | SQLTypeDMLSelect 24 | SQLTypeDMLSelectForUpdate 25 | SQLTypeDMLUpdate 26 | SQLTypeDMLInsert 27 | SQLTypeDMLDelete 28 | SQLTypeDDLCreateTable 29 | SQLTypeDDLAlterTable 30 | SQLTypeDDLCreateIndex 31 | SQLTypeTxnBegin 32 | SQLTypeTxnCommit 33 | SQLTypeTxnRollback 34 | SQLTypeExec 35 | SQLTypeExit 36 | SQLTypeSleep 37 | SQLTypeCreateDatabase 38 | SQLTypeDropDatabase 39 | ) 40 | 41 | // SQL struct 42 | type SQL struct { 43 | SQLType SQLType 44 | SQLStmt string 45 | SQLTable string 46 | // ExecTime is for avoid lock watched interference before time out 47 | // useful for sleep statement 48 | ExecTime int 49 | } 50 | 51 | func (t SQLType) String() string { 52 | switch t { 53 | case SQLTypeReloadSchema: 54 | return "SQLTypeReloadSchema" 55 | case SQLTypeDMLSelect: 56 | return "SQLTypeDMLSelect" 57 | case SQLTypeDMLSelectForUpdate: 58 | return "SQLTypeDMLSelectForUpdate" 59 | case SQLTypeDMLUpdate: 60 | return "SQLTypeDMLUpdate" 61 | case SQLTypeDMLInsert: 62 | return "SQLTypeDMLInsert" 63 | case SQLTypeDMLDelete: 64 | return "SQLTypeDMLDelete" 65 | case SQLTypeDDLCreateTable: 66 | return "SQLTypeDDLCreateTable" 67 | case SQLTypeDDLAlterTable: 68 | return "SQLTypeDDLAlterTable" 69 | case SQLTypeDDLCreateIndex: 70 | return "SQLTypeDDLCreateIndex" 71 | case SQLTypeTxnBegin: 72 | return "SQLTypeTxnBegin" 73 | case SQLTypeTxnCommit: 74 | return "SQLTypeTxnCommit" 75 | case SQLTypeTxnRollback: 76 | return "SQLTypeTxnRollback" 77 | case SQLTypeExec: 78 | return "SQLTypeExec" 79 | case SQLTypeExit: 80 | return "SQLTypeExit" 81 | case SQLTypeSleep: 82 | return "SQLTypeSleep" 83 | case SQLTypeCreateDatabase: 84 | return "SQLTypeCreateDatabase" 85 | case SQLTypeDropDatabase: 86 | return "SQLTypeDropDatabase" 87 | default: 88 | return "SQLTypeUnknown" 89 | } 90 | } 91 | --------------------------------------------------------------------------------