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 +6 -6
- package/src/controllers/config.js +17 -0
- package/src/controllers/databaseConnections.js +9 -2
- package/src/currentVersion.js +2 -2
- package/src/proc/databaseConnectionProcess.js +40 -3
- package/src/proc/sessionProcess.js +20 -3
- package/src/shell/copyStream.js +2 -1
- package/src/utility/healthStatus.js +3 -1
- package/src/utility/loginchecker.js +5 -0
- package/src/utility/queryResultMetadata.js +268 -5
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.
|
|
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.
|
|
33
|
+
"dbgate-datalib": "7.2.1",
|
|
34
34
|
"dbgate-query-splitter": "^4.12.0",
|
|
35
|
-
"dbgate-rest": "7.2.
|
|
36
|
-
"dbgate-sqltree": "7.2.
|
|
37
|
-
"dbgate-tools": "7.2.
|
|
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.
|
|
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
|
|
161
|
-
logger.error(
|
|
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
|
|
package/src/currentVersion.js
CHANGED
|
@@ -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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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('
|
|
297
|
-
|
|
298
|
-
|
|
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() {
|
package/src/shell/copyStream.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
};
|