feat(MySQL): ability to cancel queries

This commit is contained in:
Fabio Di Stasio 2021-12-19 11:59:09 +01:00
parent e7a1858091
commit a59f77f618
10 changed files with 149 additions and 19 deletions

View File

@ -1,6 +1,7 @@
{
"extends": [
"stylelint-config-standard"
"stylelint-config-standard",
"stylelint-config-standard-scss"
],
"fix": true,
"formatter": "verbose",

View File

@ -11,6 +11,7 @@ module.exports = {
sslConnection: false,
sshConnection: false,
fileConnection: false,
cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,

View File

@ -12,6 +12,7 @@ module.exports = {
engines: true,
sslConnection: true,
sshConnection: true,
cancelQueries: true,
// Tools
processesList: true,
// Structure

View File

@ -135,7 +135,7 @@ export default connections => {
}
});
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid }) => {
if (!query) return;
try {
@ -143,6 +143,7 @@ export default connections => {
nest: true,
details: true,
schema,
tabUid,
comments: false
});
@ -152,4 +153,16 @@ export default connections => {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
if (!tabUid) return;
try {
await connections[uid].killTabQuery(tabUid);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@ -9,6 +9,7 @@ export class MySQLClient extends AntaresCore {
super(args);
this._schema = null;
this._runningConnections = new Map();
this.types = {
0: 'DECIMAL',
@ -1210,10 +1211,26 @@ export class MySQLClient extends AntaresCore {
});
}
/**
*
* @param {number} id
* @returns {Promise<null>}
*/
async killProcess (id) {
return await this.raw(`KILL ${id}`);
}
/**
*
* @param {string} tabUid
* @returns {Promise<null>}
*/
async killTabQuery (tabUid) {
const id = this._runningConnections.get(tabUid);
if (id)
return await this.killProcess(id);
}
/**
* CREATE TABLE
*
@ -1535,6 +1552,9 @@ export class MySQLClient extends AntaresCore {
const isPool = typeof this._connection.getConnection === 'function';
const connection = isPool ? await this._connection.getConnection() : this._connection;
if (args.tabUid && isPool)
this._runningConnections.set(args.tabUid, connection.connection.connectionId);
if (args.schema)
await connection.query(`USE \`${args.schema}\``);
@ -1595,7 +1615,10 @@ export class MySQLClient extends AntaresCore {
});
}
catch (err) {
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
@ -1604,7 +1627,10 @@ export class MySQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response;
}
catch (err) {
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
}
@ -1619,7 +1645,10 @@ export class MySQLClient extends AntaresCore {
keys: keysArr
});
}).catch((err) => {
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
});
});
@ -1627,7 +1656,10 @@ export class MySQLClient extends AntaresCore {
resultsArr.push({ rows, report, fields, keys, duration });
}
if (isPool) connection.release();
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
}

View File

@ -4,6 +4,7 @@
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
tabindex="0"
@keydown.116="runQuery(query)"
@keydown.75="killTabQuery"
@keydown.ctrl.alt.87="clear"
@keydown.ctrl.66="beautify"
@keydown.ctrl.71="openHistoryModal"
@ -22,16 +23,29 @@
<div ref="resizer" class="query-area-resizer" />
<div class="workspace-query-runner-footer">
<div class="workspace-query-buttons">
<button
class="btn btn-primary btn-sm"
:class="{'loading':isQuering}"
:disabled="!query"
title="F5"
@click="runQuery(query)"
>
<i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span>
</button>
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)">
<button
v-if="showCancel && isQuering"
class="btn btn-primary btn-sm cancellable"
:disabled="!query"
:title="$t('word.cancel')"
@click="killTabQuery()"
>
<i class="mdi mdi-24px mdi-window-close" />
<span class="d-invisible pr-1">{{ $t('word.run') }}</span>
</button>
<button
v-else
class="btn btn-primary btn-sm"
:class="{'loading':isQuering}"
:disabled="!query"
title="F5"
@click="runQuery(query)"
>
<i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span>
</button>
</div>
<button
class="btn btn-link btn-sm mr-0"
:disabled="!query || isQuering"
@ -110,7 +124,7 @@
</div>
</div>
</div>
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" />
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" :customizations="workspace.customizations" />
<div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" />
<WorkspaceTabQueryTable
@ -166,6 +180,8 @@ export default {
query: '',
lastQuery: '',
isQuering: false,
isCancelling: false,
showCancel: false,
results: [],
selectedSchema: null,
resultsCount: 0,
@ -248,6 +264,7 @@ export default {
const params = {
uid: this.connection.uid,
schema: this.selectedSchema,
tabUid: this.tab.uid,
query
};
@ -283,6 +300,29 @@ export default {
this.isQuering = false;
this.lastQuery = query;
},
async killTabQuery () {
if (this.isCancelling) return;
this.isCancelling = true;
try {
const params = {
uid: this.connection.uid,
tabUid: this.tab.uid
};
await Schema.killTabQuery(params);
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isCancelling = false;
},
setCancelButtonVisibility (val) {
if (this.workspace.customizations.cancelQueries)
this.showCancel = val;
},
reloadTable () {
this.runQuery(this.lastQuery);
},

View File

@ -5,6 +5,9 @@
<div class="mb-4">
{{ $t('message.runQuery') }}
</div>
<div v-if="customizations.cancelQueries" class="mb-4">
{{ $t('message.killQuery') }}
</div>
<div class="mb-4">
{{ $t('word.format') }}
</div>
@ -25,6 +28,9 @@
<div class="mb-4">
<code>F5</code>
</div>
<div v-if="customizations.cancelQueries" class="mb-4">
<code>CTRL</code> + <code>K</code>
</div>
<div class="mb-4">
<code>CTRL</code> + <code>B</code>
</div>
@ -47,7 +53,10 @@
<script>
export default {
name: 'WorkspaceTabQueryEmptyState'
name: 'WorkspaceTabQueryEmptyState',
props: {
customizations: Object
}
};
</script>

View File

@ -251,7 +251,8 @@ module.exports = {
killProcess: 'Kill process',
closeTab: 'Close tab',
goToDownloadPage: 'Go to download page',
readOnlyMode: 'Read-only mode'
readOnlyMode: 'Read-only mode',
killQuery: 'Kill query'
},
faker: {
address: 'Address',

View File

@ -46,6 +46,10 @@ export default class {
return ipcRenderer.invoke('kill-process', params);
}
static killTabQuery (params) {
return ipcRenderer.invoke('kill-tab-query', params);
}
static useSchema (params) {
return ipcRenderer.invoke('use-schema', params);
}

View File

@ -59,6 +59,34 @@ option:checked {
text-overflow: ellipsis;
}
.cancellable {
color: transparent !important;
min-height: 0.8rem;
position: relative;
> .mdi,
> .span {
visibility: hidden;
}
&::after {
content: "\2715";
color: $light-color;
font-weight: 700;
top: 36%;
display: block;
height: 0.8rem;
left: 50%;
margin-left: -0.4rem;
margin-top: -0.4rem;
opacity: 1;
padding: 0;
position: absolute;
width: 0.8rem;
z-index: 1;
}
}
.workspace-tabs {
align-content: baseline;