dbgate-api 7.2.0 → 7.2.1

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",
3
3
  "main": "src/index.js",
4
- "version": "7.2.0",
4
+ "version": "7.2.1",
5
5
  "homepage": "https://www.dbgate.io/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,11 +30,11 @@
30
30
  "compare-versions": "^3.6.0",
31
31
  "cors": "^2.8.5",
32
32
  "cross-env": "^6.0.3",
33
- "dbgate-datalib": "7.2.0",
33
+ "dbgate-datalib": "7.2.1",
34
34
  "dbgate-query-splitter": "^4.12.0",
35
- "dbgate-rest": "7.2.0",
36
- "dbgate-sqltree": "7.2.0",
37
- "dbgate-tools": "7.2.0",
35
+ "dbgate-rest": "7.2.1",
36
+ "dbgate-sqltree": "7.2.1",
37
+ "dbgate-tools": "7.2.1",
38
38
  "debug": "^4.3.4",
39
39
  "diff": "^5.0.0",
40
40
  "diff2html": "^3.4.13",
@@ -96,7 +96,7 @@
96
96
  "@types/fs-extra": "^9.0.11",
97
97
  "@types/jest": "^30.0.0",
98
98
  "@types/lodash": "^4.14.149",
99
- "dbgate-types": "7.2.0",
99
+ "dbgate-types": "7.2.1",
100
100
  "env-cmd": "^10.1.0",
101
101
  "jest": "^30.4.2",
102
102
  "jsdoc-to-markdown": "^9.0.5",
@@ -16,6 +16,7 @@ const connections = require('../controllers/connections');
16
16
  const { getAuthProviderFromReq } = require('../auth/authProvider');
17
17
  const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
18
18
  const storage = require('./storage');
19
+ const dbgateApi = require('../shell');
19
20
  const { getAuthProxyUrl, tryToGetRefreshedLicense } = require('../utility/authProxy');
20
21
  const { getPublicHardwareFingerprint } = require('../utility/hardwareFingerprint');
21
22
  const { extractErrorMessage } = require('dbgate-tools');
@@ -438,4 +439,20 @@ module.exports = {
438
439
 
439
440
  return true;
440
441
  },
442
+
443
+ createConnectionsAndSettingsZip_meta: true,
444
+ async createConnectionsAndSettingsZip({ db, filePath }, req) {
445
+ const loadedPermissions = await loadPermissionsFromRequest(req);
446
+ if (!hasPermission(`admin/config`, loadedPermissions)) {
447
+ throw new Error('Permission denied: admin/config');
448
+ }
449
+
450
+ if (connections.portalConnections) {
451
+ throw new Error('Not allowed');
452
+ }
453
+
454
+ const exportDb = process.env.STORAGE_DATABASE ? await storage.fillTeamFileContentForExport(db) : db;
455
+ await dbgateApi.zipJsonLinesData(exportDb, filePath);
456
+ return true;
457
+ },
441
458
  };
@@ -157,8 +157,15 @@ module.exports = {
157
157
 
158
158
  handle_copyStreamError(conid, database, { copyStreamError }) {
159
159
  const { progressName } = copyStreamError;
160
- const { runid } = progressName;
161
- logger.error(`DBGM-00103 Error in database connection ${conid}, database ${database}: ${copyStreamError}`);
160
+ const runid = progressName?.runid;
161
+ logger.error({ conid, database, copyStreamError }, 'DBGM-00000 Error in database connection copy stream');
162
+ if (!runid) return;
163
+ if (copyStreamError.dbgateCopyStreamErrorReported) return;
164
+ socket.emit(`runner-progress-${runid}`, {
165
+ progressName: progressName?.name,
166
+ status: 'error',
167
+ errorMessage: copyStreamError.message,
168
+ });
162
169
  socket.emit(`runner-done-${runid}`);
163
170
  },
164
171
 
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '7.2.0',
4
- buildTime: '2026-06-08T11:53:36.651Z'
3
+ version: '7.2.1',
4
+ buildTime: '2026-06-19T10:21:38.466Z'
5
5
  };
