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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5699,6 +5699,7 @@ export default withEnglishFallback({
|
||||
clickTableNavigationTargetDescription: "有効にすると、Ctrl/Cmd+クリックでテーブル構造エディタ(DDL)を開きます。無効の場合はテーブルデータビューを開きます。",
|
||||
sqlVariableSyntax: "SQL 変数・プレースホルダー置換",
|
||||
sqlVariableSyntaxDescription: "SQL 実行前に置換する変数・プレースホルダー構文をデータベース種別ごとに選択します。既定ではすべて有効です。",
|
||||
sqlVariableSubstitutionEnabled: "置換を有効化",
|
||||
sqlVariableSyntax_positional: "位置プレースホルダー",
|
||||
sqlVariableSyntax_positionalDescription: "各位置プレースホルダーを順番に置き換える値の入力を求めます。",
|
||||
sqlVariableSyntax_named: "名前付きプレースホルダー",
|
||||
|
||||
@@ -5433,6 +5433,7 @@ export default withEnglishFallback({
|
||||
prefillNewQueryWithSelectDescription: "새 쿼리를 만들 때 활성 테이블 탭이나 사이드바에서 선택한 테이블을 기반으로 편집기에 SELECT * FROM <table>을 미리 채웁니다.",
|
||||
sqlVariableSyntax: "SQL 변수 및 자리표시자 치환",
|
||||
sqlVariableSyntaxDescription: "데이터베이스 유형별로 SQL을 실행하기 전에 DBX가 치환할 변수와 자리표시자 문법을 선택하세요. 모두 기본적으로 활성화됩니다.",
|
||||
sqlVariableSubstitutionEnabled: "치환 사용",
|
||||
sqlVariableSyntax_positional: "위치 자리표시자",
|
||||
sqlVariableSyntax_positionalDescription: "각 위치 자리표시자를 순서대로 치환할 값을 입력하라는 메시지를 표시합니다.",
|
||||
sqlVariableSyntax_named: "이름 있는 자리표시자",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5948,6 +5948,7 @@ export default withEnglishFallback({
|
||||
clickTableNavigationTargetDescription: "开启后,Ctrl/Cmd+点击表名将打开表结构编辑器(DDL);关闭则打开表数据视图。",
|
||||
sqlVariableSyntax: "SQL 变量与占位符替换",
|
||||
sqlVariableSyntaxDescription: "按数据库类型选择执行 SQL 前替换哪些变量与占位符语法,默认全部开启。",
|
||||
sqlVariableSubstitutionEnabled: "启用替换",
|
||||
sqlVariableSyntax_positional: "位置占位符",
|
||||
sqlVariableSyntax_positionalDescription: "按顺序为每个位置占位符提示输入替换值。",
|
||||
sqlVariableSyntax_named: "命名占位符",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user