2023-08-01 11:20:16 +08:00
|
|
|
package helper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"database/sql"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/global"
|
|
|
|
)
|
|
|
|
|
|
|
|
type sourceOption struct {
|
|
|
|
dryRun bool
|
|
|
|
mergeInsert int
|
|
|
|
debug bool
|
|
|
|
}
|
|
|
|
type SourceOption func(*sourceOption)
|
|
|
|
|
|
|
|
func WithDryRun() SourceOption {
|
|
|
|
return func(o *sourceOption) {
|
|
|
|
o.dryRun = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func WithMergeInsert(size int) SourceOption {
|
|
|
|
return func(o *sourceOption) {
|
|
|
|
o.mergeInsert = size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func WithDebug() SourceOption {
|
|
|
|
return func(o *sourceOption) {
|
|
|
|
o.debug = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type dbWrapper struct {
|
|
|
|
DB *sql.DB
|
|
|
|
debug bool
|
|
|
|
dryRun bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDBWrapper(db *sql.DB, dryRun, debug bool) *dbWrapper {
|
|
|
|
|
|
|
|
return &dbWrapper{
|
|
|
|
DB: db,
|
|
|
|
dryRun: dryRun,
|
|
|
|
debug: debug,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *dbWrapper) Exec(query string, args ...interface{}) (sql.Result, error) {
|
|
|
|
if db.debug {
|
|
|
|
global.LOG.Debugf("query %s", query)
|
|
|
|
}
|
|
|
|
|
|
|
|
if db.dryRun {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return db.DB.Exec(query, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func Source(dns string, reader io.Reader, opts ...SourceOption) error {
|
|
|
|
start := time.Now()
|
|
|
|
global.LOG.Infof("source start at %s", start.Format("2006-01-02 15:04:05"))
|
|
|
|
defer func() {
|
|
|
|
end := time.Now()
|
|
|
|
global.LOG.Infof("source end at %s, cost %s", end.Format("2006-01-02 15:04:05"), end.Sub(start))
|
|
|
|
}()
|
|
|
|
|
|
|
|
var err error
|
|
|
|
var db *sql.DB
|
|
|
|
var o sourceOption
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(&o)
|
|
|
|
}
|
|
|
|
|
|
|
|
dbName, err := getDBNameFromDNS(dns)
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("get db name from dns failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
db, err = sql.Open("mysql", dns)
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("open mysql db failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
dbWrapper := newDBWrapper(db, o.dryRun, o.debug)
|
|
|
|
|
|
|
|
_, err = dbWrapper.Exec(fmt.Sprintf("USE `%s`;", dbName))
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("exec `use %s` failed, err: %v", dbName, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
db.SetConnMaxLifetime(3600)
|
|
|
|
|
|
|
|
r := bufio.NewReader(reader)
|
|
|
|
_, err = dbWrapper.Exec("SET autocommit=0;")
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("exec `set autocommit=0` failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
2023-10-09 17:56:27 +08:00
|
|
|
line, err := readLine(r)
|
2023-08-01 11:20:16 +08:00
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
global.LOG.Errorf("read sql failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-09 17:56:27 +08:00
|
|
|
ssql, err := trim(line)
|
2023-08-01 11:20:16 +08:00
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("trim sql failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-09 17:56:27 +08:00
|
|
|
afterInsertSql := ""
|
2023-08-01 11:20:16 +08:00
|
|
|
if o.mergeInsert > 1 && strings.HasPrefix(ssql, "INSERT INTO") {
|
|
|
|
var insertSQLs []string
|
|
|
|
insertSQLs = append(insertSQLs, ssql)
|
|
|
|
for i := 0; i < o.mergeInsert-1; i++ {
|
2023-10-09 17:56:27 +08:00
|
|
|
line, err := readLine(r)
|
2023-08-01 11:20:16 +08:00
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
2023-10-09 17:56:27 +08:00
|
|
|
ssql2, err := trim(line)
|
2023-08-01 11:20:16 +08:00
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("trim merge insert sql failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(ssql2, "INSERT INTO") {
|
|
|
|
insertSQLs = append(insertSQLs, ssql2)
|
|
|
|
continue
|
|
|
|
}
|
2023-10-09 17:56:27 +08:00
|
|
|
afterInsertSql = ssql2
|
2023-08-01 11:20:16 +08:00
|
|
|
break
|
|
|
|
}
|
|
|
|
ssql, err = mergeInsert(insertSQLs)
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("do merge insert failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = dbWrapper.Exec(ssql)
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("exec sql failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2023-10-09 17:56:27 +08:00
|
|
|
if len(afterInsertSql) != 0 {
|
|
|
|
_, err = dbWrapper.Exec(afterInsertSql)
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("exec sql failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2023-08-01 11:20:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
_, err = dbWrapper.Exec("COMMIT;")
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("exec `commit` failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = dbWrapper.Exec("SET autocommit=1;")
|
|
|
|
if err != nil {
|
|
|
|
global.LOG.Errorf("exec `autocommit=1` failed, err: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func mergeInsert(insertSQLs []string) (string, error) {
|
|
|
|
if len(insertSQLs) == 0 {
|
|
|
|
return "", errors.New("no input provided")
|
|
|
|
}
|
|
|
|
builder := strings.Builder{}
|
|
|
|
sql1 := insertSQLs[0]
|
|
|
|
sql1 = strings.TrimSuffix(sql1, ";")
|
|
|
|
builder.WriteString(sql1)
|
|
|
|
for i, insertSQL := range insertSQLs[1:] {
|
|
|
|
if i < len(insertSQLs)-1 {
|
|
|
|
builder.WriteString(",")
|
|
|
|
}
|
|
|
|
|
|
|
|
valuesIdx := strings.Index(insertSQL, "VALUES")
|
|
|
|
if valuesIdx == -1 {
|
|
|
|
return "", errors.New("invalid SQL: missing VALUES keyword")
|
|
|
|
}
|
|
|
|
sqln := insertSQL[valuesIdx:]
|
|
|
|
sqln = strings.TrimPrefix(sqln, "VALUES")
|
|
|
|
sqln = strings.TrimSuffix(sqln, ";")
|
|
|
|
builder.WriteString(sqln)
|
|
|
|
|
|
|
|
}
|
|
|
|
builder.WriteString(";")
|
|
|
|
|
|
|
|
return builder.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func trim(s string) (string, error) {
|
|
|
|
s = strings.TrimLeft(s, "\n")
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDBNameFromDNS(dns string) (string, error) {
|
|
|
|
ss1 := strings.Split(dns, "/")
|
|
|
|
if len(ss1) == 2 {
|
|
|
|
ss2 := strings.Split(ss1[1], "?")
|
|
|
|
if len(ss2) == 2 {
|
|
|
|
return ss2[0], nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("dns error: %s", dns)
|
|
|
|
}
|
2023-10-09 17:56:27 +08:00
|
|
|
|
|
|
|
func readLine(r *bufio.Reader) (string, error) {
|
|
|
|
lineItem, err := r.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
return lineItem, err
|
|
|
|
}
|
|
|
|
global.LOG.Errorf("read merge insert sql failed, err: %v", err)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if strings.HasSuffix(lineItem, ";\n") {
|
|
|
|
return lineItem, nil
|
|
|
|
}
|
|
|
|
lineAppend, err := readLine(r)
|
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
return lineItem, err
|
|
|
|
}
|
|
|
|
global.LOG.Errorf("read merge insert sql failed, err: %v", err)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return lineItem + lineAppend, nil
|
|
|
|
}
|