@datatruck/cli 0.22.2 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Task/MysqlDumpTask.d.ts +4 -0
- package/Task/MysqlDumpTask.js +119 -89
- package/config.schema.json +4 -0
- package/package.json +3 -1
- package/utils/async.d.ts +20 -0
- package/utils/async.js +50 -0
- package/utils/mysql.d.ts +23 -7
- package/utils/mysql.js +87 -32
package/Task/MysqlDumpTask.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export type MysqlDumpTaskConfigType = {
|
|
|
7
7
|
*/
|
|
8
8
|
dataFormat?: "csv" | "sql";
|
|
9
9
|
csvSharedPath?: string;
|
|
10
|
+
/**
|
|
11
|
+
* @default 1
|
|
12
|
+
*/
|
|
13
|
+
concurrency?: number;
|
|
10
14
|
} & SqlDumpTaskConfigType;
|
|
11
15
|
export declare const mysqlDumpTaskDefinition: import("json-schema").JSONSchema7;
|
|
12
16
|
export declare class MysqlDumpTask extends TaskAbstract<MysqlDumpTaskConfigType> {
|
package/Task/MysqlDumpTask.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MysqlDumpTask = exports.mysqlDumpTaskDefinition = exports.mysqlDumpTaskName = void 0;
|
|
4
4
|
const AppError_1 = require("../Error/AppError");
|
|
5
|
+
const async_1 = require("../utils/async");
|
|
5
6
|
const cli_1 = require("../utils/cli");
|
|
6
7
|
const config_1 = require("../utils/datatruck/config");
|
|
7
8
|
const fs_1 = require("../utils/fs");
|
|
@@ -16,6 +17,7 @@ const path_1 = require("path");
|
|
|
16
17
|
exports.mysqlDumpTaskName = "mysql-dump";
|
|
17
18
|
exports.mysqlDumpTaskDefinition = (0, SqlDumpTaskAbstract_1.sqlDumpTaskDefinition)({
|
|
18
19
|
dataFormat: { enum: ["csv", "sql"] },
|
|
20
|
+
concurrency: { type: "integer", minimum: 1 },
|
|
19
21
|
csvSharedPath: { type: "string" },
|
|
20
22
|
});
|
|
21
23
|
const suffix = {
|
|
@@ -27,85 +29,92 @@ const suffix = {
|
|
|
27
29
|
};
|
|
28
30
|
class MysqlDumpTask extends TaskAbstract_1.TaskAbstract {
|
|
29
31
|
async onBackup(data) {
|
|
30
|
-
const sql = (0, mysql_1.createMysqlCli)({
|
|
32
|
+
const sql = await (0, mysql_1.createMysqlCli)({
|
|
31
33
|
...this.config,
|
|
32
34
|
verbose: data.options.verbose,
|
|
33
35
|
});
|
|
34
36
|
const tableNames = await sql.fetchTableNames(this.config.database, this.config.includeTables, this.config.excludeTables);
|
|
35
37
|
const outputPath = data.package.path;
|
|
36
38
|
(0, assert_1.ok)(typeof outputPath === "string");
|
|
39
|
+
const concurrency = this.config.concurrency ?? 4;
|
|
37
40
|
const dataFormat = this.config.dataFormat ?? "sql";
|
|
38
41
|
await (0, promises_1.mkdir)(outputPath, { recursive: true });
|
|
39
42
|
const sharedDir = dataFormat === "csv"
|
|
40
43
|
? await sql.initSharedDir(this.config.csvSharedPath)
|
|
41
44
|
: undefined;
|
|
42
45
|
if (this.config.oneFileByTable || sharedDir) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
await (0, async_1.runParallel)({
|
|
47
|
+
items: tableNames,
|
|
48
|
+
concurrency,
|
|
49
|
+
onChange: async ({ processed: proccesed, buffer }) => await data.onProgress({
|
|
46
50
|
relative: {
|
|
47
|
-
description: "Exporting",
|
|
48
|
-
payload:
|
|
51
|
+
description: buffer.size > 1 ? `Exporting (${buffer.size})` : "Exporting",
|
|
52
|
+
payload: [...buffer.keys()].join(", "),
|
|
49
53
|
},
|
|
50
54
|
absolute: {
|
|
51
55
|
total: tableNames.length,
|
|
52
|
-
current,
|
|
53
|
-
percent: (0, math_1.progressPercent)(tableNames.length,
|
|
56
|
+
current: proccesed,
|
|
57
|
+
percent: (0, math_1.progressPercent)(tableNames.length, proccesed),
|
|
54
58
|
},
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
(
|
|
60
|
-
|
|
59
|
+
}),
|
|
60
|
+
onItem: async ({ item: tableName, index, controller }) => {
|
|
61
|
+
if (sharedDir) {
|
|
62
|
+
const tableSharedPath = (0, path_1.join)(sharedDir, `tmp-dtt-backup-${data.snapshot.id.slice(0, 8)}-${tableName}`);
|
|
63
|
+
if (data.options.verbose) {
|
|
64
|
+
(0, cli_1.logExec)("mkdir", ["-p", tableSharedPath]);
|
|
65
|
+
(0, cli_1.logExec)("chmod", ["777", tableSharedPath]);
|
|
66
|
+
}
|
|
67
|
+
await (0, promises_1.mkdir)(tableSharedPath, { recursive: true });
|
|
68
|
+
try {
|
|
69
|
+
await (0, promises_1.chmod)(tableSharedPath, 0o777);
|
|
70
|
+
await sql.csvDump({
|
|
71
|
+
sharedPath: tableSharedPath,
|
|
72
|
+
items: [tableName],
|
|
73
|
+
database: this.config.database,
|
|
74
|
+
onSpawn: (p) => (controller.stop = () => p.kill()),
|
|
75
|
+
});
|
|
76
|
+
const files = await (0, promises_1.readdir)(tableSharedPath);
|
|
77
|
+
const schemaFile = `${tableName}.sql`;
|
|
78
|
+
const dataFile = `${tableName}.txt`;
|
|
79
|
+
const successCsvDump = files.length === 2 &&
|
|
80
|
+
files.every((file) => file === schemaFile || file === dataFile);
|
|
81
|
+
if (!successCsvDump)
|
|
82
|
+
throw new AppError_1.AppError(`Invalid csv dump files: ${files.join(", ")}`);
|
|
83
|
+
await (0, fs_1.safeRename)((0, path_1.join)(tableSharedPath, schemaFile), (0, path_1.join)(outputPath, `${tableName}${suffix.tableSchema}`));
|
|
84
|
+
await (0, fs_1.safeRename)((0, path_1.join)(tableSharedPath, dataFile), (0, path_1.join)(outputPath, `${tableName}${suffix.tableData}`));
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await (0, promises_1.rm)(tableSharedPath, { recursive: true });
|
|
88
|
+
}
|
|
61
89
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
sharedPath: tableSharedPath,
|
|
90
|
+
else {
|
|
91
|
+
const outPath = (0, path_1.join)(outputPath, `${tableName}${suffix.table}`);
|
|
92
|
+
await sql.dump({
|
|
93
|
+
output: outPath,
|
|
67
94
|
items: [tableName],
|
|
68
95
|
database: this.config.database,
|
|
96
|
+
onSpawn: (p) => (controller.stop = () => p.kill()),
|
|
97
|
+
...(concurrency !== 1 && {
|
|
98
|
+
onProgress(progress) {
|
|
99
|
+
data.onProgress({
|
|
100
|
+
relative: {
|
|
101
|
+
description: "Exporting",
|
|
102
|
+
payload: tableName,
|
|
103
|
+
current: progress.totalBytes,
|
|
104
|
+
format: "size",
|
|
105
|
+
},
|
|
106
|
+
absolute: {
|
|
107
|
+
total: tableNames.length,
|
|
108
|
+
current: index,
|
|
109
|
+
percent: (0, math_1.progressPercent)(tableNames.length, index),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
69
114
|
});
|
|
70
|
-
const files = await (0, promises_1.readdir)(tableSharedPath);
|
|
71
|
-
const schemaFile = `${tableName}.sql`;
|
|
72
|
-
const dataFile = `${tableName}.txt`;
|
|
73
|
-
const successCsvDump = files.length === 2 &&
|
|
74
|
-
files.every((file) => file === schemaFile || file === dataFile);
|
|
75
|
-
if (!successCsvDump)
|
|
76
|
-
throw new AppError_1.AppError(`Invalid csv dump files: ${files.join(", ")}`);
|
|
77
|
-
await (0, fs_1.safeRename)((0, path_1.join)(tableSharedPath, schemaFile), (0, path_1.join)(outputPath, `${tableName}${suffix.tableSchema}`));
|
|
78
|
-
await (0, fs_1.safeRename)((0, path_1.join)(tableSharedPath, dataFile), (0, path_1.join)(outputPath, `${tableName}${suffix.tableData}`));
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
await (0, promises_1.rm)(tableSharedPath, { recursive: true });
|
|
82
115
|
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const outPath = (0, path_1.join)(outputPath, `${tableName}${suffix.table}`);
|
|
86
|
-
await sql.dump({
|
|
87
|
-
output: outPath,
|
|
88
|
-
items: [tableName],
|
|
89
|
-
database: this.config.database,
|
|
90
|
-
onProgress(progress) {
|
|
91
|
-
data.onProgress({
|
|
92
|
-
relative: {
|
|
93
|
-
description: "Exporting",
|
|
94
|
-
payload: tableName,
|
|
95
|
-
current: progress.totalBytes,
|
|
96
|
-
format: "size",
|
|
97
|
-
},
|
|
98
|
-
absolute: {
|
|
99
|
-
total: tableNames.length,
|
|
100
|
-
current,
|
|
101
|
-
percent: (0, math_1.progressPercent)(tableNames.length, current),
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
current++;
|
|
108
|
-
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
109
118
|
}
|
|
110
119
|
else {
|
|
111
120
|
await data.onProgress({
|
|
@@ -136,7 +145,7 @@ class MysqlDumpTask extends TaskAbstract_1.TaskAbstract {
|
|
|
136
145
|
}
|
|
137
146
|
}
|
|
138
147
|
async onRestore(data) {
|
|
139
|
-
const sql = (0, mysql_1.createMysqlCli)({
|
|
148
|
+
const sql = await (0, mysql_1.createMysqlCli)({
|
|
140
149
|
...this.config,
|
|
141
150
|
verbose: data.options.verbose,
|
|
142
151
|
});
|
|
@@ -181,47 +190,68 @@ class MysqlDumpTask extends TaskAbstract_1.TaskAbstract {
|
|
|
181
190
|
await sql.createDatabase(database);
|
|
182
191
|
if (data.options.verbose)
|
|
183
192
|
(0, cli_1.logExec)("readdir", [restorePath]);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
const concurrency = this.config.concurrency ?? 1;
|
|
194
|
+
let processed = 0;
|
|
195
|
+
await (0, async_1.runParallel)({
|
|
196
|
+
items: files.filter((f) => !f.endsWith(suffix.tableData)),
|
|
197
|
+
concurrency,
|
|
198
|
+
onFinished: () => {
|
|
199
|
+
processed++;
|
|
200
|
+
},
|
|
201
|
+
onChange: async ({ buffer }) => await data.onProgress({
|
|
188
202
|
relative: {
|
|
189
|
-
description: "Importing",
|
|
190
|
-
payload:
|
|
203
|
+
description: buffer.size > 1 ? `Importing (${buffer.size})` : "Importing",
|
|
204
|
+
payload: [...buffer.keys()].join(", "),
|
|
191
205
|
},
|
|
192
206
|
absolute: {
|
|
193
207
|
total: files.length,
|
|
194
|
-
current:
|
|
195
|
-
percent: (0, math_1.progressPercent)(files.length,
|
|
208
|
+
current: processed,
|
|
209
|
+
percent: (0, math_1.progressPercent)(files.length, processed),
|
|
196
210
|
},
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
211
|
+
}),
|
|
212
|
+
onItem: async ({ item: file, controller }) => {
|
|
213
|
+
await sql.importFile({
|
|
214
|
+
path: (0, path_1.join)(restorePath, file),
|
|
215
|
+
database: database.name,
|
|
216
|
+
onSpawn: (p) => (controller.stop = () => p.kill()),
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
await (0, async_1.runParallel)({
|
|
221
|
+
items: dataFiles,
|
|
222
|
+
concurrency,
|
|
223
|
+
onFinished: () => {
|
|
224
|
+
processed++;
|
|
225
|
+
},
|
|
226
|
+
onChange: async ({ buffer }) => await data.onProgress({
|
|
205
227
|
relative: {
|
|
206
|
-
description: "Importing",
|
|
207
|
-
payload:
|
|
228
|
+
description: buffer.size > 1 ? `Importing (${buffer.size})` : "Importing",
|
|
229
|
+
payload: [...buffer.keys()].join(", "),
|
|
208
230
|
},
|
|
209
231
|
absolute: {
|
|
210
232
|
total: files.length,
|
|
211
|
-
current:
|
|
212
|
-
percent: (0, math_1.progressPercent)(files.length,
|
|
233
|
+
current: processed,
|
|
234
|
+
percent: (0, math_1.progressPercent)(files.length, processed),
|
|
213
235
|
},
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
236
|
+
}),
|
|
237
|
+
onItem: async ({ item: file, controller }) => {
|
|
238
|
+
const filePath = (0, path_1.join)(restorePath, file);
|
|
239
|
+
const tableName = file.slice(0, suffix.tableData.length * -1);
|
|
240
|
+
const sharedFilePath = (0, path_1.join)(sharedDir, `tmp-dtt-restore-${data.snapshot.id.slice(0, 8)}-${tableName}.data.csv`);
|
|
241
|
+
try {
|
|
242
|
+
await (0, fs_1.safeRename)(filePath, sharedFilePath);
|
|
243
|
+
await sql.importCsvFile({
|
|
244
|
+
path: sharedFilePath,
|
|
245
|
+
database: database.name,
|
|
246
|
+
table: tableName,
|
|
247
|
+
onSpawn: (p) => (controller.stop = () => p.kill()),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
await (0, promises_1.rm)(sharedFilePath);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
});
|
|
225
255
|
}
|
|
226
256
|
}
|
|
227
257
|
exports.MysqlDumpTask = MysqlDumpTask;
|
package/config.schema.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datatruck/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"dependencies": {
|
|
5
|
+
"@supercharge/promise-pool": "^3.1.0",
|
|
5
6
|
"ajv": "^8.12.0",
|
|
6
7
|
"async": "^3.2.4",
|
|
7
8
|
"chalk": "^4.1.2",
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
"fast-folder-size": "^2.2.0",
|
|
12
13
|
"fast-glob": "^3.3.1",
|
|
13
14
|
"micromatch": "^4.0.5",
|
|
15
|
+
"mysql2": "^3.6.1",
|
|
14
16
|
"pretty-bytes": "^5.6.0",
|
|
15
17
|
"sqlite": "^5.0.1",
|
|
16
18
|
"sqlite3": "^5.1.6"
|
package/utils/async.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type ControllerItem = {
|
|
2
|
+
stop?: () => void;
|
|
3
|
+
};
|
|
4
|
+
type ItemBuffer<T> = Map<T, ControllerItem>;
|
|
5
|
+
export declare function runParallel<T>(options: {
|
|
6
|
+
items: T[];
|
|
7
|
+
concurrency: number;
|
|
8
|
+
onChange: (data: {
|
|
9
|
+
buffer: ItemBuffer<T>;
|
|
10
|
+
processed: number;
|
|
11
|
+
proccesing: number;
|
|
12
|
+
}) => Promise<void> | void;
|
|
13
|
+
onItem: (data: {
|
|
14
|
+
item: T;
|
|
15
|
+
index: number;
|
|
16
|
+
controller: ControllerItem;
|
|
17
|
+
}) => Promise<void> | void;
|
|
18
|
+
onFinished?: () => Promise<void> | void;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
export {};
|
package/utils/async.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runParallel = void 0;
|
|
4
|
+
const promise_pool_1 = require("@supercharge/promise-pool");
|
|
5
|
+
async function runParallel(options) {
|
|
6
|
+
const buffer = new Map();
|
|
7
|
+
let processed = 0;
|
|
8
|
+
let error;
|
|
9
|
+
await promise_pool_1.PromisePool.for(options.items)
|
|
10
|
+
.withConcurrency(options.concurrency)
|
|
11
|
+
.process(async (item, index, pool) => {
|
|
12
|
+
const controller = {};
|
|
13
|
+
buffer.set(item, controller);
|
|
14
|
+
try {
|
|
15
|
+
await options.onChange({
|
|
16
|
+
processed,
|
|
17
|
+
proccesing: buffer.size,
|
|
18
|
+
buffer,
|
|
19
|
+
});
|
|
20
|
+
await options.onItem({
|
|
21
|
+
item,
|
|
22
|
+
index,
|
|
23
|
+
controller,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (_error) {
|
|
27
|
+
error = _error;
|
|
28
|
+
buffer.delete(item);
|
|
29
|
+
pool.stop();
|
|
30
|
+
for (const [, $controller] of buffer.entries())
|
|
31
|
+
try {
|
|
32
|
+
$controller.stop?.();
|
|
33
|
+
}
|
|
34
|
+
catch (_) { }
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
buffer.delete(item);
|
|
38
|
+
processed++;
|
|
39
|
+
await options.onFinished?.();
|
|
40
|
+
await options.onChange({
|
|
41
|
+
processed,
|
|
42
|
+
proccesing: buffer.size,
|
|
43
|
+
buffer,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
if (error)
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
exports.runParallel = runParallel;
|
package/utils/mysql.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ChildProcess } from "child_process";
|
|
1
2
|
export type MysqlCliOptions = {
|
|
2
3
|
password: string | {
|
|
3
4
|
path: string;
|
|
@@ -6,24 +7,33 @@ export type MysqlCliOptions = {
|
|
|
6
7
|
port?: number;
|
|
7
8
|
username: string;
|
|
8
9
|
verbose?: boolean;
|
|
10
|
+
database?: string;
|
|
9
11
|
};
|
|
10
|
-
export declare function createMysqlCli(options: MysqlCliOptions): {
|
|
12
|
+
export declare function createMysqlCli(options: MysqlCliOptions): Promise<{
|
|
11
13
|
options: MysqlCliOptions;
|
|
12
14
|
initSharedDir: (sharedDir?: string) => Promise<string>;
|
|
13
15
|
args: () => Promise<string[]>;
|
|
14
|
-
run: (query: string, database?: string, extra?: string[]) => Promise<import("./process").ExecResultType>;
|
|
15
|
-
|
|
16
|
+
run: (query: string, database?: string, extra?: string[], onSpawn?: ((p: ChildProcess) => void) | undefined) => Promise<import("./process").ExecResultType>;
|
|
17
|
+
execute: (query: string, params?: any[]) => Promise<void>;
|
|
18
|
+
insert: (tableName: string, item: Record<string, any>) => Promise<void>;
|
|
19
|
+
changeDatabase: (name: string) => Promise<void>;
|
|
20
|
+
fetchAll: <T>(query: string, params?: any[]) => Promise<T[]>;
|
|
16
21
|
dump: (input: {
|
|
17
22
|
output: string;
|
|
18
23
|
database: string;
|
|
19
24
|
items?: string[] | undefined;
|
|
20
25
|
onlyStoredPrograms?: boolean | undefined;
|
|
26
|
+
onSpawn?: ((p: ChildProcess) => void) | undefined;
|
|
21
27
|
onProgress?: ((data: {
|
|
22
28
|
totalBytes: number;
|
|
23
29
|
}) => void) | undefined;
|
|
24
30
|
}) => Promise<[void, import("./process").ExecResultType]>;
|
|
25
31
|
fetchTableNames: (database: string, include?: string[], exclude?: string[]) => Promise<string[]>;
|
|
26
|
-
importFile: (
|
|
32
|
+
importFile: (input: {
|
|
33
|
+
path: string;
|
|
34
|
+
database: string;
|
|
35
|
+
onSpawn?: ((p: ChildProcess) => void) | undefined;
|
|
36
|
+
}) => Promise<import("./process").ExecResultType>;
|
|
27
37
|
isDatabaseEmpty: (database: string) => Promise<boolean>;
|
|
28
38
|
createDatabase: (database: {
|
|
29
39
|
name: string;
|
|
@@ -32,8 +42,14 @@ export declare function createMysqlCli(options: MysqlCliOptions): {
|
|
|
32
42
|
csvDump: (input: {
|
|
33
43
|
database: string;
|
|
34
44
|
sharedPath: string;
|
|
35
|
-
items?: string[];
|
|
45
|
+
items?: string[] | undefined;
|
|
46
|
+
onSpawn?: ((p: ChildProcess) => void) | undefined;
|
|
36
47
|
}) => Promise<void>;
|
|
37
|
-
importCsvFile: (
|
|
48
|
+
importCsvFile: (input: {
|
|
49
|
+
path: string;
|
|
50
|
+
database: string;
|
|
51
|
+
table: string;
|
|
52
|
+
onSpawn?: ((p: ChildProcess) => void) | undefined;
|
|
53
|
+
}) => Promise<import("./process").ExecResultType>;
|
|
38
54
|
fetchVariable: (name: string) => Promise<string | undefined>;
|
|
39
|
-
}
|
|
55
|
+
}>;
|
package/utils/mysql.js
CHANGED
|
@@ -2,19 +2,39 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createMysqlCli = void 0;
|
|
4
4
|
const AppError_1 = require("../Error/AppError");
|
|
5
|
+
const cli_1 = require("./cli");
|
|
5
6
|
const fs_1 = require("./fs");
|
|
6
7
|
const process_1 = require("./process");
|
|
7
8
|
const string_1 = require("./string");
|
|
8
9
|
const crypto_1 = require("crypto");
|
|
9
10
|
const fs_2 = require("fs");
|
|
10
11
|
const promises_1 = require("fs/promises");
|
|
12
|
+
const promise_1 = require("mysql2/promise");
|
|
11
13
|
const os_1 = require("os");
|
|
12
14
|
const path_1 = require("path");
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
function flatQuery(query, params) {
|
|
16
|
+
query = query.replace(/\s{1,}/g, " ").trim();
|
|
17
|
+
let paramIndex = 0;
|
|
18
|
+
return params
|
|
19
|
+
? query.replace(/\?/g, () => {
|
|
20
|
+
const param = params[paramIndex++];
|
|
21
|
+
return param ? JSON.stringify(param) : "?";
|
|
22
|
+
})
|
|
23
|
+
: query;
|
|
24
|
+
}
|
|
25
|
+
async function createMysqlCli(options) {
|
|
26
|
+
let sqlConfigPath;
|
|
27
|
+
const password = (await (0, fs_1.fetchData)(options.password, (p) => p.path)) ?? "";
|
|
28
|
+
const sql = await (0, promise_1.createConnection)({
|
|
29
|
+
host: options.hostname,
|
|
30
|
+
user: options.username,
|
|
31
|
+
password,
|
|
32
|
+
port: options.port,
|
|
33
|
+
database: options.database,
|
|
34
|
+
});
|
|
35
|
+
async function createSqlConfig() {
|
|
36
|
+
if (sqlConfigPath)
|
|
37
|
+
return sqlConfigPath;
|
|
18
38
|
const dir = await (0, fs_1.mkTmpDir)("mysql-cli");
|
|
19
39
|
const password = await (0, fs_1.fetchData)(options.password, (p) => p.path);
|
|
20
40
|
const data = [
|
|
@@ -24,29 +44,31 @@ function createMysqlCli(options) {
|
|
|
24
44
|
`user = "${options.username}"`,
|
|
25
45
|
`password = "${password}"`,
|
|
26
46
|
];
|
|
27
|
-
await (0, promises_1.writeFile)((
|
|
28
|
-
return
|
|
47
|
+
await (0, promises_1.writeFile)((sqlConfigPath = (0, path_1.join)(dir, "mysql.conf")), data.join("\n"));
|
|
48
|
+
return sqlConfigPath;
|
|
29
49
|
}
|
|
30
50
|
async function args() {
|
|
31
|
-
return [`--defaults-file=${await
|
|
51
|
+
return [`--defaults-file=${await createSqlConfig()}`];
|
|
32
52
|
}
|
|
33
|
-
async function run(query, database, extra = []) {
|
|
53
|
+
async function run(query, database, extra = [], onSpawn) {
|
|
34
54
|
return await (0, process_1.exec)("mysql", [
|
|
35
55
|
...(await args()),
|
|
36
56
|
...(database ? [database] : []),
|
|
37
57
|
...(extra || []),
|
|
38
58
|
"-e",
|
|
39
|
-
query
|
|
59
|
+
flatQuery(query),
|
|
40
60
|
"-N",
|
|
41
61
|
"--silent",
|
|
42
62
|
], undefined, {
|
|
63
|
+
onSpawn,
|
|
43
64
|
log: options.verbose,
|
|
44
65
|
stderr: { toExitCode: true },
|
|
45
66
|
stdout: { save: true },
|
|
46
67
|
});
|
|
47
68
|
}
|
|
48
|
-
async function fetchAll(query,
|
|
49
|
-
|
|
69
|
+
async function fetchAll(query, params) {
|
|
70
|
+
const [rows] = await sql.query(query, params);
|
|
71
|
+
return rows;
|
|
50
72
|
}
|
|
51
73
|
async function fetchTableNames(database, include, exclude) {
|
|
52
74
|
return (await fetchAll(`
|
|
@@ -55,11 +77,11 @@ function createMysqlCli(options) {
|
|
|
55
77
|
FROM
|
|
56
78
|
information_schema.tables
|
|
57
79
|
WHERE
|
|
58
|
-
table_schema =
|
|
80
|
+
table_schema = ?
|
|
59
81
|
ORDER BY
|
|
60
82
|
table_name
|
|
61
|
-
|
|
62
|
-
.map((r) => r
|
|
83
|
+
`, [database]))
|
|
84
|
+
.map((r) => r.table_name)
|
|
63
85
|
.filter((0, string_1.createMatchFilter)(include, exclude));
|
|
64
86
|
}
|
|
65
87
|
async function dump(input) {
|
|
@@ -88,6 +110,7 @@ function createMysqlCli(options) {
|
|
|
88
110
|
...(input.items || []),
|
|
89
111
|
], null, {
|
|
90
112
|
stderr: { toExitCode: true },
|
|
113
|
+
onSpawn: input.onSpawn,
|
|
91
114
|
pipe: {
|
|
92
115
|
stream,
|
|
93
116
|
onWriteProgress: input.onProgress,
|
|
@@ -106,11 +129,14 @@ function createMysqlCli(options) {
|
|
|
106
129
|
input.database,
|
|
107
130
|
"--lock-tables=false",
|
|
108
131
|
"--skip-add-drop-table=false",
|
|
132
|
+
"--fields-terminated-by=0x09",
|
|
133
|
+
"--lines-terminated-by=0x0a",
|
|
109
134
|
"-T",
|
|
110
135
|
input.sharedPath,
|
|
111
136
|
...(input.items || []),
|
|
112
137
|
], null, {
|
|
113
138
|
stderr: { toExitCode: true },
|
|
139
|
+
onSpawn: input.onSpawn,
|
|
114
140
|
log: {
|
|
115
141
|
exec: options.verbose,
|
|
116
142
|
stderr: options.verbose,
|
|
@@ -118,7 +144,7 @@ function createMysqlCli(options) {
|
|
|
118
144
|
},
|
|
119
145
|
});
|
|
120
146
|
}
|
|
121
|
-
async function importFile(
|
|
147
|
+
async function importFile(input) {
|
|
122
148
|
return await (0, process_1.exec)("mysql", [
|
|
123
149
|
...(await args()),
|
|
124
150
|
`--init-command=SET ${[
|
|
@@ -126,10 +152,11 @@ function createMysqlCli(options) {
|
|
|
126
152
|
"unique_checks=0",
|
|
127
153
|
"foreign_key_checks=0",
|
|
128
154
|
].join(",")};`,
|
|
129
|
-
database,
|
|
155
|
+
input.database,
|
|
130
156
|
], null, {
|
|
157
|
+
onSpawn: input.onSpawn,
|
|
131
158
|
pipe: {
|
|
132
|
-
stream: (0, fs_2.createReadStream)(path),
|
|
159
|
+
stream: (0, fs_2.createReadStream)(input.path),
|
|
133
160
|
onReadProgress: (data) => {
|
|
134
161
|
if (options.verbose)
|
|
135
162
|
(0, process_1.logExecStdout)({
|
|
@@ -144,35 +171,36 @@ function createMysqlCli(options) {
|
|
|
144
171
|
log: options.verbose,
|
|
145
172
|
});
|
|
146
173
|
}
|
|
147
|
-
async function importCsvFile(
|
|
174
|
+
async function importCsvFile(input) {
|
|
148
175
|
return run(`
|
|
149
|
-
LOAD DATA LOCAL INFILE
|
|
150
|
-
INTO TABLE ${table}
|
|
151
|
-
FIELDS TERMINATED BY '
|
|
152
|
-
|
|
153
|
-
LINES TERMINATED BY '\\n'`, database, ["--local-infile"]);
|
|
176
|
+
LOAD DATA LOCAL INFILE ${JSON.stringify(input.path.replaceAll("\\", "/"))}
|
|
177
|
+
INTO TABLE ${input.table}
|
|
178
|
+
FIELDS TERMINATED BY '\\t'
|
|
179
|
+
LINES TERMINATED BY '\\n'`, input.database, ["--local-infile"], input.onSpawn);
|
|
154
180
|
}
|
|
155
181
|
async function isDatabaseEmpty(database) {
|
|
156
|
-
const [
|
|
182
|
+
const [row] = await fetchAll(`
|
|
157
183
|
SELECT
|
|
158
184
|
COUNT(*) AS total
|
|
159
185
|
FROM
|
|
160
186
|
information_schema.tables
|
|
161
187
|
WHERE
|
|
162
|
-
table_schema =
|
|
163
|
-
|
|
164
|
-
return Number(total) ? false : true;
|
|
188
|
+
table_schema = ?
|
|
189
|
+
`, [database]);
|
|
190
|
+
return Number(row.total) ? false : true;
|
|
165
191
|
}
|
|
166
192
|
async function createDatabase(database) {
|
|
167
|
-
await
|
|
193
|
+
await sql.execute(`
|
|
168
194
|
CREATE DATABASE IF NOT EXISTS \`${database.name}\`
|
|
169
195
|
CHARACTER SET ${database.charset ?? "utf8"}
|
|
170
196
|
COLLATE ${database.charset ?? "utf8_general_ci"}
|
|
171
197
|
`);
|
|
172
198
|
}
|
|
173
199
|
async function fetchVariable(name) {
|
|
174
|
-
const
|
|
175
|
-
|
|
200
|
+
const rows = await fetchAll(`SHOW VARIABLES LIKE ?`, [
|
|
201
|
+
name,
|
|
202
|
+
]);
|
|
203
|
+
return (0, string_1.undefIfEmpty)(rows?.[0].Value);
|
|
176
204
|
}
|
|
177
205
|
async function initSharedDir(sharedDir) {
|
|
178
206
|
const secure_file_priv = await fetchVariable("secure_file_priv");
|
|
@@ -192,7 +220,7 @@ function createMysqlCli(options) {
|
|
|
192
220
|
try {
|
|
193
221
|
await (0, fs_1.mkdirIfNotExists)(dir);
|
|
194
222
|
await (0, promises_1.chmod)(dir, 0o777);
|
|
195
|
-
await
|
|
223
|
+
await sql.execute(`SELECT 1 INTO OUTFILE ${outFileVar}`);
|
|
196
224
|
const exists = await (0, fs_1.existsFile)(outFile);
|
|
197
225
|
if (!exists)
|
|
198
226
|
throw new AppError_1.AppError(`MySQL shared dir is not reached: ${dir}`);
|
|
@@ -204,11 +232,38 @@ function createMysqlCli(options) {
|
|
|
204
232
|
catch (e) { }
|
|
205
233
|
}
|
|
206
234
|
}
|
|
235
|
+
async function execute(query, params = []) {
|
|
236
|
+
if (options.verbose) {
|
|
237
|
+
(0, cli_1.logExec)(`mysql`, [
|
|
238
|
+
...(await args()),
|
|
239
|
+
"-e",
|
|
240
|
+
`"${flatQuery(query)}"`,
|
|
241
|
+
...(sql.config.database ? [sql.config.database] : []),
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
await sql.execute(query, params);
|
|
245
|
+
}
|
|
246
|
+
async function insert(tableName, item) {
|
|
247
|
+
const columnsExpr = Object.keys(item)
|
|
248
|
+
.map((v) => v)
|
|
249
|
+
.join(", ");
|
|
250
|
+
const paramsExpr = Array.from({ length: Object.keys(item).length })
|
|
251
|
+
.fill("?")
|
|
252
|
+
.join(", ");
|
|
253
|
+
const params = Object.values(item);
|
|
254
|
+
await execute(`INSERT INTO ${tableName} (${columnsExpr}) VALUES (${paramsExpr})`, params);
|
|
255
|
+
}
|
|
256
|
+
async function changeDatabase(name) {
|
|
257
|
+
await sql.changeUser({ database: name });
|
|
258
|
+
}
|
|
207
259
|
return {
|
|
208
260
|
options,
|
|
209
261
|
initSharedDir,
|
|
210
262
|
args,
|
|
211
263
|
run,
|
|
264
|
+
execute,
|
|
265
|
+
insert,
|
|
266
|
+
changeDatabase,
|
|
212
267
|
fetchAll,
|
|
213
268
|
dump,
|
|
214
269
|
fetchTableNames,
|