@datatruck/cli 0.22.2 → 0.23.0

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.
@@ -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> {
@@ -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
- let current = 0;
44
- for (const tableName of tableNames) {
45
- await data.onProgress({
46
+ await (0, async_1.runParallel)({
47
+ items: tableNames,
48
+ concurrency,
49
+ onChange: async ({ processed: proccesed, buffer }) => await data.onProgress({
46
50
  relative: {
47
51
  description: "Exporting",
48
- payload: tableName,
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, current),
56
+ current: proccesed,
57
+ percent: (0, math_1.progressPercent)(tableNames.length, proccesed),
54
58
  },
55
- });
56
- if (sharedDir) {
57
- const tableSharedPath = (0, path_1.join)(sharedDir, `tmp-dtt-backup-${data.snapshot.id.slice(0, 8)}-${tableName}`);
58
- if (data.options.verbose) {
59
- (0, cli_1.logExec)("mkdir", ["-p", tableSharedPath]);
60
- (0, cli_1.logExec)("chmod", ["777", tableSharedPath]);
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
- await (0, promises_1.mkdir)(tableSharedPath, { recursive: true });
63
- await (0, promises_1.chmod)(tableSharedPath, 0o777);
64
- try {
65
- await sql.csvDump({
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
- else {
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,61 @@ 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
- let current = 0;
185
- for (const file of files.filter((f) => !f.endsWith(suffix.tableData))) {
186
- const path = (0, path_1.join)(restorePath, file);
187
- data.onProgress({
193
+ const concurrency = this.config.concurrency ?? 1;
194
+ await (0, async_1.runParallel)({
195
+ items: files.filter((f) => !f.endsWith(suffix.tableData)),
196
+ concurrency,
197
+ onChange: async ({ processed: proccesed, buffer }) => await data.onProgress({
188
198
  relative: {
189
199
  description: "Importing",
190
- payload: file,
200
+ payload: [...buffer.keys()].join(", "),
191
201
  },
192
202
  absolute: {
193
203
  total: files.length,
194
- current: current,
195
- percent: (0, math_1.progressPercent)(files.length, current),
204
+ current: proccesed,
205
+ percent: (0, math_1.progressPercent)(files.length, proccesed),
196
206
  },
197
- });
198
- await sql.importFile(path, database.name);
199
- current++;
200
- }
201
- for (const file of dataFiles) {
202
- const filePath = (0, path_1.join)(restorePath, file);
203
- const tableName = file.slice(0, suffix.tableData.length * -1);
204
- data.onProgress({
207
+ }),
208
+ onItem: async ({ item: file, controller }) => {
209
+ await sql.importFile({
210
+ path: (0, path_1.join)(restorePath, file),
211
+ database: database.name,
212
+ onSpawn: (p) => (controller.stop = () => p.kill()),
213
+ });
214
+ },
215
+ });
216
+ await (0, async_1.runParallel)({
217
+ items: dataFiles,
218
+ concurrency,
219
+ onChange: async ({ processed: proccesed, buffer }) => await data.onProgress({
205
220
  relative: {
206
221
  description: "Importing",
207
- payload: file,
222
+ payload: [...buffer.keys()].join(", "),
208
223
  },
209
224
  absolute: {
210
225
  total: files.length,
211
- current: current,
212
- percent: (0, math_1.progressPercent)(files.length, current),
226
+ current: proccesed,
227
+ percent: (0, math_1.progressPercent)(files.length, proccesed),
213
228
  },
214
- });
215
- const sharedFilePath = (0, path_1.join)(sharedDir, `tmp-dtt-restore-${data.snapshot.id.slice(0, 8)}-${tableName}.data.csv`);
216
- try {
217
- await (0, fs_1.safeRename)(filePath, sharedFilePath);
218
- await sql.importCsvFile(sharedFilePath, database.name, tableName);
219
- }
220
- finally {
221
- await (0, promises_1.rm)(sharedFilePath);
222
- }
223
- current++;
224
- }
229
+ }),
230
+ onItem: async ({ item: file, controller }) => {
231
+ const filePath = (0, path_1.join)(restorePath, file);
232
+ const tableName = file.slice(0, suffix.tableData.length * -1);
233
+ const sharedFilePath = (0, path_1.join)(sharedDir, `tmp-dtt-restore-${data.snapshot.id.slice(0, 8)}-${tableName}.data.csv`);
234
+ try {
235
+ await (0, fs_1.safeRename)(filePath, sharedFilePath);
236
+ await sql.importCsvFile({
237
+ path: sharedFilePath,
238
+ database: database.name,
239
+ table: tableName,
240
+ onSpawn: (p) => (controller.stop = () => p.kill()),
241
+ });
242
+ }
243
+ finally {
244
+ await (0, promises_1.rm)(sharedFilePath);
245
+ }
246
+ },
247
+ });
225
248
  }
226
249
  }
227
250
  exports.MysqlDumpTask = MysqlDumpTask;
@@ -895,6 +895,10 @@
895
895
  "sql"
896
896
  ]
897
897
  },
898
+ "concurrency": {
899
+ "type": "integer",
900
+ "minimum": 1
901
+ },
898
902
  "csvSharedPath": {
899
903
  "type": "string"
900
904
  },
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.22.2",
3
+ "version": "0.23.0",
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"
@@ -0,0 +1,19 @@
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>;
13
+ onItem: (data: {
14
+ item: T;
15
+ index: number;
16
+ controller: ControllerItem;
17
+ }) => Promise<void>;
18
+ }): Promise<void>;
19
+ export {};
package/utils/async.js ADDED
@@ -0,0 +1,49 @@
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.onChange({
40
+ processed,
41
+ proccesing: buffer.size,
42
+ buffer,
43
+ });
44
+ }
45
+ });
46
+ if (error)
47
+ throw error;
48
+ }
49
+ 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
- fetchAll: (query: string, database?: string) => Promise<string[][]>;
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: (path: string, database: string) => Promise<import("./process").ExecResultType>;
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: (path: string, database: string, table: string) => Promise<import("./process").ExecResultType>;
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 createMysqlCli(options) {
14
- let defaultsFilePath;
15
- async function getDefaultsFilePath() {
16
- if (defaultsFilePath)
17
- return defaultsFilePath;
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)((defaultsFilePath = (0, path_1.join)(dir, "mysql.conf")), data.join("\n"));
28
- return defaultsFilePath;
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 getDefaultsFilePath()}`];
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.replace(/\s{1,}/g, " "),
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, database) {
49
- return (0, string_1.splitLines)((await run(query, database)).stdout).map((line) => line.split("\t"));
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 = '${database}'
80
+ table_schema = ?
59
81
  ORDER BY
60
82
  table_name
61
- `))
62
- .map((r) => r[0])
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(path, database) {
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(path, database, table) {
174
+ async function importCsvFile(input) {
148
175
  return run(`
149
- LOAD DATA LOCAL INFILE '${path.replaceAll("\\", "/")}'
150
- INTO TABLE ${table}
151
- FIELDS TERMINATED BY ','
152
- ENCLOSED BY '"'
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 [total] = await fetchAll(`
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 = '${database}'
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 run(`
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 stdout = (0, string_1.undefIfEmpty)((await run(`SHOW VARIABLES LIKE "${name}"`)).stdout.trim());
175
- return stdout ? (0, string_1.undefIfEmpty)(stdout.slice(name.length).trim()) : undefined;
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 run(`SELECT 1 INTO OUTFILE ${outFileVar}`);
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,