mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-02 00:54:00 +00:00
SYNC: Limit query result rows #1098
This commit is contained in:
committed by
Diflow
parent
c3e09ddab0
commit
fb036935e6
@@ -141,7 +141,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
executeQuery_meta: true,
|
executeQuery_meta: true,
|
||||||
async executeQuery({ sesid, sql, autoCommit }) {
|
async executeQuery({ sesid, sql, autoCommit, limitRows }) {
|
||||||
const session = this.opened.find(x => x.sesid == sesid);
|
const session = this.opened.find(x => x.sesid == sesid);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
throw new Error('Invalid session');
|
throw new Error('Invalid session');
|
||||||
@@ -149,7 +149,7 @@ module.exports = {
|
|||||||
|
|
||||||
logger.info({ sesid, sql }, 'Processing query');
|
logger.info({ sesid, sql }, 'Processing query');
|
||||||
this.dispatchMessage(sesid, 'Query execution started');
|
this.dispatchMessage(sesid, 'Query execution started');
|
||||||
session.subprocess.send({ msgtype: 'executeQuery', sql, autoCommit });
|
session.subprocess.send({ msgtype: 'executeQuery', sql, autoCommit, limitRows });
|
||||||
|
|
||||||
return { state: 'ok' };
|
return { state: 'ok' };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ async function handleExecuteControlCommand({ command }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleExecuteQuery({ sql, autoCommit }) {
|
async function handleExecuteQuery({ sql, autoCommit, limitRows }) {
|
||||||
lastActivity = new Date().getTime();
|
lastActivity = new Date().getTime();
|
||||||
|
|
||||||
await waitConnected();
|
await waitConnected();
|
||||||
@@ -146,7 +146,7 @@ async function handleExecuteQuery({ sql, autoCommit }) {
|
|||||||
...driver.getQuerySplitterOptions('stream'),
|
...driver.getQuerySplitterOptions('stream'),
|
||||||
returnRichInfo: true,
|
returnRichInfo: true,
|
||||||
})) {
|
})) {
|
||||||
await handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem);
|
await handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem, undefined, limitRows);
|
||||||
// const handler = new StreamHandler(resultIndex);
|
// const handler = new StreamHandler(resultIndex);
|
||||||
// const stream = await driver.stream(systemConnection, sqlItem, handler);
|
// const stream = await driver.stream(systemConnection, sqlItem, handler);
|
||||||
// handler.stream = stream;
|
// handler.stream = stream;
|
||||||
|
|||||||
@@ -82,20 +82,27 @@ class QueryStreamTableWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close(afterClose) {
|
close(afterClose) {
|
||||||
|
return new Promise(resolve => {
|
||||||
if (this.currentStream) {
|
if (this.currentStream) {
|
||||||
this.currentStream.end(() => {
|
this.currentStream.end(() => {
|
||||||
this.writeCurrentStats(true, true);
|
this.writeCurrentStats(true, true);
|
||||||
if (afterClose) afterClose();
|
if (afterClose) afterClose();
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StreamHandler {
|
class StreamHandler {
|
||||||
constructor(queryStreamInfoHolder, resolve, startLine, sesid = undefined) {
|
constructor(queryStreamInfoHolder, resolve, startLine, sesid = undefined, limitRows = undefined) {
|
||||||
this.recordset = this.recordset.bind(this);
|
this.recordset = this.recordset.bind(this);
|
||||||
this.startLine = startLine;
|
this.startLine = startLine;
|
||||||
this.sesid = sesid;
|
this.sesid = sesid;
|
||||||
|
this.limitRows = limitRows;
|
||||||
|
this.rowsLimitOverflow = false;
|
||||||
this.row = this.row.bind(this);
|
this.row = this.row.bind(this);
|
||||||
// this.error = this.error.bind(this);
|
// this.error = this.error.bind(this);
|
||||||
this.done = this.done.bind(this);
|
this.done = this.done.bind(this);
|
||||||
@@ -107,6 +114,7 @@ class StreamHandler {
|
|||||||
this.plannedStats = false;
|
this.plannedStats = false;
|
||||||
this.queryStreamInfoHolder = queryStreamInfoHolder;
|
this.queryStreamInfoHolder = queryStreamInfoHolder;
|
||||||
this.resolve = resolve;
|
this.resolve = resolve;
|
||||||
|
this.rowCounter = 0;
|
||||||
// currentHandlers = [...currentHandlers, this];
|
// currentHandlers = [...currentHandlers, this];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +126,9 @@ class StreamHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recordset(columns) {
|
recordset(columns) {
|
||||||
|
if (this.rowsLimitOverflow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.closeCurrentWriter();
|
this.closeCurrentWriter();
|
||||||
this.currentWriter = new QueryStreamTableWriter(this.sesid);
|
this.currentWriter = new QueryStreamTableWriter(this.sesid);
|
||||||
this.currentWriter.initializeFromQuery(
|
this.currentWriter.initializeFromQuery(
|
||||||
@@ -125,6 +136,7 @@ class StreamHandler {
|
|||||||
this.queryStreamInfoHolder.resultIndex
|
this.queryStreamInfoHolder.resultIndex
|
||||||
);
|
);
|
||||||
this.queryStreamInfoHolder.resultIndex += 1;
|
this.queryStreamInfoHolder.resultIndex += 1;
|
||||||
|
this.rowCounter = 0;
|
||||||
|
|
||||||
// this.writeCurrentStats();
|
// this.writeCurrentStats();
|
||||||
|
|
||||||
@@ -135,8 +147,36 @@ class StreamHandler {
|
|||||||
// }, 500);
|
// }, 500);
|
||||||
}
|
}
|
||||||
row(row) {
|
row(row) {
|
||||||
if (this.currentWriter) this.currentWriter.row(row);
|
if (this.rowsLimitOverflow) {
|
||||||
else if (row.message) process.send({ msgtype: 'info', info: { message: row.message }, sesid: this.sesid });
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.limitRows && this.rowCounter >= this.limitRows) {
|
||||||
|
process.send({
|
||||||
|
msgtype: 'info',
|
||||||
|
info: { message: `Rows limit overflow, loaded ${this.rowCounter} rows, canceling query`, severity: 'error' },
|
||||||
|
sesid: this.sesid,
|
||||||
|
});
|
||||||
|
this.rowsLimitOverflow = true;
|
||||||
|
|
||||||
|
this.queryStreamInfoHolder.canceled = true;
|
||||||
|
if (this.currentWriter) {
|
||||||
|
this.currentWriter.close().then(() => {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentWriter) {
|
||||||
|
this.currentWriter.row(row);
|
||||||
|
this.rowCounter += 1;
|
||||||
|
} else if (row.message) {
|
||||||
|
process.send({ msgtype: 'info', info: { message: row.message }, sesid: this.sesid });
|
||||||
|
}
|
||||||
// this.onRow(this.jslid);
|
// this.onRow(this.jslid);
|
||||||
}
|
}
|
||||||
// error(error) {
|
// error(error) {
|
||||||
@@ -161,10 +201,10 @@ class StreamHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem, sesid = undefined) {
|
function handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem, sesid = undefined, limitRows = undefined) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const start = sqlItem.trimStart || sqlItem.start;
|
const start = sqlItem.trimStart || sqlItem.start;
|
||||||
const handler = new StreamHandler(queryStreamInfoHolder, resolve, start && start.line, sesid);
|
const handler = new StreamHandler(queryStreamInfoHolder, resolve, start && start.line, sesid, limitRows);
|
||||||
driver.stream(dbhan, sqlItem.text, handler);
|
driver.stream(dbhan, sqlItem.text, handler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,6 +222,7 @@
|
|||||||
|
|
||||||
'icon premium': 'mdi mdi-star',
|
'icon premium': 'mdi mdi-star',
|
||||||
'icon upload': 'mdi mdi-upload',
|
'icon upload': 'mdi mdi-upload',
|
||||||
|
'icon limit': 'mdi mdi-car-speed-limiter',
|
||||||
|
|
||||||
'img ok': 'mdi mdi-check-circle color-icon-green',
|
'img ok': 'mdi mdi-check-circle color-icon-green',
|
||||||
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',
|
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',
|
||||||
|
|||||||
41
packages/web/src/modals/RowsLimitModal.svelte
Normal file
41
packages/web/src/modals/RowsLimitModal.svelte
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||||
|
|
||||||
|
import FormProvider from '../forms/FormProvider.svelte';
|
||||||
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
|
import FormTextField from '../forms/FormTextField.svelte';
|
||||||
|
import ModalBase from './ModalBase.svelte';
|
||||||
|
import { closeCurrentModal } from './modalTools';
|
||||||
|
|
||||||
|
export let value;
|
||||||
|
export let onConfirm;
|
||||||
|
|
||||||
|
const handleSubmit = async value => {
|
||||||
|
closeCurrentModal();
|
||||||
|
onConfirm(value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormProvider initialValues={{ value }}>
|
||||||
|
<ModalBase {...$$restProps}>
|
||||||
|
<svelte:fragment slot="header">Rows limit</svelte:fragment>
|
||||||
|
|
||||||
|
<FormTextField
|
||||||
|
label="Return only N rows from query"
|
||||||
|
name="value"
|
||||||
|
focused
|
||||||
|
data-testid="RowsLimitModal_value"
|
||||||
|
placeholder="(No rows limit)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<svelte:fragment slot="footer">
|
||||||
|
<FormSubmit
|
||||||
|
value="OK"
|
||||||
|
on:click={e => handleSubmit(parseInt(e.detail.value) || null)}
|
||||||
|
data-testid="RowsLimitModal_setLimit"
|
||||||
|
/>
|
||||||
|
<FormStyledButton value="Set no limit" on:click={e => handleSubmit(null)} data-testid="RowsLimitModal_setNoLimit" />
|
||||||
|
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} data-testid="RowsLimitModal_cancel" />
|
||||||
|
</svelte:fragment>
|
||||||
|
</ModalBase>
|
||||||
|
</FormProvider>
|
||||||
@@ -227,6 +227,12 @@ ORDER BY
|
|||||||
</FormFieldTemplateLarge>
|
</FormFieldTemplateLarge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<FormTextField
|
||||||
|
name="sqlEditor.limitRows"
|
||||||
|
label="Return only N rows from query"
|
||||||
|
placeholder="(No rows limit)"
|
||||||
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="2">
|
<svelte:fragment slot="2">
|
||||||
<div class="heading">Connection</div>
|
<div class="heading">Connection</div>
|
||||||
|
|||||||
@@ -144,6 +144,9 @@
|
|||||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||||
import QueryAiAssistant from '../query/QueryAiAssistant.svelte';
|
import QueryAiAssistant from '../query/QueryAiAssistant.svelte';
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
|
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||||
|
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||||
|
import RowsLimitModal from '../modals/RowsLimitModal.svelte';
|
||||||
|
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
@@ -197,6 +200,21 @@
|
|||||||
let isInTransaction = false;
|
let isInTransaction = false;
|
||||||
let isAutocommit = false;
|
let isAutocommit = false;
|
||||||
|
|
||||||
|
const queryRowsLimitLocalStorageKey = `tabdata_limitRows_${tabid}`;
|
||||||
|
function getInitialRowsLimit() {
|
||||||
|
const storageValue = localStorage.getItem(queryRowsLimitLocalStorageKey);
|
||||||
|
if (storageValue == 'nolimit') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (storageValue) {
|
||||||
|
return parseInt(storageValue) ?? null;
|
||||||
|
}
|
||||||
|
return getIntSettingsValue('sqlEditor.limitRows', null, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let queryRowsLimit = getInitialRowsLimit();
|
||||||
|
$: localStorage.setItem(queryRowsLimitLocalStorageKey, queryRowsLimit ? queryRowsLimit.toString() : 'nolimit');
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
intervalId = setInterval(() => {
|
intervalId = setInterval(() => {
|
||||||
if (!driver?.singleConnectionOnly && sessionId) {
|
if (!driver?.singleConnectionOnly && sessionId) {
|
||||||
@@ -362,6 +380,7 @@
|
|||||||
sesid,
|
sesid,
|
||||||
sql,
|
sql,
|
||||||
autoCommit: driver?.implicitTransactions && isAutocommit,
|
autoCommit: driver?.implicitTransactions && isAutocommit,
|
||||||
|
limitRows: queryRowsLimit ? queryRowsLimit : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await apiCall('query-history/write', {
|
await apiCall('query-history/write', {
|
||||||
@@ -713,6 +732,20 @@
|
|||||||
<ToolStripCommandButton command="query.kill" data-testid="QueryTab_killButton" />
|
<ToolStripCommandButton command="query.kill" data-testid="QueryTab_killButton" />
|
||||||
<ToolStripSaveButton idPrefix="query" />
|
<ToolStripSaveButton idPrefix="query" />
|
||||||
<ToolStripCommandButton command="query.formatCode" />
|
<ToolStripCommandButton command="query.formatCode" />
|
||||||
|
{#if !driver?.singleConnectionOnly}
|
||||||
|
<ToolStripButton
|
||||||
|
icon="icon limit"
|
||||||
|
on:click={() =>
|
||||||
|
showModal(RowsLimitModal, {
|
||||||
|
value: queryRowsLimit,
|
||||||
|
onConfirm: value => {
|
||||||
|
queryRowsLimit = value;
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{queryRowsLimit ? `Limit ${queryRowsLimit} rows` : 'Unlimited rows'}</ToolStripButton
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
{#if resultCount == 1}
|
{#if resultCount == 1}
|
||||||
<ToolStripExportButton command="jslTableGrid.export" {quickExportHandlerRef} label="Export result" />
|
<ToolStripExportButton command="jslTableGrid.export" {quickExportHandlerRef} label="Export result" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user