feat(mysql): terminate sessions from the process list
This commit is contained in:
21 files changed
+312
-18
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { Activity, AlertTriangle, ArrowDown, ArrowUp, Ban, Copy, Loader2, RefreshCcw, Search } from "@lucide/vue";
|
import { Activity, AlertTriangle, ArrowDown, ArrowUp, Ban, Copy, Loader2, PlugZap, RefreshCcw, Search } from "@lucide/vue";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -54,14 +54,18 @@ let timer: ReturnType<typeof setInterval> | undefined;
|
|||||||
|
|
||||||
const cancelTarget = ref<ProcessRow | null>(null);
|
const cancelTarget = ref<ProcessRow | null>(null);
|
||||||
const canceling = ref(false);
|
const canceling = ref(false);
|
||||||
|
const terminateTarget = ref<ProcessRow | null>(null);
|
||||||
|
const terminating = ref(false);
|
||||||
const batchSupported = computed(() => driver.value?.supportsBatchCancel === true);
|
const batchSupported = computed(() => driver.value?.supportsBatchCancel === true);
|
||||||
|
// Engines without a terminate statement only expose query cancellation.
|
||||||
|
const terminateSupported = computed(() => typeof driver.value?.buildTerminateSessionSql === "function");
|
||||||
const selectedIds = ref(new Set<number>());
|
const selectedIds = ref(new Set<number>());
|
||||||
// Anchor row for Shift+click range selection, in the current display order.
|
// Anchor row for Shift+click range selection, in the current display order.
|
||||||
const selectionAnchorId = ref<number | null>(null);
|
const selectionAnchorId = ref<number | null>(null);
|
||||||
const batchTargets = ref<ProcessRow[] | null>(null);
|
const batchTargets = ref<ProcessRow[] | null>(null);
|
||||||
const batchResult = ref<{ succeeded: number; failures: { id: number; message: string }[] } | null>(null);
|
const batchResult = ref<{ succeeded: number; failures: { id: number; message: string }[] } | null>(null);
|
||||||
const refreshing = ref(false);
|
const refreshing = ref(false);
|
||||||
const actionsLocked = computed(() => canceling.value || cancelTarget.value !== null || batchTargets.value !== null);
|
const actionsLocked = computed(() => canceling.value || cancelTarget.value !== null || terminating.value || terminateTarget.value !== null || batchTargets.value !== null);
|
||||||
let connectionGeneration = 0;
|
let connectionGeneration = 0;
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
const fallbackListSql = ref<string | null>(null);
|
const fallbackListSql = ref<string | null>(null);
|
||||||
@@ -307,6 +311,62 @@ async function confirmCancel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestTerminate(row: ProcessRow) {
|
||||||
|
if (isOwnSession(row) || actionsLocked.value || refreshing.value || !terminateSupported.value) return;
|
||||||
|
terminateTarget.value = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the session itself. `KILL QUERY` / `pg_cancel_backend` leave an idle session
|
||||||
|
* connected, so an idle row could never be removed from the list before this action.
|
||||||
|
*/
|
||||||
|
async function confirmTerminate() {
|
||||||
|
const target = terminateTarget.value;
|
||||||
|
const activeDriver = driver.value;
|
||||||
|
const buildTerminateSql = activeDriver?.buildTerminateSessionSql;
|
||||||
|
if (!target || !activeDriver || !buildTerminateSql || terminating.value) return;
|
||||||
|
const connection = { ...props.connection };
|
||||||
|
const generation = connectionGeneration;
|
||||||
|
const isCurrent = () => !disposed && generation === connectionGeneration;
|
||||||
|
terminating.value = true;
|
||||||
|
try {
|
||||||
|
const terminateSql = buildTerminateSql.call(activeDriver, target.id);
|
||||||
|
let usedFallbackTerminateSql = false;
|
||||||
|
const executeTerminateSql = async (sql: string) => {
|
||||||
|
if (!isCurrent()) return undefined;
|
||||||
|
const results = await api.executeMulti(connection.id, "", sql, undefined, undefined, { maxRows: 1 });
|
||||||
|
const executionError = processListExecutionError(results);
|
||||||
|
if (executionError) throw new Error(executionError);
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
const result = await executeWithProductionSqlGuard({
|
||||||
|
connection,
|
||||||
|
database: "",
|
||||||
|
sql: terminateSql,
|
||||||
|
source: t("production.sourceAdmin"),
|
||||||
|
execute: async () => {
|
||||||
|
try {
|
||||||
|
return await executeTerminateSql(terminateSql);
|
||||||
|
} catch (error) {
|
||||||
|
if (!activeDriver.buildFallbackTerminateSessionSql || !activeDriver.shouldUseFallbackTerminateSessionSql?.(error)) throw error;
|
||||||
|
usedFallbackTerminateSql = true;
|
||||||
|
return executeTerminateSql(activeDriver.buildFallbackTerminateSessionSql(target.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (result === undefined || !isCurrent()) return;
|
||||||
|
const terminateResultError = usedFallbackTerminateSql ? activeDriver.fallbackTerminateSessionResultError?.(result) : activeDriver.terminateSessionResultError?.(result);
|
||||||
|
if (terminateResultError) throw new Error(terminateResultError);
|
||||||
|
toast(t("processList.terminateSuccess", { id: target.id }), 2500);
|
||||||
|
terminateTarget.value = null;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (isCurrent()) toast(t("processList.terminateFailed", { message: error?.message || String(error) }), 5000);
|
||||||
|
} finally {
|
||||||
|
terminating.value = false;
|
||||||
|
if (terminateTarget.value === null) void load({ silent: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function stopTimer() {
|
function stopTimer() {
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
@@ -433,7 +493,7 @@ onBeforeUnmount(() => {
|
|||||||
<ArrowDown v-else-if="sortKey === column.key && sortDir === 'desc'" class="h-3 w-3" />
|
<ArrowDown v-else-if="sortKey === column.key && sortDir === 'desc'" class="h-3 w-3" />
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="w-16 whitespace-nowrap border-b px-3 py-2 text-right font-medium">{{ t("processList.colActions") }}</th>
|
<th class="w-40 whitespace-nowrap border-b px-3 py-2 text-right font-medium">{{ t("processList.colActions") }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -463,17 +523,31 @@ onBeforeUnmount(() => {
|
|||||||
<template v-else>{{ row[column.key] === null || row[column.key] === undefined ? "—" : row[column.key] }}</template>
|
<template v-else>{{ row[column.key] === null || row[column.key] === undefined ? "—" : row[column.key] }}</template>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1.5 text-right">
|
<td class="px-3 py-1.5 text-right">
|
||||||
<Button
|
<span class="inline-flex items-center justify-end gap-1">
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
class="h-6 gap-1 px-1.5 text-[11px] text-destructive hover:bg-destructive/10 hover:text-destructive disabled:opacity-40"
|
size="sm"
|
||||||
:disabled="isOwnSession(row) || actionsLocked || refreshing"
|
class="h-6 gap-1 px-1.5 text-[11px] text-destructive hover:bg-destructive/10 hover:text-destructive disabled:opacity-40"
|
||||||
:title="isOwnSession(row) ? t('processList.cannotKillSelf') : t('processList.kill')"
|
:disabled="isOwnSession(row) || actionsLocked || refreshing"
|
||||||
@click="requestCancel(row)"
|
:title="isOwnSession(row) ? t('processList.cannotKillSelf') : t('processList.kill')"
|
||||||
>
|
@click="requestCancel(row)"
|
||||||
<Ban class="h-3.5 w-3.5" />
|
>
|
||||||
{{ t("processList.kill") }}
|
<Ban class="h-3.5 w-3.5" />
|
||||||
</Button>
|
{{ t("processList.kill") }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="terminateSupported"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 gap-1 px-1.5 text-[11px] text-destructive hover:bg-destructive/10 hover:text-destructive disabled:opacity-40"
|
||||||
|
:disabled="isOwnSession(row) || actionsLocked || refreshing"
|
||||||
|
:title="isOwnSession(row) ? t('processList.cannotTerminateSelf') : t('processList.terminate')"
|
||||||
|
@click="requestTerminate(row)"
|
||||||
|
>
|
||||||
|
<PlugZap class="h-3.5 w-3.5" />
|
||||||
|
{{ t("processList.terminate") }}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="!loading && filteredRows.length === 0">
|
<tr v-if="!loading && filteredRows.length === 0">
|
||||||
@@ -513,6 +587,34 @@ onBeforeUnmount(() => {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
:open="terminateTarget !== null"
|
||||||
|
@update:open="
|
||||||
|
(open) => {
|
||||||
|
if (!open && !terminating) terminateTarget = null;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<DialogContent class="max-w-sm" :show-close-button="!terminating">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle class="flex items-center gap-2">
|
||||||
|
<AlertTriangle class="h-4 w-4 text-destructive" />
|
||||||
|
{{ t("processList.terminateTitle") }}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<p v-if="terminateTarget" class="text-sm text-muted-foreground">
|
||||||
|
{{ t("processList.terminateConfirm", { id: terminateTarget.id, user: terminateTarget.user }) }}
|
||||||
|
</p>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" :disabled="terminating" @click="terminateTarget = null">{{ t("dangerDialog.cancel") }}</Button>
|
||||||
|
<Button variant="destructive" :disabled="terminating" @click="confirmTerminate">
|
||||||
|
<Loader2 v-if="terminating" class="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
||||||
|
{{ t("processList.terminate") }}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
:open="batchTargets !== null"
|
:open="batchTargets !== null"
|
||||||
@update:open="
|
@update:open="
|
||||||
|
|||||||
@@ -3850,6 +3850,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "{id} ({user}) sessiyasında icra olunan sorğu ləğv edilsin? Sessiyanın əlaqəsi saxlanacaq.",
|
killConfirm: "{id} ({user}) sessiyasında icra olunan sorğu ləğv edilsin? Sessiyanın əlaqəsi saxlanacaq.",
|
||||||
killSuccess: "{id} sessiyasında icra olunan sorğu ləğv edildi",
|
killSuccess: "{id} sessiyasında icra olunan sorğu ləğv edildi",
|
||||||
killFailed: "Sorğunu ləğv etmək mümkün olmadı: {message}",
|
killFailed: "Sorğunu ləğv etmək mümkün olmadı: {message}",
|
||||||
|
terminate: "Sessiyanı sonlandır",
|
||||||
|
cannotTerminateSelf: "Öz sessiyanızı sonlandıra bilməzsiniz",
|
||||||
|
terminateTitle: "Sessiyanı sonlandır",
|
||||||
|
terminateConfirm: "{id} ({user}) sessiyası sonlandırılsın? Əlaqə dərhal bağlanır və təsdiqlənməmiş tranzaksiyalar geri alınır.",
|
||||||
|
terminateSuccess: "{id} sessiyası sonlandırıldı",
|
||||||
|
terminateFailed: "Sessiyanı sonlandırmaq mümkün olmadı: {message}",
|
||||||
previewTitle: "İfadə",
|
previewTitle: "İfadə",
|
||||||
copy: "Kopyala",
|
copy: "Kopyala",
|
||||||
copied: "Kopyalandı",
|
copied: "Kopyalandı",
|
||||||
|
|||||||
@@ -4048,6 +4048,12 @@ export default {
|
|||||||
killConfirm: "Cancel the running query for session {id} ({user})? The session will remain connected.",
|
killConfirm: "Cancel the running query for session {id} ({user})? The session will remain connected.",
|
||||||
killSuccess: "Canceled the running query for session {id}",
|
killSuccess: "Canceled the running query for session {id}",
|
||||||
killFailed: "Failed to cancel query: {message}",
|
killFailed: "Failed to cancel query: {message}",
|
||||||
|
terminate: "Terminate session",
|
||||||
|
cannotTerminateSelf: "You cannot terminate your own session",
|
||||||
|
terminateTitle: "Terminate session",
|
||||||
|
terminateConfirm: "Terminate session {id} ({user})? The connection is closed immediately and any uncommitted transaction is rolled back.",
|
||||||
|
terminateSuccess: "Terminated session {id}",
|
||||||
|
terminateFailed: "Failed to terminate session: {message}",
|
||||||
previewTitle: "Statement",
|
previewTitle: "Statement",
|
||||||
copy: "Copy",
|
copy: "Copy",
|
||||||
copied: "Copied",
|
copied: "Copied",
|
||||||
|
|||||||
@@ -9538,6 +9538,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "¿Cancelar la consulta en ejecución de la sesión {id} ({user})? La sesión permanecerá conectada.",
|
killConfirm: "¿Cancelar la consulta en ejecución de la sesión {id} ({user})? La sesión permanecerá conectada.",
|
||||||
killSuccess: "Consulta en ejecución de la sesión {id} cancelada",
|
killSuccess: "Consulta en ejecución de la sesión {id} cancelada",
|
||||||
killFailed: "Error al cancelar la consulta: {message}",
|
killFailed: "Error al cancelar la consulta: {message}",
|
||||||
|
terminate: "Terminar sesión",
|
||||||
|
cannotTerminateSelf: "No se puede terminar la sesión actual",
|
||||||
|
terminateTitle: "Terminar sesión",
|
||||||
|
terminateConfirm: "¿Terminar la sesión {id} ({user})? La conexión se cierra de inmediato y cualquier transacción sin confirmar se revierte.",
|
||||||
|
terminateSuccess: "Sesión {id} terminada",
|
||||||
|
terminateFailed: "Error al terminar la sesión: {message}",
|
||||||
colPid: "PID",
|
colPid: "PID",
|
||||||
colClient: "Cliente",
|
colClient: "Cliente",
|
||||||
colApp: "Aplicación",
|
colApp: "Aplicación",
|
||||||
|
|||||||
@@ -9538,6 +9538,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "Annullare la query in esecuzione della sessione {id} ({user})? La sessione resterà connessa.",
|
killConfirm: "Annullare la query in esecuzione della sessione {id} ({user})? La sessione resterà connessa.",
|
||||||
killSuccess: "Query in esecuzione della sessione {id} annullata",
|
killSuccess: "Query in esecuzione della sessione {id} annullata",
|
||||||
killFailed: "Impossibile annullare la query: {message}",
|
killFailed: "Impossibile annullare la query: {message}",
|
||||||
|
terminate: "Termina sessione",
|
||||||
|
cannotTerminateSelf: "Impossibile terminare la sessione corrente",
|
||||||
|
terminateTitle: "Termina sessione",
|
||||||
|
terminateConfirm: "Terminare la sessione {id} ({user})? La connessione viene chiusa immediatamente e le transazioni non confermate vengono annullate.",
|
||||||
|
terminateSuccess: "Sessione {id} terminata",
|
||||||
|
terminateFailed: "Impossibile terminare la sessione: {message}",
|
||||||
colPid: "PID",
|
colPid: "PID",
|
||||||
colClient: "Client",
|
colClient: "Client",
|
||||||
colApp: "Applicazione",
|
colApp: "Applicazione",
|
||||||
|
|||||||
@@ -9588,6 +9588,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "セッション {id}({user})で実行中のクエリをキャンセルしますか?セッションの接続は維持されます。",
|
killConfirm: "セッション {id}({user})で実行中のクエリをキャンセルしますか?セッションの接続は維持されます。",
|
||||||
killSuccess: "セッション {id} で実行中のクエリをキャンセルしました。",
|
killSuccess: "セッション {id} で実行中のクエリをキャンセルしました。",
|
||||||
killFailed: "クエリのキャンセルに失敗しました:{message}",
|
killFailed: "クエリのキャンセルに失敗しました:{message}",
|
||||||
|
terminate: "セッションを終了",
|
||||||
|
cannotTerminateSelf: "現在のセッションは終了できません",
|
||||||
|
terminateTitle: "セッションを終了",
|
||||||
|
terminateConfirm: "セッション {id}({user})を終了しますか?接続は直ちに切断され、未コミットのトランザクションはロールバックされます。",
|
||||||
|
terminateSuccess: "セッション {id} を終了しました。",
|
||||||
|
terminateFailed: "セッションの終了に失敗しました:{message}",
|
||||||
colPid: "PID",
|
colPid: "PID",
|
||||||
colClient: "クライアント",
|
colClient: "クライアント",
|
||||||
colApp: "アプリケーション",
|
colApp: "アプリケーション",
|
||||||
|
|||||||
@@ -3812,6 +3812,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "세션 {id} ({user})에서 실행 중인 쿼리를 취소하시겠습니까? 세션 연결은 유지됩니다.",
|
killConfirm: "세션 {id} ({user})에서 실행 중인 쿼리를 취소하시겠습니까? 세션 연결은 유지됩니다.",
|
||||||
killSuccess: "세션 {id}에서 실행 중인 쿼리를 취소했습니다",
|
killSuccess: "세션 {id}에서 실행 중인 쿼리를 취소했습니다",
|
||||||
killFailed: "쿼리 취소에 실패했습니다: {message}",
|
killFailed: "쿼리 취소에 실패했습니다: {message}",
|
||||||
|
terminate: "세션 종료",
|
||||||
|
cannotTerminateSelf: "현재 세션은 종료할 수 없습니다",
|
||||||
|
terminateTitle: "세션 종료",
|
||||||
|
terminateConfirm: "세션 {id} ({user})을(를) 종료하시겠습니까? 연결이 즉시 끊기고 커밋되지 않은 트랜잭션은 롤백됩니다.",
|
||||||
|
terminateSuccess: "세션 {id}을(를) 종료했습니다",
|
||||||
|
terminateFailed: "세션 종료에 실패했습니다: {message}",
|
||||||
previewTitle: "구문",
|
previewTitle: "구문",
|
||||||
copy: "복사",
|
copy: "복사",
|
||||||
copied: "복사했습니다",
|
copied: "복사했습니다",
|
||||||
|
|||||||
@@ -9539,6 +9539,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "Cancelar a consulta em execução da sessão {id} ({user})? A sessão permanecerá conectada.",
|
killConfirm: "Cancelar a consulta em execução da sessão {id} ({user})? A sessão permanecerá conectada.",
|
||||||
killSuccess: "Consulta em execução da sessão {id} cancelada.",
|
killSuccess: "Consulta em execução da sessão {id} cancelada.",
|
||||||
killFailed: "Falha ao cancelar a consulta: {message}",
|
killFailed: "Falha ao cancelar a consulta: {message}",
|
||||||
|
terminate: "Encerrar sessão",
|
||||||
|
cannotTerminateSelf: "Não é possível encerrar a sessão atual",
|
||||||
|
terminateTitle: "Encerrar sessão",
|
||||||
|
terminateConfirm: "Encerrar a sessão {id} ({user})? A conexão é fechada imediatamente e qualquer transação não confirmada é revertida.",
|
||||||
|
terminateSuccess: "Sessão {id} encerrada",
|
||||||
|
terminateFailed: "Falha ao encerrar a sessão: {message}",
|
||||||
colPid: "PID",
|
colPid: "PID",
|
||||||
colClient: "Cliente",
|
colClient: "Cliente",
|
||||||
colApp: "Aplicativo",
|
colApp: "Aplicativo",
|
||||||
|
|||||||
@@ -3822,6 +3822,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "{id} ({user}) oturumundaki çalışan sorgu iptal edilsin mi? Oturum bağlı kalmaya devam eder.",
|
killConfirm: "{id} ({user}) oturumundaki çalışan sorgu iptal edilsin mi? Oturum bağlı kalmaya devam eder.",
|
||||||
killSuccess: "{id} oturumundaki çalışan sorgu iptal edildi",
|
killSuccess: "{id} oturumundaki çalışan sorgu iptal edildi",
|
||||||
killFailed: "Sorgu iptal edilemedi: {message}",
|
killFailed: "Sorgu iptal edilemedi: {message}",
|
||||||
|
terminate: "Oturumu sonlandır",
|
||||||
|
cannotTerminateSelf: "Kendi oturumunuzu sonlandıramazsınız",
|
||||||
|
terminateTitle: "Oturumu sonlandır",
|
||||||
|
terminateConfirm: "{id} ({user}) oturumu sonlandırılsın mı? Bağlantı hemen kapatılır ve işlenmemiş işlemler geri alınır.",
|
||||||
|
terminateSuccess: "{id} oturumu sonlandırıldı",
|
||||||
|
terminateFailed: "Oturum sonlandırılamadı: {message}",
|
||||||
previewTitle: "İfade",
|
previewTitle: "İfade",
|
||||||
copy: "Kopyala",
|
copy: "Kopyala",
|
||||||
copied: "Kopyalandı",
|
copied: "Kopyalandı",
|
||||||
|
|||||||
@@ -3958,6 +3958,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "确定取消会话 {id}({user})中正在运行的查询?该会话将保持连接。",
|
killConfirm: "确定取消会话 {id}({user})中正在运行的查询?该会话将保持连接。",
|
||||||
killSuccess: "已取消会话 {id} 中正在运行的查询",
|
killSuccess: "已取消会话 {id} 中正在运行的查询",
|
||||||
killFailed: "取消查询失败:{message}",
|
killFailed: "取消查询失败:{message}",
|
||||||
|
terminate: "终止会话",
|
||||||
|
cannotTerminateSelf: "无法终止当前会话",
|
||||||
|
terminateTitle: "终止会话",
|
||||||
|
terminateConfirm: "确定终止会话 {id}({user})?该连接会立即断开,未提交的事务将回滚。",
|
||||||
|
terminateSuccess: "已终止会话 {id}",
|
||||||
|
terminateFailed: "终止会话失败:{message}",
|
||||||
previewTitle: "语句",
|
previewTitle: "语句",
|
||||||
copy: "复制",
|
copy: "复制",
|
||||||
copied: "已复制",
|
copied: "已复制",
|
||||||
|
|||||||
@@ -9537,6 +9537,12 @@ export default withEnglishFallback({
|
|||||||
killConfirm: "確定取消會話 {id}({user})中執行中的查詢?該會話將保持連線。",
|
killConfirm: "確定取消會話 {id}({user})中執行中的查詢?該會話將保持連線。",
|
||||||
killSuccess: "已取消會話 {id} 中執行中的查詢",
|
killSuccess: "已取消會話 {id} 中執行中的查詢",
|
||||||
killFailed: "取消查詢失敗:{message}",
|
killFailed: "取消查詢失敗:{message}",
|
||||||
|
terminate: "終止會話",
|
||||||
|
cannotTerminateSelf: "無法終止目前會話",
|
||||||
|
terminateTitle: "終止會話",
|
||||||
|
terminateConfirm: "確定終止會話 {id}({user})?該連線會立即中斷,未提交的交易將回滾。",
|
||||||
|
terminateSuccess: "已終止會話 {id}",
|
||||||
|
terminateFailed: "終止會話失敗:{message}",
|
||||||
colPid: "PID",
|
colPid: "PID",
|
||||||
colClient: "用戶端",
|
colClient: "用戶端",
|
||||||
colApp: "應用程式",
|
colApp: "應用程式",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { QueryResult } from "@/types/database";
|
import type { QueryResult } from "@/types/database";
|
||||||
import { buildCancelQuerySql, clampInterval, createProcessListLoadCoordinator, mapProcessRows, processListExecutionError, processListSessionCount, supportsProcessList } from "@/lib/database/mysqlProcessList";
|
import { buildCancelQuerySql, buildTerminateSessionSql, clampInterval, createProcessListLoadCoordinator, mapProcessRows, processListExecutionError, processListSessionCount, supportsProcessList } from "@/lib/database/mysqlProcessList";
|
||||||
|
|
||||||
function result(columns: string[], rows: (string | number | boolean | null)[][]): QueryResult {
|
function result(columns: string[], rows: (string | number | boolean | null)[][]): QueryResult {
|
||||||
return { columns, rows, affected_rows: 0, execution_time_ms: 0 };
|
return { columns, rows, affected_rows: 0, execution_time_ms: 0 };
|
||||||
@@ -48,6 +48,19 @@ describe("buildCancelQuerySql", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("buildTerminateSessionSql", () => {
|
||||||
|
it("builds KILL for a valid id so an idle session is really disconnected", () => {
|
||||||
|
expect(buildTerminateSessionSql(8213)).toBe("KILL 8213");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects non-integer or nonpositive ids", () => {
|
||||||
|
expect(() => buildTerminateSessionSql(1.5)).toThrow();
|
||||||
|
expect(() => buildTerminateSessionSql(0)).toThrow();
|
||||||
|
expect(() => buildTerminateSessionSql(-1)).toThrow();
|
||||||
|
expect(() => buildTerminateSessionSql(Number.NaN)).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("clampInterval", () => {
|
describe("clampInterval", () => {
|
||||||
it("clamps below the minimum to 1 second", () => {
|
it("clamps below the minimum to 1 second", () => {
|
||||||
expect(clampInterval(0)).toBe(1);
|
expect(clampInterval(0)).toBe(1);
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ import type { QueryResult } from "@/types/database";
|
|||||||
import {
|
import {
|
||||||
buildKingbaseCancelQuerySql,
|
buildKingbaseCancelQuerySql,
|
||||||
buildKingbasePgCancelQuerySql,
|
buildKingbasePgCancelQuerySql,
|
||||||
|
buildKingbasePgTerminateSessionSql,
|
||||||
|
buildKingbaseTerminateSessionSql,
|
||||||
buildPgCancelQuerySql,
|
buildPgCancelQuerySql,
|
||||||
|
buildPgTerminateSessionSql,
|
||||||
isKingbaseOwnSessionCatalogCompatibilityError,
|
isKingbaseOwnSessionCatalogCompatibilityError,
|
||||||
isKingbaseProcessListCatalogCompatibilityError,
|
isKingbaseProcessListCatalogCompatibilityError,
|
||||||
isKingbaseCancelCatalogCompatibilityError,
|
isKingbaseCancelCatalogCompatibilityError,
|
||||||
|
isKingbaseTerminateCatalogCompatibilityError,
|
||||||
isPgProcessListCompatibilityError,
|
isPgProcessListCompatibilityError,
|
||||||
KINGBASE_OWN_SESSION_SQL,
|
KINGBASE_OWN_SESSION_SQL,
|
||||||
KINGBASE_PG_OWN_SESSION_SQL,
|
KINGBASE_PG_OWN_SESSION_SQL,
|
||||||
@@ -18,6 +22,7 @@ import {
|
|||||||
kingbaseCancelQueryResultError,
|
kingbaseCancelQueryResultError,
|
||||||
kingbasePgCancelQueryResultError,
|
kingbasePgCancelQueryResultError,
|
||||||
pgCancelQueryResultError,
|
pgCancelQueryResultError,
|
||||||
|
pgTerminateSessionResultError,
|
||||||
PG_PROCESS_LIST_LEGACY_SQL,
|
PG_PROCESS_LIST_LEGACY_SQL,
|
||||||
PG_PROCESS_LIST_SQL,
|
PG_PROCESS_LIST_SQL,
|
||||||
} from "@/lib/database/postgresProcessList";
|
} from "@/lib/database/postgresProcessList";
|
||||||
@@ -120,6 +125,32 @@ describe("PostgreSQL-family process SQL", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("session termination statements", () => {
|
||||||
|
it("builds pg_terminate_backend so an idle backend is really disconnected", () => {
|
||||||
|
expect(buildPgTerminateSessionSql(4211)).toBe("SELECT pg_terminate_backend(4211)");
|
||||||
|
expect(buildKingbaseTerminateSessionSql(4211)).toBe("SELECT sys_terminate_backend(4211)");
|
||||||
|
expect(buildKingbasePgTerminateSessionSql(4211)).toBe("SELECT pg_terminate_backend(4211)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects non-integer or nonpositive pids", () => {
|
||||||
|
expect(() => buildPgTerminateSessionSql(1.5)).toThrow();
|
||||||
|
expect(() => buildPgTerminateSessionSql(0)).toThrow();
|
||||||
|
expect(() => buildPgTerminateSessionSql(Number.NaN)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires the server to confirm that a backend was terminated", () => {
|
||||||
|
expect(pgTerminateSessionResultError([result(["pg_terminate_backend"], [[true]])])).toBeNull();
|
||||||
|
expect(pgTerminateSessionResultError([result(["pg_terminate_backend"], [["t"]])])).toBeNull();
|
||||||
|
expect(pgTerminateSessionResultError([result(["pg_terminate_backend"], [[false]])])).toContain("did not terminate");
|
||||||
|
expect(pgTerminateSessionResultError([result(["pg_terminate_backend"], [])])).toContain("did not terminate");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects the Kingbase fallback trigger for a missing sys_terminate_backend", () => {
|
||||||
|
expect(isKingbaseTerminateCatalogCompatibilityError(new Error("function sys_terminate_backend(integer) does not exist"))).toBe(true);
|
||||||
|
expect(isKingbaseTerminateCatalogCompatibilityError(new Error("permission denied"))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Postgres compatibility", () => {
|
describe("Postgres compatibility", () => {
|
||||||
it("provides a pre-9.6 query and only falls back for missing wait-event columns", () => {
|
it("provides a pre-9.6 query and only falls back for missing wait-event columns", () => {
|
||||||
expect(PG_PROCESS_LIST_SQL).toContain("wait_event_type");
|
expect(PG_PROCESS_LIST_SQL).toContain("wait_event_type");
|
||||||
@@ -145,7 +176,10 @@ describe("resolveProcessListDriver", () => {
|
|||||||
const mysql = resolveProcessListDriver("mysql");
|
const mysql = resolveProcessListDriver("mysql");
|
||||||
const postgres = resolveProcessListDriver("postgres");
|
const postgres = resolveProcessListDriver("postgres");
|
||||||
expect(mysql?.buildCancelQuerySql(7)).toBe("KILL QUERY 7");
|
expect(mysql?.buildCancelQuerySql(7)).toBe("KILL QUERY 7");
|
||||||
|
expect(mysql?.buildTerminateSessionSql?.(7)).toBe("KILL 7");
|
||||||
expect(postgres?.buildCancelQuerySql(7)).toBe("SELECT pg_cancel_backend(7)");
|
expect(postgres?.buildCancelQuerySql(7)).toBe("SELECT pg_cancel_backend(7)");
|
||||||
|
expect(postgres?.buildTerminateSessionSql?.(7)).toBe("SELECT pg_terminate_backend(7)");
|
||||||
|
expect(postgres?.terminateSessionResultError?.([result(["pg_terminate_backend"], [[false]])])).toContain("did not terminate");
|
||||||
expect(postgres?.fallbackListSql).toBe(PG_PROCESS_LIST_LEGACY_SQL);
|
expect(postgres?.fallbackListSql).toBe(PG_PROCESS_LIST_LEGACY_SQL);
|
||||||
expect(postgres?.shouldUseFallbackListSql?.(new Error('column "wait_event" does not exist'))).toBe(true);
|
expect(postgres?.shouldUseFallbackListSql?.(new Error('column "wait_event" does not exist'))).toBe(true);
|
||||||
expect(postgres?.cancelQueryResultError?.([result(["pg_cancel_backend"], [[false]])])).toContain("did not cancel");
|
expect(postgres?.cancelQueryResultError?.([result(["pg_cancel_backend"], [[false]])])).toContain("did not cancel");
|
||||||
@@ -177,6 +211,8 @@ describe("connectionSupportsProcessList", () => {
|
|||||||
expect(connectionSupportsProcessList(conn({ db_type: "gaussdb", driver_profile: "opengauss" }))).toBe(true);
|
expect(connectionSupportsProcessList(conn({ db_type: "gaussdb", driver_profile: "opengauss" }))).toBe(true);
|
||||||
expect(connectionSupportsProcessList(conn({ db_type: "gaussdb", driver_profile: "gaussdb" }))).toBe(false);
|
expect(connectionSupportsProcessList(conn({ db_type: "gaussdb", driver_profile: "gaussdb" }))).toBe(false);
|
||||||
expect(connectionSupportsProcessList(conn({ db_type: "kingbase" }))).toBe(true);
|
expect(connectionSupportsProcessList(conn({ db_type: "kingbase" }))).toBe(true);
|
||||||
|
expect(resolveProcessListDriver("kingbase")?.buildTerminateSessionSql?.(7)).toBe("SELECT sys_terminate_backend(7)");
|
||||||
|
expect(resolveProcessListDriver("opengauss")?.buildTerminateSessionSql?.(7)).toBe("SELECT pg_terminate_backend(7)");
|
||||||
expect(connectionSupportsProcessList(conn({ db_type: "sqlite" }))).toBe(false);
|
expect(connectionSupportsProcessList(conn({ db_type: "sqlite" }))).toBe(false);
|
||||||
expect(connectionSupportsProcessList(undefined)).toBe(false);
|
expect(connectionSupportsProcessList(undefined)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -137,6 +137,21 @@ export function buildCancelQuerySql(id: number): string {
|
|||||||
return `KILL QUERY ${id}`;
|
return `KILL QUERY ${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the `KILL <id>` statement that disconnects the session itself.
|
||||||
|
*
|
||||||
|
* `KILL QUERY` only interrupts the statement running at that instant, so an idle
|
||||||
|
* (`Sleep`) session survives it and keeps showing up in the process list. When the
|
||||||
|
* user asks to close a session, only this statement actually removes it (and rolls
|
||||||
|
* back the session's uncommitted transaction, the same as the server-side `KILL`).
|
||||||
|
*/
|
||||||
|
export function buildTerminateSessionSql(id: number): string {
|
||||||
|
if (!Number.isInteger(id) || id <= 0) {
|
||||||
|
throw new Error(`Invalid session id: ${id}`);
|
||||||
|
}
|
||||||
|
return `KILL ${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Clamp a user-entered refresh interval to a safe integer range of seconds. */
|
/** Clamp a user-entered refresh interval to a safe integer range of seconds. */
|
||||||
export function clampInterval(seconds: number): number {
|
export function clampInterval(seconds: number): number {
|
||||||
if (!Number.isFinite(seconds)) return DEFAULT_REFRESH_SECONDS;
|
if (!Number.isFinite(seconds)) return DEFAULT_REFRESH_SECONDS;
|
||||||
|
|||||||
@@ -174,6 +174,27 @@ export function buildKingbasePgCancelQuerySql(pid: number): string {
|
|||||||
return `SELECT pg_cancel_backend(${pid})`;
|
return `SELECT pg_cancel_backend(${pid})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the statement that disconnects the backend instead of only interrupting the
|
||||||
|
* statement it is running right now. `pg_cancel_backend` cancels the current query and
|
||||||
|
* leaves an idle backend alive, so the process list keeps listing it; terminating the
|
||||||
|
* backend is what actually removes the session (rolling back its open transaction).
|
||||||
|
*/
|
||||||
|
export function buildPgTerminateSessionSql(pid: number): string {
|
||||||
|
validateBackendPid(pid);
|
||||||
|
return `SELECT pg_terminate_backend(${pid})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildKingbaseTerminateSessionSql(pid: number): string {
|
||||||
|
validateBackendPid(pid);
|
||||||
|
return `SELECT sys_terminate_backend(${pid})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildKingbasePgTerminateSessionSql(pid: number): string {
|
||||||
|
validateBackendPid(pid);
|
||||||
|
return `SELECT pg_terminate_backend(${pid})`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Return an error when PostgreSQL reports that no running query was canceled. */
|
/** Return an error when PostgreSQL reports that no running query was canceled. */
|
||||||
export function pgCancelQueryResultError(results: QueryResult[]): string | null {
|
export function pgCancelQueryResultError(results: QueryResult[]): string | null {
|
||||||
return backendCancelQueryResultError(results, "pg_cancel_backend");
|
return backendCancelQueryResultError(results, "pg_cancel_backend");
|
||||||
@@ -187,11 +208,24 @@ export function kingbasePgCancelQueryResultError(results: QueryResult[]): string
|
|||||||
return backendCancelQueryResultError(results, "pg_cancel_backend");
|
return backendCancelQueryResultError(results, "pg_cancel_backend");
|
||||||
}
|
}
|
||||||
|
|
||||||
function backendCancelQueryResultError(results: QueryResult[], functionName: string): string | null {
|
/** Return an error when the server reports that no backend was terminated. */
|
||||||
|
export function pgTerminateSessionResultError(results: QueryResult[]): string | null {
|
||||||
|
return backendCancelQueryResultError(results, "pg_terminate_backend", "did not terminate the session");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function kingbaseTerminateSessionResultError(results: QueryResult[]): string | null {
|
||||||
|
return backendCancelQueryResultError(results, "sys_terminate_backend", "did not terminate the session");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function kingbasePgTerminateSessionResultError(results: QueryResult[]): string | null {
|
||||||
|
return backendCancelQueryResultError(results, "pg_terminate_backend", "did not terminate the session");
|
||||||
|
}
|
||||||
|
|
||||||
|
function backendCancelQueryResultError(results: QueryResult[], functionName: string, failure = "did not cancel a running query"): string | null {
|
||||||
const result = results.find((item) => item.execution_error !== true);
|
const result = results.find((item) => item.execution_error !== true);
|
||||||
const value = result?.rows?.[0]?.[0];
|
const value = result?.rows?.[0]?.[0];
|
||||||
if (value === true || value === 1 || String(value).toLowerCase() === "t" || String(value).toLowerCase() === "true") return null;
|
if (value === true || value === 1 || String(value).toLowerCase() === "t" || String(value).toLowerCase() === "true") return null;
|
||||||
return `${functionName} did not cancel a running query`;
|
return `${functionName} ${failure}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Detect the undefined-column failure produced by pre-9.6 pg_stat_activity. */
|
/** Detect the undefined-column failure produced by pre-9.6 pg_stat_activity. */
|
||||||
@@ -213,3 +247,7 @@ export function isKingbaseOwnSessionCatalogCompatibilityError(error: unknown): b
|
|||||||
export function isKingbaseCancelCatalogCompatibilityError(error: unknown): boolean {
|
export function isKingbaseCancelCatalogCompatibilityError(error: unknown): boolean {
|
||||||
return isMissingKingbaseSysFunction(error, ["sys_cancel_backend"]);
|
return isMissingKingbaseSysFunction(error, ["sys_cancel_backend"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isKingbaseTerminateCatalogCompatibilityError(error: unknown): boolean {
|
||||||
|
return isMissingKingbaseSysFunction(error, ["sys_terminate_backend"]);
|
||||||
|
}
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
import type { ConnectionConfig, DatabaseType, QueryResult } from "@/types/database";
|
import type { ConnectionConfig, DatabaseType, QueryResult } from "@/types/database";
|
||||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||||
import { buildCancelQuerySql as buildMysqlCancelQuerySql, mapProcessRows as mapMysqlProcessRows, PROCESS_LIST_SQL as MYSQL_PROCESS_LIST_SQL, supportsProcessList as supportsMysqlProcessList } from "./mysqlProcessList";
|
import { buildCancelQuerySql as buildMysqlCancelQuerySql, buildTerminateSessionSql as buildMysqlTerminateSessionSql, mapProcessRows as mapMysqlProcessRows, PROCESS_LIST_SQL as MYSQL_PROCESS_LIST_SQL, supportsProcessList as supportsMysqlProcessList } from "./mysqlProcessList";
|
||||||
import {
|
import {
|
||||||
buildKingbaseCancelQuerySql,
|
buildKingbaseCancelQuerySql,
|
||||||
buildKingbasePgCancelQuerySql,
|
buildKingbasePgCancelQuerySql,
|
||||||
|
buildKingbasePgTerminateSessionSql,
|
||||||
|
buildKingbaseTerminateSessionSql,
|
||||||
buildPgCancelQuerySql,
|
buildPgCancelQuerySql,
|
||||||
|
buildPgTerminateSessionSql,
|
||||||
isKingbaseOwnSessionCatalogCompatibilityError,
|
isKingbaseOwnSessionCatalogCompatibilityError,
|
||||||
isKingbaseProcessListCatalogCompatibilityError,
|
isKingbaseProcessListCatalogCompatibilityError,
|
||||||
isKingbaseCancelCatalogCompatibilityError,
|
isKingbaseCancelCatalogCompatibilityError,
|
||||||
|
isKingbaseTerminateCatalogCompatibilityError,
|
||||||
isPgProcessListCompatibilityError,
|
isPgProcessListCompatibilityError,
|
||||||
kingbaseCancelQueryResultError,
|
kingbaseCancelQueryResultError,
|
||||||
kingbasePgCancelQueryResultError,
|
kingbasePgCancelQueryResultError,
|
||||||
|
kingbasePgTerminateSessionResultError,
|
||||||
|
kingbaseTerminateSessionResultError,
|
||||||
KINGBASE_OWN_SESSION_SQL,
|
KINGBASE_OWN_SESSION_SQL,
|
||||||
KINGBASE_PG_OWN_SESSION_SQL,
|
KINGBASE_PG_OWN_SESSION_SQL,
|
||||||
KINGBASE_PG_PROCESS_LIST_SQL,
|
KINGBASE_PG_PROCESS_LIST_SQL,
|
||||||
@@ -19,6 +25,7 @@ import {
|
|||||||
OPENGAUSS_OWN_SESSION_SQL,
|
OPENGAUSS_OWN_SESSION_SQL,
|
||||||
OPENGAUSS_PROCESS_LIST_SQL,
|
OPENGAUSS_PROCESS_LIST_SQL,
|
||||||
pgCancelQueryResultError,
|
pgCancelQueryResultError,
|
||||||
|
pgTerminateSessionResultError,
|
||||||
PG_OWN_SESSION_SQL,
|
PG_OWN_SESSION_SQL,
|
||||||
PG_PROCESS_LIST_LEGACY_SQL,
|
PG_PROCESS_LIST_LEGACY_SQL,
|
||||||
PG_PROCESS_LIST_SQL,
|
PG_PROCESS_LIST_SQL,
|
||||||
@@ -79,6 +86,19 @@ export interface ProcessListDriver {
|
|||||||
cancelQueryResultError?(results: QueryResult[]): string | null;
|
cancelQueryResultError?(results: QueryResult[]): string | null;
|
||||||
/** Validate the success value returned by the compatibility cancellation statement. */
|
/** Validate the success value returned by the compatibility cancellation statement. */
|
||||||
fallbackCancelQueryResultError?(results: QueryResult[]): string | null;
|
fallbackCancelQueryResultError?(results: QueryResult[]): string | null;
|
||||||
|
/**
|
||||||
|
* Build the validated statement that disconnects the session. Engines that omit it
|
||||||
|
* only offer query cancellation, because they cannot close a session over the wire.
|
||||||
|
*/
|
||||||
|
buildTerminateSessionSql?(id: number): string;
|
||||||
|
/** Build the compatibility statement used when the primary terminate function is unavailable. */
|
||||||
|
buildFallbackTerminateSessionSql?(id: number): string;
|
||||||
|
/** Restrict terminate fallback attempts to known compatibility failures. */
|
||||||
|
shouldUseFallbackTerminateSessionSql?(error: unknown): boolean;
|
||||||
|
/** Validate any engine-specific success value returned by the terminate statement. */
|
||||||
|
terminateSessionResultError?(results: QueryResult[]): string | null;
|
||||||
|
/** Validate the success value returned by the compatibility terminate statement. */
|
||||||
|
fallbackTerminateSessionResultError?(results: QueryResult[]): string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MYSQL_COLUMNS: ProcessColumn[] = [
|
const MYSQL_COLUMNS: ProcessColumn[] = [
|
||||||
@@ -114,6 +134,7 @@ const MYSQL_DRIVER: ProcessListDriver = {
|
|||||||
// Typed structs carry no index signature; they are plain string-keyed objects at runtime.
|
// Typed structs carry no index signature; they are plain string-keyed objects at runtime.
|
||||||
mapRows: (result) => mapMysqlProcessRows(result) as unknown as ProcessRow[],
|
mapRows: (result) => mapMysqlProcessRows(result) as unknown as ProcessRow[],
|
||||||
buildCancelQuerySql: buildMysqlCancelQuerySql,
|
buildCancelQuerySql: buildMysqlCancelQuerySql,
|
||||||
|
buildTerminateSessionSql: buildMysqlTerminateSessionSql,
|
||||||
};
|
};
|
||||||
|
|
||||||
const POSTGRES_DRIVER: ProcessListDriver = {
|
const POSTGRES_DRIVER: ProcessListDriver = {
|
||||||
@@ -127,6 +148,8 @@ const POSTGRES_DRIVER: ProcessListDriver = {
|
|||||||
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
||||||
buildCancelQuerySql: buildPgCancelQuerySql,
|
buildCancelQuerySql: buildPgCancelQuerySql,
|
||||||
cancelQueryResultError: pgCancelQueryResultError,
|
cancelQueryResultError: pgCancelQueryResultError,
|
||||||
|
buildTerminateSessionSql: buildPgTerminateSessionSql,
|
||||||
|
terminateSessionResultError: pgTerminateSessionResultError,
|
||||||
};
|
};
|
||||||
|
|
||||||
const OPENGAUSS_DRIVER: ProcessListDriver = {
|
const OPENGAUSS_DRIVER: ProcessListDriver = {
|
||||||
@@ -138,6 +161,8 @@ const OPENGAUSS_DRIVER: ProcessListDriver = {
|
|||||||
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
mapRows: (result) => mapPgProcessRows(result) as unknown as ProcessRow[],
|
||||||
buildCancelQuerySql: buildPgCancelQuerySql,
|
buildCancelQuerySql: buildPgCancelQuerySql,
|
||||||
cancelQueryResultError: pgCancelQueryResultError,
|
cancelQueryResultError: pgCancelQueryResultError,
|
||||||
|
buildTerminateSessionSql: buildPgTerminateSessionSql,
|
||||||
|
terminateSessionResultError: pgTerminateSessionResultError,
|
||||||
};
|
};
|
||||||
|
|
||||||
const KINGBASE_DRIVER: ProcessListDriver = {
|
const KINGBASE_DRIVER: ProcessListDriver = {
|
||||||
@@ -156,6 +181,11 @@ const KINGBASE_DRIVER: ProcessListDriver = {
|
|||||||
shouldUseFallbackCancelQuerySql: isKingbaseCancelCatalogCompatibilityError,
|
shouldUseFallbackCancelQuerySql: isKingbaseCancelCatalogCompatibilityError,
|
||||||
cancelQueryResultError: kingbaseCancelQueryResultError,
|
cancelQueryResultError: kingbaseCancelQueryResultError,
|
||||||
fallbackCancelQueryResultError: kingbasePgCancelQueryResultError,
|
fallbackCancelQueryResultError: kingbasePgCancelQueryResultError,
|
||||||
|
buildTerminateSessionSql: buildKingbaseTerminateSessionSql,
|
||||||
|
buildFallbackTerminateSessionSql: buildKingbasePgTerminateSessionSql,
|
||||||
|
shouldUseFallbackTerminateSessionSql: isKingbaseTerminateCatalogCompatibilityError,
|
||||||
|
terminateSessionResultError: kingbaseTerminateSessionResultError,
|
||||||
|
fallbackTerminateSessionResultError: kingbasePgTerminateSessionResultError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Resolve the process-list driver for a connection, or null if unsupported. */
|
/** Resolve the process-list driver for a connection, or null if unsupported. */
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
Reference in new issue
Block a user