dbgate-api-premium 6.2.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api-premium",
3
3
  "main": "src/index.js",
4
- "version": "6.2.1",
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.2.1",
32
+ "dbgate-datalib": "^6.3.0",
33
33
  "dbgate-query-splitter": "^4.11.3",
34
- "dbgate-sqltree": "^6.2.1",
35
- "dbgate-tools": "^6.2.1",
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.2.1",
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",
@@ -43,6 +43,7 @@ function authMiddleware(req, res, next) {
43
43
  '/connections/dblogin-app',
44
44
  '/connections/dblogin-auth',
45
45
  '/connections/dblogin-auth-token',
46
+ '/health',
46
47
  ];
47
48
 
48
49
  // console.log('********************* getAuthProvider()', getAuthProvider());
@@ -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 [resolve, reject] = this.requests[runid];
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 [resolve, reject] = this.requests[runid];
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] = [resolve, reject];
326
+ this.requests[runid] = { resolve, reject, exitOnStreamError: true };
235
327
  this.startCore(runid, loaderScriptTemplate(prefix, functionName, props, runid));
236
328
  });
237
329
  return promise;
@@ -127,6 +127,9 @@ module.exports = {
127
127
  this.dispatchMessage(sesid, 'Query session closed');
128
128
  socket.emit(`session-closed-${sesid}`);
129
129
  });
130
+ subprocess.on('error', () => {
131
+ this.opened = this.opened.filter(x => x.sesid != sesid);
132
+ });
130
133
 
