feat(ui): refresh latest ddl on demand
This commit is contained in:
17 files changed
+186
-11
No files matched your search
@@ -86,6 +86,7 @@ import { tableObjectSourceKind } from "@/lib/table/tableObjectSourceKind";
|
||||
import { tableColumnDefaultDisplayValue } from "@/lib/table/tableColumnDefaultPresentation";
|
||||
import { shouldNavigateFromTableInfoColumnClick } from "@/lib/table/tableInfoColumnNavigation";
|
||||
import { tableInfoTabForDrawerToggle } from "@/lib/table/tableInfoTabPreference";
|
||||
import { loadObjectDdl } from "@/lib/metadata/objectDdlCache";
|
||||
import { loadObjectMetadataFacet } from "@/lib/metadata/objectMetadataCache";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { formatElapsedSeconds } from "@/lib/common/elapsedTime";
|
||||
@@ -10785,6 +10786,9 @@ function clampCellDetailPanelSize(value: number, layout = cellDetailPanelLayout.
|
||||
const showTableInfo = ref(false);
|
||||
const activeTableInfoTab = ref<TableInfoTab>(settingsStore.editorSettings.tableInfoActiveTab);
|
||||
const ddlContent = ref("");
|
||||
const tableInfoColumns = ref<ColumnInfo[]>(props.tableMeta?.columns ?? []);
|
||||
const tableInfoColumnsLoading = ref(false);
|
||||
let tableInfoColumnsRequestGeneration = 0;
|
||||
const ddlPreRef = ref<HTMLPreElement | null>(null);
|
||||
const ddlSearchMatchCount = ref(0);
|
||||
const ddlSearchMatchIndex = ref(0);
|
||||
@@ -10943,6 +10947,14 @@ let constraintsRequestGeneration = 0;
|
||||
// also shown (each constraint appears once; FK navigation stays in that tab).
|
||||
const constraintsForTab = computed(() => constraintsForConstraintsTab(constraints.value, tableMetadataCapabilities.value.foreignKeys));
|
||||
const searchQuery = ref("");
|
||||
const activeTableInfoLoading = computed(() => {
|
||||
if (activeTableInfoTab.value === "ddl") return ddlLoading.value;
|
||||
if (activeTableInfoTab.value === "columns") return tableInfoColumnsLoading.value;
|
||||
if (activeTableInfoTab.value === "indexes") return indexesLoading.value;
|
||||
if (activeTableInfoTab.value === "foreignKeys") return foreignKeysLoading.value;
|
||||
if (activeTableInfoTab.value === "constraints") return constraintsLoading.value;
|
||||
return activeTableInfoTab.value === "triggers" && triggersLoading.value;
|
||||
});
|
||||
const cellDetailPanelLayout = computed(() => settingsStore.editorSettings.cellDetailPanelLayout);
|
||||
const cellDetailPanelIsBottom = computed(() => cellDetailPanelLayout.value === "bottom");
|
||||
|
||||
@@ -11042,7 +11054,7 @@ const tableInfoTabs = computed(() => {
|
||||
id: "columns",
|
||||
label: t("grid.tableInfoColumns"),
|
||||
icon: ListTree,
|
||||
count: props.tableMeta?.columns.length,
|
||||
count: tableInfoColumns.value.length,
|
||||
});
|
||||
}
|
||||
if (canShowTableIndexes.value) {
|
||||
@@ -11116,13 +11128,24 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function fetchDdl() {
|
||||
async function fetchDdl(force = settingsStore.editorSettings.refreshDdlOnOpen) {
|
||||
if (!props.connectionId || !props.tableMeta) return;
|
||||
showTableInfo.value = true;
|
||||
ddlLoading.value = true;
|
||||
try {
|
||||
// Preserve view identity so the backend loads the stored view source instead of synthesizing table DDL.
|
||||
ddlContent.value = await api.getTableDisplayDdl(props.connectionId, props.database || "", props.tableMeta.schema || props.database || "", props.tableMeta.tableName, tableObjectSourceKind(props.tableMeta.tableType), props.tableMeta.catalog);
|
||||
const { ddl } = await loadObjectDdl(
|
||||
{
|
||||
connectionId: props.connectionId,
|
||||
database: props.database || "",
|
||||
schema: props.tableMeta.schema || props.database || "",
|
||||
tableName: props.tableMeta.tableName,
|
||||
objectType: tableObjectSourceKind(props.tableMeta.tableType),
|
||||
catalog: props.tableMeta.catalog,
|
||||
},
|
||||
{ force },
|
||||
);
|
||||
ddlContent.value = ddl;
|
||||
} catch (e: any) {
|
||||
ddlContent.value = `-- Error: ${e}`;
|
||||
} finally {
|
||||
@@ -11130,6 +11153,53 @@ async function fetchDdl() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTableInfoColumns(force = false) {
|
||||
if (!props.connectionId || !props.tableMeta) return;
|
||||
const requestGeneration = ++tableInfoColumnsRequestGeneration;
|
||||
tableInfoColumnsLoading.value = true;
|
||||
try {
|
||||
// Route through the shared metadata facet cache so a grid-side refresh
|
||||
// invalidates/populates the same cache the sidebar and hover views read.
|
||||
const request = {
|
||||
connectionId: props.connectionId,
|
||||
database: props.database || "",
|
||||
schema: props.tableMeta.schema || props.database || "",
|
||||
tableName: props.tableMeta.tableName,
|
||||
objectType: tableObjectSourceKind(props.tableMeta.tableType),
|
||||
catalog: props.tableMeta.catalog,
|
||||
};
|
||||
const { value: columns } = await loadObjectMetadataFacet(request, "columns", () => api.getColumns(request.connectionId, request.database, request.schema, request.tableName, request.catalog), { force });
|
||||
if (requestGeneration !== tableInfoColumnsRequestGeneration) return;
|
||||
tableInfoColumns.value = columns;
|
||||
} catch (error) {
|
||||
if (requestGeneration !== tableInfoColumnsRequestGeneration) return;
|
||||
toast(translateBackendError(t, error), 5000);
|
||||
} finally {
|
||||
if (requestGeneration === tableInfoColumnsRequestGeneration) tableInfoColumnsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshActiveTableInfo() {
|
||||
if (!showTableInfo.value || !props.tableMeta) return;
|
||||
if (canShowTableOwner.value) void fetchTableOwner(true);
|
||||
|
||||
if (activeTableInfoTab.value === "ddl") await fetchDdl(true);
|
||||
else if (activeTableInfoTab.value === "columns") await fetchTableInfoColumns(true);
|
||||
else if (activeTableInfoTab.value === "indexes") {
|
||||
indexesLoaded.value = false;
|
||||
await fetchIndexes();
|
||||
} else if (activeTableInfoTab.value === "foreignKeys") {
|
||||
foreignKeysLoaded.value = false;
|
||||
await fetchForeignKeys();
|
||||
} else if (activeTableInfoTab.value === "constraints") {
|
||||
constraintsLoaded.value = false;
|
||||
await fetchConstraints();
|
||||
} else if (activeTableInfoTab.value === "triggers") {
|
||||
triggersLoaded.value = false;
|
||||
await fetchTriggers();
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchIndexes() {
|
||||
const requestIdentity = currentIndexTableIdentity.value;
|
||||
if (!props.connectionId || !props.tableMeta || !canShowTableIndexes.value || !requestIdentity || indexesLoaded.value || indexesLoading.value) return;
|
||||
@@ -11318,6 +11388,9 @@ async function fetchConstraints() {
|
||||
watch(
|
||||
() => [props.connectionId, props.database, props.tableMeta?.catalog, props.tableMeta?.schema, props.tableMeta?.tableName],
|
||||
() => {
|
||||
tableInfoColumns.value = props.tableMeta?.columns ?? [];
|
||||
tableInfoColumnsLoading.value = false;
|
||||
tableInfoColumnsRequestGeneration += 1;
|
||||
tableOwner.value = null;
|
||||
tableOwnerLoading.value = false;
|
||||
tableOwnerError.value = "";
|
||||
@@ -11350,6 +11423,13 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.tableMeta?.columns,
|
||||
(columns) => {
|
||||
if (!tableInfoColumnsLoading.value) tableInfoColumns.value = columns ?? [];
|
||||
},
|
||||
);
|
||||
|
||||
watch([showTableInfo, canShowTableOwner], ([shown, supported]) => {
|
||||
if (shown && supported && !tableOwnerLoading.value && tableOwner.value === null && !tableOwnerError.value) void fetchTableOwner();
|
||||
});
|
||||
@@ -11793,7 +11873,7 @@ onUnmounted(() => {
|
||||
stopLoadingElapsedTimer();
|
||||
});
|
||||
|
||||
const filteredColumns = computed(() => filterObjectBrowserTableColumns(props.tableMeta?.columns ?? [], searchQuery.value));
|
||||
const filteredColumns = computed(() => filterObjectBrowserTableColumns(tableInfoColumns.value, searchQuery.value));
|
||||
|
||||
const filteredIndexes = computed(() => {
|
||||
if (!searchQuery.value) return indexes.value;
|
||||
@@ -14002,6 +14082,9 @@ function openGridSnapshot() {
|
||||
<X class="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7 shrink-0" :disabled="activeTableInfoLoading" :title="t('structureEditor.refresh')" :aria-label="t('structureEditor.refresh')" @click="refreshActiveTableInfo">
|
||||
<RefreshCw class="h-3.5 w-3.5" :class="{ 'animate-spin': activeTableInfoLoading }" />
|
||||
</Button>
|
||||
<template v-if="activeTableInfoTab === 'ddl'">
|
||||
<span class="w-9 shrink-0 text-center text-[11px] tabular-nums text-muted-foreground">
|
||||
{{ searchQuery ? (ddlSearchMatchCount > 0 ? `${ddlSearchMatchIndex + 1}/${ddlSearchMatchCount}` : "0") : "" }}
|
||||
@@ -14017,7 +14100,10 @@ function openGridSnapshot() {
|
||||
</div>
|
||||
|
||||
<div v-if="activeTableInfoTab === 'columns'" class="flex-1 min-h-0 overflow-auto">
|
||||
<div v-if="searchQuery && filteredColumns.length === 0" class="p-6 text-center text-xs text-muted-foreground">
|
||||
<div v-if="tableInfoColumnsLoading" class="h-full flex items-center justify-center">
|
||||
<Loader2 class="w-4 h-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
<div v-else-if="searchQuery && filteredColumns.length === 0" class="p-6 text-center text-xs text-muted-foreground">
|
||||
{{ t("grid.tableInfoNoResults") }}
|
||||
</div>
|
||||
<table v-else class="w-full text-xs">
|
||||
|
||||
@@ -7,4 +7,15 @@ describe("DataGrid DDL search navigation", () => {
|
||||
it("resets navigation when the raw search query changes", () => {
|
||||
expect(dataGridSource).toMatch(/watch\(\s*\[filteredDdlContent, searchQuery\],/);
|
||||
});
|
||||
|
||||
it("uses the shared DDL cache policy and exposes refresh controls", () => {
|
||||
expect(dataGridSource).toContain('from "@/lib/metadata/objectDdlCache"');
|
||||
expect(dataGridSource).toContain("async function fetchDdl(force = settingsStore.editorSettings.refreshDdlOnOpen)");
|
||||
expect(dataGridSource).toMatch(/loadObjectDdl\([\s\S]*?objectType: tableObjectSourceKind\(props\.tableMeta\.tableType\),[\s\S]*?\{ force \},/);
|
||||
expect(dataGridSource).toMatch(/async function refreshActiveTableInfo\(\)[\s\S]*?fetchDdl\(true\)[\s\S]*?fetchTableInfoColumns\(true\)[\s\S]*?fetchIndexes\(\)[\s\S]*?fetchForeignKeys\(\)[\s\S]*?fetchConstraints\(\)[\s\S]*?fetchTriggers\(\)/);
|
||||
expect(dataGridSource).toContain('@click="refreshActiveTableInfo"');
|
||||
expect(dataGridSource).toMatch(/loadObjectMetadataFacet\(request, "columns"/);
|
||||
expect(dataGridSource).toMatch(/activeTableInfoTab\.value === "constraints"\) return constraintsLoading\.value/);
|
||||
expect(dataGridSource).not.toContain("setTableInfoRefreshDdlOnOpen");
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,8 @@ import { formatSqlForDisplay, type SqlFormatDialect } from "@/lib/sql/sqlFormatt
|
||||
import { loadObjectDdl } from "@/lib/metadata/objectDdlCache";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { HelpTooltip } from "@/components/ui/tooltip";
|
||||
import EditorSearchPanel from "@/components/editor/EditorSearchPanel.vue";
|
||||
import type { EditorView } from "@codemirror/view";
|
||||
import type { DatabaseType, ObjectSourceKind } from "@/types/database";
|
||||
@@ -131,7 +133,7 @@ async function loadDdl(force = false) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads the persisted table DDL when the dialog opens. */
|
||||
/** Loads from the persisted snapshot by default; users can opt into a fresh database query on every open. */
|
||||
watch(
|
||||
() => props.open,
|
||||
async (open) => {
|
||||
@@ -140,7 +142,7 @@ watch(
|
||||
const active = document.activeElement;
|
||||
editorRootToRestoreFocus = active instanceof HTMLElement ? active.closest(".cm-editor") : null;
|
||||
ddlContent.value = "";
|
||||
await loadDdl();
|
||||
await loadDdl(settingsStore.editorSettings.refreshDdlOnOpen);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -269,6 +271,11 @@ function retry() {
|
||||
void loadDdl(true);
|
||||
}
|
||||
|
||||
function setRefreshDdlOnOpen(value: boolean) {
|
||||
settingsStore.updateEditorSettings({ refreshDdlOnOpen: value });
|
||||
if (value) void loadDdl(true);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
emit("update:open", false);
|
||||
}
|
||||
@@ -298,6 +305,15 @@ function onClose() {
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<div class="mr-auto flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Switch id="ddl-refresh-on-open" size="sm" :model-value="settingsStore.editorSettings.refreshDdlOnOpen" @update:model-value="setRefreshDdlOnOpen" />
|
||||
<div class="flex items-center gap-1">
|
||||
<label for="ddl-refresh-on-open" class="cursor-pointer">{{ t("contextMenu.refreshDdlOnOpen") }}</label>
|
||||
<HelpTooltip :label="t('contextMenu.refreshDdlOnOpenHint')" trigger-class="[&_svg]:h-3 [&_svg]:w-3">
|
||||
{{ t("contextMenu.refreshDdlOnOpenHint") }}
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" @click="onClose">{{ t("common.close") }}</Button>
|
||||
<Button variant="outline" :disabled="ddlLoading" :title="t('structureEditor.refresh')" @click="loadDdl(true)">
|
||||
<RefreshCw class="h-4 w-4" />
|
||||
|
||||
@@ -246,6 +246,13 @@ const tableConstraintsLoaded = ref(false);
|
||||
const tableConstraintsForTab = computed(() => constraintsForConstraintsTab(tableConstraints.value, tableMetadataCapabilities.value.foreignKeys));
|
||||
const tableInfoSearchQuery = ref("");
|
||||
const tableInfoDdlPreRef = ref<HTMLPreElement | null>(null);
|
||||
const activeTableInfoLoading = computed(() => {
|
||||
if (tableInfoTab.value === "ddl") return tableDdlLoading.value;
|
||||
if (tableInfoTab.value === "columns") return tableColumnsLoading.value;
|
||||
if (tableInfoTab.value === "indexes") return tableIndexesLoading.value;
|
||||
if (tableInfoTab.value === "foreignKeys") return tableForeignKeysLoading.value;
|
||||
return tableInfoTab.value === "triggers" && tableTriggersLoading.value;
|
||||
});
|
||||
const SIDE_PANEL_MIN_WIDTH = 280;
|
||||
const SIDE_PANEL_MAX_WIDTH = 900;
|
||||
const sidePanelWidth = ref(settingsStore.editorSettings.tableInfoDrawerWidth || 420);
|
||||
@@ -1098,7 +1105,7 @@ function tableMetadataRequest(row: ObjectBrowserRow): ObjectDdlRequest {
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchTableDdl(force = false) {
|
||||
async function fetchTableDdl(force = settingsStore.editorSettings.refreshDdlOnOpen) {
|
||||
const row = sidePanelRow.value;
|
||||
if (!row || (tableDdlLoaded.value && !force)) return;
|
||||
const epoch = sidePanelGuard.capture();
|
||||
@@ -3536,14 +3543,17 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<span class="block truncate">{{ tab.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-2 py-1.5 border-b shrink-0 bg-background">
|
||||
<div class="relative">
|
||||
<div class="flex items-center gap-1 px-2 py-1.5 border-b shrink-0 bg-background">
|
||||
<div class="relative min-w-0 flex-1">
|
||||
<Search class="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" />
|
||||
<input v-model="tableInfoSearchQuery" :placeholder="t('grid.tableInfoSearch')" class="w-full h-7 pl-7 pr-6 text-xs bg-muted/50 rounded border border-border focus:outline-none focus:border-primary/50" @keydown.escape="tableInfoSearchQuery = ''" />
|
||||
<button v-if="tableInfoSearchQuery" class="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" @click="tableInfoSearchQuery = ''">
|
||||
<X class="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7 shrink-0" :disabled="activeTableInfoLoading" :title="t('structureEditor.refresh')" :aria-label="t('structureEditor.refresh')" @click="refreshActiveTableInfo">
|
||||
<RefreshCw class="h-3.5 w-3.5" :class="{ 'animate-spin': activeTableInfoLoading }" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="tableInfoTab === 'columns'" class="flex-1 min-h-0 overflow-auto">
|
||||
<div v-if="tableColumnsLoading" class="h-full flex items-center justify-center">
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const source = readFileSync(new URL("../DdlViewDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("DdlViewDialog freshness", () => {
|
||||
it("uses the persisted refresh preference when the dialog opens", () => {
|
||||
expect(source).toMatch(/watch\(\s*\(\) => props\.open,[\s\S]*?await loadDdl\(settingsStore\.editorSettings\.refreshDdlOnOpen\);/);
|
||||
});
|
||||
|
||||
it("refreshes the current DDL immediately when the preference is enabled", () => {
|
||||
expect(source).toMatch(/function setRefreshDdlOnOpen\(value: boolean\)[\s\S]*?if \(value\) void loadDdl\(true\);/);
|
||||
});
|
||||
|
||||
it("explains how the refresh preference affects cached DDL", () => {
|
||||
expect(source).toContain('import { HelpTooltip } from "@/components/ui/tooltip";');
|
||||
expect(source).toMatch(/<HelpTooltip :label="t\('contextMenu\.refreshDdlOnOpenHint'\)"[\s\S]*?t\("contextMenu\.refreshDdlOnOpenHint"\)[\s\S]*?<\/HelpTooltip>/);
|
||||
});
|
||||
});
|
||||
+6
@@ -47,6 +47,12 @@ describe("ObjectBrowser table metadata refresh", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("shares the DDL refresh preference and exposes an explicit refresh action", () => {
|
||||
expect(source).toContain("async function fetchTableDdl(force = settingsStore.editorSettings.refreshDdlOnOpen)");
|
||||
expect(source).toContain('@click="refreshActiveTableInfo"');
|
||||
expect(source).not.toContain("setTableInfoRefreshDdlOnOpen");
|
||||
});
|
||||
|
||||
it("keeps automatic object reloads free of extra metadata requests", () => {
|
||||
expect(functionBody("reload")).not.toContain("refreshActiveTableInfo");
|
||||
});
|
||||
|
||||
@@ -3094,6 +3094,8 @@ export default {
|
||||
viewDdl: "View DDL",
|
||||
viewDdlLoading: "Loading DDL...",
|
||||
ddlCopied: "DDL copied",
|
||||
refreshDdlOnOpen: "Refresh each time it opens",
|
||||
refreshDdlOnOpenHint: "When enabled, DDL is reloaded from the database each time it opens; when disabled, cached data is preferred",
|
||||
dropObject: "Drop Object",
|
||||
dropEventSuccess: 'Event "{name}" dropped',
|
||||
editObject: "Edit Object",
|
||||
|
||||
@@ -2994,6 +2994,8 @@ export default withEnglishFallback({
|
||||
viewDdl: "Ver DDL",
|
||||
viewDdlLoading: "Cargando DDL...",
|
||||
ddlCopied: "DDL copiado",
|
||||
refreshDdlOnOpen: "Actualizar cada vez que se abra",
|
||||
refreshDdlOnOpenHint: "Si se activa, el DDL se vuelve a cargar de la base de datos cada vez que se abre; si se desactiva, se usan primero los datos en caché",
|
||||
dropObject: "Eliminar objeto",
|
||||
dropView: "Eliminar vista",
|
||||
dropColumn: "Eliminar columna",
|
||||
|
||||
@@ -2992,6 +2992,8 @@ export default withEnglishFallback({
|
||||
viewDdl: "Visualizza DDL",
|
||||
viewDdlLoading: "Caricamento DDL...",
|
||||
ddlCopied: "DDL copiato",
|
||||
refreshDdlOnOpen: "Aggiorna a ogni apertura",
|
||||
refreshDdlOnOpenHint: "Se attivo, il DDL viene ricaricato dal database a ogni apertura; se disattivo, vengono usati prima i dati memorizzati nella cache",
|
||||
dropObject: "Elimina Oggetto",
|
||||
dropView: "Elimina Vista",
|
||||
dropColumn: "Elimina Colonna",
|
||||
|
||||
@@ -3225,6 +3225,8 @@ export default withEnglishFallback({
|
||||
instanceInfo: "インスタンス情報",
|
||||
viewDdlLoading: "DDLを読み込み中...",
|
||||
ddlCopied: "DDLをコピーしました",
|
||||
refreshDdlOnOpen: "開くたびに更新",
|
||||
refreshDdlOnOpenHint: "有効にすると開くたびにデータベースから DDL を再取得し、無効にするとキャッシュを優先して使用します",
|
||||
manageExtension: "拡張機能を管理...",
|
||||
dropExtension: "拡張機能を削除",
|
||||
processList: "プロセス一覧",
|
||||
|
||||
@@ -3113,6 +3113,8 @@ export default withEnglishFallback({
|
||||
eventLoading: "Loading...",
|
||||
eventCancel: "Cancel",
|
||||
eventSave: "Save",
|
||||
refreshDdlOnOpen: "열 때마다 새로 고침",
|
||||
refreshDdlOnOpenHint: "켜면 열 때마다 데이터베이스에서 DDL을 다시 불러오고, 끄면 캐시된 데이터를 우선 사용합니다",
|
||||
},
|
||||
visibleDatabases: {
|
||||
title: "표시할 데이터베이스",
|
||||
|
||||
@@ -2990,6 +2990,8 @@ export default withEnglishFallback({
|
||||
viewDdl: "Ver DDL",
|
||||
viewDdlLoading: "Carregando DDL...",
|
||||
ddlCopied: "DDL copiado",
|
||||
refreshDdlOnOpen: "Atualizar sempre que abrir",
|
||||
refreshDdlOnOpenHint: "Quando ativado, o DDL é recarregado do banco de dados sempre que é aberto; quando desativado, os dados em cache têm prioridade",
|
||||
dropObject: "Remover Objeto",
|
||||
dropView: "Remover Visão",
|
||||
dropColumn: "Remover Coluna",
|
||||
|
||||
@@ -3017,6 +3017,8 @@ export default withEnglishFallback({
|
||||
viewDdl: "查看 DDL",
|
||||
viewDdlLoading: "正在读取 DDL...",
|
||||
ddlCopied: "DDL 已复制",
|
||||
refreshDdlOnOpen: "每次打开时刷新",
|
||||
refreshDdlOnOpenHint: "开启后每次打开都会从数据库重新读取;关闭时优先使用缓存",
|
||||
dropObject: "删除对象",
|
||||
dropView: "删除视图",
|
||||
dropColumn: "删除字段",
|
||||
|
||||
@@ -2991,6 +2991,8 @@ export default withEnglishFallback({
|
||||
viewDdl: "檢視 DDL",
|
||||
viewDdlLoading: "正在讀取 DDL...",
|
||||
ddlCopied: "DDL 已複製",
|
||||
refreshDdlOnOpen: "每次開啟時重新整理",
|
||||
refreshDdlOnOpenHint: "開啟後每次開啟都會從資料庫重新讀取;關閉時優先使用快取",
|
||||
dropObject: "刪除物件",
|
||||
dropView: "刪除檢視",
|
||||
dropColumn: "刪除欄位",
|
||||
|
||||
@@ -5,6 +5,13 @@ import { DEFAULT_EDITOR_SETTINGS, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, enforceR
|
||||
import type { AiConfigItem } from "@/types/ai";
|
||||
|
||||
describe("normalizeEditorSettings", () => {
|
||||
it("keeps automatic DDL refresh disabled unless explicitly enabled", () => {
|
||||
expect(normalizeEditorSettings({}).refreshDdlOnOpen).toBe(false);
|
||||
expect(normalizeEditorSettings({ refreshDdlOnOpen: true }).refreshDdlOnOpen).toBe(true);
|
||||
expect(normalizeEditorSettings({ refreshDdlOnOpen: false }).refreshDdlOnOpen).toBe(false);
|
||||
expect(normalizeEditorSettings({ refreshDdlOnOpen: "true" } as any).refreshDdlOnOpen).toBe(false);
|
||||
});
|
||||
|
||||
it("enables SQL variable substitution by default and only preserves booleans", () => {
|
||||
expect(normalizeEditorSettings({}).sqlVariableSubstitutionEnabled).toBe(true);
|
||||
expect(normalizeEditorSettings({ sqlVariableSubstitutionEnabled: true }).sqlVariableSubstitutionEnabled).toBe(true);
|
||||
|
||||
@@ -596,6 +596,7 @@ export interface EditorSettings {
|
||||
selectFirstCompletionOnOpen: boolean;
|
||||
wordWrap: boolean;
|
||||
tableDdlWordWrap: boolean;
|
||||
refreshDdlOnOpen: boolean;
|
||||
vimModeEnabled: boolean;
|
||||
autoCloseBrackets: boolean;
|
||||
sqlSemanticDiagnosticsMode: SqlSemanticDiagnosticsMode;
|
||||
@@ -817,6 +818,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
selectFirstCompletionOnOpen: true,
|
||||
wordWrap: false,
|
||||
tableDdlWordWrap: true,
|
||||
refreshDdlOnOpen: false,
|
||||
vimModeEnabled: false,
|
||||
autoCloseBrackets: true,
|
||||
sqlSemanticDiagnosticsMode: "auto",
|
||||
@@ -1227,6 +1229,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
||||
selectFirstCompletionOnOpen: typeof settings.selectFirstCompletionOnOpen === "boolean" ? settings.selectFirstCompletionOnOpen : DEFAULT_EDITOR_SETTINGS.selectFirstCompletionOnOpen,
|
||||
wordWrap: settings.wordWrap ?? DEFAULT_EDITOR_SETTINGS.wordWrap,
|
||||
tableDdlWordWrap: typeof settings.tableDdlWordWrap === "boolean" ? settings.tableDdlWordWrap : DEFAULT_EDITOR_SETTINGS.tableDdlWordWrap,
|
||||
refreshDdlOnOpen: typeof settings.refreshDdlOnOpen === "boolean" ? settings.refreshDdlOnOpen : DEFAULT_EDITOR_SETTINGS.refreshDdlOnOpen,
|
||||
vimModeEnabled: typeof settings.vimModeEnabled === "boolean" ? settings.vimModeEnabled : DEFAULT_EDITOR_SETTINGS.vimModeEnabled,
|
||||
autoCloseBrackets: typeof settings.autoCloseBrackets === "boolean" ? settings.autoCloseBrackets : DEFAULT_EDITOR_SETTINGS.autoCloseBrackets,
|
||||
sqlSemanticDiagnosticsMode,
|
||||
@@ -1847,6 +1850,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
if (partial.selectFirstCompletionOnOpen !== undefined) editorSettings.value.selectFirstCompletionOnOpen = partial.selectFirstCompletionOnOpen === true;
|
||||
if (partial.wordWrap !== undefined) editorSettings.value.wordWrap = partial.wordWrap;
|
||||
if (partial.tableDdlWordWrap !== undefined) editorSettings.value.tableDdlWordWrap = partial.tableDdlWordWrap === true;
|
||||
if (partial.refreshDdlOnOpen !== undefined) editorSettings.value.refreshDdlOnOpen = partial.refreshDdlOnOpen === true;
|
||||
if (partial.vimModeEnabled !== undefined) editorSettings.value.vimModeEnabled = partial.vimModeEnabled === true;
|
||||
if (partial.autoCloseBrackets !== undefined) editorSettings.value.autoCloseBrackets = partial.autoCloseBrackets === true;
|
||||
if (partial.sqlSemanticDiagnosticsMode !== undefined || partial.sqlSemanticDiagnosticsEnabled !== undefined) {
|
||||
|
||||
Reference in new issue
Block a user