@datatruck/cli 0.22.1 → 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, promises_1.rename)((0, path_1.join)(tableSharedPath, schemaFile), (0, path_1.join)(outputPath, `${tableName}${suffix.tableSchema}`));
78
- await (0, promises_1.rename)((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, promises_1.rename)(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.1",
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/fs.d.ts CHANGED
@@ -120,4 +120,5 @@ export declare function createWriteStreamPool(options: {
120
120
  };
121
121
  export declare function countFileLines(path: string): Promise<number>;
122
122
  export declare function fetchData<T>(input: T, onPath?: (input: Exclude<T, string>) => string | undefined): Promise<string | null>;
123
+ export declare function safeRename(oldPath: string, newPath: string): Promise<void>;
123
124
  export {};
package/utils/fs.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.fetchData = exports.countFileLines = exports.createWriteStreamPool = exports.createFileScanner = exports.cpy = exports.isNotFoundError = exports.updateFileStats = exports.copyFileWithStreams = exports.waitForClose = exports.writeGitIgnoreList = exports.fastglobToGitIgnore = exports.forEachFile = exports.readDir = exports.readPartialFile = exports.mkTmpDir = exports.fastFolderSizeAsync = exports.tmpDir = exports.rmTmpDir = exports.isTmpDir = exports.sessionTmpDir = exports.parentTmpDir = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.writeJSONFile = exports.existsFile = exports.existsDir = exports.safeStat = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isLocalDir = exports.isEmptyDir = exports.isWSLSystem = void 0;
6
+ exports.safeRename = exports.fetchData = exports.countFileLines = exports.createWriteStreamPool = exports.createFileScanner = exports.cpy = exports.isNotFoundError = exports.updateFileStats = exports.copyFileWithStreams = exports.waitForClose = exports.writeGitIgnoreList = exports.fastglobToGitIgnore = exports.forEachFile = exports.readDir = exports.readPartialFile = exports.mkTmpDir = exports.fastFolderSizeAsync = exports.tmpDir = exports.rmTmpDir = exports.isTmpDir = exports.sessionTmpDir = exports.parentTmpDir = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.writeJSONFile = exports.existsFile = exports.existsDir = exports.safeStat = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isLocalDir = exports.isEmptyDir = exports.isWSLSystem = void 0;
7
7
  const globalData_1 = __importDefault(require("../globalData"));
8
8
  const math_1 = require("./math");
9
9
  const path_1 = require("./path");
@@ -544,3 +544,19 @@ async function fetchData(input, onPath) {
544
544
  return null;
545
545
  }
546
546
  exports.fetchData = fetchData;
547
+ async function safeRename(oldPath, newPath) {
548
+ try {
549
+ await (0, promises_1.rename)(oldPath, newPath);
550
+ }
551
+ catch (error) {
552
+ if (error instanceof Error &&
553
+ error.message.includes("cross-device link not permitted")) {
554
+ await (0, promises_1.cp)(oldPath, newPath, { recursive: true });
555
+ await (0, promises_1.rm)(oldPath, { recursive: true });
556
+ }
557
+ else {
558
+ throw error;
559
+ }
560
+ }
561
+ }
562
+ exports.safeRename = safeRename;
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,30 +44,31 @@ function createMysqlCli(options) {
24
44
  `user = "${options.username}"`,
25
45
  `password = "${password}"`,
26
46
  ];
27
- console.log(data.join("\n"));
28
- await (0, promises_1.writeFile)((defaultsFilePath = (0, path_1.join)(dir, "mysql.conf")), data.join("\n"));
29
- return defaultsFilePath;
47
+ await (0, promises_1.writeFile)((sqlConfigPath = (0, path_1.join)(dir, "mysql.conf")), data.join("\n"));
48
+ return sqlConfigPath;
30
49
  }
31
50
  async function args() {
32
- return [`--defaults-file=${await getDefaultsFilePath()}`];
51
+ return [`--defaults-file=${await createSqlConfig()}`];
33
52
  }
34
- async function run(query, database, extra = []) {
53
+ async function run(query, database, extra = [], onSpawn) {
35
54
  return await (0, process_1.exec)("mysql", [
36
55
  ...(await args()),
37
56
  ...(database ? [database] : []),
38
57
  ...(extra || []),
39
58
  "-e",
40
- query.replace(/\s{1,}/g, " "),
59
+ flatQuery(query),
41
60
  "-N",
42
61
  "--silent",
43
62
  ], undefined, {
63
+ onSpawn,
44
64
  log: options.verbose,
45
65
  stderr: { toExitCode: true },
46
66
  stdout: { save: true },
47
67
  });
48
68
  }
49
- async function fetchAll(query, database) {
50
- 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;
51
72
  }
52
73
  async function fetchTableNames(database, include, exclude) {
53
74
  return (await fetchAll(`
@@ -56,11 +77,11 @@ function createMysqlCli(options) {
56
77
  FROM
57
78
  information_schema.tables
58
79
  WHERE
59
- table_schema = '${database}'
80
+ table_schema = ?
60
81
  ORDER BY
61
82
  table_name
62
- `))
63
- .map((r) => r[0])
83
+ `, [database]))
84
+ .map((r) => r.table_name)
64
85
  .filter((0, string_1.createMatchFilter)(include, exclude));
65
86
  }
66
87
  async function dump(input) {
@@ -89,6 +110,7 @@ function createMysqlCli(options) {
89
110
  ...(input.items || []),
90
111
  ], null, {
91
112
  stderr: { toExitCode: true },
113
+ onSpawn: input.onSpawn,
92
114
  pipe: {
93
115
  stream,
94
116
  onWriteProgress: input.onProgress,
@@ -107,11 +129,14 @@ function createMysqlCli(options) {
107
129
  input.database,
108
130
  "--lock-tables=false",
109
131
  "--skip-add-drop-table=false",
132
+ "--fields-terminated-by=0x09",
133
+ "--lines-terminated-by=0x0a",
110
134
  "-T",
111
135
  input.sharedPath,
112
136
  ...(input.items || []),
113
137
  ], null, {
114
138
  stderr: { toExitCode: true },
139
+ onSpawn: input.onSpawn,
115
140
  log: {
116
141
  exec: options.verbose,
117
142
  stderr: options.verbose,
@@ -119,7 +144,7 @@ function createMysqlCli(options) {
119
144
  },
120
145
  });
121
146
  }
122
- async function importFile(path, database) {
147
+ async function importFile(input) {
123
148
  return await (0, process_1.exec)("mysql", [
124
149
  ...(await args()),
125
150
  `--init-command=SET ${[
@@ -127,10 +152,11 @@ function createMysqlCli(options) {
127
152
  "unique_checks=0",
128
153
  "foreign_key_checks=0",
129
154
  ].join(",")};`,
130
- database,
155
+ input.database,
131
156
  ], null, {
157
+ onSpawn: input.onSpawn,
132
158
  pipe: {
133
- stream: (0, fs_2.createReadStream)(path),
159
+ stream: (0, fs_2.createReadStream)(input.path),
134
160
  onReadProgress: (data) => {
135
161
  if (options.verbose)
136
162
  (0, process_1.logExecStdout)({
@@ -145,35 +171,36 @@ function createMysqlCli(options) {
145
171
  log: options.verbose,
146
172
  });
147
173
  }
148
- async function importCsvFile(path, database, table) {
174
+ async function importCsvFile(input) {
149
175
  return run(`
150
- LOAD DATA LOCAL INFILE '${path.replaceAll("\\", "/")}'
151
- INTO TABLE ${table}
152
- FIELDS TERMINATED BY ','
153
- ENCLOSED BY '"'
154
- 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);
155
180
  }
156
181
  async function isDatabaseEmpty(database) {
157
- const [total] = await fetchAll(`
182
+ const [row] = await fetchAll(`
158
183
  SELECT
159
184
  COUNT(*) AS total
160
185
  FROM
161
186
  information_schema.tables
162
187
  WHERE
163
- table_schema = '${database}'
164
- `);
165
- return Number(total) ? false : true;
188
+ table_schema = ?
189
+ `, [database]);
190
+ return Number(row.total) ? false : true;
166
191
  }
167
192
  async function createDatabase(database) {
168
- await run(`
193
+ await sql.execute(`
169
194
  CREATE DATABASE IF NOT EXISTS \`${database.name}\`
170
195
  CHARACTER SET ${database.charset ?? "utf8"}
171
196
  COLLATE ${database.charset ?? "utf8_general_ci"}
172
197
  `);
173
198
  }
174
199
  async function fetchVariable(name) {
175
- const stdout = (0, string_1.undefIfEmpty)((await run(`SHOW VARIABLES LIKE "${name}"`)).stdout.trim());
176
- 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);
177
204
  }
178
205
  async function initSharedDir(sharedDir) {
179
206
  const secure_file_priv = await fetchVariable("secure_file_priv");
@@ -193,7 +220,7 @@ function createMysqlCli(options) {
193
220
  try {
194
221
  await (0, fs_1.mkdirIfNotExists)(dir);
195
222
  await (0, promises_1.chmod)(dir, 0o777);
196
- await run(`SELECT 1 INTO OUTFILE ${outFileVar}`);
223
+ await sql.execute(`SELECT 1 INTO OUTFILE ${outFileVar}`);
197
224
  const exists = await (0, fs_1.existsFile)(outFile);
198
225
  if (!exists)
199
226
  throw new AppError_1.AppError(`MySQL shared dir is not reached: ${dir}`);
@@ -205,11 +232,38 @@ function createMysqlCli(options) {
205
232
  catch (e) { }
206
233
  }
207
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
+ }
208
259
  return {
209
260
  options,
210
261
  initSharedDir,
211
262
  args,
212
263
  run,
264
+ execute,
265
+ insert,
266
+ changeDatabase,
213
267
  fetchAll,
214
268
  dump,
215
269
  fetchTableNames,