131
134
  subprocess.send({
132
135
  msgtype: 'connect',
@@ -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,
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '6.2.1',
4
- buildTime: '2025-02-28T09:57:07.127Z'
3
+ version: '6.3.0',
4
+ buildTime: '2025-03-18T11:15:02.094Z'
5
5
  };
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;
@@ -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
- // return new Promise((resolve, reject) => {
24
- // Stream.pipeline(input, ...transforms, output, err => {
25
- // if (err) {
26
- // reject(err);
27
- // } else {
28
- // resolve();
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
- lastStream.pipe(output);
44
-
45
- // if (output.requireFixedStructure) {
46
- // const ensureHeader = new EnsureStreamHeaderStream();
47
- // input.pipe(ensureHeader);
48
- // ensureHeader.pipe(output);
49
- // } else {
50
- // input.pipe(output);
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;
@@ -24,8 +24,6 @@ async function dataDuplicator({
24
24
  const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
25
25
 
26
26
  try {
27
- logger.info(`Connected.`);
28
-
29
27
  if (!analysedStructure) {
30
28
  analysedStructure = await driver.analyseFull(dbhan);
31
29
  }
@@ -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
  }
@@ -36,7 +36,7 @@ async function executeQuery({
36
36
  }
37
37
 
38
38
  try {
39
- logger.info(`Connected.`);
39
+ logger.debug(`Running SQL query, length: ${sql.length}`);
40
40
 
41
41
  await driver.script(dbhan, sql, { logScriptItems });
42
42
  } finally {
@@ -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
- // @ts-ignore
76
- splittedStream.pipe(importStream);
77
- await awaitStreamEnd(importStream);
63
+
64
+ await streamPipeline(splittedStream, importStream);
78
65
  } finally {
79
66
  if (!systemConnection) {
80
67
  await driver.close(dbhan);
@@ -23,83 +23,98 @@ async function importDbFromFolder({ connection, systemConnection, driver, folder
23
23
  const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
24
24
 
25
25
  try {
26
- const model = await importDbModel(folder);
26
+ if (driver?.databaseEngineTypes?.includes('sql')) {
27
+ const model = await importDbModel(folder);
27
28
 
28
- let modelAdapted = {
29
- ...model,
30
- tables: model.tables.map(table => driver.adaptTableInfo(table)),
31
- };
32
- for (const transform of modelTransforms || []) {
33
- modelAdapted = transform(modelAdapted);
34
- }
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
+ }
35
36
 
36
- const modelNoFk = {
37
- ...modelAdapted,
38
- tables: modelAdapted.tables.map(table => ({
39
- ...table,
40
- foreignKeys: [],
41
- })),
42
- };
37
+ const modelNoFk = {
38
+ ...modelAdapted,
39
+ tables: modelAdapted.tables.map(table => ({
40
+ ...table,
41
+ foreignKeys: [],
42
+ })),
43
+ };
43
44
 
44
- // const plan = createAlterDatabasePlan(
45
- // DatabaseAnalyser.createEmptyStructure(),
46
- // driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
47
- // {},
48
- // DatabaseAnalyser.createEmptyStructure(),
49
- // driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
50
- // driver
51
- // );
52
- // const dmp1 = driver.createDumper({ useHardSeparator: true });
53
- // if (driver.dialect.enableAllForeignKeys) {
54
- // dmp1.enableAllForeignKeys(false);
55
- // }
56
- // plan.run(dmp1);
57
- // if (driver.dialect.enableAllForeignKeys) {
58
- // dmp1.enableAllForeignKeys(true);
59
- // }
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
+ // }
60
61
 
61
- const { sql } = getAlterDatabaseScript(
62
- DatabaseAnalyser.createEmptyStructure(),
63
- driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
64
- {},
65
- DatabaseAnalyser.createEmptyStructure(),
66
- driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
67
- driver
68
- );
69
- // console.log('CREATING STRUCTURE:', sql);
70
- await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
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 });
71
72
 
72
- if (driver.dialect.enableAllForeignKeys) {
73
- await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(false));
74
- }
73
+ if (driver.dialect.enableAllForeignKeys) {
74
+ await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(false));
75
+ }
75
76
 
76
- for (const table of modelAdapted.tables) {
77
- const fileName = path.join(folder, `${table.pureName}.jsonl`);
78
- if (await fs.exists(fileName)) {
79
- const src = await jsonLinesReader({ fileName });
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) });
80
109
  const dst = await tableWriter({
81
110
  systemConnection: dbhan,
82
- pureName: table.pureName,
111
+ pureName,
83
112
  driver,
84
- targetTableStructure: table,
113
+ createIfNotExists: true,
85
114
  });
86
115
  await copyStream(src, dst);
87
116
  }
88
117
  }
89
-
90
- if (driver.dialect.enableAllForeignKeys) {
91
- await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(true));
92
- } else if (driver.dialect.createForeignKey) {
93
- const dmp = driver.createDumper();
94
- for (const table of modelAdapted.tables) {
95
- for (const fk of table.foreignKeys) {
96
- dmp.createForeignKey(fk);
97
- }
98
- }
99
-
100
- // create foreign keys
101
- await executeQuery({ connection, systemConnection: dbhan, driver, sql: dmp.s, logScriptItems: true });
102
- }
103
118
  } finally {
104
119
  if (!systemConnection) {
105
120
  await driver.close(dbhan);
@@ -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');
@@ -61,7 +60,6 @@ const dbgateApi = {
61
60
  loadFile,
62
61
  deployDb,
63
62
  initializeApiEnvironment,
64
- dumpDatabase,
65
63
  importDatabase,
66
64
  loadDatabase,
67
65
  generateModelSql,
@@ -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.pipe(parser);
57
- return parser;
56
+ return [liner, parser];
58
57
  }
59
58
 
60
59
  module.exports = jsonLinesReader;
@@ -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
- fileStream.pipe(parseJsonStream);
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
- parseJsonStream.pipe(filterStream);
85
- filterStream.pipe(tramsformer);
86
- } else {
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 parseStream;
100
+ return resultPipe;
93
101
  }
94
102
 
95
103
  module.exports = jsonReader;
@@ -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.pipe(fileStream);
103
- stringify['finisher'] = fileStream;
104
- return stringify;
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.info(`Analysing database`);
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.info(`Analyse finished`);
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.pipe(parser);
145
- return parser;
144
+ return [liner, parser];
145
+ // liner.pipe(parser);
146
+ // return parser;
146
147
  }
147
148
 
148
149
  module.exports = modifyJsonLinesReader;
@@ -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.pipe(fileStream);
48
- stringify['finisher'] = fileStream;
49
- return stringify;
47
+ return [stringify, fileStream];
48
+ // stringify.pipe(fileStream);
49
+ // stringify['finisher'] = fileStream;
50
+ // return stringify;
50
51
  }
51
52
 
52
53
  module.exports = sqlDataWriter;
@@ -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
 
@@ -15,6 +15,7 @@ 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
18
19
  * @param {any} options.targetTableStructure - target table structure (don't analyse if given)
19
20
  * @returns {Promise<writerType>} - writer object
20
21
  */
@@ -26,8 +27,20 @@ async function tableWriter({ connection, schemaName, pureName, driver, systemCon
26
27
  }
27
28
  const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
28
29
 
29
- logger.info(`Connected.`);
30
- return await driver.writeTable(dbhan, { schemaName, pureName }, options);
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
+ }
31
44
  }
32
45
 
33
46
  module.exports = tableWriter;
@@ -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;