├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── go-build-test.yml
│ └── golangci-lint.yml
├── .golangci.yaml
├── LICENSE
├── README-zh.md
├── README.md
├── example
├── dump
│ ├── dump.sql
│ └── main.go
└── source
│ ├── dump.sql
│ └── main.go
├── go.mod
├── go.sum
├── images
└── logo.png
├── mysqldump.go
├── source.go
├── source_test.go
└── util.go
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 报告缺陷 Bug Report
3 | about: 报告缺陷以帮助我们改进 Report defects to help us improve
4 | ---
5 |
6 | ### 描述问题 Describe the problem
7 |
8 |
12 |
13 | ````````markdown
14 | 如果是解析渲染问题的话请在此处贴入 Markdown 原文
15 | If it is a problem of parsing and rendering, please post the original Markdown here
16 | ````````
17 |
18 | ### 期待的结果 Expected result
19 |
20 |
24 |
25 | ### 截屏或录像 Screenshot or video
26 |
27 |
34 |
35 | ### 版本环境 Version environment
36 |
37 | * 版本 Version:
38 | * 操作系统 Operating system:
39 | * 浏览器(如果使用)Browser (if used):
40 |
41 | ### 其他信息 Other information
42 |
43 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Jarvan的博客
4 | url: https://jarvans.com
5 | about: 大厂程序员, 公众号 硬核的Jarvan
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 请求新功能 Request new features
3 | about: 提出你期待的功能特性 Come up with the features you expect
4 | ---
5 |
6 | ### 你在什么场景下需要该功能? In what scenarios do you need this function?
7 |
8 |
12 |
13 | ### 描述最优的解决方案 Describe the optimal solution
14 |
15 |
19 |
20 | ### 描述候选解决方案 Describe the candidate solution
21 |
22 |
26 |
27 | ### 其他信息 Other information
28 |
29 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/workflows/go-build-test.yml:
--------------------------------------------------------------------------------
1 | name: Go package
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 |
12 | - name: Set up Go
13 | uses: actions/setup-go@v4
14 | with:
15 | go-version: '1.15'
16 |
17 | - name: Build
18 | run: go build -v ./...
19 |
20 | - name: Test
21 | run: go test -json > TestResults-${{ matrix.go-version }}.json
22 | - name: Upload Go test results
23 | uses: actions/upload-artifact@v3
24 | with:
25 | name: Go-results-${{ matrix.go-version }}
26 | path: TestResults-${{ matrix.go-version }}.json
27 | retention-days: 5
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - main
7 | pull_request:
8 |
9 | permissions:
10 | contents: read
11 | # Optional: allow read access to pull request. Use with `only-new-issues` option.
12 | # pull-requests: read
13 |
14 | jobs:
15 | golangci:
16 | name: lint
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v3
20 | - uses: actions/setup-go@v4
21 | with:
22 | go-version: '1.21'
23 | cache: false
24 | - name: golangci-lint
25 | uses: golangci/golangci-lint-action@v3
26 | with:
27 | # Require: The version of golangci-lint to use.
28 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
29 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
30 | version: v1.54
31 |
32 | # Optional: working directory, useful for monorepos
33 | # working-directory: somedir
34 |
35 | # Optional: golangci-lint command line arguments.
36 | #
37 | # Note: By default, the `.golangci.yml` file should be at the root of the repository.
38 | # The location of the configuration file can be changed by using `--config=`
39 | # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0
40 |
41 | # Optional: show only new issues if it's a pull request. The default value is `false`.
42 | # only-new-issues: true
43 |
44 | # Optional: if set to true, then all caching functionality will be completely disabled,
45 | # takes precedence over all other caching options.
46 | # skip-cache: true
47 |
48 | # Optional: if set to true, then the action won't cache or restore ~/go/pkg.
49 | # skip-pkg-cache: true
50 |
51 | # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build.
52 | # skip-build-cache: true
53 |
54 | # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'.
55 | # install-mode: "goinstall"
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Lingfei Kong . All rights reserved.
2 | # Use of this source code is governed by a MIT style
3 | # license that can be found in the LICENSE file.
4 |
5 | # This file contains all available configuration options
6 | # with their default values.
7 |
8 | # options for analysis running
9 | run:
10 | # default concurrency is a available CPU number
11 | concurrency: 4
12 |
13 | # timeout for analysis, e.g. 30s, 5m, default is 1m
14 | timeout: 5m
15 |
16 | # exit code when at least one issue was found, default is 1
17 | issues-exit-code: 1
18 |
19 | # include test files or not, default is true
20 | tests: true
21 |
22 | # list of build tags, all linters use it. Default is empty list.
23 | build-tags:
24 | - mytag
25 |
26 | # which dirs to skip: issues from them won't be reported;
27 | # can use regexp here: generated.*, regexp is applied on full path;
28 | # default value is empty list, but default dirs are skipped independently
29 | # from this option's value (see skip-dirs-use-default).
30 | # "/" will be replaced by current OS file path separator to properly work
31 | # on Windows.
32 | skip-dirs:
33 | - test # 测试目录
34 | - tools # 工具目录
35 |
36 | # default is true. Enables skipping of directories:
37 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
38 | skip-dirs-use-default: true
39 |
40 | # which files to skip: they will be analyzed, but issues from them
41 | # won't be reported. Default value is empty list, but there is
42 | # no need to include all autogenerated files, we confidently recognize
43 | # autogenerated files. If it's not please let us know.
44 | # "/" will be replaced by current OS file path separator to properly work
45 | # on Windows.
46 | skip-files:
47 | - ".*\\.my\\.go$"
48 | - _test.go
49 |
50 | # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
51 | # If invoked with -mod=readonly, the go command is disallowed from the implicit
52 | # automatic updating of go.mod described above. Instead, it fails when any changes
53 | # to go.mod are needed. This setting is most useful to check that go.mod does
54 | # not need updates, such as in a continuous integration and testing system.
55 | # If invoked with -mod=vendor, the go command assumes that the vendor
56 | # directory holds the correct copies of dependencies and ignores
57 | # the dependency descriptions in go.mod.
58 | #modules-download-mode: release|readonly|vendor
59 |
60 | # Allow multiple parallel golangci-lint instances running.
61 | # If false (default) - golangci-lint acquires file lock on start.
62 | allow-parallel-runners: true
63 |
64 |
65 | # output configuration options
66 | output:
67 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
68 | format: colored-line-number
69 |
70 | # print lines of code with issue, default is true
71 | print-issued-lines: true
72 |
73 | # print linter name in the end of issue text, default is true
74 | print-linter-name: true
75 |
76 | # make issues output unique by line, default is true
77 | uniq-by-line: true
78 |
79 | # add a prefix to the output file references; default is no prefix
80 | path-prefix: ""
81 |
82 | # sorts results by: filepath, line and column
83 | sort-results: true
84 |
85 | # all available settings of specific linters
86 | linters-settings:
87 | gocyclo:
88 | # Minimal code complexity to report.
89 | # Default: 30 (but we recommend 10-20)
90 | min-complexity: 20
91 | linters:
92 | # please, do not use `enable-all`: it's deprecated and will be removed soon.
93 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
94 | # enable-all: true
95 | disable-all: true
96 | enable:
97 | - typecheck # 类型检查
98 | - bodyclose # body是否关闭
99 | - durationcheck # duration相乘检查
100 | - errcheck # 错误检查
101 | - exportloopref # 循环指针检查
102 | # - gofmt # gofmt
103 | # - gofumpt # gofumpt
104 | # - goimports # 导包顺序检查
105 | # - gosec # go安全检查
106 | - gosimple # 简化代码检查
107 | - govet
108 | - ineffassign # 变量是否未被使用
109 | - makezero # make非0长度切片
110 | - nilerr
111 | #- prealloc # 切片预分配
112 | #- revive 冗余代码检查
113 | - staticcheck # 静态检查
114 | - unparam # 无用的参数
115 | - unused # 变量是否使用
116 | - errchkjson # json err是否处理
117 | - gocyclo # 圈复杂度检查
118 | fast: false
119 |
120 | issues:
121 |
122 | # Show only new issues created after git revision `REV`
123 | # new-from-rev: REV
124 |
125 | # Show only new issues created in git patch with set file path.
126 | #new-from-patch: path/to/patch/file
127 |
128 | # Fix found issues (if it's supported by the linter)
129 | fix: false
130 |
131 |
132 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-present Dengjiawen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## mysqldump
12 |
13 | [English](./README.md)
14 |
15 | golang 实现的零依赖、支持所有类型、高性能、并发 mysqldump 工具。
16 |
17 |
18 | ## Features
19 |
20 | * 自定义 Writer: 如本地文件、多文件储存、远程服务器、云存储等。(默认控制台输出)。
21 | * 支持所有 MYSQL 数据类型.
22 | * 支持 INSERT Merge, 大幅提升数据恢复性能
23 |
24 | ## QuickStart
25 |
26 | ### Create Table and Insert Test Data
27 |
28 | ```sql
29 | DROP TABLE IF EXISTS `test`;
30 |
31 | CREATE TABLE `test` (
32 | `id` bigint unsigned NOT NULL AUTO_INCREMENT,
33 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
34 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
35 | `binary_col` binary(10) DEFAULT NULL,
36 | `varbinary_col` varbinary(255) DEFAULT NULL,
37 | `tinyblob_col` tinyblob,
38 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
39 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
40 | `blob_col` blob,
41 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
42 | `mediumblob_col` mediumblob,
43 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
44 | `longblob_col` longblob,
45 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
46 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
47 | `bit_col` bit(8) DEFAULT NULL,
48 | `tinyint_col` tinyint NOT NULL DEFAULT '0',
49 | `bool_col` tinyint(1) NOT NULL DEFAULT '0',
50 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0',
51 | `smallint_col` smallint NOT NULL DEFAULT '0',
52 | `mediumint_col` mediumint NOT NULL DEFAULT '0',
53 | `int_col` int NOT NULL DEFAULT '0',
54 | `integer_col` int NOT NULL DEFAULT '0',
55 | `bigint_col` bigint NOT NULL DEFAULT '0',
56 | `float_col` float(8,2) NOT NULL DEFAULT '0.00',
57 | `double_col` double(8,2) NOT NULL DEFAULT '0.00',
58 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00',
59 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00',
60 | `date_col` date DEFAULT NULL,
61 | `datetime_col` datetime DEFAULT NULL,
62 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
63 | `time_col` time DEFAULT NULL,
64 | `year_col` year DEFAULT NULL,
65 | PRIMARY KEY (`id`)
66 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
67 |
68 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023);
69 |
70 | ```
71 |
72 | ### Dump SQL
73 |
74 | ```go
75 | import (
76 | "os"
77 |
78 | "github.com/jarvanstack/mysqldump"
79 | )
80 |
81 | func main() {
82 |
83 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
84 |
85 | f, _ := os.Create("dump.sql")
86 |
87 | _ = mysqldump.Dump(
88 | dsn, // DSN
89 | mysqldump.WithDropTable(), // Option: Delete table before create (Default: Not delete table)
90 | mysqldump.WithData(), // Option: Dump Data (Default: Only dump table schema)
91 | mysqldump.WithTables("test"), // Option: Dump Tables (Default: All tables)
92 | mysqldump.WithWriter(f), // Option: Writer (Default: os.Stdout)
93 | )
94 | }
95 |
96 | ```
97 |
98 | ### Output File dump.sql
99 |
100 | ```sql
101 | -- ----------------------------
102 | -- MySQL Database Dump
103 | -- Start Time: 2023-04-21 14:16:56
104 | -- ----------------------------
105 |
106 |
107 | USE `dc3`;
108 | DROP TABLE IF EXISTS `test`;
109 | -- ----------------------------
110 | -- Table structure for test
111 | -- ----------------------------
112 | CREATE TABLE IF NOT EXISTS `test` (
113 | `id` bigint unsigned NOT NULL AUTO_INCREMENT,
114 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
115 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
116 | `binary_col` binary(10) DEFAULT NULL,
117 | `varbinary_col` varbinary(255) DEFAULT NULL,
118 | `tinyblob_col` tinyblob,
119 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
120 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
121 | `blob_col` blob,
122 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
123 | `mediumblob_col` mediumblob,
124 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
125 | `longblob_col` longblob,
126 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
127 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
128 | `bit_col` bit(8) DEFAULT NULL,
129 | `tinyint_col` tinyint NOT NULL DEFAULT '0',
130 | `bool_col` tinyint(1) NOT NULL DEFAULT '0',
131 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0',
132 | `smallint_col` smallint NOT NULL DEFAULT '0',
133 | `mediumint_col` mediumint NOT NULL DEFAULT '0',
134 | `int_col` int NOT NULL DEFAULT '0',
135 | `integer_col` int NOT NULL DEFAULT '0',
136 | `bigint_col` bigint NOT NULL DEFAULT '0',
137 | `float_col` float(8,2) NOT NULL DEFAULT '0.00',
138 | `double_col` double(8,2) NOT NULL DEFAULT '0.00',
139 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00',
140 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00',
141 | `date_col` date DEFAULT NULL,
142 | `datetime_col` datetime DEFAULT NULL,
143 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
144 | `time_col` time DEFAULT NULL,
145 | `year_col` year DEFAULT NULL,
146 | PRIMARY KEY (`id`)
147 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
148 |
149 |
150 |
151 | -- ----------------------------
152 | -- Records of test
153 | -- ----------------------------
154 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023);
155 |
156 |
157 | -- ----------------------------
158 | -- Dumped by mysqldump2
159 | -- Cost Time: 5.81592ms
160 | -- ----------------------------
161 |
162 | ```
163 |
164 | ### Source SQL
165 |
166 | ```go
167 | import (
168 | "os"
169 |
170 | "github.com/jarvanstack/mysqldump"
171 | )
172 |
173 | func main() {
174 |
175 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
176 | f, _ := os.Open("dump.sql")
177 |
178 | _ = mysqldump.Source(
179 | dsn,
180 | f,
181 | mysqldump.WithMergeInsert(1000), // Option: Merge insert 1000 (Default: Not merge insert)
182 | mysqldump.WithDebug(), // Option: Print execute sql (Default: Not print execute sql)
183 | )
184 | }
185 | ```
186 |
187 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## mysqldump
12 |
13 | [简体中文](README-zh.md)
14 |
15 | A zero-dependency,all data types are supported, high-performance, concurrent mysqldump tool implemented in golang.
16 |
17 | ## Features
18 |
19 | * Supports custom Writer: data can be written to any Writer, such as local files, multiple file storage, remote servers, cloud storage, etc. (default console output).
20 | * Supports all MySQL data types QuickStart.
21 | * Support Merge Insert Option in Source Greatly improve data recovery performance
22 |
23 |
24 | ## QuickStart
25 |
26 | ### Create Table and Insert Test Data
27 |
28 | ```sql
29 | DROP TABLE IF EXISTS `test`;
30 |
31 | CREATE TABLE `test` (
32 | `id` bigint unsigned NOT NULL AUTO_INCREMENT,
33 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
34 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
35 | `binary_col` binary(10) DEFAULT NULL,
36 | `varbinary_col` varbinary(255) DEFAULT NULL,
37 | `tinyblob_col` tinyblob,
38 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
39 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
40 | `blob_col` blob,
41 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
42 | `mediumblob_col` mediumblob,
43 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
44 | `longblob_col` longblob,
45 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
46 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
47 | `bit_col` bit(8) DEFAULT NULL,
48 | `tinyint_col` tinyint NOT NULL DEFAULT '0',
49 | `bool_col` tinyint(1) NOT NULL DEFAULT '0',
50 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0',
51 | `smallint_col` smallint NOT NULL DEFAULT '0',
52 | `mediumint_col` mediumint NOT NULL DEFAULT '0',
53 | `int_col` int NOT NULL DEFAULT '0',
54 | `integer_col` int NOT NULL DEFAULT '0',
55 | `bigint_col` bigint NOT NULL DEFAULT '0',
56 | `float_col` float(8,2) NOT NULL DEFAULT '0.00',
57 | `double_col` double(8,2) NOT NULL DEFAULT '0.00',
58 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00',
59 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00',
60 | `date_col` date DEFAULT NULL,
61 | `datetime_col` datetime DEFAULT NULL,
62 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
63 | `time_col` time DEFAULT NULL,
64 | `year_col` year DEFAULT NULL,
65 | PRIMARY KEY (`id`)
66 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
67 |
68 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023);
69 |
70 | ```
71 |
72 | ### Dump SQL
73 |
74 | ```go
75 | import (
76 | "os"
77 |
78 | "github.com/jarvanstack/mysqldump"
79 | )
80 |
81 | func main() {
82 |
83 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
84 |
85 | f, _ := os.Create("dump.sql")
86 |
87 | _ = mysqldump.Dump(
88 | dsn, // DSN
89 | mysqldump.WithDropTable(), // Option: Delete table before create (Default: Not delete table)
90 | mysqldump.WithData(), // Option: Dump Data (Default: Only dump table schema)
91 | mysqldump.WithTables("test"), // Option: Dump Tables (Default: All tables)
92 | mysqldump.WithWriter(f), // Option: Writer (Default: os.Stdout)
93 | )
94 | }
95 | ```
96 |
97 | ### Output File dump.sql
98 |
99 | ```sql
100 | -- ----------------------------
101 | -- MySQL Database Dump
102 | -- Start Time: 2023-04-21 14:16:56
103 | -- ----------------------------
104 |
105 |
106 | USE `dc3`;
107 | DROP TABLE IF EXISTS `test`;
108 | -- ----------------------------
109 | -- Table structure for test
110 | -- ----------------------------
111 | CREATE TABLE IF NOT EXISTS `test` (
112 | `id` bigint unsigned NOT NULL AUTO_INCREMENT,
113 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
114 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
115 | `binary_col` binary(10) DEFAULT NULL,
116 | `varbinary_col` varbinary(255) DEFAULT NULL,
117 | `tinyblob_col` tinyblob,
118 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
119 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
120 | `blob_col` blob,
121 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
122 | `mediumblob_col` mediumblob,
123 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
124 | `longblob_col` longblob,
125 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
126 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
127 | `bit_col` bit(8) DEFAULT NULL,
128 | `tinyint_col` tinyint NOT NULL DEFAULT '0',
129 | `bool_col` tinyint(1) NOT NULL DEFAULT '0',
130 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0',
131 | `smallint_col` smallint NOT NULL DEFAULT '0',
132 | `mediumint_col` mediumint NOT NULL DEFAULT '0',
133 | `int_col` int NOT NULL DEFAULT '0',
134 | `integer_col` int NOT NULL DEFAULT '0',
135 | `bigint_col` bigint NOT NULL DEFAULT '0',
136 | `float_col` float(8,2) NOT NULL DEFAULT '0.00',
137 | `double_col` double(8,2) NOT NULL DEFAULT '0.00',
138 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00',
139 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00',
140 | `date_col` date DEFAULT NULL,
141 | `datetime_col` datetime DEFAULT NULL,
142 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
143 | `time_col` time DEFAULT NULL,
144 | `year_col` year DEFAULT NULL,
145 | PRIMARY KEY (`id`)
146 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
147 |
148 |
149 |
150 | -- ----------------------------
151 | -- Records of test
152 | -- ----------------------------
153 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023);
154 |
155 |
156 | -- ----------------------------
157 | -- Dumped by mysqldump2
158 | -- Cost Time: 5.81592ms
159 | -- ----------------------------
160 |
161 | ```
162 |
163 | ### Source SQL
164 |
165 | ```go
166 | import (
167 | "os"
168 |
169 | "github.com/jarvanstack/mysqldump"
170 | )
171 |
172 | func main() {
173 |
174 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
175 | f, _ := os.Open("dump.sql")
176 |
177 | _ = mysqldump.Source(
178 | dsn,
179 | f,
180 | mysqldump.WithMergeInsert(1000), // Option: Merge insert 1000 (Default: Not merge insert)
181 | mysqldump.WithDebug(), // Option: Print execute sql (Default: Not print execute sql)
182 | )
183 | }
184 | ```
185 |
186 |
--------------------------------------------------------------------------------
/example/dump/dump.sql:
--------------------------------------------------------------------------------
1 | -- ----------------------------
2 | -- MySQL Database Dump
3 | -- Start Time: 2023-04-21 14:16:56
4 | -- ----------------------------
5 |
6 |
7 | USE `dc3`;
8 | DROP TABLE IF EXISTS `test`;
9 | -- ----------------------------
10 | -- Table structure for test
11 | -- ----------------------------
12 | CREATE TABLE IF NOT EXISTS `test` (
13 | `id` bigint unsigned NOT NULL AUTO_INCREMENT,
14 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
15 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
16 | `binary_col` binary(10) DEFAULT NULL,
17 | `varbinary_col` varbinary(255) DEFAULT NULL,
18 | `tinyblob_col` tinyblob,
19 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
20 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
21 | `blob_col` blob,
22 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
23 | `mediumblob_col` mediumblob,
24 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
25 | `longblob_col` longblob,
26 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
27 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
28 | `bit_col` bit(8) DEFAULT NULL,
29 | `tinyint_col` tinyint NOT NULL DEFAULT '0',
30 | `bool_col` tinyint(1) NOT NULL DEFAULT '0',
31 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0',
32 | `smallint_col` smallint NOT NULL DEFAULT '0',
33 | `mediumint_col` mediumint NOT NULL DEFAULT '0',
34 | `int_col` int NOT NULL DEFAULT '0',
35 | `integer_col` int NOT NULL DEFAULT '0',
36 | `bigint_col` bigint NOT NULL DEFAULT '0',
37 | `float_col` float(8,2) NOT NULL DEFAULT '0.00',
38 | `double_col` double(8,2) NOT NULL DEFAULT '0.00',
39 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00',
40 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00',
41 | `date_col` date DEFAULT NULL,
42 | `datetime_col` datetime DEFAULT NULL,
43 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
44 | `time_col` time DEFAULT NULL,
45 | `year_col` year DEFAULT NULL,
46 | PRIMARY KEY (`id`)
47 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
48 |
49 |
50 |
51 | -- ----------------------------
52 | -- Records of test
53 | -- ----------------------------
54 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023);
55 |
56 |
57 | -- ----------------------------
58 | -- Dumped by mysqldump2
59 | -- Cost Time: 5.81592ms
60 | -- ----------------------------
61 |
--------------------------------------------------------------------------------
/example/dump/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/jarvanstack/mysqldump"
7 | )
8 |
9 | func main() {
10 |
11 | dsn := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
12 |
13 | f, _ := os.Create("dump.sql")
14 |
15 | _ = mysqldump.Dump(
16 | dsn, // DSN
17 | mysqldump.WithDropTable(), // Option: Delete table before create (Default: Not delete table)
18 | mysqldump.WithData(), // Option: Dump Data (Default: Only dump table schema)
19 | mysqldump.WithTables("test"), // Option: Dump Tables (Default: All tables)
20 | mysqldump.WithWriter(f), // Option: Writer (Default: os.Stdout)
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/example/source/dump.sql:
--------------------------------------------------------------------------------
1 | -- ----------------------------
2 | -- MySQL Database Dump
3 | -- Start Time: 2023-04-21 14:04:27
4 | -- ----------------------------
5 |
6 |
7 | USE `dc3`;
8 | DROP TABLE IF EXISTS `test`;
9 | -- ----------------------------
10 | -- Table structure for test
11 | -- ----------------------------
12 | CREATE TABLE IF NOT EXISTS `test` (
13 | `id` bigint unsigned NOT NULL AUTO_INCREMENT,
14 | `char_col` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
15 | `varchar_col` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
16 | `binary_col` binary(10) DEFAULT NULL,
17 | `varbinary_col` varbinary(255) DEFAULT NULL,
18 | `tinyblob_col` tinyblob,
19 | `tinytext_col` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
20 | `text_col` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
21 | `blob_col` blob,
22 | `mediumtext_col` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
23 | `mediumblob_col` mediumblob,
24 | `longtext_col` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
25 | `longblob_col` longblob,
26 | `enum_col` enum('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
27 | `set_col` set('value1','value2','value3') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
28 | `bit_col` bit(8) DEFAULT NULL,
29 | `tinyint_col` tinyint NOT NULL DEFAULT '0',
30 | `bool_col` tinyint(1) NOT NULL DEFAULT '0',
31 | `boolean_col` tinyint(1) NOT NULL DEFAULT '0',
32 | `smallint_col` smallint NOT NULL DEFAULT '0',
33 | `mediumint_col` mediumint NOT NULL DEFAULT '0',
34 | `int_col` int NOT NULL DEFAULT '0',
35 | `integer_col` int NOT NULL DEFAULT '0',
36 | `bigint_col` bigint NOT NULL DEFAULT '0',
37 | `float_col` float(8,2) NOT NULL DEFAULT '0.00',
38 | `double_col` double(8,2) NOT NULL DEFAULT '0.00',
39 | `decimal_col` decimal(10,2) NOT NULL DEFAULT '0.00',
40 | `dec_col` decimal(10,2) NOT NULL DEFAULT '0.00',
41 | `date_col` date DEFAULT NULL,
42 | `datetime_col` datetime DEFAULT NULL,
43 | `timestamp_col` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
44 | `time_col` time DEFAULT NULL,
45 | `year_col` year DEFAULT NULL,
46 | PRIMARY KEY (`id`)
47 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
48 |
49 |
50 |
51 | -- ----------------------------
52 | -- Records of test
53 | -- ----------------------------
54 | INSERT INTO `test` VALUES (1,'abc','def',0x61626300000000000000,0x646566,0x74696E79626C6F62,'Hello','World',0x776F726C64,'Medium Text',0x4D656469756D426C6F62,'Long Text',0x4C6F6E67426C6F62,'value2','value1,value3',0x66,-128,1,0,-32768,-8388608,-2147483648,-2147483648,-9223372036854775808,1234.56,1234.56,1234.56,1234.56,'2023-03-17','2023-03-17 10:00:00','2023-03-17 14:04:46','10:00:00',2023);
55 |
56 |
57 | -- ----------------------------
58 | -- Dumped by mysqldump2
59 | -- Cost Time: 10.353526ms
60 | -- ----------------------------
61 |
--------------------------------------------------------------------------------
/example/source/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/jarvanstack/mysqldump"
7 | )
8 |
9 | func main() {
10 |
11 | dns := "root:rootpasswd@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
12 | f, _ := os.Open("dump.sql")
13 |
14 | _ = mysqldump.Source(
15 | dns,
16 | f,
17 | mysqldump.WithMergeInsert(1000), // Option: Merge insert 1000 (Default: Not merge insert)
18 | mysqldump.WithDebug(), // Option: Print execute sql (Default: Not print execute sql)
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/jarvanstack/mysqldump
2 |
3 | go 1.18
4 |
5 | require github.com/go-sql-driver/mysql v1.7.0
6 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
3 |
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jarvanstack/mysqldump/8489f418ffe577e3a517ca17e9616879e41f65fa/images/logo.png
--------------------------------------------------------------------------------
/mysqldump.go:
--------------------------------------------------------------------------------
1 | package mysqldump
2 |
3 | import (
4 | "bufio"
5 | "database/sql"
6 | "fmt"
7 | "io"
8 | "log"
9 | "os"
10 | "strings"
11 | "time"
12 |
13 | _ "github.com/go-sql-driver/mysql"
14 | )
15 |
16 | func init() {
17 | // 打印 日志 行数
18 | log.SetFlags(log.Lshortfile | log.LstdFlags)
19 | }
20 |
21 | type dumpOption struct {
22 | // 导出表数据
23 | isData bool
24 |
25 | // 导出指定表, 与 isAllTables 互斥, isAllTables 优先级高
26 | tables []string
27 | // 导出全部表
28 | isAllTable bool
29 | // 是否删除表
30 | isDropTable bool
31 |
32 | // writer 默认为 os.Stdout
33 | writer io.Writer
34 | }
35 |
36 | type DumpOption func(*dumpOption)
37 |
38 | // 删除表
39 | func WithDropTable() DumpOption {
40 | return func(option *dumpOption) {
41 | option.isDropTable = true
42 | }
43 | }
44 |
45 | // 导出表数据
46 | func WithData() DumpOption {
47 | return func(option *dumpOption) {
48 | option.isData = true
49 | }
50 | }
51 |
52 | // 导出指定表, 与 WithAllTables 互斥, WithAllTables 优先级高
53 | func WithTables(tables ...string) DumpOption {
54 | return func(option *dumpOption) {
55 | option.tables = tables
56 | }
57 | }
58 |
59 | // 导出全部表
60 | func WithAllTable() DumpOption {
61 | return func(option *dumpOption) {
62 | option.isAllTable = true
63 | }
64 | }
65 |
66 | // 导出到指定 writer
67 | func WithWriter(writer io.Writer) DumpOption {
68 | return func(option *dumpOption) {
69 | option.writer = writer
70 | }
71 | }
72 |
73 | func Dump(dsn string, opts ...DumpOption) error {
74 | // 打印开始
75 | start := time.Now()
76 | log.Printf("[info] [dump] start at %s\n", start.Format("2006-01-02 15:04:05"))
77 | // 打印结束
78 | defer func() {
79 | end := time.Now()
80 | log.Printf("[info] [dump] end at %s, cost %s\n", end.Format("2006-01-02 15:04:05"), end.Sub(start))
81 | }()
82 |
83 | var err error
84 |
85 | var o dumpOption
86 |
87 | for _, opt := range opts {
88 | opt(&o)
89 | }
90 |
91 | if len(o.tables) == 0 {
92 | // 默认包含全部表
93 | o.isAllTable = true
94 | }
95 |
96 | if o.writer == nil {
97 | // 默认输出到 os.Stdout
98 | o.writer = os.Stdout
99 | }
100 |
101 | buf := bufio.NewWriter(o.writer)
102 | defer buf.Flush()
103 |
104 | // 打印 Header
105 | _, _ = buf.WriteString("-- ----------------------------\n")
106 | _, _ = buf.WriteString("-- MySQL Database Dump\n")
107 | _, _ = buf.WriteString("-- Start Time: " + start.Format("2006-01-02 15:04:05") + "\n")
108 | _, _ = buf.WriteString("-- ----------------------------\n")
109 | _, _ = buf.WriteString("\n\n")
110 |
111 | // 连接数据库
112 | db, err := sql.Open("mysql", dsn)
113 | if err != nil {
114 | log.Printf("[error] %v \n", err)
115 | return err
116 | }
117 | defer db.Close()
118 |
119 | // 1. 获取数据库
120 | dbName, err := GetDBNameFromDSN(dsn)
121 | if err != nil {
122 | log.Printf("[error] %v \n", err)
123 | return err
124 | }
125 | _, err = db.Exec(fmt.Sprintf("USE `%s`", dbName))
126 | if err != nil {
127 | log.Printf("[error] %v \n", err)
128 | return err
129 | }
130 |
131 | // 2. 获取表
132 | var tables []string
133 | if o.isAllTable {
134 | tmp, err := getAllTables(db)
135 | if err != nil {
136 | log.Printf("[error] %v \n", err)
137 | return err
138 | }
139 | tables = tmp
140 | } else {
141 | tables = o.tables
142 | }
143 |
144 | // 3. 导出表
145 | for _, table := range tables {
146 | // 删除表
147 | if o.isDropTable {
148 | _, _ = buf.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table))
149 | }
150 |
151 | // 导出表结构
152 | err = writeTableStruct(db, table, buf)
153 | if err != nil {
154 | log.Printf("[error] %v \n", err)
155 | return err
156 | }
157 |
158 | // 导出表数据
159 | if o.isData {
160 | err = writeTableData(db, table, buf)
161 | if err != nil {
162 | log.Printf("[error] %v \n", err)
163 | return err
164 | }
165 | }
166 | }
167 |
168 | // 导出每个表的结构和数据
169 |
170 | _, _ = buf.WriteString("-- ----------------------------\n")
171 | _, _ = buf.WriteString("-- Dumped by mysqldump\n")
172 | _, _ = buf.WriteString("-- Cost Time: " + time.Since(start).String() + "\n")
173 | _, _ = buf.WriteString("-- ----------------------------\n")
174 | buf.Flush()
175 |
176 | return nil
177 | }
178 |
179 | func getCreateTableSQL(db *sql.DB, table string) (string, error) {
180 | var createTableSQL string
181 | err := db.QueryRow(fmt.Sprintf("SHOW CREATE TABLE `%s`", table)).Scan(&table, &createTableSQL)
182 | if err != nil {
183 | return "", err
184 | }
185 | // IF NOT EXISTS
186 | createTableSQL = strings.Replace(createTableSQL, "CREATE TABLE", "CREATE TABLE IF NOT EXISTS", 1)
187 | return createTableSQL, nil
188 | }
189 |
190 | func getAllTables(db *sql.DB) ([]string, error) {
191 | var tables []string
192 | rows, err := db.Query("SHOW TABLES")
193 | if err != nil {
194 | return nil, err
195 | }
196 | defer rows.Close()
197 |
198 | for rows.Next() {
199 | var table string
200 | err = rows.Scan(&table)
201 | if err != nil {
202 | return nil, err
203 | }
204 | tables = append(tables, table)
205 | }
206 | return tables, nil
207 | }
208 |
209 | func writeTableStruct(db *sql.DB, table string, buf *bufio.Writer) error {
210 | // 导出表结构
211 | _, _ = buf.WriteString("-- ----------------------------\n")
212 | _, _ = buf.WriteString(fmt.Sprintf("-- Table structure for %s\n", table))
213 | _, _ = buf.WriteString("-- ----------------------------\n")
214 |
215 | createTableSQL, err := getCreateTableSQL(db, table)
216 | if err != nil {
217 | log.Printf("[error] %v \n", err)
218 | return err
219 | }
220 | _, _ = buf.WriteString(createTableSQL)
221 | _, _ = buf.WriteString(";")
222 |
223 | _, _ = buf.WriteString("\n\n")
224 | _, _ = buf.WriteString("\n\n")
225 | return nil
226 | }
227 |
228 | // 禁止 golangci-lint 检查
229 | // nolint: gocyclo
230 | func writeTableData(db *sql.DB, table string, buf *bufio.Writer) error {
231 |
232 | // 导出表数据
233 | _, _ = buf.WriteString("-- ----------------------------\n")
234 | _, _ = buf.WriteString(fmt.Sprintf("-- Records of %s\n", table))
235 | _, _ = buf.WriteString("-- ----------------------------\n")
236 |
237 | lineRows, err := db.Query(fmt.Sprintf("SELECT * FROM `%s`", table))
238 | if err != nil {
239 | log.Printf("[error] %v \n", err)
240 | return err
241 | }
242 | defer lineRows.Close()
243 |
244 | var columns []string
245 | columns, err = lineRows.Columns()
246 | if err != nil {
247 | log.Printf("[error] %v \n", err)
248 | return err
249 | }
250 | columnTypes, err := lineRows.ColumnTypes()
251 | if err != nil {
252 | log.Printf("[error] %v \n", err)
253 | return err
254 | }
255 |
256 | var values [][]interface{}
257 | for lineRows.Next() {
258 | row := make([]interface{}, len(columns))
259 | rowPointers := make([]interface{}, len(columns))
260 | for i := range columns {
261 | rowPointers[i] = &row[i]
262 | }
263 | err = lineRows.Scan(rowPointers...)
264 | if err != nil {
265 | log.Printf("[error] %v \n", err)
266 | return err
267 | }
268 | values = append(values, row)
269 | }
270 |
271 | for _, row := range values {
272 | ssql := "INSERT INTO `" + table + "` VALUES ("
273 |
274 | for i, col := range row {
275 | if col == nil {
276 | ssql += "NULL"
277 | } else {
278 | Type := columnTypes[i].DatabaseTypeName()
279 | // 去除 UNSIGNED 和空格
280 | Type = strings.Replace(Type, "UNSIGNED", "", -1)
281 | Type = strings.Replace(Type, " ", "", -1)
282 | switch Type {
283 | case "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "INTEGER", "BIGINT":
284 | if bs, ok := col.([]byte); ok {
285 | ssql += string(bs)
286 | } else {
287 | ssql += fmt.Sprintf("%d", col)
288 | }
289 | case "FLOAT", "DOUBLE":
290 | if bs, ok := col.([]byte); ok {
291 | ssql += string(bs)
292 | } else {
293 | ssql += fmt.Sprintf("%f", col)
294 | }
295 | case "DECIMAL", "DEC":
296 | ssql += fmt.Sprintf("%s", col)
297 |
298 | case "DATE":
299 | t, ok := col.(time.Time)
300 | if !ok {
301 | log.Println("DATE 类型转换错误")
302 | return err
303 | }
304 | ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02"))
305 | case "DATETIME":
306 | t, ok := col.(time.Time)
307 | if !ok {
308 | log.Println("DATETIME 类型转换错误")
309 | return err
310 | }
311 | ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02 15:04:05"))
312 | case "TIMESTAMP":
313 | t, ok := col.(time.Time)
314 | if !ok {
315 | log.Println("TIMESTAMP 类型转换错误")
316 | return err
317 | }
318 | ssql += fmt.Sprintf("'%s'", t.Format("2006-01-02 15:04:05"))
319 | case "TIME":
320 | t, ok := col.([]byte)
321 | if !ok {
322 | log.Println("TIME 类型转换错误")
323 | return err
324 | }
325 | ssql += fmt.Sprintf("'%s'", string(t))
326 | case "YEAR":
327 | t, ok := col.([]byte)
328 | if !ok {
329 | log.Println("YEAR 类型转换错误")
330 | return err
331 | }
332 | ssql += string(t)
333 | case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT":
334 | ssql += fmt.Sprintf("'%s'", strings.Replace(fmt.Sprintf("%s", col), "'", "''", -1))
335 | case "BIT", "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB":
336 | ssql += fmt.Sprintf("0x%X", col)
337 | case "ENUM", "SET":
338 | ssql += fmt.Sprintf("'%s'", col)
339 | case "BOOL", "BOOLEAN":
340 | if col.(bool) {
341 | ssql += "true"
342 | } else {
343 | ssql += "false"
344 | }
345 | case "JSON":
346 | ssql += fmt.Sprintf("'%s'", col)
347 | default:
348 | // unsupported type
349 | log.Printf("unsupported type: %s", Type)
350 | return fmt.Errorf("unsupported type: %s", Type)
351 | }
352 | }
353 | if i < len(row)-1 {
354 | ssql += ","
355 | }
356 | }
357 | ssql += ");\n"
358 | _, _ = buf.WriteString(ssql)
359 | }
360 |
361 | _, _ = buf.WriteString("\n\n")
362 | return nil
363 | }
364 |
--------------------------------------------------------------------------------
/source.go:
--------------------------------------------------------------------------------
1 | package mysqldump
2 |
3 | import (
4 | "bufio"
5 | "database/sql"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "log"
10 | "strings"
11 | "time"
12 | )
13 |
14 | type sourceOption struct {
15 | dryRun bool
16 | mergeInsert int
17 | debug bool
18 | }
19 | type SourceOption func(*sourceOption)
20 |
21 | func WithDryRun() SourceOption {
22 | return func(o *sourceOption) {
23 | o.dryRun = true
24 | }
25 | }
26 |
27 | func WithMergeInsert(size int) SourceOption {
28 | return func(o *sourceOption) {
29 | o.mergeInsert = size
30 | }
31 | }
32 |
33 | // WithDebug 打印执行的 SQL
34 | func WithDebug() SourceOption {
35 | return func(o *sourceOption) {
36 | o.debug = true
37 | }
38 | }
39 |
40 | type dbWrapper struct {
41 | DB *sql.DB
42 | debug bool
43 | dryRun bool
44 | }
45 |
46 | func newDBWrapper(db *sql.DB, dryRun, debug bool) *dbWrapper {
47 |
48 | return &dbWrapper{
49 | DB: db,
50 | dryRun: dryRun,
51 | debug: debug,
52 | }
53 | }
54 |
55 | func (db *dbWrapper) Exec(query string, args ...interface{}) (sql.Result, error) {
56 | if db.debug {
57 | log.Printf("[debug] [query]\n%s\n", query)
58 | }
59 |
60 | if db.dryRun {
61 | return nil, nil
62 | }
63 | return db.DB.Exec(query, args...)
64 | }
65 |
66 | // Source 加载
67 | // 禁止 golangci-lint 检查
68 | // nolint: gocyclo
69 | func Source(dsn string, reader io.Reader, opts ...SourceOption) error {
70 | // 打印开始
71 | start := time.Now()
72 | log.Printf("[info] [source] start at %s\n", start.Format("2006-01-02 15:04:05"))
73 | // 打印结束
74 | defer func() {
75 | end := time.Now()
76 | log.Printf("[info] [source] end at %s, cost %s\n", end.Format("2006-01-02 15:04:05"), end.Sub(start))
77 | }()
78 |
79 | var err error
80 | var db *sql.DB
81 | var o sourceOption
82 | for _, opt := range opts {
83 | opt(&o)
84 | }
85 |
86 | dbName, err := GetDBNameFromDSN(dsn)
87 | if err != nil {
88 | log.Printf("[error] %v\n", err)
89 | return err
90 | }
91 |
92 | // Open database
93 | db, err = sql.Open("mysql", dsn)
94 | if err != nil {
95 | log.Printf("[error] %v\n", err)
96 | return err
97 | }
98 | defer db.Close()
99 |
100 | // DB Wrapper
101 | dbWrapper := newDBWrapper(db, o.dryRun, o.debug)
102 |
103 | // Use database
104 | _, err = dbWrapper.Exec(fmt.Sprintf("USE %s;", dbName))
105 | if err != nil {
106 | log.Printf("[error] %v\n", err)
107 | return err
108 | }
109 |
110 | // 设置超时时间1小时
111 | db.SetConnMaxLifetime(3600)
112 |
113 | // 一句一句执行
114 | r := bufio.NewReader(reader)
115 | // 关闭事务
116 | _, err = dbWrapper.Exec("SET autocommit=0;")
117 | if err != nil {
118 | log.Printf("[error] %v\n", err)
119 | return err
120 | }
121 |
122 | for {
123 | line, err := r.ReadString(';')
124 | if err != nil {
125 | if err == io.EOF {
126 | break
127 | }
128 | log.Printf("[error] %v\n", err)
129 | return err
130 | }
131 |
132 | ssql := string(line)
133 |
134 | // 删除末尾的换行符
135 | ssql = trim(ssql)
136 | if err != nil {
137 | log.Printf("[error] [trim] %v\n", err)
138 | return err
139 | }
140 |
141 | // 如果 INSERT 开始, 并且 mergeInsert 为 true, 则合并 INSERT
142 | if o.mergeInsert > 1 && strings.HasPrefix(ssql, "INSERT INTO") {
143 | var insertSQLs []string
144 | insertSQLs = append(insertSQLs, ssql)
145 | for i := 0; i < o.mergeInsert-1; i++ {
146 | line, err := r.ReadString(';')
147 | if err != nil {
148 | if err == io.EOF {
149 | break
150 | }
151 | log.Printf("[error] %v\n", err)
152 | return err
153 | }
154 |
155 | ssql2 := string(line)
156 | ssql2 = trim(ssql2)
157 | if err != nil {
158 | log.Printf("[error] [trim] %v\n", err)
159 | return err
160 | }
161 | if strings.HasPrefix(ssql2, "INSERT INTO") {
162 | insertSQLs = append(insertSQLs, ssql2)
163 | continue
164 | }
165 |
166 | break
167 | }
168 | // 合并 INSERT
169 | ssql, err = mergeInsert(insertSQLs)
170 | if err != nil {
171 | log.Printf("[error] [mergeInsert] %v\n", err)
172 | return err
173 | }
174 | }
175 |
176 | _, err = dbWrapper.Exec(ssql)
177 | if err != nil {
178 | log.Printf("[error] %v\n", err)
179 | return err
180 | }
181 | }
182 |
183 | // 提交事务
184 | _, err = dbWrapper.Exec("COMMIT;")
185 | if err != nil {
186 | log.Printf("[error] %v\n", err)
187 | return err
188 | }
189 |
190 | // 开启事务
191 | _, err = dbWrapper.Exec("SET autocommit=1;")
192 | if err != nil {
193 | log.Printf("[error] %v\n", err)
194 | return err
195 | }
196 |
197 | return nil
198 | }
199 |
200 | /*
201 | 将多个 INSERT 合并为一个
202 | 输入:
203 | INSERT INTO `test` VALUES (1, 'a');
204 | INSERT INTO `test` VALUES (2, 'b');
205 | 输出
206 | INSERT INTO `test` VALUES (1, 'a'), (2, 'b');
207 | */
208 | func mergeInsert(insertSQLs []string) (string, error) {
209 | if len(insertSQLs) == 0 {
210 | return "", errors.New("no input provided")
211 | }
212 | builder := strings.Builder{}
213 | sql1 := insertSQLs[0]
214 | sql1 = strings.TrimSuffix(sql1, ";")
215 | builder.WriteString(sql1)
216 | for i, insertSQL := range insertSQLs[1:] {
217 | if i < len(insertSQLs)-1 {
218 | builder.WriteString(",")
219 | }
220 |
221 | valuesIdx := strings.Index(insertSQL, "VALUES")
222 | if valuesIdx == -1 {
223 | return "", errors.New("invalid SQL: missing VALUES keyword")
224 | }
225 | sqln := insertSQL[valuesIdx:]
226 | sqln = strings.TrimPrefix(sqln, "VALUES")
227 | sqln = strings.TrimSuffix(sqln, ";")
228 | builder.WriteString(sqln)
229 |
230 | }
231 | builder.WriteString(";")
232 |
233 | return builder.String(), nil
234 | }
235 |
236 | // 删除空白符换行符和注释
237 | func trim(s string) string {
238 | s = strings.TrimLeft(s, "\n")
239 | s = strings.TrimSpace(s)
240 | return s
241 | }
242 |
--------------------------------------------------------------------------------
/source_test.go:
--------------------------------------------------------------------------------
1 | package mysqldump
2 |
3 | import "testing"
4 |
5 | func Test_mergeInsert(t *testing.T) {
6 | type args struct {
7 | insertSQLs []string
8 | }
9 | tests := []struct {
10 | name string
11 | args args
12 | want string
13 | wantErr bool
14 | }{
15 | {
16 | args: args{
17 | insertSQLs: []string{
18 | "INSERT INTO `test` VALUES (1, 'a');",
19 | "INSERT INTO `test` VALUES (2, 'b');",
20 | },
21 | },
22 | want: "INSERT INTO `test` VALUES (1, 'a'), (2, 'b');",
23 | wantErr: false,
24 | },
25 | }
26 | for _, tt := range tests {
27 | t.Run(tt.name, func(t *testing.T) {
28 | got, err := mergeInsert(tt.args.insertSQLs)
29 | if (err != nil) != tt.wantErr {
30 | t.Errorf("mergeInsert() error = %v, wantErr %v", err, tt.wantErr)
31 | return
32 | }
33 | if got != tt.want {
34 | t.Errorf("mergeInsert() = %v, want %v", got, tt.want)
35 | }
36 | })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | package mysqldump
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | //从dsn中提取出数据库名称,并将其作为结果返回。
9 | //如果无法解析出数据库名称,将返回一个错误。
10 |
11 | func GetDBNameFromDSN(dsn string) (string, error) {
12 | ss1 := strings.Split(dsn, "/")
13 | if len(ss1) == 2 {
14 | ss2 := strings.Split(ss1[1], "?")
15 | if len(ss2) == 2 {
16 | return ss2[0], nil
17 | }
18 | }
19 |
20 | return "", fmt.Errorf("dsn error: %s", dsn)
21 | }
22 |
--------------------------------------------------------------------------------