Show executed query on log #1236

This commit is contained in:
SPRINX0\prochazka
2025-11-05 12:11:19 +01:00
parent e5c94d9698
commit ef7f050bc5
9 changed files with 169 additions and 52 deletions

View File

@@ -29,7 +29,17 @@ const generateDeploySql = require('../shell/generateDeploySql');
const { createTwoFilesPatch } = require('diff'); const { createTwoFilesPatch } = require('diff');
const diff2htmlPage = require('../utility/diff2htmlPage'); const diff2htmlPage = require('../utility/diff2htmlPage');
const processArgs = require('../utility/processArgs'); const processArgs = require('../utility/processArgs');
const { testConnectionPermission, hasPermission, loadPermissionsFromRequest, loadTablePermissionsFromRequest, getTablePermissionRole, loadDatabasePermissionsFromRequest, getDatabasePermissionRole, getTablePermissionRoleLevelIndex, testDatabaseRolePermission } = require('../utility/hasPermission'); const {
testConnectionPermission,
hasPermission,
loadPermissionsFromRequest,
loadTablePermissionsFromRequest,
getTablePermissionRole,
loadDatabasePermissionsFromRequest,
getDatabasePermissionRole,
getTablePermissionRoleLevelIndex,
testDatabaseRolePermission,
} = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions'); const { MissingCredentialsError } = require('../utility/exceptions');
const pipeForkLogs = require('../utility/pipeForkLogs'); const pipeForkLogs = require('../utility/pipeForkLogs');
const crypto = require('crypto'); const crypto = require('crypto');
@@ -100,7 +110,7 @@ module.exports = {
socket.emitChanged(`database-status-changed`, { conid, database }); socket.emitChanged(`database-status-changed`, { conid, database });
}, },
handle_ping() { }, handle_ping() {},
// session event handlers // session event handlers
@@ -256,23 +266,24 @@ module.exports = {
auditLogger: auditLogger:
auditLogSessionGroup && select?.from?.name?.pureName auditLogSessionGroup && select?.from?.name?.pureName
? response => { ? response => {
sendToAuditLog(req, { sendToAuditLog(req, {
category: 'dbop', category: 'dbop',
component: 'DatabaseConnectionsController', component: 'DatabaseConnectionsController',
event: 'sql.select', event: 'sql.select',
action: 'select', action: 'select',
severity: 'info', severity: 'info',
conid, conid,
database, database,
schemaName: select?.from?.name?.schemaName, schemaName: select?.from?.name?.schemaName,
pureName: select?.from?.name?.pureName, pureName: select?.from?.name?.pureName,
sumint1: response?.rows?.length, sumint1: response?.rows?.length,
sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${select?.from?.name?.pureName sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${
select?.from?.name?.pureName
}`, }`,
sessionGroup: auditLogSessionGroup, sessionGroup: auditLogSessionGroup,
message: `Loaded table data from ${select?.from?.name?.pureName}`, message: `Loaded table data from ${select?.from?.name?.pureName}`,
}); });
} }
: null, : null,
} }
); );
@@ -335,21 +346,21 @@ module.exports = {
auditLogger: auditLogger:
auditLogSessionGroup && options?.pureName auditLogSessionGroup && options?.pureName
? response => { ? response => {
sendToAuditLog(req, { sendToAuditLog(req, {
category: 'dbop', category: 'dbop',
component: 'DatabaseConnectionsController', component: 'DatabaseConnectionsController',
event: 'nosql.collectionData', event: 'nosql.collectionData',
action: 'select', action: 'select',
severity: 'info', severity: 'info',
conid, conid,
database, database,
pureName: options?.pureName, pureName: options?.pureName,
sumint1: response?.result?.rows?.length, sumint1: response?.result?.rows?.length,
sessionParam: `${conid}::${database}::${options?.pureName}`, sessionParam: `${conid}::${database}::${options?.pureName}`,
sessionGroup: auditLogSessionGroup, sessionGroup: auditLogSessionGroup,
message: `Loaded collection data ${options?.pureName}`, message: `Loaded collection data ${options?.pureName}`,
}); });
} }
: null, : null,
} }
); );
@@ -455,10 +466,18 @@ module.exports = {
[changeSet.inserts, 'create_update_delete'], [changeSet.inserts, 'create_update_delete'],
[changeSet.deletes, 'create_update_delete'], [changeSet.deletes, 'create_update_delete'],
[changeSet.updates, 'update_only'], [changeSet.updates, 'update_only'],
] ];
for (const [operations, requiredRole] of fieldsAndRoles) { for (const [operations, requiredRole] of fieldsAndRoles) {
for (const operation of operations) { for (const operation of operations) {
const role = getTablePermissionRole(conid, database, 'tables', operation.schemaName, operation.pureName, tablePermissions, databasePermissions); const role = getTablePermissionRole(
conid,
database,
'tables',
operation.schemaName,
operation.pureName,
tablePermissions,
databasePermissions
);
if (getTablePermissionRoleLevelIndex(role) < getTablePermissionRoleLevelIndex(requiredRole)) { if (getTablePermissionRoleLevelIndex(role) < getTablePermissionRoleLevelIndex(requiredRole)) {
throw new Error('DBGM-00262 Permission not granted'); throw new Error('DBGM-00262 Permission not granted');
} }
@@ -628,7 +647,15 @@ module.exports = {
function applyTablePermissionRole(list, objectTypeField) { function applyTablePermissionRole(list, objectTypeField) {
const res = []; const res = [];
for (const item of list ?? []) { for (const item of list ?? []) {
const tablePermissionRole = getTablePermissionRole(conid, database, objectTypeField, item.schemaName, item.pureName, tablePermissions, databasePermissionRole); const tablePermissionRole = getTablePermissionRole(
conid,
database,
objectTypeField,
item.schemaName,
item.pureName,
tablePermissions,
databasePermissionRole
);
if (tablePermissionRole != 'deny') { if (tablePermissionRole != 'deny') {
res.push({ res.push({
...item, ...item,
@@ -647,7 +674,7 @@ module.exports = {
functions: applyTablePermissionRole(opened.structure.functions, 'functions'), functions: applyTablePermissionRole(opened.structure.functions, 'functions'),
triggers: applyTablePermissionRole(opened.structure.triggers, 'triggers'), triggers: applyTablePermissionRole(opened.structure.triggers, 'triggers'),
collections: applyTablePermissionRole(opened.structure.collections, 'collections'), collections: applyTablePermissionRole(opened.structure.collections, 'collections'),
} };
return res; return res;
} }
@@ -881,17 +908,17 @@ module.exports = {
return { return {
...(command == 'backup' ...(command == 'backup'
? driver.backupDatabaseCommand( ? driver.backupDatabaseCommand(
connection, connection,
{ outputFile, database, options, selectedTables, skippedTables, argsFormat }, { outputFile, database, options, selectedTables, skippedTables, argsFormat },
// @ts-ignore // @ts-ignore
externalTools externalTools
) )
: driver.restoreDatabaseCommand( : driver.restoreDatabaseCommand(
connection, connection,
{ inputFile, database, options, argsFormat }, { inputFile, database, options, argsFormat },
// @ts-ignore // @ts-ignore
externalTools externalTools
)), )),
transformMessage: driver.transformNativeCommandMessage transformMessage: driver.transformNativeCommandMessage
? message => driver.transformNativeCommandMessage(message, command) ? message => driver.transformNativeCommandMessage(message, command)
: null, : null,
@@ -990,7 +1017,10 @@ module.exports = {
async executeSessionQuery({ sesid, conid, database, sql }, req) { async executeSessionQuery({ sesid, conid, database, sql }, req) {
await testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
logger.info({ sesid, sql }, 'DBGM-00010 Processing query'); logger.info({ sesid, sql }, 'DBGM-00010 Processing query');
sessions.dispatchMessage(sesid, 'Query execution started'); sessions.dispatchMessage(sesid, {
message: 'Query execution started',
sql,
});
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
opened.subprocess.send({ msgtype: 'executeSessionQuery', sql, sesid }); opened.subprocess.send({ msgtype: 'executeSessionQuery', sql, sesid });

View File

@@ -188,7 +188,10 @@ module.exports = {
}); });
logger.info({ sesid, sql }, 'DBGM-00019 Processing query'); logger.info({ sesid, sql }, 'DBGM-00019 Processing query');
this.dispatchMessage(sesid, 'Query execution started'); this.dispatchMessage(sesid, {
message: 'Query execution started',
sql,
});
session.subprocess.send({ session.subprocess.send({
msgtype: 'executeQuery', msgtype: 'executeQuery',
sql, sql,

View File

@@ -5,6 +5,8 @@
import { onMount, afterUpdate } from 'svelte'; import { onMount, afterUpdate } from 'svelte';
export let code = ''; export let code = '';
export let inline = false;
export let onClick = null;
let domCode; let domCode;
@@ -29,7 +31,11 @@
The `sql` class hints the language; highlight.js will The `sql` class hints the language; highlight.js will
read it even though we register the grammar explicitly. read it even though we register the grammar explicitly.
--> -->
<pre bind:this={domCode} class="sql">{code}</pre> {#if inline}
<span bind:this={domCode} class="sql" class:clickable={!!onClick} on:click={onClick}>{code}</span>
{:else}
<pre bind:this={domCode} class="sql" class:clickable={!!onClick} on:click={onClick}>{code}</pre>
{/if}
{/key} {/key}
<style> <style>
@@ -38,4 +44,8 @@
padding: 0; padding: 0;
padding: 0.5em; padding: 0.5em;
} }
.clickable {
cursor: pointer;
}
</style> </style>

View File

@@ -0,0 +1,53 @@
<script>
import _ from 'lodash';
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import newQuery from '../query/newQuery';
import SqlEditor from '../query/SqlEditor.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
export let sql;
export let onConfirm;
export let engine = null;
</script>
<ModalBase {...$$restProps}>
<div slot="header">SQL Script</div>
<div class="editor">
<SqlEditor {engine} value={sql} readOnly />
</div>
<div slot="footer">
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} data-testid="ShowSqlModal_closeButton" />
<FormStyledButton
type="button"
value="Open script"
on:click={() => {
newQuery({
initialData: sql,
});
closeCurrentModal();
}}
data-testid="ShowSqlModal_openScriptButton"
/>
</div>
</ModalBase>
<style>
.editor {
position: relative;
height: 30vh;
width: 40vw;
}
.form-margin {
margin: var(--dim-large-form-margin);
}
.flex-wrap {
flex-wrap: wrap;
}
</style>

View File

@@ -75,7 +75,7 @@
--token-base: #303030; --token-base: #303030;
--token-text: #ddd; --token-text: #ddd;
--token-keyword: white; --token-keyword: #096dd9;
--token-selector-tag: white; --token-selector-tag: white;
--token-literal: white; --token-literal: white;
--token-section: white; --token-section: white;

View File

@@ -12,6 +12,7 @@
export let startLine = 0; export let startLine = 0;
export let onMessageClick = null; export let onMessageClick = null;
export let onExplainError = null; export let onExplainError = null;
export let engine = null;
export let filter = ''; export let filter = '';
@@ -92,6 +93,7 @@
previousRow={index > 0 ? items[index - 1] : null} previousRow={index > 0 ? items[index - 1] : null}
{onMessageClick} {onMessageClick}
{onExplainError} {onExplainError}
{engine}
/> />
{/each} {/each}
</table> </table>

View File

@@ -17,6 +17,9 @@
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import { plusExpandIcon } from '../icons/expandIcons'; import { plusExpandIcon } from '../icons/expandIcons';
import InlineButton from '../buttons/InlineButton.svelte'; import InlineButton from '../buttons/InlineButton.svelte';
import SqlHighlighter from '../elements/SqlHighlighter.svelte';
import { showModal } from '../modals/modalTools';
import ShowSqlModal from '../modals/ShowSqlModal.svelte';
export let row; export let row;
export let index; export let index;
@@ -29,6 +32,7 @@
export let previousRow = null; export let previousRow = null;
export let onMessageClick = null; export let onMessageClick = null;
export let onExplainError = null; export let onExplainError = null;
export let engine = null;
let isExpanded = false; let isExpanded = false;
</script> </script>
@@ -55,10 +59,22 @@
}}><FontIcon icon="img ai" /> Explain</InlineButton }}><FontIcon icon="img ai" /> Explain</InlineButton
> >
{/if} {/if}
{#if row.sql}
<SqlHighlighter
code={row.sql.substring(0, 100) + (row.sql.length > 100 ? '...' : '')}
inline
onClick={() => {
showModal(ShowSqlModal, {
sql: row.sql,
engine,
});
}}
/>
{/if}
</td> </td>
<td>{moment(row.time).format('HH:mm:ss')}</td> <td>{moment(row.time).format('HH:mm:ss')}</td>
<td>{formatDuration(new Date(row.time).getTime() - time0)}</td> <td>{formatDuration(new Date(row.time).getTime() - time0)}</td>
<td> {previousRow ? formatDuration(new Date(row.time).getTime() - new Date(previousRow.time).getTime()) : 'n/a'}</td> <td>{previousRow ? formatDuration(new Date(row.time).getTime() - new Date(previousRow.time).getTime()) : 'n/a'}</td>
{#if showProcedure} {#if showProcedure}
<td>{row.procedure || ''}</td> <td>{row.procedure || ''}</td>
{/if} {/if}

View File

@@ -18,6 +18,7 @@
export let onChangeErrors = null; export let onChangeErrors = null;
export let onMessageClick = null; export let onMessageClick = null;
export let onExplainError = null; export let onExplainError = null;
export let engine = null;
const cachedMessagesRef = createRef([]); const cachedMessagesRef = createRef([]);
const lastErrorMessageCountRef = createRef(0); const lastErrorMessageCountRef = createRef(0);
@@ -79,5 +80,6 @@
{showCaller} {showCaller}
{startLine} {startLine}
{onExplainError} {onExplainError}
{engine}
/> />
{/if} {/if}

View File

@@ -777,6 +777,7 @@
showLine showLine
onChangeErrors={handleChangeErrors} onChangeErrors={handleChangeErrors}
onExplainError={isProApp() ? handleExplainError : null} onExplainError={isProApp() ? handleExplainError : null}
engine={$connection && $connection.engine}
/> />
</svelte:fragment> </svelte:fragment>
</ResultTabs> </ResultTabs>