2017-02-12 11:13:54 +00:00
|
|
|
package dump
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/juju/errors"
|
2019-01-01 08:57:46 +00:00
|
|
|
"github.com/siddontang/go-log/log"
|
|
|
|
. "github.com/siddontang/go-mysql/mysql"
|
2017-02-12 11:13:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Unlick mysqldump, Dumper is designed for parsing and syning data easily.
|
|
|
|
type Dumper struct {
|
|
|
|
// mysqldump execution path, like mysqldump or /usr/bin/mysqldump, etc...
|
|
|
|
ExecutionPath string
|
|
|
|
|
|
|
|
Addr string
|
|
|
|
User string
|
|
|
|
Password string
|
|
|
|
|
|
|
|
// Will override Databases
|
|
|
|
Tables []string
|
|
|
|
TableDB string
|
|
|
|
|
|
|
|
Databases []string
|
|
|
|
|
2019-01-01 08:57:46 +00:00
|
|
|
Where string
|
|
|
|
Charset string
|
|
|
|
|
2017-02-12 11:13:54 +00:00
|
|
|
IgnoreTables map[string][]string
|
|
|
|
|
|
|
|
ErrOut io.Writer
|
2019-01-01 08:57:46 +00:00
|
|
|
|
|
|
|
masterDataSkipped bool
|
|
|
|
maxAllowedPacket int
|
|
|
|
hexBlob bool
|
2017-02-12 11:13:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewDumper(executionPath string, addr string, user string, password string) (*Dumper, error) {
|
|
|
|
if len(executionPath) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
path, err := exec.LookPath(executionPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Trace(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
d := new(Dumper)
|
|
|
|
d.ExecutionPath = path
|
|
|
|
d.Addr = addr
|
|
|
|
d.User = user
|
|
|
|
d.Password = password
|
|
|
|
d.Tables = make([]string, 0, 16)
|
|
|
|
d.Databases = make([]string, 0, 16)
|
2019-01-01 08:57:46 +00:00
|
|
|
d.Charset = DEFAULT_CHARSET
|
2017-02-12 11:13:54 +00:00
|
|
|
d.IgnoreTables = make(map[string][]string)
|
2019-01-01 08:57:46 +00:00
|
|
|
d.masterDataSkipped = false
|
2017-02-12 11:13:54 +00:00
|
|
|
|
|
|
|
d.ErrOut = os.Stderr
|
|
|
|
|
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
2019-01-01 08:57:46 +00:00
|
|
|
func (d *Dumper) SetCharset(charset string) {
|
|
|
|
d.Charset = charset
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dumper) SetWhere(where string) {
|
|
|
|
d.Where = where
|
|
|
|
}
|
|
|
|
|
2017-02-12 11:13:54 +00:00
|
|
|
func (d *Dumper) SetErrOut(o io.Writer) {
|
|
|
|
d.ErrOut = o
|
|
|
|
}
|
|
|
|
|
2019-01-01 08:57:46 +00:00
|
|
|
// In some cloud MySQL, we have no privilege to use `--master-data`.
|
|
|
|
func (d *Dumper) SkipMasterData(v bool) {
|
|
|
|
d.masterDataSkipped = v
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dumper) SetMaxAllowedPacket(i int) {
|
|
|
|
d.maxAllowedPacket = i
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dumper) SetHexBlob(v bool) {
|
|
|
|
d.hexBlob = v
|
|
|
|
}
|
|
|
|
|
2017-02-12 11:13:54 +00:00
|
|
|
func (d *Dumper) AddDatabases(dbs ...string) {
|
|
|
|
d.Databases = append(d.Databases, dbs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dumper) AddTables(db string, tables ...string) {
|
|
|
|
if d.TableDB != db {
|
|
|
|
d.TableDB = db
|
|
|
|
d.Tables = d.Tables[0:0]
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Tables = append(d.Tables, tables...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dumper) AddIgnoreTables(db string, tables ...string) {
|
|
|
|
t, _ := d.IgnoreTables[db]
|
|
|
|
t = append(t, tables...)
|
|
|
|
d.IgnoreTables[db] = t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dumper) Reset() {
|
|
|
|
d.Tables = d.Tables[0:0]
|
|
|
|
d.TableDB = ""
|
|
|
|
d.IgnoreTables = make(map[string][]string)
|
|
|
|
d.Databases = d.Databases[0:0]
|
2019-01-01 08:57:46 +00:00
|
|
|
d.Where = ""
|
2017-02-12 11:13:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dumper) Dump(w io.Writer) error {
|
|
|
|
args := make([]string, 0, 16)
|
|
|
|
|
|
|
|
// Common args
|
2019-01-01 08:57:46 +00:00
|
|
|
if strings.Contains(d.Addr, "/") {
|
|
|
|
args = append(args, fmt.Sprintf("--socket=%s", d.Addr))
|
|
|
|
} else {
|
|
|
|
seps := strings.SplitN(d.Addr, ":", 2)
|
|
|
|
args = append(args, fmt.Sprintf("--host=%s", seps[0]))
|
|
|
|
if len(seps) > 1 {
|
|
|
|
args = append(args, fmt.Sprintf("--port=%s", seps[1]))
|
|
|
|
}
|
2017-02-12 11:13:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
args = append(args, fmt.Sprintf("--user=%s", d.User))
|
|
|
|
args = append(args, fmt.Sprintf("--password=%s", d.Password))
|
|
|
|
|
2019-01-01 08:57:46 +00:00
|
|
|
if !d.masterDataSkipped {
|
|
|
|
args = append(args, "--master-data")
|
|
|
|
}
|
|
|
|
|
|
|
|
if d.maxAllowedPacket > 0 {
|
|
|
|
// mysqldump param should be --max-allowed-packet=%dM not be --max_allowed_packet=%dM
|
|
|
|
args = append(args, fmt.Sprintf("--max-allowed-packet=%dM", d.maxAllowedPacket))
|
|
|
|
}
|
|
|
|
|
2017-02-12 11:13:54 +00:00
|
|
|
args = append(args, "--single-transaction")
|
|
|
|
args = append(args, "--skip-lock-tables")
|
|
|
|
|
|
|
|
// Disable uncessary data
|
|
|
|
args = append(args, "--compact")
|
|
|
|
args = append(args, "--skip-opt")
|
|
|
|
args = append(args, "--quick")
|
|
|
|
|
|
|
|
// We only care about data
|
|
|
|
args = append(args, "--no-create-info")
|
|
|
|
|
|
|
|
// Multi row is easy for us to parse the data
|
|
|
|
args = append(args, "--skip-extended-insert")
|
|
|
|
|
2019-01-01 08:57:46 +00:00
|
|
|
if d.hexBlob {
|
|
|
|
// Use hex for the binary type
|
|
|
|
args = append(args, "--hex-blob")
|
|
|
|
}
|
|
|
|
|
2017-02-12 11:13:54 +00:00
|
|
|
for db, tables := range d.IgnoreTables {
|
|
|
|
for _, table := range tables {
|
|
|
|
args = append(args, fmt.Sprintf("--ignore-table=%s.%s", db, table))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-01 08:57:46 +00:00
|
|
|
if len(d.Charset) != 0 {
|
|
|
|
args = append(args, fmt.Sprintf("--default-character-set=%s", d.Charset))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(d.Where) != 0 {
|
|
|
|
args = append(args, fmt.Sprintf("--where=%s", d.Where))
|
|
|
|
}
|
|
|
|
|
2017-02-12 11:13:54 +00:00
|
|
|
if len(d.Tables) == 0 && len(d.Databases) == 0 {
|
|
|
|
args = append(args, "--all-databases")
|
|
|
|
} else if len(d.Tables) == 0 {
|
|
|
|
args = append(args, "--databases")
|
|
|
|
args = append(args, d.Databases...)
|
|
|
|
} else {
|
|
|
|
args = append(args, d.TableDB)
|
|
|
|
args = append(args, d.Tables...)
|
|
|
|
|
|
|
|
// If we only dump some tables, the dump data will not have database name
|
|
|
|
// which makes us hard to parse, so here we add it manually.
|
|
|
|
|
|
|
|
w.Write([]byte(fmt.Sprintf("USE `%s`;\n", d.TableDB)))
|
|
|
|
}
|
|
|
|
|
2019-01-01 08:57:46 +00:00
|
|
|
log.Infof("exec mysqldump with %v", args)
|
2017-02-12 11:13:54 +00:00
|
|
|
cmd := exec.Command(d.ExecutionPath, args...)
|
|
|
|
|
|
|
|
cmd.Stderr = d.ErrOut
|
|
|
|
cmd.Stdout = w
|
|
|
|
|
|
|
|
return cmd.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dump MySQL and parse immediately
|
|
|
|
func (d *Dumper) DumpAndParse(h ParseHandler) error {
|
|
|
|
r, w := io.Pipe()
|
|
|
|
|
|
|
|
done := make(chan error, 1)
|
|
|
|
go func() {
|
2019-01-01 08:57:46 +00:00
|
|
|
err := Parse(r, h, !d.masterDataSkipped)
|
2017-02-12 11:13:54 +00:00
|
|
|
r.CloseWithError(err)
|
|
|
|
done <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
err := d.Dump(w)
|
|
|
|
w.CloseWithError(err)
|
|
|
|
|
|
|
|
err = <-done
|
|
|
|
|
|
|
|
return errors.Trace(err)
|
|
|
|
}
|