@@ -623,13 +623,50 @@ async function handleEvalJsonScript({ script, runid }) {
623
623
  const directory = path.join(rundir(), runid);
624
624
  fs.mkdirSync(directory);
625
625
  const originalCwd = process.cwd();
626
+ let scriptError = null;
627
+ let finalizerError = null;
626
628
 
627
629
  try {
628
630
  process.chdir(directory);
629
631
 
630
- const evalWriter = new ScriptWriterEval(dbgateApi, requirePlugin, dbhan, runid);
631
- await playJsonScriptWriter(script, evalWriter);
632
- process.send({ msgtype: 'runnerDone', runid });
632
+ try {
633
+ const evalWriter = new ScriptWriterEval(dbgateApi, requirePlugin, dbhan, runid);
634
+ await playJsonScriptWriter(script, evalWriter);
635
+ } catch (err) {
636
+ scriptError = err;
637
+ } finally {
638
+ try {
639
+ await dbgateApi.finalizer.run();
640
+ } catch (err) {
641
+ finalizerError = err;
642
+ }
643
+ }
644
+
645
+ const shouldReportScriptError = scriptError && !scriptError.dbgateCopyStreamErrorReported;
646
+
647
+ if (shouldReportScriptError || finalizerError) {
648
+ if (shouldReportScriptError) {
649
+ logger.error(extractErrorLogData(scriptError), 'DBGM-00000 Error running JSON script on database connection');
650
+ }
651
+ if (finalizerError) {
652
+ logger.error(extractErrorLogData(finalizerError), 'DBGM-00000 Error running JSON script finalizers');
653
+ }
654
+
655
+ process.send({
656
+ msgtype: 'copyStreamError',
657
+ copyStreamError: {
658
+ message: [
659
+ shouldReportScriptError && extractErrorMessage(scriptError),
660
+ finalizerError && `Finalizer failed: ${extractErrorMessage(finalizerError)}`,
661
+ ]
662
+ .filter(Boolean)
663
+ .join('\n'),
664
+ progressName: { name: 'script', runid },
665
+ },
666
+ });
667
+ } else {
668
+ process.send({ msgtype: 'runnerDone', runid });
669
+ }
633
670
  } finally {
634
671
  process.chdir(originalCwd);
635
672
  }
@@ -290,14 +290,31 @@ async function handleExecuteReader({ jslid, sql, fileName }) {
290
290
 
291
291
  const reader = await driver.readQuery(dbhan, sql);
292
292
 
293
+ let isFinished = false;
294
+ const finishReader = () => {
295
+ if (isFinished) return;
296
+ isFinished = true;
297
+ writer.close().then(() => {
298
+ process.send({ msgtype: 'done' });
299
+ });
300
+ };
301
+
293
302
  reader.on('data', data => {
303
+ if (isFinished) return;
294
304
  writer.rowFromReader(data);
295
305
  });
296
- reader.on('end', () => {
297
- writer.close(() => {
298
- process.send({ msgtype: 'done' });
306
+ reader.on('error', err => {
307
+ process.send({
308
+ msgtype: 'info',
309
+ info: {
310
+ message: extractErrorMessage(err),
311
+ severity: 'error',
312
+ time: new Date(),
313
+ },
299
314
  });
315
+ finishReader();
300
316
  });
317
+ reader.on('end', finishReader);
301
318
  }
302
319
 
303
320
  function handlePing() {
@@ -66,6 +66,7 @@ async function copyStream(input, output, options) {
66
66
  }
67
67
  } catch (err) {
68
68
  logger.error(extractErrorLogData(err, { progressName }), 'DBGM-00157 Import/export job failed');
69
+ err.dbgateCopyStreamErrorReported = true;
69
70
 
70
71
  process.send({
71
72
  msgtype: 'copyStreamError',
@@ -84,7 +85,7 @@ async function copyStream(input, output, options) {
84
85
  errorMessage: extractErrorMessage(err),
85
86
  });
86
87
  }
87
- // throw err;
88
+ throw err;
88
89
  }
89
90
  }
90
91
 
@@ -4,6 +4,7 @@ const databaseConnections = require('../controllers/databaseConnections');
4
4
  const serverConnections = require('../controllers/serverConnections');
5
5
  const sessions = require('../controllers/sessions');
6
6
  const runners = require('../controllers/runners');
7
+ const { getLoggedUserCount } = require('./loginchecker');
7
8
 
8
9
  async function getHealthStatus() {
9
10
  const memory = process.memoryUsage();
@@ -13,7 +14,8 @@ async function getHealthStatus() {
13
14
  status: 'ok',
14
15
  databaseConnectionCount: databaseConnections.opened.length,
15
16
  serverConnectionCount: serverConnections.opened.length,
16
- sessionCount: sessions.opened.length,
17
+ querySessionCount: sessions.opened.length,
18
+ loggedUserCount: getLoggedUserCount(),
17
19
  runProcessCount: runners.opened.length,
18
20
  memory,
19
21
  cpuUsage,
@@ -10,9 +10,14 @@ function markLoginAsLoggedOut(licenseUid) {}
10
10
 
11
11
  const LOGIN_LIMIT_ERROR = '';
12
12
 
13
+ function getLoggedUserCount() {
14
+ return 0;
15
+ }
16
+
13
17
  module.exports = {
14
18
  markUserAsActive,
15
19
  isLoginLicensed,
16
20
  markLoginAsLoggedOut,
21
+ getLoggedUserCount,
17
22
  LOGIN_LIMIT_ERROR,
18
23
  };
@@ -20,6 +20,268 @@ function getColumnMetadata(columnMetadata, columnName) {
20
20
  return matchingKeys.length == 1 ? columnMetadata[matchingKeys[0]] : null;
21
21
  }
22
22
 
23
+ function namesEqual(left, right) {
24
+ return left != null && right != null && left.toLowerCase() == right.toLowerCase();
25
+ }
26
+
27
+ function findTable(dbinfo, schemaName, pureName) {
28
+ if (!dbinfo?.tables?.length || !pureName) return null;
29
+ const schemaMatches = table => !schemaName || namesEqual(table.schemaName, schemaName);
30
+ const exactTables = dbinfo.tables.filter(table => schemaMatches(table) && table.pureName == pureName);
31
+ if (exactTables.length == 1) return exactTables[0];
32
+ const matchingTables = dbinfo.tables.filter(table => schemaMatches(table) && namesEqual(table.pureName, pureName));
33
+ if (matchingTables.length == 1) return matchingTables[0];
34
+ const uniqueNameTables = dbinfo.tables.filter(table => namesEqual(table.pureName, pureName));
35
+ return uniqueNameTables.length == 1 ? uniqueNameTables[0] : null;
36
+ }
37
+
38
+ function findView(dbinfo, schemaName, pureName) {
39
+ const views = [...(dbinfo?.views || []), ...(dbinfo?.matviews || [])];
40
+ if (!views.length || !pureName) return null;
41
+ if (schemaName) {
42
+ const exactView = views.find(view => view.schemaName == schemaName && view.pureName == pureName);
43
+ if (exactView) return exactView;
44
+ const schemaViews = views.filter(view => namesEqual(view.schemaName, schemaName) && namesEqual(view.pureName, pureName));
45
+ if (schemaViews.length == 1) return schemaViews[0];
46
+ const schemaLessViews = views.filter(view => !view.schemaName && namesEqual(view.pureName, pureName));
47
+ return schemaLessViews.length == 1 ? schemaLessViews[0] : null;
48
+ }
49
+ const matchingViews = views.filter(view => namesEqual(view.pureName, pureName));
50
+ return matchingViews.length == 1 ? matchingViews[0] : null;
51
+ }
52
+
53
+ function extractSelectFromCreateView(createSql) {
54
+ if (!createSql) return null;
55
+ const match = createSql.match(/\bas\s+(select\b[\s\S]*)/i);
56
+ return match ? match[1] : createSql;
57
+ }
58
+
59
+ function unquoteIdentifier(identifier) {
60
+ const trimmed = identifier?.trim();
61
+ if (!trimmed) return null;
62
+ if (
63
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
64
+ (trimmed.startsWith('`') && trimmed.endsWith('`')) ||
65
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))
66
+ ) {
67
+ return trimmed.slice(1, -1).replace(/""/g, '"').replace(/``/g, '`').replace(/]]/g, ']');
68
+ }
69
+ return trimmed;
70
+ }
71
+
72
+ function extractCreateViewColumnNames(createSql) {
73
+ if (!createSql) return null;
74
+ const asMatch = createSql.match(/\bas\b/i);
75
+ if (!asMatch) return null;
76
+ const prefix = createSql.substring(0, asMatch.index);
77
+ const match = prefix.match(/\(([^()]*)\)\s*$/);
78
+ if (!match) return null;
79
+ const columns = match[1]
80
+ .split(',')
81
+ .map(column => unquoteIdentifier(column))
82
+ .filter(Boolean);
83
+ return columns.length > 0 ? columns : null;
84
+ }
85
+
86
+ function getViewColumnNames(view) {
87
+ const analysedColumns = view?.columns?.map(column => column.columnName).filter(Boolean);
88
+ if (analysedColumns?.length) return analysedColumns;
89
+ return extractCreateViewColumnNames(view?.createSql);
90
+ }
91
+
92
+ function isSelectStarFromSingleTable(selectSql) {
93
+ return /^\s*select\s+\*\s+from\b/i.test(selectSql || '');
94
+ }
95
+
96
+ function getSelectListItemCount(selectSql) {
97
+ const selectMatch = selectSql?.match(/^\s*select\s+([\s\S]+?)\s+from\s/i);
98
+ return selectMatch ? splitTopLevelCommaList(selectMatch[1]).length : null;
99
+ }
100
+
101
+ const identifierPattern = '`(?:``|[^`])+`|"(?:""|[^"])+"|\\[[^\\]]+\\]|[A-Za-z_@$#][A-Za-z0-9_@$#]*';
102
+ const qualifiedIdentifierPattern = `(?:${identifierPattern})(?:\\s*\\.\\s*(?:${identifierPattern}))*`;
103
+ const qualifiedIdentifierOnlyRegex = new RegExp(`^\\s*${qualifiedIdentifierPattern}\\s*$`, 'i');
104
+ const aliasStopWordPattern =
105
+ 'on|where|join|inner|left|right|full|outer|cross|straight_join|group|order|having|limit|union';
106
+
107
+ function splitTopLevelCommaList(text) {
108
+ const result = [];
109
+ let current = '';
110
+ let quote = null;
111
+ let depth = 0;
112
+
113
+ for (let index = 0; index < text.length; index++) {
114
+ const ch = text[index];
115
+ const next = text[index + 1];
116
+
117
+ if (quote) {
118
+ current += ch;
119
+ if (ch == quote) {
120
+ if ((quote == '"' || quote == '`') && next == quote) {
121
+ current += next;
122
+ index++;
123
+ } else {
124
+ quote = null;
125
+ }
126
+ }
127
+ continue;
128
+ }
129
+
130
+ if (ch == '"' || ch == '`' || ch == '[') {
131
+ quote = ch == '[' ? ']' : ch;
132
+ current += ch;
133
+ continue;
134
+ }
135
+ if (ch == '(') depth++;
136
+ if (ch == ')' && depth > 0) depth--;
137
+ if (ch == ',' && depth == 0) {
138
+ result.push(current.trim());
139
+ current = '';
140
+ continue;
141
+ }
142
+ current += ch;
143
+ }
144
+
145
+ if (current.trim()) result.push(current.trim());
146
+ return result;
147
+ }
148
+
149
+ function extractIdentifierParts(text) {
150
+ const identifierRegex = new RegExp(identifierPattern, 'g');
151
+ return (text.match(identifierRegex) || []).map(identifier => unquoteIdentifier(identifier));
152
+ }
153
+
154
+ function extractMySqlStyleViewColumnMetadata(selectSql, dbinfo) {
155
+ if (!selectSql) return null;
156
+ const selectMatch = selectSql.match(/^\s*select\s+([\s\S]+?)\s+from\s/i);
157
+ if (!selectMatch) return null;
158
+
159
+ const tableByAlias = {};
160
+ const tableRegex = new RegExp(
161
+ `\\b(?:from|join)\\s+\\(*\\s*(${qualifiedIdentifierPattern})(?:\\s+(?:as\\s+)?(?!\\b(?:${aliasStopWordPattern})\\b)(${identifierPattern}))?`,
162
+ 'gi'
163
+ );
164
+ let tableMatch;
165
+ while ((tableMatch = tableRegex.exec(selectSql))) {
166
+ const tableParts = extractIdentifierParts(tableMatch[1]);
167
+ const alias = unquoteIdentifier(tableMatch[2]);
168
+ if (!tableParts.length) continue;
169
+ const metadata = {
170
+ schemaName: tableParts.length >= 2 ? tableParts[tableParts.length - 2] : undefined,
171
+ tableName: tableParts[tableParts.length - 1],
172
+ };
173
+ if (alias) tableByAlias[alias.toLowerCase()] = metadata;
174
+ tableByAlias[metadata.tableName.toLowerCase()] = metadata;
175
+ }
176
+ if (Object.keys(tableByAlias).length == 0) return null;
177
+
178
+ const result = {};
179
+ for (const item of splitTopLevelCommaList(selectMatch[1])) {
180
+ const [sourceText, aliasText] = item.split(/\s+\bas\b\s+/i);
181
+ if (!qualifiedIdentifierOnlyRegex.test(sourceText)) continue;
182
+ const sourceParts = extractIdentifierParts(sourceText);
183
+ if (sourceParts.length < 2) continue;
184
+ const sourceColumnName = sourceParts[sourceParts.length - 1];
185
+ const qualifier = sourceParts[sourceParts.length - 2];
186
+ const table = tableByAlias[qualifier.toLowerCase()];
187
+ if (!table) continue;
188
+ const aliasParts = aliasText ? extractIdentifierParts(aliasText) : [];
189
+ const resultColumnName = aliasParts[0] || sourceColumnName;
190
+ result[resultColumnName] = resolveMetadataTable(
191
+ {
192
+ ...table,
193
+ sourceColumnName,
194
+ },
195
+ dbinfo
196
+ );
197
+ }
198
+
199
+ return Object.keys(result).length == 0 ? null : result;
200
+ }
201
+
202
+ function resolveMetadataTable(metadata, dbinfo) {
203
+ if (!metadata?.tableName) return metadata;
204
+ const table = findTable(dbinfo, metadata.schemaName, metadata.tableName);
205
+ if (!table) return metadata;
206
+ return {
207
+ ...metadata,
208
+ tableName: table.pureName,
209
+ schemaName: table.schemaName,
210
+ };
211
+ }
212
+
213
+ function createViewColumnMetadata(view, dbinfo) {
214
+ const selectSql = extractSelectFromCreateView(view?.createSql);
215
+ if (!selectSql) return null;
216
+ const viewColumnNames = getViewColumnNames(view);
217
+ const columnMetadata = extractColumnMetadataFromSql(selectSql) || extractMySqlStyleViewColumnMetadata(selectSql, dbinfo);
218
+
219
+ if (columnMetadata && Object.keys(columnMetadata).length > 0) {
220
+ if (!viewColumnNames?.length) {
221
+ return Object.fromEntries(
222
+ Object.entries(columnMetadata).map(([columnName, metadata]) => [
223
+ columnName,
224
+ resolveMetadataTable(metadata, dbinfo),
225
+ ])
226
+ );
227
+ }
228
+
229
+ const metadataValues = Object.values(columnMetadata).map(metadata => resolveMetadataTable(metadata, dbinfo));
230
+ const allowPositionalFallback =
231
+ metadataValues.length == viewColumnNames.length && metadataValues.length == getSelectListItemCount(selectSql);
232
+ const result = {};
233
+ for (let index = 0; index < viewColumnNames.length; index++) {
234
+ const columnName = viewColumnNames[index];
235
+ const metadata = getColumnMetadata(columnMetadata, columnName);
236
+ const resolvedMetadata = metadata
237
+ ? resolveMetadataTable(metadata, dbinfo)
238
+ : allowPositionalFallback
239
+ ? metadataValues[index]
240
+ : null;
241
+ if (resolvedMetadata) result[columnName] = resolvedMetadata;
242
+ }
243
+ return result;
244
+ }
245
+
246
+ if (!viewColumnNames?.length || !isSelectStarFromSingleTable(selectSql)) return columnMetadata;
247
+ const sourceTable = extractSingleTableFromSql(selectSql);
248
+ const table = findTable(dbinfo, sourceTable?.schemaName, sourceTable?.tableName);
249
+ if (!table?.columns?.length) return columnMetadata;
250
+
251
+ const result = {};
252
+ for (let index = 0; index < viewColumnNames.length; index++) {
253
+ const sourceColumn = table.columns[index];
254
+ if (!sourceColumn?.columnName) continue;
255
+ result[viewColumnNames[index]] = {
256
+ tableName: table.pureName,
257
+ schemaName: table.schemaName,
258
+ sourceColumnName: sourceColumn.columnName,
259
+ };
260
+ }
261
+ return result;
262
+ }
263
+
264
+ function resolveViewResultColumns(columns, dbinfo) {
265
+ if (!columns?.length) return columns;
266
+ if (!dbinfo?.views?.length && !dbinfo?.matviews?.length) return columns;
267
+
268
+ return columns.map(column => {
269
+ const view = findView(dbinfo, column.tableSchema, column.tableName);
270
+ if (!view?.createSql) return column;
271
+
272
+ const columnMetadata = createViewColumnMetadata(view, dbinfo);
273
+ const metadata = getColumnMetadata(columnMetadata, column.columnName);
274
+ if (!metadata) return column;
275
+
276
+ return {
277
+ ...column,
278
+ tableName: metadata.tableName,
279
+ tableSchema: metadata.schemaName,
280
+ sourceColumnName: metadata.sourceColumnName,
281
+ };
282
+ });
283
+ }
284
+
23
285
  async function enrichQueryResultColumns({ columns, sql, driver, dbhan, dbinfo, onNativeMetadataError, onFallbackMetadataError }) {
24
286
  if (!columns?.length || !driver?.databaseEngineTypes?.includes('sql') || !driver?.supportsEditableQueryResults) {
25
287
  return columns;
@@ -27,7 +289,7 @@ async function enrichQueryResultColumns({ columns, sql, driver, dbhan, dbinfo, o
27
289
 
28
290
  if (driver.enrichColumnMetadata) {
29
291
  try {
30
- const enriched = await driver.enrichColumnMetadata(dbhan, sql, columns, dbinfo);
292
+ const enriched = resolveViewResultColumns(await driver.enrichColumnMetadata(dbhan, sql, columns, dbinfo), dbinfo);
31
293
  if (enriched?.every(column => column.tableName && column.sourceColumnName)) return enriched;
32
294
  if (enriched?.length == columns.length) columns = enriched;
33
295
  } catch (err) {
@@ -38,7 +300,7 @@ async function enrichQueryResultColumns({ columns, sql, driver, dbhan, dbinfo, o
38
300
  try {
39
301
  const columnMetadata = extractColumnMetadataFromSql(sql);
40
302
  if (columnMetadata) {
41
- return columns.map(column => {
303
+ return resolveViewResultColumns(columns.map(column => {
42
304
  const metadata = getColumnMetadata(columnMetadata, column.columnName);
43
305
  return {
44
306
  ...column,
@@ -46,19 +308,19 @@ async function enrichQueryResultColumns({ columns, sql, driver, dbhan, dbinfo, o
46
308
  tableSchema: column.tableSchema || metadata?.schemaName,
47
309
  sourceColumnName: column.sourceColumnName || metadata?.sourceColumnName,
48
310
  };
49
- });
311
+ }), dbinfo);
50
312
  }
51
313
 
52
314
  const table = extractSingleTableFromSql(sql);
53
315
  if (!table) return columns;
54
316
  const columnSources = extractColumnSourcesFromSql(sql);
55
317
 
56
- return columns.map(column => ({
318
+ return resolveViewResultColumns(columns.map(column => ({
57
319
  ...column,
58
320
  tableName: column.tableName || table.tableName,
59
321
  tableSchema: column.tableSchema || table.schemaName,
60
322
  sourceColumnName: column.sourceColumnName || getColumnSourceName(columnSources, column.columnName) || column.columnName,
61
- }));
323
+ })), dbinfo);
62
324
  } catch (err) {
63
325
  onFallbackMetadataError?.(err);
64
326
  return columns;
@@ -67,4 +329,5 @@ async function enrichQueryResultColumns({ columns, sql, driver, dbhan, dbinfo, o
67
329
 
68
330
  module.exports = {
69
331
  enrichQueryResultColumns,
332
+ resolveViewResultColumns,
70
333
  };