Skip to content

Commit 9ddf008

Browse files
committed
add support for dump/import and BR
1 parent 314107b commit 9ddf008

File tree

8 files changed

+184
-6
lines changed

8 files changed

+184
-6
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313

1414
require (
1515
github.com/davecgh/go-spew v1.1.1 // indirect
16+
github.com/google/uuid v1.6.0 // indirect
1617
github.com/pmezard/go-difflib v1.0.0 // indirect
1718
go.uber.org/atomic v1.11.0 // indirect
1819
golang.org/x/sys v0.5.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
44
github.com/defined2014/mysql v0.0.0-20231121061906-fcfacaa39f49 h1:Q3Ri7Ycix4T+Ig7I896I6w0WuCajid2SgyierI16NSo=
55
github.com/defined2014/mysql v0.0.0-20231121061906-fcfacaa39f49/go.mod h1:5GYlY+PrT+c8FHAJTMIsyOuHUNf62KAQuRPMGssbixo=
6+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
7+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
68
github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32 h1:m5ZsBa5o/0CkzZXfXLaThzKuR85SnHHetqBCpzQ30h8=
79
github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
810
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

r/example.result

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ a b
66
SELECT 1 FROM NON_EXISTING_TABLE;
77
Error 1146 (42S02): Table 'example.NON_EXISTING_TABLE' doesn't exist
88
SELECT 2 FROM NON_EXISTING_TABLE;
9+
Error 1146 (42S02): Table 'example.NON_EXISTING_TABLE' doesn't exist
910
SELECT 3 FROM NON_EXISTING_TABLE;
10-
Got one of the listed errors
11+
Error 1146 (42S02): Table 'example.NON_EXISTING_TABLE' doesn't exist
1112
SELECT 4;
1213
4
1314
4
@@ -20,19 +21,42 @@ SELECT 6;
2021
1 SELECT;
2122
Error 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your TiDB version for the right syntax to use line 1 column 1 near "1 SELECT;"
2223
2 SELECT;
24+
Error 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your TiDB version for the right syntax to use line 1 column 1 near "2 SELECT;"
2325
3 SELECT;
24-
Got one of the listed errors
26+
Error 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your TiDB version for the right syntax to use line 1 column 1 near "3 SELECT;"
2527
explain analyze format='brief' select * from t;
2628
id estRows actRows task access object execution info operator info memory disk
27-
TableReader 10000.00 5 root NULL time:<num>, loops:<num>, RU:<num>, cop_task: {num:<num>, max:<num>, proc_keys:<num>, rpc_num:<num>, rpc_time:<num>, copr_cache_hit_ratio:<num>, build_task_duration:<num>, max_distsql_concurrency:<num>} data:TableFullScan <num> Bytes N/A
28-
└─TableFullScan 10000.00 5 cop[tikv] table:t tikv_task:{time:<num>, loops:<num>} keep order:false, stats:pseudo N/A N/A
29+
TableReader 10000.00 5 root NULL time:<num>, loops:<num>, RU:<num>, cop_task: {num:<num>, max:<num>, proc_keys:<num>, tot_proc:<num>, tot_wait:<num>, copr_cache_hit_ratio:<num>, build_task_duration:<num>, max_distsql_concurrency:<num>, rpc_info:{Cop:{num_rpc:<num>, total_time:<num>}} data:TableFullScan <num> Bytes N/A
30+
└─TableFullScan 10000.00 5 cop[tikv] table:t tikv_task:{time:<num>, loops:<num>, scan_detail: {total_process_keys:<num>, total_process_keys_size:<num>, total_keys:<num>, get_snapshot_time:<num>, rocksdb: {key_skipped_count:<num>, block: {}}}, time_detail: {total_process_time:<num>, total_wait_time:<num>, tikv_wall_time:<num>} keep order:false, stats:pseudo N/A N/A
2931
explain analyze select * from t;
3032
id estRows actRows task access object execution info operator info memory disk
31-
TableReader_5 10000.00 5 root NULL time:<num>, loops:<num>, RU:<num>, cop_task: {num:<num>, max:<num>, proc_keys:<num>, rpc_num:<num>, rpc_time:<num>, copr_cache_hit_ratio:<num>, build_task_duration:<num>, max_distsql_concurrency:<num>} data:TableFullScan_4 <num> Bytes N/A
32-
└─TableFullScan_4 10000.00 5 cop[tikv] table:t tikv_task:{time:<num>, loops:<num>} keep order:false, stats:pseudo N/A N/A
33+
TableReader_5 10000.00 5 root NULL time:<num>, loops:<num>, RU:<num>, cop_task: {num:<num>, max:<num>, proc_keys:<num>, tot_proc:<num>, tot_wait:<num>, copr_cache_hit_ratio:<num>, build_task_duration:<num>, max_distsql_concurrency:<num>, rpc_info:{Cop:{num_rpc:<num>, total_time:<num>}} data:TableFullScan_4 <num> Bytes N/A
34+
└─TableFullScan_4 10000.00 5 cop[tikv] table:t tikv_task:{time:<num>, loops:<num>, scan_detail: {total_process_keys:<num>, total_process_keys_size:<num>, total_keys:<num>, get_snapshot_time:<num>, rocksdb: {key_skipped_count:<num>, block: {}}}, time_detail: {total_process_time:<num>, total_wait_time:<num>, tikv_wall_time:<num>} keep order:false, stats:pseudo N/A N/A
3335
insert into t values (6, 6);
3436
affected rows: 1
3537
info:
38+
Destination Size BackupTS Queue Time Execution Time
39+
/tmp/t_b7987183-d39f-4572-868d-75e25c8cd215 1876 451473879442653228 2024-07-29 14:56:13 2024-07-29 14:56:13
40+
affected rows: 0
41+
info:
42+
affected rows: 0
43+
info:
44+
affected rows: 0
45+
info:
46+
Destination Size BackupTS Cluster TS Queue Time Execution Time
47+
/tmp/t_b7987183-d39f-4572-868d-75e25c8cd215 1876 451473879442653228 451473880386371620 2024-07-29 14:56:17 2024-07-29 14:56:17
48+
affected rows: 0
49+
info:
50+
affected rows: 0
51+
info:
52+
affected rows: 0
53+
info:
54+
affected rows: 0
55+
info:
56+
Job_ID Data_Source Target_Table Table_ID Phase Status Source_File_Size Imported_Rows Result_Message Create_Time Start_Time End_Time Created_By
57+
3 /tmp/t_6cac1a43-c66c-4af9-962f-95287fa12432/example.t.000000000.csv `example`.`td` 453 finished 30B 6 2024-07-29 14:56:17.619215 2024-07-29 14:56:18.125792 2024-07-29 14:56:19.640005 root@%
58+
affected rows: 0
59+
info:
3660
DROP TABLE IF EXISTS t1;
3761
affected rows: 0
3862
info:

src/main.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"flag"
2121
"fmt"
2222
"os"
23+
"os/exec"
2324
"path/filepath"
2425
"regexp"
2526
"sort"
@@ -29,6 +30,7 @@ import (
2930
"time"
3031

3132
"github.com/defined2014/mysql"
33+
"github.com/google/uuid"
3234
"github.com/pingcap/errors"
3335
log "github.com/sirupsen/logrus"
3436
)
@@ -47,6 +49,8 @@ var (
4749
retryConnCount int
4850
collationDisable bool
4951
checkErr bool
52+
pathBR string
53+
pathDumpling string
5054
)
5155

5256
func init() {
@@ -63,6 +67,8 @@ func init() {
6367
flag.IntVar(&retryConnCount, "retry-connection-count", 120, "The max number to retry to connect to the database.")
6468
flag.BoolVar(&checkErr, "check-error", false, "if --error ERR does not match, return error instead of just warn")
6569
flag.BoolVar(&collationDisable, "collation-disable", false, "run collation related-test with new-collation disabled")
70+
flag.StringVar(&pathBR, "path-br", "", "Path of BR")
71+
flag.StringVar(&pathDumpling, "path-dumpling", "", "Path of Dumpling")
6672
}
6773

6874
const (
@@ -98,6 +104,11 @@ type ReplaceRegex struct {
98104
replace string
99105
}
100106

107+
type SourceAndTarget struct {
108+
sourceTable string
109+
targetTable string
110+
}
111+
101112
type tester struct {
102113
mdb *sql.DB
103114
name string
@@ -148,6 +159,12 @@ type tester struct {
148159

149160
// replace output result through --replace_regex /\.dll/.so/
150161
replaceRegex []*ReplaceRegex
162+
163+
// backup and restore context through --backup_and_restore $BACKUP_TABLE as $RESTORE_TABLE'
164+
backupAndRestore *SourceAndTarget
165+
166+
// dump and import context through --dump_and_import $SOURCE_TABLE as $TARGET_TABLE'
167+
dumpAndImport *SourceAndTarget
151168
}
152169

153170
func newTester(name string) *tester {
@@ -352,6 +369,58 @@ func (t *tester) addSuccess(testSuite *XUnitTestSuite, startTime *time.Time, cnt
352369
})
353370
}
354371

372+
func generateBRStatements(source, target string) (string, string) {
373+
// Generate a random UUID
374+
uuid := uuid.NewString()
375+
376+
// Create the TMP_DIR path
377+
tmpDir := fmt.Sprintf("/tmp/%s_%s", source, uuid)
378+
379+
// Generate the SQL statements
380+
backupSQL := fmt.Sprintf("BACKUP TABLE `%s` TO '%s'", source, tmpDir)
381+
restoreSQL := fmt.Sprintf("RESTORE TABLE `%s` FROM '%s'", source, tmpDir)
382+
383+
return backupSQL, restoreSQL
384+
}
385+
386+
func (t *tester) dumpTable(source string) (string, error) {
387+
log.Warnf("Start dumping table: %s", source)
388+
path := "/tmp/" + source + "_" + uuid.NewString()
389+
cmdArgs := []string{
390+
fmt.Sprintf("-h%s", host),
391+
fmt.Sprintf("-P%s", port),
392+
fmt.Sprintf("-u%s", user),
393+
fmt.Sprintf("-T%s.%s", t.name, source),
394+
fmt.Sprintf("-o%s", path),
395+
"--no-header",
396+
"--filetype",
397+
"csv",
398+
}
399+
400+
if passwd != "" {
401+
cmdArgs = append(cmdArgs, fmt.Sprintf("-p%s", passwd))
402+
}
403+
404+
cmd := exec.Command(pathDumpling, cmdArgs...)
405+
406+
output, err := cmd.CombinedOutput()
407+
if err != nil {
408+
log.Warnf("Failed executing commands: %s, output: %s)",
409+
cmd.String(), string(output))
410+
return "", err
411+
}
412+
log.Warnf("Done executing commands: %s, output: %s)",
413+
cmd.String(), string(output))
414+
return path, nil
415+
}
416+
417+
func (t *tester) importTableStmt(path, target string) string {
418+
return fmt.Sprintf(`
419+
IMPORT INTO %s
420+
FROM '%s/example.t.000000000.csv'
421+
`, target, path)
422+
}
423+
355424
func (t *tester) Run() error {
356425
t.preProcess()
357426
defer t.postProcess()
@@ -523,6 +592,61 @@ func (t *tester) Run() error {
523592
return errors.Annotate(err, fmt.Sprintf("Could not parse regex in --replace_regex: line: %d sql:%v", q.Line, q.Query))
524593
}
525594
t.replaceRegex = regex
595+
case Q_BACKUP_AND_RESTORE:
596+
t.backupAndRestore, err = parseSourceAndTarget(q.Query)
597+
if err != nil {
598+
return errors.Annotate(err, fmt.Sprintf("Could not parse backup table and restore table name in --backup_and_restore, line: %d sql:%v", q.Line, q.Query))
599+
}
600+
backupStmt, restoreStmt := generateBRStatements(t.backupAndRestore.sourceTable, t.backupAndRestore.targetTable)
601+
log.WithFields(log.Fields{"stmt": backupStmt, "line": q.Line}).Warn("Backup started")
602+
if err := t.executeStmt(backupStmt); err != nil {
603+
return err
604+
}
605+
log.WithFields(log.Fields{"stmt": backupStmt, "line": q.Line}).Warn("Backup end")
606+
tempTable := t.backupAndRestore.sourceTable + uuid.NewString()
607+
renameStmt := fmt.Sprintf("RENAME TABLE `%s` TO `%s`", t.backupAndRestore.sourceTable, tempTable)
608+
if err := t.executeStmt(renameStmt); err != nil {
609+
return err
610+
}
611+
dupTableStmt := fmt.Sprintf("CREATE TABLE `%s` LIKE `%s`", t.backupAndRestore.sourceTable, tempTable)
612+
if err := t.executeStmt(dupTableStmt); err != nil {
613+
return err
614+
}
615+
log.WithFields(log.Fields{"stmt": restoreStmt, "line": q.Line}).Warn("Restore start")
616+
if err := t.executeStmt(restoreStmt); err != nil {
617+
return err
618+
}
619+
log.WithFields(log.Fields{"stmt": restoreStmt, "line": q.Line}).Warn("Restore end")
620+
renameStmt = fmt.Sprintf("RENAME TABLE `%s` TO `%s`", t.backupAndRestore.sourceTable, t.backupAndRestore.targetTable)
621+
if err := t.executeStmt(renameStmt); err != nil {
622+
return err
623+
}
624+
renameStmt = fmt.Sprintf("RENAME TABLE `%s` TO `%s`", tempTable, t.backupAndRestore.sourceTable)
625+
if err := t.executeStmt(renameStmt); err != nil {
626+
return err
627+
}
628+
case Q_DUMP_AND_IMPORT:
629+
t.dumpAndImport, err = parseSourceAndTarget(q.Query)
630+
if err != nil {
631+
return err
632+
}
633+
path, err := t.dumpTable(t.dumpAndImport.sourceTable)
634+
if err != nil {
635+
return err
636+
}
637+
638+
dupTableStmt := fmt.Sprintf("CREATE TABLE `%s` LIKE `%s`", t.dumpAndImport.targetTable, t.backupAndRestore.sourceTable)
639+
if err := t.executeStmt(dupTableStmt); err != nil {
640+
return err
641+
}
642+
643+
importStmt := t.importTableStmt(path, t.dumpAndImport.targetTable)
644+
log.WithFields(log.Fields{"stmt": importStmt, "line": q.Line}).Warn("Import start")
645+
if err = t.executeStmt(importStmt); err != nil {
646+
return err
647+
}
648+
log.WithFields(log.Fields{"stmt": importStmt, "line": q.Line}).Warn("Restore end")
649+
526650
default:
527651
log.WithFields(log.Fields{"command": q.firstWord, "arguments": q.Query, "line": q.Line}).Warn("command not implemented")
528652
}

src/query.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ const (
124124
Q_COMMENT /* Comments, ignored. */
125125
Q_COMMENT_WITH_COMMAND
126126
Q_EMPTY_LINE
127+
Q_BACKUP_AND_RESTORE
128+
Q_DUMP_AND_IMPORT
127129
)
128130

129131
// ParseQueries parses an array of string into an array of query object.

src/type.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ var commandMap = map[string]int{
114114
"single_query": Q_SINGLE_QUERY,
115115
"begin_concurrent": Q_BEGIN_CONCURRENT,
116116
"end_concurrent": Q_END_CONCURRENT,
117+
"backup_and_restore": Q_BACKUP_AND_RESTORE,
118+
"dump_and_import": Q_DUMP_AND_IMPORT,
117119
}
118120

119121
func findType(cmdName string) int {

src/util.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package main
1515

1616
import (
1717
"database/sql"
18+
"fmt"
1819
"regexp"
1920
"strings"
2021
"time"
@@ -104,3 +105,21 @@ func ParseReplaceRegex(originalString string) ([]*ReplaceRegex, error) {
104105
}
105106
return ret, nil
106107
}
108+
109+
func parseSourceAndTarget(s string) (*SourceAndTarget, error) {
110+
s = strings.ToLower(strings.TrimSpace(s))
111+
112+
parts := strings.Split(s, "as")
113+
if len(parts) != 2 {
114+
return nil, errors.Errorf("Could not parse source table and target table name: %v", s)
115+
}
116+
117+
st := &SourceAndTarget{
118+
sourceTable: strings.TrimSpace(parts[0]),
119+
targetTable: strings.TrimSpace(parts[1]),
120+
}
121+
122+
fmt.Printf("Parse source: %s and target: %s\n", st.sourceTable, st.targetTable)
123+
124+
return st, nil
125+
}

t/example.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ explain analyze select * from t;
3838
--enable_info
3939
insert into t values (6, 6);
4040

41+
--backup_and_restore t AS tt
42+
43+
--dump_and_import t AS td
44+
4145
DROP TABLE IF EXISTS t1;
4246
CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT NOT NULL UNIQUE);
4347
INSERT t1 VALUES (1, 1);

0 commit comments

Comments
 (0)