dbgate-api 4.7.3-alpha.5 → 4.7.4-alpha.2

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/.env CHANGED
@@ -1 +1,3 @@
1
1
  DEVMODE=1
2
+ # DISABLE_SHELL=1
3
+ # HIDE_APP_EDITOR=1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api",
3
3
  "main": "src/index.js",
4
- "version": "4.7.3-alpha.5",
4
+ "version": "4.7.4-alpha.2",
5
5
  "homepage": "https://dbgate.org/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -25,9 +25,9 @@
25
25
  "compare-versions": "^3.6.0",
26
26
  "cors": "^2.8.5",
27
27
  "cross-env": "^6.0.3",
28
- "dbgate-query-splitter": "^4.7.3-alpha.5",
29
- "dbgate-sqltree": "^4.7.3-alpha.5",
30
- "dbgate-tools": "^4.7.3-alpha.5",
28
+ "dbgate-query-splitter": "^4.7.4-alpha.2",
29
+ "dbgate-sqltree": "^4.7.4-alpha.2",
30
+ "dbgate-tools": "^4.7.4-alpha.2",
31
31
  "diff": "^5.0.0",
32
32
  "diff2html": "^3.4.13",
33
33
  "eslint": "^6.8.0",
@@ -63,7 +63,7 @@
63
63
  "devDependencies": {
64
64
  "@types/fs-extra": "^9.0.11",
65
65
  "@types/lodash": "^4.14.149",
66
- "dbgate-types": "^4.7.3-alpha.5",
66
+ "dbgate-types": "^4.7.4-alpha.2",
67
67
  "env-cmd": "^10.1.0",
68
68
  "node-loader": "^1.0.2",
69
69
  "nodemon": "^2.0.2",
@@ -32,6 +32,7 @@ module.exports = {
32
32
  return {
33
33
  runAsPortal: !!connections.portalConnections,
34
34
  singleDatabase: connections.singleDatabase,
35
+ hideAppEditor: !!process.env.HIDE_APP_EDITOR,
35
36
  permissions,
36
37
  ...currentVersion,
37
38
  };
@@ -55,6 +55,7 @@ function getPortalCollections() {
55
55
  (process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null),
56
56
  singleDatabase: !!process.env[`DATABASE_${id}`] || !!process.env[`FILE_${id}`],
57
57
  displayName: process.env[`LABEL_${id}`],
58
+ isReadOnly: process.env[`READONLY_${id}`],
58
59
 
59
60
  // SSH tunnel
60
61
  useSshTunnel: process.env[`USE_SSH_${id}`],
@@ -199,6 +200,9 @@ module.exports = {
199
200
  }
200
201
  socket.emitChanged('connection-list-changed');
201
202
  socket.emitChanged('used-apps-changed');
203
+ if (this._closeAll) {
204
+ this._closeAll(connection._id);
205
+ }
202
206
  // for (const db of connection.databases || []) {
203
207
  // socket.emitChanged(`db-apps-changed-${connection._id}-${db.name}`);
204
208
  // }
@@ -240,6 +244,7 @@ module.exports = {
240
244
 
241
245
  get_meta: true,
242
246
  async get({ conid }) {
247
+ if (!conid) return null;
243
248
  if (portalConnections) return portalConnections.find(x => x._id == conid) || null;
244
249
  const res = await this.datastore.get(conid);
245
250
  return res || null;
@@ -33,6 +33,10 @@ module.exports = {
33
33
  closed: {},
34
34
  requests: {},
35
35
 
36
+ async _init() {
37
+ connections._closeAll = conid => this.closeAll(conid);
38
+ },
39
+
36
40
  handle_structure(conid, database, { structure }) {
37
41
  const existing = this.opened.find(x => x.conid == conid && x.database == database);
38
42
  if (!existing) return;
@@ -136,6 +140,13 @@ module.exports = {
136
140
  return res;
137
141
  },
138
142
 
143
+ sqlSelect_meta: true,
144
+ async sqlSelect({ conid, database, select }) {
145
+ const opened = await this.ensureOpened(conid, database);
146
+ const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select });
147
+ return res;
148
+ },
149
+
139
150
  runScript_meta: true,
140
151
  async runScript({ conid, database, sql }) {
141
152
  console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`);
@@ -148,14 +159,64 @@ module.exports = {
148
159
  async collectionData({ conid, database, options }) {
149
160
  const opened = await this.ensureOpened(conid, database);
150
161
  const res = await this.sendRequest(opened, { msgtype: 'collectionData', options });
151
- return res.result;
162
+ return res.result || null;
163
+ },
164
+
165
+ async loadDataCore(msgtype, { conid, database, ...args }) {
166
+ const opened = await this.ensureOpened(conid, database);
167
+ const res = await this.sendRequest(opened, { msgtype, ...args });
168
+ if (res.errorMessage) {
169
+ console.error(res.errorMessage);
170
+
171
+ return {
172
+ errorMessage: res.errorMessage,
173
+ };
174
+ }
175
+ return res.result || null;
176
+ },
177
+
178
+ loadKeys_meta: true,
179
+ async loadKeys({ conid, database, root }) {
180
+ return this.loadDataCore('loadKeys', { conid, database, root });
181
+ },
182
+
183
+ loadKeyInfo_meta: true,
184
+ async loadKeyInfo({ conid, database, key }) {
185
+ return this.loadDataCore('loadKeyInfo', { conid, database, key });
186
+ },
187
+
188
+ loadKeyTableRange_meta: true,
189
+ async loadKeyTableRange({ conid, database, key, cursor, count }) {
190
+ return this.loadDataCore('loadKeyTableRange', { conid, database, key, cursor, count });
191
+ },
192
+
193
+ loadFieldValues_meta: true,
194
+ async loadFieldValues({ conid, database, schemaName, pureName, field, search }) {
195
+ return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search });
196
+ },
197
+
198
+ callMethod_meta: true,
199
+ async callMethod({ conid, database, method, args }) {
200
+ return this.loadDataCore('callMethod', { conid, database, method, args });
201
+
202
+ // const opened = await this.ensureOpened(conid, database);
203
+ // const res = await this.sendRequest(opened, { msgtype: 'callMethod', method, args });
204
+ // if (res.errorMessage) {
205
+ // console.error(res.errorMessage);
206
+ // }
207
+ // return res.result || null;
152
208
  },
153
209
 
154
210
  updateCollection_meta: true,
155
211
  async updateCollection({ conid, database, changeSet }) {
156
212
  const opened = await this.ensureOpened(conid, database);
157
213
  const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet });
158
- return res.result;
214
+ if (res.errorMessage) {
215
+ return {
216
+ errorMessage: res.errorMessage,
217
+ };
218
+ }
219
+ return res.result || null;
159
220
  },
160
221
 
161
222
  status_meta: true,
@@ -228,6 +289,12 @@ module.exports = {
228
289
  }
229
290
  },
230
291
 
292
+ closeAll(conid, kill = true) {
293
+ for (const existing of this.opened.filter(x => x.conid == conid)) {
294
+ this.close(conid, existing.database, kill);
295
+ }
296
+ },
297
+
231
298
  disconnect_meta: true,
232
299
  async disconnect({ conid, database }) {
233
300
  await this.close(conid, database, true);
@@ -111,18 +111,22 @@ module.exports = {
111
111
  getInfo_meta: true,
112
112
  async getInfo({ jslid }) {
113
113
  const file = getJslFileName(jslid);
114
- const firstLine = await readFirstLine(file);
115
- if (firstLine) {
116
- const parsed = JSON.parse(firstLine);
117
- if (parsed.__isStreamHeader) {
118
- return parsed;
114
+ try {
115
+ const firstLine = await readFirstLine(file);
116
+ if (firstLine) {
117
+ const parsed = JSON.parse(firstLine);
118
+ if (parsed.__isStreamHeader) {
119
+ return parsed;
120
+ }
121
+ return {
122
+ __isStreamHeader: true,
123
+ __isDynamicStructure: true,
124
+ };
119
125
  }
120
- return {
121
- __isStreamHeader: true,
122
- __isDynamicStructure: true,
123
- };
126
+ return null;
127
+ } catch (err) {
128
+ return null;
124
129
  }
125
- return null;
126
130
  },
127
131
 
128
132
  getRows_meta: true,
@@ -148,7 +148,11 @@ module.exports = {
148
148
  },
149
149
 
150
150
  start_meta: true,
151
- async start({ script }) {
151
+ async start({ script, isGeneratedScript }) {
152
+ if (!isGeneratedScript && process.env.DISABLE_SHELL) {
153
+ return { errorMessage: 'Shell is disabled' };
154
+ }
155
+
152
156
  const runid = uuidv1();
153
157
  return this.startCore(runid, scriptTemplate(script, false));
154
158
  },
@@ -4,8 +4,10 @@ const connections = require('./connections');
4
4
  const socket = require('../utility/socket');
5
5
  const { fork } = require('child_process');
6
6
  const jsldata = require('./jsldata');
7
+ const path = require('path');
7
8
  const { handleProcessCommunication } = require('../utility/processComm');
8
9
  const processArgs = require('../utility/processArgs');
10
+ const { appdir } = require('../utility/directories');
9
11
 
10
12
  module.exports = {
11
13
  /** @type {import('dbgate-types').OpenedSession[]} */
@@ -46,9 +48,15 @@ module.exports = {
46
48
  this.dispatchMessage(sesid, info);
47
49
  },
48
50
 
49
- handle_done(sesid) {
51
+ handle_done(sesid, props) {
50
52
  socket.emit(`session-done-${sesid}`);
51
- this.dispatchMessage(sesid, 'Query execution finished');
53
+ if (!props.skipFinishedMessage) {
54
+ this.dispatchMessage(sesid, 'Query execution finished');
55
+ }
56
+ const session = this.opened.find(x => x.sesid == sesid);
57
+ if (session.killOnDone) {
58
+ this.kill({ sesid });
59
+ }
52
60
  },
53
61
 
54
62
  handle_recordset(sesid, props) {
@@ -60,6 +68,11 @@ module.exports = {
60
68
  jsldata.notifyChangedStats(stats);
61
69
  },
62
70
 
71
+ handle_initializeFile(sesid, props) {
72
+ const { jslid } = props;
73
+ socket.emit(`session-initialize-file-${jslid}`);
74
+ },
75
+
63
76
  handle_ping() {},
64
77
 
65
78
  create_meta: true,
@@ -105,6 +118,19 @@ module.exports = {
105
118
  return { state: 'ok' };
106
119
  },
107
120
 
121
+ executeReader_meta: true,
122
+ async executeReader({ conid, database, sql, queryName, appFolder }) {
123
+ const { sesid } = await this.create({ conid, database });
124
+ const session = this.opened.find(x => x.sesid == sesid);
125
+ session.killOnDone = true;
126
+ const jslid = uuidv1();
127
+ const fileName = queryName && appFolder ? path.join(appdir(), appFolder, `${queryName}.query.sql`) : null;
128
+
129
+ session.subprocess.send({ msgtype: 'executeReader', sql, fileName, jslid });
130
+
131
+ return { jslid };
132
+ },
133
+
108
134
  // cancel_meta: true,
109
135
  // async cancel({ sesid }) {
110
136
  // const session = this.opened.find((x) => x.sesid == sesid);
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '4.7.3-alpha.5',
4
- buildTime: '2022-03-14T18:31:03.883Z'
3
+ version: '4.7.4-alpha.2',
4
+ buildTime: '2022-03-17T18:56:01.877Z'
5
5
  };
@@ -7,6 +7,7 @@ const connectUtility = require('../utility/connectUtility');
7
7
  const { handleProcessCommunication } = require('../utility/processComm');
8
8
  const { SqlGenerator } = require('dbgate-tools');
9
9
  const generateDeploySql = require('../shell/generateDeploySql');
10
+ const { dumpSqlSelect } = require('dbgate-sqltree');
10
11
 
11
12
  let systemConnection;
12
13
  let storedConnection;
@@ -154,6 +155,7 @@ async function handleRunScript({ msgid, sql }) {
154
155
  await waitConnected();
155
156
  const driver = requireEngineDriver(storedConnection);
156
157
  try {
158
+ ensureExecuteCustomScript(driver);
157
159
  await driver.script(systemConnection, sql);
158
160
  process.send({ msgtype: 'response', msgid });
159
161
  } catch (err) {
@@ -165,6 +167,7 @@ async function handleQueryData({ msgid, sql }) {
165
167
  await waitConnected();
166
168
  const driver = requireEngineDriver(storedConnection);
167
169
  try {
170
+ ensureExecuteCustomScript(driver);
168
171
  const res = await driver.query(systemConnection, sql);
169
172
  process.send({ msgtype: 'response', msgid, ...res });
170
173
  } catch (err) {
@@ -172,21 +175,67 @@ async function handleQueryData({ msgid, sql }) {
172
175
  }
173
176
  }
174
177
 
175
- async function handleCollectionData({ msgid, options }) {
178
+ async function handleSqlSelect({ msgid, select }) {
179
+ const driver = requireEngineDriver(storedConnection);
180
+ const dmp = driver.createDumper();
181
+ dumpSqlSelect(dmp, select);
182
+ return handleQueryData({ msgid, sql: dmp.s });
183
+ }
184
+
185
+ async function handleDriverDataCore(msgid, callMethod) {
176
186
  await waitConnected();
177
187
  const driver = requireEngineDriver(storedConnection);
178
188
  try {
179
- const result = await driver.readCollection(systemConnection, options);
189
+ const result = await callMethod(driver);
180
190
  process.send({ msgtype: 'response', msgid, result });
181
191
  } catch (err) {
182
192
  process.send({ msgtype: 'response', msgid, errorMessage: err.message });
183
193
  }
184
194
  }
185
195
 
196
+ async function handleCollectionData({ msgid, options }) {
197
+ return handleDriverDataCore(msgid, driver => driver.readCollection(systemConnection, options));
198
+ }
199
+
200
+ async function handleLoadKeys({ msgid, root }) {
201
+ return handleDriverDataCore(msgid, driver => driver.loadKeys(systemConnection, root));
202
+ }
203
+
204
+ async function handleLoadKeyInfo({ msgid, key }) {
205
+ return handleDriverDataCore(msgid, driver => driver.loadKeyInfo(systemConnection, key));
206
+ }
207
+
208
+ async function handleCallMethod({ msgid, method, args }) {
209
+ return handleDriverDataCore(msgid, driver => {
210
+ ensureExecuteCustomScript(driver);
211
+ return driver.callMethod(systemConnection, method, args);
212
+ });
213
+ }
214
+
215
+ async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
216
+ return handleDriverDataCore(msgid, driver => driver.loadKeyTableRange(systemConnection, key, cursor, count));
217
+ }
218
+
219
+ async function handleLoadFieldValues({ msgid, schemaName, pureName, field, search }) {
220
+ return handleDriverDataCore(msgid, driver =>
221
+ driver.loadFieldValues(systemConnection, { schemaName, pureName }, field, search)
222
+ );
223
+ }
224
+
225
+ function ensureExecuteCustomScript(driver) {
226
+ if (driver.readOnlySessions) {
227
+ return;
228
+ }
229
+ if (storedConnection.isReadOnly) {
230
+ throw new Error('Connection is read only');
231
+ }
232
+ }
233
+
186
234
  async function handleUpdateCollection({ msgid, changeSet }) {
187
235
  await waitConnected();
188
236
  const driver = requireEngineDriver(storedConnection);
189
237
  try {
238
+ ensureExecuteCustomScript(driver);
190
239
  const result = await driver.updateCollection(systemConnection, changeSet);
191
240
  process.send({ msgtype: 'response', msgid, result });
192
241
  } catch (err) {
@@ -248,10 +297,16 @@ const messageHandlers = {
248
297
  runScript: handleRunScript,
249
298
  updateCollection: handleUpdateCollection,
250
299
  collectionData: handleCollectionData,
300
+ loadKeys: handleLoadKeys,
301
+ loadKeyInfo: handleLoadKeyInfo,
302
+ callMethod: handleCallMethod,
303
+ loadKeyTableRange: handleLoadKeyTableRange,
251
304
  sqlPreview: handleSqlPreview,
252
305
  ping: handlePing,
253
306
  syncModel: handleSyncModel,
254
307
  generateDeploySql: handleGenerateDeploySql,
308
+ loadFieldValues: handleLoadFieldValues,
309
+ sqlSelect: handleSqlSelect,
255
310
  // runCommand: handleRunCommand,
256
311
  };
257
312
 
@@ -17,11 +17,15 @@ let afterConnectCallbacks = [];
17
17
  // let currentHandlers = [];
18
18
 
19
19
  class TableWriter {
20
- constructor(structure, resultIndex) {
21
- this.jslid = uuidv1();
22
- this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
20
+ constructor() {
23
21
  this.currentRowCount = 0;
24
22
  this.currentChangeIndex = 1;
23
+ this.initializedFile = false;
24
+ }
25
+
26
+ initializeFromQuery(structure, resultIndex) {
27
+ this.jslid = uuidv1();
28
+ this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
25
29
  fs.writeFileSync(
26
30
  this.currentFile,
27
31
  JSON.stringify({
@@ -32,13 +36,21 @@ class TableWriter {
32
36
  this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
33
37
  this.writeCurrentStats(false, false);
34
38
  this.resultIndex = resultIndex;
39
+ this.initializedFile = true;
35
40
  process.send({ msgtype: 'recordset', jslid: this.jslid, resultIndex });
36
41
  }
37
42
 
43
+ initializeFromReader(jslid) {
44
+ this.jslid = jslid;
45
+ this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
46
+ this.writeCurrentStats(false, false);
47
+ }
48
+
38
49
  row(row) {
39
50
  // console.log('ACCEPT ROW', row);
40
51
  this.currentStream.write(JSON.stringify(row) + '\n');
41
52
  this.currentRowCount += 1;
53
+
42
54
  if (!this.plannedStats) {
43
55
  this.plannedStats = true;
44
56
  process.nextTick(() => {
@@ -49,6 +61,21 @@ class TableWriter {
49
61
  }
50
62
  }
51
63
 
64
+ rowFromReader(row) {
65
+ if (!this.initializedFile) {
66
+ process.send({ msgtype: 'initializeFile', jslid: this.jslid });
67
+ this.initializedFile = true;
68
+
69
+ fs.writeFileSync(this.currentFile, JSON.stringify(row) + '\n');
70
+ this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
71
+ this.writeCurrentStats(false, false);
72
+ this.initializedFile = true;
73
+ return;
74
+ }
75
+
76
+ this.row(row);
77
+ }
78
+
52
79
  writeCurrentStats(isFinished = false, emitEvent = false) {
53
80
  const stats = {
54
81
  rowCount: this.currentRowCount,
@@ -63,10 +90,11 @@ class TableWriter {
63
90
  }
64
91
  }
65
92
 
66
- close() {
93
+ close(afterClose) {
67
94
  if (this.currentStream) {
68
95
  this.currentStream.end(() => {
69
96
  this.writeCurrentStats(true, true);
97
+ if (afterClose) afterClose();
70
98
  });
71
99
  }
72
100
  }
@@ -98,7 +126,11 @@ class StreamHandler {
98
126
 
99
127
  recordset(columns) {
100
128
  this.closeCurrentWriter();
101
- this.currentWriter = new TableWriter(Array.isArray(columns) ? { columns } : columns, this.resultIndexHolder.value);
129
+ this.currentWriter = new TableWriter();
130
+ this.currentWriter.initializeFromQuery(
131
+ Array.isArray(columns) ? { columns } : columns,
132
+ this.resultIndexHolder.value
133
+ );
102
134
  this.resultIndexHolder.value += 1;
103
135
 
104
136
  // this.writeCurrentStats();
@@ -110,7 +142,6 @@ class StreamHandler {
110
142
  // }, 500);
111
143
  }
112
144
  row(row) {
113
- // console.log('ACCEPT ROW', row);
114
145
  if (this.currentWriter) this.currentWriter.row(row);
115
146
  else if (row.message) process.send({ msgtype: 'info', info: { message: row.message } });
116
147
  // this.onRow(this.jslid);
@@ -135,6 +166,17 @@ function handleStream(driver, resultIndexHolder, sql) {
135
166
  });
136
167
  }
137
168
 
169
+ function allowExecuteCustomScript(driver) {
170
+ if (driver.readOnlySessions) {
171
+ return true;
172
+ }
173
+ if (storedConnection.isReadOnly) {
174
+ return false;
175
+ // throw new Error('Connection is read only');
176
+ }
177
+ return true;
178
+ }
179
+
138
180
  async function handleConnect(connection) {
139
181
  storedConnection = connection;
140
182
 
@@ -163,6 +205,19 @@ async function handleExecuteQuery({ sql }) {
163
205
  await waitConnected();
164
206
  const driver = requireEngineDriver(storedConnection);
165
207
 
208
+ if (!allowExecuteCustomScript(driver)) {
209
+ process.send({
210
+ msgtype: 'info',
211
+ info: {
212
+ message: 'Connection without read-only sessions is read only',
213
+ severity: 'error',
214
+ },
215
+ });
216
+ process.send({ msgtype: 'done', skipFinishedMessage: true });
217
+ return;
218
+ //process.send({ msgtype: 'error', error: e.message });
219
+ }
220
+
166
221
  const resultIndexHolder = {
167
222
  value: 0,
168
223
  };
@@ -176,9 +231,39 @@ async function handleExecuteQuery({ sql }) {
176
231
  process.send({ msgtype: 'done' });
177
232
  }
178
233
 
234
+ async function handleExecuteReader({ jslid, sql, fileName }) {
235
+ await waitConnected();
236
+
237
+ const driver = requireEngineDriver(storedConnection);
238
+
239
+ if (fileName) {
240
+ sql = fs.readFileSync(fileName, 'utf-8');
241
+ } else {
242
+ if (!allowExecuteCustomScript(driver)) {
243
+ process.send({ msgtype: 'done' });
244
+ return;
245
+ }
246
+ }
247
+
248
+ const writer = new TableWriter();
249
+ writer.initializeFromReader(jslid);
250
+
251
+ const reader = await driver.readQuery(systemConnection, sql);
252
+
253
+ reader.on('data', data => {
254
+ writer.rowFromReader(data);
255
+ });
256
+ reader.on('end', () => {
257
+ writer.close(() => {
258
+ process.send({ msgtype: 'done' });
259
+ });
260
+ });
261
+ }
262
+
179
263
  const messageHandlers = {
180
264
  connect: handleConnect,
181
265
  executeQuery: handleExecuteQuery,
266
+ executeReader: handleExecuteReader,
182
267
  // cancel: handleCancel,
183
268
  };
184
269
 
@@ -9,7 +9,7 @@ async function tableReader({ connection, pureName, schemaName }) {
9
9
 
10
10
  const fullName = { pureName, schemaName };
11
11
 
12
- if (driver.dialect.nosql) {
12
+ if (driver.databaseEngineTypes.includes('document')) {
13
13
  // @ts-ignore
14
14
  console.log(`Reading collection ${fullNameToString(fullName)}`);
15
15
  // @ts-ignore