dbgate-api-premium 6.2.0 → 6.3.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.
- package/package.json +5 -5
- package/src/controllers/auth.js +1 -0
- package/src/controllers/connections.js +5 -1
- package/src/controllers/databaseConnections.js +127 -0
- package/src/controllers/runners.js +97 -5
- package/src/controllers/sessions.js +27 -3
- package/src/controllers/uploads.js +48 -1
- package/src/currentVersion.js +2 -2
- package/src/main.js +9 -1
- package/src/proc/sessionProcess.js +43 -2
- package/src/shell/copyStream.js +67 -32
- package/src/shell/dataDuplicator.js +0 -2
- package/src/shell/dropAllDbObjects.js +0 -2
- package/src/shell/executeQuery.js +1 -1
- package/src/shell/importDatabase.js +3 -16
- package/src/shell/importDbFromFolder.js +125 -0
- package/src/shell/index.js +2 -2
- package/src/shell/jsonLinesReader.js +1 -2
- package/src/shell/jsonReader.js +16 -8
- package/src/shell/jsonWriter.js +4 -3
- package/src/shell/loadDatabase.js +2 -4
- package/src/shell/modifyJsonLinesReader.js +3 -2
- package/src/shell/queryReader.js +0 -1
- package/src/shell/sqlDataWriter.js +4 -3
- package/src/shell/tableReader.js +0 -1
- package/src/shell/tableWriter.js +16 -2
- package/src/utility/crypting.js +3 -0
- package/src/utility/healthStatus.js +27 -0
- package/src/utility/streamPipeline.js +18 -0
- package/src/nativeModulesContent.js +0 -9
- package/src/shell/dumpDatabase.js +0 -49
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbgate-api-premium",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.3.0",
|
|
5
5
|
"homepage": "https://dbgate.org/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"compare-versions": "^3.6.0",
|
|
30
30
|
"cors": "^2.8.5",
|
|
31
31
|
"cross-env": "^6.0.3",
|
|
32
|
-
"dbgate-datalib": "^6.
|
|
32
|
+
"dbgate-datalib": "^6.3.0",
|
|
33
33
|
"dbgate-query-splitter": "^4.11.3",
|
|
34
|
-
"dbgate-sqltree": "^6.
|
|
35
|
-
"dbgate-tools": "^6.
|
|
34
|
+
"dbgate-sqltree": "^6.3.0",
|
|
35
|
+
"dbgate-tools": "^6.3.0",
|
|
36
36
|
"debug": "^4.3.4",
|
|
37
37
|
"diff": "^5.0.0",
|
|
38
38
|
"diff2html": "^3.4.13",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@types/fs-extra": "^9.0.11",
|
|
85
85
|
"@types/lodash": "^4.14.149",
|
|
86
|
-
"dbgate-types": "^6.
|
|
86
|
+
"dbgate-types": "^6.3.0",
|
|
87
87
|
"env-cmd": "^10.1.0",
|
|
88
88
|
"jsdoc-to-markdown": "^9.0.5",
|
|
89
89
|
"node-loader": "^1.0.2",
|
package/src/controllers/auth.js
CHANGED
|
@@ -62,7 +62,10 @@ function getPortalCollections() {
|
|
|
62
62
|
port: process.env[`PORT_${id}`],
|
|
63
63
|
databaseUrl: process.env[`URL_${id}`],
|
|
64
64
|
useDatabaseUrl: !!process.env[`URL_${id}`],
|
|
65
|
-
databaseFile: process.env[`FILE_${id}`]
|
|
65
|
+
databaseFile: process.env[`FILE_${id}`]?.replace(
|
|
66
|
+
'%%E2E_TEST_DATA_DIRECTORY%%',
|
|
67
|
+
path.join(path.dirname(path.dirname(__dirname)), 'e2e-tests', 'tmpdata')
|
|
68
|
+
),
|
|
66
69
|
socketPath: process.env[`SOCKET_PATH_${id}`],
|
|
67
70
|
serviceName: process.env[`SERVICE_NAME_${id}`],
|
|
68
71
|
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
|
|
@@ -77,6 +80,7 @@ function getPortalCollections() {
|
|
|
77
80
|
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
|
|
78
81
|
parent: process.env[`PARENT_${id}`] || undefined,
|
|
79
82
|
useSeparateSchemas: !!process.env[`USE_SEPARATE_SCHEMAS_${id}`],
|
|
83
|
+
localDataCenter: process.env[`LOCAL_DATA_CENTER_${id}`],
|
|
80
84
|
|
|
81
85
|
// SSH tunnel
|
|
82
86
|
useSshTunnel: process.env[`USE_SSH_${id}`],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const connections = require('./connections');
|
|
2
|
+
const runners = require('./runners');
|
|
2
3
|
const archive = require('./archive');
|
|
3
4
|
const socket = require('../utility/socket');
|
|
4
5
|
const { fork } = require('child_process');
|
|
@@ -233,6 +234,7 @@ module.exports = {
|
|
|
233
234
|
dispatchDatabaseChangedEvent_meta: true,
|
|
234
235
|
dispatchDatabaseChangedEvent({ event, conid, database }) {
|
|
235
236
|
socket.emitChanged(event, { conid, database });
|
|
237
|
+
return null;
|
|
236
238
|
},
|
|
237
239
|
|
|
238
240
|
loadKeys_meta: true,
|
|
@@ -612,4 +614,129 @@ module.exports = {
|
|
|
612
614
|
|
|
613
615
|
return res;
|
|
614
616
|
},
|
|
617
|
+
|
|
618
|
+
async getNativeOpCommandArgs(
|
|
619
|
+
command,
|
|
620
|
+
{ conid, database, outputFile, inputFile, options, selectedTables, skippedTables, argsFormat }
|
|
621
|
+
) {
|
|
622
|
+
const connection = await connections.getCore({ conid });
|
|
623
|
+
const driver = requireEngineDriver(connection);
|
|
624
|
+
|
|
625
|
+
const settingsValue = await config.getSettings();
|
|
626
|
+
|
|
627
|
+
const externalTools = {};
|
|
628
|
+
for (const pair of Object.entries(settingsValue || {})) {
|
|
629
|
+
const [name, value] = pair;
|
|
630
|
+
if (name.startsWith('externalTools.')) {
|
|
631
|
+
externalTools[name.substring('externalTools.'.length)] = value;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return {
|
|
636
|
+
...(command == 'backup'
|
|
637
|
+
? driver.backupDatabaseCommand(
|
|
638
|
+
connection,
|
|
639
|
+
{ outputFile, database, options, selectedTables, skippedTables, argsFormat },
|
|
640
|
+
// @ts-ignore
|
|
641
|
+
externalTools
|
|
642
|
+
)
|
|
643
|
+
: driver.restoreDatabaseCommand(
|
|
644
|
+
connection,
|
|
645
|
+
{ inputFile, database, options, argsFormat },
|
|
646
|
+
// @ts-ignore
|
|
647
|
+
externalTools
|
|
648
|
+
)),
|
|
649
|
+
transformMessage: driver.transformNativeCommandMessage
|
|
650
|
+
? message => driver.transformNativeCommandMessage(message, command)
|
|
651
|
+
: null,
|
|
652
|
+
};
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
commandArgsToCommandLine(commandArgs) {
|
|
656
|
+
const { command, args, stdinFilePath } = commandArgs;
|
|
657
|
+
let res = `${command} ${args.join(' ')}`;
|
|
658
|
+
if (stdinFilePath) {
|
|
659
|
+
res += ` < ${stdinFilePath}`;
|
|
660
|
+
}
|
|
661
|
+
return res;
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
nativeBackup_meta: true,
|
|
665
|
+
async nativeBackup({ conid, database, outputFile, runid, options, selectedTables, skippedTables }) {
|
|
666
|
+
const commandArgs = await this.getNativeOpCommandArgs('backup', {
|
|
667
|
+
conid,
|
|
668
|
+
database,
|
|
669
|
+
inputFile: undefined,
|
|
670
|
+
outputFile,
|
|
671
|
+
options,
|
|
672
|
+
selectedTables,
|
|
673
|
+
skippedTables,
|
|
674
|
+
argsFormat: 'spawn',
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
return runners.nativeRunCore(runid, {
|
|
678
|
+
...commandArgs,
|
|
679
|
+
onFinished: () => {
|
|
680
|
+
socket.emitChanged(`files-changed`, { folder: 'sql' });
|
|
681
|
+
socket.emitChanged(`all-files-changed`);
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
nativeBackupCommand_meta: true,
|
|
687
|
+
async nativeBackupCommand({ conid, database, outputFile, options, selectedTables, skippedTables }) {
|
|
688
|
+
const commandArgs = await this.getNativeOpCommandArgs('backup', {
|
|
689
|
+
conid,
|
|
690
|
+
database,
|
|
691
|
+
outputFile,
|
|
692
|
+
inputFile: undefined,
|
|
693
|
+
options,
|
|
694
|
+
selectedTables,
|
|
695
|
+
skippedTables,
|
|
696
|
+
argsFormat: 'shell',
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
...commandArgs,
|
|
701
|
+
transformMessage: null,
|
|
702
|
+
commandLine: this.commandArgsToCommandLine(commandArgs),
|
|
703
|
+
};
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
nativeRestore_meta: true,
|
|
707
|
+
async nativeRestore({ conid, database, inputFile, runid }) {
|
|
708
|
+
const commandArgs = await this.getNativeOpCommandArgs('restore', {
|
|
709
|
+
conid,
|
|
710
|
+
database,
|
|
711
|
+
inputFile,
|
|
712
|
+
outputFile: undefined,
|
|
713
|
+
options: undefined,
|
|
714
|
+
argsFormat: 'spawn',
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
return runners.nativeRunCore(runid, {
|
|
718
|
+
...commandArgs,
|
|
719
|
+
onFinished: () => {
|
|
720
|
+
this.syncModel({ conid, database, isFullRefresh: true });
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
},
|
|
724
|
+
|
|
725
|
+
nativeRestoreCommand_meta: true,
|
|
726
|
+
async nativeRestoreCommand({ conid, database, inputFile }) {
|
|
727
|
+
const commandArgs = await this.getNativeOpCommandArgs('restore', {
|
|
728
|
+
conid,
|
|
729
|
+
database,
|
|
730
|
+
inputFile,
|
|
731
|
+
outputFile: undefined,
|
|
732
|
+
options: undefined,
|
|
733
|
+
argsFormat: 'shell',
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
...commandArgs,
|
|
738
|
+
transformMessage: null,
|
|
739
|
+
commandLine: this.commandArgsToCommandLine(commandArgs),
|
|
740
|
+
};
|
|
741
|
+
},
|
|
615
742
|
};
|
|
@@ -4,7 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
5
|
const byline = require('byline');
|
|
6
6
|
const socket = require('../utility/socket');
|
|
7
|
-
const { fork } = require('child_process');
|
|
7
|
+
const { fork, spawn } = require('child_process');
|
|
8
8
|
const { rundir, uploadsdir, pluginsdir, getPluginBackendPath, packagedPluginList } = require('../utility/directories');
|
|
9
9
|
const {
|
|
10
10
|
extractShellApiPlugins,
|
|
@@ -13,6 +13,8 @@ const {
|
|
|
13
13
|
getLogger,
|
|
14
14
|
safeJsonParse,
|
|
15
15
|
pinoLogRecordToMessageRecord,
|
|
16
|
+
extractErrorMessage,
|
|
17
|
+
extractErrorLogData,
|
|
16
18
|
} = require('dbgate-tools');
|
|
17
19
|
const { handleProcessCommunication } = require('../utility/processComm');
|
|
18
20
|
const processArgs = require('../utility/processArgs');
|
|
@@ -80,6 +82,7 @@ module.exports = {
|
|
|
80
82
|
}
|
|
81
83
|
: {
|
|
82
84
|
message,
|
|
85
|
+
severity: 'info',
|
|
83
86
|
time: new Date(),
|
|
84
87
|
};
|
|
85
88
|
|
|
@@ -94,14 +97,26 @@ module.exports = {
|
|
|
94
97
|
handle_ping() {},
|
|
95
98
|
|
|
96
99
|
handle_freeData(runid, { freeData }) {
|
|
97
|
-
const
|
|
100
|
+
const { resolve } = this.requests[runid];
|
|
98
101
|
resolve(freeData);
|
|
99
102
|
delete this.requests[runid];
|
|
100
103
|
},
|
|
101
104
|
|
|
105
|
+
handle_copyStreamError(runid, { copyStreamError }) {
|
|
106
|
+
const { reject, exitOnStreamError } = this.requests[runid] || {};
|
|
107
|
+
if (exitOnStreamError) {
|
|
108
|
+
reject(copyStreamError);
|
|
109
|
+
delete this.requests[runid];
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
handle_progress(runid, progressData) {
|
|
114
|
+
socket.emit(`runner-progress-${runid}`, progressData);
|
|
115
|
+
},
|
|
116
|
+
|
|
102
117
|
rejectRequest(runid, error) {
|
|
103
118
|
if (this.requests[runid]) {
|
|
104
|
-
const
|
|
119
|
+
const { reject } = this.requests[runid];
|
|
105
120
|
reject(error);
|
|
106
121
|
delete this.requests[runid];
|
|
107
122
|
}
|
|
@@ -113,6 +128,8 @@ module.exports = {
|
|
|
113
128
|
fs.writeFileSync(`${scriptFile}`, scriptText);
|
|
114
129
|
fs.mkdirSync(directory);
|
|
115
130
|
const pluginNames = extractPlugins(scriptText);
|
|
131
|
+
// console.log('********************** SCRIPT TEXT **********************');
|
|
132
|
+
// console.log(scriptText);
|
|
116
133
|
logger.info({ scriptFile }, 'Running script');
|
|
117
134
|
// const subprocess = fork(scriptFile, ['--checkParent', '--max-old-space-size=8192'], {
|
|
118
135
|
const subprocess = fork(
|
|
@@ -150,17 +167,21 @@ module.exports = {
|
|
|
150
167
|
byline(subprocess.stdout).on('data', pipeDispatcher('info'));
|
|
151
168
|
byline(subprocess.stderr).on('data', pipeDispatcher('error'));
|
|
152
169
|
subprocess.on('exit', code => {
|
|
170
|
+
// console.log('... EXITED', code);
|
|
153
171
|
this.rejectRequest(runid, { message: 'No data returned, maybe input data source is too big' });
|
|
154
172
|
logger.info({ code, pid: subprocess.pid }, 'Exited process');
|
|
155
173
|
socket.emit(`runner-done-${runid}`, code);
|
|
174
|
+
this.opened = this.opened.filter(x => x.runid != runid);
|
|
156
175
|
});
|
|
157
176
|
subprocess.on('error', error => {
|
|
177
|
+
// console.log('... ERROR subprocess', error);
|
|
158
178
|
this.rejectRequest(runid, { message: error && (error.message || error.toString()) });
|
|
159
179
|
console.error('... ERROR subprocess', error);
|
|
160
|
-
this.dispatchMessage({
|
|
180
|
+
this.dispatchMessage(runid, {
|
|
161
181
|
severity: 'error',
|
|
162
182
|
message: error.toString(),
|
|
163
183
|
});
|
|
184
|
+
this.opened = this.opened.filter(x => x.runid != runid);
|
|
164
185
|
});
|
|
165
186
|
const newOpened = {
|
|
166
187
|
runid,
|
|
@@ -176,6 +197,77 @@ module.exports = {
|
|
|
176
197
|
return _.pick(newOpened, ['runid']);
|
|
177
198
|
},
|
|
178
199
|
|
|
200
|
+
nativeRunCore(runid, commandArgs) {
|
|
201
|
+
const { command, args, env, transformMessage, stdinFilePath, onFinished } = commandArgs;
|
|
202
|
+
const pipeDispatcher = severity => data => {
|
|
203
|
+
let messageObject = {
|
|
204
|
+
message: data.toString().trim(),
|
|
205
|
+
severity,
|
|
206
|
+
};
|
|
207
|
+
if (transformMessage) {
|
|
208
|
+
messageObject = transformMessage(messageObject);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (messageObject) {
|
|
212
|
+
return this.dispatchMessage(runid, messageObject);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const subprocess = spawn(command, args, { env: { ...process.env, ...env } });
|
|
217
|
+
|
|
218
|
+
byline(subprocess.stdout).on('data', pipeDispatcher('info'));
|
|
219
|
+
byline(subprocess.stderr).on('data', pipeDispatcher('error'));
|
|
220
|
+
|
|
221
|
+
subprocess.on('exit', code => {
|
|
222
|
+
console.log('... EXITED', code);
|
|
223
|
+
logger.info({ code, pid: subprocess.pid }, 'Exited process');
|
|
224
|
+
this.dispatchMessage(runid, `Finished external process with code ${code}`);
|
|
225
|
+
socket.emit(`runner-done-${runid}`, code);
|
|
226
|
+
if (onFinished) {
|
|
227
|
+
onFinished();
|
|
228
|
+
}
|
|
229
|
+
this.opened = this.opened.filter(x => x.runid != runid);
|
|
230
|
+
});
|
|
231
|
+
subprocess.on('spawn', () => {
|
|
232
|
+
this.dispatchMessage(runid, `Started external process ${command}`);
|
|
233
|
+
});
|
|
234
|
+
subprocess.on('error', error => {
|
|
235
|
+
console.log('... ERROR subprocess', error);
|
|
236
|
+
this.dispatchMessage(runid, {
|
|
237
|
+
severity: 'error',
|
|
238
|
+
message: error.toString(),
|
|
239
|
+
});
|
|
240
|
+
if (error['code'] == 'ENOENT') {
|
|
241
|
+
this.dispatchMessage(runid, {
|
|
242
|
+
severity: 'error',
|
|
243
|
+
message: `Command ${command} not found, please install it and configure its location in DbGate settings, Settings/External tools, if ${command} is not in system PATH`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
socket.emit(`runner-done-${runid}`);
|
|
247
|
+
this.opened = this.opened.filter(x => x.runid != runid);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (stdinFilePath) {
|
|
251
|
+
const inputStream = fs.createReadStream(stdinFilePath);
|
|
252
|
+
inputStream.pipe(subprocess.stdin);
|
|
253
|
+
|
|
254
|
+
subprocess.stdin.on('error', err => {
|
|
255
|
+
this.dispatchMessage(runid, {
|
|
256
|
+
severity: 'error',
|
|
257
|
+
message: extractErrorMessage(err),
|
|
258
|
+
});
|
|
259
|
+
logger.error(extractErrorLogData(err), 'Caught error on stdin');
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const newOpened = {
|
|
264
|
+
runid,
|
|
265
|
+
subprocess,
|
|
266
|
+
};
|
|
267
|
+
this.opened.push(newOpened);
|
|
268
|
+
return _.pick(newOpened, ['runid']);
|
|
269
|
+
},
|
|
270
|
+
|
|
179
271
|
start_meta: true,
|
|
180
272
|
async start({ script }) {
|
|
181
273
|
const runid = crypto.randomUUID();
|
|
@@ -231,7 +323,7 @@ module.exports = {
|
|
|
231
323
|
|
|
232
324
|
const promise = new Promise((resolve, reject) => {
|
|
233
325
|
const runid = crypto.randomUUID();
|
|
234
|
-
this.requests[runid] =
|
|
326
|
+
this.requests[runid] = { resolve, reject, exitOnStreamError: true };
|
|
235
327
|
this.startCore(runid, loaderScriptTemplate(prefix, functionName, props, runid));
|
|
236
328
|
});
|
|
237
329
|
return promise;
|
|
@@ -56,12 +56,19 @@ module.exports = {
|
|
|
56
56
|
handle_done(sesid, props) {
|
|
57
57
|
socket.emit(`session-done-${sesid}`);
|
|
58
58
|
if (!props.skipFinishedMessage) {
|
|
59
|
-
|
|
59
|
+
if (props.controlCommand) {
|
|
60
|
+
this.dispatchMessage(sesid, `${_.startCase(props.controlCommand)} finished`);
|
|
61
|
+
} else {
|
|
62
|
+
this.dispatchMessage(sesid, 'Query execution finished');
|
|
63
|
+
}
|
|
60
64
|
}
|
|
61
65
|
const session = this.opened.find(x => x.sesid == sesid);
|
|
62
66
|
if (session.loadingReader_jslid) {
|
|
63
67
|
socket.emit(`session-jslid-done-${session.loadingReader_jslid}`);
|
|
64
68
|
}
|
|
69
|
+
if (props.autoCommit) {
|
|
70
|
+
this.executeControlCommand({ sesid, command: 'commitTransaction' });
|
|
71
|
+
}
|
|
65
72
|
if (session.killOnDone) {
|
|
66
73
|
this.kill({ sesid });
|
|
67
74
|
}
|
|
@@ -120,6 +127,9 @@ module.exports = {
|
|
|
120
127
|
this.dispatchMessage(sesid, 'Query session closed');
|
|
121
128
|
socket.emit(`session-closed-${sesid}`);
|
|
122
129
|
});
|
|
130
|
+
subprocess.on('error', () => {
|
|
131
|
+
this.opened = this.opened.filter(x => x.sesid != sesid);
|
|
132
|
+
});
|
|
123
133
|
|
|
124
134
|
subprocess.send({
|
|
125
135
|
msgtype: 'connect',
|
|
@@ -131,7 +141,7 @@ module.exports = {
|
|
|
131
141
|
},
|
|
132
142
|
|
|
133
143
|
executeQuery_meta: true,
|
|
134
|
-
async executeQuery({ sesid, sql }) {
|
|
144
|
+
async executeQuery({ sesid, sql, autoCommit }) {
|
|
135
145
|
const session = this.opened.find(x => x.sesid == sesid);
|
|
136
146
|
if (!session) {
|
|
137
147
|
throw new Error('Invalid session');
|
|
@@ -139,7 +149,21 @@ module.exports = {
|
|
|
139
149
|
|
|
140
150
|
logger.info({ sesid, sql }, 'Processing query');
|
|
141
151
|
this.dispatchMessage(sesid, 'Query execution started');
|
|
142
|
-
session.subprocess.send({ msgtype: 'executeQuery', sql });
|
|
152
|
+
session.subprocess.send({ msgtype: 'executeQuery', sql, autoCommit });
|
|
153
|
+
|
|
154
|
+
return { state: 'ok' };
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
executeControlCommand_meta: true,
|
|
158
|
+
async executeControlCommand({ sesid, command }) {
|
|
159
|
+
const session = this.opened.find(x => x.sesid == sesid);
|
|
160
|
+
if (!session) {
|
|
161
|
+
throw new Error('Invalid session');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
logger.info({ sesid, command }, 'Processing control command');
|
|
165
|
+
this.dispatchMessage(sesid, `${_.startCase(command)} started`);
|
|
166
|
+
session.subprocess.send({ msgtype: 'executeControlCommand', command });
|
|
143
167
|
|
|
144
168
|
return { state: 'ok' };
|
|
145
169
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { uploadsdir, getLogsFilePath } = require('../utility/directories');
|
|
3
|
+
const { uploadsdir, getLogsFilePath, filesdir } = require('../utility/directories');
|
|
4
4
|
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
|
5
5
|
const logger = getLogger('uploads');
|
|
6
6
|
const axios = require('axios');
|
|
@@ -13,6 +13,7 @@ const serverConnections = require('./serverConnections');
|
|
|
13
13
|
const config = require('./config');
|
|
14
14
|
const gistSecret = require('../gistSecret');
|
|
15
15
|
const currentVersion = require('../currentVersion');
|
|
16
|
+
const socket = require('../utility/socket');
|
|
16
17
|
|
|
17
18
|
module.exports = {
|
|
18
19
|
upload_meta: {
|
|
@@ -38,6 +39,52 @@ module.exports = {
|
|
|
38
39
|
});
|
|
39
40
|
},
|
|
40
41
|
|
|
42
|
+
uploadDataFile_meta: {
|
|
43
|
+
method: 'post',
|
|
44
|
+
raw: true,
|
|
45
|
+
},
|
|
46
|
+
uploadDataFile(req, res) {
|
|
47
|
+
const { data } = req.files || {};
|
|
48
|
+
|
|
49
|
+
if (!data) {
|
|
50
|
+
res.json(null);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (data.name.toLowerCase().endsWith('.sql')) {
|
|
55
|
+
logger.info(`Uploading SQL file ${data.name}, size=${data.size}`);
|
|
56
|
+
data.mv(path.join(filesdir(), 'sql', data.name), () => {
|
|
57
|
+
res.json({
|
|
58
|
+
name: data.name,
|
|
59
|
+
folder: 'sql',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
socket.emitChanged(`files-changed`, { folder: 'sql' });
|
|
63
|
+
socket.emitChanged(`all-files-changed`);
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
res.json(null);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
saveDataFile_meta: true,
|
|
72
|
+
async saveDataFile({ filePath }) {
|
|
73
|
+
if (filePath.toLowerCase().endsWith('.sql')) {
|
|
74
|
+
logger.info(`Saving SQL file ${filePath}`);
|
|
75
|
+
await fs.copyFile(filePath, path.join(filesdir(), 'sql', path.basename(filePath)));
|
|
76
|
+
|
|
77
|
+
socket.emitChanged(`files-changed`, { folder: 'sql' });
|
|
78
|
+
socket.emitChanged(`all-files-changed`);
|
|
79
|
+
return {
|
|
80
|
+
name: path.basename(filePath),
|
|
81
|
+
folder: 'sql',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
},
|
|
87
|
+
|
|
41
88
|
get_meta: {
|
|
42
89
|
method: 'get',
|
|
43
90
|
raw: true,
|
package/src/currentVersion.js
CHANGED
package/src/main.js
CHANGED
|
@@ -30,7 +30,7 @@ const queryHistory = require('./controllers/queryHistory');
|
|
|
30
30
|
const onFinished = require('on-finished');
|
|
31
31
|
const processArgs = require('./utility/processArgs');
|
|
32
32
|
|
|
33
|
-
const { rundir } = require('./utility/directories');
|
|
33
|
+
const { rundir, filesdir } = require('./utility/directories');
|
|
34
34
|
const platformInfo = require('./utility/platformInfo');
|
|
35
35
|
const getExpressPath = require('./utility/getExpressPath');
|
|
36
36
|
const _ = require('lodash');
|
|
@@ -38,6 +38,7 @@ const { getLogger } = require('dbgate-tools');
|
|
|
38
38
|
const { getDefaultAuthProvider } = require('./auth/authProvider');
|
|
39
39
|
const startCloudUpgradeTimer = require('./utility/cloudUpgrade');
|
|
40
40
|
const { isProApp } = require('./utility/checkLicense');
|
|
41
|
+
const getHealthStatus = require('./utility/healthStatus');
|
|
41
42
|
|
|
42
43
|
const logger = getLogger('main');
|
|
43
44
|
|
|
@@ -117,6 +118,12 @@ function start() {
|
|
|
117
118
|
});
|
|
118
119
|
});
|
|
119
120
|
|
|
121
|
+
app.get(getExpressPath('/health'), async function (req, res) {
|
|
122
|
+
res.setHeader('Content-Type', 'application/json');
|
|
123
|
+
const health = await getHealthStatus();
|
|
124
|
+
res.end(JSON.stringify(health, null, 2));
|
|
125
|
+
});
|
|
126
|
+
|
|
120
127
|
app.use(bodyParser.json({ limit: '50mb' }));
|
|
121
128
|
|
|
122
129
|
app.use(
|
|
@@ -133,6 +140,7 @@ function start() {
|
|
|
133
140
|
// }
|
|
134
141
|
|
|
135
142
|
app.use(getExpressPath('/runners/data'), express.static(rundir()));
|
|
143
|
+
app.use(getExpressPath('/files/data'), express.static(filesdir()));
|
|
136
144
|
|
|
137
145
|
if (platformInfo.isDocker) {
|
|
138
146
|
const port = process.env.PORT || 3000;
|
|
@@ -245,7 +245,47 @@ async function handleStopProfiler({ jslid }) {
|
|
|
245
245
|
currentProfiler = null;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
async function
|
|
248
|
+
async function handleExecuteControlCommand({ command }) {
|
|
249
|
+
lastActivity = new Date().getTime();
|
|
250
|
+
|
|
251
|
+
await waitConnected();
|
|
252
|
+
const driver = requireEngineDriver(storedConnection);
|
|
253
|
+
|
|
254
|
+
if (command == 'commitTransaction' && !allowExecuteCustomScript(driver)) {
|
|
255
|
+
process.send({
|
|
256
|
+
msgtype: 'info',
|
|
257
|
+
info: {
|
|
258
|
+
message: 'Connection without read-only sessions is read only',
|
|
259
|
+
severity: 'error',
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
process.send({ msgtype: 'done', skipFinishedMessage: true });
|
|
263
|
+
return;
|
|
264
|
+
//process.send({ msgtype: 'error', error: e.message });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
executingScripts++;
|
|
268
|
+
try {
|
|
269
|
+
const dmp = driver.createDumper();
|
|
270
|
+
switch (command) {
|
|
271
|
+
case 'commitTransaction':
|
|
272
|
+
await dmp.commitTransaction();
|
|
273
|
+
break;
|
|
274
|
+
case 'rollbackTransaction':
|
|
275
|
+
await dmp.rollbackTransaction();
|
|
276
|
+
break;
|
|
277
|
+
case 'beginTransaction':
|
|
278
|
+
await dmp.beginTransaction();
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
await driver.query(dbhan, dmp.s, { discardResult: true });
|
|
282
|
+
process.send({ msgtype: 'done', controlCommand: command });
|
|
283
|
+
} finally {
|
|
284
|
+
executingScripts--;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function handleExecuteQuery({ sql, autoCommit }) {
|
|
249
289
|
lastActivity = new Date().getTime();
|
|
250
290
|
|
|
251
291
|
await waitConnected();
|
|
@@ -279,7 +319,7 @@ async function handleExecuteQuery({ sql }) {
|
|
|
279
319
|
// handler.stream = stream;
|
|
280
320
|
// resultIndex = handler.resultIndex;
|
|
281
321
|
}
|
|
282
|
-
process.send({ msgtype: 'done' });
|
|
322
|
+
process.send({ msgtype: 'done', autoCommit });
|
|
283
323
|
} finally {
|
|
284
324
|
executingScripts--;
|
|
285
325
|
}
|
|
@@ -323,6 +363,7 @@ function handlePing() {
|
|
|
323
363
|
const messageHandlers = {
|
|
324
364
|
connect: handleConnect,
|
|
325
365
|
executeQuery: handleExecuteQuery,
|
|
366
|
+
executeControlCommand: handleExecuteControlCommand,
|
|
326
367
|
executeReader: handleExecuteReader,
|
|
327
368
|
startProfiler: handleStartProfiler,
|
|
328
369
|
stopProfiler: handleStopProfiler,
|
package/src/shell/copyStream.js
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
const EnsureStreamHeaderStream = require('../utility/EnsureStreamHeaderStream');
|
|
2
|
-
const Stream = require('stream');
|
|
3
2
|
const ColumnMapTransformStream = require('../utility/ColumnMapTransformStream');
|
|
3
|
+
const streamPipeline = require('../utility/streamPipeline');
|
|
4
|
+
const { getLogger, extractErrorLogData, RowProgressReporter, extractErrorMessage } = require('dbgate-tools');
|
|
5
|
+
const logger = getLogger('copyStream');
|
|
6
|
+
const stream = require('stream');
|
|
7
|
+
|
|
8
|
+
class ReportingTransform extends stream.Transform {
|
|
9
|
+
constructor(reporter, options = {}) {
|
|
10
|
+
super({ ...options, objectMode: true });
|
|
11
|
+
this.reporter = reporter;
|
|
12
|
+
}
|
|
13
|
+
_transform(chunk, encoding, callback) {
|
|
14
|
+
if (!chunk?.__isStreamHeader) {
|
|
15
|
+
this.reporter.add(1);
|
|
16
|
+
}
|
|
17
|
+
this.push(chunk);
|
|
18
|
+
callback();
|
|
19
|
+
}
|
|
20
|
+
_flush(callback) {
|
|
21
|
+
this.reporter.finish();
|
|
22
|
+
callback();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
4
25
|
|
|
5
26
|
/**
|
|
6
27
|
* Copies reader to writer. Used for import, export tables and transfer data between tables
|
|
@@ -9,10 +30,23 @@ const ColumnMapTransformStream = require('../utility/ColumnMapTransformStream');
|
|
|
9
30
|
* @param {object} options - options
|
|
10
31
|
* @returns {Promise}
|
|
11
32
|
*/
|
|
12
|
-
function copyStream(input, output, options) {
|
|
13
|
-
const { columns } = options || {};
|
|
33
|
+
async function copyStream(input, output, options) {
|
|
34
|
+
const { columns, progressName } = options || {};
|
|
35
|
+
|
|
36
|
+
if (progressName) {
|
|
37
|
+
process.send({
|
|
38
|
+
msgtype: 'progress',
|
|
39
|
+
progressName,
|
|
40
|
+
status: 'running',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
14
43
|
|
|
15
44
|
const transforms = [];
|
|
45
|
+
|
|
46
|
+
if (progressName) {
|
|
47
|
+
const reporter = new RowProgressReporter(progressName, 'readRowCount');
|
|
48
|
+
transforms.push(new ReportingTransform(reporter));
|
|
49
|
+
}
|
|
16
50
|
if (columns) {
|
|
17
51
|
transforms.push(new ColumnMapTransformStream(columns));
|
|
18
52
|
}
|
|
@@ -20,36 +54,37 @@ function copyStream(input, output, options) {
|
|
|
20
54
|
transforms.push(new EnsureStreamHeaderStream());
|
|
21
55
|
}
|
|
22
56
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const finisher = output['finisher'] || output;
|
|
35
|
-
finisher.on('finish', resolve);
|
|
36
|
-
finisher.on('error', reject);
|
|
37
|
-
|
|
38
|
-
let lastStream = input;
|
|
39
|
-
for (const tran of transforms) {
|
|
40
|
-
lastStream.pipe(tran);
|
|
41
|
-
lastStream = tran;
|
|
57
|
+
try {
|
|
58
|
+
await streamPipeline(input, transforms, output);
|
|
59
|
+
|
|
60
|
+
if (progressName) {
|
|
61
|
+
process.send({
|
|
62
|
+
msgtype: 'progress',
|
|
63
|
+
progressName,
|
|
64
|
+
status: 'done',
|
|
65
|
+
});
|
|
42
66
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
} catch (err) {
|
|
68
|
+
process.send({
|
|
69
|
+
msgtype: 'copyStreamError',
|
|
70
|
+
copyStreamError: {
|
|
71
|
+
message: extractErrorMessage(err),
|
|
72
|
+
...err,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (progressName) {
|
|
77
|
+
process.send({
|
|
78
|
+
msgtype: 'progress',
|
|
79
|
+
progressName,
|
|
80
|
+
status: 'error',
|
|
81
|
+
errorMessage: extractErrorMessage(err),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
logger.error(extractErrorLogData(err, { progressName }), 'Import/export job failed');
|
|
86
|
+
// throw err;
|
|
87
|
+
}
|
|
53
88
|
}
|
|
54
89
|
|
|
55
90
|
module.exports = copyStream;
|
|
@@ -19,8 +19,6 @@ async function dropAllDbObjects({ connection, systemConnection, driver, analysed
|
|
|
19
19
|
|
|
20
20
|
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
|
|
21
21
|
|
|
22
|
-
logger.info(`Connected.`);
|
|
23
|
-
|
|
24
22
|
if (!analysedStructure) {
|
|
25
23
|
analysedStructure = await driver.analyseFull(dbhan);
|
|
26
24
|
}
|
|
@@ -5,6 +5,7 @@ const { splitQueryStream } = require('dbgate-query-splitter/lib/splitQueryStream
|
|
|
5
5
|
const download = require('./download');
|
|
6
6
|
const stream = require('stream');
|
|
7
7
|
const { getLogger } = require('dbgate-tools');
|
|
8
|
+
const streamPipeline = require('../utility/streamPipeline');
|
|
8
9
|
|
|
9
10
|
const logger = getLogger('importDb');
|
|
10
11
|
|
|
@@ -43,25 +44,12 @@ class ImportStream extends stream.Transform {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
function awaitStreamEnd(stream) {
|
|
47
|
-
return new Promise((resolve, reject) => {
|
|
48
|
-
stream.once('end', () => {
|
|
49
|
-
resolve(true);
|
|
50
|
-
});
|
|
51
|
-
stream.once('error', err => {
|
|
52
|
-
reject(err);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
47
|
async function importDatabase({ connection = undefined, systemConnection = undefined, driver = undefined, inputFile }) {
|
|
58
48
|
logger.info(`Importing database`);
|
|
59
49
|
|
|
60
50
|
if (!driver) driver = requireEngineDriver(connection);
|
|
61
51
|
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
|
|
62
52
|
try {
|
|
63
|
-
logger.info(`Connected.`);
|
|
64
|
-
|
|
65
53
|
logger.info(`Input file: ${inputFile}`);
|
|
66
54
|
const downloadedFile = await download(inputFile);
|
|
67
55
|
logger.info(`Downloaded file: ${downloadedFile}`);
|
|
@@ -72,9 +60,8 @@ async function importDatabase({ connection = undefined, systemConnection = undef
|
|
|
72
60
|
returnRichInfo: true,
|
|
73
61
|
});
|
|
74
62
|
const importStream = new ImportStream(dbhan, driver);
|
|
75
|
-
|
|
76
|
-
splittedStream
|
|
77
|
-
await awaitStreamEnd(importStream);
|
|
63
|
+
|
|
64
|
+
await streamPipeline(splittedStream, importStream);
|
|
78
65
|
} finally {
|
|
79
66
|
if (!systemConnection) {
|
|
80
67
|
await driver.close(dbhan);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const executeQuery = require('./executeQuery');
|
|
4
|
+
const { connectUtility } = require('../utility/connectUtility');
|
|
5
|
+
const requireEngineDriver = require('../utility/requireEngineDriver');
|
|
6
|
+
const { getAlterDatabaseScript, DatabaseAnalyser, runCommandOnDriver } = require('dbgate-tools');
|
|
7
|
+
const importDbModel = require('../utility/importDbModel');
|
|
8
|
+
const jsonLinesReader = require('./jsonLinesReader');
|
|
9
|
+
const tableWriter = require('./tableWriter');
|
|
10
|
+
const copyStream = require('./copyStream');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Deploys database model stored in modelFolder (table as yamls) to database
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {connectionType} options.connection - connection object
|
|
16
|
+
* @param {object} options.systemConnection - system connection (result of driver.connect). If not provided, new connection will be created
|
|
17
|
+
* @param {object} options.driver - driver object. If not provided, it will be loaded from connection
|
|
18
|
+
* @param {string} options.folder - folder with model files (YAML files for tables, SQL files for views, procedures, ...)
|
|
19
|
+
* @param {function[]} options.modelTransforms - array of functions for transforming model
|
|
20
|
+
*/
|
|
21
|
+
async function importDbFromFolder({ connection, systemConnection, driver, folder, modelTransforms }) {
|
|
22
|
+
if (!driver) driver = requireEngineDriver(connection);
|
|
23
|
+
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
if (driver?.databaseEngineTypes?.includes('sql')) {
|
|
27
|
+
const model = await importDbModel(folder);
|
|
28
|
+
|
|
29
|
+
let modelAdapted = {
|
|
30
|
+
...model,
|
|
31
|
+
tables: model.tables.map(table => driver.adaptTableInfo(table)),
|
|
32
|
+
};
|
|
33
|
+
for (const transform of modelTransforms || []) {
|
|
34
|
+
modelAdapted = transform(modelAdapted);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const modelNoFk = {
|
|
38
|
+
...modelAdapted,
|
|
39
|
+
tables: modelAdapted.tables.map(table => ({
|
|
40
|
+
...table,
|
|
41
|
+
foreignKeys: [],
|
|
42
|
+
})),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// const plan = createAlterDatabasePlan(
|
|
46
|
+
// DatabaseAnalyser.createEmptyStructure(),
|
|
47
|
+
// driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
|
|
48
|
+
// {},
|
|
49
|
+
// DatabaseAnalyser.createEmptyStructure(),
|
|
50
|
+
// driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
|
|
51
|
+
// driver
|
|
52
|
+
// );
|
|
53
|
+
// const dmp1 = driver.createDumper({ useHardSeparator: true });
|
|
54
|
+
// if (driver.dialect.enableAllForeignKeys) {
|
|
55
|
+
// dmp1.enableAllForeignKeys(false);
|
|
56
|
+
// }
|
|
57
|
+
// plan.run(dmp1);
|
|
58
|
+
// if (driver.dialect.enableAllForeignKeys) {
|
|
59
|
+
// dmp1.enableAllForeignKeys(true);
|
|
60
|
+
// }
|
|
61
|
+
|
|
62
|
+
const { sql } = getAlterDatabaseScript(
|
|
63
|
+
DatabaseAnalyser.createEmptyStructure(),
|
|
64
|
+
driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
|
|
65
|
+
{},
|
|
66
|
+
DatabaseAnalyser.createEmptyStructure(),
|
|
67
|
+
driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
|
|
68
|
+
driver
|
|
69
|
+
);
|
|
70
|
+
// console.log('CREATING STRUCTURE:', sql);
|
|
71
|
+
await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
|
|
72
|
+
|
|
73
|
+
if (driver.dialect.enableAllForeignKeys) {
|
|
74
|
+
await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(false));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const table of modelAdapted.tables) {
|
|
78
|
+
const fileName = path.join(folder, `${table.pureName}.jsonl`);
|
|
79
|
+
if (await fs.exists(fileName)) {
|
|
80
|
+
const src = await jsonLinesReader({ fileName });
|
|
81
|
+
const dst = await tableWriter({
|
|
82
|
+
systemConnection: dbhan,
|
|
83
|
+
pureName: table.pureName,
|
|
84
|
+
driver,
|
|
85
|
+
targetTableStructure: table,
|
|
86
|
+
});
|
|
87
|
+
await copyStream(src, dst);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (driver.dialect.enableAllForeignKeys) {
|
|
92
|
+
await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(true));
|
|
93
|
+
} else if (driver.dialect.createForeignKey) {
|
|
94
|
+
const dmp = driver.createDumper();
|
|
95
|
+
for (const table of modelAdapted.tables) {
|
|
96
|
+
for (const fk of table.foreignKeys) {
|
|
97
|
+
dmp.createForeignKey(fk);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// create foreign keys
|
|
102
|
+
await executeQuery({ connection, systemConnection: dbhan, driver, sql: dmp.s, logScriptItems: true });
|
|
103
|
+
}
|
|
104
|
+
} else if (driver?.databaseEngineTypes?.includes('document')) {
|
|
105
|
+
for (const file of fs.readdirSync(folder)) {
|
|
106
|
+
if (!file.endsWith('.jsonl')) continue;
|
|
107
|
+
const pureName = path.parse(file).name;
|
|
108
|
+
const src = await jsonLinesReader({ fileName: path.join(folder, file) });
|
|
109
|
+
const dst = await tableWriter({
|
|
110
|
+
systemConnection: dbhan,
|
|
111
|
+
pureName,
|
|
112
|
+
driver,
|
|
113
|
+
createIfNotExists: true,
|
|
114
|
+
});
|
|
115
|
+
await copyStream(src, dst);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
if (!systemConnection) {
|
|
120
|
+
await driver.close(dbhan);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = importDbFromFolder;
|
package/src/shell/index.js
CHANGED
|
@@ -21,7 +21,6 @@ const executeQuery = require('./executeQuery');
|
|
|
21
21
|
const loadFile = require('./loadFile');
|
|
22
22
|
const deployDb = require('./deployDb');
|
|
23
23
|
const initializeApiEnvironment = require('./initializeApiEnvironment');
|
|
24
|
-
const dumpDatabase = require('./dumpDatabase');
|
|
25
24
|
const importDatabase = require('./importDatabase');
|
|
26
25
|
const loadDatabase = require('./loadDatabase');
|
|
27
26
|
const generateModelSql = require('./generateModelSql');
|
|
@@ -35,6 +34,7 @@ const sqlTextReplacementTransform = require('./sqlTextReplacementTransform');
|
|
|
35
34
|
const autoIndexForeignKeysTransform = require('./autoIndexForeignKeysTransform');
|
|
36
35
|
const generateDeploySql = require('./generateDeploySql');
|
|
37
36
|
const dropAllDbObjects = require('./dropAllDbObjects');
|
|
37
|
+
const importDbFromFolder = require('./importDbFromFolder');
|
|
38
38
|
|
|
39
39
|
const dbgateApi = {
|
|
40
40
|
queryReader,
|
|
@@ -60,7 +60,6 @@ const dbgateApi = {
|
|
|
60
60
|
loadFile,
|
|
61
61
|
deployDb,
|
|
62
62
|
initializeApiEnvironment,
|
|
63
|
-
dumpDatabase,
|
|
64
63
|
importDatabase,
|
|
65
64
|
loadDatabase,
|
|
66
65
|
generateModelSql,
|
|
@@ -73,6 +72,7 @@ const dbgateApi = {
|
|
|
73
72
|
autoIndexForeignKeysTransform,
|
|
74
73
|
generateDeploySql,
|
|
75
74
|
dropAllDbObjects,
|
|
75
|
+
importDbFromFolder,
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
requirePlugin.initializeDbgateApi(dbgateApi);
|
|
@@ -53,8 +53,7 @@ async function jsonLinesReader({ fileName, encoding = 'utf-8', limitRows = undef
|
|
|
53
53
|
);
|
|
54
54
|
const liner = byline(fileStream);
|
|
55
55
|
const parser = new ParseStream({ limitRows });
|
|
56
|
-
liner
|
|
57
|
-
return parser;
|
|
56
|
+
return [liner, parser];
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
module.exports = jsonLinesReader;
|
package/src/shell/jsonReader.js
CHANGED
|
@@ -10,7 +10,6 @@ const download = require('./download');
|
|
|
10
10
|
|
|
11
11
|
const logger = getLogger('jsonReader');
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
class ParseStream extends stream.Transform {
|
|
15
14
|
constructor({ limitRows, jsonStyle, keyField }) {
|
|
16
15
|
super({ objectMode: true });
|
|
@@ -72,8 +71,12 @@ async function jsonReader({
|
|
|
72
71
|
// @ts-ignore
|
|
73
72
|
encoding
|
|
74
73
|
);
|
|
74
|
+
|
|
75
75
|
const parseJsonStream = parser();
|
|
76
|
-
|
|
76
|
+
|
|
77
|
+
const resultPipe = [fileStream, parseJsonStream];
|
|
78
|
+
|
|
79
|
+
// fileStream.pipe(parseJsonStream);
|
|
77
80
|
|
|
78
81
|
const parseStream = new ParseStream({ limitRows, jsonStyle, keyField });
|
|
79
82
|
|
|
@@ -81,15 +84,20 @@ async function jsonReader({
|
|
|
81
84
|
|
|
82
85
|
if (rootField) {
|
|
83
86
|
const filterStream = pick({ filter: rootField });
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
parseJsonStream.pipe(tramsformer);
|
|
87
|
+
resultPipe.push(filterStream);
|
|
88
|
+
// parseJsonStream.pipe(filterStream);
|
|
89
|
+
// filterStream.pipe(tramsformer);
|
|
88
90
|
}
|
|
91
|
+
// else {
|
|
92
|
+
// parseJsonStream.pipe(tramsformer);
|
|
93
|
+
// }
|
|
94
|
+
|
|
95
|
+
resultPipe.push(tramsformer);
|
|
96
|
+
resultPipe.push(parseStream);
|
|
89
97
|
|
|
90
|
-
tramsformer.pipe(parseStream);
|
|
98
|
+
// tramsformer.pipe(parseStream);
|
|
91
99
|
|
|
92
|
-
return
|
|
100
|
+
return resultPipe;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
module.exports = jsonReader;
|
package/src/shell/jsonWriter.js
CHANGED
|
@@ -99,9 +99,10 @@ async function jsonWriter({ fileName, jsonStyle, keyField = '_key', rootField, e
|
|
|
99
99
|
logger.info(`Writing file ${fileName}`);
|
|
100
100
|
const stringify = new StringifyStream({ jsonStyle, keyField, rootField });
|
|
101
101
|
const fileStream = fs.createWriteStream(fileName, encoding);
|
|
102
|
-
stringify
|
|
103
|
-
stringify
|
|
104
|
-
|
|
102
|
+
return [stringify, fileStream];
|
|
103
|
+
// stringify.pipe(fileStream);
|
|
104
|
+
// stringify['finisher'] = fileStream;
|
|
105
|
+
// return stringify;
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
module.exports = jsonWriter;
|
|
@@ -6,15 +6,13 @@ const exportDbModel = require('../utility/exportDbModel');
|
|
|
6
6
|
const logger = getLogger('analyseDb');
|
|
7
7
|
|
|
8
8
|
async function loadDatabase({ connection = undefined, systemConnection = undefined, driver = undefined, outputDir }) {
|
|
9
|
-
logger.
|
|
9
|
+
logger.debug(`Analysing database`);
|
|
10
10
|
|
|
11
11
|
if (!driver) driver = requireEngineDriver(connection);
|
|
12
12
|
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
|
|
13
13
|
try {
|
|
14
|
-
logger.info(`Connected.`);
|
|
15
|
-
|
|
16
14
|
const dbInfo = await driver.analyseFull(dbhan);
|
|
17
|
-
logger.
|
|
15
|
+
logger.debug(`Analyse finished`);
|
|
18
16
|
|
|
19
17
|
await exportDbModel(dbInfo, outputDir);
|
|
20
18
|
} finally {
|
|
@@ -141,8 +141,9 @@ async function modifyJsonLinesReader({
|
|
|
141
141
|
);
|
|
142
142
|
const liner = byline(fileStream);
|
|
143
143
|
const parser = new ParseStream({ limitRows, changeSet, mergedRows, mergeKey, mergeMode });
|
|
144
|
-
liner
|
|
145
|
-
|
|
144
|
+
return [liner, parser];
|
|
145
|
+
// liner.pipe(parser);
|
|
146
|
+
// return parser;
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
module.exports = modifyJsonLinesReader;
|
package/src/shell/queryReader.js
CHANGED
|
@@ -30,7 +30,6 @@ async function queryReader({
|
|
|
30
30
|
|
|
31
31
|
const driver = requireEngineDriver(connection);
|
|
32
32
|
const pool = await connectUtility(driver, connection, queryType == 'json' ? 'read' : 'script');
|
|
33
|
-
logger.info(`Connected.`);
|
|
34
33
|
const reader =
|
|
35
34
|
queryType == 'json' ? await driver.readJsonQuery(pool, query) : await driver.readQuery(pool, query || sql);
|
|
36
35
|
return reader;
|
|
@@ -44,9 +44,10 @@ async function sqlDataWriter({ fileName, dataName, driver, encoding = 'utf-8' })
|
|
|
44
44
|
logger.info(`Writing file ${fileName}`);
|
|
45
45
|
const stringify = new SqlizeStream({ fileName, dataName });
|
|
46
46
|
const fileStream = fs.createWriteStream(fileName, encoding);
|
|
47
|
-
stringify
|
|
48
|
-
stringify
|
|
49
|
-
|
|
47
|
+
return [stringify, fileStream];
|
|
48
|
+
// stringify.pipe(fileStream);
|
|
49
|
+
// stringify['finisher'] = fileStream;
|
|
50
|
+
// return stringify;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
module.exports = sqlDataWriter;
|
package/src/shell/tableReader.js
CHANGED
|
@@ -18,7 +18,6 @@ async function tableReader({ connection, systemConnection, pureName, schemaName,
|
|
|
18
18
|
driver = requireEngineDriver(connection);
|
|
19
19
|
}
|
|
20
20
|
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
|
|
21
|
-
logger.info(`Connected.`);
|
|
22
21
|
|
|
23
22
|
const fullName = { pureName, schemaName };
|
|
24
23
|
|
package/src/shell/tableWriter.js
CHANGED
|
@@ -15,6 +15,8 @@ const logger = getLogger('tableWriter');
|
|
|
15
15
|
* @param {boolean} options.truncate - truncate table before insert
|
|
16
16
|
* @param {boolean} options.createIfNotExists - create table if not exists
|
|
17
17
|
* @param {boolean} options.commitAfterInsert - commit transaction after insert
|
|
18
|
+
* @param {string} options.progressName - name for reporting progress
|
|
19
|
+
* @param {any} options.targetTableStructure - target table structure (don't analyse if given)
|
|
18
20
|
* @returns {Promise<writerType>} - writer object
|
|
19
21
|
*/
|
|
20
22
|
async function tableWriter({ connection, schemaName, pureName, driver, systemConnection, ...options }) {
|
|
@@ -25,8 +27,20 @@ async function tableWriter({ connection, schemaName, pureName, driver, systemCon
|
|
|
25
27
|
}
|
|
26
28
|
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
try {
|
|
31
|
+
return await driver.writeTable(dbhan, { schemaName, pureName }, options);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (options.progressName) {
|
|
34
|
+
process.send({
|
|
35
|
+
msgtype: 'progress',
|
|
36
|
+
progressName: options.progressName,
|
|
37
|
+
status: 'error',
|
|
38
|
+
errorMessage: err.message,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
module.exports = tableWriter;
|
package/src/utility/crypting.js
CHANGED
|
@@ -88,6 +88,9 @@ function decryptConnection(connection) {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function pickSafeConnectionInfo(connection) {
|
|
91
|
+
if (process.env.LOG_CONNECTION_SENSITIVE_VALUES) {
|
|
92
|
+
return connection;
|
|
93
|
+
}
|
|
91
94
|
return _.mapValues(connection, (v, k) => {
|
|
92
95
|
if (k == 'engine' || k == 'port' || k == 'authType' || k == 'sshMode' || k == 'passwordMode') return v;
|
|
93
96
|
if (v === null || v === true || v === false) return v;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
|
|
3
|
+
const databaseConnections = require('../controllers/databaseConnections');
|
|
4
|
+
const serverConnections = require('../controllers/serverConnections');
|
|
5
|
+
const sessions = require('../controllers/sessions');
|
|
6
|
+
const runners = require('../controllers/runners');
|
|
7
|
+
|
|
8
|
+
async function getHealthStatus() {
|
|
9
|
+
const memory = process.memoryUsage();
|
|
10
|
+
const cpuUsage = process.cpuUsage();
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
status: 'ok',
|
|
14
|
+
databaseConnectionCount: databaseConnections.opened.length,
|
|
15
|
+
serverConnectionCount: serverConnections.opened.length,
|
|
16
|
+
sessionCount: sessions.opened.length,
|
|
17
|
+
runProcessCount: runners.opened.length,
|
|
18
|
+
memory,
|
|
19
|
+
cpuUsage,
|
|
20
|
+
systemMemory: {
|
|
21
|
+
total: os.totalmem(),
|
|
22
|
+
free: os.freemem(),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = getHealthStatus;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const stream = require('stream');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
|
|
4
|
+
function streamPipeline(...processedStreams) {
|
|
5
|
+
const streams = _.flattenDeep(processedStreams);
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
stream.pipeline(...streams, err => {
|
|
9
|
+
if (err) {
|
|
10
|
+
reject(err);
|
|
11
|
+
} else {
|
|
12
|
+
resolve();
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = streamPipeline;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
|
|
3
|
-
const content = {};
|
|
4
|
-
|
|
5
|
-
content['better-sqlite3'] = () => require('better-sqlite3');
|
|
6
|
-
content['oracledb'] = () => require('oracledb');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
module.exports = content;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const requireEngineDriver = require('../utility/requireEngineDriver');
|
|
2
|
-
const { connectUtility } = require('../utility/connectUtility');
|
|
3
|
-
const { getLogger } = require('dbgate-tools');
|
|
4
|
-
|
|
5
|
-
const logger = getLogger('dumpDb');
|
|
6
|
-
|
|
7
|
-
function doDump(dumper) {
|
|
8
|
-
return new Promise((resolve, reject) => {
|
|
9
|
-
dumper.once('end', () => {
|
|
10
|
-
resolve(true);
|
|
11
|
-
});
|
|
12
|
-
dumper.once('error', err => {
|
|
13
|
-
reject(err);
|
|
14
|
-
});
|
|
15
|
-
dumper.run();
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function dumpDatabase({
|
|
20
|
-
connection = undefined,
|
|
21
|
-
systemConnection = undefined,
|
|
22
|
-
driver = undefined,
|
|
23
|
-
outputFile,
|
|
24
|
-
databaseName,
|
|
25
|
-
schemaName,
|
|
26
|
-
}) {
|
|
27
|
-
logger.info(`Dumping database`);
|
|
28
|
-
|
|
29
|
-
if (!driver) driver = requireEngineDriver(connection);
|
|
30
|
-
|
|
31
|
-
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read', { forceRowsAsObjects: true }));
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
logger.info(`Connected.`);
|
|
35
|
-
|
|
36
|
-
const dumper = await driver.createBackupDumper(dbhan, {
|
|
37
|
-
outputFile,
|
|
38
|
-
databaseName,
|
|
39
|
-
schemaName,
|
|
40
|
-
});
|
|
41
|
-
await doDump(dumper);
|
|
42
|
-
} finally {
|
|
43
|
-
if (!systemConnection) {
|
|
44
|
-
await driver.close(dbhan);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
module.exports = dumpDatabase;
|