├── README.md ├── cmd └── main.go └── handler.go /README.md: -------------------------------------------------------------------------------- 1 | Run the proxy: 2 | 3 | ```go run cmd/main.go --vitess_server=localhost:15999 --keyspace=test_keyspace --shard=0``` 4 | 5 | Connect to the proxy: 6 | 7 | ```mysql -h127.0.0.1 -P4000 -uroot -p``` 8 | 9 | Now all your MySQL requests will be redirected to Vitess. 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/medvednikov/vitess-mysql-proxy" 10 | mysqlserver "github.com/siddontang/go-mysql/server" 11 | "github.com/youtube/vitess/go/vt/vitessdriver" 12 | 13 | "database/sql" 14 | "fmt" 15 | ) 16 | 17 | var vitessdb *sql.DB 18 | 19 | func main() { 20 | vitessServer := flag.String("vitess_server", "localhost:15999", "vitess server") 21 | keyspace := flag.String("keyspace", "test_keyspace", "vitess keyspace") 22 | shard := flag.String("shard", "0", "vitess shard") 23 | flag.Parse() 24 | initVitess(*vitessServer, *keyspace, *shard) 25 | startMySQLServer() 26 | } 27 | 28 | func initVitess(server, keyspace, shard string) { 29 | fmt.Println("Connecting to vitess on ", server, "...") 30 | timeout := 10 * time.Second 31 | var err error 32 | // Connect to vtgate 33 | vitessdb, err = vitessdriver.OpenShard( 34 | server, keyspace, shard, "master", timeout) 35 | if err != nil { 36 | log.Fatal("vitess client error: ", err) 37 | } 38 | fmt.Println("Done\n\n") 39 | //defer vitessdb.Close() 40 | } 41 | 42 | func startMySQLServer() { 43 | fmt.Println("Starting MySQL server...") 44 | l, err := net.Listen("tcp", "127.0.0.1:4000") 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | defer l.Close() 49 | for { 50 | c, err := l.Accept() 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | go handleTCPConn(c) 55 | } 56 | } 57 | 58 | func handleTCPConn(c net.Conn) { 59 | // Create a connection with user root and an empty passowrd 60 | conn, err := mysqlserver.NewConn(c, "root", "", vitessproxy.VitessHandler{vitessdb}) 61 | if err != nil { 62 | log.Println(err) 63 | } 64 | for { 65 | conn.HandleCommand() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package vitessproxy 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "github.com/siddontang/go-mysql/mysql" 11 | ) 12 | 13 | var execStatements = []string{"insert", "update", "delete", "create", "alter"} 14 | var selectStatements = []string{"select", "show"} 15 | 16 | type VitessHandler struct { 17 | DB *sql.DB 18 | } 19 | 20 | //handle COM_QUERY comamnd, like SELECT, INSERT, UPDATE, etc... 21 | //If Result has a Resultset (SELECT, SHOW, etc...), we will send this as the 22 | //repsonse, otherwise, we will send Result 23 | func (h VitessHandler) HandleQuery(query string) (*mysql.Result, error) { 24 | fmt.Println("query = ", query) 25 | // Get the statement (first word of the query) 26 | statement := strings.ToLower(strings.Split(query, " ")[0]) 27 | if contains(execStatements, statement) { 28 | return h.executeQuery(query) 29 | } else if contains(selectStatements, statement) { 30 | return h.selectQuery(query) 31 | } 32 | return nil, errors.New("Unsupported statement") 33 | } 34 | 35 | func (h VitessHandler) executeQuery(query string) (*mysql.Result, error) { 36 | fmt.Println("Updating/deleting/inserting into master...") 37 | tx, err := h.DB.Begin() 38 | if err != nil { 39 | fmt.Printf("begin failed: %v\n", err) 40 | return nil, err 41 | } 42 | res, err := tx.Exec(query) 43 | if err != nil { 44 | fmt.Printf("exec failed: %v\n", err) 45 | return nil, err 46 | } 47 | err = tx.Commit() 48 | if err != nil { 49 | fmt.Printf("commit failed: %v\n", err) 50 | return nil, err 51 | } 52 | nrRows, _ := res.RowsAffected() 53 | return &mysql.Result{0, 0, uint64(nrRows), nil}, nil 54 | } 55 | 56 | func (h VitessHandler) selectQuery(query string) (*mysql.Result, error) { 57 | rows, err := h.DB.Query(query) 58 | if err != nil { 59 | fmt.Printf("query failed: %v\n", err) 60 | return &mysql.Result{0, 0, 0, nil}, err 61 | } 62 | columns, _ := rows.Columns() 63 | count := len(columns) 64 | // Read rows and convert them to [][]interface{} 65 | res := make([][]interface{}, 0) 66 | for rows.Next() { 67 | values := make([]interface{}, count) 68 | valuePtrs := make([]interface{}, count) 69 | for i, _ := range columns { 70 | valuePtrs[i] = &values[i] 71 | } 72 | if err := rows.Scan(valuePtrs...); err != nil { 73 | fmt.Printf("scan failed: %v\n", err) 74 | os.Exit(1) 75 | } 76 | res = append(res, values) 77 | } 78 | if err := rows.Err(); err != nil { 79 | fmt.Printf("row iteration failed: %v\n", err) 80 | os.Exit(1) 81 | } 82 | r, err := mysql.BuildSimpleResultset(columns, res, false) 83 | return &mysql.Result{0, 0, 0, r}, err 84 | } 85 | 86 | func contains(slice []string, s string) bool { 87 | for i := 0; i < len(slice); i++ { 88 | if slice[i] == s { 89 | return true 90 | } 91 | } 92 | return false 93 | } 94 | 95 | //handle COM_FILED_LIST command 96 | func (h VitessHandler) HandleFieldList(table string, fieldWildcard string) ([]*mysql.Field, error) { 97 | return nil, nil 98 | } 99 | 100 | //handle COM_STMT_PREPARE, params is the param number for this statement, 101 | //columns is the column number context will be used later for statement execute 102 | func (h VitessHandler) HandleStmtPrepare(query string) (params int, columns int, context interface{}, err error) { 103 | return 0, 0, nil, nil 104 | } 105 | 106 | //handle COM_STMT_EXECUTE, context is the previous one set in prepare query is 107 | //the statement prepare query, and args is the params for this statement 108 | func (h VitessHandler) HandleStmtExecute(context interface{}, query string, args []interface{}) (*mysql.Result, error) { 109 | return nil, nil 110 | } 111 | 112 | //handle COM_INIT_DB command, you can check whether the dbName is valid, or other. 113 | func (h VitessHandler) UseDB(dbName string) error { 114 | return nil 115 | } 116 | --------------------------------------------------------------------------------