├── mysql_linux ├── mysql_darwin ├── shell.txt ├── test.sql ├── go.mod ├── README.md ├── go.sum └── main.go /mysql_linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxoxx/mysql-go/HEAD/mysql_linux -------------------------------------------------------------------------------- /mysql_darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxoxx/mysql-go/HEAD/mysql_darwin -------------------------------------------------------------------------------- /shell.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GOOS=linux GOARCH=amd64 go build main.go 5 | 6 | GOOS=darwin GOARCH=amd64 go build main.go 7 | ./main -uroot -p123456 -h192.168.3.100 -P3306 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test.sql: -------------------------------------------------------------------------------- 1 | 2 | use testdst; 3 | INSERT INTO `test` (`id`, `name`, `pwd`, `hash`, `username`, `password`) VALUES ('17', 'd', 'd', 'd', 'd', 'tt'); 4 | INSERT INTO `test` (`id`, `name`, `pwd`, `hash`, `username`, `password`) VALUES ('18', 'd', 'd', 'd', 'd', 'tt'); 5 | INSERT INTO `test` (`id`, `name`, `pwd`, `hash`, `username`, `password`) VALUES ('19', 'd', 'd', 'd', 'd', 'tt'); -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mysqlclient 2 | 3 | go 1.21 4 | 5 | require ( 6 | filippo.io/edwards25519 v1.1.0 // indirect 7 | github.com/c-bata/go-prompt v0.2.6 // indirect 8 | github.com/chzyer/readline v1.5.1 // indirect 9 | github.com/go-sql-driver/mysql v1.8.0 // indirect 10 | github.com/mattn/go-colorable v0.1.13 // indirect 11 | github.com/mattn/go-isatty v0.0.20 // indirect 12 | github.com/mattn/go-runewidth v0.0.15 // indirect 13 | github.com/mattn/go-tty v0.0.5 // indirect 14 | github.com/pkg/term v1.2.0-beta.2 // indirect 15 | github.com/rivo/uniseg v0.4.7 // indirect 16 | golang.org/x/sys v0.18.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mysql-go 2 | mysql-go 3 | 4 | --- 5 | ### 为什么有这个轮子 6 | - mysql官方的命令行客户端没有静态编译的版本(网上也没找到) 7 | - 一般紧急临时用,mysql官方的命令行客户端体积很大,几百M 8 | - 日常管理还是建议使用navicate之类的客户端 9 | 10 | ### Feature 11 | - 类似linux下的grep,这样在管理过程中就少写一些 where,like等 12 | ``` 13 | mysql> show variables; | grep log_bin 14 | Variable_name Value 15 | log_bin ON 16 | log_bin_basename /var/lib/mysql/binlog 17 | log_bin_index /var/lib/mysql/binlog.index 18 | log_bin_trust_function_creators OFF 19 | log_bin_use_v1_row_events OFF 20 | sql_log_bin ON 21 | 6 rows in set (0.01 sec) 22 | ``` 23 | mysql> show variables; | grep binlog 24 | - 类似linux下的grep -v 排除过滤 25 | ``` 26 | mysql> show processlist; | grepv sleep 27 | Id User Host db Command Time State Info 28 | 5 event_scheduler localhost NULL Daemon 1902954 Waiting on empty queue NULL 29 | 115 root 192.168.3.101:63598 NULL Query 0 init show processlist 30 | 2 rows in set (0.00 sec) 31 | ``` 32 | 33 | ### 使用例子 基于跟官方的用法一至 34 | ``` 35 | mysql -uroot -p123456 -h127.0.0.1 -P3306 36 | mysql -u root -p 123456 -h 127.0.0.1 -P 3306 37 | mysql -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname 38 | mysql -uroot -p123456 -h127.0.0.1 -P3306 -f xxx.sql 39 | mysql -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname < xxx.sql 40 | mysql -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname -e 'select * from users limit 10;' 41 | ``` 42 | 43 | ### 安装 44 | ``` 45 | wget https://gitee.com/tinatmp/mysql/releases/download/mysql/mysql_linux -O /usr/local/bin/mysql chmod +x /usr/local/bin/mysql 46 | ``` 47 | 48 | ### 其它 49 | - 兼容 mysql5.7 mysql8 tidb 50 | - 测试不全,可能存在bug 51 | - 想要更强大的功能 可参考 https://github.com/xo/usql https://github.com/johejo/go-mysql-client https://github.com/danvergara/dblab 52 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= 4 | github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= 5 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= 6 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= 7 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= 8 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 9 | github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= 10 | github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 11 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 12 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 13 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 14 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 15 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 16 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 17 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 18 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 19 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 20 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 21 | github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 22 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 23 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 24 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 25 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 26 | github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= 27 | github.com/mattn/go-tty v0.0.5 h1:s09uXI7yDbXzzTTfw3zonKFzwGkyYlgU3OMjqA0ddz4= 28 | github.com/mattn/go-tty v0.0.5/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= 29 | github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= 30 | github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= 31 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 32 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 33 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 34 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 46 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 47 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "database/sql" 6 | "fmt" 7 | "github.com/chzyer/readline" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | _ "github.com/go-sql-driver/mysql" 15 | ) 16 | 17 | func executeSQLFromStdin(db *sql.DB) { 18 | scanner := bufio.NewScanner(os.Stdin) 19 | var sqlStatements string 20 | 21 | for scanner.Scan() { 22 | sqlStatements += scanner.Text() 23 | } 24 | 25 | if err := scanner.Err(); err != nil { 26 | fmt.Println("读取标准输入错误:", err) 27 | os.Exit(1) 28 | } 29 | 30 | // 你可能需要根据实际的SQL分隔符调整这里的逻辑 31 | for _, statement := range strings.Split(sqlStatements, ";") { 32 | statement = strings.TrimSpace(statement) 33 | if statement == "" { 34 | continue 35 | } 36 | 37 | _, err := db.Exec(statement) 38 | if err != nil { 39 | fmt.Println("执行SQL失败:", err) 40 | return 41 | } 42 | } 43 | 44 | fmt.Println("SQL文件执行完成") 45 | } 46 | 47 | // containsFilter 检查一行数据中是否至少有一个值包含了过滤字符串 48 | // exclude 参数为 true 时,实现 grep -v 的功能 49 | func containsFilter(values []sql.RawBytes, filterString string, exclude bool) bool { 50 | filterStringLower := strings.ToLower(filterString) 51 | if exclude { 52 | // 排除模式: 所有字段都必须不包含过滤字符串才返回true 53 | for _, value := range values { 54 | valueLower := strings.ToLower(string(value)) 55 | if strings.Contains(valueLower, filterStringLower) { 56 | return false // 只要有一个字段包含过滤字符串,就返回false 57 | } 58 | } 59 | return true // 所有字段都不包含过滤字符串,返回true 60 | } else { 61 | // 包含模式: 只要有一个字段包含过滤字符串就返回true 62 | for _, value := range values { 63 | valueLower := strings.ToLower(string(value)) 64 | if strings.Contains(valueLower, filterStringLower) { 65 | return true // 找到匹配的字段,返回true 66 | } 67 | } 68 | return false // 没有字段包含过滤字符串,返回false 69 | } 70 | } 71 | 72 | // 执行SQL语句并打印结果 73 | func executeAndPrintSQL(db *sql.DB, sqlStatement string) { 74 | start := time.Now() 75 | rows, err := db.Query(sqlStatement) 76 | if err != nil { 77 | fmt.Printf("执行查询失败: %v\n", err) 78 | return 79 | } 80 | defer rows.Close() 81 | 82 | cols, err := rows.Columns() 83 | if err != nil { 84 | fmt.Printf("获取列失败: %v\n", err) 85 | return 86 | } 87 | 88 | fmt.Println(strings.Join(cols, "\t")) 89 | 90 | var rowCount int 91 | for rows.Next() { 92 | rowCount++ 93 | columns := make([]interface{}, len(cols)) 94 | columnPointers := make([]interface{}, len(cols)) 95 | for i := range columns { 96 | columnPointers[i] = &columns[i] 97 | } 98 | 99 | if err := rows.Scan(columnPointers...); err != nil { 100 | fmt.Printf("读取行失败: %v\n", err) 101 | return 102 | } 103 | 104 | for _, col := range columns { 105 | switch v := col.(type) { 106 | case nil: 107 | fmt.Print("NULL\t") 108 | case []byte: 109 | fmt.Print(string(v) + "\t") 110 | default: 111 | // 对于非[]byte类型,使用 fmt.Sprint 将其转换为字符串 112 | fmt.Print(fmt.Sprint(v) + "\t") 113 | } 114 | } 115 | fmt.Println() 116 | } 117 | 118 | fmt.Printf("%d rows in set (%.2f sec)\n", rowCount, time.Since(start).Seconds()) 119 | } 120 | 121 | // 此函数用于格式化并打印查询结果 122 | func printResults(rows *sql.Rows, verticalFormat bool, start time.Time, filterString string, exclude bool) error { 123 | cols, err := rows.Columns() 124 | if err != nil { 125 | return err 126 | } 127 | filterString = strings.TrimSuffix(filterString, ";") 128 | 129 | // 对于垂直格式的输出 130 | if verticalFormat { 131 | var values = make([]interface{}, len(cols)) 132 | for i := range values { 133 | values[i] = new(sql.RawBytes) 134 | } 135 | 136 | rowCount := 0 137 | for rows.Next() { 138 | rowCount++ 139 | err = rows.Scan(values...) 140 | if err != nil { 141 | return err 142 | } 143 | fmt.Printf("*************************** %d. row ***************************\n", rowCount) 144 | for i, col := range cols { 145 | val := string(*(values[i].(*sql.RawBytes))) 146 | fmt.Printf("%30s: %s\n", col, val) 147 | } 148 | } 149 | fmt.Printf("%d row in set (%.2f sec)\n", rowCount, time.Since(start).Seconds()) 150 | } else { 151 | fmt.Println(strings.Join(cols, "\t")) 152 | var rowCount int 153 | for rows.Next() { 154 | var values = make([]sql.RawBytes, len(cols)) 155 | var scanArgs = make([]interface{}, len(values)) 156 | for i := range values { 157 | scanArgs[i] = &values[i] 158 | } 159 | 160 | err = rows.Scan(scanArgs...) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | // 检查整行数据是否包含过滤字符串 166 | if filterString == "" || containsFilter(values, filterString, exclude) { 167 | rowCount++ 168 | var valueStrings []string 169 | for _, value := range values { 170 | if value == nil { 171 | valueStrings = append(valueStrings, "NULL") 172 | } else { 173 | valueStrings = append(valueStrings, string(value)) 174 | } 175 | } 176 | fmt.Println(strings.Join(valueStrings, "\t")) 177 | } 178 | } 179 | fmt.Printf("%d rows in set (%.2f sec)\n", rowCount, time.Since(start).Seconds()) 180 | } 181 | 182 | return rows.Err() 183 | } 184 | 185 | func parseArgs(args []string) (user, password, host string, port int, dbname string, executeSQL string, sqlFilePath string, timeout string) { 186 | host = "localhost" // 默认值 187 | port = 3306 // 默认值 188 | timeout = "10" 189 | 190 | for i := 0; i < len(args); i++ { 191 | arg := args[i] 192 | if strings.HasPrefix(arg, "-") { 193 | var value string 194 | if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { 195 | value = args[i+1] 196 | i++ // 跳过下一个参数,因为它已经被作为当前参数的值处理了 197 | } else { 198 | value = strings.TrimPrefix(arg, arg[:2]) 199 | } 200 | 201 | switch { 202 | case strings.HasPrefix(arg, "-u"): 203 | user = value 204 | case strings.HasPrefix(arg, "-p"): 205 | password = value 206 | case strings.HasPrefix(arg, "-h"): 207 | host = value 208 | case strings.HasPrefix(arg, "-P"): 209 | var err error 210 | port, err = strconv.Atoi(value) 211 | if err != nil { 212 | fmt.Printf("无效的端口号: %s\n", value) 213 | os.Exit(1) 214 | } 215 | case strings.HasPrefix(arg, "-D"): 216 | dbname = value 217 | case strings.HasPrefix(arg, "-e"): 218 | executeSQL = value 219 | case strings.HasPrefix(arg, "-f"): 220 | sqlFilePath = value 221 | case strings.HasPrefix(arg, "-t"): 222 | timeout = value 223 | } 224 | } 225 | } 226 | return 227 | } 228 | 229 | func getServerVersion(db *sql.DB) string { 230 | var version string 231 | err := db.QueryRow("SELECT VERSION();").Scan(&version) 232 | if err != nil { 233 | fmt.Println("获取服务器版本失败:", err) 234 | return "Unknown version" 235 | } 236 | return version 237 | } 238 | 239 | func executeSQLFile(db *sql.DB, filePath string) error { 240 | fileContent, err := os.ReadFile(filePath) 241 | if err != nil { 242 | return fmt.Errorf("读取文件失败: %v", err) 243 | } 244 | 245 | // 可能需要根据你的需求调整逻辑,例如处理多个SQL语句等 246 | requests := strings.Split(string(fileContent), ";") 247 | 248 | for _, request := range requests { 249 | request = strings.TrimSpace(request) 250 | if request == "" { 251 | continue // 忽略空语句 252 | } 253 | _, err := db.Exec(request) 254 | if err != nil { 255 | return fmt.Errorf("执行SQL失败: %v", err) 256 | } 257 | } 258 | 259 | return nil 260 | } 261 | 262 | // 分割SQL语句并检查是否包含SELECT或SHOW 263 | func containsSelectOrShow(sql string) bool { 264 | // 以分号分割SQL语句,可能需要考虑去除注释和字符串常量中的分号 265 | statements := strings.Split(sql, ";") 266 | for _, statement := range statements { 267 | trimmedStatement := strings.TrimSpace(statement) 268 | if strings.HasPrefix(strings.ToUpper(trimmedStatement), "SELECT") || strings.HasPrefix(strings.ToUpper(trimmedStatement), "SHOW") { 269 | return true 270 | } 271 | } 272 | return false 273 | } 274 | 275 | func main() { 276 | // 通过自定义解析来获取数据库连接信息 277 | user, password, host, port, dbname, executeSQL, sqlFilePath, timeout := parseArgs(os.Args[1:]) 278 | // 构建数据源名称(DSN) 279 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?multiStatements=true&timeout=%ss", user, password, host, port, dbname, timeout) 280 | // 连接MySQL数据库 281 | db, err := sql.Open("mysql", dsn) 282 | executableName := filepath.Base(os.Args[0]) 283 | if err != nil { 284 | fmt.Println("连接数据库失败:", err) 285 | fmt.Println("使用例子:") 286 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306\n", executableName) 287 | fmt.Printf("%s -u root -p 123456 -h 127.0.0.1 -P 3306\n", executableName) 288 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname\n", executableName) 289 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -f xxx.sql\n", executableName) 290 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname < xxx.sql\n", executableName) 291 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname -e 'select * from users limit 10;'\n", executableName) 292 | os.Exit(1) 293 | } 294 | defer db.Close() 295 | // 确保连接正常 296 | if err := db.Ping(); err != nil { 297 | fmt.Println("数据库连接异常:", err) 298 | fmt.Println("使用例子:") 299 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306\n", executableName) 300 | fmt.Printf("%s -u root -p 123456 -h 127.0.0.1 -P 3306\n", executableName) 301 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname\n", executableName) 302 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -f xxx.sql\n", executableName) 303 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname < xxx.sql\n", executableName) 304 | fmt.Printf("%s -uroot -p123456 -h127.0.0.1 -P3306 -Ddbname -e 'select * from users limit 10;'\n", executableName) 305 | os.Exit(1) 306 | } 307 | 308 | // 执行导入文件 309 | if sqlFilePath != "" { 310 | // 执行SQL文件 311 | if err := executeSQLFile(db, sqlFilePath); err != nil { 312 | fmt.Println(err) 313 | os.Exit(1) 314 | } 315 | fmt.Println("SQL文件执行完成") 316 | os.Exit(0) 317 | } 318 | 319 | // 检查是否有从标准输入传入的数据 320 | fileInfo, err := os.Stdin.Stat() 321 | if err != nil { 322 | fmt.Println("检查标准输入失败:", err) 323 | os.Exit(1) 324 | } 325 | if fileInfo.Mode()&os.ModeCharDevice == 0 { 326 | // 有数据通过标准输入传入,例如重定向或管道 327 | executeSQLFromStdin(db) 328 | os.Exit(0) 329 | } 330 | 331 | if executeSQL != "" { 332 | // 如果有直接执行的SQL语句,执行它并退出 333 | // 检测是否为SELECT或SHOW命令 334 | if strings.HasPrefix(strings.ToUpper(executeSQL), "SELECT") || strings.HasPrefix(strings.ToUpper(executeSQL), "SHOW") || containsSelectOrShow(executeSQL) { 335 | // 执行查询并打印结果 336 | executeAndPrintSQL(db, executeSQL) 337 | } else { 338 | // 对于非查询操作,使用Exec并输出受影响的行数 339 | result, err := db.Exec(executeSQL) 340 | if err != nil { 341 | fmt.Printf("执行命令失败: %v\n", err) 342 | } else { 343 | affectedRows, err := result.RowsAffected() 344 | if err != nil { 345 | fmt.Printf("获取受影响行数失败: %v\n", err) 346 | } else { 347 | fmt.Printf("Query OK, %d row(s) affected\n", affectedRows) 348 | } 349 | } 350 | } 351 | os.Exit(0) 352 | } 353 | // 获取服务器版本 354 | version := getServerVersion(db) 355 | fmt.Println("欢迎使用MySQL-go客户端. by:yy") 356 | fmt.Printf("Server version: %s\n", version) 357 | fmt.Println("退出命令:exit exit; quit quit;") 358 | fmt.Println("导入文件:mysql -uroot -p123 -h192.168.0.100 -P4000 -fxxxx.sql\n") 359 | fmt.Println("过滤命令:select * from xxx; | grep xxxxxxx") 360 | fmt.Println("排除过滤命令:select * from xxx; | grepv xxxxxxx #类似linux中的grep -v\n") 361 | 362 | rl, err := readline.New("mysql> ") 363 | if err != nil { 364 | panic(err) 365 | } 366 | defer rl.Close() 367 | 368 | var multiLineQuery string 369 | for { 370 | line, err := rl.Readline() 371 | if err != nil { // EOF 或 Ctrl+C 会产生错误 372 | break 373 | } 374 | // 分离命令和可能的grep部分 375 | var filterString string 376 | var excludeFilter bool // 用于指示是否应排除包含特定关键字的行 377 | 378 | // 检测 grep 或 grepv 命令 379 | if parts := strings.Split(line, "| grep "); len(parts) > 1 { 380 | line = parts[0] // 命令部分 381 | filterString = parts[1] // grep过滤器 382 | excludeFilter = false // 不排除 383 | } else if parts := strings.Split(line, "| grepv "); len(parts) > 1 { 384 | line = parts[0] // 命令部分 385 | filterString = parts[1] // grepv过滤器 386 | excludeFilter = true // 排除匹配行 387 | } 388 | if parts := strings.Split(line, "|grep "); len(parts) > 1 { 389 | line = parts[0] // 命令部分 390 | filterString = parts[1] // grep过滤器 391 | excludeFilter = false // 不排除 392 | } else if parts := strings.Split(line, "|grepv "); len(parts) > 1 { 393 | line = parts[0] // 命令部分 394 | filterString = parts[1] // grepv过滤器 395 | excludeFilter = true // 排除匹配行 396 | } 397 | 398 | trimmedLine := strings.TrimSpace(line) 399 | // 先检查是否是退出命令 400 | if trimmedLine == "exit" || trimmedLine == "exit;" || trimmedLine == "quit" || trimmedLine == "quit;" { 401 | fmt.Println("退出程序") 402 | break 403 | } 404 | 405 | multiLineQuery += " " + line // 继续累积命令行 406 | 407 | // 检查是否需要执行查询:查询以分号结束或包含\G(可能跟随分号) 408 | executeQuery := false 409 | verticalFormat := false 410 | 411 | // 检查垂直格式标记\G 412 | if strings.Contains(strings.ToUpper(multiLineQuery), "\\G") { 413 | verticalFormat = true 414 | // 移除\G和\G;,准备执行 415 | multiLineQuery = strings.TrimSuffix(multiLineQuery, "\\G") // 对于没有分号的情况 416 | multiLineQuery = strings.Replace(multiLineQuery, "\\G;", ";", -1) // 对于\G;的情况 417 | executeQuery = true 418 | } 419 | 420 | if strings.HasSuffix(strings.TrimSpace(multiLineQuery), ";") { 421 | executeQuery = true 422 | // 确保移除结尾的分号,如果之前没有做 423 | multiLineQuery = strings.TrimSuffix(multiLineQuery, ";") 424 | } 425 | 426 | if executeQuery { 427 | // 执行SQL查询 428 | multiLineQuery = strings.TrimSpace(multiLineQuery) // 清理前后空格 429 | start := time.Now() // 开始计时 430 | 431 | var result sql.Result 432 | var affectedRows int64 433 | var err error 434 | if strings.HasPrefix(strings.ToUpper(multiLineQuery), "SELECT") || strings.HasPrefix(strings.ToUpper(multiLineQuery), "SHOW") || containsSelectOrShow(multiLineQuery) { 435 | // 对于SELECT和SHOW语句使用Query 436 | rows, err := db.Query(multiLineQuery) 437 | if err != nil { 438 | fmt.Println("执行查询失败:", err) 439 | } else { 440 | defer rows.Close() 441 | if err := printResults(rows, verticalFormat, start, filterString, excludeFilter); err != nil { 442 | fmt.Println("打印结果失败:", err) 443 | } 444 | } 445 | } else { 446 | result, err = db.Exec(multiLineQuery) // 对于非SELECT语句使用Exec 447 | if err != nil { 448 | fmt.Println("执行命令失败:", err) 449 | } else { 450 | affectedRows, err = result.RowsAffected() // 获取受影响的行数 451 | if err != nil { 452 | fmt.Println("获取受影响行数失败:", err) 453 | } else { 454 | fmt.Printf("Query OK, %d rows affected (%.2f sec)\n", affectedRows, time.Since(start).Seconds()) 455 | } 456 | } 457 | } 458 | 459 | // 重置multiLineQuery以便于下一个查询 460 | multiLineQuery = "" 461 | } 462 | } 463 | 464 | } 465 | --------------------------------------------------------------------------------