feat(editor): add SQL substitution master switch

Closes #5759
This commit is contained in:
t8y2 committed 2026-08-14 23:54:54 +00:00
1 parent dc2d3ea1dc
commit fbaf556f3a
18 files changed
+171 -19

No files matched your search

@@ -332,6 +332,7 @@ const editAutoCalculateTotalRows = ref(settingsStore.editorSettings.autoCalculat
const editFlatteningMultiLineText = ref(settingsStore.editorSettings.flatteningMultiLineText);
const editTableColumnTemplateRows = ref<TableColumnTemplateGridRow[]>(tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields));
const editTableColumnTemplateDatabaseType = ref<DatabaseType>(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES[0] ?? "mysql");
const editSqlVariableSubstitutionEnabled = ref(settingsStore.editorSettings.sqlVariableSubstitutionEnabled);
const editSqlVariableSyntaxOverrides = ref<SqlVariableSyntaxOverrides>(normalizeSqlVariableSyntaxOverrides(settingsStore.editorSettings.sqlVariableSyntaxOverrides));
const editSqlVariableSyntaxDatabaseType = ref<DatabaseType>(SQL_VARIABLE_SYNTAX_DATABASE_TYPES[0] ?? "mysql");
@@ -534,6 +535,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
updateDownloadSource: editUpdateDownloadSource.value,
toolbarItems: { ...editToolbarItems.value },
snippets: editSnippets.value,
sqlVariableSubstitutionEnabled: editSqlVariableSubstitutionEnabled.value,
sqlVariableSyntaxOverrides: editSqlVariableSyntaxOverrides.value,
clickTableNavigationTarget: editClickTableNavigationTarget.value,
};
@@ -813,6 +815,7 @@ function syncEditorSettingsDraftFromStore() {
editUpdateDownloadSource.value = settingsStore.editorSettings.updateDownloadSource;
editToolbarItems.value = { ...settingsStore.editorSettings.toolbarItems };
editSnippets.value = settingsStore.editorSettings.snippets.map(editableSnippet);
editSqlVariableSubstitutionEnabled.value = settingsStore.editorSettings.sqlVariableSubstitutionEnabled;
editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(settingsStore.editorSettings.sqlVariableSyntaxOverrides);
editClickTableNavigationTarget.value = settingsStore.editorSettings.clickTableNavigationTarget;
editEditorSettingsBase.value = editorSettingsDraftFromSettings(settingsStore.editorSettings);
@@ -981,6 +984,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editConfirmUnsavedSqlClose.value = DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose;
editSavedSqlOpenTargetMode.value = DEFAULT_EDITOR_SETTINGS.savedSqlOpenTargetMode;
editClickTableNavigationTarget.value = DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
editSqlVariableSubstitutionEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlVariableSubstitutionEnabled;
editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(DEFAULT_EDITOR_SETTINGS.sqlVariableSyntaxOverrides);
} else if (tab === "formatter") {
editSqlFormatter.value = normalizeSqlFormatterSettings(DEFAULT_EDITOR_SETTINGS.sqlFormatter);
@@ -1079,6 +1083,7 @@ function resetAllDefaults() {
editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution;
editConfirmUnsavedSqlClose.value = DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose;
editSavedSqlOpenTargetMode.value = DEFAULT_EDITOR_SETTINGS.savedSqlOpenTargetMode;
editSqlVariableSubstitutionEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlVariableSubstitutionEnabled;
editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(DEFAULT_EDITOR_SETTINGS.sqlVariableSyntaxOverrides);
editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout;
editShowTrayIcon.value = DEFAULT_DESKTOP_SETTINGS.show_tray_icon;
@@ -3938,19 +3943,27 @@ onUnmounted(() => {
{{ t("settings.sqlVariableSyntaxDescription") }}
</p>
</div>
<Select v-model="editSqlVariableSyntaxDatabaseType">
<SelectTrigger class="h-8 w-44 px-2 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent class="max-h-72">
<SelectItem v-for="dbType in SQL_VARIABLE_SYNTAX_DATABASE_TYPES" :key="dbType" :value="dbType">
{{ dbType }}
</SelectItem>
</SelectContent>
</Select>
<div class="flex flex-wrap items-center justify-end gap-3">
<div class="flex items-center gap-2">
<Label for="sql-variable-substitution-enabled" class="text-xs">
{{ t("settings.sqlVariableSubstitutionEnabled") }}
</Label>
<Switch id="sql-variable-substitution-enabled" v-model="editSqlVariableSubstitutionEnabled" />
</div>
<Select v-model="editSqlVariableSyntaxDatabaseType" :disabled="!editSqlVariableSubstitutionEnabled">
<SelectTrigger class="h-8 w-44 px-2 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent class="max-h-72">
<SelectItem v-for="dbType in SQL_VARIABLE_SYNTAX_DATABASE_TYPES" :key="dbType" :value="dbType">
{{ dbType }}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div class="grid gap-3 md:grid-cols-2">
<div v-for="key in SQL_VARIABLE_SYNTAX_KEYS" :key="key" class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div v-for="key in SQL_VARIABLE_SYNTAX_KEYS" :key="key" class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2" :class="{ 'opacity-50': !editSqlVariableSubstitutionEnabled }">
<div class="min-w-0 space-y-1">
<Label :for="`sql-var-syntax-${key}`" class="flex items-center gap-1.5">
<span class="font-mono text-xs text-primary">{{ SQL_VARIABLE_SYNTAX_TOKENS[key] }}</span>
@@ -3960,7 +3973,7 @@ onUnmounted(() => {
{{ t(`settings.sqlVariableSyntax_${key}Description`) }}
</p>
</div>
<Switch :id="`sql-var-syntax-${key}`" :model-value="sqlVariableSyntaxToggle(key)" class="mt-0.5 shrink-0" @update:model-value="(value) => setSqlVariableSyntaxToggle(key, value as boolean)" />
<Switch :id="`sql-var-syntax-${key}`" :model-value="sqlVariableSyntaxToggle(key)" :disabled="!editSqlVariableSubstitutionEnabled" class="mt-0.5 shrink-0" @update:model-value="(value) => setSqlVariableSyntaxToggle(key, value as boolean)" />
</div>
</div>
</div>
@@ -220,7 +220,7 @@ const connectionStore = useConnectionStore();
const settingsStore = useSettingsStore();
function sqlStatementParameterOptions() {
const toggles = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, props.databaseType);
const toggles = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, props.databaseType, settingsStore.editorSettings.sqlVariableSubstitutionEnabled);
return { databaseType: props.databaseType, enabledSyntaxes: enabledSqlParameterSyntaxes(toggles) };
}
const { isDark, themePalette } = useTheme();
@@ -128,6 +128,33 @@ describe("useSqlExecution", () => {
setActivePinia(createPinia());
});
it("sends every placeholder syntax and @set unchanged when substitution is disabled", async () => {
const sql = ["@set tenant = 42;", "SELECT ?, :named, ${shell}, #{mybatis}, @sqlserver, @tenant;"].join("\n");
const activeTab = ref<QueryTab | undefined>({ ...queryTab("app"), sql });
const activeConnection = ref<ConnectionConfig | undefined>(connection("mysql"));
const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result");
const queryStore = useQueryStore();
const settingsStore = useSettingsStore();
const executeCurrentSql = vi.spyOn(queryStore, "executeCurrentSql").mockImplementation(async () => {
if (activeTab.value) activeTab.value.result = { columns: ["ok"], rows: [[1]], affected_rows: 0, execution_time_ms: 1 };
});
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
settingsStore.editorSettings.sqlVariableSubstitutionEnabled = false;
const execution = useSqlExecution({
activeTab: computed(() => activeTab.value),
activeConnection: computed(() => activeConnection.value),
executableSql: computed(() => sql),
activeOutputView,
});
await execution.tryExecute();
expect(execution.showSqlParameterDialog.value).toBe(false);
expect(execution.sqlParameterNames.value).toEqual([]);
expect(executeCurrentSql).toHaveBeenCalledWith(sql, {});
});
it("passes the selected statement's editor offset to the query store", async () => {
const fullSql = "SELECT * FROM users;\nSELECT * FROM users;";
const selectionFrom = fullSql.lastIndexOf("SELECT");
@@ -139,7 +139,7 @@ export function useSqlExecution(deps: {
let pendingSqlParameterContinuation: ((sql: string, sourceOffset?: number) => Promise<void> | void) | undefined;
async function resolvedExecutableSql(source?: SqlExecutionOverride): Promise<{ sql: string; sourceOffset?: number }> {
const atSetEnabled = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, deps.activeConnection.value?.db_type).atSet;
const atSetEnabled = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, deps.activeConnection.value?.db_type, settingsStore.editorSettings.sqlVariableSubstitutionEnabled).atSet;
const expand = (sql: string) => (atSetEnabled ? expandSqlVariables(sql).sql : sql);
if (typeof source === "string") return { sql: expand(source) };
@@ -228,7 +228,7 @@ export function useSqlExecution(deps: {
function prepareSqlParameterDialog(sql: string, sourceOffset?: number, options: SqlExecutionOptions = {}, continuation?: (sql: string, sourceOffset?: number) => Promise<void> | void): boolean {
const databaseType = deps.activeConnection.value?.db_type;
const toggles = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, databaseType);
const toggles = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, databaseType, settingsStore.editorSettings.sqlVariableSubstitutionEnabled);
const enabledSyntaxes = enabledSqlParameterSyntaxes(toggles);
const parameters = extractSqlParameterDescriptors(sql, { databaseType, enabledSyntaxes });
if (!parameters.length) return false;
+1
View File
@@ -5962,6 +5962,7 @@ export default {
clickTableNavigationTargetDescription: "When enabled, Ctrl/Cmd+clicking a table name opens the table structure editor (DDL). When disabled, it opens the table data view.",
sqlVariableSyntax: "SQL variable & placeholder substitution",
sqlVariableSyntaxDescription: "Choose which variable and placeholder syntaxes DBX substitutes before running SQL, per database type. All are enabled by default.",
sqlVariableSubstitutionEnabled: "Enabled",
sqlVariableSyntax_positional: "Positional placeholder",
sqlVariableSyntax_positionalDescription: "Prompt for a value to substitute each positional placeholder in order.",
sqlVariableSyntax_named: "Named placeholder",
+1
View File
@@ -5677,6 +5677,7 @@ export default withEnglishFallback({
clickTableNavigationTargetDescription: "Cuando está activado, Ctrl/Cmd+clic en el nombre de la tabla abre el editor de estructura (DDL). Cuando está desactivado, abre la vista de datos.",
sqlVariableSyntax: "Sustitución de variables y marcadores SQL",
sqlVariableSyntaxDescription: "Elige qué sintaxis de variables y marcadores sustituye DBX antes de ejecutar SQL, por tipo de base de datos. Todas están habilitadas de forma predeterminada.",
sqlVariableSubstitutionEnabled: "Activada",
sqlVariableSyntax_positional: "Marcador posicional",
sqlVariableSyntax_positionalDescription: "Solicita un valor para sustituir cada marcador posicional en orden.",
sqlVariableSyntax_named: "Marcador con nombre",
+1
View File
@@ -5677,6 +5677,7 @@ export default withEnglishFallback({
clickTableNavigationTargetDescription: "Quando attivato, Ctrl/Cmd+clic sul nome della tabella apre l'editor di struttura (DDL). Quando disattivato, apre la vista dati.",
sqlVariableSyntax: "Sostituzione di variabili e segnaposto SQL",
sqlVariableSyntaxDescription: "Scegli quali sintassi di variabili e segnaposto DBX sostituisce prima di eseguire SQL, per tipo di database. Tutte abilitate per impostazione predefinita.",
sqlVariableSubstitutionEnabled: "Attiva",
sqlVariableSyntax_positional: "Segnaposto posizionale",
sqlVariableSyntax_positionalDescription: "Richiede un valore per sostituire ogni segnaposto posizionale in ordine.",
sqlVariableSyntax_named: "Segnaposto con nome",
+1
View File
@@ -5699,6 +5699,7 @@ export default withEnglishFallback({
clickTableNavigationTargetDescription: "有効にすると、Ctrl/Cmd+クリックでテーブル構造エディタ(DDL)を開きます。無効の場合はテーブルデータビューを開きます。",
sqlVariableSyntax: "SQL 変数・プレースホルダー置換",
sqlVariableSyntaxDescription: "SQL 実行前に置換する変数・プレースホルダー構文をデータベース種別ごとに選択します。既定ではすべて有効です。",
sqlVariableSubstitutionEnabled: "置換を有効化",
sqlVariableSyntax_positional: "位置プレースホルダー",
sqlVariableSyntax_positionalDescription: "各位置プレースホルダーを順番に置き換える値の入力を求めます。",
sqlVariableSyntax_named: "名前付きプレースホルダー",
+1
View File
@@ -5433,6 +5433,7 @@ export default withEnglishFallback({
prefillNewQueryWithSelectDescription: "새 쿼리를 만들 때 활성 테이블 탭이나 사이드바에서 선택한 테이블을 기반으로 편집기에 SELECT * FROM <table>을 미리 채웁니다.",
sqlVariableSyntax: "SQL 변수 및 자리표시자 치환",
sqlVariableSyntaxDescription: "데이터베이스 유형별로 SQL을 실행하기 전에 DBX가 치환할 변수와 자리표시자 문법을 선택하세요. 모두 기본적으로 활성화됩니다.",
sqlVariableSubstitutionEnabled: "치환 사용",
sqlVariableSyntax_positional: "위치 자리표시자",
sqlVariableSyntax_positionalDescription: "각 위치 자리표시자를 순서대로 치환할 값을 입력하라는 메시지를 표시합니다.",
sqlVariableSyntax_named: "이름 있는 자리표시자",
+1
View File
@@ -5679,6 +5679,7 @@ export default withEnglishFallback({
clickTableNavigationTargetDescription: "Quando ativado, Ctrl/Cmd+clicar no nome da tabela abre o editor de estrutura (DDL). Quando desativado, abre a visualização de dados.",
sqlVariableSyntax: "Substituição de variáveis e espaços reservados SQL",
sqlVariableSyntaxDescription: "Escolha quais sintaxes de variáveis e espaços reservados o DBX substitui antes de executar SQL, por tipo de banco de dados. Todas ativadas por padrão.",
sqlVariableSubstitutionEnabled: "Ativada",
sqlVariableSyntax_positional: "Espaço reservado posicional",
sqlVariableSyntax_positionalDescription: "Solicita um valor para substituir cada espaço reservado posicional em ordem.",
sqlVariableSyntax_named: "Espaço reservado nomeado",
+1
View File
@@ -5948,6 +5948,7 @@ export default withEnglishFallback({
clickTableNavigationTargetDescription: "开启后,Ctrl/Cmd+点击表名将打开表结构编辑器(DDL);关闭则打开表数据视图。",
sqlVariableSyntax: "SQL 变量与占位符替换",
sqlVariableSyntaxDescription: "按数据库类型选择执行 SQL 前替换哪些变量与占位符语法,默认全部开启。",
sqlVariableSubstitutionEnabled: "启用替换",
sqlVariableSyntax_positional: "位置占位符",
sqlVariableSyntax_positionalDescription: "按顺序为每个位置占位符提示输入替换值。",
sqlVariableSyntax_named: "命名占位符",
+1
View File
@@ -5013,6 +5013,7 @@ export default withEnglishFallback({
clickTableNavigationTargetDescription: "開啟後,Ctrl/Cmd+點擊表名將開啟表結構編輯器(DDL);關閉則開啟表資料檢視。",
sqlVariableSyntax: "SQL 變數與佔位符替換",
sqlVariableSyntaxDescription: "依資料庫類型選擇執行 SQL 前替換哪些變數與佔位符語法,預設全部開啟。",
sqlVariableSubstitutionEnabled: "啟用替換",
sqlVariableSyntax_positional: "位置佔位符",
sqlVariableSyntax_positionalDescription: "依序為每個位置佔位符提示輸入替換值。",
sqlVariableSyntax_named: "具名佔位符",
@@ -26,6 +26,33 @@ describe("resolveSqlVariableSyntaxToggles", () => {
atSet: false,
});
});
it("disables every syntax when substitution is globally disabled", () => {
expect(resolveSqlVariableSyntaxToggles({ mysql: { shell: false } }, "mysql", false)).toEqual({
positional: false,
named: false,
shell: false,
mybatis: false,
sqlserver: false,
atSet: false,
});
});
it("restores per-database overrides when substitution is re-enabled", () => {
const overrides = { mysql: { shell: false as const, atSet: false as const } };
resolveSqlVariableSyntaxToggles(overrides, "mysql", false);
expect(resolveSqlVariableSyntaxToggles(overrides, "mysql", true)).toEqual({
positional: true,
named: true,
shell: false,
mybatis: true,
sqlserver: true,
atSet: false,
});
expect(overrides).toEqual({ mysql: { shell: false, atSet: false } });
});
});
describe("enabledSqlParameterSyntaxes", () => {
@@ -25,6 +25,7 @@ function makeSettings(overrides: Partial<EditorSettings> = {}): EditorSettings {
confirmUnsavedSqlClose: true,
savedSqlOpenTargetMode: "saved",
objectBrowserViewMode: "list",
sqlVariableSubstitutionEnabled: true,
sqlVariableSyntaxOverrides: {},
tabLayout: "scroll",
...overrides,
@@ -67,9 +68,34 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
it("includes completionTriggerMode", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("completionTriggerMode");
});
it("includes the SQL variable substitution master switch", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("sqlVariableSubstitutionEnabled");
});
});
describe("editorSettingsDraftFromSettings", () => {
it("toggles substitution without discarding per-database overrides", () => {
const base = editorSettingsDraftFromSettings(
makeSettings({
sqlVariableSubstitutionEnabled: true,
sqlVariableSyntaxOverrides: { mysql: { shell: false } },
}),
);
const draft = editorSettingsDraftFromSettings(
makeSettings({
sqlVariableSubstitutionEnabled: true,
sqlVariableSyntaxOverrides: { mysql: { shell: false } },
}),
);
draft.sqlVariableSubstitutionEnabled = false;
expect(editorSettingsPatchFromDraft(draft, base)).toEqual({ sqlVariableSubstitutionEnabled: false });
expect(draft.sqlVariableSyntaxOverrides).toEqual({ mysql: { shell: false } });
expect(base.sqlVariableSyntaxOverrides).toEqual({ mysql: { shell: false } });
});
it("does not include persisted global timeout values in editor drafts", () => {
const settings = makeSettings({ globalConnectTimeoutSecs: 17, globalQueryTimeoutSecs: 43 });
const draft = editorSettingsDraftFromSettings(settings);
@@ -73,6 +73,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"updateDownloadSource",
"toolbarItems",
"snippets",
"sqlVariableSubstitutionEnabled",
"sqlVariableSyntaxOverrides",
"continueOnErrorOnBatch",
"clickTableNavigationTarget",
+15 -4
View File
@@ -53,11 +53,22 @@ export type SqlVariableSyntaxOverrides = Partial<Record<DatabaseType, Partial<Sq
export const SQL_VARIABLE_SYNTAX_DATABASE_TYPES: DatabaseType[] = manifestDatabaseTypes();
/**
* Resolve the effective toggles for a database type. Any syntax not explicitly
* disabled in `overrides` is enabled. A pure function safe to call on every
* execution.
* Resolve the effective toggles for a database type. The global gate disables
* every syntax without modifying the saved overrides. Otherwise, any syntax not
* explicitly disabled in `overrides` is enabled. A pure function safe to call
* on every execution.
*/
export function resolveSqlVariableSyntaxToggles(overrides: SqlVariableSyntaxOverrides | undefined, dbType: DatabaseType | undefined): SqlVariableSyntaxToggles {
export function resolveSqlVariableSyntaxToggles(overrides: SqlVariableSyntaxOverrides | undefined, dbType: DatabaseType | undefined, enabled = true): SqlVariableSyntaxToggles {
if (!enabled) {
return {
positional: false,
named: false,
shell: false,
mybatis: false,
sqlserver: false,
atSet: false,
};
}
const partial = dbType ? overrides?.[dbType] : undefined;
return {
positional: partial?.positional ?? true,
+35
View File
@@ -5,6 +5,14 @@ import { DEFAULT_EDITOR_SETTINGS, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, enforceR
import type { AiConfigItem } from "@/types/ai";
describe("normalizeEditorSettings", () => {
it("enables SQL variable substitution by default and only preserves booleans", () => {
expect(normalizeEditorSettings({}).sqlVariableSubstitutionEnabled).toBe(true);
expect(normalizeEditorSettings({ sqlVariableSubstitutionEnabled: true }).sqlVariableSubstitutionEnabled).toBe(true);
expect(normalizeEditorSettings({ sqlVariableSubstitutionEnabled: false }).sqlVariableSubstitutionEnabled).toBe(false);
expect(normalizeEditorSettings({ sqlVariableSubstitutionEnabled: "false" } as any).sqlVariableSubstitutionEnabled).toBe(true);
expect(normalizeEditorSettings({ sqlVariableSubstitutionEnabled: null } as any).sqlVariableSubstitutionEnabled).toBe(true);
});
it("keeps data type colors disabled by default and preserves an explicit opt-in", () => {
expect(normalizeEditorSettings({}).colorizeDataGridCellTypes).toBe(false);
expect(normalizeEditorSettings({ colorizeDataGridCellTypes: true }).colorizeDataGridCellTypes).toBe(true);
@@ -628,6 +636,33 @@ describe("settingsStore persisted settings initialization", () => {
expect(saveEditorSettings).toHaveBeenCalledWith(expect.objectContaining({ fontSize: 17, theme: "xcode-dark", appLayout: "separated" }));
});
it("loads and persists the substitution switch without discarding syntax overrides", async () => {
const loadEditorSettings = vi.fn().mockResolvedValue({
sqlVariableSubstitutionEnabled: false,
sqlVariableSyntaxOverrides: { mysql: { shell: false } },
});
const saveEditorSettings = vi.fn().mockResolvedValue(undefined);
vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings }));
const { useSettingsStore } = await import("@/stores/settingsStore");
const store = useSettingsStore();
await store.initEditorSettings();
expect(store.editorSettings.sqlVariableSubstitutionEnabled).toBe(false);
expect(store.editorSettings.sqlVariableSyntaxOverrides).toEqual({ mysql: { shell: false } });
await store.updateEditorSettingsAndPersist({ sqlVariableSubstitutionEnabled: true });
expect(store.editorSettings.sqlVariableSubstitutionEnabled).toBe(true);
expect(store.editorSettings.sqlVariableSyntaxOverrides).toEqual({ mysql: { shell: false } });
expect(saveEditorSettings).toHaveBeenCalledWith(
expect.objectContaining({
sqlVariableSubstitutionEnabled: true,
sqlVariableSyntaxOverrides: { mysql: { shell: false } },
}),
);
});
it("shares concurrent initialization and applies startup changes after saved settings load", async () => {
let resolveLoad!: (value: unknown) => void;
const loadEditorSettings = vi.fn(
+4
View File
@@ -602,6 +602,7 @@ export interface EditorSettings {
toolbarItems: ToolbarItems;
objectBrowserShowCheckbox: boolean;
objectBrowserViewMode: "list" | "grid";
sqlVariableSubstitutionEnabled: boolean;
sqlVariableSyntaxOverrides: SqlVariableSyntaxOverrides;
continueOnErrorOnBatch: boolean;
clickTableNavigationTarget: ClickTableNavigationTarget;
@@ -799,6 +800,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
toolbarItems: { ...DEFAULT_TOOLBAR_ITEMS },
objectBrowserShowCheckbox: false,
objectBrowserViewMode: "list",
sqlVariableSubstitutionEnabled: true,
sqlVariableSyntaxOverrides: {},
continueOnErrorOnBatch: false,
clickTableNavigationTarget: "data",
@@ -1179,6 +1181,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
toolbarItems: normalizeToolbarItems(settings.toolbarItems),
objectBrowserShowCheckbox: typeof settings.objectBrowserShowCheckbox === "boolean" ? settings.objectBrowserShowCheckbox : DEFAULT_EDITOR_SETTINGS.objectBrowserShowCheckbox,
objectBrowserViewMode: settings.objectBrowserViewMode === "grid" ? "grid" : DEFAULT_EDITOR_SETTINGS.objectBrowserViewMode,
sqlVariableSubstitutionEnabled: typeof settings.sqlVariableSubstitutionEnabled === "boolean" ? settings.sqlVariableSubstitutionEnabled : DEFAULT_EDITOR_SETTINGS.sqlVariableSubstitutionEnabled,
sqlVariableSyntaxOverrides: normalizeSqlVariableSyntaxOverrides(settings.sqlVariableSyntaxOverrides),
continueOnErrorOnBatch: settings.continueOnErrorOnBatch === true,
clickTableNavigationTarget: normalizeClickTableNavigationTarget(settings.clickTableNavigationTarget),
@@ -1721,6 +1724,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.toolbarItems !== undefined) editorSettings.value.toolbarItems = normalizeToolbarItems(partial.toolbarItems);
if (partial.objectBrowserShowCheckbox !== undefined) editorSettings.value.objectBrowserShowCheckbox = partial.objectBrowserShowCheckbox === true;
if (partial.objectBrowserViewMode !== undefined) editorSettings.value.objectBrowserViewMode = partial.objectBrowserViewMode === "grid" ? "grid" : "list";
if (partial.sqlVariableSubstitutionEnabled !== undefined) editorSettings.value.sqlVariableSubstitutionEnabled = partial.sqlVariableSubstitutionEnabled === true;
if (partial.sqlVariableSyntaxOverrides !== undefined) editorSettings.value.sqlVariableSyntaxOverrides = normalizeSqlVariableSyntaxOverrides(partial.sqlVariableSyntaxOverrides);
if (partial.continueOnErrorOnBatch !== undefined) editorSettings.value.continueOnErrorOnBatch = partial.continueOnErrorOnBatch === true;
if (partial.clickTableNavigationTarget !== undefined) editorSettings.value.clickTableNavigationTarget = normalizeClickTableNavigationTarget(partial.clickTableNavigationTarget);